diff --git a/libs/image/kis_cached_paint_device.h b/libs/image/kis_cached_paint_device.h index e4c3b39c60..1cb038d39b 100644 --- a/libs/image/kis_cached_paint_device.h +++ b/libs/image/kis_cached_paint_device.h @@ -1,71 +1,73 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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. */ #ifndef __KIS_CACHED_PAINT_DEVICE_H #define __KIS_CACHED_PAINT_DEVICE_H #include "tiles3/kis_lockless_stack.h" +#include "kis_paint_device.h" +#include "kis_selection.h" class KisCachedPaintDevice { public: KisPaintDeviceSP getDevice(KisPaintDeviceSP prototype) { KisPaintDeviceSP device; if(!m_stack.pop(device)) { device = new KisPaintDevice(prototype->colorSpace()); } device->prepareClone(prototype); return device; } void putDevice(KisPaintDeviceSP device) { device->clear(); device->setDefaultBounds(new KisDefaultBounds()); m_stack.push(device); } private: KisLocklessStack m_stack; }; class KisCachedSelection { public: KisSelectionSP getSelection() { KisSelectionSP selection; if(!m_stack.pop(selection)) { selection = new KisSelection(); } return selection; } void putSelection(KisSelectionSP selection) { selection->clear(); selection->setDefaultBounds(new KisDefaultBounds()); m_stack.push(selection); } private: KisLocklessStack m_stack; }; #endif /* __KIS_CACHED_PAINT_DEVICE_H */ diff --git a/libs/image/kis_fill_painter.cc b/libs/image/kis_fill_painter.cc index 05372cb713..b38058d5cf 100644 --- a/libs/image/kis_fill_painter.cc +++ b/libs/image/kis_fill_painter.cc @@ -1,317 +1,324 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Bart Coppens * Copyright (c) 2010 Lukáš Tvrdý * * 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 "kis_fill_painter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "generator/kis_generator.h" #include "filter/kis_filter_configuration.h" #include "generator/kis_generator_registry.h" #include "kis_processing_information.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_paint_device.h" #include #include "KoColorSpace.h" #include "kis_transaction.h" #include "kis_pixel_selection.h" #include #include #include "kis_selection_filters.h" KisFillPainter::KisFillPainter() : KisPainter() { initFillPainter(); } KisFillPainter::KisFillPainter(KisPaintDeviceSP device) : KisPainter(device) { initFillPainter(); } KisFillPainter::KisFillPainter(KisPaintDeviceSP device, KisSelectionSP selection) : KisPainter(device, selection) { initFillPainter(); } void KisFillPainter::initFillPainter() { m_width = m_height = -1; m_careForSelection = false; m_sizemod = 0; m_feather = 0; m_useCompositioning = false; m_threshold = 0; } void KisFillPainter::fillSelection(const QRect &rc, const KoColor &color) { KisPaintDeviceSP fillDevice = new KisPaintDevice(device()->colorSpace()); fillDevice->setDefaultPixel(color); bitBlt(rc.topLeft(), fillDevice, rc); } // '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(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoColor& kc, quint8 opacity) { if (w > 0 && h > 0) { // Make sure we're in the right colorspace KoColor kc2(kc); // get rid of const kc2.convertTo(device()->colorSpace()); quint8 * data = kc2.data(); device()->colorSpace()->setOpacity(data, opacity, 1); device()->fill(x1, y1, w, h, data); addDirtyRect(QRect(x1, y1, w, h)); } } -void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPattern * pattern) +void KisFillPainter::fillRect(const QRect &rc, const KoPattern *pattern, const QPoint &offset) +{ + fillRect(rc.x(), rc.y(), rc.width(), rc.height(), pattern, offset); +} + +void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPattern * pattern, const QPoint &offset) { if (!pattern) return; if (!pattern->valid()) return; if (!device()) return; if (w < 1) return; if (h < 1) return; KisPaintDeviceSP patternLayer = new KisPaintDevice(device()->compositionSourceColorSpace(), pattern->name()); patternLayer->convertFromQImage(pattern->pattern(), 0); - fillRect(x1, y1, w, h, patternLayer, QRect(0, 0, pattern->width(), pattern->height())); + if (!offset.isNull()) { + patternLayer->moveTo(offset); + } + + fillRect(x1, y1, w, h, patternLayer, QRect(offset.x(), offset.y(), pattern->width(), pattern->height())); } void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect) { - Q_ASSERT(deviceRect.x() == 0); // the case x,y != 0,0 is not yet implemented - Q_ASSERT(deviceRect.y() == 0); - int sx, sy, sw, sh; + const QRect &patternRect = deviceRect; + const QRect fillRect(x1, y1, w, h); - int y = y1; + auto toPatternLocal = [](int value, int offset, int width) { + const int normalizedValue = value - offset; + return offset + (normalizedValue >= 0 ? + normalizedValue % width : + width - (-normalizedValue - 1) % width - 1); + }; - if (y >= 0) { - sy = y % deviceRect.height(); - } else { - sy = deviceRect.height() - (((-y - 1) % deviceRect.height()) + 1); - } + int dstY = fillRect.y(); + while (dstY <= fillRect.bottom()) { + const int dstRowsRemaining = fillRect.bottom() - dstY + 1; - while (y < y1 + h) { - sh = qMin((y1 + h) - y, deviceRect.height() - sy); + const int srcY = toPatternLocal(dstY, patternRect.y(), patternRect.height()); + const int height = qMin(patternRect.height() - srcY + patternRect.y(), dstRowsRemaining); - int x = x1; + int dstX = fillRect.x(); + while (dstX <= fillRect.right()) { + const int dstColumnsRemaining = fillRect.right() - dstX + 1; - if (x >= 0) { - sx = x % deviceRect.width(); - } else { - sx = deviceRect.width() - (((-x - 1) % deviceRect.width()) + 1); - } + const int srcX = toPatternLocal(dstX, patternRect.x(), patternRect.width()); + const int width = qMin(patternRect.width() - srcX + patternRect.x(), dstColumnsRemaining); - while (x < x1 + w) { - sw = qMin((x1 + w) - x, deviceRect.width() - sx); + bitBlt(dstX, dstY, device, srcX, srcY, width, height); - bitBlt(x, y, device, sx, sy, sw, sh); - x += sw; sx = 0; + dstX += width; } - - y += sh; sy = 0; + dstY += height; } addDirtyRect(QRect(x1, y1, w, h)); } void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisFilterConfigurationSP generator) { if (!generator) return; KisGeneratorSP g = KisGeneratorRegistry::instance()->value(generator->name()); if (!device()) return; if (w < 1) return; if (h < 1) return; QRect tmpRc(x1, y1, w, h); KisProcessingInformation dstCfg(device(), tmpRc.topLeft(), 0); g->generate(dstCfg, tmpRc.size(), generator); addDirtyRect(tmpRc); } // flood filling void KisFillPainter::fillColor(int startX, int startY, KisPaintDeviceSP sourceDevice) { if (!m_useCompositioning) { if (m_sizemod || m_feather || compositeOp()->id() != COMPOSITE_OVER || opacity() != MAX_SELECTED || sourceDevice != device()) { warnKrita << "WARNING: Fast Flood Fill (no compositioning mode)" << "does not support compositeOps, opacity, " << "selection enhancements and separate source " << "devices"; } QRect fillBoundsRect(0, 0, m_width, m_height); QPoint startPoint(startX, startY); if (!fillBoundsRect.contains(startPoint)) return; KisScanlineFill gc(device(), startPoint, fillBoundsRect); gc.setThreshold(m_threshold); gc.fillColor(paintColor()); } else { genericFillStart(startX, startY, sourceDevice); // Now create a layer and fill it KisPaintDeviceSP filled = device()->createCompositionSourceDevice(); Q_CHECK_PTR(filled); KisFillPainter painter(filled); painter.fillRect(0, 0, m_width, m_height, paintColor()); painter.end(); genericFillEnd(filled); } } void KisFillPainter::fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice) { genericFillStart(startX, startY, sourceDevice); // Now create a layer and fill it KisPaintDeviceSP filled = device()->createCompositionSourceDevice(); Q_CHECK_PTR(filled); KisFillPainter painter(filled); painter.fillRect(0, 0, m_width, m_height, pattern()); painter.end(); genericFillEnd(filled); } void KisFillPainter::genericFillStart(int startX, int startY, KisPaintDeviceSP sourceDevice) { Q_ASSERT(m_width > 0); Q_ASSERT(m_height > 0); // Create a selection from the surrounding area m_fillSelection = createFloodSelection(startX, startY, sourceDevice); } void KisFillPainter::genericFillEnd(KisPaintDeviceSP filled) { if (progressUpdater() && progressUpdater()->interrupted()) { m_width = m_height = -1; return; } // TODO: filling using the correct bound of the selection would be better, *but* // the selection is limited to the exact bound of a layer, while in reality, we don't // want that, since we want a transparent layer to be completely filled // QRect rc = m_fillSelection->selectedExactRect(); /** * Apply the real selection to a filled one */ KisSelectionSP realSelection = selection(); if (realSelection) { m_fillSelection->pixelSelection()->applySelection( realSelection->projection(), SELECTION_INTERSECT); } setSelection(m_fillSelection); bitBlt(0, 0, filled, 0, 0, m_width, m_height); setSelection(realSelection); if (progressUpdater()) progressUpdater()->setProgress(100); m_width = m_height = -1; } KisSelectionSP KisFillPainter::createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice) { if (m_width < 0 || m_height < 0) { if (selection() && m_careForSelection) { QRect rc = selection()->selectedExactRect(); m_width = rc.width() - (startX - rc.x()); m_height = rc.height() - (startY - rc.y()); } } dbgImage << "Width: " << m_width << " Height: " << m_height; // Otherwise the width and height should have been set Q_ASSERT(m_width > 0 && m_height > 0); QRect fillBoundsRect(0, 0, m_width, m_height); QPoint startPoint(startX, startY); KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(device())); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (!fillBoundsRect.contains(startPoint)) { return selection; } KisScanlineFill gc(sourceDevice, startPoint, fillBoundsRect); gc.setThreshold(m_threshold); gc.fillSelection(pixelSelection); if (m_sizemod > 0) { KisGrowSelectionFilter biggy(m_sizemod, m_sizemod); biggy.process(pixelSelection, selection->selectedRect().adjusted(-m_sizemod, -m_sizemod, m_sizemod, m_sizemod)); } else if (m_sizemod < 0) { KisShrinkSelectionFilter tiny(-m_sizemod, -m_sizemod, false); tiny.process(pixelSelection, selection->selectedRect()); } if (m_feather > 0) { KisFeatherSelectionFilter feathery(m_feather); feathery.process(pixelSelection, selection->selectedRect().adjusted(-m_feather, -m_feather, m_feather, m_feather)); } return selection; } diff --git a/libs/image/kis_fill_painter.h b/libs/image/kis_fill_painter.h index b6132d6a9c..4f1648a60e 100644 --- a/libs/image/kis_fill_painter.h +++ b/libs/image/kis_fill_painter.h @@ -1,285 +1,281 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Bart Coppens * * 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. */ #ifndef KIS_FILL_PAINTER_H_ #define KIS_FILL_PAINTER_H_ #include #include "KoColor.h" #include "KoColorSpaceRegistry.h" #include "kis_painter.h" #include "kis_types.h" #include "kis_selection.h" #include class KoPattern; class KisFilterConfiguration; // XXX: Filling should set dirty rect. /** * This painter can be used to fill paint devices in different ways. This can also be used * for flood filling related operations. */ class KRITAIMAGE_EXPORT KisFillPainter : public KisPainter { public: /** * Construct an empty painter. Use the begin(KisPaintDeviceSP) method to attach * to a paint device */ KisFillPainter(); /** * Start painting on the specified paint device */ KisFillPainter(KisPaintDeviceSP device); KisFillPainter(KisPaintDeviceSP device, KisSelectionSP selection); private: void initFillPainter(); public: /** * Fill a rectangle with black transparent pixels (0, 0, 0, 0 for RGBA). */ void eraseRect(qint32 x1, qint32 y1, qint32 w, qint32 h); /** * Overloaded version of the above function. */ void eraseRect(const QRect& rc); /** * Fill current selection of KisPainter with a specified \p color. * * The filling rect is limited by \p rc to allow multithreaded * filling/processing. */ void fillSelection(const QRect &rc, const KoColor &color); /** * Fill a rectangle with a certain color. */ void fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor& c); /** * Overloaded version of the above function. */ void fillRect(const QRect& rc, const KoColor& c); /** * Fill a rectangle with a certain color and opacity. */ void fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor& c, quint8 opacity); /** * Overloaded version of the above function. */ void fillRect(const QRect& rc, const KoColor& c, quint8 opacity); /** * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the * entire rectangle. */ - void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPattern * pattern); + void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPattern * pattern, const QPoint &offset = QPoint()); /** * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the * entire rectangle. */ void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect); /** * Overloaded version of the above function. */ - void fillRect(const QRect& rc, const KoPattern * pattern); + void fillRect(const QRect& rc, const KoPattern * pattern, const QPoint &offset = QPoint()); /** * Fill the specified area with the output of the generator plugin that is configured * in the generator parameter */ void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisFilterConfigurationSP generator); /** * Fills the enclosed area around the point with the set color. If * there is a selection, the whole selection is filled. Note that * you must have set the width and height on the painter if you * don't have a selection. * * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on */ void fillColor(int startX, int startY, KisPaintDeviceSP sourceDevice); /** * Fills the enclosed area around the point with the set pattern. * If there is a selection, the whole selection is filled. Note * that you must have set the width and height on the painter if * you don't have a selection. * * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on */ void fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice); /** * Returns a selection mask for the floodfill starting at the specified position. * * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on */ KisSelectionSP createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice); /** * Set the threshold for floodfill. The range is 0-255: 0 means the fill will only * fill parts that are the exact same color, 255 means anything will be filled */ void setFillThreshold(int threshold); /** Returns the fill threshold, see setFillThreshold for details */ int fillThreshold() const { return m_threshold; } bool useCompositioning() const { return m_useCompositioning; } void setUseCompositioning(bool useCompositioning) { m_useCompositioning = useCompositioning; } /** Sets the width of the paint device */ void setWidth(int w) { m_width = w; } /** Sets the height of the paint device */ void setHeight(int h) { m_height = h; } /** If true, floodfill doesn't fill outside the selected area of a layer */ bool careForSelection() const { return m_careForSelection; } /** Set caring for selection. See careForSelection for details */ void setCareForSelection(bool set) { m_careForSelection = set; } /** Sets the auto growth/shrinking radius */ void setSizemod(int sizemod) { m_sizemod = sizemod; } /** Sets how much to auto-grow or shrink (if @param sizemod is negative) the selection flood before painting, this affects every fill operation except fillRect */ int sizemod() { return m_sizemod; } /** Sets feathering radius */ void setFeather(int feather) { m_feather = feather; } /** defines the feathering radius for selection flood operations, this affects every fill operation except fillRect */ uint feather() { return m_feather; } private: // for floodfill void genericFillStart(int startX, int startY, KisPaintDeviceSP sourceDevice); void genericFillEnd(KisPaintDeviceSP filled); KisSelectionSP m_fillSelection; int m_feather; int m_sizemod; int m_threshold; int m_width, m_height; QRect m_rect; bool m_careForSelection; bool m_useCompositioning; }; inline void KisFillPainter::fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor& c) { fillRect(x, y, w, h, c, OPACITY_OPAQUE_U8); } inline void KisFillPainter::fillRect(const QRect& rc, const KoColor& c) { fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, OPACITY_OPAQUE_U8); } inline void KisFillPainter::eraseRect(qint32 x1, qint32 y1, qint32 w, qint32 h) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor c(Qt::black, cs); fillRect(x1, y1, w, h, c, OPACITY_TRANSPARENT_U8); } inline void KisFillPainter::eraseRect(const QRect& rc) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor c(Qt::black, cs); fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, OPACITY_TRANSPARENT_U8); } inline void KisFillPainter::fillRect(const QRect& rc, const KoColor& c, quint8 opacity) { fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, opacity); } -inline -void KisFillPainter::fillRect(const QRect& rc, const KoPattern* pattern) -{ - fillRect(rc.x(), rc.y(), rc.width(), rc.height(), pattern); -} + inline void KisFillPainter::setFillThreshold(int threshold) { m_threshold = threshold; } #endif //KIS_FILL_PAINTER_H_ diff --git a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp index fea07ce239..c8ff470e5e 100644 --- a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp +++ b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp @@ -1,117 +1,117 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * 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 "kis_layer_style_filter_projection_plane.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_layer_style_filter.h" #include "kis_layer_style_filter_environment.h" #include "kis_psd_layer_style.h" #include "kis_painter.h" #include "kis_multiple_projection.h" struct KisLayerStyleFilterProjectionPlane::Private { KisLayer *sourceLayer; QScopedPointer filter; KisPSDLayerStyleSP style; QScopedPointer environment; KisMultipleProjection projection; }; KisLayerStyleFilterProjectionPlane:: KisLayerStyleFilterProjectionPlane(KisLayer *sourceLayer) : m_d(new Private) { Q_ASSERT(sourceLayer); m_d->sourceLayer = sourceLayer; m_d->environment.reset(new KisLayerStyleFilterEnvironment(sourceLayer)); } KisLayerStyleFilterProjectionPlane::~KisLayerStyleFilterProjectionPlane() { } void KisLayerStyleFilterProjectionPlane::setStyle(KisLayerStyleFilter *filter, KisPSDLayerStyleSP style) { m_d->filter.reset(filter); m_d->style = style; } QRect KisLayerStyleFilterProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode) { Q_UNUSED(filthyNode); if (!m_d->sourceLayer || !m_d->filter) { warnKrita << "KisLayerStyleFilterProjectionPlane::recalculate(): [BUG] is not initialized"; return QRect(); } m_d->projection.clear(rect); m_d->filter->processDirectly(m_d->sourceLayer->projection(), &m_d->projection, rect, m_d->style, m_d->environment.data()); return rect; } void KisLayerStyleFilterProjectionPlane::apply(KisPainter *painter, const QRect &rect) { - m_d->projection.apply(painter->device(), rect); + m_d->projection.apply(painter->device(), rect, m_d->environment.data()); } KisPaintDeviceList KisLayerStyleFilterProjectionPlane::getLodCapableDevices() const { return m_d->projection.getLodCapableDevices(); } QRect KisLayerStyleFilterProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { if (!m_d->sourceLayer || !m_d->filter) { warnKrita << "KisLayerStyleFilterProjectionPlane::needRect(): [BUG] is not initialized"; return rect; } KIS_ASSERT_RECOVER_NOOP(pos == KisLayer::N_ABOVE_FILTHY); return m_d->filter->neededRect(rect, m_d->style, m_d->environment.data()); } QRect KisLayerStyleFilterProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { if (!m_d->sourceLayer || !m_d->filter) { warnKrita << "KisLayerStyleFilterProjectionPlane::changeRect(): [BUG] is not initialized"; return rect; } KIS_ASSERT_RECOVER_NOOP(pos == KisLayer::N_ABOVE_FILTHY); return m_d->filter->changedRect(rect, m_d->style, m_d->environment.data()); } QRect KisLayerStyleFilterProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { return needRect(rect, pos); } diff --git a/libs/image/layerstyles/kis_ls_bevel_emboss_filter.cpp b/libs/image/layerstyles/kis_ls_bevel_emboss_filter.cpp index 08432cd975..a04f8166b2 100644 --- a/libs/image/layerstyles/kis_ls_bevel_emboss_filter.cpp +++ b/libs/image/layerstyles/kis_ls_bevel_emboss_filter.cpp @@ -1,493 +1,492 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * See LayerFX plugin for Gimp as a reference implementation of this style: * http://registry.gimp.org/node/186 * * 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 "kis_ls_bevel_emboss_filter.h" #include #include #include #include #include #include "psd.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_psd_layer_style.h" #include "kis_layer_style_filter_environment.h" #include "kis_ls_utils.h" #include "gimp_bump_map.h" #include "kis_transaction.h" #include "kis_multiple_projection.h" KisLsBevelEmbossFilter::KisLsBevelEmbossFilter() : KisLayerStyleFilter(KoID("lsstroke", i18n("Stroke (style)"))) { } void paintBevelSelection(KisPixelSelectionSP srcSelection, KisPixelSelectionSP dstSelection, const QRect &applyRect, int size, int initialSize, bool invert) { KisSelectionSP tmpBaseSelection = new KisSelection(new KisSelectionEmptyBounds(0)); KisPixelSelectionSP tmpSelection = tmpBaseSelection->pixelSelection(); // NOTE: we are not using createCompositionSourceDevice() intentionally, // because the source device doesn't have alpha channel KisPixelSelectionSP fillDevice = new KisPixelSelection(); KisPainter gc(dstSelection); gc.setCompositeOp(COMPOSITE_COPY); for (int i = 0; i < size; i++) { const int growSize = initialSize - i - 1; quint8 selectedness = invert ? qRound(qreal(size - i - 1) / size * 255.0) : qRound(qreal(i + 1) / size * 255.0); fillDevice->setDefaultPixel(KoColor(&selectedness, fillDevice->colorSpace())); tmpSelection->makeCloneFromRough(srcSelection, srcSelection->selectedRect()); QRect changeRect = KisLsUtils::growSelectionUniform(tmpSelection, growSize, applyRect); gc.setSelection(tmpBaseSelection); gc.bitBlt(changeRect.topLeft(), fillDevice, changeRect); } } struct ContrastOp { static const bool supportsCaching = false; ContrastOp(qreal contrast) : m_contrast(contrast) { } int operator() (int iValue) { qreal value = qreal(iValue - 127) / 127.0; qreal slant = std::tan ((m_contrast + 1) * M_PI_4); value = (value - 0.5) * slant + 0.5; return qRound(value * 255.0); } private: qreal m_contrast; }; struct HighlightsFetchOp { static const bool supportsCaching = true; int operator() (int value) { return qRound(qMax(0, value - 127) * (255.0 / (255 - 127))); } }; struct ShadowsFetchOp { static const bool supportsCaching = true; int operator() (int value) { return 255 - qRound(qMin(value, 127) * (255.0 / 127.0)); } }; template void mapPixelValues(KisPixelSelectionSP srcSelection, KisPixelSelectionSP dstSelection, MapOp mapOp, const QRect &applyRect) { static quint8 mapTable[256]; static bool mapInitialized = false; if (!MapOp::supportsCaching || !mapInitialized) { mapInitialized = true; for (int i = 0; i < 256; i++) { mapTable[i] = mapOp(i); } } KisSequentialConstIterator srcIt(srcSelection, applyRect); KisSequentialIterator dstIt(dstSelection, applyRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *dstPtr = dstIt.rawData(); *dstPtr = mapTable[*srcPtr]; } } template void mapPixelValues(KisPixelSelectionSP dstSelection, MapOp mapOp, const QRect &applyRect) { static quint8 mapTable[256]; static bool mapInitialized = false; if (!MapOp::supportsCaching || !mapInitialized) { mapInitialized = true; for (int i = 0; i < 256; i++) { mapTable[i] = mapOp(i); } } KisSequentialIterator dstIt(dstSelection, applyRect); while (dstIt.nextPixel()) { quint8 *dstPtr = dstIt.rawData(); *dstPtr = mapTable[*dstPtr]; } } struct BevelEmbossRectCalculator { BevelEmbossRectCalculator(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { shadowHighlightsFinalRect = applyRect; applyGaussianRect = shadowHighlightsFinalRect; applyGlossContourRect = KisLsUtils::growRectFromRadius(applyGaussianRect, config->soften()); applyBumpmapRect = applyGlossContourRect; applyContourRect = applyBumpmapRect; applyTextureRect = applyContourRect; applyBevelRect = calcBevelNeedRect(applyTextureRect, config); initialFetchRect = kisGrowRect(applyBevelRect, 1); } QRect totalChangeRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { QRect changeRect = calcBevelChangeRect(applyRect, config); changeRect = kisGrowRect(changeRect, 1); // bumpmap method changeRect = KisLsUtils::growRectFromRadius(changeRect, config->soften()); return changeRect; } QRect totalNeedRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { QRect changeRect = applyRect; changeRect = KisLsUtils::growRectFromRadius(changeRect, config->soften()); changeRect = kisGrowRect(changeRect, 1); // bumpmap method changeRect = calcBevelNeedRect(applyRect, config); return changeRect; } QRect initialFetchRect; QRect applyBevelRect; QRect applyTextureRect; QRect applyContourRect; QRect applyBumpmapRect; QRect applyGlossContourRect; QRect applyGaussianRect; QRect shadowHighlightsFinalRect; private: QRect calcBevelChangeRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { const int size = config->size(); int limitingGrowSize = 0; switch (config->style()) { case psd_bevel_outer_bevel: limitingGrowSize = size; break; case psd_bevel_inner_bevel: limitingGrowSize = 0; break; case psd_bevel_emboss: { const int initialSize = std::ceil(qreal(size) / 2.0); limitingGrowSize = initialSize; break; } case psd_bevel_pillow_emboss: { const int halfSizeC = std::ceil(qreal(size) / 2.0); limitingGrowSize = halfSizeC; break; } case psd_bevel_stroke_emboss: warnKrita << "WARNING: Stroke Emboss style is not implemented yet!"; return applyRect; } return kisGrowRect(applyRect, limitingGrowSize); } QRect calcBevelNeedRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { const int size = config->size(); int limitingGrowSize = size; return kisGrowRect(applyRect, limitingGrowSize); } }; void KisLsBevelEmbossFilter::applyBevelEmboss(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &applyRect, const psd_layer_effects_bevel_emboss *config, KisLayerStyleFilterEnvironment *env) const { if (applyRect.isEmpty()) return; BevelEmbossRectCalculator d(applyRect, config); KisSelectionSP baseSelection = KisLsUtils::selectionFromAlphaChannel(srcDevice, d.initialFetchRect); KisPixelSelectionSP selection = baseSelection->pixelSelection(); //selection->convertToQImage(0, QRect(0,0,300,300)).save("0_selection_initial.png"); const int size = config->size(); int limitingGrowSize = 0; KisPixelSelectionSP bumpmapSelection = new KisPixelSelection(new KisSelectionEmptyBounds(0)); switch (config->style()) { case psd_bevel_outer_bevel: paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, size, false); limitingGrowSize = size; break; case psd_bevel_inner_bevel: paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, 0, false); limitingGrowSize = 0; break; case psd_bevel_emboss: { const int initialSize = std::ceil(qreal(size) / 2.0); paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, initialSize, false); limitingGrowSize = initialSize; break; } case psd_bevel_pillow_emboss: { const int halfSizeF = std::floor(qreal(size) / 2.0); const int halfSizeC = std::ceil(qreal(size) / 2.0); // TODO: probably not correct! paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, halfSizeC, halfSizeC, false); paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, halfSizeF, 0, true); limitingGrowSize = halfSizeC; break; } case psd_bevel_stroke_emboss: warnKrita << "WARNING: Stroke Emboss style is not implemented yet!"; return; } KisPixelSelectionSP limitingSelection = new KisPixelSelection(*selection); { QRect changeRectUnused = KisLsUtils::growSelectionUniform(limitingSelection, limitingGrowSize, d.applyBevelRect); Q_UNUSED(changeRectUnused); } //bumpmapSelection->convertToQImage(0, QRect(0,0,300,300)).save("1_selection_xconv.png"); if (config->textureEnabled()) { KisPixelSelectionSP textureSelection = new KisPixelSelection(new KisSelectionEmptyBounds(0)); KisLsUtils::fillPattern(textureSelection, d.applyTextureRect, env, config->textureScale(), config->texturePattern(), config->textureHorizontalPhase(), config->textureVerticalPhase(), config->textureAlignWithLayer()); int contrastadj = 0; { using namespace std; int tex_depth = config->textureDepth(); if (tex_depth >= 0.0) { if (tex_depth <= 100.0) { contrastadj = int(qRound((1-(tex_depth/100.0)) * -127)); } else { contrastadj = int(qRound(((tex_depth-100.0)/900.0) * 127)); } } else { textureSelection->invert(); if (tex_depth >= -100.0) { contrastadj = int(qRound((1-(abs(tex_depth)/100.0)) * -127)); } else { contrastadj = int(qRound(((abs(tex_depth)-100.0)/900.0) * 127)); } } } qreal contrast = qBound(-1.0, qreal(contrastadj) / 127.0, 1.0); mapPixelValues(textureSelection, ContrastOp(contrast), d.applyTextureRect); { KisPainter gc(bumpmapSelection); gc.setCompositeOp(COMPOSITE_MULT); gc.bitBlt(d.applyTextureRect.topLeft(), textureSelection, d.applyTextureRect); gc.end(); } } //bumpmapSelection->convertToQImage(0, QRect(0,0,300,300)).save("15_selection_texture.png"); if (config->contourEnabled()) { if (config->range() != KisLsUtils::FULL_PERCENT_RANGE) { KisLsUtils::adjustRange(bumpmapSelection, d.applyContourRect, config->range()); } KisLsUtils::applyContourCorrection(bumpmapSelection, d.applyContourRect, config->contourLookupTable(), config->antiAliased(), true); } bumpmap_vals_t bmvals; bmvals.azimuth = config->angle(); bmvals.elevation = config->altitude(); bmvals.depth = config->depth(); bmvals.ambient = 0; bmvals.compensate = true; bmvals.invert = config->direction() == psd_direction_down; bmvals.type = LINEAR; bumpmap(bumpmapSelection, d.applyBumpmapRect, bmvals); //bumpmapSelection->convertToQImage(0, QRect(0,0,300,300)).save("3_selection_bumpmap.png"); { // TODO: optimize! KisLsUtils::applyContourCorrection(bumpmapSelection, d.applyGlossContourRect, config->glossContourLookupTable(), config->glossAntiAliased(), true); } if (config->soften()) { KisLsUtils::applyGaussian(bumpmapSelection, d.applyGaussianRect, config->soften()); } if (config->textureEnabled() && config->textureInvert()) { bumpmapSelection->invert(); } selection->clear(); mapPixelValues(bumpmapSelection, selection, ShadowsFetchOp(), d.shadowHighlightsFinalRect); selection->applySelection(limitingSelection, SELECTION_INTERSECT); //dstDevice->convertToQImage(0, QRect(0,0,300,300)).save("4_dst_before_apply.png"); //selection->convertToQImage(0, QRect(0,0,300,300)).save("4_shadows_sel.png"); { - KisPaintDeviceSP dstDevice = dst->getProjection("00_bevel_shadow", config->shadowBlendMode(), srcDevice); + KisPaintDeviceSP dstDevice = dst->getProjection("00_bevel_shadow", + config->shadowBlendMode(), + config->shadowOpacity(), + QBitArray(), + srcDevice); const KoColor fillColor(config->shadowColor(), dstDevice->colorSpace()); const QRect &fillRect = d.shadowHighlightsFinalRect; KisPaintDeviceSP fillDevice = new KisPaintDevice(dstDevice->colorSpace()); fillDevice->setDefaultPixel(fillColor); - KisPainter gc(dstDevice); - gc.setSelection(baseSelection); - gc.setCompositeOp(COMPOSITE_OVER); - env->setupFinalPainter(&gc, config->shadowOpacity(), QBitArray()); - gc.bitBlt(fillRect.topLeft(), fillDevice, fillRect); - gc.end(); + KisPainter::copyAreaOptimized(fillRect.topLeft(), fillDevice, dstDevice, fillRect, baseSelection); } selection->clear(); mapPixelValues(bumpmapSelection, selection, HighlightsFetchOp(), d.shadowHighlightsFinalRect); selection->applySelection(limitingSelection, SELECTION_INTERSECT); //selection->convertToQImage(0, QRect(0,0,300,300)).save("5_highlights_sel.png"); { - KisPaintDeviceSP dstDevice = dst->getProjection("01_bevel_highlight", config->highlightBlendMode(), srcDevice); + KisPaintDeviceSP dstDevice = dst->getProjection("01_bevel_highlight", + config->highlightBlendMode(), + config->highlightOpacity(), + QBitArray(), + srcDevice); const KoColor fillColor(config->highlightColor(), dstDevice->colorSpace()); const QRect &fillRect = d.shadowHighlightsFinalRect; KisPaintDeviceSP fillDevice = new KisPaintDevice(dstDevice->colorSpace()); fillDevice->setDefaultPixel(fillColor); - KisPainter gc(dstDevice); - gc.setSelection(baseSelection); - gc.setCompositeOp(COMPOSITE_OVER); - env->setupFinalPainter(&gc, config->highlightOpacity(), QBitArray()); - gc.bitBlt(fillRect.topLeft(), fillDevice, fillRect); - gc.end(); + + KisPainter::copyAreaOptimized(fillRect.topLeft(), fillDevice, dstDevice, fillRect, baseSelection); } } void KisLsBevelEmbossFilter::processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(env); KIS_ASSERT_RECOVER_RETURN(style); const psd_layer_effects_bevel_emboss *config = style->bevelAndEmboss(); if (!KisLsUtils::checkEffectEnabled(config, dst)) return; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); applyBevelEmboss(src, dst, applyRect, w.config, env); } QRect KisLsBevelEmbossFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_bevel_emboss *config = style->bevelAndEmboss(); if (!config->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); BevelEmbossRectCalculator d(rect, w.config); return d.totalNeedRect(rect, w.config); } QRect KisLsBevelEmbossFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_bevel_emboss *config = style->bevelAndEmboss(); if (!config->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); BevelEmbossRectCalculator d(rect, w.config); return d.totalChangeRect(rect, w.config); } diff --git a/libs/image/layerstyles/kis_ls_overlay_filter.cpp b/libs/image/layerstyles/kis_ls_overlay_filter.cpp index baa1494793..d709b2992b 100644 --- a/libs/image/layerstyles/kis_ls_overlay_filter.cpp +++ b/libs/image/layerstyles/kis_ls_overlay_filter.cpp @@ -1,143 +1,133 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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 "kis_ls_overlay_filter.h" #include #include #include #include #include "psd.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_psd_layer_style.h" #include "kis_layer_style_filter_environment.h" #include "kis_ls_utils.h" #include "kis_multiple_projection.h" KisLsOverlayFilter::KisLsOverlayFilter(Mode mode) : KisLayerStyleFilter(KoID("lsoverlay", i18n("Overlay (style)"))), m_mode(mode) { } void KisLsOverlayFilter::applyOverlay(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &applyRect, const psd_layer_effects_overlay_base *config, KisLayerStyleFilterEnvironment *env) const { if (applyRect.isEmpty()) return; - KisPaintDeviceSP tempDevice = new KisPaintDevice(srcDevice->colorSpace()); + KisPaintDeviceSP overlayDevice = m_cachedDevices.getDevice(srcDevice); + KisLsUtils::fillOverlayDevice(overlayDevice, applyRect, config, env); - { - // Create overlay device + const QString compositeOp = config->blendMode(); + const quint8 opacityU8 = 255.0 / 100.0 * config->opacity(); - KisPaintDeviceSP fillDevice = new KisPaintDevice(srcDevice->colorSpace()); - KisLsUtils::fillOverlayDevice(fillDevice, applyRect, config, env); + KisPaintDeviceSP dstDevice = dst->getProjection(KisMultipleProjection::defaultProjectionId(), + compositeOp, + opacityU8, + QBitArray(), + srcDevice); - KisPainter gc(tempDevice); - gc.setCompositeOp(COMPOSITE_OVER); - gc.bitBlt(applyRect.topLeft(), srcDevice, applyRect); + KisPainter::copyAreaOptimized(applyRect.topLeft(), srcDevice, dstDevice, applyRect); - QBitArray channelFlags = srcDevice->colorSpace()->channelFlags(true, false); - gc.setChannelFlags(channelFlags); - gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect); - gc.end(); - } - - { - // Paint over destination - - const QString compositeOp = config->blendMode(); - const quint8 opacityU8 = 255.0 / 100.0 * config->opacity(); - KisPaintDeviceSP dstDevice = dst->getProjection(KisMultipleProjection::defaultProjectionId(), compositeOp, srcDevice); + KisPainter gc(dstDevice); + gc.setCompositeOp(COMPOSITE_OVER); - KisPainter gc(dstDevice); + const QBitArray channelFlags = srcDevice->colorSpace()->channelFlags(true, false); + gc.setChannelFlags(channelFlags); + gc.bitBlt(applyRect.topLeft(), overlayDevice, applyRect); + gc.end(); - gc.setCompositeOp(COMPOSITE_OVER); - env->setupFinalPainter(&gc, opacityU8, QBitArray()); - - gc.bitBlt(applyRect.topLeft(), tempDevice, applyRect); - gc.end(); - } + m_cachedDevices.putDevice(overlayDevice); } const psd_layer_effects_overlay_base* KisLsOverlayFilter::getOverlayStruct(KisPSDLayerStyleSP style) const { const psd_layer_effects_overlay_base *config = 0; if (m_mode == Color) { config = style->colorOverlay(); } else if (m_mode == Gradient) { config = style->gradientOverlay(); } else if (m_mode == Pattern) { config = style->patternOverlay(); } return config; } void KisLsOverlayFilter::processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(env); KIS_ASSERT_RECOVER_RETURN(style); const psd_layer_effects_overlay_base *config = getOverlayStruct(style); if (!KisLsUtils::checkEffectEnabled(config, dst)) return; applyOverlay(src, dst, applyRect, config, env); } QRect KisLsOverlayFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(style); Q_UNUSED(env); return rect; } QRect KisLsOverlayFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(style); Q_UNUSED(env); return rect; } diff --git a/libs/image/layerstyles/kis_ls_overlay_filter.h b/libs/image/layerstyles/kis_ls_overlay_filter.h index 57f965e5d8..2c5d79f88b 100644 --- a/libs/image/layerstyles/kis_ls_overlay_filter.h +++ b/libs/image/layerstyles/kis_ls_overlay_filter.h @@ -1,64 +1,66 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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. */ #ifndef KIS_LS_OVERLAY_FILTER_H #define KIS_LS_OVERLAY_FILTER_H #include #include "kis_layer_style_filter.h" #include +#include "kis_cached_paint_device.h" struct psd_layer_effects_overlay_base; class KRITAIMAGE_EXPORT KisLsOverlayFilter : public KisLayerStyleFilter { public: enum Mode { Color, Gradient, Pattern }; public: KisLsOverlayFilter(Mode mode); void processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect neededRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect changedRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; private: const psd_layer_effects_overlay_base* getOverlayStruct(KisPSDLayerStyleSP style) const; void applyOverlay(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &applyRect, const psd_layer_effects_overlay_base *config, KisLayerStyleFilterEnvironment *env) const; private: Mode m_mode; + mutable KisCachedPaintDevice m_cachedDevices; }; #endif diff --git a/libs/image/layerstyles/kis_ls_stroke_filter.cpp b/libs/image/layerstyles/kis_ls_stroke_filter.cpp index 2843abfc64..a0c7760a42 100644 --- a/libs/image/layerstyles/kis_ls_stroke_filter.cpp +++ b/libs/image/layerstyles/kis_ls_stroke_filter.cpp @@ -1,156 +1,154 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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 "kis_ls_stroke_filter.h" #include #include #include #include #include "psd.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_psd_layer_style.h" #include "kis_layer_style_filter_environment.h" #include "kis_ls_utils.h" #include "kis_multiple_projection.h" KisLsStrokeFilter::KisLsStrokeFilter() : KisLayerStyleFilter(KoID("lsstroke", i18n("Stroke (style)"))) { } void paintPathOnSelection(KisPixelSelectionSP selection, const QPainterPath &path, const QRect &applyRect, int size) { QPen pen(Qt::white, size); KisPainter gc(selection); gc.setPaintColor(KoColor(Qt::white, selection->colorSpace())); gc.drawPainterPath(path, pen, applyRect); gc.end(); } void KisLsStrokeFilter::applyStroke(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &applyRect, const psd_layer_effects_stroke *config, KisLayerStyleFilterEnvironment *env) const { if (applyRect.isEmpty()) return; KisSelectionSP baseSelection = new KisSelection(new KisSelectionEmptyBounds(0)); KisPixelSelectionSP selection = baseSelection->pixelSelection(); //selection->convertToQImage(0, QRect(0,0,300,300)).save("0_selection_initial.png"); QPainterPath strokePath = env->layerOutlineCache(); if (strokePath.isEmpty()) return; if (config->position() == psd_stroke_center) { paintPathOnSelection(selection, strokePath, applyRect, config->size()); } else if (config->position() == psd_stroke_outside || config->position() == psd_stroke_inside) { paintPathOnSelection(selection, strokePath, applyRect, 2 * config->size()); KisSelectionSP knockOutSelection = KisLsUtils::selectionFromAlphaChannel(srcDevice, applyRect); // disabled intentionally, because it creates artifacts on smooth lines // KisLsUtils::findEdge(knockOutSelection->pixelSelection(), applyRect, true); if (config->position() == psd_stroke_inside) { knockOutSelection->pixelSelection()->invert(); } KisPainter gc(selection); gc.setCompositeOp(COMPOSITE_ERASE); gc.bitBlt(applyRect.topLeft(), knockOutSelection->pixelSelection(), applyRect); gc.end(); } //selection->convertToQImage(0, QRect(0,0,300,300)).save("1_selection_stroke.png"); KisPaintDeviceSP fillDevice = new KisPaintDevice(srcDevice->colorSpace()); KisLsUtils::fillOverlayDevice(fillDevice, applyRect, config, env); const QString compositeOp = config->blendMode(); const quint8 opacityU8 = 255.0 / 100.0 * config->opacity(); - KisPaintDeviceSP dstDevice = dst->getProjection(KisMultipleProjection::defaultProjectionId(), compositeOp, srcDevice); - dstDevice->clear(applyRect); + KisPaintDeviceSP dstDevice = dst->getProjection(KisMultipleProjection::defaultProjectionId(), + compositeOp, + opacityU8, + QBitArray(), + srcDevice); - KisPainter gc(dstDevice); - gc.setCompositeOp(COMPOSITE_OVER); - env->setupFinalPainter(&gc, opacityU8, QBitArray()); - gc.setSelection(baseSelection); - - gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect); + KisPainter::copyAreaOptimized(applyRect.topLeft(), fillDevice, dstDevice, applyRect, baseSelection); } void KisLsStrokeFilter::processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(env); KIS_ASSERT_RECOVER_RETURN(style); const psd_layer_effects_stroke *config = style->stroke(); if (!KisLsUtils::checkEffectEnabled(config, dst)) return; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); applyStroke(src, dst, applyRect, w.config, env); } QRect KisLsStrokeFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment */*env*/) const { Q_UNUSED(style); return rect; } QRect KisLsStrokeFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_stroke *config = style->stroke(); if (!config->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); const int borderSize = w.config->size() + 1; return kisGrowRect(rect, borderSize); } diff --git a/libs/image/layerstyles/kis_ls_utils.cpp b/libs/image/layerstyles/kis_ls_utils.cpp index 0e0d0ca3a0..e5583ba0be 100644 --- a/libs/image/layerstyles/kis_ls_utils.cpp +++ b/libs/image/layerstyles/kis_ls_utils.cpp @@ -1,599 +1,586 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * 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 "kis_ls_utils.h" #include #include #include #include "psd.h" #include "kis_default_bounds.h" #include "kis_pixel_selection.h" #include "kis_random_accessor_ng.h" #include "kis_iterator_ng.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_layer_style_filter_environment.h" #include "kis_selection_filters.h" #include "kis_multiple_projection.h" namespace KisLsUtils { QRect growSelectionUniform(KisPixelSelectionSP selection, int growSize, const QRect &applyRect) { QRect changeRect = applyRect; if (growSize > 0) { KisGrowSelectionFilter filter(growSize, growSize); changeRect = filter.changeRect(applyRect); filter.process(selection, applyRect); } else if (growSize < 0) { KisShrinkSelectionFilter filter(qAbs(growSize), qAbs(growSize), false); changeRect = filter.changeRect(applyRect); filter.process(selection, applyRect); } return changeRect; } KisSelectionSP selectionFromAlphaChannel(KisPaintDeviceSP device, const QRect &srcRect) { const KoColorSpace *cs = device->colorSpace(); KisSelectionSP baseSelection = new KisSelection(new KisSelectionEmptyBounds(0)); KisPixelSelectionSP selection = baseSelection->pixelSelection(); KisSequentialConstIterator srcIt(device, srcRect); KisSequentialIterator dstIt(selection, srcRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *dstPtr = dstIt.rawData(); const quint8* srcPtr = srcIt.rawDataConst(); *dstPtr = cs->opacityU8(srcPtr); } return baseSelection; } void findEdge(KisPixelSelectionSP selection, const QRect &applyRect, const bool edgeHidden) { KisSequentialIterator dstIt(selection, applyRect); if (edgeHidden) { while(dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = (*pixelPtr < 24) ? *pixelPtr * 10 : 0xFF; } } else { while(dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = 0xFF; } } } QRect growRectFromRadius(const QRect &rc, int radius) { int halfSize = KisGaussianKernel::kernelSizeFromRadius(radius) / 2; return rc.adjusted(-halfSize, -halfSize, halfSize, halfSize); } void applyGaussian(KisPixelSelectionSP selection, const QRect &applyRect, qreal radius) { KisGaussianKernel::applyGaussian(selection, applyRect, radius, radius, QBitArray(), 0); } namespace Private { void getGradientTable(const KoAbstractGradient *gradient, QVector *table, const KoColorSpace *colorSpace) { KIS_ASSERT_RECOVER_RETURN(table->size() == 256); for (int i = 0; i < 256; i++) { gradient->colorAt(((*table)[i]), qreal(i) / 255.0); (*table)[i].convertTo(colorSpace); } } struct LinearGradientIndex { int popOneIndex(int selectionAlpha) { return 255 - selectionAlpha; } bool nextPixel() { return true; } }; struct JitterGradientIndex { JitterGradientIndex(const QRect &applyRect, int jitter, const KisLayerStyleFilterEnvironment *env) : randomSelection(env->cachedRandomSelection(applyRect)), noiseIt(randomSelection, applyRect), m_jitterCoeff(jitter * 255 / 100) { } int popOneIndex(int selectionAlpha) { int gradientIndex = 255 - selectionAlpha; gradientIndex += m_jitterCoeff * *noiseIt.rawDataConst() >> 8; gradientIndex &= 0xFF; return gradientIndex; } bool nextPixel() { return noiseIt.nextPixel(); } private: KisPixelSelectionSP randomSelection; KisSequentialConstIterator noiseIt; int m_jitterCoeff; }; template void applyGradientImpl(KisPaintDeviceSP device, KisPixelSelectionSP selection, const QRect &applyRect, const QVector &table, bool edgeHidden, IndexFetcher &indexFetcher) { KIS_ASSERT_RECOVER_RETURN( *table.first().colorSpace() == *device->colorSpace()); const KoColorSpace *cs = device->colorSpace(); const int pixelSize = cs->pixelSize(); KisSequentialConstIterator selIt(selection, applyRect); KisSequentialIterator dstIt(device, applyRect); if (edgeHidden) { while (selIt.nextPixel() && dstIt.nextPixel() && indexFetcher.nextPixel()) { quint8 selAlpha = *selIt.rawDataConst(); int gradientIndex = indexFetcher.popOneIndex(selAlpha); const KoColor &color = table[gradientIndex]; quint8 tableAlpha = color.opacityU8(); memcpy(dstIt.rawData(), color.data(), pixelSize); if (selAlpha < 24 && tableAlpha == 255) { tableAlpha = int(selAlpha) * 10 * tableAlpha >> 8; cs->setOpacity(dstIt.rawData(), tableAlpha, 1); } } } else { while (selIt.nextPixel() && dstIt.nextPixel() && indexFetcher.nextPixel()) { int gradientIndex = indexFetcher.popOneIndex(*selIt.rawDataConst()); const KoColor &color = table[gradientIndex]; memcpy(dstIt.rawData(), color.data(), pixelSize); } } } void applyGradient(KisPaintDeviceSP device, KisPixelSelectionSP selection, const QRect &applyRect, const QVector &table, bool edgeHidden, int jitter, const KisLayerStyleFilterEnvironment *env) { if (!jitter) { LinearGradientIndex fetcher; applyGradientImpl(device, selection, applyRect, table, edgeHidden, fetcher); } else { JitterGradientIndex fetcher(applyRect, jitter, env); applyGradientImpl(device, selection, applyRect, table, edgeHidden, fetcher); } } } const int noiseNeedBorder = 8; void applyNoise(KisPixelSelectionSP selection, const QRect &applyRect, int noise, const psd_layer_effects_context *context, const KisLayerStyleFilterEnvironment *env) { Q_UNUSED(context); const QRect overlayRect = kisGrowRect(applyRect, noiseNeedBorder); KisPixelSelectionSP randomSelection = env->cachedRandomSelection(overlayRect); KisPixelSelectionSP randomOverlay = new KisPixelSelection(); KisSequentialConstIterator noiseIt(randomSelection, overlayRect); KisSequentialConstIterator srcIt(selection, overlayRect); KisRandomAccessorSP dstIt = randomOverlay->createRandomAccessorNG(overlayRect.x(), overlayRect.y()); while (noiseIt.nextPixel() && srcIt.nextPixel()) { int itX = noiseIt.x(); int itY = noiseIt.y(); int x = itX + (*noiseIt.rawDataConst() >> 4) - 8; int y = itY + (*noiseIt.rawDataConst() & 0x0F) - 8; x = (x + itX) >> 1; y = (y + itY) >> 1; dstIt->moveTo(x, y); quint8 dstAlpha = *dstIt->rawData(); quint8 srcAlpha = *srcIt.rawDataConst(); int value = qMin(255, dstAlpha + srcAlpha); *dstIt->rawData() = value; } noise = noise * 255 / 100; KisPainter gc(selection); gc.setOpacity(noise); gc.setCompositeOp(COMPOSITE_COPY); gc.bitBlt(applyRect.topLeft(), randomOverlay, applyRect); } //const int FULL_PERCENT_RANGE = 100; void adjustRange(KisPixelSelectionSP selection, const QRect &applyRect, const int range) { KIS_ASSERT_RECOVER_RETURN(range >= 1 && range <= 100); quint8 rangeTable[256]; for(int i = 0; i < 256; i ++) { quint8 value = i * 100 / range; rangeTable[i] = qMin(value, quint8(255)); } KisSequentialIterator dstIt(selection, applyRect); while (dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = rangeTable[*pixelPtr]; } } void applyContourCorrection(KisPixelSelectionSP selection, const QRect &applyRect, const quint8 *lookup_table, bool antiAliased, bool edgeHidden) { quint8 contour[PSD_LOOKUP_TABLE_SIZE] = { 0x00, 0x0b, 0x16, 0x21, 0x2c, 0x37, 0x42, 0x4d, 0x58, 0x63, 0x6e, 0x79, 0x84, 0x8f, 0x9a, 0xa5, 0xb0, 0xbb, 0xc6, 0xd1, 0xdc, 0xf2, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; if (edgeHidden) { if (antiAliased) { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = contour[i] * lookup_table[i] >> 8; } } else { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = contour[i] * lookup_table[(int)((int)(i / 2.55) * 2.55 + 0.5)] >> 8; } } } else { if (antiAliased) { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = lookup_table[i]; } } else { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = lookup_table[(int)((int)(i / 2.55) * 2.55 + 0.5)]; } } } KisSequentialIterator dstIt(selection, applyRect); while (dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = contour[*pixelPtr]; } } void knockOutSelection(KisPixelSelectionSP selection, KisPixelSelectionSP knockOutSelection, const QRect &srcRect, const QRect &dstRect, const QRect &totalNeedRect, const bool knockOutInverted) { KIS_ASSERT_RECOVER_RETURN(knockOutSelection); QRect knockOutRect = !knockOutInverted ? srcRect : totalNeedRect; knockOutRect &= dstRect; KisPainter gc(selection); gc.setCompositeOp(COMPOSITE_ERASE); gc.bitBlt(knockOutRect.topLeft(), knockOutSelection, knockOutRect); } void fillPattern(KisPaintDeviceSP fillDevice, const QRect &applyRect, KisLayerStyleFilterEnvironment *env, int scale, KoPattern *pattern, int horizontalPhase, int verticalPhase, bool alignWithLayer) { if (scale != 100) { warnKrita << "KisLsOverlayFilter::applyOverlay(): Pattern scaling is NOT implemented!"; } QSize psize(pattern->width(), pattern->height()); QPoint patternOffset(qreal(psize.width()) * horizontalPhase / 100, qreal(psize.height()) * verticalPhase / 100); const QRect boundsRect = alignWithLayer ? env->layerBounds() : env->defaultBounds(); patternOffset += boundsRect.topLeft(); patternOffset.rx() %= psize.width(); patternOffset.ry() %= psize.height(); QRect fillRect = applyRect | applyRect.translated(patternOffset); KisFillPainter gc(fillDevice); gc.fillRect(fillRect.x(), fillRect.y(), - fillRect.width(), fillRect.height(), pattern); + fillRect.width(), fillRect.height(), pattern, -patternOffset); gc.end(); - - fillDevice->setX(-patternOffset.x()); - fillDevice->setY(-patternOffset.y()); } void fillOverlayDevice(KisPaintDeviceSP fillDevice, const QRect &applyRect, const psd_layer_effects_overlay_base *config, KisLayerStyleFilterEnvironment *env) { if (config->fillType() == psd_fill_solid_color) { KoColor color(config->color(), fillDevice->colorSpace()); fillDevice->setDefaultPixel(color); } else if (config->fillType() == psd_fill_pattern) { fillPattern(fillDevice, applyRect, env, config->scale(), config->pattern(), config->horizontalPhase(), config->verticalPhase(), config->alignWithLayer()); } else if (config->fillType() == psd_fill_gradient) { const QRect boundsRect = config->alignWithLayer() ? env->layerBounds() : env->defaultBounds(); QPoint center = boundsRect.center(); center += QPoint(boundsRect.width() * config->gradientXOffset() / 100, boundsRect.height() * config->gradientYOffset() / 100); int width = (boundsRect.width() * config->scale() + 100) / 200; int height = (boundsRect.height() * config->scale() + 100) / 200; /* copy paste from libpsd */ int angle = config->angle(); int corner_angle = (int)(atan((qreal)boundsRect.height() / boundsRect.width()) * 180 / M_PI + 0.5); int sign_x = 1; int sign_y = 1; if(angle < 0) { angle += 360; } if (angle >= 90 && angle < 180) { angle = 180 - angle; sign_x = -1; } else if (angle >= 180 && angle < 270) { angle = angle - 180; sign_x = -1; sign_y = -1; } else if (angle >= 270 && angle <= 360) { angle = 360 - angle; sign_y = -1; } int radius_x = 0; int radius_y = 0; if (angle <= corner_angle) { radius_x = width; radius_y = (int)(radius_x * tan(kisDegreesToRadians(qreal(angle))) + 0.5); } else { radius_y = height; radius_x = (int)(radius_y / tan(kisDegreesToRadians(qreal(angle))) + 0.5); } int radius_corner = (int)(std::sqrt((qreal)(radius_x * radius_x + radius_y * radius_y)) + 0.5); /* end of copy paste from libpsd */ KisGradientPainter gc(fillDevice); gc.setGradient(config->gradient().data()); QPointF gradStart; QPointF gradEnd; KisGradientPainter::enumGradientRepeat repeat = KisGradientPainter::GradientRepeatNone; QPoint rectangularOffset(sign_x * radius_x, -sign_y * radius_y); switch(config->style()) { case psd_gradient_style_linear: gc.setGradientShape(KisGradientPainter::GradientShapeLinear); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center - rectangularOffset; gradEnd = center + rectangularOffset; break; case psd_gradient_style_radial: gc.setGradientShape(KisGradientPainter::GradientShapeRadial); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center; gradEnd = center + QPointF(radius_corner, 0); break; case psd_gradient_style_angle: gc.setGradientShape(KisGradientPainter::GradientShapeConical); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center; gradEnd = center + rectangularOffset; break; case psd_gradient_style_reflected: gc.setGradientShape(KisGradientPainter::GradientShapeLinear); repeat = KisGradientPainter::GradientRepeatAlternate; gradStart = center - rectangularOffset; gradEnd = center; break; case psd_gradient_style_diamond: gc.setGradientShape(KisGradientPainter::GradientShapeBiLinear); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center - rectangularOffset; gradEnd = center + rectangularOffset; break; default: qFatal("Gradient Overlay: unknown switch case!"); break; } gc.paintGradient(gradStart, gradEnd, repeat, 0.0, config->reverse(), applyRect); } } void applyFinalSelection(const QString &projectionId, KisSelectionSP baseSelection, KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &/*srcRect*/, const QRect &dstRect, const psd_layer_effects_context */*context*/, const psd_layer_effects_shadow_base *config, const KisLayerStyleFilterEnvironment *env) { const KoColor effectColor(config->color(), srcDevice->colorSpace()); const QRect effectRect(dstRect); const QString compositeOp = config->blendMode(); const quint8 opacityU8 = 255.0 / 100.0 * config->opacity(); - KisPaintDeviceSP dstDevice = dst->getProjection(projectionId, compositeOp, srcDevice); + KisPaintDeviceSP dstDevice = dst->getProjection(projectionId, compositeOp, opacityU8, QBitArray(), srcDevice); if (config->fillType() == psd_fill_solid_color) { KisFillPainter gc(dstDevice); - gc.setCompositeOp(COMPOSITE_OVER); - env->setupFinalPainter(&gc, opacityU8, QBitArray()); + gc.setCompositeOp(COMPOSITE_COPY); gc.setSelection(baseSelection); gc.fillSelection(effectRect, effectColor); gc.end(); } else if (config->fillType() == psd_fill_gradient) { if (!config->gradient()) { warnKrita << "KisLsUtils::applyFinalSelection: Gradient object is null! Skipping..."; return; } - KisPaintDeviceSP overlayDevice = - new KisPaintDevice(dstDevice->colorSpace()); - QVector table(256); Private::getGradientTable(config->gradient().data(), &table, dstDevice->colorSpace()); - Private::applyGradient(overlayDevice, baseSelection->pixelSelection(), + Private::applyGradient(dstDevice, baseSelection->pixelSelection(), effectRect, table, true, config->jitter(), env); - - KisPainter gc(dstDevice); - gc.setCompositeOp(COMPOSITE_OVER); - env->setupFinalPainter(&gc, opacityU8, QBitArray()); - gc.bitBlt(effectRect.topLeft(), overlayDevice, effectRect); - gc.end(); } //dstDevice->convertToQImage(0, QRect(0,0,300,300)).save("6_device_shadow.png"); } bool checkEffectEnabled(const psd_layer_effects_shadow_base *config, KisMultipleProjection *dst) { bool result = config->effectEnabled(); if (!result) { dst->freeAllProjections(); } return result; } } diff --git a/libs/image/layerstyles/kis_multiple_projection.cpp b/libs/image/layerstyles/kis_multiple_projection.cpp index b3e4ae9637..ffc3e3c3af 100644 --- a/libs/image/layerstyles/kis_multiple_projection.cpp +++ b/libs/image/layerstyles/kis_multiple_projection.cpp @@ -1,147 +1,161 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * 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 "kis_multiple_projection.h" #include #include #include #include "kis_painter.h" #include "kis_paint_device.h" +#include "kis_layer_style_filter_environment.h" struct ProjectionStruct { KisPaintDeviceSP device; QString compositeOpId; + quint8 opacity = OPACITY_OPAQUE_U8; + QBitArray channelFlags; }; typedef QMap PlanesMap; struct KisMultipleProjection::Private { QReadWriteLock lock; PlanesMap planes; }; KisMultipleProjection::KisMultipleProjection() : m_d(new Private) { } KisMultipleProjection::~KisMultipleProjection() { } QString KisMultipleProjection::defaultProjectionId() { return "00_default"; } -KisPaintDeviceSP KisMultipleProjection::getProjection(const QString &id, const QString &compositeOpId, KisPaintDeviceSP prototype) +KisPaintDeviceSP KisMultipleProjection::getProjection(const QString &id, + const QString &compositeOpId, + quint8 opacity, + const QBitArray &channelFlags, + KisPaintDeviceSP prototype) { QReadLocker readLocker(&m_d->lock); PlanesMap::const_iterator constIt = m_d->planes.constFind(id); if (constIt == m_d->planes.constEnd() || constIt->compositeOpId != compositeOpId || + constIt->opacity != opacity || + constIt->channelFlags != channelFlags || *constIt->device->colorSpace() != *prototype->colorSpace()) { readLocker.unlock(); { QWriteLocker writeLocker(&m_d->lock); PlanesMap::iterator writeIt = m_d->planes.find(id); if (writeIt == m_d->planes.end()) { ProjectionStruct plane; plane.device = new KisPaintDevice(prototype->colorSpace()); plane.device->prepareClone(prototype); plane.compositeOpId = compositeOpId; + plane.opacity = opacity; + plane.channelFlags = channelFlags; writeIt = m_d->planes.insert(id, plane); } else if (writeIt->compositeOpId != compositeOpId || *writeIt->device->colorSpace() != *prototype->colorSpace()) { writeIt->device->prepareClone(prototype); writeIt->compositeOpId = compositeOpId; + writeIt->opacity = opacity; + writeIt->channelFlags = channelFlags; } return writeIt->device; } } return constIt->device; } void KisMultipleProjection::freeProjection(const QString &id) { QWriteLocker writeLocker(&m_d->lock); m_d->planes.remove(id); } void KisMultipleProjection::freeAllProjections() { QWriteLocker writeLocker(&m_d->lock); m_d->planes.clear(); } void KisMultipleProjection::clear(const QRect &rc) { QReadLocker readLocker(&m_d->lock); PlanesMap::const_iterator it = m_d->planes.constBegin(); PlanesMap::const_iterator end = m_d->planes.constEnd(); for (; it != end; ++it) { const_cast(it->device.data())->clear(rc); } } -void KisMultipleProjection::apply(KisPaintDeviceSP dstDevice, const QRect &rect) +void KisMultipleProjection::apply(KisPaintDeviceSP dstDevice, const QRect &rect, KisLayerStyleFilterEnvironment *env) { QReadLocker readLocker(&m_d->lock); PlanesMap::const_iterator it = m_d->planes.constBegin(); PlanesMap::const_iterator end = m_d->planes.constEnd(); for (; it != end; ++it) { KisPainter gc(dstDevice); gc.setCompositeOp(it->compositeOpId); + env->setupFinalPainter(&gc, it->opacity, it->channelFlags); gc.bitBlt(rect.topLeft(), it->device, rect); } } KisPaintDeviceList KisMultipleProjection::getLodCapableDevices() const { QReadLocker readLocker(&m_d->lock); PlanesMap::const_iterator it = m_d->planes.constBegin(); PlanesMap::const_iterator end = m_d->planes.constEnd(); KisPaintDeviceList list; for (; it != end; ++it) { list << it->device; } return list; } diff --git a/libs/image/layerstyles/kis_multiple_projection.h b/libs/image/layerstyles/kis_multiple_projection.h index a6f9038a6b..3055d01600 100644 --- a/libs/image/layerstyles/kis_multiple_projection.h +++ b/libs/image/layerstyles/kis_multiple_projection.h @@ -1,50 +1,51 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * 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. */ #ifndef __KIS_MULTIPLE_PROJECTION_H #define __KIS_MULTIPLE_PROJECTION_H #include #include "kis_types.h" #include "kritaimage_export.h" +class KisLayerStyleFilterEnvironment; class KRITAIMAGE_EXPORT KisMultipleProjection { public: KisMultipleProjection(); ~KisMultipleProjection(); static QString defaultProjectionId(); - KisPaintDeviceSP getProjection(const QString &id, const QString &compositeOpId, KisPaintDeviceSP prototype); + KisPaintDeviceSP getProjection(const QString &id, const QString &compositeOpId, quint8 opacity, const QBitArray &channelFlags, KisPaintDeviceSP prototype); void freeProjection(const QString &id); void freeAllProjections(); void clear(const QRect &rc); - void apply(KisPaintDeviceSP dstDevice, const QRect &rect); + void apply(KisPaintDeviceSP dstDevice, const QRect &rect, KisLayerStyleFilterEnvironment *env); KisPaintDeviceList getLodCapableDevices() const; private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_MULTIPLE_PROJECTION_H */ diff --git a/libs/image/tests/data/fill_painter/patterns_fill__custom_origin.png b/libs/image/tests/data/fill_painter/patterns_fill__custom_origin.png new file mode 100644 index 0000000000..5d19d0955f Binary files /dev/null and b/libs/image/tests/data/fill_painter/patterns_fill__custom_origin.png differ diff --git a/libs/image/tests/data/fill_painter/patterns_fill__null_origin.png b/libs/image/tests/data/fill_painter/patterns_fill__null_origin.png new file mode 100644 index 0000000000..b0877caa3e Binary files /dev/null and b/libs/image/tests/data/fill_painter/patterns_fill__null_origin.png differ diff --git a/libs/image/tests/kis_fill_painter_test.cpp b/libs/image/tests/kis_fill_painter_test.cpp index 2b08bc16c1..577ba562aa 100644 --- a/libs/image/tests/kis_fill_painter_test.cpp +++ b/libs/image/tests/kis_fill_painter_test.cpp @@ -1,153 +1,208 @@ /* * Copyright (c) 2007 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 "kis_fill_painter_test.h" #include "testutil.h" #include #include "kis_fill_painter.h" #include #define THRESHOLD 10 void KisFillPainterTest::testCreation() { KisFillPainter test; } void KisFillPainterTest::benchmarkFillPainter(const QPoint &startPoint, bool useCompositioning) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QImage srcImage(TestUtil::fetchDataFileLazy("heavy_labyrinth.png")); QVERIFY(!srcImage.isNull()); QRect imageRect = srcImage.rect(); dev->convertFromQImage(srcImage, 0, 0, 0); QBENCHMARK_ONCE { KisFillPainter gc(dev); gc.setFillThreshold(THRESHOLD); gc.setWidth(imageRect.width()); gc.setHeight(imageRect.height()); gc.setPaintColor(KoColor(Qt::red, dev->colorSpace())); gc.setUseCompositioning(useCompositioning); gc.fillColor(startPoint.x(), startPoint.y(), dev); } QImage resultImage = dev->convertToQImage(0, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height()); QString testName = QString("heavy_labyrinth_%1_%2_%3") .arg(startPoint.x()) .arg(startPoint.y()) .arg(useCompositioning ? "composed" : "direct"); QVERIFY(TestUtil::checkQImage(resultImage, "fill_painter", "general_", testName)); } void KisFillPainterTest::benchmarkFillPainter() { benchmarkFillPainter(QPoint(), false); } void KisFillPainterTest::benchmarkFillPainterOffset() { benchmarkFillPainter(QPoint(5,5), false); } void KisFillPainterTest::benchmarkFillPainterOffsetCompositioning() { benchmarkFillPainter(QPoint(5,5), true); } void KisFillPainterTest::benchmarkFillingScanlineColor() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QImage srcImage(TestUtil::fetchDataFileLazy("heavy_labyrinth.png")); QVERIFY(!srcImage.isNull()); QRect imageRect = srcImage.rect(); dev->convertFromQImage(srcImage, 0, 0, 0); QBENCHMARK_ONCE { KisScanlineFill gc(dev, QPoint(), imageRect); gc.setThreshold(THRESHOLD); gc.fillColor(KoColor(Qt::red, dev->colorSpace())); } QImage resultImage = dev->convertToQImage(0, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height()); QVERIFY(TestUtil::checkQImage(resultImage, "fill_painter", "scanline_", "heavy_labyrinth_top_left")); } void KisFillPainterTest::benchmarkFillingScanlineSelection() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QImage srcImage(TestUtil::fetchDataFileLazy("heavy_labyrinth.png")); QVERIFY(!srcImage.isNull()); QRect imageRect = srcImage.rect(); dev->convertFromQImage(srcImage, 0, 0, 0); KisPixelSelectionSP pixelSelection = new KisPixelSelection(); QBENCHMARK_ONCE { KisScanlineFill gc(dev, QPoint(), imageRect); gc.setThreshold(THRESHOLD); gc.fillSelection(pixelSelection); } QImage resultImage = pixelSelection->convertToQImage(0, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height()); QVERIFY(TestUtil::checkQImage(resultImage, "fill_painter", "scanline_", "heavy_labyrinth_top_left_selection")); } +void KisFillPainterTest::testPatternFill() +{ + const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); + KisPaintDeviceSP dst = new KisPaintDevice(cs); + + KisPaintDeviceSP pattern = new KisPaintDevice(cs); + pattern->fill(QRect(0,0,32,32), KoColor(Qt::red, cs)); + pattern->fill(QRect(32,32,32,32), KoColor(Qt::red, cs)); + pattern->fill(QRect(32,0,32,32), KoColor(Qt::yellow, cs)); + pattern->fill(QRect(0,32,32,32), KoColor(Qt::white, cs)); + + const QRect fillRect(-128,-128,384,384); + KisFillPainter painter(dst); + + + { // fill aligned + const QRect patternRect = pattern->exactBounds(); + painter.fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), pattern, patternRect); + dst->fill(QRect(0,0,10,10), KoColor(Qt::black, cs)); + + QImage resultImage = + dst->convertToQImage(0, + fillRect.x(), fillRect.y(), + fillRect.width(), fillRect.height()); + + QVERIFY(TestUtil::checkQImage(resultImage, + "fill_painter", + "patterns_fill_", + "null_origin")); + } + + { // fill with offset + dst->clear(); + pattern->setX(7); + pattern->setY(-13); + + + const QRect patternRect = pattern->exactBounds(); + + painter.fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), pattern, patternRect); + dst->fill(QRect(0,0,10,10), KoColor(Qt::black, cs)); + + QImage resultImage = + dst->convertToQImage(0, + fillRect.x(), fillRect.y(), + fillRect.width(), fillRect.height()); + + QVERIFY(TestUtil::checkQImage(resultImage, + "fill_painter", + "patterns_fill_", + "custom_origin")); + } + +} + QTEST_MAIN(KisFillPainterTest) diff --git a/libs/image/tests/kis_fill_painter_test.h b/libs/image/tests/kis_fill_painter_test.h index 1fcc02a26f..d194d589c7 100644 --- a/libs/image/tests/kis_fill_painter_test.h +++ b/libs/image/tests/kis_fill_painter_test.h @@ -1,41 +1,43 @@ /* * Copyright (c) 2007 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. */ #ifndef KIS_FILL_PAINTER_TEST_H #define KIS_FILL_PAINTER_TEST_H #include class KisFillPainterTest : public QObject { Q_OBJECT private: void benchmarkFillPainter(const QPoint &startPoint, bool useCompositioning); private Q_SLOTS: void testCreation(); void benchmarkFillPainter(); void benchmarkFillPainterOffset(); void benchmarkFillPainterOffsetCompositioning(); void benchmarkFillingScanlineColor(); void benchmarkFillingScanlineSelection(); + + void testPatternFill(); }; #endif diff --git a/libs/image/tests/kis_layer_styles_test.cpp b/libs/image/tests/kis_layer_styles_test.cpp index a79d02221e..318984e647 100644 --- a/libs/image/tests/kis_layer_styles_test.cpp +++ b/libs/image/tests/kis_layer_styles_test.cpp @@ -1,279 +1,279 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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 "kis_layer_styles_test.h" #include #include "kis_transaction.h" #include "testutil.h" #include #include "layerstyles/kis_layer_style_filter.h" #include "layerstyles/kis_layer_style_filter_environment.h" #include "layerstyles/kis_ls_drop_shadow_filter.h" #include "kis_psd_layer_style.h" #include "layerstyles/kis_multiple_projection.h" struct TestConfig { TestConfig() : distance(0), angle(0), spread(0), size(0), noise(0), knocks_out(false), keep_original(false) { } int distance; int angle; int spread; int size; int noise; int knocks_out; int opacity; bool keep_original; void writeProperties(KisPSDLayerStyleSP style) const { style->context()->keep_original = keep_original; style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(distance); style->dropShadow()->setSpread(spread); style->dropShadow()->setSize(size); style->dropShadow()->setNoise(noise); style->dropShadow()->setKnocksOut(knocks_out); style->dropShadow()->setOpacity(opacity); } QString genTestname(const QString &prefix) const { return QString("%1_d_%2_an_%3_sz_%4_spr_%5_nz_%6_ko_%7_keep_%8") .arg(prefix) .arg(distance) .arg(angle) .arg(size) .arg(spread) .arg(noise) .arg(knocks_out) .arg(keep_original); } }; void testDropShadowImpl(const TestConfig &config, const QVector &applyRects, const QString &testName, bool useSeparateDevices) { Q_UNUSED(useSeparateDevices); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QRect srcRect(50, 50, 100, 100); QRect dstRect(0, 0, 200, 200); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(srcRect, KoColor(Qt::red, cs)); KisMultipleProjection projection; KisLsDropShadowFilter lsFilter; KisPSDLayerStyleSP style(new KisPSDLayerStyle()); config.writeProperties(style); TestUtil::MaskParent parent; KisLayerStyleFilterEnvironment env(parent.layer.data()); Q_FOREACH (const QRect &rc, applyRects) { lsFilter.processDirectly(dev, &projection, rc, style, &env); } KisPaintDeviceSP dst = new KisPaintDevice(cs); - projection.apply(dst, dstRect); + projection.apply(dst, dstRect, &env); QImage resultImage = dst->convertToQImage(0, dstRect); TestUtil::checkQImage(resultImage, "layer_styles_test", "common", config.genTestname(testName)); } void KisLayerStylesTest::testLayerStylesFull() { TestConfig c; c.distance = 20; c.angle = 135; c.spread = 50; c.size = 10; c.noise = 30; c.knocks_out = false; c.opacity = 50; c.keep_original = false; testDropShadowImpl(c, QVector() << QRect(0,0,200,200), "full", false); } void KisLayerStylesTest::testLayerStylesPartial() { QVector rects; for (int y = 0; y < 200; y += 50) { for (int x = 0; x < 200; x += 50) { rects << QRect(x, y, 50, 50); } } TestConfig c; c.distance = 20; c.angle = 135; c.spread = 50; c.size = 10; c.noise = 30; c.knocks_out = false; c.opacity = 50; c.keep_original = false; testDropShadowImpl(c, rects, "partial", true); } void KisLayerStylesTest::testLayerStylesPartialVary() { QVector rects; for (int y = 0; y < 200; y += 50) { for (int x = 0; x < 200; x += 50) { rects << QRect(x, y, 50, 50); } } TestConfig c; c.distance = 20; c.angle = 135; c.spread = 50; c.size = 10; c.noise = 30; c.knocks_out = false; c.opacity = 50; c.keep_original = true; testDropShadowImpl(c, rects, "partial", true); c.noise = 90; testDropShadowImpl(c, rects, "partial", true); c.noise = 0; testDropShadowImpl(c, rects, "partial", true); c.noise = 10; testDropShadowImpl(c, rects, "partial", true); c.angle = 90; testDropShadowImpl(c, rects, "partial", true); c.angle = 45; testDropShadowImpl(c, rects, "partial", true); c.knocks_out = true; testDropShadowImpl(c, rects, "partial", true); c.spread = 90; testDropShadowImpl(c, rects, "partial", true); } void testDropShadowNeedChangeRects(int distance, int noise, int size, int spread, const QRect &applyRect, const QRect &needRect, const QRect &changeRect) { TestConfig c; c.distance = distance; c.spread = spread; c.size = size; c.noise = noise; c.angle = 90; c.knocks_out = false; c.opacity = 50; KisLsDropShadowFilter lsFilter; KisPSDLayerStyleSP style(new KisPSDLayerStyle()); c.writeProperties(style); TestUtil::MaskParent parent; KisLayerStyleFilterEnvironment env(parent.layer.data()); QCOMPARE(lsFilter.neededRect(applyRect, style, &env), needRect); QCOMPARE(lsFilter.changedRect(applyRect, style, &env), changeRect); } void KisLayerStylesTest::testLayerStylesRects() { QRect applyRect; QRect needRect; QRect changeRect; applyRect = QRect(10,10,10,10); needRect = applyRect; changeRect = applyRect; testDropShadowNeedChangeRects(0, 0, 0, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(10,0,10,20); changeRect = QRect(10,10,10,20); testDropShadowNeedChangeRects(10, 0, 0, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(2,2,26,26); changeRect = QRect(2,2,26,26); testDropShadowNeedChangeRects(0, 30, 0, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(-2,-2,34,34); changeRect = QRect(-2,-2,34,34); testDropShadowNeedChangeRects(0, 0, 10, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(-2,-2,34,34); changeRect = QRect(-2,-2,34,34); testDropShadowNeedChangeRects(0, 0, 10, 50, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(-2,-2,34,34); changeRect = QRect(-2,-2,34,34); testDropShadowNeedChangeRects(0, 0, 10, 75, applyRect, needRect, changeRect); } QTEST_MAIN(KisLayerStylesTest)