summaryrefslogtreecommitdiffstats
path: root/chalk/core/kis_fill_painter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'chalk/core/kis_fill_painter.cpp')
-rw-r--r--chalk/core/kis_fill_painter.cpp407
1 files changed, 407 insertions, 0 deletions
diff --git a/chalk/core/kis_fill_painter.cpp b/chalk/core/kis_fill_painter.cpp
new file mode 100644
index 00000000..6b88759a
--- /dev/null
+++ b/chalk/core/kis_fill_painter.cpp
@@ -0,0 +1,407 @@
+/*
+ * 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 <tdelocale.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");
+ TQ_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");
+ TQ_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 } Status;
+
+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*/));
+
+ Status* map = new Status[m_size];
+
+ memset(map, None, m_size * sizeof(Status));
+
+ 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;
+}