diff options
Diffstat (limited to 'chalk/core/kis_fill_painter.cpp')
-rw-r--r-- | chalk/core/kis_fill_painter.cpp | 407 |
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; +} |