You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/chalk/ui/kis_selection_manager.cc

1644 lines
47 KiB

/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <tqobject.h>
#include <tqapplication.h>
#include <tqclipboard.h>
#include <tqcolor.h>
#include <tqcursor.h>
#include <kdebug.h>
#include <kaction.h>
#include <klocale.h>
#include <kstdaction.h>
#include <KoDocument.h>
#include <KoMainWindow.h>
#include <KoQueryTrader.h>
#include "kis_cursor.h"
#include "kis_part_layer.h"
#include "kis_adjustment_layer.h"
#include "kis_clipboard.h"
#include "kis_types.h"
#include "kis_view.h"
#include "kis_doc.h"
#include "kis_image.h"
#include "kis_selection.h"
#include "kis_selection_manager.h"
#include "kis_painter.h"
#include "kis_iterators_pixel.h"
#include "kis_iteratorpixeltrait.h"
#include "kis_layer.h"
#include "kis_group_layer.h"
#include "kis_paint_layer.h"
#include "kis_paint_device.h"
#include "kis_channelinfo.h"
#include "kis_dlg_apply_profile.h"
#include "kis_config.h"
#include "kis_debug_areas.h"
#include "kis_transaction.h"
#include "kis_undo_adapter.h"
#include "kis_selected_transaction.h"
#include "kis_convolution_painter.h"
#include "kis_integer_maths.h"
#include "kis_fill_painter.h"
#include "kis_canvas.h"
KisSelectionManager::KisSelectionManager(KisView * parent, KisDoc * doc)
: m_parent(parent),
m_doc(doc),
m_copy(0),
m_cut(0),
m_paste(0),
m_pasteNew(0),
m_cutToNewLayer(0),
m_selectAll(0),
m_deselect(0),
m_clear(0),
m_reselect(0),
m_invert(0),
m_toNewLayer(0),
m_feather(0),
m_border(0),
m_expand(0),
m_smooth(0),
m_contract(0),
m_similar(0),
m_transform(0),
m_load(0),
m_save(0),
m_fillForegroundColor(0),
m_fillBackgroundColor(0),
m_fillPattern(0)
{
m_pluginActions.setAutoDelete(true);
m_clipboard = KisClipboard::instance();
}
KisSelectionManager::~KisSelectionManager()
{
m_pluginActions.clear();
}
void KisSelectionManager::setup(KActionCollection * collection)
{
// XXX: setup shortcuts!
m_cut = KStdAction::cut(this,
TQT_SLOT(cut()),
collection,
"cut");
m_copy = KStdAction::copy(this,
TQT_SLOT(copy()),
collection,
"copy");
m_paste = KStdAction::paste(this,
TQT_SLOT(paste()),
collection,
"paste");
m_pasteNew = new KAction(i18n("Paste into &New Image"),
0, 0,
this, TQT_SLOT(pasteNew()),
collection,
"paste_new");
m_selectAll = KStdAction::selectAll(this,
TQT_SLOT(selectAll()),
collection,
"select_all");
m_deselect = KStdAction::deselect(this,
TQT_SLOT(deselect()),
collection,
"deselect");
m_clear = KStdAction::clear(this,
TQT_SLOT(clear()),
collection,
"clear");
m_reselect = new KAction(i18n("&Reselect"),
0, "Ctrl+Shift+D",
this, TQT_SLOT(reselect()),
collection, "reselect");
m_invert = new KAction(i18n("&Invert"),
0, "Ctrl+I",
this, TQT_SLOT(invert()),
collection, "invert");
m_toNewLayer = new KAction(i18n("Copy Selection to New Layer"),
0, "Ctrl+J",
this, TQT_SLOT(copySelectionToNewLayer()),
collection, "copy_selection_to_new_layer");
m_cutToNewLayer = new KAction(i18n("Cut Selection to New Layer"),
0, "Ctrl+Shift+J",
this, TQT_SLOT(cutToNewLayer()),
collection, "cut_selection_to_new_layer");
m_feather = new KAction(i18n("Feather"),
0, "Ctrl+Alt+D",
this, TQT_SLOT(feather()),
collection, "feather");
m_fillForegroundColor = new KAction(i18n("Fill with Foreground Color"),
"Alt+backspace", this,
TQT_SLOT(fillForegroundColor()),
collection,
"fill_selection_foreground_color");
m_fillBackgroundColor = new KAction(i18n("Fill with Background Color"),
"backspace", this,
TQT_SLOT(fillBackgroundColor()),
collection,
"fill_selection_background_color");
m_fillPattern = new KAction(i18n("Fill with Pattern"),
0, this,
TQT_SLOT(fillPattern()),
collection,
"fill_selection_pattern");
m_toggleDisplaySelection = new KToggleAction(i18n("Display Selection"), "Ctrl+h", this, TQT_SLOT(toggleDisplaySelection()), collection, "toggle_display_selection");
m_toggleDisplaySelection->setCheckedState(KGuiItem(i18n("Hide Selection")));
m_toggleDisplaySelection->setChecked(true);
m_border =
new KAction(i18n("Border..."),
0, 0,
this, TQT_SLOT(border()),
collection, "border");
m_expand =
new KAction(i18n("Expand..."),
0, 0,
this, TQT_SLOT(expand()),
collection, "expand");
m_smooth =
new KAction(i18n("Smooth..."),
0, 0,
this, TQT_SLOT(smooth()),
collection, "smooth");
m_contract =
new KAction(i18n("Contract..."),
0, 0,
this, TQT_SLOT(contract()),
collection, "contract");
m_similar =
new KAction(i18n("Similar"),
0, 0,
this, TQT_SLOT(similar()),
collection, "similar");
m_transform
= new KAction(i18n("Transform..."),
0, 0,
this, TQT_SLOT(transform()),
collection, "transform_selection");
// m_load
// = new KAction(i18n("Load..."),
// 0, 0,
// this, TQT_SLOT(load()),
// collection, "load_selection");
//
//
// m_save
// = new KAction(i18n("Save As..."),
// 0, 0,
// this, TQT_SLOT(save()),
// collection, "save_selection");
TQClipboard *cb = TQApplication::clipboard();
connect(cb, TQT_SIGNAL(dataChanged()), TQT_SLOT(clipboardDataChanged()));
}
void KisSelectionManager::clipboardDataChanged()
{
updateGUI();
}
void KisSelectionManager::addSelectionAction(KAction * action)
{
m_pluginActions.append(action);
}
void KisSelectionManager::updateGUI()
{
Q_ASSERT(m_parent);
Q_ASSERT(m_clipboard);
if (m_parent == 0) {
// "Eek, no parent!
return;
}
if (m_clipboard == 0) {
// Eek, no clipboard!
return;
}
KisImageSP img = m_parent->currentImg();
KisLayerSP l = 0;
KisPaintDeviceSP dev = 0;
bool enable = false;
if (img && img->activeDevice() && img->activeLayer()) {
l = img->activeLayer();
dev = img->activeDevice();
KisPartLayer * partLayer = dynamic_cast<KisPartLayer*>(l.data());
KisAdjustmentLayer * adjLayer = dynamic_cast<KisAdjustmentLayer*>(l.data());
enable = l && dev&& dev->hasSelection() && !l->locked() && l->visible() && (partLayer==0);
if(dev && !adjLayer)
m_reselect->setEnabled( dev->selectionDeselected() );
if (adjLayer) // There's no reselect for adjustment layers
m_reselect->setEnabled(false);
}
m_cut->setEnabled(enable);
m_cutToNewLayer->setEnabled(enable);
m_selectAll->setEnabled(img != 0);
m_deselect->setEnabled(enable);
m_clear->setEnabled(enable);
m_fillForegroundColor->setEnabled(enable);
m_fillBackgroundColor->setEnabled(enable);
m_fillPattern->setEnabled(enable);
m_invert->setEnabled(enable);
m_feather->setEnabled(enable);
m_border->setEnabled(enable);
m_expand->setEnabled(enable);
m_smooth->setEnabled(enable);
m_contract->setEnabled(enable);
m_similar->setEnabled(enable);
m_transform->setEnabled(enable);
// m_load->setEnabled(enable);
// m_save->setEnabled(enable);
KAction * a;
for (a = m_pluginActions.first(); a; a = m_pluginActions.next()) {
a->setEnabled(img != 0);
}
// You can copy from locked layers and paste the clip into a new layer, even when
// the current layer is locked.
enable = false;
if (img && l && dev) {
enable = dev->hasSelection() && l->visible();
}
m_copy->setEnabled(enable);
m_paste->setEnabled(img != 0 && m_clipboard->hasClip());
m_pasteNew->setEnabled(img != 0 && m_clipboard->hasClip());
m_toNewLayer->setEnabled(enable);
m_parent->updateStatusBarSelectionLabel();
}
void KisSelectionManager::imgSelectionChanged(KisImageSP img)
{
if (img == m_parent->currentImg()) {
updateGUI();
}
}
void KisSelectionManager::cut()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
if (!dev->hasSelection()) return;
copy();
KisSelectedTransaction *t = 0;
if (img->undo()) {
t = new KisSelectedTransaction(i18n("Cut"), dev);
Q_CHECK_PTR(t);
}
dev->clearSelection();
dev->deselect();
dev->emitSelectionChanged();
if (img->undo()) {
img->undoAdapter()->addCommand(t);
}
}
void KisSelectionManager::copy()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
if (!dev->hasSelection()) return;
KisSelectionSP selection = dev->selection();
TQRect r = selection->selectedExactRect();
KisPaintDeviceSP clip = new KisPaintDevice(dev->colorSpace(), "clip");
Q_CHECK_PTR(clip);
KisColorSpace * cs = clip->colorSpace();
// TODO if the source is linked... copy from all linked layers?!?
// Copy image data
KisPainter gc;
gc.begin(clip);
gc.bitBlt(0, 0, COMPOSITE_COPY, dev, r.x(), r.y(), r.width(), r.height());
gc.end();
// Apply selection mask.
for (TQ_INT32 y = 0; y < r.height(); y++) {
KisHLineIteratorPixel layerIt = clip->createHLineIterator(0, y, r.width(), true);
KisHLineIteratorPixel selectionIt = selection->createHLineIterator(r.x(), r.y() + y, r.width(), false);
while (!layerIt.isDone()) {
cs->applyAlphaU8Mask( layerIt.rawData(), selectionIt.rawData(), 1 );
++layerIt;
++selectionIt;
}
}
m_clipboard->setClip(clip);
imgSelectionChanged(m_parent->currentImg());
}
KisLayerSP KisSelectionManager::paste()
{
KisImageSP img = m_parent->currentImg();
if (!img) return 0;
KisPaintDeviceSP clip = m_clipboard->clip();
if (clip) {
TQApplication::setOverrideCursor(KisCursor::waitCursor());
KisPaintLayer *layer = new KisPaintLayer(img, img->nextLayerName() + i18n("(pasted)"), OPACITY_OPAQUE);
Q_CHECK_PTR(layer);
TQRect r = clip->exactBounds();
KisPainter gc;
gc.begin(layer->paintDevice());
gc.bitBlt(0, 0, COMPOSITE_COPY, clip, r.x(), r.y(), r.width(), r.height());
gc.end();
//figure out where to position the clip
KisCanvasController *cc = m_parent->getCanvasController();
TQPoint center = cc->viewToWindow(TQPoint(cc->kiscanvas()->width()/2, cc->kiscanvas()->height()/2));
TQPoint bottomright = cc->viewToWindow(TQPoint(cc->kiscanvas()->width(), cc->kiscanvas()->height()));
if(bottomright.x() > img->width())
center.setX(img->width()/2);
if(bottomright.y() > img->height())
center.setY(img->height()/2);
center -= TQPoint(r.width()/2, r.height()/2);
layer->setX(center.x());
layer->setY(center.y());
/*XXX CBR have an idea of asking the user if he is about to paste a clip ion another cs than that of
the image if that is what he want rather than silently converting
if (clip->colorSpace != img ->colorSpace())
if (dlg->exec() == TQDialog::Accepted)
layer->convertTo(img->colorSpace());
*/
TQApplication::restoreOverrideCursor();
if(img->addLayer(layer, img->activeLayer()->parent(), img->activeLayer()))
{
return layer;
} else {
return 0;
}
}
return 0;
}
void KisSelectionManager::pasteNew()
{
KisPaintDeviceSP clip = m_clipboard->clip();
if (!clip) return;
TQRect r = clip->exactBounds();
if (r.width() < 1 && r.height() < 1) {
// Don't paste empty clips
return;
}
const TQCString mimetype = KoDocument::readNativeFormatMimeType();
KoDocumentEntry entry = KoDocumentEntry::queryByMimeType( mimetype );
KisDoc * doc = (KisDoc*) entry.createDoc();
Q_ASSERT(doc->undoAdapter() != 0);
doc->undoAdapter()->setUndo(false);
KisImageSP img = new KisImage(doc->undoAdapter(), r.width(), r.height(), clip->colorSpace(), "Pasted");
KisPaintLayer *layer = new KisPaintLayer(img, clip->name(), OPACITY_OPAQUE, clip->colorSpace());
KisPainter p(layer->paintDevice());
p.bitBlt(0, 0, COMPOSITE_COPY, clip, OPACITY_OPAQUE, r.x(), r.y(), r.width(), r.height());
p.end();
img->addLayer(layer, img->rootLayer(), 0);
doc->setCurrentImage(img);
doc->undoAdapter()->setUndo(true);
KoMainWindow *win = new KoMainWindow( doc->instance() );
win->show();
win->setRootDocument( doc );
}
void KisSelectionManager::selectAll()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
KisSelectedTransaction * t = 0;
if (img->undo()) t = new KisSelectedTransaction(i18n("Select All"), dev);
Q_CHECK_PTR(t);
// Make adjustment layers behave better
KisAdjustmentLayer* adj = dynamic_cast<KisAdjustmentLayer*>(img->activeLayer().data());
if (adj) {
adj->clearSelection();
adj->selection()->invert();
} else {
dev->selection()->clear();
dev->selection()->invert();
}
dev->setDirty();
dev->emitSelectionChanged();
if (img->undo())
img->undoAdapter()->addCommand(t);
}
void KisSelectionManager::deselect()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
KisSelectedTransaction * t = 0;
if (img->undo()) t = new KisSelectedTransaction(i18n("Deselect"), dev);
Q_CHECK_PTR(t);
// Make adjustment layers behave almost the same (except no reselect)
KisAdjustmentLayer* adj = dynamic_cast<KisAdjustmentLayer*>(img->activeLayer().data());
if (adj) {
adj->clearSelection();
} else {
dev->deselect();
}
dev->setDirty();
dev->emitSelectionChanged();
if (img->undo())
img->undoAdapter()->addCommand(t);
}
void KisSelectionManager::clear()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
if (!dev->hasSelection()) return;
KisTransaction * t = 0;
if (img->undo()) {
t = new KisTransaction(i18n("Clear"), dev);
}
dev->clearSelection();
dev->setDirty();
dev->emitSelectionChanged();
if (img->undo()) img->undoAdapter()->addCommand(t);
}
void KisSelectionManager::fill(const KisColor& color, bool fillWithPattern, const TQString& transactionText)
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
if (!dev->hasSelection()) return;
KisSelectionSP selection = dev->selection();
KisPaintDeviceSP filled = new KisPaintDevice(dev->colorSpace());
KisFillPainter painter(filled);
if (fillWithPattern) {
painter.fillRect(0, 0, img->width(), img->height(),
m_parent->currentPattern());
} else {
painter.fillRect(0, 0, img->width(), img->height(), color);
}
painter.end();
KisPainter painter2(dev);
if (img->undo()) painter2.beginTransaction(transactionText);
painter2.bltSelection(0, 0, COMPOSITE_OVER, filled, OPACITY_OPAQUE,
0, 0, img->width(), img->height());
dev->setDirty();
dev->emitSelectionChanged();
if (img->undo()) {
img->undoAdapter()->addCommand(painter2.endTransaction());
}
}
void KisSelectionManager::fillForegroundColor()
{
fill(m_parent->fgColor(), false, i18n("Fill with Foreground Color"));
}
void KisSelectionManager::fillBackgroundColor()
{
fill(m_parent->bgColor(), false, i18n("Fill with Background Color"));
}
void KisSelectionManager::fillPattern()
{
fill(KisColor(), true, i18n("Fill with Pattern"));
}
void KisSelectionManager::reselect()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img ->activeDevice();
if (!dev) return;
KisSelectedTransaction * t = 0;
if (img->undo()) t = new KisSelectedTransaction(i18n("Reselect"), dev);
Q_CHECK_PTR(t);
dev->reselect(); // sets hasSelection=true
dev->setDirty();
dev->emitSelectionChanged();
if (img->undo())
img->undoAdapter()->addCommand(t);
}
void KisSelectionManager::invert()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
if (dev->hasSelection()) {
KisSelectionSP s = dev->selection();
KisSelectedTransaction * t = 0;
if (img->undo())
{
t = new KisSelectedTransaction(i18n("Invert"), dev);
Q_CHECK_PTR(t);
}
s->invert();
dev->setDirty();
dev->emitSelectionChanged();
if (t) {
img->undoAdapter()->addCommand(t);
}
}
}
void KisSelectionManager::copySelectionToNewLayer()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
copy();
paste();
}
void KisSelectionManager::cutToNewLayer()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
cut();
paste();
}
void KisSelectionManager::feather()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
if (!dev->hasSelection()) {
// activate it, but don't do anything with it
dev->selection();
return;
}
KisSelectionSP selection = dev->selection();
KisSelectedTransaction * t = 0;
if (img->undo()) t = new KisSelectedTransaction(i18n("Feather..."), dev);
Q_CHECK_PTR(t);
// XXX: we should let gaussian blur & others influence alpha channels as well
// (on demand of the caller)
KisConvolutionPainter painter(selection.data());
KisKernelSP k = new KisKernel();
k->width = 3;
k->height = 3;
k->factor = 16;
k->offset = 0;
k->data = new TQ_INT32[9];
k->data[0] = 1;
k->data[1] = 2;
k->data[2] = 1;
k->data[3] = 2;
k->data[4] = 4;
k->data[5] = 2;
k->data[6] = 1;
k->data[7] = 2;
k->data[8] = 1;
TQRect rect = selection->selectedRect();
// Make sure we've got enough space around the edges.
rect = TQRect(rect.x() - 3, rect.y() - 3, rect.width() + 6, rect.height() + 6);
rect &= TQRect(0, 0, img->width(), img->height());
painter.applyMatrix(k, rect.x(), rect.y(), rect.width(), rect.height(), BORDER_AVOID, KisChannelInfo::FLAG_ALPHA);
painter.end();
dev->setDirty(rect);
dev->emitSelectionChanged();
if (img->undo())
img->undoAdapter()->addCommand(t);
}
void KisSelectionManager::toggleDisplaySelection()
{
m_parent->selectionDisplayToggled(displaySelection());
}
bool KisSelectionManager::displaySelection()
{
return m_toggleDisplaySelection->isChecked();
}
// XXX: Maybe move these esoteric functions to plugins?
void KisSelectionManager::border() {}
void KisSelectionManager::expand() {}
void KisSelectionManager::contract() {}
void KisSelectionManager::similar() {}
void KisSelectionManager::transform() {}
void KisSelectionManager::load() {}
void KisSelectionManager::save() {}
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
void KisSelectionManager::grow (TQ_INT32 xradius, TQ_INT32 yradius)
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
if (!dev->hasSelection()) return;
KisSelectionSP selection = dev->selection();
//determine the layerSize
TQRect layerSize = dev->exactBounds();
/*
Any bugs in this fuction are probably also in thin_region
Blame all bugs in this function on jaycox@gimp.org
*/
TQ_UINT8 **buf; // caches the region's pixel data
TQ_UINT8 **max; // caches the largest values for each column
if (xradius <= 0 || yradius <= 0)
return;
KisSelectedTransaction *t = 0;
if (img->undo()) {
t = new KisSelectedTransaction(i18n("Grow"), dev);
Q_CHECK_PTR(t);
}
max = new TQ_UINT8* [layerSize.width() + 2 * xradius];
buf = new TQ_UINT8* [yradius + 1];
for (TQ_INT32 i = 0; i < yradius + 1; i++)
{
buf[i] = new TQ_UINT8[layerSize.width()];
}
TQ_UINT8* buffer = new TQ_UINT8[ ( layerSize.width() + 2 * xradius ) * ( yradius + 1 ) ];
for (TQ_INT32 i = 0; i < layerSize.width() + 2 * xradius; i++)
{
if (i < xradius)
max[i] = buffer;
else if (i < layerSize.width() + xradius)
max[i] = &buffer[(yradius + 1) * (i - xradius)];
else
max[i] = &buffer[(yradius + 1) * (layerSize.width() + xradius - 1)];
for (TQ_INT32 j = 0; j < xradius + 1; j++)
max[i][j] = 0;
}
/* offset the max pointer by xradius so the range of the array
is [-xradius] to [region->w + xradius] */
max += xradius;
TQ_UINT8* out = new TQ_UINT8[ layerSize.width() ]; // holds the new scan line we are computing
TQ_INT32* circ = new TQ_INT32[ 2 * xradius + 1 ]; // holds the y coords of the filter's mask
computeBorder (circ, xradius, yradius);
/* offset the circ pointer by xradius so the range of the array
is [-xradius] to [xradius] */
circ += xradius;
memset (buf[0], 0, layerSize.width());
for (TQ_INT32 i = 0; i < yradius && i < layerSize.height(); i++) // load top of image
{
selection->readBytes(buf[i + 1], layerSize.x(), layerSize.y() + i, layerSize.width(), 1);
}
for (TQ_INT32 x = 0; x < layerSize.width() ; x++) // set up max for top of image
{
max[x][0] = 0; // buf[0][x] is always 0
max[x][1] = buf[1][x]; // MAX (buf[1][x], max[x][0]) always = buf[1][x]
for (TQ_INT32 j = 2; j < yradius + 1; j++)
{
max[x][j] = MAX(buf[j][x], max[x][j-1]);
}
}
for (TQ_INT32 y = 0; y < layerSize.height(); y++)
{
rotatePointers (buf, yradius + 1);
if (y < layerSize.height() - (yradius))
selection->readBytes(buf[yradius], layerSize.x(), layerSize.y() + y + yradius, layerSize.width(), 1);
else
memset (buf[yradius], 0, layerSize.width());
for (TQ_INT32 x = 0; x < layerSize.width(); x++) /* update max array */
{
for (TQ_INT32 i = yradius; i > 0; i--)
{
max[x][i] = MAX (MAX (max[x][i - 1], buf[i - 1][x]), buf[i][x]);
}
max[x][0] = buf[0][x];
}
TQ_INT32 last_max = max[0][circ[-1]];
TQ_INT32 last_index = 1;
for (TQ_INT32 x = 0; x < layerSize.width(); x++) /* render scan line */
{
last_index--;
if (last_index >= 0)
{
if (last_max == 255)
out[x] = 255;
else
{
last_max = 0;
for (TQ_INT32 i = xradius; i >= 0; i--)
if (last_max < max[x + i][circ[i]])
{
last_max = max[x + i][circ[i]];
last_index = i;
}
out[x] = last_max;
}
}
else
{
last_index = xradius;
last_max = max[x + xradius][circ[xradius]];
for (TQ_INT32 i = xradius - 1; i >= -xradius; i--)
if (last_max < max[x + i][circ[i]])
{
last_max = max[x + i][circ[i]];
last_index = i;
}
out[x] = last_max;
}
}
selection->writeBytes(out, layerSize.x(), layerSize.y() + y, layerSize.width(), 1);
}
/* undo the offsets to the pointers so we can free the malloced memmory */
circ -= xradius;
max -= xradius;
//XXXX: replace delete by delete[] where it is necessary to avoid memory leaks!
delete[] circ;
delete[] buffer;
delete[] max;
for (TQ_INT32 i = 0; i < yradius + 1; i++)
delete[] buf[i];
delete[] buf;
delete[] out;
dev->setDirty();
dev->emitSelectionChanged();
if (t) {
img->undoAdapter()->addCommand(t);
}
}
void KisSelectionManager::shrink (TQ_INT32 xradius, TQ_INT32 yradius, bool edge_lock)
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
if (!dev->hasSelection()) return;
KisSelectionSP selection = dev->selection();
//determine the layerSize
TQRect layerSize = dev->exactBounds();
/*
pretty much the same as fatten_region only different
blame all bugs in this function on jaycox@gimp.org
*/
/* If edge_lock is true we assume that pixels outside the region
we are passed are identical to the edge pixels.
If edge_lock is false, we assume that pixels outside the region are 0
*/
TQ_UINT8 **buf; // caches the the region's pixels
TQ_UINT8 **max; // caches the smallest values for each column
TQ_INT32 last_max, last_index;
if (xradius <= 0 || yradius <= 0)
return;
max = new TQ_UINT8* [layerSize.width() + 2 * xradius];
buf = new TQ_UINT8* [yradius + 1];
for (TQ_INT32 i = 0; i < yradius + 1; i++)
{
buf[i] = new TQ_UINT8[layerSize.width()];
}
TQ_INT32 buffer_size = (layerSize.width() + 2 * xradius + 1) * (yradius + 1);
TQ_UINT8* buffer = new TQ_UINT8[buffer_size];
if (edge_lock)
memset(buffer, 255, buffer_size);
else
memset(buffer, 0, buffer_size);
for (TQ_INT32 i = 0; i < layerSize.width() + 2 * xradius; i++)
{
if (i < xradius)
if (edge_lock)
max[i] = buffer;
else
max[i] = &buffer[(yradius + 1) * (layerSize.width() + xradius)];
else if (i < layerSize.width() + xradius)
max[i] = &buffer[(yradius + 1) * (i - xradius)];
else
if (edge_lock)
max[i] = &buffer[(yradius + 1) * (layerSize.width() + xradius - 1)];
else
max[i] = &buffer[(yradius + 1) * (layerSize.width() + xradius)];
}
if (!edge_lock)
for (TQ_INT32 j = 0 ; j < xradius + 1; j++) max[0][j] = 0;
// offset the max pointer by xradius so the range of the array is [-xradius] to [region->w + xradius]
max += xradius;
TQ_UINT8* out = new TQ_UINT8[layerSize.width()]; // holds the new scan line we are computing
TQ_INT32* circ = new TQ_INT32[2 * xradius + 1]; // holds the y coords of the filter's mask
computeBorder (circ, xradius, yradius);
// offset the circ pointer by xradius so the range of the array is [-xradius] to [xradius]
circ += xradius;
for (TQ_INT32 i = 0; i < yradius && i < layerSize.height(); i++) // load top of image
selection->readBytes(buf[i + 1], layerSize.x(), layerSize.y() + i, layerSize.width(), 1);
if (edge_lock)
memcpy (buf[0], buf[1], layerSize.width());
else
memset (buf[0], 0, layerSize.width());
for (TQ_INT32 x = 0; x < layerSize.width(); x++) // set up max for top of image
{
max[x][0] = buf[0][x];
for (TQ_INT32 j = 1; j < yradius + 1; j++)
max[x][j] = MIN(buf[j][x], max[x][j-1]);
}
for (TQ_INT32 y = 0; y < layerSize.height(); y++)
{
rotatePointers (buf, yradius + 1);
if (y < layerSize.height() - yradius)
selection->readBytes(buf[yradius], layerSize.x(), layerSize.y() + y + yradius, layerSize.width(), 1);
else if (edge_lock)
memcpy (buf[yradius], buf[yradius - 1], layerSize.width());
else
memset (buf[yradius], 0, layerSize.width());
for (TQ_INT32 x = 0 ; x < layerSize.width(); x++) // update max array
{
for (TQ_INT32 i = yradius; i > 0; i--)
{
max[x][i] = MIN (MIN (max[x][i - 1], buf[i - 1][x]), buf[i][x]);
}
max[x][0] = buf[0][x];
}
last_max = max[0][circ[-1]];
last_index = 0;
for (TQ_INT32 x = 0 ; x < layerSize.width(); x++) // render scan line
{
last_index--;
if (last_index >= 0)
{
if (last_max == 0)
out[x] = 0;
else
{
last_max = 255;
for (TQ_INT32 i = xradius; i >= 0; i--)
if (last_max > max[x + i][circ[i]])
{
last_max = max[x + i][circ[i]];
last_index = i;
}
out[x] = last_max;
}
}
else
{
last_index = xradius;
last_max = max[x + xradius][circ[xradius]];
for (TQ_INT32 i = xradius - 1; i >= -xradius; i--)
if (last_max > max[x + i][circ[i]])
{
last_max = max[x + i][circ[i]];
last_index = i;
}
out[x] = last_max;
}
}
selection->writeBytes(out, layerSize.x(), layerSize.y() + y, layerSize.width(), 1);
}
// undo the offsets to the pointers so we can free the malloced memmory
circ -= xradius;
max -= xradius;
//free the memmory
//XXXX: replace delete by delete[] where it is necessary to avoid memory leaks!
delete[] circ;
delete[] buffer;
delete[] max;
for (TQ_INT32 i = 0; i < yradius + 1; i++)
delete buf[i];
delete[] buf;
delete[] out;
dev->setDirty(layerSize);
dev->emitSelectionChanged();
}
//Simple convolution filter to smooth a mask (1bpp)
void KisSelectionManager::smooth()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
if (!dev->hasSelection()) return;
KisSelectionSP selection = dev->selection();
//determine the layerSize
TQRect layerSize = dev->exactBounds();
TQ_UINT8 *buf[3];
TQ_INT32 width = layerSize.width();
for (TQ_INT32 i = 0; i < 3; i++) buf[i] = new TQ_UINT8[width + 2];
TQ_UINT8* out = new TQ_UINT8[width];
// load top of image
selection->readBytes(buf[0] + 1, layerSize.x(), layerSize.y(), width, 1);
buf[0][0] = buf[0][1];
buf[0][width + 1] = buf[0][width];
memcpy (buf[1], buf[0], width + 2);
for (TQ_INT32 y = 0; y < layerSize.height(); y++)
{
if (y + 1 < layerSize.height())
{
selection->readBytes(buf[2] + 1, layerSize.x(), layerSize.y() + y + 1, width, 1);
buf[2][0] = buf[2][1];
buf[2][width + 1] = buf[2][width];
}
else
{
memcpy (buf[2], buf[1], width + 2);
}
for (TQ_INT32 x = 0 ; x < width; x++)
{
TQ_INT32 value = (buf[0][x] + buf[0][x+1] + buf[0][x+2] +
buf[1][x] + buf[2][x+1] + buf[1][x+2] +
buf[2][x] + buf[1][x+1] + buf[2][x+2]);
out[x] = value / 9;
}
selection->writeBytes(out, layerSize.x(), layerSize.y() + y, width, 1);
rotatePointers (buf, 3);
}
for (TQ_INT32 i = 0; i < 3; i++)
delete[] buf[i];
delete[] out;
dev->setDirty();
dev->emitSelectionChanged();
}
// Erode (radius 1 pixel) a mask (1bpp)
void KisSelectionManager::erode()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
if (!dev->hasSelection()) return;
KisSelectionSP selection = dev->selection();
//determine the layerSize
TQRect layerSize = dev->exactBounds();
TQ_UINT8* buf[3];
TQ_INT32 width = layerSize.width();
for (TQ_INT32 i = 0; i < 3; i++)
buf[i] = new TQ_UINT8[width + 2];
TQ_UINT8* out = new TQ_UINT8[width];
// load top of image
selection->readBytes(buf[0] + 1, layerSize.x(), layerSize.y(), width, 1);
buf[0][0] = buf[0][1];
buf[0][width + 1] = buf[0][width];
memcpy (buf[1], buf[0], width + 2);
for (TQ_INT32 y = 0; y < layerSize.height(); y++)
{
if (y + 1 < layerSize.height())
{
selection->readBytes(buf[2] + 1, layerSize.x(), layerSize.y() + y + 1, width, 1);
buf[2][0] = buf[2][1];
buf[2][width + 1] = buf[2][width];
}
else
{
memcpy (buf[2], buf[1], width + 2);
}
for (TQ_INT32 x = 0 ; x < width; x++)
{
TQ_INT32 min = 255;
if (buf[0][x+1] < min) min = buf[0][x+1];
if (buf[1][x] < min) min = buf[1][x];
if (buf[1][x+1] < min) min = buf[1][x+1];
if (buf[1][x+2] < min) min = buf[1][x+2];
if (buf[2][x+1] < min) min = buf[2][x+1];
out[x] = min;
}
selection->writeBytes(out, layerSize.x(), layerSize.y() + y, width, 1);
rotatePointers (buf, 3);
}
for (TQ_INT32 i = 0; i < 3; i++)
delete[] buf[i];
delete[] out;
dev->setDirty();
dev->emitSelectionChanged();
}
// dilate (radius 1 pixel) a mask (1bpp)
void KisSelectionManager::dilate()
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
if (!dev->hasSelection()) return;
KisSelectionSP selection = dev->selection();
//determine the layerSize
TQRect layerSize = dev->exactBounds();
TQ_UINT8* buf[3];
TQ_INT32 width = layerSize.width();
for (TQ_INT32 i = 0; i < 3; i++)
buf[i] = new TQ_UINT8[width + 2];
TQ_UINT8* out = new TQ_UINT8[width];
// load top of image
selection->readBytes(buf[0] + 1, layerSize.x(), layerSize.y(), width, 1);
buf[0][0] = buf[0][1];
buf[0][width + 1] = buf[0][width];
memcpy (buf[1], buf[0], width + 2);
for (TQ_INT32 y = 0; y < layerSize.height(); y++)
{
if (y + 1 < layerSize.height())
{
selection->readBytes(buf[2] + 1, layerSize.x(), layerSize.y() + y + 1, width, 1);
buf[2][0] = buf[2][1];
buf[2][width + 1] = buf[2][width];
}
else
{
memcpy (buf[2], buf[1], width + 2);
}
for (TQ_INT32 x = 0 ; x < width; x++)
{
TQ_INT32 max = 0;
if (buf[0][x+1] > max) max = buf[0][x+1];
if (buf[1][x] > max) max = buf[1][x];
if (buf[1][x+1] > max) max = buf[1][x+1];
if (buf[1][x+2] > max) max = buf[1][x+2];
if (buf[2][x+1] > max) max = buf[2][x+1];
out[x] = max;
}
selection->writeBytes(out, layerSize.x(), layerSize.y() + y, width, 1);
rotatePointers (buf, 3);
}
for (TQ_INT32 i = 0; i < 3; i++)
delete[] buf[i];
delete[] out;
dev->setDirty();
dev->emitSelectionChanged();
}
void KisSelectionManager::border(TQ_INT32 xradius, TQ_INT32 yradius)
{
KisImageSP img = m_parent->currentImg();
if (!img) return;
KisPaintDeviceSP dev = img->activeDevice();
if (!dev) return;
if (!dev->hasSelection()) return;
KisSelectionSP selection = dev->selection();
//determine the layerSize
TQRect layerSize = dev->exactBounds();
/*
This function has no bugs, but if you imagine some you can
blame them on jaycox@gimp.org
*/
TQ_UINT8 *buf[3];
TQ_UINT8 **density;
TQ_UINT8 **transition;
if (xradius == 1 && yradius == 1) // optimize this case specifically
{
TQ_UINT8* source[3];
for (TQ_INT32 i = 0; i < 3; i++)
source[i] = new TQ_UINT8[layerSize.width()];
TQ_UINT8* transition = new TQ_UINT8[layerSize.width()];
selection->readBytes(source[0], layerSize.x(), layerSize.y(), layerSize.width(), 1);
memcpy (source[1], source[0], layerSize.width());
if (layerSize.height() > 1)
selection->readBytes(source[2], layerSize.x(), layerSize.y() + 1, layerSize.width(), 1);
else
memcpy (source[2], source[1], layerSize.width());
computeTransition (transition, source, layerSize.width());
selection->writeBytes(transition, layerSize.x(), layerSize.y(), layerSize.width(), 1);
for (TQ_INT32 y = 1; y < layerSize.height(); y++)
{
rotatePointers (source, 3);
if (y + 1 < layerSize.height())
selection->readBytes(source[2], layerSize.x(), layerSize.y() + y + 1, layerSize.width(), 1);
else
memcpy(source[2], source[1], layerSize.width());
computeTransition (transition, source, layerSize.width());
selection->writeBytes(transition, layerSize.x(), layerSize.y() + y, layerSize.width(), 1);
}
for (TQ_INT32 i = 0; i < 3; i++)
delete[] source[i];
delete[] transition;
return;
}
TQ_INT32* max = new TQ_INT32[layerSize.width() + 2 * xradius];
for (TQ_INT32 i = 0; i < (layerSize.width() + 2 * xradius); i++)
max[i] = yradius + 2;
max += xradius;
for (TQ_INT32 i = 0; i < 3; i++)
buf[i] = new TQ_UINT8[layerSize.width()];
transition = new TQ_UINT8*[yradius + 1];
for (TQ_INT32 i = 0; i < yradius + 1; i++)
{
transition[i] = new TQ_UINT8[layerSize.width() + 2 * xradius];
memset(transition[i], 0, layerSize.width() + 2 * xradius);
transition[i] += xradius;
}
TQ_UINT8* out = new TQ_UINT8[layerSize.width()];
density = new TQ_UINT8*[2 * xradius + 1];
density += xradius;
for (TQ_INT32 x = 0; x < (xradius + 1); x++) // allocate density[][]
{
density[ x] = new TQ_UINT8[2 * yradius + 1];
density[ x] += yradius;
density[-x] = density[x];
}
for (TQ_INT32 x = 0; x < (xradius + 1); x++) // compute density[][]
{
double tmpx, tmpy, dist;
TQ_UINT8 a;
if (x > 0)
tmpx = x - 0.5;
else if (x < 0)
tmpx = x + 0.5;
else
tmpx = 0.0;
for (TQ_INT32 y = 0; y < (yradius + 1); y++)
{
if (y > 0)
tmpy = y - 0.5;
else if (y < 0)
tmpy = y + 0.5;
else
tmpy = 0.0;
dist = ((tmpy * tmpy) / (yradius * yradius) +
(tmpx * tmpx) / (xradius * xradius));
if (dist < 1.0)
a = 255 * (TQ_UINT8)(1.0 - sqrt (dist));
else
a = 0;
density[ x][ y] = a;
density[ x][-y] = a;
density[-x][ y] = a;
density[-x][-y] = a;
}
}
selection->readBytes(buf[0], layerSize.x(), layerSize.y(), layerSize.width(), 1);
memcpy (buf[1], buf[0], layerSize.width());
if (layerSize.height() > 1)
selection->readBytes(buf[2], layerSize.x(), layerSize.y() + 1, layerSize.width(), 1);
else
memcpy (buf[2], buf[1], layerSize.width());
computeTransition (transition[1], buf, layerSize.width());
for (TQ_INT32 y = 1; y < yradius && y + 1 < layerSize.height(); y++) // set up top of image
{
rotatePointers (buf, 3);
selection->readBytes(buf[2], layerSize.x(), layerSize.y() + y + 1, layerSize.width(), 1);
computeTransition (transition[y + 1], buf, layerSize.width());
}
for (TQ_INT32 x = 0; x < layerSize.width(); x++) // set up max[] for top of image
{
max[x] = -(yradius + 7);
for (TQ_INT32 j = 1; j < yradius + 1; j++)
if (transition[j][x])
{
max[x] = j;
break;
}
}
for (TQ_INT32 y = 0; y < layerSize.height(); y++) // main calculation loop
{
rotatePointers (buf, 3);
rotatePointers (transition, yradius + 1);
if (y < layerSize.height() - (yradius + 1))
{
selection->readBytes(buf[2], layerSize.x(), layerSize.y() + y + yradius + 1, layerSize.width(), 1);
computeTransition (transition[yradius], buf, layerSize.width());
}
else
memcpy (transition[yradius], transition[yradius - 1], layerSize.width());
for (TQ_INT32 x = 0; x < layerSize.width(); x++) // update max array
{
if (max[x] < 1)
{
if (max[x] <= -yradius)
{
if (transition[yradius][x])
max[x] = yradius;
else
max[x]--;
}
else
if (transition[-max[x]][x])
max[x] = -max[x];
else if (transition[-max[x] + 1][x])
max[x] = -max[x] + 1;
else
max[x]--;
}
else
max[x]--;
if (max[x] < -yradius - 1)
max[x] = -yradius - 1;
}
TQ_UINT8 last_max = max[0][density[-1]];
TQ_INT32 last_index = 1;
for (TQ_INT32 x = 0 ; x < layerSize.width(); x++) // render scan line
{
last_index--;
if (last_index >= 0)
{
last_max = 0;
for (TQ_INT32 i = xradius; i >= 0; i--)
if (max[x + i] <= yradius && max[x + i] >= -yradius && density[i][max[x+i]] > last_max)
{
last_max = density[i][max[x + i]];
last_index = i;
}
out[x] = last_max;
}
else
{
last_max = 0;
for (TQ_INT32 i = xradius; i >= -xradius; i--)
if (max[x + i] <= yradius && max[x + i] >= -yradius && density[i][max[x + i]] > last_max)
{
last_max = density[i][max[x + i]];
last_index = i;
}
out[x] = last_max;
}
if (last_max == 0)
{
TQ_INT32 i;
for (i = x + 1; i < layerSize.width(); i++)
{
if (max[i] >= -yradius)
break;
}
if (i - x > xradius)
{
for (; x < i - xradius; x++)
out[x] = 0;
x--;
}
last_index = xradius;
}
}
selection->writeBytes(out, layerSize.x(), layerSize.y() + y, layerSize.width(), 1);
}
delete [] out;
for (TQ_INT32 i = 0; i < 3; i++)
delete buf[i];
max -= xradius;
delete[] max;
for (TQ_INT32 i = 0; i < yradius + 1; i++)
{
transition[i] -= xradius;
delete transition[i];
}
delete[] transition;
for (TQ_INT32 i = 0; i < xradius + 1 ; i++)
{
density[i] -= yradius;
delete density[i];
}
density -= xradius;
delete[] density;
dev->setDirty();
dev->emitSelectionChanged();
}
#define RINT(x) floor ((x) + 0.5)
void KisSelectionManager::computeBorder (TQ_INT32 *circ, TQ_INT32 xradius, TQ_INT32 yradius)
{
Q_ASSERT(xradius != 0);
TQ_INT32 i;
TQ_INT32 diameter = xradius * 2 + 1;
double tmp;
for (i = 0; i < diameter; i++)
{
if (i > xradius)
tmp = (i - xradius) - 0.5;
else if (i < xradius)
tmp = (xradius - i) - 0.5;
else
tmp = 0.0;
circ[i] = (TQ_INT32) RINT (yradius / (double) xradius * sqrt (xradius * xradius - tmp * tmp));
}
}
void KisSelectionManager::rotatePointers (TQ_UINT8 **p, TQ_UINT32 n)
{
TQ_UINT32 i;
TQ_UINT8 *tmp;
tmp = p[0];
for (i = 0; i < n - 1; i++) p[i] = p[i + 1];
p[i] = tmp;
}
void KisSelectionManager::computeTransition (TQ_UINT8* transition, TQ_UINT8** buf, TQ_INT32 width)
{
TQ_INT32 x = 0;
if (width == 1)
{
if (buf[1][x] > 127 && (buf[0][x] < 128 || buf[2][x] < 128))
transition[x] = 255;
else
transition[x] = 0;
return;
}
if (buf[1][x] > 127)
{
if ( buf[0][x] < 128 || buf[0][x + 1] < 128 ||
buf[1][x + 1] < 128 ||
buf[2][x] < 128 || buf[2][x + 1] < 128 )
transition[x] = 255;
else
transition[x] = 0;
}
else
transition[x] = 0;
for (TQ_INT32 x = 1; x < width - 1; x++)
{
if (buf[1][x] >= 128)
{
if (buf[0][x - 1] < 128 || buf[0][x] < 128 || buf[0][x + 1] < 128 ||
buf[1][x - 1] < 128 || buf[1][x + 1] < 128 ||
buf[2][x - 1] < 128 || buf[2][x] < 128 || buf[2][x + 1] < 128)
transition[x] = 255;
else
transition[x] = 0;
}
else
transition[x] = 0;
}
if (buf[1][x] >= 128)
{
if (buf[0][x - 1] < 128 || buf[0][x] < 128 ||
buf[1][x - 1] < 128 ||
buf[2][x - 1] < 128 || buf[2][x] < 128)
transition[x] = 255;
else
transition[x] = 0;
}
else
transition[x] = 0;
}
#include "kis_selection_manager.moc"