diff --git a/libs/flake/KoSnapProxy.cpp b/libs/flake/KoSnapProxy.cpp index bd7a3c3e3b..e48a324c35 100644 --- a/libs/flake/KoSnapProxy.cpp +++ b/libs/flake/KoSnapProxy.cpp @@ -1,187 +1,189 @@ /* This file is part of the KDE project * Copyright (C) 2008-2009 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoSnapProxy.h" #include "KoSnapGuide.h" #include "KoCanvasBase.h" #include "KoShapeManager.h" #include "KoPathShape.h" #include "KoPathPoint.h" #include +#include KoSnapProxy::KoSnapProxy(KoSnapGuide * snapGuide) : m_snapGuide(snapGuide) { } QList KoSnapProxy::pointsInRect(const QRectF &rect, bool omitEditedShape) { QList points; QList shapes = shapesInRect(rect, omitEditedShape); Q_FOREACH (KoShape * shape, shapes) { Q_FOREACH (const QPointF & point, pointsFromShape(shape)) { if (rect.contains(point)) points.append(point); } } return points; } QList KoSnapProxy::shapesInRect(const QRectF &rect, bool omitEditedShape) { QList shapes = m_snapGuide->canvas()->shapeManager()->shapesAt(rect); Q_FOREACH (KoShape * shape, m_snapGuide->ignoredShapes()) { const int index = shapes.indexOf(shape); if (index >= 0) { shapes.removeAt(index); } } if (omitEditedShape) { Q_FOREACH (KoPathPoint *point, m_snapGuide->ignoredPathPoints()) { const int index = shapes.indexOf(point->parent()); if (index >= 0) { shapes.removeAt(index); } } } if (!omitEditedShape && m_snapGuide->additionalEditedShape()) { QRectF bound = m_snapGuide->additionalEditedShape()->boundingRect(); if (rect.intersects(bound) || rect.contains(bound)) shapes.append(m_snapGuide->additionalEditedShape()); } return shapes; } QList KoSnapProxy::pointsFromShape(KoShape * shape) { QList snapPoints; // no snapping to hidden shapes if (! shape->isVisible()) return snapPoints; // return the special snap points of the shape snapPoints += shape->snapData().snapPoints(); KoPathShape * path = dynamic_cast(shape); if (path) { QTransform m = path->absoluteTransformation(); QList ignoredPoints = m_snapGuide->ignoredPathPoints(); int subpathCount = path->subpathCount(); for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { int pointCount = path->subpathPointCount(subpathIndex); for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) { KoPathPoint * p = path->pointByIndex(KoPathPointIndex(subpathIndex, pointIndex)); if (! p || ignoredPoints.contains(p)) continue; snapPoints.append(m.map(p->point())); } } } else { // add the bounding box corners as default snap points QRectF bbox = shape->boundingRect(); snapPoints.append(bbox.topLeft()); snapPoints.append(bbox.topRight()); snapPoints.append(bbox.bottomRight()); snapPoints.append(bbox.bottomLeft()); } return snapPoints; } QList KoSnapProxy::segmentsInRect(const QRectF &rect, bool omitEditedShape) { QList shapes = shapesInRect(rect, omitEditedShape); QList ignoredPoints = m_snapGuide->ignoredPathPoints(); QList segments; Q_FOREACH (KoShape * shape, shapes) { QList shapeSegments; QRectF rectOnShape = shape->documentToShape(rect); KoPathShape * path = dynamic_cast(shape); if (path) { shapeSegments = path->segmentsAt(rectOnShape); } else { Q_FOREACH (const KoPathSegment & s, shape->snapData().snapSegments()) { QRectF controlRect = s.controlPointRect(); if (! rect.intersects(controlRect) && ! controlRect.contains(rect)) continue; QRectF bound = s.boundingRect(); if (! rect.intersects(bound) && ! bound.contains(rect)) continue; shapeSegments.append(s); } } QTransform m = shape->absoluteTransformation(); // transform segments to document coordinates Q_FOREACH (const KoPathSegment & s, shapeSegments) { if (ignoredPoints.contains(s.first()) || ignoredPoints.contains(s.second())) continue; segments.append(s.mapped(m)); } } return segments; } QList KoSnapProxy::shapes(bool omitEditedShape) { QList allShapes = m_snapGuide->canvas()->shapeManager()->shapes(); QList filteredShapes; QList ignoredShapes = m_snapGuide->ignoredShapes(); // filter all hidden and ignored shapes Q_FOREACH (KoShape * shape, allShapes) { if (shape->isVisible() && - !ignoredShapes.contains(shape)) { + !ignoredShapes.contains(shape) && + !dynamic_cast(shape)) { filteredShapes.append(shape); } } if (omitEditedShape) { Q_FOREACH (KoPathPoint *point, m_snapGuide->ignoredPathPoints()) { const int index = filteredShapes.indexOf(point->parent()); if (index >= 0) { filteredShapes.removeAt(index); } } } if (!omitEditedShape && m_snapGuide->additionalEditedShape()) { filteredShapes.append(m_snapGuide->additionalEditedShape()); } return filteredShapes; } KoCanvasBase * KoSnapProxy::canvas() { return m_snapGuide->canvas(); } diff --git a/libs/image/floodfill/kis_scanline_fill.cpp b/libs/image/floodfill/kis_scanline_fill.cpp index af38c0f2c4..a65204ccca 100644 --- a/libs/image/floodfill/kis_scanline_fill.cpp +++ b/libs/image/floodfill/kis_scanline_fill.cpp @@ -1,726 +1,726 @@ /* * 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_scanline_fill.h" #include #include #include #include #include #include "kis_image.h" #include "kis_fill_interval_map.h" #include "kis_pixel_selection.h" #include "kis_random_accessor_ng.h" #include "kis_fill_sanity_checks.h" template class CopyToSelection : public BaseClass { public: typedef KisRandomConstAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomConstAccessorNG(); } public: void setDestinationSelection(KisPaintDeviceSP pixelSelection) { m_pixelSelection = pixelSelection; m_it = m_pixelSelection->createRandomAccessorNG(); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(dstPtr); m_it->moveTo(x, y); *m_it->rawData() = opacity; } private: KisPaintDeviceSP m_pixelSelection; KisRandomAccessorSP m_it; }; template class FillWithColor : public BaseClass { public: typedef KisRandomAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomAccessorNG(); } public: FillWithColor() : m_pixelSize(0) {} void setFillColor(const KoColor &sourceColor) { m_sourceColor = sourceColor; m_pixelSize = sourceColor.colorSpace()->pixelSize(); m_data = m_sourceColor.data(); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(x); Q_UNUSED(y); if (opacity == MAX_SELECTED) { memcpy(dstPtr, m_data, m_pixelSize); } } private: KoColor m_sourceColor; const quint8 *m_data; int m_pixelSize; }; template class FillWithColorExternal : public BaseClass { public: typedef KisRandomConstAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomConstAccessorNG(); } public: void setDestinationDevice(KisPaintDeviceSP device) { m_externalDevice = device; m_it = m_externalDevice->createRandomAccessorNG(); } void setFillColor(const KoColor &sourceColor) { m_sourceColor = sourceColor; m_pixelSize = sourceColor.colorSpace()->pixelSize(); m_data = m_sourceColor.data(); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(dstPtr); m_it->moveTo(x, y); if (opacity == MAX_SELECTED) { memcpy(m_it->rawData(), m_data, m_pixelSize); } } private: KisPaintDeviceSP m_externalDevice; KisRandomAccessorSP m_it; KoColor m_sourceColor; const quint8 *m_data {0}; int m_pixelSize {0}; }; class DifferencePolicySlow { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) { m_colorSpace = device->colorSpace(); m_srcPixel = srcPixel; m_srcPixelPtr = m_srcPixel.data(); m_threshold = threshold; } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { if (m_threshold == 1) { if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) { return 0; } return quint8_MAX; } else { - return m_colorSpace->difference(m_srcPixelPtr, pixelPtr); + return m_colorSpace->differenceA(m_srcPixelPtr, pixelPtr); } } private: const KoColorSpace *m_colorSpace; KoColor m_srcPixel; const quint8 *m_srcPixelPtr; int m_threshold; }; template class DifferencePolicyOptimized { typedef SrcPixelType HashKeyType; typedef QHash HashType; public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) { m_colorSpace = device->colorSpace(); m_srcPixel = srcPixel; m_srcPixelPtr = m_srcPixel.data(); m_threshold = threshold; } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { HashKeyType key = *reinterpret_cast(pixelPtr); quint8 result; typename HashType::iterator it = m_differences.find(key); if (it != m_differences.end()) { result = *it; } else { if (m_threshold == 1) { if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) { result = 0; } else { result = quint8_MAX; } } else { - result = m_colorSpace->difference(m_srcPixelPtr, pixelPtr); + result = m_colorSpace->differenceA(m_srcPixelPtr, pixelPtr); } m_differences.insert(key, result); } return result; } private: HashType m_differences; const KoColorSpace *m_colorSpace; KoColor m_srcPixel; const quint8 *m_srcPixelPtr; int m_threshold; }; template class PixelFiller> class SelectionPolicy : public PixelFiller { public: typename PixelFiller::SourceAccessorType m_srcIt; public: SelectionPolicy(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) : m_threshold(threshold) { this->initDifferences(device, srcPixel, threshold); m_srcIt = this->createSourceDeviceAccessor(device); } ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) { quint8 diff = this->calculateDifference(pixelPtr); if (!useSmoothSelection) { return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED; } else { quint8 selectionValue = qMax(0, m_threshold - diff); quint8 result = MIN_SELECTED; if (selectionValue > 0) { qreal selectionNorm = qreal(selectionValue) / m_threshold; result = MAX_SELECTED * selectionNorm; } return result; } } private: int m_threshold; }; class IsNonNullPolicySlow { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) { Q_UNUSED(srcPixel); m_pixelSize = device->pixelSize(); m_testPixel.resize(m_pixelSize); } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { if (memcmp(m_testPixel.data(), pixelPtr, m_pixelSize) == 0) { return 0; } return quint8_MAX; } private: int m_pixelSize {0}; QByteArray m_testPixel; }; template class IsNonNullPolicyOptimized { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) { Q_UNUSED(device); Q_UNUSED(srcPixel); } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { SrcPixelType *pixel = reinterpret_cast(pixelPtr); return *pixel == 0; } }; class GroupSplitPolicy { public: typedef KisRandomConstAccessorSP SourceAccessorType; SourceAccessorType m_srcIt; public: GroupSplitPolicy(KisPaintDeviceSP scribbleDevice, KisPaintDeviceSP groupMapDevice, qint32 groupIndex, quint8 referenceValue, int threshold) : m_threshold(threshold), m_groupIndex(groupIndex), m_referenceValue(referenceValue) { KIS_SAFE_ASSERT_RECOVER_NOOP(m_groupIndex > 0); m_srcIt = scribbleDevice->createRandomConstAccessorNG(); m_groupMapIt = groupMapDevice->createRandomAccessorNG(); } ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) { // TODO: either threshold should always be null, or there should be a special // case for *pixelPtr == 0, which is different from all the other groups, // whatever the threshold is int diff = qAbs(int(*pixelPtr) - m_referenceValue); return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED; } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(opacity); // erase the scribble *dstPtr = 0; // write group index into the map m_groupMapIt->moveTo(x, y); qint32 *groupMapPtr = reinterpret_cast(m_groupMapIt->rawData()); if (*groupMapPtr != 0) { dbgImage << ppVar(*groupMapPtr) << ppVar(m_groupIndex); } KIS_SAFE_ASSERT_RECOVER_NOOP(*groupMapPtr == 0); *groupMapPtr = m_groupIndex; } private: int m_threshold; qint32 m_groupIndex; quint8 m_referenceValue; KisRandomAccessorSP m_groupMapIt; }; struct Q_DECL_HIDDEN KisScanlineFill::Private { KisPaintDeviceSP device; QPoint startPoint; QRect boundingRect; int threshold; int rowIncrement; KisFillIntervalMap backwardMap; QStack forwardStack; inline void swapDirection() { rowIncrement *= -1; KIS_SAFE_ASSERT_RECOVER_NOOP(forwardStack.isEmpty() && "FATAL: the forward stack must be empty " "on a direction swap"); forwardStack = QStack(backwardMap.fetchAllIntervals(rowIncrement)); backwardMap.clear(); } }; KisScanlineFill::KisScanlineFill(KisPaintDeviceSP device, const QPoint &startPoint, const QRect &boundingRect) : m_d(new Private) { m_d->device = device; m_d->startPoint = startPoint; m_d->boundingRect = boundingRect; m_d->rowIncrement = 1; m_d->threshold = 0; } KisScanlineFill::~KisScanlineFill() { } void KisScanlineFill::setThreshold(int threshold) { m_d->threshold = threshold; } template void KisScanlineFill::extendedPass(KisFillInterval *currentInterval, int srcRow, bool extendRight, T &pixelPolicy) { int x; int endX; int columnIncrement; int *intervalBorder; int *backwardIntervalBorder; KisFillInterval backwardInterval(currentInterval->start, currentInterval->end, srcRow); if (extendRight) { x = currentInterval->end; endX = m_d->boundingRect.right(); if (x >= endX) return; columnIncrement = 1; intervalBorder = ¤tInterval->end; backwardInterval.start = currentInterval->end + 1; backwardIntervalBorder = &backwardInterval.end; } else { x = currentInterval->start; endX = m_d->boundingRect.left(); if (x <= endX) return; columnIncrement = -1; intervalBorder = ¤tInterval->start; backwardInterval.end = currentInterval->start - 1; backwardIntervalBorder = &backwardInterval.start; } do { x += columnIncrement; pixelPolicy.m_srcIt->moveTo(x, srcRow); quint8 *pixelPtr = const_cast(pixelPolicy.m_srcIt->rawDataConst()); // TODO: avoid doing const_cast quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr); if (opacity) { *intervalBorder = x; *backwardIntervalBorder = x; pixelPolicy.fillPixel(pixelPtr, opacity, x, srcRow); } else { break; } } while (x != endX); if (backwardInterval.isValid()) { m_d->backwardMap.insertInterval(backwardInterval); } } template void KisScanlineFill::processLine(KisFillInterval interval, const int rowIncrement, T &pixelPolicy) { m_d->backwardMap.cropInterval(&interval); if (!interval.isValid()) return; int firstX = interval.start; int lastX = interval.end; int x = firstX; int row = interval.row; int nextRow = row + rowIncrement; KisFillInterval currentForwardInterval; int numPixelsLeft = 0; quint8 *dataPtr = 0; const int pixelSize = m_d->device->pixelSize(); while(x <= lastX) { // a bit of optimzation for not calling slow random accessor // methods too often if (numPixelsLeft <= 0) { pixelPolicy.m_srcIt->moveTo(x, row); numPixelsLeft = pixelPolicy.m_srcIt->numContiguousColumns(x) - 1; dataPtr = const_cast(pixelPolicy.m_srcIt->rawDataConst()); } else { numPixelsLeft--; dataPtr += pixelSize; } quint8 *pixelPtr = dataPtr; quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr); if (opacity) { if (!currentForwardInterval.isValid()) { currentForwardInterval.start = x; currentForwardInterval.end = x; currentForwardInterval.row = nextRow; } else { currentForwardInterval.end = x; } pixelPolicy.fillPixel(pixelPtr, opacity, x, row); if (x == firstX) { extendedPass(¤tForwardInterval, row, false, pixelPolicy); } if (x == lastX) { extendedPass(¤tForwardInterval, row, true, pixelPolicy); } } else { if (currentForwardInterval.isValid()) { m_d->forwardStack.push(currentForwardInterval); currentForwardInterval.invalidate(); } } x++; } if (currentForwardInterval.isValid()) { m_d->forwardStack.push(currentForwardInterval); } } template void KisScanlineFill::runImpl(T &pixelPolicy) { KIS_ASSERT_RECOVER_RETURN(m_d->forwardStack.isEmpty()); KisFillInterval startInterval(m_d->startPoint.x(), m_d->startPoint.x(), m_d->startPoint.y()); m_d->forwardStack.push(startInterval); /** * In the end of the first pass we should add an interval * containing the starting pixel, but directed into the opposite * direction. We cannot do it in the very beginning because the * intervals are offset by 1 pixel during every swap operation. */ bool firstPass = true; while (!m_d->forwardStack.isEmpty()) { while (!m_d->forwardStack.isEmpty()) { KisFillInterval interval = m_d->forwardStack.pop(); if (interval.row > m_d->boundingRect.bottom() || interval.row < m_d->boundingRect.top()) { continue; } processLine(interval, m_d->rowIncrement, pixelPolicy); } m_d->swapDirection(); if (firstPass) { startInterval.row--; m_d->forwardStack.push(startInterval); firstPass = false; } } } void KisScanlineFill::fillColor(const KoColor &originalFillColor) { KoColor srcColor(m_d->device->pixel(m_d->startPoint)); KoColor fillColor(originalFillColor); fillColor.convertTo(m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } } void KisScanlineFill::fillColor(const KoColor &originalFillColor, KisPaintDeviceSP externalDevice) { KoColor srcColor(m_d->device->pixel(m_d->startPoint)); KoColor fillColor(originalFillColor); fillColor.convertTo(m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } } void KisScanlineFill::fillSelection(KisPixelSelectionSP pixelSelection) { KoColor srcColor(m_d->device->pixel(m_d->startPoint)); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } } void KisScanlineFill::clearNonZeroComponent() { const int pixelSize = m_d->device->pixelSize(); KoColor srcColor(Qt::transparent, m_d->device->colorSpace()); if (pixelSize == 1) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } } void KisScanlineFill::fillContiguousGroup(KisPaintDeviceSP groupMapDevice, qint32 groupIndex) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->device->pixelSize() == 1); KIS_SAFE_ASSERT_RECOVER_RETURN(groupMapDevice->pixelSize() == 4); const quint8 referenceValue = *m_d->device->pixel(m_d->startPoint).data(); GroupSplitPolicy policy(m_d->device, groupMapDevice, groupIndex, referenceValue, m_d->threshold); runImpl(policy); } void KisScanlineFill::testingProcessLine(const KisFillInterval &processInterval) { KoColor srcColor(QColor(0,0,0,0), m_d->device->colorSpace()); KoColor fillColor(QColor(200,200,200,200), m_d->device->colorSpace()); SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); processLine(processInterval, 1, policy); } QVector KisScanlineFill::testingGetForwardIntervals() const { return QVector(m_d->forwardStack); } KisFillIntervalMap* KisScanlineFill::testingGetBackwardIntervals() const { return &m_d->backwardMap; } diff --git a/libs/image/kis_liquify_transform_worker.cpp b/libs/image/kis_liquify_transform_worker.cpp index e7d171bd43..d03ebc5c65 100644 --- a/libs/image/kis_liquify_transform_worker.cpp +++ b/libs/image/kis_liquify_transform_worker.cpp @@ -1,602 +1,608 @@ /* * 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_liquify_transform_worker.h" #include "kis_grid_interpolation_tools.h" #include "kis_dom_utils.h" #include "krita_utils.h" struct Q_DECL_HIDDEN KisLiquifyTransformWorker::Private { Private(const QRect &_srcBounds, KoUpdater *_progress, int _pixelPrecision) : srcBounds(_srcBounds), progress(_progress), pixelPrecision(_pixelPrecision) { } const QRect srcBounds; QVector originalPoints; QVector transformedPoints; KoUpdater *progress; int pixelPrecision; QSize gridSize; void preparePoints(); struct MapIndexesOp; template void processTransformedPixelsBuildUp(ProcessOp op, const QPointF &base, qreal sigma); template void processTransformedPixelsWash(ProcessOp op, const QPointF &base, qreal sigma, qreal flow); template void processTransformedPixels(ProcessOp op, const QPointF &base, qreal sigma, bool useWashMode, qreal flow); }; KisLiquifyTransformWorker::KisLiquifyTransformWorker(const QRect &srcBounds, KoUpdater *progress, int pixelPrecision) : m_d(new Private(srcBounds, progress, pixelPrecision)) { KIS_ASSERT_RECOVER_RETURN(!srcBounds.isEmpty()); // TODO: implement 'progress' stuff m_d->preparePoints(); } KisLiquifyTransformWorker::KisLiquifyTransformWorker(const KisLiquifyTransformWorker &rhs) : m_d(new Private(*rhs.m_d.data())) { } KisLiquifyTransformWorker::~KisLiquifyTransformWorker() { } bool KisLiquifyTransformWorker::operator==(const KisLiquifyTransformWorker &other) const { bool result = m_d->srcBounds == other.m_d->srcBounds && m_d->pixelPrecision == other.m_d->pixelPrecision && m_d->gridSize == other.m_d->gridSize && m_d->originalPoints.size() == other.m_d->originalPoints.size() && m_d->transformedPoints.size() == other.m_d->transformedPoints.size(); if (!result) return false; const qreal eps = 1e-6; result = KisAlgebra2D::fuzzyPointCompare(m_d->originalPoints, other.m_d->originalPoints, eps) && KisAlgebra2D::fuzzyPointCompare(m_d->transformedPoints, other.m_d->transformedPoints, eps); return result; } +bool KisLiquifyTransformWorker::isIdentity() const +{ + const qreal eps = 1e-6; + return KisAlgebra2D::fuzzyPointCompare(m_d->originalPoints, m_d->transformedPoints, eps); +} + int KisLiquifyTransformWorker::pointToIndex(const QPoint &cellPt) { return GridIterationTools::pointToIndex(cellPt, m_d->gridSize); } QSize KisLiquifyTransformWorker::gridSize() const { return m_d->gridSize; } const QVector& KisLiquifyTransformWorker::originalPoints() const { return m_d->originalPoints; } QVector& KisLiquifyTransformWorker::transformedPoints() { return m_d->transformedPoints; } struct AllPointsFetcherOp { AllPointsFetcherOp(QRectF srcRect) : m_srcRect(srcRect) {} inline void processPoint(int col, int row, int prevCol, int prevRow, int colIndex, int rowIndex) { Q_UNUSED(prevCol); Q_UNUSED(prevRow); Q_UNUSED(colIndex); Q_UNUSED(rowIndex); QPointF pt(col, row); m_points << pt; } inline void nextLine() { } QVector m_points; QRectF m_srcRect; }; void KisLiquifyTransformWorker::Private::preparePoints() { gridSize = GridIterationTools::calcGridSize(srcBounds, pixelPrecision); AllPointsFetcherOp pointsOp(srcBounds); GridIterationTools::processGrid(pointsOp, srcBounds, pixelPrecision); const int numPoints = pointsOp.m_points.size(); KIS_ASSERT_RECOVER_RETURN(numPoints == gridSize.width() * gridSize.height()); originalPoints = pointsOp.m_points; transformedPoints = pointsOp.m_points; } void KisLiquifyTransformWorker::translate(const QPointF &offset) { QVector::iterator it = m_d->transformedPoints.begin(); QVector::iterator end = m_d->transformedPoints.end(); QVector::iterator refIt = m_d->originalPoints.begin(); KIS_ASSERT_RECOVER_RETURN(m_d->originalPoints.size() == m_d->transformedPoints.size()); for (; it != end; ++it, ++refIt) { *it += offset; *refIt += offset; } } void KisLiquifyTransformWorker::undoPoints(const QPointF &base, qreal amount, qreal sigma) { const qreal maxDistCoeff = 3.0; const qreal maxDist = maxDistCoeff * sigma; QRectF clipRect(base.x() - maxDist, base.y() - maxDist, 2 * maxDist, 2 * maxDist); QVector::iterator it = m_d->transformedPoints.begin(); QVector::iterator end = m_d->transformedPoints.end(); QVector::iterator refIt = m_d->originalPoints.begin(); KIS_ASSERT_RECOVER_RETURN(m_d->originalPoints.size() == m_d->transformedPoints.size()); for (; it != end; ++it, ++refIt) { if (!clipRect.contains(*it)) continue; QPointF diff = *it - base; qreal dist = KisAlgebra2D::norm(diff); if (dist > maxDist) continue; qreal lambda = exp(-0.5 * pow2(dist / sigma)); lambda *= amount; *it = *refIt * lambda + *it * (1.0 - lambda); } } template void KisLiquifyTransformWorker::Private:: processTransformedPixelsBuildUp(ProcessOp op, const QPointF &base, qreal sigma) { const qreal maxDist = ProcessOp::maxDistCoeff * sigma; QRectF clipRect(base.x() - maxDist, base.y() - maxDist, 2 * maxDist, 2 * maxDist); QVector::iterator it = transformedPoints.begin(); QVector::iterator end = transformedPoints.end(); for (; it != end; ++it) { if (!clipRect.contains(*it)) continue; QPointF diff = *it - base; qreal dist = KisAlgebra2D::norm(diff); if (dist > maxDist) continue; const qreal lambda = exp(-0.5 * pow2(dist / sigma)); *it = op(*it, base, diff, lambda); } } template void KisLiquifyTransformWorker::Private:: processTransformedPixelsWash(ProcessOp op, const QPointF &base, qreal sigma, qreal flow) { const qreal maxDist = ProcessOp::maxDistCoeff * sigma; QRectF clipRect(base.x() - maxDist, base.y() - maxDist, 2 * maxDist, 2 * maxDist); QVector::iterator it = transformedPoints.begin(); QVector::iterator end = transformedPoints.end(); QVector::iterator refIt = originalPoints.begin(); KIS_ASSERT_RECOVER_RETURN(originalPoints.size() == transformedPoints.size()); for (; it != end; ++it, ++refIt) { if (!clipRect.contains(*it)) continue; QPointF diff = *refIt - base; qreal dist = KisAlgebra2D::norm(diff); if (dist > maxDist) continue; const qreal lambda = exp(-0.5 * pow2(dist / sigma)); QPointF dstPt = op(*refIt, base, diff, lambda); if (kisDistance(dstPt, *refIt) > kisDistance(*it, *refIt)) { *it = (1.0 - flow) * (*it) + flow * dstPt; } } } template void KisLiquifyTransformWorker::Private:: processTransformedPixels(ProcessOp op, const QPointF &base, qreal sigma, bool useWashMode, qreal flow) { if (useWashMode) { processTransformedPixelsWash(op, base, sigma, flow); } else { processTransformedPixelsBuildUp(op, base, sigma); } } struct TranslateOp { TranslateOp(const QPointF &offset) : m_offset(offset) {} QPointF operator() (const QPointF &pt, const QPointF &base, const QPointF &diff, qreal lambda) { Q_UNUSED(base); Q_UNUSED(diff); return pt + lambda * m_offset; } static const qreal maxDistCoeff; QPointF m_offset; }; const qreal TranslateOp::maxDistCoeff = 3.0; struct ScaleOp { ScaleOp(qreal scale) : m_scale(scale) {} QPointF operator() (const QPointF &pt, const QPointF &base, const QPointF &diff, qreal lambda) { Q_UNUSED(pt); Q_UNUSED(diff); return base + (1.0 + m_scale * lambda) * diff; } static const qreal maxDistCoeff; qreal m_scale; }; const qreal ScaleOp::maxDistCoeff = 3.0; struct RotateOp { RotateOp(qreal angle) : m_angle(angle) {} QPointF operator() (const QPointF &pt, const QPointF &base, const QPointF &diff, qreal lambda) { Q_UNUSED(pt); const qreal angle = m_angle * lambda; const qreal sinA = std::sin(angle); const qreal cosA = std::cos(angle); qreal x = cosA * diff.x() + sinA * diff.y(); qreal y = -sinA * diff.x() + cosA * diff.y(); return base + QPointF(x, y); } static const qreal maxDistCoeff; qreal m_angle; }; const qreal RotateOp::maxDistCoeff = 3.0; void KisLiquifyTransformWorker::translatePoints(const QPointF &base, const QPointF &offset, qreal sigma, bool useWashMode, qreal flow) { TranslateOp op(offset); m_d->processTransformedPixels(op, base, sigma, useWashMode, flow); } void KisLiquifyTransformWorker::scalePoints(const QPointF &base, qreal scale, qreal sigma, bool useWashMode, qreal flow) { ScaleOp op(scale); m_d->processTransformedPixels(op, base, sigma, useWashMode, flow); } void KisLiquifyTransformWorker::rotatePoints(const QPointF &base, qreal angle, qreal sigma, bool useWashMode, qreal flow) { RotateOp op(angle); m_d->processTransformedPixels(op, base, sigma, useWashMode, flow); } struct KisLiquifyTransformWorker::Private::MapIndexesOp { MapIndexesOp(KisLiquifyTransformWorker::Private *d) : m_d(d) { } inline QVector calculateMappedIndexes(int col, int row, int *numExistingPoints) const { *numExistingPoints = 4; QVector cellIndexes = GridIterationTools::calculateCellIndexes(col, row, m_d->gridSize); return cellIndexes; } inline int tryGetValidIndex(const QPoint &cellPt) const { Q_UNUSED(cellPt); KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable"); return -1; } inline QPointF getSrcPointForce(const QPoint &cellPt) const { Q_UNUSED(cellPt); KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable"); return QPointF(); } inline const QPolygonF srcCropPolygon() const { KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable"); return QPolygonF(); } KisLiquifyTransformWorker::Private *m_d; }; void KisLiquifyTransformWorker::run(KisPaintDeviceSP device) { KisPaintDeviceSP srcDev = new KisPaintDevice(*device.data()); device->clear(); using namespace GridIterationTools; PaintDevicePolygonOp polygonOp(srcDev, device); Private::MapIndexesOp indexesOp(m_d.data()); iterateThroughGrid(polygonOp, indexesOp, m_d->gridSize, m_d->originalPoints, m_d->transformedPoints); } QRect KisLiquifyTransformWorker::approxChangeRect(const QRect &rc) { const qreal margin = 0.05; /** * Here we just return the full area occupied by the transformed grid. * We sample grid points for not doing too much work. */ const int maxSamplePoints = 200; const int minStep = 3; const int step = qMax(minStep, m_d->transformedPoints.size() / maxSamplePoints); QVector samplePoints; for (auto it = m_d->transformedPoints.constBegin(); it != m_d->transformedPoints.constEnd(); ++it) { samplePoints << it->toPoint(); } QRect resultRect = KisAlgebra2D::approximateRectFromPoints(samplePoints); return KisAlgebra2D::blowRect(resultRect | rc, margin); } QRect KisLiquifyTransformWorker::approxNeedRect(const QRect &rc, const QRect &fullBounds) { Q_UNUSED(rc); return fullBounds; } #include #include using PointMapFunction = std::function; PointMapFunction bindPointMapTransform(const QTransform &transform) { using namespace std::placeholders; typedef QPointF (QTransform::*MapFuncType)(const QPointF&) const; return std::bind(static_cast(&QTransform::map), &transform, _1); } QImage KisLiquifyTransformWorker::runOnQImage(const QImage &srcImage, const QPointF &srcImageOffset, const QTransform &imageToThumbTransform, QPointF *newOffset) { KIS_ASSERT_RECOVER(m_d->originalPoints.size() == m_d->transformedPoints.size()) { return QImage(); } KIS_ASSERT_RECOVER(!srcImage.isNull()) { return QImage(); } KIS_ASSERT_RECOVER(srcImage.format() == QImage::Format_ARGB32) { return QImage(); } QVector originalPointsLocal(m_d->originalPoints); QVector transformedPointsLocal(m_d->transformedPoints); PointMapFunction mapFunc = bindPointMapTransform(imageToThumbTransform); std::transform(originalPointsLocal.begin(), originalPointsLocal.end(), originalPointsLocal.begin(), mapFunc); std::transform(transformedPointsLocal.begin(), transformedPointsLocal.end(), transformedPointsLocal.begin(), mapFunc); QRectF dstBounds; Q_FOREACH (const QPointF &pt, transformedPointsLocal) { KisAlgebra2D::accumulateBounds(pt, &dstBounds); } const QRectF srcBounds(srcImageOffset, srcImage.size()); dstBounds |= srcBounds; QPointF dstQImageOffset = dstBounds.topLeft(); *newOffset = dstQImageOffset; QRect dstBoundsI = dstBounds.toAlignedRect(); QImage dstImage(dstBoundsI.size(), srcImage.format()); dstImage.fill(0); GridIterationTools::QImagePolygonOp polygonOp(srcImage, dstImage, srcImageOffset, dstQImageOffset); Private::MapIndexesOp indexesOp(m_d.data()); GridIterationTools::iterateThroughGrid (polygonOp, indexesOp, m_d->gridSize, originalPointsLocal, transformedPointsLocal); return dstImage; } void KisLiquifyTransformWorker::toXML(QDomElement *e) const { QDomDocument doc = e->ownerDocument(); QDomElement liqEl = doc.createElement("liquify_points"); e->appendChild(liqEl); KisDomUtils::saveValue(&liqEl, "srcBounds", m_d->srcBounds); KisDomUtils::saveValue(&liqEl, "originalPoints", m_d->originalPoints); KisDomUtils::saveValue(&liqEl, "transformedPoints", m_d->transformedPoints); KisDomUtils::saveValue(&liqEl, "pixelPrecision", m_d->pixelPrecision); KisDomUtils::saveValue(&liqEl, "gridSize", m_d->gridSize); } KisLiquifyTransformWorker* KisLiquifyTransformWorker::fromXML(const QDomElement &e) { QDomElement liquifyEl; QRect srcBounds; QVector originalPoints; QVector transformedPoints; int pixelPrecision; QSize gridSize; bool result = false; result = KisDomUtils::findOnlyElement(e, "liquify_points", &liquifyEl) && KisDomUtils::loadValue(liquifyEl, "srcBounds", &srcBounds) && KisDomUtils::loadValue(liquifyEl, "originalPoints", &originalPoints) && KisDomUtils::loadValue(liquifyEl, "transformedPoints", &transformedPoints) && KisDomUtils::loadValue(liquifyEl, "pixelPrecision", &pixelPrecision) && KisDomUtils::loadValue(liquifyEl, "gridSize", &gridSize); if (!result) { warnKrita << "WARNING: Failed to load liquify worker from XML"; return new KisLiquifyTransformWorker(QRect(0,0,1024, 1024), 0, 8); } KisLiquifyTransformWorker *worker = new KisLiquifyTransformWorker(srcBounds, 0, pixelPrecision); const int numPoints = originalPoints.size(); if (numPoints != transformedPoints.size() || numPoints != worker->m_d->originalPoints.size() || gridSize != worker->m_d->gridSize) { warnKrita << "WARNING: Inconsistent number of points!"; warnKrita << ppVar(originalPoints.size()); warnKrita << ppVar(transformedPoints.size()); warnKrita << ppVar(gridSize); warnKrita << ppVar(worker->m_d->originalPoints.size()); warnKrita << ppVar(worker->m_d->transformedPoints.size()); warnKrita << ppVar(worker->m_d->gridSize); return worker; } for (int i = 0; i < numPoints; i++) { worker->m_d->originalPoints[i] = originalPoints[i]; worker->m_d->transformedPoints[i] = transformedPoints[i]; } return worker; } diff --git a/libs/image/kis_liquify_transform_worker.h b/libs/image/kis_liquify_transform_worker.h index 83d7ab5016..669533b4bb 100644 --- a/libs/image/kis_liquify_transform_worker.h +++ b/libs/image/kis_liquify_transform_worker.h @@ -1,95 +1,97 @@ /* * 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_LIQUIFY_TRANSFORM_WORKER_H #define __KIS_LIQUIFY_TRANSFORM_WORKER_H #include #include #include #include class QImage; class QRect; class QSize; class QTransform; class QDomElement; class KRITAIMAGE_EXPORT KisLiquifyTransformWorker : boost::equality_comparable { public: KisLiquifyTransformWorker(const QRect &srcBounds, KoUpdater *progress, int pixelPrecision = 8); KisLiquifyTransformWorker(const KisLiquifyTransformWorker &rhs); ~KisLiquifyTransformWorker(); bool operator==(const KisLiquifyTransformWorker &other) const; + bool isIdentity() const; + int pointToIndex(const QPoint &cellPt); QSize gridSize() const; void translatePoints(const QPointF &base, const QPointF &offset, qreal sigma, bool useWashMode, qreal flow); void scalePoints(const QPointF &base, qreal scale, qreal sigma, bool useWashMode, qreal flow); void rotatePoints(const QPointF &base, qreal angle, qreal sigma, bool useWashMode, qreal flow); void undoPoints(const QPointF &base, qreal amount, qreal sigma); const QVector& originalPoints() const; QVector& transformedPoints(); void run(KisPaintDeviceSP device); QImage runOnQImage(const QImage &srcImage, const QPointF &srcImageOffset, const QTransform &imageToThumbTransform, QPointF *newOffset); void toXML(QDomElement *e) const; static KisLiquifyTransformWorker* fromXML(const QDomElement &e); void translate(const QPointF &offset); QRect approxChangeRect(const QRect &rc); QRect approxNeedRect(const QRect &rc, const QRect &fullBounds); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_LIQUIFY_TRANSFORM_WORKER_H */ diff --git a/libs/pigment/KoColorSpace.h b/libs/pigment/KoColorSpace.h index dd693420f8..b83fcb4a09 100644 --- a/libs/pigment/KoColorSpace.h +++ b/libs/pigment/KoColorSpace.h @@ -1,708 +1,715 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (c) 2006-2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORSPACE_H #define KOCOLORSPACE_H #include #include #include #include #include #include #include "KoColorSpaceConstants.h" #include "KoColorConversionTransformation.h" #include "KoColorProofingConversionTransformation.h" #include "KoCompositeOp.h" #include #include "kritapigment_export.h" class QDomDocument; class QDomElement; class KoChannelInfo; class KoColorProfile; class KoColorTransformation; class QBitArray; enum Deletability { OwnedByRegistryDoNotDelete, OwnedByRegistryRegistryDeletes, NotOwnedByRegistry }; enum ColorSpaceIndependence { FULLY_INDEPENDENT, TO_LAB16, TO_RGBA8, TO_RGBA16 }; class KoMixColorsOp; class KoConvolutionOp; /** * A KoColorSpace is the definition of a certain color space. * * A color model and a color space are two related concepts. A color * model is more general in that it describes the channels involved and * how they in broad terms combine to describe a color. Examples are * RGB, HSV, CMYK. * * A color space is more specific in that it also describes exactly how * the channels are combined. So for each color model there can be a * number of specific color spaces. So RGB is the model and sRGB, * adobeRGB, etc are colorspaces. * * In Pigment KoColorSpace acts as both a color model and a color space. * You can think of the class definition as the color model, but the * instance of the class as representing a colorspace. * * A third concept is the profile represented by KoColorProfile. It * represents the info needed to specialize a color model into a color * space. * * KoColorSpace is an abstract class serving as an interface. * * Subclasses implement actual color spaces * Some subclasses implement only some parts and are named Traits * */ class KRITAPIGMENT_EXPORT KoColorSpace : public boost::equality_comparable { friend class KoColorSpaceRegistry; friend class KoColorSpaceFactory; protected: /// Only for use by classes that serve as baseclass for real color spaces KoColorSpace(); public: /// Should be called by real color spaces KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp); virtual bool operator==(const KoColorSpace& rhs) const; protected: virtual ~KoColorSpace(); public: //========== Gamut and other basic info ===================================// /* * @returns QPolygonF with 5*channel samples converted to xyY. * maybe convert to 3d space in future? */ QPolygonF gamutXYY() const; /* * @returns a polygon with 5 samples per channel converted to xyY, but unlike * gamutxyY it focuses on the luminance. This then can be used to visualise * the approximate trc of a given colorspace. */ QPolygonF estimatedTRCXYY() const; QVector lumaCoefficients() const; //========== Channels =====================================================// /// Return a list describing all the channels this color model has. The order /// of the channels in the list is the order of channels in the pixel. To find /// out the preferred display position, use KoChannelInfo::displayPosition. QList channels() const; /** * The total number of channels for a single pixel in this color model */ virtual quint32 channelCount() const = 0; /** * Position of the alpha channel in a pixel */ virtual quint32 alphaPos() const = 0; /** * The total number of color channels (excludes alpha) for a single * pixel in this color model. */ virtual quint32 colorChannelCount() const = 0; /** * returns a QBitArray that contains true for the specified * channel types: * * @param color if true, set all color channels to true * @param alpha if true, set all alpha channels to true * * The order of channels is the colorspace descriptive order, * not the pixel order. */ QBitArray channelFlags(bool color = true, bool alpha = false) const; /** * The size in bytes of a single pixel in this color model */ virtual quint32 pixelSize() const = 0; /** * Return a string with the channel's value suitable for display in the gui. */ virtual QString channelValueText(const quint8 *pixel, quint32 channelIndex) const = 0; /** * Return a string with the channel's value with integer * channels normalised to the floating point range 0 to 1, if * appropriate. */ virtual QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const = 0; /** * Return a QVector of floats with channels' values normalized * to floating point range 0 to 1. */ virtual void normalisedChannelsValue(const quint8 *pixel, QVector &channels) const = 0; /** * Write in the pixel the value from the normalized vector. */ virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) const = 0; /** * Convert the value of the channel at the specified position into * an 8-bit value. The position is not the number of bytes, but * the position of the channel as defined in the channel info list. */ virtual quint8 scaleToU8(const quint8 * srcPixel, qint32 channelPos) const = 0; /** * Set dstPixel to the pixel containing only the given channel of srcPixel. The remaining channels * should be set to whatever makes sense for 'empty' channels of this color space, * with the intent being that the pixel should look like it only has the given channel. */ virtual void singleChannelPixel(quint8 *dstPixel, const quint8 *srcPixel, quint32 channelIndex) const = 0; //========== Identification ===============================================// /** * ID for use in files and internally: unchanging name. As the id must be unique * it is usually the concatenation of the id of the color model and of the color * depth, for instance "RGBA8" or "CMYKA16" or "XYZA32f". */ QString id() const; /** * User visible name which contains the name of the color model and of the color depth. * For instance "RGBA (8-bits)" or "CMYKA (16-bits)". */ QString name() const; /** * @return a string that identify the color model (for instance "RGB" or "CMYK" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorModelId() const = 0; /** * @return a string that identify the bit depth (for instance "U8" or "F16" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorDepthId() const = 0; /** * @return true if the profile given in argument can be used by this color space */ virtual bool profileIsCompatible(const KoColorProfile* profile) const = 0; /** * If false, images in this colorspace will degrade considerably by * functions, tools and filters that have the given measure of colorspace * independence. * * @param independence the measure to which this colorspace will suffer * from the manipulations of the tool or filter asking * @return false if no degradation will take place, true if degradation will * take place */ virtual bool willDegrade(ColorSpaceIndependence independence) const = 0; //========== Capabilities =================================================// /** * Tests if the colorspace offers the specific composite op. */ virtual bool hasCompositeOp(const QString & id) const; /** * Returns the list of user-visible composite ops supported by this colorspace. */ virtual QList compositeOps() const; /** * Retrieve a single composite op from the ones this colorspace offers. * If the requeste composite op does not exist, COMPOSITE_OVER is returned. */ const KoCompositeOp * compositeOp(const QString & id) const; /** * add a composite op to this colorspace. */ virtual void addCompositeOp(const KoCompositeOp * op); /** * Returns true if the colorspace supports channel values outside the * (normalised) range 0 to 1. */ virtual bool hasHighDynamicRange() const = 0; //========== Display profiles =============================================// /** * Return the profile of this color space. */ virtual const KoColorProfile * profile() const = 0; //================= Conversion functions ==================================// /** * The fromQColor methods take a given color defined as an RGB QColor * and fills a byte array with the corresponding color in the * the colorspace managed by this strategy. * * @param color the QColor that will be used to fill dst * @param dst a pointer to a pixel * @param profile the optional profile that describes the color values of QColor */ virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const = 0; /** * The toQColor methods take a byte array that is at least pixelSize() long * and converts the contents to a QColor, using the given profile as a source * profile and the optional profile as a destination profile. * * @param src a pointer to the source pixel * @param c the QColor that will be filled with the color at src * @param profile the optional profile that describes the color in c, for instance the monitor profile */ virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const = 0; /** * Convert the pixels in data to (8-bit BGRA) QImage using the specified profiles. * * @param data A pointer to a contiguous memory region containing width * height pixels * @param width in pixels * @param height in pixels * @param dstProfile destination profile * @param renderingIntent the rendering intent * @param conversionFlags conversion flags */ virtual QImage convertToQImage(const quint8 *data, qint32 width, qint32 height, const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Convert the specified data to Lab (D50). All colorspaces are guaranteed to support this * * @param src the source data * @param dst the destination data * @param nPixels the number of source pixels */ virtual void toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data from Lab (D50). to this colorspace. All colorspaces are * guaranteed to support this. * * @param src the pixels in 16 bit lab format * @param dst the destination data * @param nPixels the number of pixels in the array */ virtual void fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data to sRGB 16 bits. All colorspaces are guaranteed to support this * * @param src the source data * @param dst the destination data * @param nPixels the number of source pixels */ virtual void toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data from sRGB 16 bits. to this colorspace. All colorspaces are * guaranteed to support this. * * @param src the pixels in 16 bit rgb format * @param dst the destination data * @param nPixels the number of pixels in the array */ virtual void fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Create a color conversion transformation. */ virtual KoColorConversionTransformation* createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Convert a byte array of srcLen pixels *src to the specified color space * and put the converted bytes into the prepared byte array *dst. * * Returns false if the conversion failed, true if it succeeded * * This function is not thread-safe. If you want to apply multiple conversion * in different threads at the same time, you need to create one color converter * per-thread using createColorConverter. */ virtual bool convertPixelsTo(const quint8 * src, quint8 * dst, const KoColorSpace * dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; virtual KoColorConversionTransformation *createProofingTransform(const KoColorSpace * dstColorSpace, const KoColorSpace * proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, quint8 *gamutWarning, double adaptationState) const; /** * @brief proofPixelsTo * @param src source * @param dst destination * @param numPixels the amount of pixels. * @param proofingTransform the intent used for proofing. * @return */ virtual bool proofPixelsTo(const quint8 * src, quint8 * dst, quint32 numPixels, KoColorConversionTransformation *proofingTransform) const; /** * Convert @p nPixels pixels in @p src into their human-visible * visual representation. The channel is shown as grayscale. * * Both buffers are in the same color space. * * @param src source buffer in (*this) color space * @param dst destination buffer in the same color space as @p src * @param nPixels length of the buffers in number of pixels * @param pixelSize stride of each pixel in the destination buffer * @param selectedChannelIndex Index of the selected channel. */ virtual void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const = 0; /** * Convert @p nPixels pixels in @p src into their human-visible * visual representation. The channels are shown as if other channels were null (or, if Lab, L = 1.0, *a = *b = 0.0). * * Both buffers are in the same color space. * * @param src source buffer in (*this) color space * @param dst destination buffer in the same color space as @p src * @param nPixels length of the buffers in number of pixels * @param pixelSize stride of each pixel in the destination buffer * @param selectedChannels Bitmap of selected channels */ virtual void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const = 0; //============================== Manipulation functions ==========================// // // The manipulation functions have default implementations that _convert_ the pixel // to a QColor and back. Reimplement these methods in your color strategy! // /** * Get the alpha value of the given pixel, downscaled to an 8-bit value. */ virtual quint8 opacityU8(const quint8 * pixel) const = 0; virtual qreal opacityF(const quint8 * pixel) const = 0; /** * Set the alpha channel of the given run of pixels to the given value. * * pixels -- a pointer to the pixels that will have their alpha set to this value * alpha -- a downscaled 8-bit value for opacity * nPixels -- the number of pixels * */ virtual void setOpacity(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0; virtual void setOpacity(quint8 * pixels, qreal alpha, qint32 nPixels) const = 0; /** * Multiply the alpha channel of the given run of pixels by the given value. * * pixels -- a pointer to the pixels that will have their alpha set to this value * alpha -- a downscaled 8-bit value for opacity * nPixels -- the number of pixels * */ virtual void multiplyAlpha(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0; /** * Applies the specified 8-bit alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; the alpha values * are assumed to be 8-bits. */ virtual void applyAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0; /** * Applies the inverted 8-bit alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; the alpha values * are assumed to be 8-bits. */ virtual void applyInverseAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0; /** * Applies the specified float alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0 */ virtual void applyAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0; /** * Applies the inverted specified float alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0 */ virtual void applyInverseNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0; /** * Fills \p pixels with specified \p brushColor and then applies inverted brush * mask specified in \p alpha. */ virtual void fillInverseAlphaNormedFloatMaskWithColor(quint8 * pixels, const float * alpha, const quint8 *brushColor, qint32 nPixels) const = 0; /** * Fills \p dst with specified \p brushColor and then applies inverted brush * mask specified in \p brush. Premultiplied red channel of the brush is * used as an alpha channel for destination pixels. * * The equation is: * * dstC = colorC; * dstA = qAlpha(brush) * (255 - qRed(brush)) / 255; */ virtual void fillGrayBrushWithColor(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const = 0; /** * Fills \p dst with specified \p brushColor and then applies inverted brush * mask specified in \p brush. Inverted red channel of the brush is used * as lightness of the destination. Alpha channel of the brush is used as * alpha of the destination. * * The equation is: * * dstL_hsl = preserveLightness(colorL_hsl, lightFactor); * dstA = qAlpha(brush); * * For details on preserveLightness() formula, * see KoColorSpacePreserveLightnessUtils.h */ virtual void fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const; /** * Create an adjustment object for adjusting the brightness and contrast * transferValues is a 256 bins array with values from 0 to 0xFFFF * This function is thread-safe, but you need to create one KoColorTransformation per thread. */ virtual KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const = 0; /** * Create an adjustment object for adjusting individual channels * transferValues is an array of colorChannelCount number of 256 bins array with values from 0 to 0xFFFF * This function is thread-safe, but you need to create one KoColorTransformation per thread. * * The layout of the channels must be the following: * * 0..N-2 - color channels of the pixel; * N-1 - alpha channel of the pixel (if exists) */ virtual KoColorTransformation *createPerChannelAdjustment(const quint16 * const* transferValues) const = 0; /** * Darken all color channels with the given amount. If compensate is true, * the compensation factor will be used to limit the darkening. * */ virtual KoColorTransformation *createDarkenAdjustment(qint32 shade, bool compensate, qreal compensation) const = 0; /** * Invert color channels of the given pixels * This function is thread-safe, but you need to create one KoColorTransformation per thread. */ virtual KoColorTransformation *createInvertTransformation() const = 0; /** * Get the difference between 2 colors, normalized in the range (0,255). Only completely * opaque and completely transparent are taken into account when computing the difference; * other transparency levels are not regarded when finding the difference. + * + * Completely transparent pixels are treated as if they are completely + * different from any non-transparent pixels. */ virtual quint8 difference(const quint8* src1, const quint8* src2) const = 0; /** * Get the difference between 2 colors, normalized in the range (0,255). This function * takes the Alpha channel of the pixel into account. Alpha channel has the same * weight as Lightness channel. + * + * Completely transparent pixels are treated as if their color channels are + * the same as ones of the other pixel. In other words, only alpha channel + * difference is compared. */ virtual quint8 differenceA(const quint8* src1, const quint8* src2) const = 0; /** * @return the mix color operation of this colorspace (do not delete it locally, it's deleted by the colorspace). */ virtual KoMixColorsOp* mixColorsOp() const; /** * @return the convolution operation of this colorspace (do not delete it locally, it's deleted by the colorspace). */ virtual KoConvolutionOp* convolutionOp() const; /** * Calculate the intensity of the given pixel, scaled down to the range 0-255. XXX: Maybe this should be more flexible */ virtual quint8 intensity8(const quint8 * src) const = 0; /* *increase luminosity by step */ virtual void increaseLuminosity(quint8 * pixel, qreal step) const; virtual void decreaseLuminosity(quint8 * pixel, qreal step) const; virtual void increaseSaturation(quint8 * pixel, qreal step) const; virtual void decreaseSaturation(quint8 * pixel, qreal step) const; virtual void increaseHue(quint8 * pixel, qreal step) const; virtual void decreaseHue(quint8 * pixel, qreal step) const; virtual void increaseRed(quint8 * pixel, qreal step) const; virtual void increaseGreen(quint8 * pixel, qreal step) const; virtual void increaseBlue(quint8 * pixel, qreal step) const; virtual void increaseYellow(quint8 * pixel, qreal step) const; virtual void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const = 0; virtual QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const = 0; virtual void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const = 0; virtual QVector fromYUV(qreal *y, qreal *u, qreal *v) const = 0; /** * Compose two arrays of pixels together. If source and target * are not the same color model, the source pixels will be * converted to the target model. We're "dst" -- "dst" pixels are always in _this_ * colorspace. * * @param srcSpace the colorspace of the source pixels that will be composited onto "us" * @param params the information needed for blitting e.g. the source and destination pixel data, * the opacity and flow, ... * @param op the composition operator to use, e.g. COPY_OVER * @param renderingIntent the rendering intent * @param conversionFlags the conversion flags. * */ virtual void bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Serialize this color following Create's swatch color specification available * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft * * This function doesn't create the \ element but rather the \, * \, \ ... elements. It is assumed that colorElt is the \ * element. * * @param pixel buffer to serialized * @param colorElt root element for the serialization, it is assumed that this * element is \ * @param doc is the document containing colorElt */ virtual void colorToXML(const quint8* pixel, QDomDocument& doc, QDomElement& colorElt) const = 0; /** * Unserialize a color following Create's swatch color specification available * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft * * @param pixel buffer where the color will be unserialized * @param elt the element to unserialize (\, \, \) * @return the unserialize color, or an empty color object if the function failed * to unserialize the color */ virtual void colorFromXML(quint8* pixel, const QDomElement& elt) const = 0; KoColorTransformation* createColorTransformation(const QString & id, const QHash & parameters) const; protected: /** * Use this function in the constructor of your colorspace to add the information about a channel. * @param ci a pointer to the information about a channel */ virtual void addChannel(KoChannelInfo * ci); const KoColorConversionTransformation* toLabA16Converter() const; const KoColorConversionTransformation* fromLabA16Converter() const; const KoColorConversionTransformation* toRgbA16Converter() const; const KoColorConversionTransformation* fromRgbA16Converter() const; /** * Returns the thread-local conversion cache. If it doesn't exist * yet, it is created. If it is currently too small, it is resized. */ QVector * threadLocalConversionCache(quint32 size) const; /** * This function defines the behavior of the bitBlt function * when the composition of pixels in different colorspaces is * requested, that is in case: * * srcCS == any * dstCS == this * * 1) preferCompositionInSourceColorSpace() == false, * * the source pixels are first converted to *this color space * and then composition is performed. * * 2) preferCompositionInSourceColorSpace() == true, * * the destination pixels are first converted into *srcCS color * space, then the composition is done, and the result is finally * converted into *this colorspace. * * This is used by alpha8() color space mostly, because it has * weaker representation of the color, so the composition * should be done in CS with richer functionality. */ virtual bool preferCompositionInSourceColorSpace() const; struct Private; Private * const d; }; inline QDebug operator<<(QDebug dbg, const KoColorSpace *cs) { if (cs) { dbg.nospace() << cs->name() << " (" << cs->colorModelId().id() << "," << cs->colorDepthId().id() << " )"; } else { dbg.nospace() << "0x0"; } return dbg.space(); } #endif // KOCOLORSPACE_H diff --git a/libs/ui/kis_derived_resources.cpp b/libs/ui/kis_derived_resources.cpp index 624502a832..e6e619184f 100644 --- a/libs/ui/kis_derived_resources.cpp +++ b/libs/ui/kis_derived_resources.cpp @@ -1,313 +1,313 @@ /* * Copyright (c) 2016 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_derived_resources.h" #include "kis_signal_auto_connection.h" #include "kis_canvas_resource_provider.h" #include "kis_paintop_preset.h" #include "kis_paintop_settings.h" #include "kis_paintop_settings_update_proxy.h" struct KisPresetUpdateMediator::Private { KisSignalAutoConnectionsStore connections; }; KisPresetUpdateMediator::KisPresetUpdateMediator() : KoResourceUpdateMediator(KisCanvasResourceProvider::CurrentPaintOpPreset), m_d(new Private) { } KisPresetUpdateMediator::~KisPresetUpdateMediator() { } void KisPresetUpdateMediator::connectResource(QVariant sourceResource) { KisPaintOpPresetSP preset = sourceResource.value(); if (!preset) return; m_d->connections.clear(); m_d->connections.addUniqueConnection( preset->updateProxy(), SIGNAL(sigSettingsChanged()), this, SLOT(slotSettingsChanged())); } void KisPresetUpdateMediator::slotSettingsChanged() { emit sigResourceChanged(key()); } /*********************************************************************/ /* KisCompositeOpResourceConverter */ /*********************************************************************/ KisCompositeOpResourceConverter::KisCompositeOpResourceConverter() : KoDerivedResourceConverter(KisCanvasResourceProvider::CurrentCompositeOp, KisCanvasResourceProvider::CurrentPaintOpPreset) { } QVariant KisCompositeOpResourceConverter::fromSource(const QVariant &value) { KisPaintOpPresetSP preset = value.value(); return preset ? preset->settings()->paintOpCompositeOp() : QVariant(); } QVariant KisCompositeOpResourceConverter::toSource(const QVariant &value, const QVariant &sourceValue) { KisPaintOpPresetSP preset = sourceValue.value(); if (!preset) return sourceValue; preset->settings()->setPaintOpCompositeOp(value.toString()); return QVariant::fromValue(preset); } /*********************************************************************/ /* KisEffectiveCompositeOpResourceConverter */ /*********************************************************************/ KisEffectiveCompositeOpResourceConverter::KisEffectiveCompositeOpResourceConverter() : KoDerivedResourceConverter(KisCanvasResourceProvider::CurrentEffectiveCompositeOp, KisCanvasResourceProvider::CurrentPaintOpPreset) { } QVariant KisEffectiveCompositeOpResourceConverter::fromSource(const QVariant &value) { KisPaintOpPresetSP preset = value.value(); return preset ? preset->settings()->effectivePaintOpCompositeOp() : QVariant(); } QVariant KisEffectiveCompositeOpResourceConverter::toSource(const QVariant &value, const QVariant &sourceValue) { Q_UNUSED(value); // WARNING: we don't save that! KisPaintOpPresetSP preset = sourceValue.value(); if (!preset) return sourceValue; return QVariant::fromValue(preset); } /*********************************************************************/ /* KisOpacityResourceConverter */ /*********************************************************************/ KisOpacityResourceConverter::KisOpacityResourceConverter() : KoDerivedResourceConverter(KisCanvasResourceProvider::Opacity, KisCanvasResourceProvider::CurrentPaintOpPreset) { } QVariant KisOpacityResourceConverter::fromSource(const QVariant &value) { KisPaintOpPresetSP preset = value.value(); - return preset ? preset->settings()->paintOpOpacity() : QVariant(); + return preset ? preset->settings()->paintOpOpacity() : QVariant(1.0); } QVariant KisOpacityResourceConverter::toSource(const QVariant &value, const QVariant &sourceValue) { KisPaintOpPresetSP preset = sourceValue.value(); if (!preset) return sourceValue; preset->settings()->setPaintOpOpacity(value.toReal()); return QVariant::fromValue(preset); } /*********************************************************************/ /* KisFlowResourceConverter */ /*********************************************************************/ KisFlowResourceConverter::KisFlowResourceConverter() : KoDerivedResourceConverter(KisCanvasResourceProvider::Flow, KisCanvasResourceProvider::CurrentPaintOpPreset) { } QVariant KisFlowResourceConverter::fromSource(const QVariant &value) { KisPaintOpPresetSP preset = value.value(); return preset ? preset->settings()->paintOpFlow() : QVariant(); } QVariant KisFlowResourceConverter::toSource(const QVariant &value, const QVariant &sourceValue) { KisPaintOpPresetSP preset = sourceValue.value(); if (!preset) return sourceValue; preset->settings()->setPaintOpFlow(value.toReal()); return QVariant::fromValue(preset); } /*********************************************************************/ /* KisSizeResourceConverter */ /*********************************************************************/ KisSizeResourceConverter::KisSizeResourceConverter() : KoDerivedResourceConverter(KisCanvasResourceProvider::Size, KisCanvasResourceProvider::CurrentPaintOpPreset) { } QVariant KisSizeResourceConverter::fromSource(const QVariant &value) { KisPaintOpPresetSP preset = value.value(); return preset ? preset->settings()->paintOpSize() : QVariant(); } QVariant KisSizeResourceConverter::toSource(const QVariant &value, const QVariant &sourceValue) { KisPaintOpPresetSP preset = sourceValue.value(); if (!preset) return sourceValue; preset->settings()->setPaintOpSize(value.toReal()); return QVariant::fromValue(preset); } /*********************************************************************/ /* KisPatternSizeResourceConverter */ /*********************************************************************/ KisPatternSizeResourceConverter::KisPatternSizeResourceConverter() : KoDerivedResourceConverter(KisCanvasResourceProvider::Size, KisCanvasResourceProvider::CurrentPaintOpPreset) { } QVariant KisPatternSizeResourceConverter::fromSource(const QVariant& value) { KisPaintOpPresetSP preset = value.value(); return preset ? preset->settings()->paintOpPatternSize() : QVariant(); } QVariant KisPatternSizeResourceConverter::toSource(const QVariant& value, const QVariant& sourceValue) { KisPaintOpPresetSP preset = sourceValue.value(); if (!preset) return sourceValue; preset->settings()->setPaintOpPatternSize(value.toReal()); return QVariant::fromValue(preset); } /*********************************************************************/ /* KisLodAvailabilityResourceConverter */ /*********************************************************************/ KisLodAvailabilityResourceConverter::KisLodAvailabilityResourceConverter() : KoDerivedResourceConverter(KisCanvasResourceProvider::LodAvailability, KisCanvasResourceProvider::CurrentPaintOpPreset) { } QVariant KisLodAvailabilityResourceConverter::fromSource(const QVariant &value) { KisPaintOpPresetSP preset = value.value(); return preset ? KisPaintOpSettings::isLodUserAllowed(preset->settings()) : QVariant(); } QVariant KisLodAvailabilityResourceConverter::toSource(const QVariant &value, const QVariant &sourceValue) { KisPaintOpPresetSP preset = sourceValue.value(); if (!preset) return sourceValue; KisPaintOpSettings::setLodUserAllowed(preset->settings().data(), value.toBool()); return QVariant::fromValue(preset); } /*********************************************************************/ /* KisLodSizeThresholdResourceConverter */ /*********************************************************************/ KisLodSizeThresholdResourceConverter::KisLodSizeThresholdResourceConverter() : KoDerivedResourceConverter(KisCanvasResourceProvider::LodSizeThreshold, KisCanvasResourceProvider::CurrentPaintOpPreset) { } QVariant KisLodSizeThresholdResourceConverter::fromSource(const QVariant &value) { KisPaintOpPresetSP preset = value.value(); return preset ? preset->settings()->lodSizeThreshold() : QVariant(); } QVariant KisLodSizeThresholdResourceConverter::toSource(const QVariant &value, const QVariant &sourceValue) { KisPaintOpPresetSP preset = sourceValue.value(); if (!preset) return sourceValue; preset->settings()->setLodSizeThreshold(value.toDouble()); return QVariant::fromValue(preset); } /*********************************************************************/ /* KisLodSizeThresholdSupportedResourceConverter */ /*********************************************************************/ KisLodSizeThresholdSupportedResourceConverter::KisLodSizeThresholdSupportedResourceConverter() : KoDerivedResourceConverter(KisCanvasResourceProvider::LodSizeThresholdSupported, KisCanvasResourceProvider::CurrentPaintOpPreset) { } QVariant KisLodSizeThresholdSupportedResourceConverter::fromSource(const QVariant &value) { KisPaintOpPresetSP preset = value.value(); return preset ? preset->settings()->lodSizeThresholdSupported() : QVariant(); } QVariant KisLodSizeThresholdSupportedResourceConverter::toSource(const QVariant &value, const QVariant &sourceValue) { // this property of the preset is immutable Q_UNUSED(value); return sourceValue; } /*********************************************************************/ /* KisEraserModeResourceConverter */ /*********************************************************************/ KisEraserModeResourceConverter::KisEraserModeResourceConverter() : KoDerivedResourceConverter(KisCanvasResourceProvider::EraserMode, KisCanvasResourceProvider::CurrentPaintOpPreset) { } QVariant KisEraserModeResourceConverter::fromSource(const QVariant &value) { KisPaintOpPresetSP preset = value.value(); return preset ? preset->settings()->eraserMode() : QVariant(); } QVariant KisEraserModeResourceConverter::toSource(const QVariant &value, const QVariant &sourceValue) { KisPaintOpPresetSP preset = sourceValue.value(); if (!preset) return sourceValue; preset->settings()->setEraserMode(value.toBool()); return QVariant::fromValue(preset); } diff --git a/libs/ui/tool/strokes/kis_filter_stroke_strategy.cpp b/libs/ui/tool/strokes/kis_filter_stroke_strategy.cpp index cee7ed6c1d..9d054138e2 100644 --- a/libs/ui/tool/strokes/kis_filter_stroke_strategy.cpp +++ b/libs/ui/tool/strokes/kis_filter_stroke_strategy.cpp @@ -1,194 +1,197 @@ /* * Copyright (c) 2013 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_filter_stroke_strategy.h" #include #include #include #include struct KisFilterStrokeStrategy::Private { Private() : updatesFacade(0), cancelSilently(false), secondaryTransaction(0), levelOfDetail(0) { } Private(const Private &rhs) : filter(rhs.filter), filterConfig(rhs.filterConfig), node(rhs.node), updatesFacade(rhs.updatesFacade), cancelSilently(rhs.cancelSilently), filterDevice(), filterDeviceBounds(), secondaryTransaction(0), progressHelper(), levelOfDetail(0) { KIS_ASSERT_RECOVER_RETURN(!rhs.filterDevice); KIS_ASSERT_RECOVER_RETURN(rhs.filterDeviceBounds.isEmpty()); KIS_ASSERT_RECOVER_RETURN(!rhs.secondaryTransaction); KIS_ASSERT_RECOVER_RETURN(!rhs.progressHelper); KIS_ASSERT_RECOVER_RETURN(!rhs.levelOfDetail); } KisFilterSP filter; KisFilterConfigurationSP filterConfig; KisNodeSP node; KisUpdatesFacade *updatesFacade; bool cancelSilently; KisPaintDeviceSP filterDevice; QRect filterDeviceBounds; KisTransaction *secondaryTransaction; QScopedPointer progressHelper; int levelOfDetail; }; KisFilterStrokeStrategy::KisFilterStrokeStrategy(KisFilterSP filter, KisFilterConfigurationSP filterConfig, KisResourcesSnapshotSP resources) : KisPainterBasedStrokeStrategy(QLatin1String("FILTER_STROKE"), kundo2_i18n("Filter \"%1\"", filter->name()), resources, QVector(),false), m_d(new Private()) { m_d->filter = filter; m_d->filterConfig = filterConfig; m_d->node = resources->currentNode(); m_d->updatesFacade = resources->image().data(); m_d->cancelSilently = false; m_d->secondaryTransaction = 0; m_d->levelOfDetail = 0; setSupportsWrapAroundMode(true); enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE); } KisFilterStrokeStrategy::KisFilterStrokeStrategy(const KisFilterStrokeStrategy &rhs, int levelOfDetail) : KisPainterBasedStrokeStrategy(rhs, levelOfDetail), m_d(new Private(*rhs.m_d)) { // only non-started transaction are allowed KIS_ASSERT_RECOVER_NOOP(!m_d->secondaryTransaction); m_d->levelOfDetail = levelOfDetail; } KisFilterStrokeStrategy::~KisFilterStrokeStrategy() { delete m_d; } void KisFilterStrokeStrategy::initStrokeCallback() { KisPainterBasedStrokeStrategy::initStrokeCallback(); KisPaintDeviceSP dev = targetDevice(); m_d->filterDeviceBounds = dev->extent(); + if (m_d->filter->needsTransparentPixels(m_d->filterConfig.data(), dev->colorSpace())) { + m_d->filterDeviceBounds |= dev->defaultBounds()->bounds(); + } if (activeSelection() || (dev->colorSpace() != dev->compositionSourceColorSpace() && *dev->colorSpace() != *dev->compositionSourceColorSpace())) { m_d->filterDevice = dev->createCompositionSourceDevice(dev); m_d->secondaryTransaction = new KisTransaction(m_d->filterDevice); if (activeSelection()) { m_d->filterDeviceBounds &= activeSelection()->selectedRect(); } } else { m_d->filterDevice = dev; } m_d->progressHelper.reset(new KisProcessingVisitor::ProgressHelper(m_d->node)); } void KisFilterStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { Data *d = dynamic_cast(data); CancelSilentlyMarker *cancelJob = dynamic_cast(data); if (d) { const QRect rc = d->processRect; if (!m_d->filterDeviceBounds.intersects( m_d->filter->neededRect(rc, m_d->filterConfig.data(), m_d->levelOfDetail))) { return; } m_d->filter->processImpl(m_d->filterDevice, rc, m_d->filterConfig.data(), m_d->progressHelper->updater()); if (m_d->secondaryTransaction) { KisPainter::copyAreaOptimized(rc.topLeft(), m_d->filterDevice, targetDevice(), rc, activeSelection()); // Free memory m_d->filterDevice->clear(rc); } m_d->node->setDirty(rc); } else if (cancelJob) { m_d->cancelSilently = true; } else { qFatal("KisFilterStrokeStrategy: job type is not known"); } } void KisFilterStrokeStrategy::cancelStrokeCallback() { delete m_d->secondaryTransaction; m_d->filterDevice = 0; if (m_d->cancelSilently) { m_d->updatesFacade->disableDirtyRequests(); } KisPainterBasedStrokeStrategy::cancelStrokeCallback(); if (m_d->cancelSilently) { m_d->updatesFacade->enableDirtyRequests(); } } void KisFilterStrokeStrategy::finishStrokeCallback() { delete m_d->secondaryTransaction; m_d->filterDevice = 0; KisPainterBasedStrokeStrategy::finishStrokeCallback(); } KisStrokeStrategy* KisFilterStrokeStrategy::createLodClone(int levelOfDetail) { if (!m_d->filter->supportsLevelOfDetail(m_d->filterConfig.data(), levelOfDetail)) return 0; KisFilterStrokeStrategy *clone = new KisFilterStrokeStrategy(*this, levelOfDetail); return clone; } diff --git a/libs/ui/widgets/kis_gradient_chooser.cc b/libs/ui/widgets/kis_gradient_chooser.cc index c02a8e1abf..ebfa522d34 100644 --- a/libs/ui/widgets/kis_gradient_chooser.cc +++ b/libs/ui/widgets/kis_gradient_chooser.cc @@ -1,209 +1,210 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (C) 2011 Srikanth Tiyyagura * * 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 "widgets/kis_gradient_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisViewManager.h" #include "kis_global.h" #include "kis_autogradient.h" #include "kis_canvas_resource_provider.h" #include "kis_stopgradient_editor.h" KisCustomGradientDialog::KisCustomGradientDialog(KoAbstractGradient* gradient, QWidget *parent, const char *name) : KoDialog(parent, Qt::Dialog) { setButtons(Close); setDefaultButton(Close); setObjectName(name); setModal(false); KoStopGradient* stopGradient = dynamic_cast(gradient); if (stopGradient) { m_page = new KisStopGradientEditor(stopGradient, this, "autogradient", i18n("Custom Stop Gradient")); } else { KoSegmentGradient* segmentedGradient = dynamic_cast(gradient); if (segmentedGradient) { m_page = new KisAutogradientEditor(segmentedGradient, this, "autogradient", i18n("Custom Segmented Gradient")); } } setCaption(m_page->windowTitle()); setMainWidget(m_page); } KisGradientChooser::KisGradientChooser(QWidget *parent, const char *name) : QFrame(parent) { setObjectName(name); m_lbName = new QLabel(); KoResourceServer * rserver = KoResourceServerProvider::instance()->gradientServer(); QSharedPointer adapter (new KoResourceServerAdapter(rserver)); m_itemChooser = new KoResourceItemChooser(adapter, this); m_itemChooser->showTaggingBar(true); m_itemChooser->setFixedSize(250, 250); m_itemChooser->setColumnCount(1); + m_itemChooser->itemView()->keepAspectRatio(false); connect(m_itemChooser, SIGNAL(resourceSelected(KoResource*)), this, SLOT(update(KoResource*))); connect(m_itemChooser, SIGNAL(resourceSelected(KoResource*)), this, SIGNAL(resourceSelected(KoResource*))); QWidget* buttonWidget = new QWidget(this); QHBoxLayout* buttonLayout = new QHBoxLayout(buttonWidget); m_addGradient = new QToolButton(this); m_addGradient->setText(i18n("Add...")); m_addGradient->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); connect(m_addGradient, SIGNAL(clicked()), this, SLOT(addStopGradient())); buttonLayout->addWidget(m_addGradient); QMenu *menuAddGradient = new QMenu(m_addGradient); QAction* addStopGradient = new QAction(i18n("Stop gradient"), this); connect(addStopGradient, SIGNAL(triggered(bool)), this, SLOT(addStopGradient())); menuAddGradient->addAction(addStopGradient); QAction* addSegmentedGradient = new QAction(i18n("Segmented gradient"), this); connect(addSegmentedGradient, SIGNAL(triggered(bool)), this, SLOT(addSegmentedGradient())); menuAddGradient->addAction(addSegmentedGradient); m_addGradient->setMenu(menuAddGradient); m_addGradient->setPopupMode(QToolButton::MenuButtonPopup); m_editGradient = new QPushButton(); m_editGradient->setText(i18n("Edit...")); m_editGradient->setEnabled(false); connect(m_editGradient, SIGNAL(clicked()), this, SLOT(editGradient())); buttonLayout->addWidget(m_editGradient); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setObjectName("main layout"); mainLayout->setMargin(2); mainLayout->addWidget(m_lbName); mainLayout->addWidget(m_itemChooser, 10); mainLayout->addWidget(buttonWidget); slotUpdateIcons(); setLayout(mainLayout); } KisGradientChooser::~KisGradientChooser() { } KoResource *KisGradientChooser::currentResource() { return m_itemChooser->currentResource(); } void KisGradientChooser::setCurrentResource(KoResource *resource) { m_itemChooser->setCurrentResource(resource); } void KisGradientChooser::setCurrentItem(int row, int column) { m_itemChooser->setCurrentItem(row, column); if (currentResource()) update(currentResource()); } void KisGradientChooser::slotUpdateIcons() { if (m_addGradient && m_editGradient) { m_addGradient->setIcon(KisIconUtils::loadIcon("list-add")); m_editGradient->setIcon(KisIconUtils::loadIcon("configure")); } } void KisGradientChooser::update(KoResource * resource) { KoAbstractGradient *gradient = static_cast(resource); m_lbName->setText(gradient ? i18n(gradient->name().toUtf8().data()) : ""); m_editGradient->setEnabled(gradient && gradient->removable()); } void KisGradientChooser::addStopGradient() { KoStopGradient* gradient = new KoStopGradient(""); QList stops; stops << KoGradientStop(0.0, KoColor(QColor(250, 250, 0), KoColorSpaceRegistry::instance()->rgb8())) << KoGradientStop(1.0, KoColor(QColor(255, 0, 0, 255), KoColorSpaceRegistry::instance()->rgb8())); gradient->setType(QGradient::LinearGradient); gradient->setStops(stops); addGradient(gradient); } void KisGradientChooser::addSegmentedGradient() { KoSegmentGradient* gradient = new KoSegmentGradient(""); gradient->createSegment(INTERP_LINEAR, COLOR_INTERP_RGB, 0.0, 1.0, 0.5, Qt::black, Qt::white); gradient->setName(i18n("unnamed")); addGradient(gradient); } void KisGradientChooser::addGradient(KoAbstractGradient* gradient) { KoResourceServer * rserver = KoResourceServerProvider::instance()->gradientServer(); QString saveLocation = rserver->saveLocation(); KisCustomGradientDialog dialog(gradient, this, "KisCustomGradientDialog"); dialog.exec(); gradient->setFilename(saveLocation + gradient->name() + gradient->defaultFileExtension()); gradient->setValid(true); rserver->addResource(gradient); m_itemChooser->setCurrentResource(gradient); } void KisGradientChooser::editGradient() { KisCustomGradientDialog dialog(static_cast(currentResource()), this, "KisCustomGradientDialog"); dialog.exec(); } diff --git a/libs/ui/widgets/kis_workspace_chooser.cpp b/libs/ui/widgets/kis_workspace_chooser.cpp index d15f31bbbb..80a1676960 100644 --- a/libs/ui/widgets/kis_workspace_chooser.cpp +++ b/libs/ui/widgets/kis_workspace_chooser.cpp @@ -1,238 +1,239 @@ /* This file is part of the KDE project * Copyright (C) 2011 Sven Langkamp * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_workspace_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "kis_workspace_resource.h" #include "KisViewManager.h" #include #include #include #include #include #include class KisWorkspaceDelegate : public QAbstractItemDelegate { public: KisWorkspaceDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {} ~KisWorkspaceDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } }; void KisWorkspaceDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { if (!index.isValid()) return; KoResource* workspace = static_cast(index.internalPointer()); QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Active : QPalette::Disabled; QPalette::ColorRole cr = (option.state & QStyle::State_Selected) ? QPalette::HighlightedText : QPalette::Text; painter->setPen(option.palette.color(cg, cr)); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); } else { painter->fillRect(option.rect, option.palette.base()); } painter->drawText(option.rect.x() + 5, option.rect.y() + painter->fontMetrics().ascent() + 5, workspace->name()); } KisWorkspaceChooser::KisWorkspaceChooser(KisViewManager * view, QWidget* parent): QWidget(parent), m_view(view) { KoResourceServer * workspaceServer = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer workspaceAdapter(new KoResourceServerAdapter(workspaceServer)); KoResourceServer * windowLayoutServer = KisResourceServerProvider::instance()->windowLayoutServer(); QSharedPointer windowLayoutAdapter(new KoResourceServerAdapter(windowLayoutServer)); m_layout = new QGridLayout(this); m_workspaceWidgets = createChooserWidgets(workspaceAdapter, i18n("Workspaces")); m_windowLayoutWidgets = createChooserWidgets(windowLayoutAdapter, i18n("Window layouts")); connect(m_workspaceWidgets.itemChooser, SIGNAL(resourceSelected(KoResource*)), this, SLOT(workspaceSelected(KoResource*))); connect(m_workspaceWidgets.saveButton, SIGNAL(clicked(bool)), this, SLOT(slotSaveWorkspace())); connect(m_windowLayoutWidgets.itemChooser, SIGNAL(resourceSelected(KoResource*)), this, SLOT(windowLayoutSelected(KoResource*))); connect(m_windowLayoutWidgets.saveButton, SIGNAL(clicked(bool)), this, SLOT(slotSaveWindowLayout())); } KisWorkspaceChooser::ChooserWidgets KisWorkspaceChooser::createChooserWidgets(QSharedPointer adapter, const QString &title) { ChooserWidgets widgets; QLabel *titleLabel = new QLabel(this); QFont titleFont; titleFont.setBold(true); titleLabel->setFont(titleFont); titleLabel->setText(title); widgets.itemChooser = new KoResourceItemChooser(adapter, this); widgets.itemChooser->setItemDelegate(new KisWorkspaceDelegate(this)); widgets.itemChooser->setFixedSize(250, 250); widgets.itemChooser->setRowHeight(30); widgets.itemChooser->setColumnCount(1); widgets.itemChooser->showTaggingBar(false); + widgets.itemChooser->itemView()->keepAspectRatio(false); widgets.saveButton = new QPushButton(i18n("Save")); widgets.nameEdit = new QLineEdit(this); widgets.nameEdit->setPlaceholderText(i18n("Insert name")); widgets.nameEdit->setClearButtonEnabled(true); int firstRow = m_layout->rowCount(); m_layout->addWidget(titleLabel, firstRow, 0, 1, 2); m_layout->addWidget(widgets.itemChooser, firstRow + 1, 0, 1, 2); m_layout->addWidget(widgets.nameEdit, firstRow + 2, 0, 1, 1); m_layout->addWidget(widgets.saveButton, firstRow + 2, 1, 1, 1); return widgets; } KisWorkspaceChooser::~KisWorkspaceChooser() { } void KisWorkspaceChooser::slotSaveWorkspace() { if (!m_view->qtMainWindow()) { return; } KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(QString()); workspace->setDockerState(m_view->qtMainWindow()->saveState()); m_view->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); QString name = m_workspaceWidgets.nameEdit->text(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); } void KisWorkspaceChooser::workspaceSelected(KoResource *resource) { if (!m_view->qtMainWindow()) { return; } KisConfig cfg(false); cfg.writeEntry("CurrentWorkspace", resource->name()); KisWorkspaceResource* workspace = static_cast(resource); KisMainWindow *mainWindow = qobject_cast(m_view->qtMainWindow()); mainWindow->restoreWorkspace(workspace); } void KisWorkspaceChooser::slotSaveWindowLayout() { KisMainWindow *thisWindow = qobject_cast(m_view->qtMainWindow()); if (!thisWindow) return; KisNewWindowLayoutDialog dlg; dlg.setName(m_windowLayoutWidgets.nameEdit->text()); dlg.exec(); if (dlg.result() != QDialog::Accepted) return; QString name = dlg.name(); bool showImageInAllWindows = dlg.showImageInAllWindows(); bool primaryWorkspaceFollowsFocus = dlg.primaryWorkspaceFollowsFocus(); auto *layout = KisWindowLayoutResource::fromCurrentWindows(name, KisPart::instance()->mainWindows(), showImageInAllWindows, primaryWorkspaceFollowsFocus, thisWindow); layout->setValid(true); KisWindowLayoutManager::instance()->setShowImageInAllWindowsEnabled(showImageInAllWindows); KisWindowLayoutManager::instance()->setPrimaryWorkspaceFollowsFocus(primaryWorkspaceFollowsFocus, thisWindow->id()); KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); QString saveLocation = rserver->saveLocation(); bool newName = false; if (name.isEmpty()) { newName = true; name = i18n("Window Layout"); } QFileInfo fileInfo(saveLocation + name + layout->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + layout->defaultFileExtension()); i++; } layout->setFilename(fileInfo.filePath()); if (newName) { name = i18n("Window Layout %1", i); } layout->setName(name); rserver->addResource(layout); } void KisWorkspaceChooser::windowLayoutSelected(KoResource * resource) { auto *layout = static_cast(resource); layout->applyLayout(); } diff --git a/libs/widgets/KoTableView.cpp b/libs/widgets/KoTableView.cpp index e932586e89..7eac1c254d 100644 --- a/libs/widgets/KoTableView.cpp +++ b/libs/widgets/KoTableView.cpp @@ -1,95 +1,100 @@ /* * Copyright (C) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTableView.h" #include #include #include KoTableView::KoTableView(QWidget *parent) : QTableView(parent) { setSelectionMode(QAbstractItemView::SingleSelection); verticalHeader()->hide(); horizontalHeader()->hide(); verticalHeader()->setDefaultSectionSize(20); setContextMenuPolicy(Qt::DefaultContextMenu); setViewMode(FIXED_COLUMNS); + keepAspectRatio(); QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChange(QScroller::State))); } } void KoTableView::resizeEvent(QResizeEvent *event) { QTableView::resizeEvent(event); updateView(); emit sigSizeChanged(); } void KoTableView::setViewMode(KoTableView::ViewMode mode) { m_viewMode = mode; switch (m_viewMode) { case FIXED_COLUMNS: setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Horizontal scrollbar is never needed setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); break; case FIXED_ROWS: setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Vertical scrollbar is never needed break; default: setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } } +void KoTableView::keepAspectRatio(bool keepRatio) { + m_keepRatio = keepRatio; +} + void KoTableView::updateView() { int columnCount = model()->columnCount(QModelIndex()); int rowCount = model()->rowCount(QModelIndex()); int rowHeight, columnWidth; if (m_viewMode == FIXED_COLUMNS) { columnWidth = qFloor(viewport()->size().width() / static_cast(columnCount)); for (int i = 0; i < columnCount; ++i) { setColumnWidth(i, columnWidth); } // keep aspect ratio always square. - if (columnCount >= 1) { + if (m_keepRatio) { for (int i = 0; i < rowCount; ++i) { setRowHeight(i, columnWidth); } } } else if (m_viewMode == FIXED_ROWS) { if (rowCount == 0) return; // Don't divide by zero rowHeight = qFloor(viewport()->size().height() / static_cast(rowCount)); for (int i = 0; i < rowCount; ++i) { setRowHeight(i, rowHeight); } } } diff --git a/libs/widgets/KoTableView.h b/libs/widgets/KoTableView.h index f8afa41219..3cbaa4e52c 100644 --- a/libs/widgets/KoTableView.h +++ b/libs/widgets/KoTableView.h @@ -1,68 +1,71 @@ /* * Copyright (C) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOTABLEVIEW_H #define KOTABLEVIEW_H #include #include #include "kritawidgets_export.h" #include class QEvent; class QModelIndex; /** * @brief The KoTableView class provides a QTableView with fixed columns or rows */ class KRITAWIDGETS_EXPORT KoTableView: public QTableView { Q_OBJECT public: enum ViewMode { FIXED_COLUMNS, /// The number of columns is fixed FIXED_ROWS /// The number of rows is fixed }; explicit KoTableView(QWidget *parent = 0); ~KoTableView() override {} /** reimplemented * This will draw a number of rows based on the number of columns if m_viewMode is FIXED_COLUMNS * And it will draw a number of columns based on the number of rows if m_viewMode is FIXED_ROWS */ void resizeEvent(QResizeEvent *event) override; void setViewMode(ViewMode mode); + void keepAspectRatio(bool keepRatio = true); + void updateView(); public Q_SLOTS: void slotScrollerStateChange(QScroller::State state){ KisKineticScroller::updateCursor(this, state); } Q_SIGNALS: void sigSizeChanged(); private: ViewMode m_viewMode; + bool m_keepRatio; }; #endif // KOTABLEVIEW_H diff --git a/plugins/color/lcms2engine/LcmsColorSpace.h b/plugins/color/lcms2engine/LcmsColorSpace.h index 669ccbf44e..749a00ea31 100644 --- a/plugins/color/lcms2engine/LcmsColorSpace.h +++ b/plugins/color/lcms2engine/LcmsColorSpace.h @@ -1,493 +1,496 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005-2006 C. Boemann * Copyright (c) 2004,2006-2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOLCMSCOLORSPACE_H_ #define KOLCMSCOLORSPACE_H_ #include #include #include #include #include "kis_assert.h" class LcmsColorProfileContainer; class KoLcmsInfo { struct Private { cmsUInt32Number cmType; // The colorspace type as defined by littlecms cmsColorSpaceSignature colorSpaceSignature; // The colorspace signature as defined in icm/icc files }; public: KoLcmsInfo(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature) : d(new Private) { d->cmType = cmType; d->colorSpaceSignature = colorSpaceSignature; } virtual ~KoLcmsInfo() { delete d; } virtual quint32 colorSpaceType() const { return d->cmType; } virtual cmsColorSpaceSignature colorSpaceSignature() const { return d->colorSpaceSignature; } private: Private *const d; }; struct KoLcmsDefaultTransformations { cmsHTRANSFORM toRGB; cmsHTRANSFORM fromRGB; static cmsHPROFILE s_RGBProfile; static QMap< QString, QMap< LcmsColorProfileContainer *, KoLcmsDefaultTransformations * > > s_transformations; }; /** * This is the base class for all colorspaces that are based on the lcms library, for instance * RGB 8bits and 16bits, CMYK 8bits and 16bits, LAB... */ template class LcmsColorSpace : public KoColorSpaceAbstract<_CSTraits>, public KoLcmsInfo { struct KoLcmsColorTransformation : public KoColorTransformation { KoLcmsColorTransformation(const KoColorSpace *colorSpace) : KoColorTransformation() , m_colorSpace(colorSpace) { csProfile = 0; cmstransform = 0; cmsAlphaTransform = 0; profiles[0] = 0; profiles[1] = 0; profiles[2] = 0; } ~KoLcmsColorTransformation() override { if (cmstransform) { cmsDeleteTransform(cmstransform); } if (profiles[0] && profiles[0] != csProfile) { cmsCloseProfile(profiles[0]); } if (profiles[1] && profiles[1] != csProfile) { cmsCloseProfile(profiles[1]); } if (profiles[2] && profiles[2] != csProfile) { cmsCloseProfile(profiles[2]); } } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { cmsDoTransform(cmstransform, const_cast(src), dst, nPixels); qint32 numPixels = nPixels; qint32 pixelSize = m_colorSpace->pixelSize(); int index = 0; if (cmsAlphaTransform) { qreal *alpha = new qreal[nPixels]; qreal *dstalpha = new qreal[nPixels]; while (index < nPixels) { alpha[index] = m_colorSpace->opacityF(src); src += pixelSize; index++; } cmsDoTransform(cmsAlphaTransform, const_cast(alpha), static_cast(dstalpha), nPixels); for (int i = 0; i < numPixels; i++) { m_colorSpace->setOpacity(dst, dstalpha[i], 1); dst += pixelSize; } delete [] alpha; delete [] dstalpha; } else { while (numPixels > 0) { qreal alpha = m_colorSpace->opacityF(src); m_colorSpace->setOpacity(dst, alpha, 1); src += pixelSize; dst += pixelSize; numPixels--; } } } const KoColorSpace *m_colorSpace; cmsHPROFILE csProfile; cmsHPROFILE profiles[3]; cmsHTRANSFORM cmstransform; cmsHTRANSFORM cmsAlphaTransform; }; struct Private { mutable quint8 *qcolordata; // A small buffer for conversion from and to qcolor. KoLcmsDefaultTransformations *defaultTransformations; mutable cmsHPROFILE lastRGBProfile; // Last used profile to transform to/from RGB mutable cmsHTRANSFORM lastToRGB; // Last used transform to transform to RGB mutable cmsHTRANSFORM lastFromRGB; // Last used transform to transform from RGB LcmsColorProfileContainer *profile; KoColorProfile *colorProfile; QMutex mutex; }; protected: LcmsColorSpace(const QString &id, const QString &name, cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature, KoColorProfile *p) : KoColorSpaceAbstract<_CSTraits>(id, name) , KoLcmsInfo(cmType, colorSpaceSignature) , d(new Private()) { Q_ASSERT(p); // No profile means the lcms color space can't work Q_ASSERT(profileIsCompatible(p)); d->profile = asLcmsProfile(p); Q_ASSERT(d->profile); d->colorProfile = p; d->qcolordata = 0; d->lastRGBProfile = 0; d->lastToRGB = 0; d->lastFromRGB = 0; d->defaultTransformations = 0; } ~LcmsColorSpace() override { delete d->colorProfile; delete[] d->qcolordata; delete d->defaultTransformations; delete d; } void init() { // Default pixel buffer for QColor conversion d->qcolordata = new quint8[3]; Q_CHECK_PTR(d->qcolordata); KIS_ASSERT(d->profile); if (KoLcmsDefaultTransformations::s_RGBProfile == 0) { KoLcmsDefaultTransformations::s_RGBProfile = cmsCreate_sRGBProfile(); } d->defaultTransformations = KoLcmsDefaultTransformations::s_transformations[this->id()][ d->profile]; if (!d->defaultTransformations) { d->defaultTransformations = new KoLcmsDefaultTransformations; d->defaultTransformations->fromRGB = cmsCreateTransform(KoLcmsDefaultTransformations::s_RGBProfile, TYPE_BGR_8, d->profile->lcmsProfile(), this->colorSpaceType(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); KIS_SAFE_ASSERT_RECOVER_NOOP(d->defaultTransformations->fromRGB || !d->colorProfile->isSuitableForOutput()); d->defaultTransformations->toRGB = cmsCreateTransform(d->profile->lcmsProfile(), this->colorSpaceType(), KoLcmsDefaultTransformations::s_RGBProfile, TYPE_BGR_8, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); KIS_SAFE_ASSERT_RECOVER_NOOP(d->defaultTransformations->toRGB); KoLcmsDefaultTransformations::s_transformations[ this->id()][ d->profile ] = d->defaultTransformations; } } public: bool hasHighDynamicRange() const override { return false; } const KoColorProfile *profile() const override { return d->colorProfile; } bool profileIsCompatible(const KoColorProfile *profile) const override { const IccColorProfile *p = dynamic_cast(profile); return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature()); } void fromQColor(const QColor &color, quint8 *dst, const KoColorProfile *koprofile = 0) const override { QMutexLocker locker(&d->mutex); d->qcolordata[2] = color.red(); d->qcolordata[1] = color.green(); d->qcolordata[0] = color.blue(); LcmsColorProfileContainer *profile = asLcmsProfile(koprofile); if (profile == 0) { // Default sRGB KIS_ASSERT(d->defaultTransformations && d->defaultTransformations->fromRGB); cmsDoTransform(d->defaultTransformations->fromRGB, d->qcolordata, dst, 1); } else { if (d->lastFromRGB == 0 || (d->lastFromRGB != 0 && d->lastRGBProfile != profile->lcmsProfile())) { d->lastFromRGB = cmsCreateTransform(profile->lcmsProfile(), TYPE_BGR_8, d->profile->lcmsProfile(), this->colorSpaceType(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); d->lastRGBProfile = profile->lcmsProfile(); } KIS_ASSERT(d->lastFromRGB); cmsDoTransform(d->lastFromRGB, d->qcolordata, dst, 1); } this->setOpacity(dst, (quint8)(color.alpha()), 1); } void toQColor(const quint8 *src, QColor *c, const KoColorProfile *koprofile = 0) const override { QMutexLocker locker(&d->mutex); LcmsColorProfileContainer *profile = asLcmsProfile(koprofile); if (profile == 0) { // Default sRGB transform Q_ASSERT(d->defaultTransformations && d->defaultTransformations->toRGB); cmsDoTransform(d->defaultTransformations->toRGB, const_cast (src), d->qcolordata, 1); } else { if (d->lastToRGB == 0 || (d->lastToRGB != 0 && d->lastRGBProfile != profile->lcmsProfile())) { d->lastToRGB = cmsCreateTransform(d->profile->lcmsProfile(), this->colorSpaceType(), profile->lcmsProfile(), TYPE_BGR_8, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); d->lastRGBProfile = profile->lcmsProfile(); } cmsDoTransform(d->lastToRGB, const_cast (src), d->qcolordata, 1); } c->setRgb(d->qcolordata[2], d->qcolordata[1], d->qcolordata[0]); c->setAlpha(this->opacityU8(src)); } KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const override { if (!d->profile) { return 0; } cmsToneCurve *transferFunctions[3]; transferFunctions[0] = cmsBuildTabulatedToneCurve16(0, 256, transferValues); transferFunctions[1] = cmsBuildGamma(0, 1.0); transferFunctions[2] = cmsBuildGamma(0, 1.0); KoLcmsColorTransformation *adj = new KoLcmsColorTransformation(this); adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigLabData, transferFunctions); cmsSetDeviceClass(adj->profiles[1], cmsSigAbstractClass); adj->profiles[0] = d->profile->lcmsProfile(); adj->profiles[2] = d->profile->lcmsProfile(); adj->cmstransform = cmsCreateMultiprofileTransform(adj->profiles, 3, this->colorSpaceType(), this->colorSpaceType(), KoColorConversionTransformation::adjustmentRenderingIntent(), KoColorConversionTransformation::adjustmentConversionFlags()); adj->csProfile = d->profile->lcmsProfile(); return adj; } KoColorTransformation *createPerChannelAdjustment(const quint16 *const *transferValues) const override { if (!d->profile) { return 0; } cmsToneCurve **transferFunctions = new cmsToneCurve*[ this->colorChannelCount()]; for (uint ch = 0; ch < this->colorChannelCount(); ch++) { transferFunctions[ch] = transferValues[ch] ? cmsBuildTabulatedToneCurve16(0, 256, transferValues[ch]) : cmsBuildGamma(0, 1.0); } cmsToneCurve **alphaTransferFunctions = new cmsToneCurve*[1]; alphaTransferFunctions[0] = transferValues[this->colorChannelCount()] ? cmsBuildTabulatedToneCurve16(0, 256, transferValues[this->colorChannelCount()]) : cmsBuildGamma(0, 1.0); KoLcmsColorTransformation *adj = new KoLcmsColorTransformation(this); adj->profiles[0] = cmsCreateLinearizationDeviceLink(this->colorSpaceSignature(), transferFunctions); adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigGrayData, alphaTransferFunctions); adj->profiles[2] = 0; adj->csProfile = d->profile->lcmsProfile(); adj->cmstransform = cmsCreateTransform(adj->profiles[0], this->colorSpaceType(), 0, this->colorSpaceType(), KoColorConversionTransformation::adjustmentRenderingIntent(), KoColorConversionTransformation::adjustmentConversionFlags()); adj->cmsAlphaTransform = cmsCreateTransform(adj->profiles[1], TYPE_GRAY_DBL, 0, TYPE_GRAY_DBL, KoColorConversionTransformation::adjustmentRenderingIntent(), KoColorConversionTransformation::adjustmentConversionFlags()); delete [] transferFunctions; delete [] alphaTransferFunctions; return adj; } quint8 difference(const quint8 *src1, const quint8 *src2) const override { quint8 lab1[8], lab2[8]; cmsCIELab labF1, labF2; if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) { return (this->opacityU8(src1) == this->opacityU8(src2) ? 0 : 255); } Q_ASSERT(this->toLabA16Converter()); this->toLabA16Converter()->transform(src1, lab1, 1); this->toLabA16Converter()->transform(src2, lab2, 1); cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1); cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2); qreal diff = cmsDeltaE(&labF1, &labF2); if (diff > 255.0) { return 255; } else { return quint8(diff); } } quint8 differenceA(const quint8 *src1, const quint8 *src2) const override { quint8 lab1[8]; quint8 lab2[8]; cmsCIELab labF1; cmsCIELab labF2; if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) { - return (this->opacityU8(src1) == this->opacityU8(src2) ? 0 : 255); + + + const qreal alphaScale = 100.0 / 255.0; + return qRound(alphaScale * qAbs(this->opacityU8(src1) - this->opacityU8(src2))); } Q_ASSERT(this->toLabA16Converter()); this->toLabA16Converter()->transform(src1, lab1, 1); this->toLabA16Converter()->transform(src2, lab2, 1); cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1); cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2); cmsFloat64Number dL; cmsFloat64Number da; cmsFloat64Number db; cmsFloat64Number dAlpha; dL = fabs((qreal)(labF1.L - labF2.L)); da = fabs((qreal)(labF1.a - labF2.a)); db = fabs((qreal)(labF1.b - labF2.b)); static const int LabAAlphaPos = 3; static const cmsFloat64Number alphaScale = 100.0 / KoColorSpaceMathsTraits::max; quint16 alpha1 = reinterpret_cast(lab1)[LabAAlphaPos]; quint16 alpha2 = reinterpret_cast(lab2)[LabAAlphaPos]; dAlpha = fabs((qreal)(alpha1 - alpha2)) * alphaScale; qreal diff = pow(dL * dL + da * da + db * db + dAlpha * dAlpha, 0.5); if (diff > 255.0) { return 255; } else { return quint8(diff); } } private: inline LcmsColorProfileContainer *lcmsProfile() const { return d->profile; } inline static LcmsColorProfileContainer *asLcmsProfile(const KoColorProfile *p) { if (!p) { return 0; } const IccColorProfile *iccp = dynamic_cast(p); if (!iccp) { return 0; } Q_ASSERT(iccp->asLcms()); return iccp->asLcms(); } Private *const d; }; /** * Base class for all LCMS based ColorSpace factories. */ class LcmsColorSpaceFactory : public KoColorSpaceFactory, private KoLcmsInfo { public: LcmsColorSpaceFactory(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature) : KoLcmsInfo(cmType, colorSpaceSignature) { } bool profileIsCompatible(const KoColorProfile *profile) const override { const IccColorProfile *p = dynamic_cast(profile); return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature()); } QString colorSpaceEngine() const override { return "icc"; } bool isHdr() const override { return false; } int crossingCost() const override { return 1; } QList colorConversionLinks() const override; KoColorProfile *createColorProfile(const QByteArray &rawData) const override; }; #endif diff --git a/plugins/python/batch_exporter/kritapykrita_batch_exporter.desktop b/plugins/python/batch_exporter/kritapykrita_batch_exporter.desktop index 5b5e8e1e58..387d74ce6a 100644 --- a/plugins/python/batch_exporter/kritapykrita_batch_exporter.desktop +++ b/plugins/python/batch_exporter/kritapykrita_batch_exporter.desktop @@ -1,30 +1,32 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=batch_exporter X-Krita-Manual=Manual.html X-Python-2-Compatible=false Name=Batch Exporter Name[ca]=Exportador per lots Name[en_GB]=Batch Exporter Name[es]=Exportador por lotes Name[et]=Hulgieksport Name[nl]=Exportprogramma voor bulk Name[nn]=Fleirbileteksport Name[pt]=Exportação em Lote Name[pt_BR]=Exportador em lote Name[sv]=Bakgrundsexport Name[uk]=Пакетне експортування Name[x-test]=xxBatch Exporterxx +Name[zh_CN]=批量导出工具 Comment=Smart export tool that uses layer names to scale and (re-)export art assets in batches fast Comment[ca]=Eina d'exportació intel·ligent que usa els noms de les capes per escalar i (re)exportar elements d'art en lots ràpidament Comment[en_GB]=Smart export tool that uses layer names to scale and (re-)export art assets in batches fast Comment[es]=Herramienta de exportación inteligente que usa nombres de capas para escalar y (volver a) exportar recursos artísticos por lotes de un modo rápido Comment[et]=Nutikas eksporditööriist, mid kasutab kihtide nimesid kunstiresursside kiireks hulgiskaleerimiseks ja -(taas)eksportimiseks Comment[nl]=Slim hulpmiddel voor exporteren die lagennamen gebruikt om snel kunstbezittingen te schalen en (opnieuw) te exporteren in bulk Comment[nn]=Smart eksportverktøy som brukar lagnamn til å skalera og (re)eksportera til fleire bilete Comment[pt]=Uma ferramenta inteligente de exportação em lote que usa os nomes das camadas para definir a escala e (re-)exportar os itens gráficos em lote de forma rápida Comment[pt_BR]=Ferramenta de exportação inteligente que usa nomes de camada para dimensionar e (re)exportar ativos de arte em lotes rapidamente Comment[sv]=Smart exportverktyg som använder lagernamn för att snabbt skala och exportera om grafiktillgångar i bakgrunden Comment[uk]=Інструмент пакетного експортування, який використовує назви шарів для масштабування і швидкого (повторного) пакетного експортування художніх елементів Comment[x-test]=xxSmart export tool that uses layer names to scale and (re-)export art assets in batches fastxx +Comment[zh_CN]=智能化的导出工具,能够根据图层名称批量缩放和导出图像。 diff --git a/plugins/python/channels2layers/kritapykrita_channels2layers.desktop b/plugins/python/channels2layers/kritapykrita_channels2layers.desktop index 6ce6331805..cc763a20cc 100644 --- a/plugins/python/channels2layers/kritapykrita_channels2layers.desktop +++ b/plugins/python/channels2layers/kritapykrita_channels2layers.desktop @@ -1,30 +1,32 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=channels2layers X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name=Channels to layers Name[ca]=Canals a capes Name[en_GB]=Channels to layers Name[es]=Canales a capas Name[et]=Kanalid kihtideks Name[nl]=Kanalen naar lagen Name[nn]=Kanalar til lag Name[pt]=Canais para camadas Name[pt_BR]=Canais para camadas Name[sv]=Kanaler till lager Name[uk]=Канали у шари Name[x-test]=xxChannels to layersxx +Name[zh_CN]=转换通道为图层 Comment=Extract channels as color layers Comment[ca]=Extreu canals com a capes de color Comment[en_GB]=Extract channels as colour layers Comment[es]=Extraer canales como capas de color Comment[et]=Kanalite ekstraktimine värvikihtidena Comment[nl]=Kanalen als kleurlagen extraheren Comment[nn]=Hent ut kanalar som fargelag Comment[pt]=Extrair os canais como camadas de cores Comment[pt_BR]=Extraia canais como camadas de cores Comment[sv]=Extrahera kanaler som färglager Comment[uk]=Видобування каналів як колірних шарів Comment[x-test]=xxExtract channels as color layersxx +Comment[zh_CN]=将色彩通道转换为颜色图层。 diff --git a/plugins/tools/tool_smart_patch/kis_inpaint.cpp b/plugins/tools/tool_smart_patch/kis_inpaint.cpp index 08b225f192..ee6e4d3b5a 100644 --- a/plugins/tools/tool_smart_patch/kis_inpaint.cpp +++ b/plugins/tools/tool_smart_patch/kis_inpaint.cpp @@ -1,994 +1,1003 @@ /* * Copyright (c) 2017 Eugene Ingerman * * 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. */ /** * Inpaint using the PatchMatch Algorithm * * | PatchMatch : A Randomized Correspondence Algorithm for Structural Image Editing * | by Connelly Barnes and Eli Shechtman and Adam Finkelstein and Dan B Goldman * | ACM Transactions on Graphics (Proc. SIGGRAPH), vol.28, aug-2009 * * Original author Xavier Philippeau * Code adopted from: David Chatting https://github.com/davidchatting/PatchMatch */ #include #include #include #include #include "kis_paint_device.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_debug.h" #include "kis_paint_device_debug_utils.h" //#include "kis_random_accessor_ng.h" #include #include #include #include "KoColor.h" #include "KoColorSpace.h" #include "KoChannelInfo.h" #include "KoMixColorsOp.h" #include "KoColorModelStandardIds.h" #include "KoColorSpaceRegistry.h" #include "KoColorSpaceTraits.h" const int MAX_DIST = 65535; const quint8 MASK_SET = 255; const quint8 MASK_CLEAR = 0; class MaskedImage; //forward decl for the forward decl below template float distance_impl(const MaskedImage& my, int x, int y, const MaskedImage& other, int xo, int yo); class ImageView { protected: quint8* m_data; int m_imageWidth; int m_imageHeight; int m_pixelSize; public: void Init(quint8* _data, int _imageWidth, int _imageHeight, int _pixelSize) { m_data = _data; m_imageWidth = _imageWidth; m_imageHeight = _imageHeight; m_pixelSize = _pixelSize; } ImageView() : m_data(nullptr) { m_imageHeight = m_imageWidth = m_pixelSize = 0; } ImageView(quint8* _data, int _imageWidth, int _imageHeight, int _pixelSize) { Init(_data, _imageWidth, _imageHeight, _pixelSize); } quint8* operator()(int x, int y) const { Q_ASSERT(m_data); Q_ASSERT((x >= 0) && (x < m_imageWidth) && (y >= 0) && (y < m_imageHeight)); return (m_data + x * m_pixelSize + y * m_imageWidth * m_pixelSize); } ImageView& operator=(const ImageView& other) { if (this != &other) { if (other.num_bytes() != num_bytes()) { delete[] m_data; m_data = nullptr; //to preserve invariance if next line throws exception m_data = new quint8[other.num_bytes()]; } std::copy(other.data(), other.data() + other.num_bytes(), m_data); m_imageHeight = other.m_imageHeight; m_imageWidth = other.m_imageWidth; m_pixelSize = other.m_pixelSize; } return *this; } //move assignment operator ImageView& operator=(ImageView&& other) noexcept { if (this != &other) { delete[] m_data; m_data = nullptr; Init(other.data(), other.m_imageWidth, other.m_imageHeight, other.m_pixelSize); other.m_data = nullptr; } return *this; } virtual ~ImageView() {} //this class doesn't own m_data, so it ain't going to delete it either. quint8* data(void) const { return m_data; } inline int num_elements(void) const { return m_imageHeight * m_imageWidth; } inline int num_bytes(void) const { return m_imageHeight * m_imageWidth * m_pixelSize; } inline int pixel_size(void) const { return m_pixelSize; } void saveToDevice(KisPaintDeviceSP outDev, QRect rect) { Q_ASSERT(outDev->colorSpace()->pixelSize() == (quint32) m_pixelSize); outDev->writeBytes(m_data, rect); } void DebugDump(const QString& fnamePrefix) { QRect imSize(QPoint(0, 0), QSize(m_imageWidth, m_imageHeight)); const KoColorSpace* cs = (m_pixelSize == 1) ? KoColorSpaceRegistry::instance()->alpha8() : (m_pixelSize == 3) ? KoColorSpaceRegistry::instance()->colorSpace("RGB", "U8", "") : KoColorSpaceRegistry::instance()->colorSpace("RGBA", "U8", ""); KisPaintDeviceSP dbout = new KisPaintDevice(cs); saveToDevice(dbout, imSize); KIS_DUMP_DEVICE_2(dbout, imSize, fnamePrefix, "./"); } }; class ImageData : public ImageView { public: ImageData() : ImageView() {} void Init(int _imageWidth, int _imageHeight, int _pixelSize) { m_data = new quint8[ _imageWidth * _imageHeight * _pixelSize ]; ImageView::Init(m_data, _imageWidth, _imageHeight, _pixelSize); } ImageData(int _imageWidth, int _imageHeight, int _pixelSize) : ImageView() { Init(_imageWidth, _imageHeight, _pixelSize); } void Init(KisPaintDeviceSP imageDev, const QRect& imageSize) { const KoColorSpace* cs = imageDev->colorSpace(); m_pixelSize = cs->pixelSize(); m_data = new quint8[ imageSize.width()*imageSize.height()*cs->pixelSize() ]; imageDev->readBytes(m_data, imageSize.x(), imageSize.y(), imageSize.width(), imageSize.height()); ImageView::Init(m_data, imageSize.width(), imageSize.height(), m_pixelSize); } ImageData(KisPaintDeviceSP imageDev, const QRect& imageSize) : ImageView() { Init(imageDev, imageSize); } ~ImageData() override { delete[] m_data; //ImageData owns m_data, so it has to delete it } }; class MaskedImage : public KisShared { private: template friend float distance_impl(const MaskedImage& my, int x, int y, const MaskedImage& other, int xo, int yo); QRect imageSize; int nChannels; const KoColorSpace* cs; const KoColorSpace* csMask; ImageData maskData; ImageData imageData; void cacheImage(KisPaintDeviceSP imageDev, QRect rect) { cs = imageDev->colorSpace(); nChannels = cs->channelCount(); imageData.Init(imageDev, rect); imageSize = rect; } void cacheMask(KisPaintDeviceSP maskDev, QRect rect) { Q_ASSERT(maskDev->colorSpace()->pixelSize() == 1); csMask = maskDev->colorSpace(); maskData.Init(maskDev, rect); //hard threshold for the initial mask //may be optional. needs testing std::for_each(maskData.data(), maskData.data() + maskData.num_bytes(), [](quint8 & v) { v = (v > MASK_CLEAR) ? MASK_SET : MASK_CLEAR; }); } MaskedImage() {} public: std::function< float(const MaskedImage&, int, int, const MaskedImage& , int , int ) > distance; - void toPaintDevice(KisPaintDeviceSP imageDev, QRect rect) + void toPaintDevice(KisPaintDeviceSP imageDev, QRect rect, KisSelectionSP selection) { - imageData.saveToDevice(imageDev, rect); + if (!selection) { + imageData.saveToDevice(imageDev, rect); + } else { + KisPaintDeviceSP dev = new KisPaintDevice(imageDev->colorSpace()); + dev->setDefaultBounds(imageDev->defaultBounds()); + + imageData.saveToDevice(dev, rect); + + KisPainter::copyAreaOptimized(rect.topLeft(), dev, imageDev, rect, selection); + } } void DebugDump(const QString& name) { imageData.DebugDump(name + "_img"); maskData.DebugDump(name + "_mask"); } void clearMask(void) { std::fill(maskData.data(), maskData.data() + maskData.num_bytes(), MASK_CLEAR); } void initialize(KisPaintDeviceSP _imageDev, KisPaintDeviceSP _maskDev, QRect _maskRect) { cacheImage(_imageDev, _maskRect); cacheMask(_maskDev, _maskRect); //distance function is the only that needs to know the type //For performance reasons we can't use functions provided by color space KoID colorDepthId = _imageDev->colorSpace()->colorDepthId(); //Use RGB traits to assign actual pixel data types. distance = &distance_impl; if( colorDepthId == Integer16BitsColorDepthID ) distance = &distance_impl; #ifdef HAVE_OPENEXR if( colorDepthId == Float16BitsColorDepthID ) distance = &distance_impl; #endif if( colorDepthId == Float32BitsColorDepthID ) distance = &distance_impl; if( colorDepthId == Float64BitsColorDepthID ) distance = &distance_impl; } MaskedImage(KisPaintDeviceSP _imageDev, KisPaintDeviceSP _maskDev, QRect _maskRect) { initialize(_imageDev, _maskDev, _maskRect); } void downsample2x(void) { int H = imageSize.height(); int W = imageSize.width(); int newW = W / 2, newH = H / 2; KisPaintDeviceSP imageDev = new KisPaintDevice(cs); KisPaintDeviceSP maskDev = new KisPaintDevice(csMask); imageDev->writeBytes(imageData.data(), 0, 0, W, H); maskDev->writeBytes(maskData.data(), 0, 0, W, H); ImageData newImage(newW, newH, cs->pixelSize()); ImageData newMask(newW, newH, 1); KoDummyUpdater updater; KisTransformWorker worker(imageDev, 1. / 2., 1. / 2., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); KisTransformWorker workerMask(maskDev, 1. / 2., 1. / 2., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); workerMask.run(); imageDev->readBytes(newImage.data(), 0, 0, newW, newH); maskDev->readBytes(newMask.data(), 0, 0, newW, newH); imageData = std::move(newImage); maskData = std::move(newMask); for (int i = 0; i < imageData.num_elements(); ++i) { quint8* maskPix = maskData.data() + i * maskData.pixel_size(); if (*maskPix == MASK_SET) { for (int k = 0; k < imageData.pixel_size(); k++) *(imageData.data() + i * imageData.pixel_size() + k) = 0; } else { *maskPix = MASK_CLEAR; } } imageSize = QRect(0, 0, newW, newH); } void upscale(int newW, int newH) { int H = imageSize.height(); int W = imageSize.width(); ImageData newImage(newW, newH, cs->pixelSize()); ImageData newMask(newW, newH, 1); QVector colors(nChannels, 0.f); QVector v(nChannels, 0.f); for (int y = 0; y < newH; ++y) { for (int x = 0; x < newW; ++x) { // original pixel int xs = (x * W) / newW; int ys = (y * H) / newH; // copy to new image if (!isMasked(xs, ys)) { std::copy(imageData(xs, ys), imageData(xs, ys) + imageData.pixel_size(), newImage(x, y)); *newMask(x, y) = MASK_CLEAR; } else { std::fill(newImage(x, y), newImage(x, y) + newImage.pixel_size(), 0); *newMask(x, y) = MASK_SET; } } } imageData = std::move(newImage); maskData = std::move(newMask); imageSize = QRect(0, 0, newW, newH); } QRect size() { return imageSize; } KisSharedPtr copy(void) { KisSharedPtr clone = new MaskedImage(); clone->imageSize = this->imageSize; clone->nChannels = this->nChannels; clone->maskData = this->maskData; clone->imageData = this->imageData; clone->cs = this->cs; clone->csMask = this->csMask; clone->distance = this->distance; return clone; } int countMasked(void) { int count = std::count_if(maskData.data(), maskData.data() + maskData.num_elements(), [](quint8 v) { return v > MASK_CLEAR; }); return count; } inline bool isMasked(int x, int y) { return (*maskData(x, y) > MASK_CLEAR); } //returns true if the patch contains a masked pixel bool containsMasked(int x, int y, int S) { for (int dy = -S; dy <= S; ++dy) { int ys = y + dy; if (ys < 0 || ys >= imageSize.height()) continue; for (int dx = -S; dx <= S; ++dx) { int xs = x + dx; if (xs < 0 || xs >= imageSize.width()) continue; if (isMasked(xs, ys)) return true; } } return false; } inline quint8 getImagePixelU8(int x, int y, int chan) const { return cs->scaleToU8(imageData(x, y), chan); } inline QVector getImagePixels(int x, int y) const { QVector v(cs->channelCount()); cs->normalisedChannelsValue(imageData(x, y), v); return v; } inline quint8* getImagePixel(int x, int y) { return imageData(x, y); } inline void setImagePixels(int x, int y, QVector& value) { cs->fromNormalisedChannelsValue(imageData(x, y), value); } inline void mixColors(std::vector< quint8* > pixels, std::vector< float > w, float wsum, quint8* dst) { const KoMixColorsOp* mixOp = cs->mixColorsOp(); size_t n = w.size(); assert(pixels.size() == n); std::vector< qint16 > weights; weights.clear(); float dif = 0; float scale = 255 / (wsum + 0.001); for (auto& v : w) { //compensated summation to increase accuracy float v1 = v * scale + dif; float v2 = std::round(v1); dif = v1 - v2; weights.push_back(v2); } mixOp->mixColors(pixels.data(), weights.data(), n, dst); } inline void setMask(int x, int y, quint8 v) { *(maskData(x, y)) = v; } inline int channelCount(void) const { return cs->channelCount(); } }; //Generic version of the distance function. produces distance between colors in the range [0, MAX_DIST]. This //is a fast distance computation. More accurate, but very slow implementation is to use color space operations. template float distance_impl(const MaskedImage& my, int x, int y, const MaskedImage& other, int xo, int yo) { float dsq = 0; quint32 nchannels = my.channelCount(); quint8* v1 = my.imageData(x, y); quint8* v2 = other.imageData(xo, yo); for (quint32 chan = 0; chan < nchannels; chan++) { //It's very important not to lose precision in the next line float v = ((float)(*((T*)v1 + chan)) - (float)(*((T*)v2 + chan))); dsq += v * v; } return dsq / ( (float)KoColorSpaceMathsTraits::unitValue * (float)KoColorSpaceMathsTraits::unitValue / MAX_DIST ); } typedef KisSharedPtr MaskedImageSP; struct NNPixel { int x; int y; int distance; }; typedef boost::multi_array NNArray_type; struct Vote_elem { QVector channel_values; float w; }; typedef boost::multi_array Vote_type; class NearestNeighborField : public KisShared { private: template< typename T> T randomInt(T range) { return rand() % range; } //compute initial value of the distance term void initialize(void) { for (int y = 0; y < imSize.height(); y++) { for (int x = 0; x < imSize.width(); x++) { field[x][y].distance = distance(x, y, field[x][y].x, field[x][y].y); //if the distance is "infinity", try to find a better link int iter = 0; const int maxretry = 20; while (field[x][y].distance == MAX_DIST && iter < maxretry) { field[x][y].x = randomInt(imSize.width() + 1); field[x][y].y = randomInt(imSize.height() + 1); field[x][y].distance = distance(x, y, field[x][y].x, field[x][y].y); iter++; } } } } void init_similarity_curve(void) { float s_zero = 0.999; float t_halfmax = 0.10; float x = (s_zero - 0.5) * 2; float invtanh = 0.5 * std::log((1. + x) / (1. - x)); float coef = invtanh / t_halfmax; similarity.resize(MAX_DIST + 1); for (int i = 0; i < (int)similarity.size(); i++) { float t = (float)i / similarity.size(); similarity[i] = 0.5 - 0.5 * std::tanh(coef * (t - t_halfmax)); } } private: int patchSize; //patch size public: MaskedImageSP input; MaskedImageSP output; QRect imSize; NNArray_type field; std::vector similarity; quint32 nColors; QList channels; public: NearestNeighborField(const MaskedImageSP _input, MaskedImageSP _output, int _patchsize) : patchSize(_patchsize), input(_input), output(_output) { imSize = input->size(); field.resize(boost::extents[imSize.width()][imSize.height()]); init_similarity_curve(); nColors = input->channelCount(); //only color count, doesn't include alpha channels } void randomize(void) { for (int y = 0; y < imSize.height(); y++) { for (int x = 0; x < imSize.width(); x++) { field[x][y].x = randomInt(imSize.width() + 1); field[x][y].y = randomInt(imSize.height() + 1); field[x][y].distance = MAX_DIST; } } initialize(); } //initialize field from an existing (possibly smaller) nearest neighbor field void initialize(const NearestNeighborField& nnf) { float xscale = qreal(imSize.width()) / nnf.imSize.width(); float yscale = qreal(imSize.height()) / nnf.imSize.height(); for (int y = 0; y < imSize.height(); y++) { for (int x = 0; x < imSize.width(); x++) { int xlow = std::min((int)(x / xscale), nnf.imSize.width() - 1); int ylow = std::min((int)(y / yscale), nnf.imSize.height() - 1); field[x][y].x = nnf.field[xlow][ylow].x * xscale; field[x][y].y = nnf.field[xlow][ylow].y * yscale; field[x][y].distance = MAX_DIST; } } initialize(); } //multi-pass NN-field minimization (see "PatchMatch" paper referenced above - page 4) void minimize(int pass) { int min_x = 0; int min_y = 0; int max_x = imSize.width() - 1; int max_y = imSize.height() - 1; for (int i = 0; i < pass; i++) { //scanline order for (int y = min_y; y < max_y; y++) for (int x = min_x; x <= max_x; x++) if (field[x][y].distance > 0) minimizeLink(x, y, 1); //reverse scanline order for (int y = max_y; y >= min_y; y--) for (int x = max_x; x >= min_x; x--) if (field[x][y].distance > 0) minimizeLink(x, y, -1); } } void minimizeLink(int x, int y, int dir) { int xp, yp, dp; //Propagation Left/Right if (x - dir > 0 && x - dir < imSize.width()) { xp = field[x - dir][y].x + dir; yp = field[x - dir][y].y; dp = distance(x, y, xp, yp); if (dp < field[x][y].distance) { field[x][y].x = xp; field[x][y].y = yp; field[x][y].distance = dp; } } //Propagation Up/Down if (y - dir > 0 && y - dir < imSize.height()) { xp = field[x][y - dir].x; yp = field[x][y - dir].y + dir; dp = distance(x, y, xp, yp); if (dp < field[x][y].distance) { field[x][y].x = xp; field[x][y].y = yp; field[x][y].distance = dp; } } //Random search int wi = std::max(output->size().width(), output->size().height()); int xpi = field[x][y].x; int ypi = field[x][y].y; while (wi > 0) { xp = xpi + randomInt(2 * wi) - wi; yp = ypi + randomInt(2 * wi) - wi; xp = std::max(0, std::min(output->size().width() - 1, xp)); yp = std::max(0, std::min(output->size().height() - 1, yp)); dp = distance(x, y, xp, yp); if (dp < field[x][y].distance) { field[x][y].x = xp; field[x][y].y = yp; field[x][y].distance = dp; } wi /= 2; } } //compute distance between two patches int distance(int x, int y, int xp, int yp) { float distance = 0; float wsum = 0; float ssdmax = nColors * 255 * 255; //for each pixel in the source patch for (int dy = -patchSize; dy <= patchSize; dy++) { for (int dx = -patchSize; dx <= patchSize; dx++) { wsum += ssdmax; int xks = x + dx; int yks = y + dy; if (xks < 0 || xks >= input->size().width()) { distance += ssdmax; continue; } if (yks < 0 || yks >= input->size().height()) { distance += ssdmax; continue; } //cannot use masked pixels as a valid source of information if (input->isMasked(xks, yks)) { distance += ssdmax; continue; } //corresponding pixel in target patch int xkt = xp + dx; int ykt = yp + dy; if (xkt < 0 || xkt >= output->size().width()) { distance += ssdmax; continue; } if (ykt < 0 || ykt >= output->size().height()) { distance += ssdmax; continue; } //cannot use masked pixels as a valid source of information if (output->isMasked(xkt, ykt)) { distance += ssdmax; continue; } //SSD distance between pixels float ssd = input->distance(*input, xks, yks, *output, xkt, ykt); distance += ssd; } } return (int)(MAX_DIST * (distance / wsum)); } static MaskedImageSP ExpectationMaximization(KisSharedPtr TargetToSource, int level, int radius, QList& pyramid); static void ExpectationStep(KisSharedPtr nnf, MaskedImageSP source, MaskedImageSP target, bool upscale); void EM_Step(MaskedImageSP source, MaskedImageSP target, int R, bool upscaled); }; typedef KisSharedPtr NearestNeighborFieldSP; class Inpaint { private: KisPaintDeviceSP devCache; MaskedImageSP initial; NearestNeighborFieldSP nnf_TargetToSource; NearestNeighborFieldSP nnf_SourceToTarget; int radius; QList pyramid; public: Inpaint(KisPaintDeviceSP dev, KisPaintDeviceSP devMask, int _radius, QRect maskRect) : devCache(dev) , initial(new MaskedImage(dev, devMask, maskRect)) , radius(_radius) { } MaskedImageSP patch(void); MaskedImageSP patch_simple(void); }; MaskedImageSP Inpaint::patch() { MaskedImageSP source = initial->copy(); pyramid.append(initial); QRect size = source->size(); //qDebug() << "countMasked: " << source->countMasked() << "\n"; while ((size.width() > radius) && (size.height() > radius) && source->countMasked() > 0) { source->downsample2x(); //source->DebugDump("Pyramid"); //qDebug() << "countMasked1: " << source->countMasked() << "\n"; pyramid.append(source->copy()); size = source->size(); } int maxlevel = pyramid.size(); //qDebug() << "MaxLevel: " << maxlevel << "\n"; // The initial target is the same as the smallest source. // We consider that this target contains no masked pixels MaskedImageSP target = source->copy(); target->clearMask(); //recursively building nearest neighbor field for (int level = maxlevel - 1; level > 0; level--) { source = pyramid.at(level); if (level == maxlevel - 1) { //random initial guess nnf_TargetToSource = new NearestNeighborField(target, source, radius); nnf_TargetToSource->randomize(); } else { // then, we use the rebuilt (upscaled) target // and reuse the previous NNF as initial guess NearestNeighborFieldSP new_nnf_rev = new NearestNeighborField(target, source, radius); new_nnf_rev->initialize(*nnf_TargetToSource); nnf_TargetToSource = new_nnf_rev; } //Build an upscaled target by EM-like algorithm (see "PatchMatch" paper referenced above - page 6) target = NearestNeighborField::ExpectationMaximization(nnf_TargetToSource, level, radius, pyramid); //target->DebugDump( "target" ); } return target; } //EM-Like algorithm (see "PatchMatch" - page 6) //Returns a float sized target image MaskedImageSP NearestNeighborField::ExpectationMaximization(NearestNeighborFieldSP nnf_TargetToSource, int level, int radius, QList& pyramid) { int iterEM = std::min(2 * level, 4); int iterNNF = std::min(5, 1 + level); MaskedImageSP source = nnf_TargetToSource->output; MaskedImageSP target = nnf_TargetToSource->input; MaskedImageSP newtarget = nullptr; //EM loop for (int emloop = 1; emloop <= iterEM; emloop++) { //set the new target as current target if (!newtarget.isNull()) { nnf_TargetToSource->input = newtarget; target = newtarget; newtarget = nullptr; } for (int x = 0; x < target->size().width(); ++x) { for (int y = 0; y < target->size().height(); ++y) { if (!source->containsMasked(x, y, radius)) { nnf_TargetToSource->field[x][y].x = x; nnf_TargetToSource->field[x][y].y = y; nnf_TargetToSource->field[x][y].distance = 0; } } } //minimize the NNF nnf_TargetToSource->minimize(iterNNF); //Now we rebuild the target using best patches from source MaskedImageSP newsource = nullptr; bool upscaled = false; // Instead of upsizing the final target, we build the last target from the next level source image // So the final target is less blurry (see "Space-Time Video Completion" - page 5) if (level >= 1 && (emloop == iterEM)) { newsource = pyramid.at(level - 1); QRect sz = newsource->size(); newtarget = target->copy(); newtarget->upscale(sz.width(), sz.height()); upscaled = true; } else { newsource = pyramid.at(level); newtarget = target->copy(); upscaled = false; } //EM Step //EM_Step(newsource, newtarget, radius, upscaled); ExpectationStep(nnf_TargetToSource, newsource, newtarget, upscaled); } return newtarget; } void NearestNeighborField::ExpectationStep(NearestNeighborFieldSP nnf, MaskedImageSP source, MaskedImageSP target, bool upscale) { //int*** field = nnf->field; int R = nnf->patchSize; if (upscale) R *= 2; int H_nnf = nnf->input->size().height(); int W_nnf = nnf->input->size().width(); int H_target = target->size().height(); int W_target = target->size().width(); int H_source = source->size().height(); int W_source = source->size().width(); std::vector< quint8* > pixels; std::vector< float > weights; pixels.reserve(R * R); weights.reserve(R * R); for (int x = 0 ; x < W_target ; ++x) { for (int y = 0 ; y < H_target; ++y) { float wsum = 0; pixels.clear(); weights.clear(); if (!source->containsMasked(x, y, R + 4) /*&& upscale*/) { //speedup computation by copying parts that are not masked. pixels.push_back(source->getImagePixel(x, y)); weights.push_back(1.f); target->mixColors(pixels, weights, 1.f, target->getImagePixel(x, y)); } else { for (int dx = -R ; dx <= R; ++dx) { for (int dy = -R ; dy <= R ; ++dy) { // xpt,ypt = center pixel of the target patch int xpt = x + dx; int ypt = y + dy; int xst, yst; float w; if (!upscale) { if (xpt < 0 || xpt >= W_nnf || ypt < 0 || ypt >= H_nnf) continue; xst = nnf->field[xpt][ypt].x; yst = nnf->field[xpt][ypt].y; float dp = nnf->field[xpt][ypt].distance; // similarity measure between the two patches w = nnf->similarity[dp]; } else { if (xpt < 0 || (xpt / 2) >= W_nnf || ypt < 0 || (ypt / 2) >= H_nnf) continue; xst = 2 * nnf->field[xpt / 2][ypt / 2].x + (xpt % 2); yst = 2 * nnf->field[xpt / 2][ypt / 2].y + (ypt % 2); float dp = nnf->field[xpt / 2][ypt / 2].distance; // similarity measure between the two patches w = nnf->similarity[dp]; } int xs = xst - dx; int ys = yst - dy; if (xs < 0 || xs >= W_source || ys < 0 || ys >= H_source) continue; if (source->isMasked(xs, ys)) continue; pixels.push_back(source->getImagePixel(xs, ys)); weights.push_back(w); wsum += w; } } if (wsum < 1) continue; target->mixColors(pixels, weights, wsum, target->getImagePixel(x, y)); } } } } QRect getMaskBoundingBox(KisPaintDeviceSP maskDev) { QRect maskRect = maskDev->nonDefaultPixelArea(); return maskRect; } -QRect patchImage(const KisPaintDeviceSP imageDev, const KisPaintDeviceSP maskDev, int patchRadius, int accuracy) +QRect patchImage(const KisPaintDeviceSP imageDev, const KisPaintDeviceSP maskDev, int patchRadius, int accuracy, KisSelectionSP selection) { QRect maskRect = getMaskBoundingBox(maskDev); QRect imageRect = imageDev->exactBounds(); float scale = 1.0 + (accuracy / 25.0); //higher accuracy means we include more surrounding area around the patch. Minimum 2x padding. int dx = maskRect.width() * scale; int dy = maskRect.height() * scale; maskRect.adjust(-dx, -dy, dx, dy); maskRect = maskRect.intersected(imageRect); if (!maskRect.isEmpty()) { Inpaint inpaint(imageDev, maskDev, patchRadius, maskRect); MaskedImageSP output = inpaint.patch(); - output->toPaintDevice(imageDev, maskRect); + output->toPaintDevice(imageDev, maskRect, selection); } return maskRect; } diff --git a/plugins/tools/tool_smart_patch/kis_tool_smart_patch.cpp b/plugins/tools/tool_smart_patch/kis_tool_smart_patch.cpp index bc9a320429..0eb78613dd 100644 --- a/plugins/tools/tool_smart_patch/kis_tool_smart_patch.cpp +++ b/plugins/tools/tool_smart_patch/kis_tool_smart_patch.cpp @@ -1,277 +1,284 @@ /* * Copyright (c) 2017 Eugene Ingerman * * 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_tool_smart_patch.h" #include "QApplication" #include "QPainterPath" #include #include #include #include "kis_canvas2.h" #include "kis_cursor.h" #include "kis_painter.h" #include "kis_paintop_preset.h" #include "kundo2magicstring.h" #include "kundo2stack.h" #include "commands_new/kis_transaction_based_command.h" #include "kis_transaction.h" #include "kis_processing_applicator.h" #include "kis_datamanager.h" #include "KoColorSpaceRegistry.h" #include "kis_tool_smart_patch_options_widget.h" #include "libs/image/kis_paint_device_debug_utils.h" #include "kis_paint_layer.h" #include "kis_algebra_2d.h" +#include "kis_resources_snapshot.h" -QRect patchImage(KisPaintDeviceSP imageDev, KisPaintDeviceSP maskDev, int radius, int accuracy); +QRect patchImage(KisPaintDeviceSP imageDev, KisPaintDeviceSP maskDev, int radius, int accuracy, KisSelectionSP selection); class KisToolSmartPatch::InpaintCommand : public KisTransactionBasedCommand { public: - InpaintCommand( KisPaintDeviceSP maskDev, KisPaintDeviceSP imageDev, int accuracy, int patchRadius ) : - m_maskDev(maskDev), m_imageDev(imageDev), m_accuracy(accuracy), m_patchRadius(patchRadius) {} + InpaintCommand( KisPaintDeviceSP maskDev, KisPaintDeviceSP imageDev, int accuracy, int patchRadius, KisSelectionSP selection) : + m_maskDev(maskDev), m_imageDev(imageDev), m_accuracy(accuracy), m_patchRadius(patchRadius), m_selection(selection) {} KUndo2Command* paint() override { KisTransaction transaction(m_imageDev); - patchImage(m_imageDev, m_maskDev, m_patchRadius, m_accuracy); + patchImage(m_imageDev, m_maskDev, m_patchRadius, m_accuracy, m_selection); return transaction.endAndTake(); } private: KisPaintDeviceSP m_maskDev, m_imageDev; int m_accuracy, m_patchRadius; + KisSelectionSP m_selection; }; struct KisToolSmartPatch::Private { KisPaintDeviceSP maskDev = nullptr; KisPainter maskDevPainter; float brushRadius = 50.; //initial default. actually read from ui. KisToolSmartPatchOptionsWidget *optionsWidget = nullptr; QRectF oldOutlineRect; QPainterPath brushOutline; }; KisToolSmartPatch::KisToolSmartPatch(KoCanvasBase * canvas) : KisToolPaint(canvas, KisCursor::blankCursor()), m_d(new Private) { setSupportOutline(true); setObjectName("tool_SmartPatch"); m_d->maskDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); m_d->maskDevPainter.begin( m_d->maskDev ); m_d->maskDevPainter.setPaintColor(KoColor(Qt::magenta, m_d->maskDev->colorSpace())); m_d->maskDevPainter.setBackgroundColor(KoColor(Qt::white, m_d->maskDev->colorSpace())); m_d->maskDevPainter.setFillStyle( KisPainter::FillStyleForegroundColor ); } KisToolSmartPatch::~KisToolSmartPatch() { m_d->optionsWidget = nullptr; m_d->maskDevPainter.end(); } void KisToolSmartPatch::activate(ToolActivation activation, const QSet &shapes) { KisToolPaint::activate(activation, shapes); } void KisToolSmartPatch::deactivate() { KisToolPaint::deactivate(); } void KisToolSmartPatch::resetCursorStyle() { KisToolPaint::resetCursorStyle(); } void KisToolSmartPatch::activatePrimaryAction() { setOutlineEnabled(true); KisToolPaint::activatePrimaryAction(); } void KisToolSmartPatch::deactivatePrimaryAction() { setOutlineEnabled(false); KisToolPaint::deactivatePrimaryAction(); } void KisToolSmartPatch::addMaskPath( KoPointerEvent *event ) { KisCanvas2 *canvas2 = dynamic_cast(canvas()); const KisCoordinatesConverter *converter = canvas2->coordinatesConverter(); QPointF imagePos = currentImage()->documentToPixel(event->point); QPainterPath currentBrushOutline = brushOutline().translated(KisAlgebra2D::alignForZoom(imagePos, converter->effectiveZoom())); m_d->maskDevPainter.fillPainterPath(currentBrushOutline); canvas()->updateCanvas(currentImage()->pixelToDocument(m_d->maskDev->exactBounds())); } void KisToolSmartPatch::beginPrimaryAction(KoPointerEvent *event) { //we can only apply inpaint operation to paint layer if ( currentNode().isNull() || !currentNode()->inherits("KisPaintLayer") || nodePaintAbility()!=NodePaintAbility::PAINT ) { KisCanvas2 * kiscanvas = static_cast(canvas()); kiscanvas->viewManager()-> showFloatingMessage( i18n("Select a paint layer to use this tool"), QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter); event->ignore(); return; } addMaskPath(event); setMode(KisTool::PAINT_MODE); KisToolPaint::beginPrimaryAction(event); } void KisToolSmartPatch::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); addMaskPath(event); KisToolPaint::continuePrimaryAction(event); } - void KisToolSmartPatch::endPrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); addMaskPath(event); KisToolPaint::endPrimaryAction(event); setMode(KisTool::HOVER_MODE); QApplication::setOverrideCursor(KisCursor::waitCursor()); int accuracy = 50; //default accuracy - middle value int patchRadius = 4; //default radius, which works well for most cases tested if (m_d->optionsWidget) { accuracy = m_d->optionsWidget->getAccuracy(); patchRadius = m_d->optionsWidget->getPatchRadius(); } + KisResourcesSnapshotSP resources = + new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); + KisProcessingApplicator applicator( image(), currentNode(), KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Smart Patch")); //actual inpaint operation. filling in areas masked by user - applicator.applyCommand( new InpaintCommand( KisPainter::convertToAlphaAsAlpha(m_d->maskDev), currentNode()->paintDevice(), accuracy, patchRadius ), + applicator.applyCommand( new InpaintCommand( KisPainter::convertToAlphaAsAlpha(m_d->maskDev), + currentNode()->paintDevice(), + accuracy, patchRadius, + resources->activeSelection()), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE ); applicator.end(); image()->waitForDone(); QApplication::restoreOverrideCursor(); m_d->maskDev->clear(); } QPainterPath KisToolSmartPatch::brushOutline( void ) { const qreal diameter = m_d->brushRadius; QPainterPath outline; outline.addEllipse(QPointF(0,0), -0.5 * diameter, -0.5 * diameter ); return outline; } QPainterPath KisToolSmartPatch::getBrushOutlinePath(const QPointF &documentPos, const KoPointerEvent *event) { Q_UNUSED(event); QPointF imagePos = currentImage()->documentToPixel(documentPos); QPainterPath path = brushOutline(); KisCanvas2 *canvas2 = dynamic_cast(canvas()); const KisCoordinatesConverter *converter = canvas2->coordinatesConverter(); return path.translated(KisAlgebra2D::alignForZoom(imagePos, converter->effectiveZoom())); } void KisToolSmartPatch::requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event) { static QPointF lastDocPoint = QPointF(0,0); if( event ) lastDocPoint=outlineDocPoint; m_d->brushRadius = currentPaintOpPreset()->settings()->paintOpSize(); m_d->brushOutline = getBrushOutlinePath(lastDocPoint, event); QRectF outlinePixelRect = m_d->brushOutline.boundingRect(); QRectF outlineDocRect = currentImage()->pixelToDocument(outlinePixelRect); // This adjusted call is needed as we paint with a 3 pixel wide brush and the pen is outside the bounds of the path // Pen uses view coordinates so we have to zoom the document value to match 2 pixel in view coordinates // See BUG 275829 qreal zoomX; qreal zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); qreal xoffset = 2.0/zoomX; qreal yoffset = 2.0/zoomY; if (!outlineDocRect.isEmpty()) { outlineDocRect.adjust(-xoffset,-yoffset,xoffset,yoffset); } if (!m_d->oldOutlineRect.isEmpty()) { canvas()->updateCanvas(m_d->oldOutlineRect); } if (!outlineDocRect.isEmpty()) { canvas()->updateCanvas(outlineDocRect); } m_d->oldOutlineRect = outlineDocRect; } void KisToolSmartPatch::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); painter.save(); QPainterPath path = pixelToView(m_d->brushOutline); paintToolOutline(&painter, path); painter.restore(); painter.save(); painter.setBrush(Qt::magenta); QImage img = m_d->maskDev->convertToQImage(0); if( !img.size().isEmpty() ){ painter.drawImage(pixelToView(m_d->maskDev->exactBounds()), img); } painter.restore(); } QWidget * KisToolSmartPatch::createOptionWidget() { KisCanvas2 * kiscanvas = dynamic_cast(canvas()); m_d->optionsWidget = new KisToolSmartPatchOptionsWidget(kiscanvas->viewManager()->canvasResourceProvider(), 0); m_d->optionsWidget->setObjectName(toolId() + "option widget"); return m_d->optionsWidget; } diff --git a/plugins/tools/tool_transform2/tool_transform_args.cc b/plugins/tools/tool_transform2/tool_transform_args.cc index 6c950b6a87..a7e93be78e 100644 --- a/plugins/tools/tool_transform2/tool_transform_args.cc +++ b/plugins/tools/tool_transform2/tool_transform_args.cc @@ -1,494 +1,494 @@ /* * tool_transform_args.h - part of Krita * * Copyright (c) 2010 Marc Pegon * * 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 "tool_transform_args.h" #include #include #include #include #include "kis_liquify_transform_worker.h" #include "kis_dom_utils.h" ToolTransformArgs::ToolTransformArgs() : m_liquifyProperties(new KisLiquifyProperties()) { KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); QString savedFilterId = configGroup.readEntry("filterId", "Bicubic"); setFilterId(savedFilterId); m_transformAroundRotationCenter = configGroup.readEntry("transformAroundRotationCenter", "0").toInt(); } void ToolTransformArgs::setFilterId(const QString &id) { m_filter = KisFilterStrategyRegistry::instance()->value(id); if (m_filter) { KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); configGroup.writeEntry("filterId", id); } } void ToolTransformArgs::setTransformAroundRotationCenter(bool value) { m_transformAroundRotationCenter = value; KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); configGroup.writeEntry("transformAroundRotationCenter", int(value)); } void ToolTransformArgs::init(const ToolTransformArgs& args) { m_mode = args.mode(); m_transformedCenter = args.transformedCenter(); m_originalCenter = args.originalCenter(); m_rotationCenterOffset = args.rotationCenterOffset(); m_transformAroundRotationCenter = args.transformAroundRotationCenter(); m_cameraPos = args.m_cameraPos; m_aX = args.aX(); m_aY = args.aY(); m_aZ = args.aZ(); m_scaleX = args.scaleX(); m_scaleY = args.scaleY(); m_shearX = args.shearX(); m_shearY = args.shearY(); m_origPoints = args.origPoints(); //it's a copy m_transfPoints = args.transfPoints(); m_warpType = args.warpType(); m_alpha = args.alpha(); m_defaultPoints = args.defaultPoints(); m_keepAspectRatio = args.keepAspectRatio(); m_filter = args.m_filter; m_flattenedPerspectiveTransform = args.m_flattenedPerspectiveTransform; m_editTransformPoints = args.m_editTransformPoints; m_pixelPrecision = args.pixelPrecision(); m_previewPixelPrecision = args.previewPixelPrecision(); if (args.m_liquifyWorker) { m_liquifyWorker.reset(new KisLiquifyTransformWorker(*args.m_liquifyWorker.data())); } m_continuedTransformation.reset(args.m_continuedTransformation ? new ToolTransformArgs(*args.m_continuedTransformation) : 0); } void ToolTransformArgs::clear() { m_origPoints.clear(); m_transfPoints.clear(); } ToolTransformArgs::ToolTransformArgs(const ToolTransformArgs& args) : m_liquifyProperties(new KisLiquifyProperties(*args.m_liquifyProperties.data())) { init(args); } KisToolChangesTrackerData *ToolTransformArgs::clone() const { return new ToolTransformArgs(*this); } ToolTransformArgs& ToolTransformArgs::operator=(const ToolTransformArgs& args) { if (this == &args) return *this; clear(); m_liquifyProperties.reset(new KisLiquifyProperties(*args.m_liquifyProperties.data())); init(args); return *this; } bool ToolTransformArgs::operator==(const ToolTransformArgs& other) const { return m_mode == other.m_mode && m_defaultPoints == other.m_defaultPoints && m_origPoints == other.m_origPoints && m_transfPoints == other.m_transfPoints && m_warpType == other.m_warpType && m_alpha == other.m_alpha && m_transformedCenter == other.m_transformedCenter && m_originalCenter == other.m_originalCenter && m_rotationCenterOffset == other.m_rotationCenterOffset && m_transformAroundRotationCenter == other.m_transformAroundRotationCenter && m_aX == other.m_aX && m_aY == other.m_aY && m_aZ == other.m_aZ && m_cameraPos == other.m_cameraPos && m_scaleX == other.m_scaleX && m_scaleY == other.m_scaleY && m_shearX == other.m_shearX && m_shearY == other.m_shearY && m_keepAspectRatio == other.m_keepAspectRatio && m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform && m_editTransformPoints == other.m_editTransformPoints && (m_liquifyProperties == other.m_liquifyProperties || *m_liquifyProperties == *other.m_liquifyProperties) && // pointer types ((m_filter && other.m_filter && m_filter->id() == other.m_filter->id()) || m_filter == other.m_filter) && ((m_liquifyWorker && other.m_liquifyWorker && *m_liquifyWorker == *other.m_liquifyWorker) || m_liquifyWorker == other.m_liquifyWorker) && m_pixelPrecision == other.m_pixelPrecision && m_previewPixelPrecision == other.m_previewPixelPrecision; } bool ToolTransformArgs::isSameMode(const ToolTransformArgs& other) const { if (m_mode != other.m_mode) return false; bool result = true; if (m_mode == FREE_TRANSFORM) { result &= m_transformedCenter == other.m_transformedCenter; result &= m_originalCenter == other.m_originalCenter; result &= m_scaleX == other.m_scaleX; result &= m_scaleY == other.m_scaleY; result &= m_shearX == other.m_shearX; result &= m_shearY == other.m_shearY; result &= m_aX == other.m_aX; result &= m_aY == other.m_aY; result &= m_aZ == other.m_aZ; } else if (m_mode == PERSPECTIVE_4POINT) { result &= m_transformedCenter == other.m_transformedCenter; result &= m_originalCenter == other.m_originalCenter; result &= m_scaleX == other.m_scaleX; result &= m_scaleY == other.m_scaleY; result &= m_shearX == other.m_shearX; result &= m_shearY == other.m_shearY; result &= m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform; } else if(m_mode == WARP || m_mode == CAGE) { result &= m_origPoints == other.m_origPoints; result &= m_transfPoints == other.m_transfPoints; } else if (m_mode == LIQUIFY) { result &= m_liquifyProperties && (m_liquifyProperties == other.m_liquifyProperties || *m_liquifyProperties == *other.m_liquifyProperties); result &= (m_liquifyWorker && other.m_liquifyWorker && *m_liquifyWorker == *other.m_liquifyWorker) || m_liquifyWorker == other.m_liquifyWorker; } else { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); } return result; } ToolTransformArgs::ToolTransformArgs(TransformMode mode, QPointF transformedCenter, QPointF originalCenter, QPointF rotationCenterOffset, bool transformAroundRotationCenter, double aX, double aY, double aZ, double scaleX, double scaleY, double shearX, double shearY, KisWarpTransformWorker::WarpType warpType, double alpha, bool defaultPoints, const QString &filterId, int pixelPrecision, int previewPixelPrecision) : m_mode(mode) , m_defaultPoints(defaultPoints) , m_origPoints {QVector()} , m_transfPoints {QVector()} , m_warpType(warpType) , m_alpha(alpha) , m_transformedCenter(transformedCenter) , m_originalCenter(originalCenter) , m_rotationCenterOffset(rotationCenterOffset) , m_transformAroundRotationCenter(transformAroundRotationCenter) , m_aX(aX) , m_aY(aY) , m_aZ(aZ) , m_scaleX(scaleX) , m_scaleY(scaleY) , m_shearX(shearX) , m_shearY(shearY) , m_liquifyProperties(new KisLiquifyProperties()) , m_pixelPrecision(pixelPrecision) , m_previewPixelPrecision(previewPixelPrecision) { setFilterId(filterId); } ToolTransformArgs::~ToolTransformArgs() { clear(); } void ToolTransformArgs::translate(const QPointF &offset) { if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) { m_originalCenter += offset; m_rotationCenterOffset += offset; m_transformedCenter += offset; } else if(m_mode == WARP || m_mode == CAGE) { for (auto &pt : m_origPoints) { pt += offset; } for (auto &pt : m_transfPoints) { pt += offset; } } else if (m_mode == LIQUIFY) { KIS_ASSERT_RECOVER_RETURN(m_liquifyWorker); m_liquifyWorker->translate(offset); } else { KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); } } bool ToolTransformArgs::isIdentity() const { if (m_mode == FREE_TRANSFORM) { return (m_transformedCenter == m_originalCenter && m_scaleX == 1 && m_scaleY == 1 && m_shearX == 0 && m_shearY == 0 && m_aX == 0 && m_aY == 0 && m_aZ == 0); } else if (m_mode == PERSPECTIVE_4POINT) { return (m_transformedCenter == m_originalCenter && m_scaleX == 1 && m_scaleY == 1 && m_shearX == 0 && m_shearY == 0 && m_flattenedPerspectiveTransform.isIdentity()); } else if(m_mode == WARP || m_mode == CAGE) { for (int i = 0; i < m_origPoints.size(); ++i) if (m_origPoints[i] != m_transfPoints[i]) return false; return true; } else if (m_mode == LIQUIFY) { - // Not implemented! - return false; + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_liquifyWorker, false); + return m_liquifyWorker->isIdentity(); } else { KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); return true; } } void ToolTransformArgs::initLiquifyTransformMode(const QRect &srcRect) { m_liquifyWorker.reset(new KisLiquifyTransformWorker(srcRect, 0, 8)); m_liquifyProperties->loadAndResetMode(); } void ToolTransformArgs::saveLiquifyTransformMode() const { m_liquifyProperties->saveMode(); } void ToolTransformArgs::toXML(QDomElement *e) const { e->setAttribute("mode", (int) m_mode); QDomDocument doc = e->ownerDocument(); if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) { QDomElement freeEl = doc.createElement("free_transform"); e->appendChild(freeEl); KisDomUtils::saveValue(&freeEl, "transformedCenter", m_transformedCenter); KisDomUtils::saveValue(&freeEl, "originalCenter", m_originalCenter); KisDomUtils::saveValue(&freeEl, "rotationCenterOffset", m_rotationCenterOffset); KisDomUtils::saveValue(&freeEl, "transformAroundRotationCenter", m_transformAroundRotationCenter); KisDomUtils::saveValue(&freeEl, "aX", m_aX); KisDomUtils::saveValue(&freeEl, "aY", m_aY); KisDomUtils::saveValue(&freeEl, "aZ", m_aZ); KisDomUtils::saveValue(&freeEl, "cameraPos", m_cameraPos); KisDomUtils::saveValue(&freeEl, "scaleX", m_scaleX); KisDomUtils::saveValue(&freeEl, "scaleY", m_scaleY); KisDomUtils::saveValue(&freeEl, "shearX", m_shearX); KisDomUtils::saveValue(&freeEl, "shearY", m_shearY); KisDomUtils::saveValue(&freeEl, "keepAspectRatio", m_keepAspectRatio); KisDomUtils::saveValue(&freeEl, "flattenedPerspectiveTransform", m_flattenedPerspectiveTransform); KisDomUtils::saveValue(&freeEl, "filterId", m_filter->id()); } else if (m_mode == WARP || m_mode == CAGE) { QDomElement warpEl = doc.createElement("warp_transform"); e->appendChild(warpEl); KisDomUtils::saveValue(&warpEl, "defaultPoints", m_defaultPoints); KisDomUtils::saveValue(&warpEl, "originalPoints", m_origPoints); KisDomUtils::saveValue(&warpEl, "transformedPoints", m_transfPoints); KisDomUtils::saveValue(&warpEl, "warpType", (int)m_warpType); // limited! KisDomUtils::saveValue(&warpEl, "alpha", m_alpha); if(m_mode == CAGE){ KisDomUtils::saveValue(&warpEl,"pixelPrecision",m_pixelPrecision); KisDomUtils::saveValue(&warpEl,"previewPixelPrecision",m_previewPixelPrecision); } } else if (m_mode == LIQUIFY) { QDomElement liqEl = doc.createElement("liquify_transform"); e->appendChild(liqEl); m_liquifyProperties->toXML(&liqEl); m_liquifyWorker->toXML(&liqEl); } else { KIS_ASSERT_RECOVER_RETURN(0 && "Unknown transform mode"); } // m_editTransformPoints should not be saved since it is reset explicitly } ToolTransformArgs ToolTransformArgs::fromXML(const QDomElement &e) { ToolTransformArgs args; int newMode = e.attribute("mode", "0").toInt(); if (newMode < 0 || newMode >= N_MODES) return ToolTransformArgs(); args.m_mode = (TransformMode) newMode; // reset explicitly args.m_editTransformPoints = false; bool result = false; if (args.m_mode == FREE_TRANSFORM || args.m_mode == PERSPECTIVE_4POINT) { QDomElement freeEl; QString filterId; result = KisDomUtils::findOnlyElement(e, "free_transform", &freeEl) && KisDomUtils::loadValue(freeEl, "transformedCenter", &args.m_transformedCenter) && KisDomUtils::loadValue(freeEl, "originalCenter", &args.m_originalCenter) && KisDomUtils::loadValue(freeEl, "rotationCenterOffset", &args.m_rotationCenterOffset) && KisDomUtils::loadValue(freeEl, "aX", &args.m_aX) && KisDomUtils::loadValue(freeEl, "aY", &args.m_aY) && KisDomUtils::loadValue(freeEl, "aZ", &args.m_aZ) && KisDomUtils::loadValue(freeEl, "cameraPos", &args.m_cameraPos) && KisDomUtils::loadValue(freeEl, "scaleX", &args.m_scaleX) && KisDomUtils::loadValue(freeEl, "scaleY", &args.m_scaleY) && KisDomUtils::loadValue(freeEl, "shearX", &args.m_shearX) && KisDomUtils::loadValue(freeEl, "shearY", &args.m_shearY) && KisDomUtils::loadValue(freeEl, "keepAspectRatio", &args.m_keepAspectRatio) && KisDomUtils::loadValue(freeEl, "flattenedPerspectiveTransform", &args.m_flattenedPerspectiveTransform) && KisDomUtils::loadValue(freeEl, "filterId", &filterId); // transformAroundRotationCenter is a new parameter introduced in Krita 4.0, // so it might be not present in older transform masks if (!KisDomUtils::loadValue(freeEl, "transformAroundRotationCenter", &args.m_transformAroundRotationCenter)) { args.m_transformAroundRotationCenter = false; } if (result) { args.m_filter = KisFilterStrategyRegistry::instance()->value(filterId); result = (bool) args.m_filter; } } else if (args.m_mode == WARP || args.m_mode == CAGE) { QDomElement warpEl; int warpType = 0; result = KisDomUtils::findOnlyElement(e, "warp_transform", &warpEl) && KisDomUtils::loadValue(warpEl, "defaultPoints", &args.m_defaultPoints) && KisDomUtils::loadValue(warpEl, "originalPoints", &args.m_origPoints) && KisDomUtils::loadValue(warpEl, "transformedPoints", &args.m_transfPoints) && KisDomUtils::loadValue(warpEl, "warpType", &warpType) && KisDomUtils::loadValue(warpEl, "alpha", &args.m_alpha); if(args.m_mode == CAGE){ // Pixel precision is a parameter introduced in Krita 4.2, so we should // expect it not being present in older files. In case it is not found, // just use the defalt value initialized by c-tor (that is, do nothing). (void) KisDomUtils::loadValue(warpEl, "pixelPrecision", &args.m_pixelPrecision); (void) KisDomUtils::loadValue(warpEl, "previewPixelPrecision", &args.m_previewPixelPrecision); } if (result && warpType >= 0 && warpType < KisWarpTransformWorker::N_MODES) { args.m_warpType = (KisWarpTransformWorker::WarpType_) warpType; } else { result = false; } } else if (args.m_mode == LIQUIFY) { QDomElement liquifyEl; result = KisDomUtils::findOnlyElement(e, "liquify_transform", &liquifyEl); *args.m_liquifyProperties = KisLiquifyProperties::fromXML(e); args.m_liquifyWorker.reset(KisLiquifyTransformWorker::fromXML(e)); } else { KIS_ASSERT_RECOVER_NOOP(0 && "Unknown transform mode"); } KIS_SAFE_ASSERT_RECOVER(result) { args = ToolTransformArgs(); } return args; } void ToolTransformArgs::saveContinuedState() { m_continuedTransformation.reset(); m_continuedTransformation.reset(new ToolTransformArgs(*this)); } void ToolTransformArgs::restoreContinuedState() { QScopedPointer tempTransformation( new ToolTransformArgs(*m_continuedTransformation)); *this = *tempTransformation; m_continuedTransformation.swap(tempTransformation); } const ToolTransformArgs* ToolTransformArgs::continuedTransform() const { return m_continuedTransformation.data(); }