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.
1644 lines
47 KiB
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 <tdeaction.h>
|
|
#include <tdelocale.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(TDEActionCollection * collection)
|
|
{
|
|
// XXX: setup shortcuts!
|
|
|
|
m_cut = KStdAction::cut(this,
|
|
TQ_SLOT(cut()),
|
|
collection,
|
|
"cut");
|
|
|
|
m_copy = KStdAction::copy(this,
|
|
TQ_SLOT(copy()),
|
|
collection,
|
|
"copy");
|
|
|
|
m_paste = KStdAction::paste(this,
|
|
TQ_SLOT(paste()),
|
|
collection,
|
|
"paste");
|
|
|
|
m_pasteNew = new TDEAction(i18n("Paste into &New Image"),
|
|
0, 0,
|
|
this, TQ_SLOT(pasteNew()),
|
|
collection,
|
|
"paste_new");
|
|
|
|
|
|
m_selectAll = KStdAction::selectAll(this,
|
|
TQ_SLOT(selectAll()),
|
|
collection,
|
|
"select_all");
|
|
|
|
m_deselect = KStdAction::deselect(this,
|
|
TQ_SLOT(deselect()),
|
|
collection,
|
|
"deselect");
|
|
|
|
|
|
m_clear = KStdAction::clear(this,
|
|
TQ_SLOT(clear()),
|
|
collection,
|
|
"clear");
|
|
|
|
m_reselect = new TDEAction(i18n("&Reselect"),
|
|
0, "Ctrl+Shift+D",
|
|
this, TQ_SLOT(reselect()),
|
|
collection, "reselect");
|
|
|
|
m_invert = new TDEAction(i18n("&Invert"),
|
|
0, "Ctrl+I",
|
|
this, TQ_SLOT(invert()),
|
|
collection, "invert");
|
|
|
|
|
|
m_toNewLayer = new TDEAction(i18n("Copy Selection to New Layer"),
|
|
0, "Ctrl+J",
|
|
this, TQ_SLOT(copySelectionToNewLayer()),
|
|
collection, "copy_selection_to_new_layer");
|
|
|
|
|
|
m_cutToNewLayer = new TDEAction(i18n("Cut Selection to New Layer"),
|
|
0, "Ctrl+Shift+J",
|
|
this, TQ_SLOT(cutToNewLayer()),
|
|
collection, "cut_selection_to_new_layer");
|
|
|
|
m_feather = new TDEAction(i18n("Feather"),
|
|
0, "Ctrl+Alt+D",
|
|
this, TQ_SLOT(feather()),
|
|
collection, "feather");
|
|
|
|
m_fillForegroundColor = new TDEAction(i18n("Fill with Foreground Color"),
|
|
"Alt+backspace", this,
|
|
TQ_SLOT(fillForegroundColor()),
|
|
collection,
|
|
"fill_selection_foreground_color");
|
|
m_fillBackgroundColor = new TDEAction(i18n("Fill with Background Color"),
|
|
"backspace", this,
|
|
TQ_SLOT(fillBackgroundColor()),
|
|
collection,
|
|
"fill_selection_background_color");
|
|
m_fillPattern = new TDEAction(i18n("Fill with Pattern"),
|
|
0, this,
|
|
TQ_SLOT(fillPattern()),
|
|
collection,
|
|
"fill_selection_pattern");
|
|
|
|
m_toggleDisplaySelection = new TDEToggleAction(i18n("Display Selection"), "Ctrl+h", this, TQ_SLOT(toggleDisplaySelection()), collection, "toggle_display_selection");
|
|
m_toggleDisplaySelection->setCheckedState(KGuiItem(i18n("Hide Selection")));
|
|
m_toggleDisplaySelection->setChecked(true);
|
|
|
|
m_border =
|
|
new TDEAction(i18n("Border..."),
|
|
0, 0,
|
|
this, TQ_SLOT(border()),
|
|
collection, "border");
|
|
m_expand =
|
|
new TDEAction(i18n("Expand..."),
|
|
0, 0,
|
|
this, TQ_SLOT(expand()),
|
|
collection, "expand");
|
|
|
|
m_smooth =
|
|
new TDEAction(i18n("Smooth..."),
|
|
0, 0,
|
|
this, TQ_SLOT(smooth()),
|
|
collection, "smooth");
|
|
|
|
|
|
m_contract =
|
|
new TDEAction(i18n("Contract..."),
|
|
0, 0,
|
|
this, TQ_SLOT(contract()),
|
|
collection, "contract");
|
|
m_similar =
|
|
new TDEAction(i18n("Similar"),
|
|
0, 0,
|
|
this, TQ_SLOT(similar()),
|
|
collection, "similar");
|
|
|
|
|
|
m_transform
|
|
= new TDEAction(i18n("Transform..."),
|
|
0, 0,
|
|
this, TQ_SLOT(transform()),
|
|
collection, "transform_selection");
|
|
|
|
|
|
// m_load
|
|
// = new TDEAction(i18n("Load..."),
|
|
// 0, 0,
|
|
// this, TQ_SLOT(load()),
|
|
// collection, "load_selection");
|
|
//
|
|
//
|
|
// m_save
|
|
// = new TDEAction(i18n("Save As..."),
|
|
// 0, 0,
|
|
// this, TQ_SLOT(save()),
|
|
// collection, "save_selection");
|
|
|
|
TQClipboard *cb = TQApplication::clipboard();
|
|
connect(cb, TQ_SIGNAL(dataChanged()), TQ_SLOT(clipboardDataChanged()));
|
|
}
|
|
|
|
void KisSelectionManager::clipboardDataChanged()
|
|
{
|
|
updateGUI();
|
|
}
|
|
|
|
|
|
void KisSelectionManager::addSelectionAction(TDEAction * 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);
|
|
|
|
|
|
TDEAction * 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);
|
|
TQ_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");
|
|
TQ_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);
|
|
TQ_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);
|
|
TQ_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);
|
|
TQ_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);
|
|
TQ_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);
|
|
TQ_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);
|
|
TQ_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);
|
|
TQ_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"
|