|
|
|
/*
|
|
|
|
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
|
|
|
|
* Copyright (c) 2004 Bart Coppens <kde@bartcoppens.be>
|
|
|
|
*
|
|
|
|
* 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 <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <cfloat>
|
|
|
|
#include <stack>
|
|
|
|
|
|
|
|
#include "tqbrush.h"
|
|
|
|
#include "tqfontinfo.h"
|
|
|
|
#include "tqfontmetrics.h"
|
|
|
|
#include "tqpen.h"
|
|
|
|
#include "tqregion.h"
|
|
|
|
#include "tqwmatrix.h"
|
|
|
|
#include <tqimage.h>
|
|
|
|
#include <tqmap.h>
|
|
|
|
#include <tqpainter.h>
|
|
|
|
#include <tqpixmap.h>
|
|
|
|
#include <tqpointarray.h>
|
|
|
|
#include <tqrect.h>
|
|
|
|
#include <tqstring.h>
|
|
|
|
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <kcommand.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
|
|
|
|
#include "kis_brush.h"
|
|
|
|
#include "kis_debug_areas.h"
|
|
|
|
#include "kis_image.h"
|
|
|
|
#include "kis_layer.h"
|
|
|
|
#include "kis_paint_device.h"
|
|
|
|
#include "kis_painter.h"
|
|
|
|
#include "kis_pattern.h"
|
|
|
|
#include "kis_rect.h"
|
|
|
|
#include "kis_colorspace.h"
|
|
|
|
#include "kis_transaction.h"
|
|
|
|
#include "kis_types.h"
|
|
|
|
#include "kis_vec.h"
|
|
|
|
#include "kis_selection.h"
|
|
|
|
#include "kis_fill_painter.h"
|
|
|
|
#include "kis_iterators_pixel.h"
|
|
|
|
#include "kis_iterator.h"
|
|
|
|
#include "kis_color.h"
|
|
|
|
#include "kis_selection.h"
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
}
|
|
|
|
|
|
|
|
KisFillPainter::KisFillPainter()
|
|
|
|
: super()
|
|
|
|
{
|
|
|
|
m_width = m_height = -1;
|
|
|
|
m_sampleMerged = false;
|
|
|
|
m_careForSelection = false;
|
|
|
|
m_fuzzy = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
KisFillPainter::KisFillPainter(KisPaintDeviceSP device) : super(device)
|
|
|
|
{
|
|
|
|
m_width = m_height = -1;
|
|
|
|
m_sampleMerged = false;
|
|
|
|
m_careForSelection = false;
|
|
|
|
m_fuzzy = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 'regular' filling
|
|
|
|
// XXX: This also needs renaming, since filling ought to keep the opacity and the composite op in mind,
|
|
|
|
// this is more eraseToColor.
|
|
|
|
void KisFillPainter::fillRect(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 w, TQ_INT32 h, const KisColor& kc, TQ_UINT8 opacity)
|
|
|
|
{
|
|
|
|
if (w > 0 && h > 0) {
|
|
|
|
// Make sure we're in the right colorspace
|
|
|
|
|
|
|
|
KisColor kc2(kc); // get rid of const
|
|
|
|
kc2.convertTo(m_device->colorSpace());
|
|
|
|
TQ_UINT8 * data = kc2.data();
|
|
|
|
m_device->colorSpace()->setAlpha(data, opacity, 1);
|
|
|
|
|
|
|
|
m_device->fill(x1, y1, w, h, data);
|
|
|
|
|
|
|
|
addDirtyRect(TQRect(x1, y1, w, h));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisFillPainter::fillRect(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 w, TQ_INT32 h, KisPattern * pattern) {
|
|
|
|
if (!pattern) return;
|
|
|
|
if (!pattern->valid()) return;
|
|
|
|
if (!m_device) return;
|
|
|
|
|
|
|
|
|
|
|
|
KisPaintDeviceSP patternLayer = pattern->image(m_device->colorSpace());
|
|
|
|
|
|
|
|
int sx, sy, sw, sh;
|
|
|
|
|
|
|
|
int y = y1;
|
|
|
|
|
|
|
|
if (y >= 0) {
|
|
|
|
sy = y % pattern->height();
|
|
|
|
} else {
|
|
|
|
sy = pattern->height() - (((-y - 1) % pattern->height()) + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (y < y1 + h) {
|
|
|
|
sh = TQMIN((y1 + h) - y, pattern->height() - sy);
|
|
|
|
|
|
|
|
int x = x1;
|
|
|
|
|
|
|
|
if (x >= 0) {
|
|
|
|
sx = x % pattern->width();
|
|
|
|
} else {
|
|
|
|
sx = pattern->width() - (((-x - 1) % pattern->width()) + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (x < x1 + w) {
|
|
|
|
sw = TQMIN((x1 + w) - x, pattern->width() - sx);
|
|
|
|
|
|
|
|
bitBlt(x, y, m_compositeOp, patternLayer.data(), m_opacity, sx, sy, sw, sh);
|
|
|
|
x += sw; sx = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
y+=sh; sy = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
addDirtyRect(TQRect(x1, y1, w, h));
|
|
|
|
}
|
|
|
|
|
|
|
|
// flood filling
|
|
|
|
|
|
|
|
void KisFillPainter::fillColor(int startX, int startY) {
|
|
|
|
genericFillStart(startX, startY);
|
|
|
|
|
|
|
|
// Now create a layer and fill it
|
|
|
|
KisPaintDeviceSP filled = new KisPaintDevice(m_device->colorSpace(), "filled");
|
|
|
|
Q_CHECK_PTR(filled);
|
|
|
|
KisFillPainter painter(filled.data());
|
|
|
|
painter.fillRect(0, 0, m_width, m_height, m_paintColor);
|
|
|
|
painter.end();
|
|
|
|
|
|
|
|
genericFillEnd(filled);
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisFillPainter::fillPattern(int startX, int startY) {
|
|
|
|
genericFillStart(startX, startY);
|
|
|
|
|
|
|
|
// Now create a layer and fill it
|
|
|
|
KisPaintDeviceSP filled = new KisPaintDevice(m_device->colorSpace(), "filled");
|
|
|
|
Q_CHECK_PTR(filled);
|
|
|
|
KisFillPainter painter(filled.data());
|
|
|
|
painter.fillRect(0, 0, m_width, m_height, m_pattern);
|
|
|
|
painter.end();
|
|
|
|
|
|
|
|
genericFillEnd(filled);
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisFillPainter::genericFillStart(int startX, int startY) {
|
|
|
|
m_cancelRequested = false;
|
|
|
|
|
|
|
|
if (m_width < 0 || m_height < 0) {
|
|
|
|
if (m_device->image()) {
|
|
|
|
m_width = m_device->image()->width();
|
|
|
|
m_height = m_device->image()->height();
|
|
|
|
} else {
|
|
|
|
m_width = m_height = 500;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_size = m_width * m_height;
|
|
|
|
|
|
|
|
// Create a selection from the surrounding area
|
|
|
|
m_selection = createFloodSelection(startX, startY);
|
|
|
|
}
|
|
|
|
|
|
|
|
void KisFillPainter::genericFillEnd(KisPaintDeviceSP filled) {
|
|
|
|
if (m_cancelRequested) {
|
|
|
|
m_width = m_height = -1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQRect rc = m_selection->selectedRect();
|
|
|
|
|
|
|
|
bltSelection(rc.x(), rc.y(), m_compositeOp, filled, m_selection, m_opacity,
|
|
|
|
rc.x(), rc.y(), rc.width(), rc.height());
|
|
|
|
|
|
|
|
emit notifyProgressDone();
|
|
|
|
|
|
|
|
m_width = m_height = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct FillSegment {
|
|
|
|
FillSegment(int x, int y/*, FillSegment* parent*/) : x(x), y(y)/*, parent(parent)*/ {}
|
|
|
|
int x;
|
|
|
|
int y;
|
|
|
|
// FillSegment* parent;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef enum { None = 0, Added = 1, Checked = 2 } tqStatus;
|
|
|
|
|
|
|
|
KisSelectionSP KisFillPainter::createFloodSelection(int startX, int startY) {
|
|
|
|
if (m_width < 0 || m_height < 0) {
|
|
|
|
if (m_device->hasSelection() && m_careForSelection) {
|
|
|
|
|
|
|
|
TQRect rc = m_device->selection()->selectedRect();
|
|
|
|
m_width = rc.width() - (startX - rc.x());
|
|
|
|
m_height = rc.height() - (startY - rc.y());
|
|
|
|
|
|
|
|
} else if (m_device->image()) {
|
|
|
|
|
|
|
|
m_width = m_device->image()->width();
|
|
|
|
m_height = m_device->image()->height();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
m_width = m_height = 500;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't try to fill if we start outside the borders, just return an empty 'fill'
|
|
|
|
if (startX < 0 || startY < 0 || startX >= m_width || startY >= m_height)
|
|
|
|
return new KisSelection(m_device);
|
|
|
|
|
|
|
|
KisPaintDeviceSP sourceDevice = 0;
|
|
|
|
|
|
|
|
// sample merged?
|
|
|
|
if (m_sampleMerged) {
|
|
|
|
if (!m_device->image()) {
|
|
|
|
return new KisSelection(m_device);
|
|
|
|
}
|
|
|
|
sourceDevice = m_device->image()->mergedImage();
|
|
|
|
} else {
|
|
|
|
sourceDevice = m_device;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_size = m_width * m_height;
|
|
|
|
|
|
|
|
KisSelectionSP selection = new KisSelection(m_device);
|
|
|
|
KisColorSpace * colorSpace = selection->colorSpace();
|
|
|
|
KisColorSpace * devColorSpace = sourceDevice->colorSpace();
|
|
|
|
|
|
|
|
TQ_UINT8* source = new TQ_UINT8[sourceDevice->pixelSize()];
|
|
|
|
KisHLineIteratorPixel pixelIt = sourceDevice->createHLineIterator(startX, startY, startX+1, false);
|
|
|
|
|
|
|
|
memcpy(source, pixelIt.rawData(), sourceDevice->pixelSize());
|
|
|
|
|
|
|
|
std::stack<FillSegment*> stack;
|
|
|
|
|
|
|
|
stack.push(new FillSegment(startX, startY/*, 0*/));
|
|
|
|
|
|
|
|
tqStatus* map = new tqStatus[m_size];
|
|
|
|
|
|
|
|
memset(map, None, m_size * sizeof(tqStatus));
|
|
|
|
|
|
|
|
int progressPercent = 0; int pixelsDone = 0; int currentPercent = 0;
|
|
|
|
emit notifyProgressStage(i18n("Making fill outline..."), 0);
|
|
|
|
|
|
|
|
bool hasSelection = m_careForSelection && sourceDevice->hasSelection();
|
|
|
|
KisSelectionSP srcSel = 0;
|
|
|
|
if (hasSelection)
|
|
|
|
srcSel = sourceDevice->selection();
|
|
|
|
|
|
|
|
while(!stack.empty()) {
|
|
|
|
FillSegment* segment = stack.top();
|
|
|
|
stack.pop();
|
|
|
|
if (map[m_width * segment->y + segment->x] == Checked) {
|
|
|
|
delete segment;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
map[m_width * segment->y + segment->x] = Checked;
|
|
|
|
|
|
|
|
int x = segment->x;
|
|
|
|
int y = segment->y;
|
|
|
|
|
|
|
|
/* We need an iterator that is valid in the range (0,y) - (width,y). Therefore,
|
|
|
|
it is needed to start the iterator at the first position, and then skip to (x,y). */
|
|
|
|
pixelIt = sourceDevice->createHLineIterator(0, y, m_width, false);
|
|
|
|
pixelIt += x;
|
|
|
|
TQ_UINT8 diff = devColorSpace->difference(source, pixelIt.rawData());
|
|
|
|
|
|
|
|
if (diff >= m_threshold
|
|
|
|
|| (hasSelection && srcSel->selected(pixelIt.x(), pixelIt.y()) == MIN_SELECTED)) {
|
|
|
|
delete segment;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Here as well: start the iterator at (0,y)
|
|
|
|
KisHLineIteratorPixel selIt = selection->createHLineIterator(0, y, m_width, true);
|
|
|
|
selIt += x;
|
|
|
|
if (m_fuzzy)
|
|
|
|
colorSpace->fromTQColor(TQt::white, MAX_SELECTED - diff, selIt.rawData());
|
|
|
|
else
|
|
|
|
colorSpace->fromTQColor(TQt::white, MAX_SELECTED, selIt.rawData());
|
|
|
|
|
|
|
|
if (y > 0 && (map[m_width * (y - 1) + x] == None)) {
|
|
|
|
map[m_width * (y - 1) + x] = Added;
|
|
|
|
stack.push(new FillSegment(x, y-1));
|
|
|
|
}
|
|
|
|
if (y < (m_height - 1) && (map[m_width * (y + 1) + x] == None)) {
|
|
|
|
map[m_width * (y + 1) + x] = Added;
|
|
|
|
stack.push(new FillSegment(x, y+1));
|
|
|
|
}
|
|
|
|
|
|
|
|
++pixelsDone;
|
|
|
|
|
|
|
|
bool stop = false;
|
|
|
|
|
|
|
|
--pixelIt;
|
|
|
|
--selIt;
|
|
|
|
--x;
|
|
|
|
|
|
|
|
// go to the left
|
|
|
|
while(!stop && x >= 0 && (map[m_width * y + x] != Checked) ) { // FIXME optimizeable?
|
|
|
|
map[m_width * y + x] = Checked;
|
|
|
|
diff = devColorSpace->difference(source, pixelIt.rawData());
|
|
|
|
if (diff >= m_threshold
|
|
|
|
|| (hasSelection && srcSel->selected(pixelIt.x(), pixelIt.y()) == MIN_SELECTED)) {
|
|
|
|
stop = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_fuzzy)
|
|
|
|
colorSpace->fromTQColor(TQt::white, MAX_SELECTED - diff, selIt.rawData());
|
|
|
|
else
|
|
|
|
colorSpace->fromTQColor(TQt::white, MAX_SELECTED, selIt.rawData());
|
|
|
|
|
|
|
|
if (y > 0 && (map[m_width * (y - 1) + x] == None)) {
|
|
|
|
map[m_width * (y - 1) + x] = Added;
|
|
|
|
stack.push(new FillSegment(x, y-1));
|
|
|
|
}
|
|
|
|
if (y < (m_height - 1) && (map[m_width * (y + 1) + x] == None)) {
|
|
|
|
map[m_width * (y + 1) + x] = Added;
|
|
|
|
stack.push(new FillSegment(x, y+1));
|
|
|
|
}
|
|
|
|
++pixelsDone;
|
|
|
|
--pixelIt;
|
|
|
|
--selIt;
|
|
|
|
--x;
|
|
|
|
}
|
|
|
|
|
|
|
|
x = segment->x + 1;
|
|
|
|
delete segment;
|
|
|
|
|
|
|
|
if (map[m_width * y + x] == Checked)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// and go to the right
|
|
|
|
pixelIt = sourceDevice->createHLineIterator(x, y, m_width, false);
|
|
|
|
selIt = selection->createHLineIterator(x, y, m_width, true);
|
|
|
|
|
|
|
|
stop = false;
|
|
|
|
while(!stop && x < m_width && (map[m_width * y + x] != Checked) ) {
|
|
|
|
diff = devColorSpace->difference(source, pixelIt.rawData());
|
|
|
|
map[m_width * y + x] = Checked;
|
|
|
|
|
|
|
|
if (diff >= m_threshold
|
|
|
|
|| (hasSelection && srcSel->selected(pixelIt.x(), pixelIt.y()) == MIN_SELECTED) ) {
|
|
|
|
stop = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_fuzzy)
|
|
|
|
colorSpace->fromTQColor(TQt::white, MAX_SELECTED - diff, selIt.rawData());
|
|
|
|
else
|
|
|
|
colorSpace->fromTQColor(TQt::white, MAX_SELECTED, selIt.rawData());
|
|
|
|
|
|
|
|
if (y > 0 && (map[m_width * (y - 1) + x] == None)) {
|
|
|
|
map[m_width * (y - 1) + x] = Added;
|
|
|
|
stack.push(new FillSegment(x, y-1));
|
|
|
|
}
|
|
|
|
if (y < (m_height - 1) && (map[m_width * (y + 1) + x] == None)) {
|
|
|
|
map[m_width * (y + 1) + x] = Added;
|
|
|
|
stack.push(new FillSegment(x, y+1));
|
|
|
|
}
|
|
|
|
++pixelsDone;
|
|
|
|
++pixelIt;
|
|
|
|
++selIt;
|
|
|
|
++x;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_size > 0) {
|
|
|
|
progressPercent = (pixelsDone * 100) / m_size;
|
|
|
|
if (progressPercent > currentPercent) {
|
|
|
|
emit notifyProgress(progressPercent);
|
|
|
|
currentPercent = progressPercent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
delete[] map;
|
|
|
|
delete[] source;
|
|
|
|
|
|
|
|
return selection;
|
|
|
|
}
|