diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc index fe28fef884..7e8cc21aef 100644 --- a/libs/image/kis_painter.cc +++ b/libs/image/kis_painter.cc @@ -1,3021 +1,3014 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2008-2010 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara Toloza * Copyright (c) 2011 Silvio Heinrich * * 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_painter.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif #include #include #include #include #include #include #include #include "kis_image.h" #include "filter/kis_filter.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_transaction.h" #include "kis_vec.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "filter/kis_filter_configuration.h" #include "kis_pixel_selection.h" #include #include "kis_paintop_registry.h" #include "kis_perspective_math.h" #include "tiles3/kis_random_accessor.h" #include #include #include "kis_lod_transform.h" #include "kis_algebra_2d.h" #include "krita_utils.h" // Maximum distance from a Bezier control point to the line through the start // and end points for the curve to be considered flat. #define BEZIER_FLATNESS_THRESHOLD 0.5 #include "kis_painter_p.h" KisPainter::KisPainter() : d(new Private(this)) { init(); } KisPainter::KisPainter(KisPaintDeviceSP device) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); } KisPainter::KisPainter(KisPaintDeviceSP device, KisSelectionSP selection) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); d->selection = selection; } void KisPainter::init() { d->selection = 0 ; d->transaction = 0; d->paintOp = 0; d->pattern = 0; d->sourceLayer = 0; d->fillStyle = FillStyleNone; d->strokeStyle = StrokeStyleBrush; d->antiAliasPolygonFill = true; d->progressUpdater = 0; d->gradient = 0; d->maskPainter = 0; d->fillPainter = 0; d->maskImageWidth = 255; d->maskImageHeight = 255; d->mirrorHorizontally = false; d->mirrorVertically = false; d->isOpacityUnit = true; d->paramInfo = KoCompositeOp::ParameterInfo(); d->renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); d->conversionFlags = KoColorConversionTransformation::internalConversionFlags(); } KisPainter::~KisPainter() { // TODO: Maybe, don't be that strict? // deleteTransaction(); end(); delete d->paintOp; delete d->maskPainter; delete d->fillPainter; delete d; } template void copyAreaOptimizedImpl(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { const QRect dstRect(dstPt, srcRect.size()); const QRect srcExtent = src->extent(); const QRect dstExtent = dst->extent(); const QRect srcSampleRect = srcExtent & srcRect; const QRect dstSampleRect = dstExtent & dstRect; const bool srcEmpty = srcSampleRect.isEmpty(); const bool dstEmpty = dstSampleRect.isEmpty(); if (!srcEmpty || !dstEmpty) { if (srcEmpty) { dst->clear(dstRect); } else { QRect srcCopyRect = srcRect; QRect dstCopyRect = dstRect; if (!srcExtent.contains(srcRect)) { if (src->defaultPixel() == dst->defaultPixel()) { const QRect dstSampleInSrcCoords = dstSampleRect.translated(srcRect.topLeft() - dstPt); if (dstSampleInSrcCoords.isEmpty() || srcSampleRect.contains(dstSampleInSrcCoords)) { srcCopyRect = srcSampleRect; } else { srcCopyRect = srcSampleRect | dstSampleInSrcCoords; } dstCopyRect = QRect(dstPt + srcCopyRect.topLeft() - srcRect.topLeft(), srcCopyRect.size()); } } KisPainter gc(dst); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); if (useOldData) { gc.bitBltOldData(dstCopyRect.topLeft(), src, srcCopyRect); } else { gc.bitBlt(dstCopyRect.topLeft(), src, srcCopyRect); } } } } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimizedOldData(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect, KisSelectionSP selection) { if (!selection) { copyAreaOptimized(dstPt, src, dst, originalSrcRect); return; } const QRect selectionRect = selection->selectedRect(); const QRect srcRect = originalSrcRect & selectionRect; const QPoint dstOffset = srcRect.topLeft() - originalSrcRect.topLeft(); const QRect dstRect = QRect(dstPt + dstOffset, srcRect.size()); const bool srcEmpty = (src->extent() & srcRect).isEmpty(); const bool dstEmpty = (dst->extent() & dstRect).isEmpty(); if (!srcEmpty || !dstEmpty) { //if (srcEmpty) { // doesn't support dstRect // dst->clearSelection(selection); // } else */ { KisPainter gc(dst); gc.setSelection(selection); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); gc.bitBlt(dstRect.topLeft(), src, srcRect); } } } KisPaintDeviceSP KisPainter::convertToAlphaAsAlpha(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); if (processRect.isEmpty()) return dst; KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); const quint8 white = srcCS->intensity8(srcPtr); const quint8 alpha = srcCS->opacityU8(srcPtr); *alpha8Ptr = KoColorSpaceMaths::multiply(alpha, KoColorSpaceMathsTraits::unitValue - white); } return dst; } KisPaintDeviceSP KisPainter::convertToAlphaAsGray(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); if (processRect.isEmpty()) return dst; KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->intensity8(srcPtr); } return dst; } bool KisPainter::checkDeviceHasTransparency(KisPaintDeviceSP dev) { const QRect deviceBounds = dev->exactBounds(); const QRect imageBounds = dev->defaultBounds()->bounds(); if (deviceBounds.isEmpty() || (deviceBounds & imageBounds) != imageBounds) { return true; } const KoColorSpace *cs = dev->colorSpace(); KisSequentialConstIterator it(dev, deviceBounds); while(it.nextPixel()) { if (cs->opacityU8(it.rawDataConst()) != OPACITY_OPAQUE_U8) { return true; } } return false; } void KisPainter::begin(KisPaintDeviceSP device) { begin(device, d->selection); } void KisPainter::begin(KisPaintDeviceSP device, KisSelectionSP selection) { if (!device) return; d->selection = selection; Q_ASSERT(device->colorSpace()); end(); d->device = device; d->colorSpace = device->colorSpace(); d->compositeOp = d->colorSpace->compositeOp(COMPOSITE_OVER); d->pixelSize = device->pixelSize(); } void KisPainter::end() { Q_ASSERT_X(!d->transaction, "KisPainter::end()", "end() was called for the painter having a transaction. " "Please use end/deleteTransaction() instead"); } void KisPainter::beginTransaction(const KUndo2MagicString& transactionName,int timedID) { Q_ASSERT_X(!d->transaction, "KisPainter::beginTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = new KisTransaction(transactionName, d->device); Q_CHECK_PTR(d->transaction); d->transaction->undoCommand()->setTimedID(timedID); } void KisPainter::revertTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::revertTransaction()", "No transaction is in progress"); d->transaction->revert(); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisPostExecutionUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } KUndo2Command* KisPainter::endAndTakeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); KUndo2Command *transactionData = d->transaction->endAndTake(); delete d->transaction; d->transaction = 0; return transactionData; } void KisPainter::deleteTransaction() { if (!d->transaction) return; delete d->transaction; d->transaction = 0; } void KisPainter::putTransaction(KisTransaction* transaction) { Q_ASSERT_X(!d->transaction, "KisPainter::putTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = transaction; } KisTransaction* KisPainter::takeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::takeTransaction()", "No transaction is in progress"); KisTransaction *temp = d->transaction; d->transaction = 0; return temp; } QVector KisPainter::takeDirtyRegion() { QVector vrect = d->dirtyRects; d->dirtyRects.clear(); return vrect; } void KisPainter::addDirtyRect(const QRect & rc) { QRect r = rc.normalized(); if (r.isValid()) { d->dirtyRects.append(rc); } } void KisPainter::addDirtyRects(const QVector &rects) { d->dirtyRects.reserve(d->dirtyRects.size() + rects.size()); Q_FOREACH (const QRect &rc, rects) { const QRect r = rc.normalized(); if (r.isValid()) { d->dirtyRects.append(rc); } } } inline bool KisPainter::Private::tryReduceSourceRect(const KisPaintDevice *srcDev, QRect *srcRect, qint32 *srcX, qint32 *srcY, qint32 *srcWidth, qint32 *srcHeight, qint32 *dstX, qint32 *dstY) { /** * In case of COMPOSITE_COPY and Wrap Around Mode even the pixels * outside the device extent matter, because they will be either * directly copied (former case) or cloned from another area of * the image. */ if (compositeOp->id() != COMPOSITE_COPY && compositeOp->id() != COMPOSITE_DESTINATION_IN && compositeOp->id() != COMPOSITE_DESTINATION_ATOP && !srcDev->defaultBounds()->wrapAroundMode()) { /** * If srcDev->extent() (the area of the tiles containing * srcDev) is smaller than srcRect, then shrink srcRect to * that size. This is done as a speed optimization, useful for * stack recomposition in KisImage. srcRect won't grow if * srcDev->extent() is larger. */ *srcRect &= srcDev->extent(); if (srcRect->isEmpty()) return true; // Readjust the function paramenters to the new dimensions. *dstX += srcRect->x() - *srcX; // This will only add, not subtract *dstY += srcRect->y() - *srcY; // Idem srcRect->getRect(srcX, srcY, srcWidth, srcHeight); } return false; } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize YET as it would obfuscate the mistake. */ Q_ASSERT(selection->bounds().contains(selRect)); Q_UNUSED(selRect); // only used by the above Q_ASSERT /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; /* Create an intermediate byte array to hold information before it is written to the current paint device (d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "dst bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); // Copy the relevant bytes of raw data from srcDev quint8* srcBytes = 0; try { srcBytes = new quint8[srcWidth * srcHeight * srcDev->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "src bytes"; return; } srcDev->readBytes(srcBytes, srcX, srcY, srcWidth, srcHeight); QRect selBounds = selection->bounds(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); /* * This checks whether there is nothing selected. */ if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8* mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOP(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; delete[] srcBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 srcWidth, qint32 srcHeight) { bitBltWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } template void KisPainter::bitBltImpl(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); if (d->compositeOp->id() == COMPOSITE_COPY) { if(!d->selection && d->isOpacityUnit && srcX == dstX && srcY == dstY && d->device->fastBitBltPossible(srcDev)) { if(useOldSrcData) { d->device->fastBitBltOldData(srcDev, srcRect); } else { d->device->fastBitBlt(srcDev, srcRect); } addDirtyRect(srcRect); return; } } else { /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; } qint32 dstY_ = dstY; qint32 srcY_ = srcY; qint32 rowsRemaining = srcHeight; // Read below KisRandomConstAccessorSP srcIt = srcDev->createRandomConstAccessorNG(srcX, srcY); KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(dstX, dstY); /* Here be a huge block of verbose code that does roughly the same than the other bit blit operations. This one is longer than the rest in an effort to optimize speed and memory use */ if (d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(dstX, dstY); while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); qint32 maskRowStride = maskIt->rowStride(dstX_, dstY_); maskIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = static_cast(maskIt.data())->rawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } else { while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBlt(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bitBltOldData(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBltOldData(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just * initializing they perform some dummy passes with those parameters, and it must not crash */ if(width == 0 || height == 0 || d->device.isNull()) return; KoColor srcColor(color, d->device->compositionSourceColorSpace()); qint32 dstY = y; qint32 rowsRemaining = height; KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(x, y); if(d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(x, y); while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); qint32 maskRowStride = maskIt->rowStride(dstX, dstY); maskIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = maskIt->oldRawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } else { while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, rowsRemaining); while(columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(x, y, width, height)); } void KisPainter::bltFixed(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ KIS_SAFE_ASSERT_RECOVER_RETURN(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bltFixed std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; if (d->selection) { /* d->selection is a KisPaintDevice, so first a readBytes is performed to get the area of interest... */ KisPaintDeviceSP selectionProjection(d->selection->projection()); quint8* selBytes = 0; try { selBytes = new quint8[srcWidth * srcHeight * selectionProjection->pixelSize()]; } catch (const std::bad_alloc&) { delete[] dstBytes; return; } selectionProjection->readBytes(selBytes, dstX, dstY, srcWidth, srcHeight); d->paramInfo.maskRowStart = selBytes; d->paramInfo.maskRowStride = srcWidth * selectionProjection->pixelSize(); } // ...and then blit. d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] d->paramInfo.maskRowStart; delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect) { bltFixed(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, quint32 srcWidth, quint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); QRect selBounds = selection->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ Q_ASSERT(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion Q_ASSERT(selBounds.contains(selRect)); Q_UNUSED(selRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8 * mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << totalBytes << "total bytes"; delete[] dstBytes; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOp(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, quint32 srcWidth, quint32 srcHeight) { bltFixedWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } void KisPainter::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->device && d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintLine(pi1, pi2, currentDistance); } } void KisPainter::paintPolyline(const vQPointF &points, int index, int numPoints) { if (d->fillStyle != FillStyleNone) { fillPolygon(points, d->fillStyle); } if (d->strokeStyle == StrokeStyleNone) return; if (index >= points.count()) return; if (numPoints < 0) numPoints = points.count(); if (index + numPoints > points.count()) numPoints = points.count() - index; if (numPoints > 1) { KisDistanceInformation saveDist(points[0], KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = index; i < index + numPoints - 1; i++) { paintLine(points [i], points [i + 1], &saveDist); } } } static void getBezierCurvePoints(const KisVector2D &pos1, const KisVector2D &control1, const KisVector2D &control2, const KisVector2D &pos2, vQPointF& points) { LineEquation line = LineEquation::Through(pos1, pos2); qreal d1 = line.absDistance(control1); qreal d2 = line.absDistance(control2); if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) { points.push_back(toQPointF(pos1)); } else { // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 KisVector2D l2 = (pos1 + control1) / 2; KisVector2D h = (control1 + control2) / 2; KisVector2D l3 = (l2 + h) / 2; KisVector2D r3 = (control2 + pos2) / 2; KisVector2D r2 = (h + r3) / 2; KisVector2D l4 = (l3 + r2) / 2; getBezierCurvePoints(pos1, l2, l3, l4, points); getBezierCurvePoints(l4, r2, r3, pos2, points); } } void KisPainter::getBezierCurvePoints(const QPointF &pos1, const QPointF &control1, const QPointF &control2, const QPointF &pos2, vQPointF& points) const { ::getBezierCurvePoints(toKisVector2D(pos1), toKisVector2D(control1), toKisVector2D(control2), toKisVector2D(pos2), points); } void KisPainter::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintBezierCurve(pi1, control1, control2, pi2, currentDistance); } } void KisPainter::paintRect(const QRectF &rect) { QRectF normalizedRect = rect.normalized(); vQPointF points; points.push_back(normalizedRect.topLeft()); points.push_back(normalizedRect.bottomLeft()); points.push_back(normalizedRect.bottomRight()); points.push_back(normalizedRect.topRight()); paintPolygon(points); } void KisPainter::paintRect(const qreal x, const qreal y, const qreal w, const qreal h) { paintRect(QRectF(x, y, w, h)); } void KisPainter::paintEllipse(const QRectF &rect) { QRectF r = rect.normalized(); // normalize before checking as negative width and height are empty too if (r.isEmpty()) return; // See http://www.whizkidtech.redprince.net/bezier/circle/ for explanation. // kappa = (4/3*(sqrt(2)-1)) const qreal kappa = 0.5522847498; const qreal lx = (r.width() / 2) * kappa; const qreal ly = (r.height() / 2) * kappa; QPointF center = r.center(); QPointF p0(r.left(), center.y()); QPointF p1(r.left(), center.y() - ly); QPointF p2(center.x() - lx, r.top()); QPointF p3(center.x(), r.top()); vQPointF points; getBezierCurvePoints(p0, p1, p2, p3, points); QPointF p4(center.x() + lx, r.top()); QPointF p5(r.right(), center.y() - ly); QPointF p6(r.right(), center.y()); getBezierCurvePoints(p3, p4, p5, p6, points); QPointF p7(r.right(), center.y() + ly); QPointF p8(center.x() + lx, r.bottom()); QPointF p9(center.x(), r.bottom()); getBezierCurvePoints(p6, p7, p8, p9, points); QPointF p10(center.x() - lx, r.bottom()); QPointF p11(r.left(), center.y() + ly); getBezierCurvePoints(p9, p10, p11, p0, points); paintPolygon(points); } void KisPainter::paintEllipse(const qreal x, const qreal y, const qreal w, const qreal h) { paintEllipse(QRectF(x, y, w, h)); } void KisPainter::paintAt(const KisPaintInformation& pi, KisDistanceInformation *savedDist) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintAt(pi, savedDist); } } void KisPainter::fillPolygon(const vQPointF& points, FillStyle fillStyle) { if (points.count() < 3) { return; } if (fillStyle == FillStyleNone) { return; } QPainterPath polygonPath; polygonPath.moveTo(points.at(0)); for (int pointIndex = 1; pointIndex < points.count(); pointIndex++) { polygonPath.lineTo(points.at(pointIndex)); } polygonPath.closeSubpath(); d->fillStyle = fillStyle; fillPainterPath(polygonPath); } void KisPainter::paintPolygon(const vQPointF& points) { if (d->fillStyle != FillStyleNone) { fillPolygon(points, d->fillStyle); } if (d->strokeStyle != StrokeStyleNone) { if (points.count() > 1) { KisDistanceInformation distance(points[0], KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = 0; i < points.count() - 1; i++) { paintLine(KisPaintInformation(points[i]), KisPaintInformation(points[i + 1]), &distance); } paintLine(points[points.count() - 1], points[0], &distance); } } } void KisPainter::paintPainterPath(const QPainterPath& path) { if (d->fillStyle != FillStyleNone) { fillPainterPath(path); } if (d->strokeStyle == StrokeStyleNone) return; QPointF lastPoint, nextPoint; int elementCount = path.elementCount(); KisDistanceInformation saveDist; for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: lastPoint = QPointF(element.x, element.y); break; case QPainterPath::LineToElement: nextPoint = QPointF(element.x, element.y); paintLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; case QPainterPath::CurveToElement: nextPoint = QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y); paintBezierCurve(KisPaintInformation(lastPoint), QPointF(path.elementAt(i).x, path.elementAt(i).y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; default: continue; } } } void KisPainter::fillPainterPath(const QPainterPath& path) { fillPainterPath(path, QRect()); } void KisPainter::fillPainterPath(const QPainterPath& path, const QRect &requestedRect) { if (d->mirrorHorizontally || d->mirrorVertically) { KisLodTransform lod(d->device); QPointF effectiveAxesCenter = lod.map(d->axesCenter); QTransform C1 = QTransform::fromTranslate(-effectiveAxesCenter.x(), -effectiveAxesCenter.y()); QTransform C2 = QTransform::fromTranslate(effectiveAxesCenter.x(), effectiveAxesCenter.y()); QTransform t; QPainterPath newPath; QRect newRect; if (d->mirrorHorizontally) { t = C1 * QTransform::fromScale(-1,1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorVertically) { t = C1 * QTransform::fromScale(1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorHorizontally && d->mirrorVertically) { t = C1 * QTransform::fromScale(-1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } } d->fillPainterPathImpl(path, requestedRect); } void KisPainter::Private::fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect) { if (fillStyle == FillStyleNone) { return; } // Fill the polygon bounding rectangle with the required contents then we'll // create a mask for the actual polygon coverage. if (!fillPainter) { polygon = device->createCompositionSourceDevice(); fillPainter = new KisFillPainter(polygon); } else { polygon->clear(); } Q_CHECK_PTR(polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (requestedRect.isValid()) { fillRect &= requestedRect; } switch (fillStyle) { default: Q_FALLTHROUGH(); - case FillStyleGradient: - // Currently unsupported - Q_FALLTHROUGH(); - case FillStyleStrokes: - // Currently unsupported - warnImage << "Unknown or unsupported fill style in fillPolygon\n"; - Q_FALLTHROUGH(); case FillStyleForegroundColor: fillPainter->fillRect(fillRect, q->paintColor(), OPACITY_OPAQUE_U8); break; case FillStyleBackgroundColor: fillPainter->fillRect(fillRect, q->backgroundColor(), OPACITY_OPAQUE_U8); break; case FillStylePattern: if (pattern) { // if the user hasn't got any patterns installed, we shouldn't crash... fillPainter->fillRect(fillRect, pattern); } break; case FillStyleGenerator: if (generator) { // if the user hasn't got any generators, we shouldn't crash... fillPainter->fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), q->generator()); } break; } if (polygonMaskImage.isNull() || (maskPainter == 0)) { polygonMaskImage = QImage(maskImageWidth, maskImageHeight, QImage::Format_ARGB32_Premultiplied); maskPainter = new QPainter(&polygonMaskImage); maskPainter->setRenderHint(QPainter::Antialiasing, q->antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); const QBrush brush(Qt::white); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += maskImageHeight) { polygonMaskImage.fill(black.rgb()); maskPainter->translate(-x, -y); maskPainter->fillPath(path, brush); maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, maskImageHeight); KisHLineIteratorSP lineIt = polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } QRect bltRect = !requestedRect.isEmpty() ? requestedRect : fillRect; q->bitBlt(bltRect.x(), bltRect.y(), polygon, bltRect.x(), bltRect.y(), bltRect.width(), bltRect.height()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen) { drawPainterPath(path, pen, QRect()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& _pen, const QRect &requestedRect) { // we are drawing mask, it has to be white // color of the path is given by paintColor() KIS_SAFE_ASSERT_RECOVER_NOOP(_pen.color() == Qt::white); QPen pen(_pen); pen.setColor(Qt::white); if (!d->fillPainter) { d->polygon = d->device->createCompositionSourceDevice(); d->fillPainter = new KisFillPainter(d->polygon); } else { d->polygon->clear(); } Q_CHECK_PTR(d->polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // take width of the pen into account int penWidth = qRound(pen.widthF()); fillRect.adjust(-penWidth, -penWidth, penWidth, penWidth); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (!requestedRect.isNull()) { fillRect &= requestedRect; } d->fillPainter->fillRect(fillRect, paintColor(), OPACITY_OPAQUE_U8); if (d->polygonMaskImage.isNull() || (d->maskPainter == 0)) { d->polygonMaskImage = QImage(d->maskImageWidth, d->maskImageHeight, QImage::Format_ARGB32_Premultiplied); d->maskPainter = new QPainter(&d->polygonMaskImage); d->maskPainter->setRenderHint(QPainter::Antialiasing, antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); QPen oldPen = d->maskPainter->pen(); d->maskPainter->setPen(pen); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += d->maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += d->maskImageHeight) { d->polygonMaskImage.fill(black.rgb()); d->maskPainter->translate(-x, -y); d->maskPainter->drawPath(path); d->maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, d->maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, d->maskImageHeight); KisHLineIteratorSP lineIt = d->polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(d->polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); d->polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } d->maskPainter->setPen(oldPen); QRect r = d->polygon->extent(); bitBlt(r.x(), r.y(), d->polygon, r.x(), r.y(), r.width(), r.height()); } inline void KisPainter::compositeOnePixel(quint8 *dst, const KoColor &color) { d->paramInfo.dstRowStart = dst; d->paramInfo.dstRowStride = 0; d->paramInfo.srcRowStart = color.data(); d->paramInfo.srcRowStride = 0; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = 1; d->paramInfo.cols = 1; d->colorSpace->bitBlt(color.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } /**/ void KisPainter::drawLine(const QPointF& start, const QPointF& end, qreal width, bool antialias){ int x1 = qFloor(start.x()); int y1 = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); if ((x2 == x1 ) && (y2 == y1)) return; int dstX = x2-x1; int dstY = y2-y1; qreal uniC = dstX*y1 - dstY*x1; qreal projectionDenominator = 1.0 / (pow((double)dstX, 2) + pow((double)dstY, 2)); qreal subPixel; if (qAbs(dstX) > qAbs(dstY)){ subPixel = start.x() - x1; }else{ subPixel = start.y() - y1; } qreal halfWidth = width * 0.5 + subPixel; int W_ = qRound(halfWidth) + 1; // save the state int X1_ = x1; int Y1_ = y1; int X2_ = x2; int Y2_ = y2; if (x2device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } for (int y = y1-W_; y < y2+W_ ; y++){ for (int x = x1-W_; x < x2+W_; x++){ projection = ( (x-X1_)* dstX + (y-Y1_)*dstY ) * projectionDenominator; scanX = X1_ + projection * dstX; scanY = Y1_ + projection * dstY; if (((scanX < x1) || (scanX > x2)) || ((scanY < y1) || (scanY > y2))) { AA_ = qMin( sqrt( pow((double)x - X1_, 2) + pow((double)y - Y1_, 2) ), sqrt( pow((double)x - X2_, 2) + pow((double)y - Y2_, 2) )); }else{ AA_ = qAbs(dstY*x - dstX*y + uniC) * denominator; } if (AA_>halfWidth) { continue; } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { KoColor mycolor = d->paintColor; if (antialias && AA_ > halfWidth-1.0) { mycolor.colorSpace()->multiplyAlpha(mycolor.data(), 1.0 - (AA_-(halfWidth-1.0)), 1); } compositeOnePixel(accessor->rawData(), mycolor); } } } } /**/ void KisPainter::drawLine(const QPointF & start, const QPointF & end) { drawThickLine(start, end, 1, 1); } void KisPainter::drawDDALine(const QPointF & start, const QPointF & end) { int x = qFloor(start.x()); int y = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); // Width and height of the line int xd = x2 - x; int yd = y2 - y; float m = (float)yd / (float)xd; float fx = x; float fy = y; int inc; KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x, y); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x, y); } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } if (fabs(m) > 1.0f) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { y = y + inc; fx = fx + m; x = qRound(fx); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { x = x + inc; fy = fy + m; y = qRound(fy); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } } void KisPainter::drawWobblyLine(const QPointF & start, const QPointF & end) { KoColor mycolor(d->paintColor); int x1 = qFloor(start.x()); int y1 = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } // Width and height of the line int xd = (x2 - x1); int yd = (y2 - y1); int x; int y; float fx = (x = x1); float fy = (y = y1); float m = (float)yd / (float)xd; int inc; if (fabs(m) > 1) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { fx = fx + m; y = y + inc; x = qRound(fx); float br1 = qFloor(fx + 1) - fx; float br2 = fx - qFloor(fx); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x + 1, y); if (selectionAccessor) selectionAccessor->moveTo(x + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { fy = fy + m; x = x + inc; y = qRound(fy); float br1 = qFloor(fy + 1) - fy; float br2 = fy - qFloor(fy); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x, y + 1); if (selectionAccessor) selectionAccessor->moveTo(x, y + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } } void KisPainter::drawWuLine(const QPointF & start, const QPointF & end) { KoColor lineColor(d->paintColor); int x1 = qFloor(start.x()); int y1 = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } float grad, xd, yd; float xgap, ygap, xend, yend, yf, xf; float brightness1, brightness2; int ix1, ix2, iy1, iy2; quint8 c1, c2; // gradient of line xd = (x2 - x1); yd = (y2 - y1); if (yd == 0) { /* Horizontal line */ int incr = (x1 < x2) ? 1 : -1; ix1 = x1; ix2 = x2; iy1 = y1; while (ix1 != ix2) { ix1 = ix1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (xd == 0) { /* Vertical line */ int incr = (y1 < y2) ? 1 : -1; iy1 = y1; iy2 = y2; ix1 = x1; while (iy1 != iy2) { iy1 = iy1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (fabs(xd) > fabs(yd)) { // horizontal line // line have to be paint from left to right if (x1 > x2) { std::swap(x1, x2); std::swap(y1, y2); xd = (x2 - x1); yd = (y2 - y1); } grad = yd / xd; // nearest X,Y integer coordinates xend = x1; yend = y1 + grad * (xend - x1); xgap = invertFrac(x1 + 0.5f); ix1 = x1; iy1 = qFloor(yend); // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix1, iy1 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop yf = yend + grad; xend = x2; yend = y2 + grad * (xend - x2); xgap = invertFrac(x2 - 0.5f); ix2 = x2; iy2 = qFloor(yend); brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2, iy2 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int x = ix1 + 1; x <= ix2 - 1; x++) { brightness1 = invertFrac(yf); brightness2 = frac(yf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(x, qFloor(yf)); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yf)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x, qFloor(yf) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yf) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } yf = yf + grad; } } else { //vertical // line have to be painted from left to right if (y1 > y2) { std::swap(x1, x2); std::swap(y1, y2); xd = (x2 - x1); yd = (y2 - y1); } grad = xd / yd; // nearest X,Y integer coordinates yend = y1; xend = x1 + grad * (yend - y1); ygap = y1; ix1 = qFloor(xend); iy1 = y1; // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x1 + 1, y1); if (selectionAccessor) selectionAccessor->moveTo(x1 + 1, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop xf = xend + grad; yend = y2; xend = x2 + grad * (yend - y2); ygap = invertFrac(y2 - 0.5f); ix2 = qFloor(xend); iy2 = y2; brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2 + 1, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2 + 1, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int y = iy1 + 1; y <= iy2 - 1; y++) { brightness1 = invertFrac(xf); brightness2 = frac(xf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(qFloor(xf), y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xf), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(qFloor(xf) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xf) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } xf = xf + grad; } }//end-of-else } void KisPainter::drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } const KoColorSpace *cs = d->device->colorSpace(); KoColor c1(d->paintColor); KoColor c2(d->paintColor); KoColor c3(d->paintColor); KoColor col1(c1); KoColor col2(c1); float grada, gradb, dxa, dxb, dya, dyb, fraca, fracb, xfa, yfa, xfb, yfb, b1a, b2a, b1b, b2b, dstX, dstY; int x, y, ix1, ix2, iy1, iy2; int x0a, y0a, x1a, y1a, x0b, y0b, x1b, y1b; int tp0, tn0, tp1, tn1; int horizontal = 0; float opacity = 1.0; tp0 = startWidth / 2; tn0 = startWidth / 2; if (startWidth % 2 == 0) // even width startWidth tn0--; tp1 = endWidth / 2; tn1 = endWidth / 2; if (endWidth % 2 == 0) // even width endWidth tn1--; int x0 = qRound(start.x()); int y0 = qRound(start.y()); int x1 = qRound(end.x()); int y1 = qRound(end.y()); dstX = x1 - x0; // run of general line dstY = y1 - y0; // rise of general line if (dstY < 0) dstY = -dstY; if (dstX < 0) dstX = -dstX; if (dstX > dstY) { // horizontalish horizontal = 1; x0a = x0; y0a = y0 - tn0; x0b = x0; y0b = y0 + tp0; x1a = x1; y1a = y1 - tn1; x1b = x1; y1b = y1 + tp1; } else { x0a = x0 - tn0; y0a = y0; x0b = x0 + tp0; y0b = y0; x1a = x1 - tn1; y1a = y1; x1b = x1 + tp1; y1b = y1; } if (horizontal) { // draw endpoints for (int i = y0a; i <= y0b; i++) { accessor->moveTo(x0, i); if (selectionAccessor) selectionAccessor->moveTo(x0, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = y1a; i <= y1b; i++) { accessor->moveTo(x1, i); if (selectionAccessor) selectionAccessor->moveTo(x1, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } else { for (int i = x0a; i <= x0b; i++) { accessor->moveTo(i, y0); if (selectionAccessor) selectionAccessor->moveTo(i, y0); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = x1a; i <= x1b; i++) { accessor->moveTo(i, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } //antialias endpoints if (x1 != x0 && y1 != y0) { if (horizontal) { accessor->moveTo(x0a, y0a - 1); if (selectionAccessor) selectionAccessor->moveTo(x0a, y0a - 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b, y1b + 1); if (selectionAccessor) selectionAccessor->moveTo(x1b, y1b + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } else { accessor->moveTo(x0a - 1, y0a); if (selectionAccessor) selectionAccessor->moveTo(x0a - 1, y0a); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b + 1, y1b); if (selectionAccessor) selectionAccessor->moveTo(x1b + 1, y1b); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } } dxa = x1a - x0a; // run of a dya = y1a - y0a; // rise of a dxb = x1b - x0b; // run of b dyb = y1b - y0b; // rise of b if (horizontal) { // horizontal-ish lines if (x1 < x0) { int xt, yt, wt; KoColor tmp; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dya / dxa; gradb = dyb / dxb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; yfa = y0a + grada; yfb = y0b + gradb; for (x = ix1 + 1; x <= ix2 - 1; x++) { fraca = yfa - qFloor(yfa); b1a = 1 - fraca; b2a = fraca; fracb = yfb - qFloor(yfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of bottom line opacity = ((x - ix1) / dstX) * c2.opacityF() + (1 - (x - ix1) / dstX) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(x, qFloor(yfa)); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfa)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of top line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, qFloor(yfb)); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfb)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of bottom line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(x, qFloor(yfa) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfa) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of top line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, qFloor(yfb) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfb) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels if (!(startWidth == 1 && endWidth == 1)) { if (yfa < yfb) for (int i = qFloor(yfa) + 1; i <= qFloor(yfb); i++) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = qFloor(yfa) + 1; i >= qFloor(yfb); i--) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } yfa += grada; yfb += gradb; } } else { // vertical-ish lines if (y1 < y0) { int xt, yt, wt; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; KoColor tmp; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dxa / dya; gradb = dxb / dyb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; xfa = x0a + grada; xfb = x0b + gradb; for (y = iy1 + 1; y <= iy2 - 1; y++) { fraca = xfa - qFloor(xfa); b1a = 1 - fraca; b2a = fraca; fracb = xfb - qFloor(xfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of left line opacity = ((y - iy1) / dstY) * c2.opacityF() + (1 - (y - iy1) / dstY) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(qFloor(xfa), y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfa), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of right line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(qFloor(xfb), y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfb), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of left line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(qFloor(xfa) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfa) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of right line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(qFloor(xfb) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfb) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels between current xfa,xfb if (!(startWidth == 1 && endWidth == 1)) { if (xfa < xfb) for (int i = qFloor(xfa) + 1; i <= qFloor(xfb); i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = qFloor(xfb); i <= qFloor(xfa) + 1; i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } xfa += grada; xfb += gradb; } } } void KisPainter::setProgress(KoUpdater * progressUpdater) { d->progressUpdater = progressUpdater; } const KisPaintDeviceSP KisPainter::device() const { return d->device; } KisPaintDeviceSP KisPainter::device() { return d->device; } void KisPainter::setChannelFlags(QBitArray channelFlags) { // Q_ASSERT(channelFlags.isEmpty() || quint32(channelFlags.size()) == d->colorSpace->channelCount()); // Now, if all bits in the channelflags are true, pass an empty channel flags bitarray // because otherwise the compositeops cannot optimize. d->paramInfo.channelFlags = channelFlags; if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { d->paramInfo.channelFlags = QBitArray(); } } QBitArray KisPainter::channelFlags() { return d->paramInfo.channelFlags; } void KisPainter::setPattern(const KoPattern * pattern) { d->pattern = pattern; } const KoPattern * KisPainter::pattern() const { return d->pattern; } void KisPainter::setPaintColor(const KoColor& color) { d->paintColor = color; if (d->device) { d->paintColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::paintColor() const { return d->paintColor; } void KisPainter::setBackgroundColor(const KoColor& color) { d->backgroundColor = color; if (d->device) { d->backgroundColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::backgroundColor() const { return d->backgroundColor; } void KisPainter::setGenerator(KisFilterConfigurationSP generator) { d->generator = generator; } const KisFilterConfigurationSP KisPainter::generator() const { return d->generator; } void KisPainter::setFillStyle(FillStyle fillStyle) { d->fillStyle = fillStyle; } KisPainter::FillStyle KisPainter::fillStyle() const { return d->fillStyle; } void KisPainter::setAntiAliasPolygonFill(bool antiAliasPolygonFill) { d->antiAliasPolygonFill = antiAliasPolygonFill; } bool KisPainter::antiAliasPolygonFill() { return d->antiAliasPolygonFill; } void KisPainter::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { d->strokeStyle = strokeStyle; } KisPainter::StrokeStyle KisPainter::strokeStyle() const { return d->strokeStyle; } void KisPainter::setFlow(quint8 flow) { d->paramInfo.flow = float(flow) / 255.0f; } quint8 KisPainter::flow() const { return quint8(d->paramInfo.flow * 255.0f); } void KisPainter::setOpacityUpdateAverage(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.updateOpacityAndAverage(float(opacity) / 255.0f); } void KisPainter::setAverageOpacity(qreal averageOpacity) { d->paramInfo.setOpacityAndAverage(d->paramInfo.opacity, averageOpacity); } qreal KisPainter::blendAverageOpacity(qreal opacity, qreal averageOpacity) { const float exponent = 0.1; return averageOpacity < opacity ? opacity : exponent * opacity + (1.0 - exponent) * (averageOpacity); } void KisPainter::setOpacity(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.opacity = float(opacity) / 255.0f; } quint8 KisPainter::opacity() const { return quint8(d->paramInfo.opacity * 255.0f); } void KisPainter::setCompositeOp(const KoCompositeOp * op) { d->compositeOp = op; } const KoCompositeOp * KisPainter::compositeOp() { return d->compositeOp; } /** * TODO: Rename this setCompositeOpId(). See KoCompositeOpRegistry.h */ void KisPainter::setCompositeOp(const QString& op) { d->compositeOp = d->colorSpace->compositeOp(op); } void KisPainter::setSelection(KisSelectionSP selection) { d->selection = selection; } KisSelectionSP KisPainter::selection() { return d->selection; } KoUpdater * KisPainter::progressUpdater() { return d->progressUpdater; } void KisPainter::setGradient(const KoAbstractGradient* gradient) { d->gradient = gradient; } const KoAbstractGradient* KisPainter::gradient() const { return d->gradient; } void KisPainter::setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image) { d->paintOpPreset = preset; KisPaintOp *paintop = KisPaintOpRegistry::instance()->paintOp(preset, this, node, image); Q_ASSERT(paintop); if (paintop) { delete d->paintOp; d->paintOp = paintop; } else { warnKrita << "Could not create paintop for preset " << preset->name(); } } KisPaintOpPresetSP KisPainter::preset() const { return d->paintOpPreset; } KisPaintOp* KisPainter::paintOp() const { return d->paintOp; } void KisPainter::setMirrorInformation(const QPointF& axesCenter, bool mirrorHorizontally, bool mirrorVertically) { d->axesCenter = axesCenter; d->mirrorHorizontally = mirrorHorizontally; d->mirrorVertically = mirrorVertically; } void KisPainter::copyMirrorInformationFrom(const KisPainter *other) { d->axesCenter = other->d->axesCenter; d->mirrorHorizontally = other->d->mirrorHorizontally; d->mirrorVertically = other->d->mirrorVertically; } bool KisPainter::hasMirroring() const { return d->mirrorHorizontally || d->mirrorVertically; } bool KisPainter::hasHorizontalMirroring() const { return d->mirrorHorizontally; } bool KisPainter::hasVerticalMirroring() const { return d->mirrorVertically; } void KisPainter::setMaskImageSize(qint32 width, qint32 height) { d->maskImageWidth = qBound(1, width, 256); d->maskImageHeight = qBound(1, height, 256); d->fillPainter = 0; d->polygonMaskImage = QImage(); } //void KisPainter::setLockAlpha(bool protect) //{ // if(d->paramInfo.channelFlags.isEmpty()) { // d->paramInfo.channelFlags = d->colorSpace->channelFlags(true, true); // } // QBitArray switcher = // d->colorSpace->channelFlags(protect, !protect); // if(protect) { // d->paramInfo.channelFlags &= switcher; // } // else { // d->paramInfo.channelFlags |= switcher; // } // Q_ASSERT(quint32(d->paramInfo.channelFlags.size()) == d->colorSpace->channelCount()); //} //bool KisPainter::alphaLocked() const //{ // QBitArray switcher = d->colorSpace->channelFlags(false, true); // return !(d->paramInfo.channelFlags & switcher).count(true); //} void KisPainter::setRenderingIntent(KoColorConversionTransformation::Intent intent) { d->renderingIntent = intent; } void KisPainter::setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags) { d->conversionFlags = conversionFlags; } void KisPainter::setRunnableStrokeJobsInterface(KisRunnableStrokeJobsInterface *interface) { d->runnableStrokeJobsInterface = interface; } KisRunnableStrokeJobsInterface *KisPainter::runnableStrokeJobsInterface() const { if (!d->runnableStrokeJobsInterface) { if (!d->fakeRunnableStrokeJobsInterface) { d->fakeRunnableStrokeJobsInterface.reset(new KisFakeRunnableStrokeJobsExecutor()); } return d->fakeRunnableStrokeJobsInterface.data(); } return d->runnableStrokeJobsInterface; } void KisPainter::renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP dabToProcess = dab; if (preserveDab) { dabToProcess = new KisFixedPaintDevice(*dab); } renderMirrorMask(rc, dabToProcess); } void KisPainter::renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP maskToProcess = mask; if (preserveMask) { maskToProcess = new KisFixedPaintDevice(*mask); } renderMirrorMask(rc, dab, sx, sy, maskToProcess); } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); dab->mirror(false,true); bltFixed(mirrorX, mirrorY, dab, 0,0,rc.width(),rc.height()); dab->mirror(true, false); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorHorizontally){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorVertically){ dab->mirror(false, true); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); dab->mirror(false,true); mask->mirror(false, true); bltFixedWithFixedSelection(mirrorX,mirrorY, dab, mask, rc.width() ,rc.height() ); dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorHorizontally){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorVertically){ dab->mirror(false, true); mask->mirror(false, true); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab){ if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->lazyGrowBufferWithoutInitialization(); dab->readBytes(mirrorDab->data(),rc); renderMirrorMask( QRect(rc.topLeft(),dabRc.size()), mirrorDab); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask) { if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->lazyGrowBufferWithoutInitialization(); dab->readBytes(mirrorDab->data(),QRect(QPoint(sx,sy),rc.size())); renderMirrorMask(rc, mirrorDab, mask); } } void KisPainter::renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab) { QVector rects; int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); rects << rc; if (d->mirrorHorizontally && d->mirrorVertically){ rects << QRect(mirrorX, y, rc.width(), rc.height()); rects << QRect(mirrorX, mirrorY, rc.width(), rc.height()); rects << QRect(x, mirrorY, rc.width(), rc.height()); } else if (d->mirrorHorizontally) { rects << QRect(mirrorX, y, rc.width(), rc.height()); } else if (d->mirrorVertically) { rects << QRect(x, mirrorY, rc.width(), rc.height()); } Q_FOREACH (const QRect &rc, rects) { d->device->clear(rc); } QRect resultRect = dab->extent() | rc; bool intersects = false; for (int i = 1; i < rects.size(); i++) { if (rects[i].intersects(resultRect)) { intersects = true; break; } } /** * If there are no cross-intersections, we can use a fast path * and do no cycling recompositioning */ if (!intersects) { rects.resize(1); } Q_FOREACH (const QRect &rc, rects) { bitBlt(rc.topLeft(), dab, rc); } Q_FOREACH (const QRect &rc, rects) { renderMirrorMask(rc, dab); } } bool KisPainter::hasDirtyRegion() const { return !d->dirtyRects.isEmpty(); } void KisPainter::mirrorRect(Qt::Orientation direction, QRect *rc) const { KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); KritaUtils::mirrorRect(direction, effectiveAxesCenter, rc); } void KisPainter::mirrorDab(Qt::Orientation direction, KisRenderedDab *dab) const { KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); KritaUtils::mirrorDab(direction, effectiveAxesCenter, dab); } namespace { inline void mirrorOneObject(Qt::Orientation dir, const QPoint ¢er, QRect *rc) { KritaUtils::mirrorRect(dir, center, rc); } inline void mirrorOneObject(Qt::Orientation dir, const QPoint ¢er, QPointF *pt) { KritaUtils::mirrorPoint(dir, center, pt); } inline void mirrorOneObject(Qt::Orientation dir, const QPoint ¢er, QPair *pair) { KritaUtils::mirrorPoint(dir, center, &pair->first); KritaUtils::mirrorPoint(dir, center, &pair->second); } } template QVector KisPainter::Private::calculateMirroredObjects(const T &object) { QVector result; KisLodTransform t(this->device); const QPoint effectiveAxesCenter = t.map(this->axesCenter).toPoint(); T baseObject = object; result << baseObject; if (this->mirrorHorizontally && this->mirrorVertically){ mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject); result << baseObject; mirrorOneObject(Qt::Vertical, effectiveAxesCenter, &baseObject); result << baseObject; mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject); result << baseObject; } else if (this->mirrorHorizontally) { mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject); result << baseObject; } else if (this->mirrorVertically) { mirrorOneObject(Qt::Vertical, effectiveAxesCenter, &baseObject); result << baseObject; } return result; } const QVector KisPainter::calculateAllMirroredRects(const QRect &rc) { return d->calculateMirroredObjects(rc); } const QVector KisPainter::calculateAllMirroredPoints(const QPointF &pos) { return d->calculateMirroredObjects(pos); } const QVector> KisPainter::calculateAllMirroredPoints(const QPair &pair) { return d->calculateMirroredObjects(pair); } diff --git a/libs/image/kis_painter.h b/libs/image/kis_painter.h index 3c1b13ae2b..2c9b2e4ef5 100644 --- a/libs/image/kis_painter.h +++ b/libs/image/kis_painter.h @@ -1,894 +1,892 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Clarence Dang * Copyright (c) 2008-2010 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara Toloza * * 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_PAINTER_H_ #define KIS_PAINTER_H_ #include #include #include #include #include "kundo2magicstring.h" #include "kis_types.h" #include #include class QPen; class KUndo2Command; class QRect; class QRectF; class QBitArray; class QPainterPath; class KoAbstractGradient; class KoUpdater; class KoColor; class KoCompositeOp; class KisUndoAdapter; class KisPostExecutionUndoAdapter; class KisTransaction; class KoPattern; class KisPaintInformation; class KisPaintOp; class KisDistanceInformation; struct KisRenderedDab; class KisRunnableStrokeJobsInterface; /** * KisPainter contains the graphics primitives necessary to draw on a * KisPaintDevice. This is the same kind of abstraction as used in Qt * itself, where you have QPainter and QPaintDevice. * * However, KisPainter works on a tiled image and supports different * color models, and that's a lot more complicated. * * KisPainter supports transactions that can group various paint operations * in one undoable step. * * For more complex operations, you might want to have a look at the subclasses * of KisPainter: KisConvolutionPainter, KisFillPainter and KisGradientPainter * * KisPainter sets a number of default values, like COMPOSITE_OVER for compositeop, * OPACITY_OPAQUE for opacity and no selection for selection. */ class KRITAIMAGE_EXPORT KisPainter { public: /// Construct painter without a device KisPainter(); /// Construct a painter, and begin painting on the device KisPainter(KisPaintDeviceSP device); /// Construct a painter, and begin painting on the device. All actions will be masked by the given selection. KisPainter(KisPaintDeviceSP device, KisSelectionSP selection); virtual ~KisPainter(); public: static void copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect); static void copyAreaOptimizedOldData(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect); static void copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect, KisSelectionSP selection); static KisPaintDeviceSP convertToAlphaAsAlpha(KisPaintDeviceSP src); static KisPaintDeviceSP convertToAlphaAsGray(KisPaintDeviceSP src); static bool checkDeviceHasTransparency(KisPaintDeviceSP dev); /** * Start painting on the specified device. Not undoable. */ void begin(KisPaintDeviceSP device); /** * Start painting on the specified paint device. All actions will be masked by the given selection. */ void begin(KisPaintDeviceSP device, KisSelectionSP selection); /** * Finish painting on the current device */ void end(); /** * If set, the painter action is cancelable, if the action supports that. */ void setProgress(KoUpdater * progressUpdater); /// Begin an undoable paint operation void beginTransaction(const KUndo2MagicString& transactionName = KUndo2MagicString(),int timedID = -1); /// Cancel all the changes made by the painter void revertTransaction(); /// Finish the undoable paint operation void endTransaction(KisUndoAdapter *undoAdapter); /** * Finish transaction and load it to a special adapter for strokes */ void endTransaction(KisPostExecutionUndoAdapter *undoAdapter); /** * Finishes a transaction and returns a pointer to its undo command */ KUndo2Command* endAndTakeTransaction(); /** * Finish the transaction and delete it's undo information. * NOTE: Be careful, because all the previous transactions * will become non-undoable after execution of this method. */ void deleteTransaction(); /// continue a transaction started somewhere else void putTransaction(KisTransaction* transaction); /// take transaction out of the reach of KisPainter KisTransaction* takeTransaction(); /// Returns the current paint device. const KisPaintDeviceSP device() const; KisPaintDeviceSP device(); /** * Blast a region of srcWidth @param srcWidth and srcHeight @param srcHeight from @param * srcDev onto the current paint device. @param srcX and @param srcY set the x and y * positions of the origin top-left corner, @param dstX and @param dstY those of * the destination. * Any pixel read outside the limits of @param srcDev will return the * default pixel, this is a property of \ref KisPaintDevice. * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param srcX the source x-coordinate * @param srcY the source y-coordinate * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated */ void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight); /** * Convenience method that uses QPoint and QRect. * * @param pos the destination coordinate, it replaces @p dstX and @p dstY. * @param srcDev the source device. * @param srcRect the rectangle describing the area to blast from @p srcDev into the current paint device. * @p srcRect replaces @p srcX, @p srcY, @p srcWidth and @p srcHeight. * */ void bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect); /** * The same as @ref bitBlt() but reads data from oldData() part of the device * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param srcX the source x-coordinate * @param srcY the source y-coordinate * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated */ void bitBltOldData(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight); /** * Convenience method that uses QPoint and QRect. * * @param pos the destination coordinate, it replaces @p dstX and @p dstY. * @param srcDev the source device. * @param srcRect the rectangle describing the area to blast from @param srcDev into the current paint device. * @p srcRect replaces @p srcX, @p srcY, @p srcWidth and @p srcHeight. * */ void bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect); /** * Blasts a @param selection of srcWidth @param srcWidth and srcHeight @param srcHeight * of @param srcDev on the current paint device. There is parameters * to control where the area begins in each distinct device, explained below. * @param selection can be used as a mask to shape @param srcDev to * something interesting in the same step it is rendered to the current * paint device. @param selection 's colorspace must be alpha8 (the * colorspace for selections/transparency), the rectangle formed by * @param selX, @param selY, @param srcWidth and @param srcHeight must not go * beyond its limits, and they must be different from zero. * @param selection and KisPainter's selection (the user selection) are * fused together through the composite operation COMPOSITE_MULT. * Any pixel read outside the limits of @param srcDev will return the * default pixel, this is a property of \ref KisPaintDevice. * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param selection the custom selection to apply on the source device * @param selX the selection x-coordinate * @param selY the selection y-coordinate * @param srcX the source x-coordinate * @param srcY the source y-coordinate * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated * */ void bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight); /** * Convenience method that assumes @p selX, @p selY, @p srcX and @p srcY are * equal to 0. Best used when @p selection and the desired area of @p srcDev have exactly * the same dimensions and are specially made for each other. * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param selection the custom selection to apply on the source device * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated */ void bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 srcWidth, qint32 srcHeight); /** * Blast a region of srcWidth @p srcWidth and srcHeight @p srcHeight from @p srcDev onto the current * paint device. @p srcX and @p srcY set the x and y positions of the * origin top-left corner, @p dstX and @p dstY those of the destination. * @p srcDev is a @ref KisFixedPaintDevice : this means that @p srcDev must have the same * colorspace as the destination device. * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param srcX the source x-coordinate * @param srcY the source y-coordinate * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated */ void bltFixed(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight); /** * Render the area \p rc from \p srcDevices on the destination device. * If \p rc doesn't cross the device's rect, then the device is not * rendered at all. */ void bltFixed(const QRect &rc, const QList allSrcDevices); /** * Convenience method that uses QPoint and QRect. * * @param pos the destination coordinate, it replaces @p dstX and @p dstY. * @param srcDev the source device. * @param srcRect the rectangle describing the area to blast from @p srcDev into the current paint device. * @param srcRect replaces @p srcX, @p srcY, @p srcWidth and @p srcHeight. * */ void bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect); /** * Blasts a @p selection of srcWidth @p srcWidth and srcHeight @p srcHeight * of @p srcDev on the current paint device. There is parameters to control * the top-left corner of the area in each respective paint device (@p dstX, * @p dstY, @p srcX, @p srcY). * @p selection can be used as a mask to shape @p srcDev to something * interesting in the same step it is rendered to the current paint device. * @p srcDev is a @ref KisFixedPaintDevice : this means that @p srcDev * must have the same colorspace as the destination device. * @p selection 's colorspace must be alpha8 (the colorspace for * selections/transparency). * The rectangle formed by the respective top-left coordinates of each device * and @p srcWidth and @p srcHeight must not go beyond their limits, and * they must be different from zero. * @p selection and KisPainter's selection (the user selection) are * fused together through the composite operation COMPOSITE_MULT. * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param selection the selection stored in fixed device * @param selX the selection x-coordinate * @param selY the selection y-coordinate * @param srcX the source x-coordinate * @param srcY the source y-coordinate * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated */ void bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, quint32 srcWidth, quint32 srcHeight); /** * Convenience method that assumes @p selX, @p selY, @p srcX and @p srcY are * equal to 0. Best used when @p selection and @p srcDev have exactly the same * dimensions and are specially made for each other. * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param selection the custom selection to apply on the source device * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated */ void bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, quint32 srcWidth, quint32 srcHeight); /** * fills a region of width @p width and height @p height of the current * paint device with the color @p color. @p x and @p y set the x and y positions of the * origin top-left corner. * * @param x the destination x-coordinate * @param y the destination y-coordinate * @param width the width of the region to be manipulated * @param height the height of the region to be manipulated * @param color the color the area is filled with */ void fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color); /** * First you need to setup the painter with setMirrorInformation, * then these set of methods provide way to render the devices mirrored * according the axesCenter vertically or horizontally or both. * * @param rc rectangle area covered by dab * @param dab this device will be mirrored in-place, it means that it will be changed */ void renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab); void renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask); void renderMirrorMask(QRect rc, KisPaintDeviceSP dab); void renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask); /** * Convenience method for renderMirrorMask(), allows to choose whether * we need to preserve out dab or do the transformations in-place. * * @param rc rectangle area covered by dab * @param dab the device to render * @param preserveDab states whether a temporary device should be * created to do the transformations */ void renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab); /** * Convenience method for renderMirrorMask(), allows to choose whether * we need to preserve our fixed mask or do the transformations in-place. * * @param rc rectangular area covered by dab * @param dab the device to render * @param sx x coordinate of the top left corner of the area * @param sy y coordinate of the top left corner of the area * @param mask mask to use for rendering * @param preserveMask states whether a temporary device should be * created to do the transformations */ void renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask); /** * A complex method that re-renders a dab on an \p rc area. * The \p rc area and all the dedicated mirroring areas are cleared * before the painting, so this method should be used by paintops * which do not update the canvas incrementally, but instead * regenerate some internal cache \p dab with the COMPOSITE_COPY op. * * \see KisExperimentPaintOp */ void renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab); /** * @return true if the painter has some rects marked as dirty * @see takeDirtyRegion(), addDirtyRect() */ bool hasDirtyRegion() const; /** * The methods in this class do not tell the paintdevice to update, but they calculate the * dirty area. This method returns this dirty area and resets it. */ QVector takeDirtyRegion(); /** * Paint a line that connects the dots in points */ void paintPolyline(const QVector &points, int index = 0, int numPoints = -1); /** * Draw a line between pos1 and pos2 using the currently set brush and color. * If savedDist is less than zero, the brush is painted at pos1 before being * painted along the line using the spacing setting. * @return the drag distance, that is the remains of the distance between p1 and p2 not covered * because the currently set brush has a spacing greater than that distance. */ void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance); /** * Draw a Bezier curve between @p pi1 and @p pi2 using control points @p control1 and @p control2. * If savedDist is less than zero, the brush is painted at pos1 before being * painted along the curve using the spacing setting. * @return the drag distance, that is the remains of the distance between @p pi1 and @p pi2 not covered * because the currently set brush has a spacing greater than that distance. */ void paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance); /** * Fill the given vector points with the points needed to draw the Bezier curve between * @p pos1 and @p pos2 using control points @p control1 and @p control2, excluding the final pos2. */ void getBezierCurvePoints(const QPointF &pos1, const QPointF &control1, const QPointF &control2, const QPointF &pos2, vQPointF& points) const; /** * Paint a rectangle. * @param rect the rectangle to paint. */ void paintRect(const QRectF &rect); /** * Paint a rectangle. * * @param x x coordinate of the top-left corner * @param y y coordinate of the top-left corner * @param w the rectangle width * @param h the rectangle height */ void paintRect(const qreal x, const qreal y, const qreal w, const qreal h); /** * Paint the ellipse that fills the given rectangle. * * @param rect the rectangle containing the ellipse to paint. */ void paintEllipse(const QRectF &rect); /** * Paint the ellipse that fills the given rectangle. * * @param x x coordinate of the top-left corner * @param y y coordinate of the top-left corner * @param w the rectangle width * @param h the rectangle height */ void paintEllipse(const qreal x, const qreal y, const qreal w, const qreal h); /** * Paint the polygon with the points given in points. It automatically closes the polygon * by drawing the line from the last point to the first. */ void paintPolygon(const vQPointF& points); /** Draw a spot at pos using the currently set paint op, brush and color */ void paintAt(const KisPaintInformation &pos, KisDistanceInformation *savedDist); /** * Stroke the given QPainterPath. */ void paintPainterPath(const QPainterPath& path); /** * Fills the area enclosed by the given QPainterPath * Convenience method for fillPainterPath(path, rect) */ void fillPainterPath(const QPainterPath& path); /** * Fills the portion of an area enclosed by the given QPainterPath * * \param path the portion of the path to fill * \param requestedRect the rectangle containing the area */ void fillPainterPath(const QPainterPath& path, const QRect &requestedRect); /** * Draw the path using the Pen * * if \p requestedRect is null, the entire path is painted */ void drawPainterPath(const QPainterPath& path, const QPen& pen, const QRect &requestedRect); // convenience overload void drawPainterPath(const QPainterPath& path, const QPen& pen); /** * paint an unstroked one-pixel wide line from specified start position to the * specified end position. * */ void drawLine(const QPointF & start, const QPointF & end); /** * paint an unstroked line with thickness from specified start position to the * specified end position. Scanline algorithm is used. */ void drawLine(const QPointF &start, const QPointF &end, qreal width, bool antialias); /** * paints an unstroked, aliased one-pixel line using the DDA algorithm from specified start position to the * specified end position. * */ void drawDDALine(const QPointF & start, const QPointF & end); /** * Paint an unstroked, wobbly one-pixel wide line from the specified start to the specified * end position. * */ void drawWobblyLine(const QPointF & start, const QPointF & end); /** * Paint an unstroked, anti-aliased one-pixel wide line from the specified start to the specified * end position using the Wu algorithm */ void drawWuLine(const QPointF & start, const QPointF & end); /** * Paint an unstroked wide line from the specified start to the specified * end position with width varying from @p start at the start to @p end at * the end. * * XXX: the width should be set in doubles, not integers. */ void drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth); /** * Set the channelflags: a bit array where true means that the * channel corresponding in position with the bit will be read * by the operation, and false means that it will not be affected. * * An empty channelFlags parameter means that all channels are * affected. * * @param channelFlags the bit array that masks the source channels; only * the channels where the corresponding bit is true will will be * composited onto the destination device. */ void setChannelFlags(QBitArray channelFlags); /// @return the channel flags QBitArray channelFlags(); /** * Set the paintop preset to use. If @p image is given, * the paintop will be created using this image as parameter. * Some paintops really want to know about the image they work * for, e.g. the clone paintop. */ void setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image); /// Return the paintop preset KisPaintOpPresetSP preset() const; /** * Return the active paintop (which is created based on the specified preset and * will be deleted as soon as the KisPainter instance dies). */ KisPaintOp* paintOp() const; void setMirrorInformation(const QPointF &axesCenter, bool mirrorHorizontally, bool mirrorVertically); void copyMirrorInformationFrom(const KisPainter *other); /** * Returns whether the mirroring methods will do any * work when called */ bool hasMirroring() const; /** * Indicates if horizontal mirroring mode is activated */ bool hasHorizontalMirroring() const; /** * Indicates if vertical mirroring mode is activated */ bool hasVerticalMirroring() const; /** * Mirror \p rc in the requested \p direction around the center point defined * in the painter. */ void mirrorRect(Qt::Orientation direction, QRect *rc) const; /** * Mirror \p dab in the requested direction around the center point defined * in the painter. The dab's offset is adjusted automatically. */ void mirrorDab(Qt::Orientation direction, KisRenderedDab *dab) const; /** * Calculate the list of the mirrored rects that will be painted on the * the canvas when calling renderMirrorMask() at al */ const QVector calculateAllMirroredRects(const QRect &rc); /** * Calculate the list of the mirrored points according to the current * mirroring configuration. */ const QVector calculateAllMirroredPoints(const QPointF &pos); /** * Calculate the list of the mirrored point pairs according to the current * mirroring configuration. */ const QVector> calculateAllMirroredPoints(const QPair &pair); /// Set the current pattern void setPattern(const KoPattern * pattern); /// Returns the currently set pattern const KoPattern * pattern() const; /** * Set the color that will be used to paint with, and convert it * to the color space of the current paint device. */ void setPaintColor(const KoColor& color); /// Returns the color that will be used to paint with const KoColor &paintColor() const; /** * Set the current background color, and convert it * to the color space of the current paint device. */ void setBackgroundColor(const KoColor& color); /// Returns the current background color const KoColor &backgroundColor() const; /// Set the current generator (a generator can be used to fill an area void setGenerator(KisFilterConfigurationSP generator); /// @return the current generator configuration const KisFilterConfigurationSP generator() const; /// This enum contains the styles with which we can fill things like polygons and ellipses enum FillStyle { FillStyleNone, FillStyleForegroundColor, FillStyleBackgroundColor, FillStylePattern, - FillStyleGradient, - FillStyleStrokes, - FillStyleGenerator, + FillStyleGenerator }; /// Set the current style with which to fill void setFillStyle(FillStyle fillStyle); /// Returns the current fill style FillStyle fillStyle() const; /// Set whether a polygon's filled area should be anti-aliased or not. The default is true. void setAntiAliasPolygonFill(bool antiAliasPolygonFill); /// Return whether a polygon's filled area should be anti-aliased or not bool antiAliasPolygonFill(); /// The style of the brush stroke around polygons and so enum StrokeStyle { StrokeStyleNone, StrokeStyleBrush }; /// Set the current brush stroke style void setStrokeStyle(StrokeStyle strokeStyle); /// Returns the current brush stroke style StrokeStyle strokeStyle() const; void setFlow(quint8 flow); quint8 flow() const; /** * Sets the opacity of the painting and recalculates the * mean opacity of the stroke. This mean value is used to * make ALPHA_DARKEN painting look correct */ void setOpacityUpdateAverage(quint8 opacity); /** * Sets average opacity, that is used to make ALPHA_DARKEN painting look correct */ void setAverageOpacity(qreal averageOpacity); /** * Calculate average opacity value after painting a single dab with \p opacity */ static qreal blendAverageOpacity(qreal opacity, qreal averageOpacity); /// Set the opacity which is used in painting (like filling polygons) void setOpacity(quint8 opacity); /// Returns the opacity that is used in painting quint8 opacity() const; /// Set the composite op for this painter void setCompositeOp(const KoCompositeOp * op); const KoCompositeOp * compositeOp(); /// Set the composite op for this painter by string. /// Note: the colorspace must be set previously! void setCompositeOp(const QString& op); /** * Add \p r to the current set of dirty rects */ void addDirtyRect(const QRect &r); /** * Add \p rects to the current set of dirty rects */ void addDirtyRects(const QVector &rects); /** * Reset the selection to the given selection. All painter actions will be * masked by the specified selection. */ void setSelection(KisSelectionSP selection); /** * @return the selection set on this painter. */ KisSelectionSP selection(); void setGradient(const KoAbstractGradient* gradient); const KoAbstractGradient* gradient() const; /** * Set the size of the tile in fillPainterPath, useful when optimizing the use of fillPainterPath * e.g. Spray paintop uses more small tiles, although selections uses bigger tiles. QImage::fill * is quite expensive so with smaller images you can save instructions * Default and maximum size is 256x256 image */ void setMaskImageSize(qint32 width, qint32 height); // /** // * If the alpha channel is locked, the alpha values of the paint device we are painting on // * will not change. // */ // void setLockAlpha(bool protect); // bool alphaLocked() const; /** * set the rendering intent in case pixels need to be converted before painting */ void setRenderingIntent(KoColorConversionTransformation::Intent intent); /** * set the conversion flags in case pixels need to be converted before painting */ void setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Set interface for running asynchronous jobs by paintops. * * NOTE: the painter does *not* own the interface device. It is the responsibility * of the caller to ensure that the interface object is alive during the lifetime * of the painter. */ void setRunnableStrokeJobsInterface(KisRunnableStrokeJobsInterface *interface); /** * Get the interface for running asynchronous jobs. It is used by paintops mostly. */ KisRunnableStrokeJobsInterface* runnableStrokeJobsInterface() const; protected: /// Initialize, set everything to '0' or defaults void init(); /// Fill the polygon defined by points with the fillStyle void fillPolygon(const vQPointF& points, FillStyle fillStyle); private: KisPainter(const KisPainter&); KisPainter& operator=(const KisPainter&); float frac(float value) { float tmp = 0; return modff(value , &tmp); } float invertFrac(float value) { float tmp = 0; return 1.0f - modff(value , &tmp); } protected: KoUpdater * progressUpdater(); private: template void bitBltImpl(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight); inline void compositeOnePixel(quint8 *dst, const KoColor &color); private: struct Private; Private* const d; }; #endif // KIS_PAINTER_H_ diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp index 9fc01734c2..fd13a06e9e 100644 --- a/libs/ui/actions/kis_selection_action_factories.cpp +++ b/libs/ui/actions/kis_selection_action_factories.cpp @@ -1,621 +1,621 @@ /* * Copyright (c) 2012 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_selection_action_factories.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_pixel_selection.h" #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_fill_painter.h" #include "kis_transaction.h" #include "kis_iterator_ng.h" #include "kis_processing_applicator.h" #include "kis_group_layer.h" #include "commands/kis_selection_commands.h" #include "commands/kis_image_layer_add_command.h" #include "kis_tool_proxy.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_selection_manager.h" #include "commands_new/kis_transaction_based_command.h" #include "kis_selection_filters.h" #include "kis_shape_selection.h" #include "kis_shape_layer.h" #include #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_keyframe_channel.h" #include #include #include "kis_figure_painting_tool_helper.h" #include "kis_update_outline_job.h" namespace ActionHelper { void copyFromDevice(KisViewManager *view, KisPaintDeviceSP device, bool makeSharpClip = false, const KisTimeRange &range = KisTimeRange()) { KisImageWSP image = view->image(); if (!image) return; KisSelectionSP selection = view->selection(); QRect rc = (selection) ? selection->selectedExactRect() : image->bounds(); KisPaintDeviceSP clip = new KisPaintDevice(device->colorSpace()); Q_CHECK_PTR(clip); const KoColorSpace *cs = clip->colorSpace(); // TODO if the source is linked... copy from all linked layers?!? // Copy image data KisPainter::copyAreaOptimized(QPoint(), device, clip, rc); if (selection) { // Apply selection mask. KisPaintDeviceSP selectionProjection = selection->projection(); KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width()); KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); const KoColorSpace *selCs = selection->projection()->colorSpace(); for (qint32 y = 0; y < rc.height(); y++) { for (qint32 x = 0; x < rc.width(); x++) { /** * Sharp method is an exact reverse of COMPOSITE_OVER * so if you cover the cut/copied piece over its source * you get an exactly the same image without any seams */ if (makeSharpClip) { qreal dstAlpha = cs->opacityF(layerIt->rawData()); qreal sel = selCs->opacityF(selectionIt->oldRawData()); qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha); float mask = newAlpha / dstAlpha; cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1); } else { cs->applyAlphaU8Mask(layerIt->rawData(), selectionIt->oldRawData(), 1); } layerIt->nextPixel(); selectionIt->nextPixel(); } layerIt->nextRow(); selectionIt->nextRow(); } } KisClipboard::instance()->setClip(clip, rc.topLeft(), range); } } void KisSelectAllActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Select All")); if (!image->globalSelection()) { ap->applyCommand(new KisSetEmptyGlobalSelectionCommand(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } struct SelectAll : public KisTransactionBasedCommand { SelectAll(KisImageSP image) : m_image(image) {} KisImageSP m_image; KUndo2Command* paint() override { KisSelectionSP selection = m_image->globalSelection(); KisSelectionTransaction transaction(selection->pixelSelection()); selection->pixelSelection()->clear(); selection->pixelSelection()->select(m_image->bounds()); return transaction.endAndTake(); } }; ap->applyCommand(new SelectAll(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisDeselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisDeselectActiveSelectionCommand(view->selection(), image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisReselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisReselectActiveSelectionCommand(view->activeNode(), image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisFillActionFactory::run(const QString &fillSource, KisViewManager *view) { KisNodeSP node = view->activeNode(); if (!node || !node->hasEditablePaintDevice()) return; KisSelectionSP selection = view->selection(); QRect selectedRect = selection ? selection->selectedRect() : view->image()->bounds(); Q_UNUSED(selectedRect); KisPaintDeviceSP filled = node->paintDevice()->createCompositionSourceDevice(); Q_UNUSED(filled); bool usePattern = false; bool useBgColor = false; if (fillSource.contains("pattern")) { usePattern = true; } else if (fillSource.contains("bg")) { useBgColor = true; } KisProcessingApplicator applicator(view->image(), node, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Flood Fill Layer")); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(view->image(), node, view->canvasResourceProvider()->resourceManager()); if (!fillSource.contains("opacity")) { resources->setOpacity(1.0); } KisProcessingVisitorSP visitor = new FillProcessingVisitor(QPoint(0, 0), // start position selection, resources, false, // fast mode usePattern, true, // fill only selection, 0, // feathering radius 0, // sizemod 80, // threshold, false, // unmerged useBgColor); applicator.applyVisitor(visitor, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); view->canvasResourceProvider()->slotPainting(); } void KisClearActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Clear action" view->canvasBase()->toolProxy()->deleteSelection(); } void KisImageResizeToSelectionActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Image Resize To Selection action" KisSelectionSP selection = view->selection(); if (!selection) return; view->image()->cropImage(selection->selectedExactRect()); } void KisCutCopyActionFactory::run(bool willCut, bool makeSharpClip, KisViewManager *view) { KisImageSP image = view->image(); if (!image) return; bool haveShapesSelected = view->selectionManager()->haveShapesSelected(); if (haveShapesSelected) { // XXX: "Add saving of XML data for Cut/Copy of shapes" KisImageBarrierLocker locker(image); if (willCut) { view->canvasBase()->toolProxy()->cut(); } else { view->canvasBase()->toolProxy()->copy(); } } else { KisNodeSP node = view->activeNode(); if (!node) return; KisSelectionSP selection = view->selection(); if (selection.isNull()) return; { KisImageBarrierLocker locker(image); KisPaintDeviceSP dev = node->paintDevice(); if (!dev) { dev = node->projection(); } if (!dev) { view->showFloatingMessage( i18nc("floating message when cannot copy from a node", "Cannot copy pixels from this type of layer "), QIcon(), 3000, KisFloatingMessage::Medium); return; } if (dev->exactBounds().isEmpty()) { view->showFloatingMessage( i18nc("floating message when copying empty selection", "Selection is empty: no pixels were copied "), QIcon(), 3000, KisFloatingMessage::Medium); return; } KisTimeRange range; KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (channel) { const int currentTime = image->animationInterface()->currentTime(); range = channel->affectedFrames(currentTime); } ActionHelper::copyFromDevice(view, dev, makeSharpClip, range); } KUndo2Command *command = 0; if (willCut && node->hasEditablePaintDevice()) { struct ClearSelection : public KisTransactionBasedCommand { ClearSelection(KisNodeSP node, KisSelectionSP sel) : m_node(node), m_sel(sel) {} KisNodeSP m_node; KisSelectionSP m_sel; KUndo2Command* paint() override { KisSelectionSP cutSelection = m_sel; // Shrinking the cutting area was previously used // for getting seamless cut-paste. Now we use makeSharpClip // instead. // QRect originalRect = cutSelection->selectedExactRect(); // static const int preciseSelectionThreshold = 16; // // if (originalRect.width() > preciseSelectionThreshold || // originalRect.height() > preciseSelectionThreshold) { // cutSelection = new KisSelection(*m_sel); // delete cutSelection->flatten(); // // KisSelectionFilter* filter = new KisShrinkSelectionFilter(1, 1, false); // // QRect processingRect = filter->changeRect(originalRect); // filter->process(cutSelection->pixelSelection(), processingRect); // } KisTransaction transaction(m_node->paintDevice()); m_node->paintDevice()->clearSelection(cutSelection); m_node->setDirty(cutSelection->selectedRect()); return transaction.endAndTake(); } }; command = new ClearSelection(node, selection); } KUndo2MagicString actionName = willCut ? kundo2_i18n("Cut") : kundo2_i18n("Copy"); KisProcessingApplicator *ap = beginAction(view, actionName); if (command) { ap->applyCommand(command, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } KisOperationConfiguration config(id()); config.setProperty("will-cut", willCut); endAction(ap, config.toXML()); } } void KisCopyMergedActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; if (!view->blockUntilOperationsFinished(image)) return; image->barrierLock(); KisPaintDeviceSP dev = image->root()->projection(); ActionHelper::copyFromDevice(view, dev); image->unlock(); KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Copy Merged")); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisInvertSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { KisSelectionFilter* filter = new KisInvertSelectionFilter(); runFilter(filter, view, config); } void KisSelectionToVectorActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (selection->hasShapeSelection()) { view->showFloatingMessage(i18nc("floating message", "Selection is already in a vector format "), QIcon(), 2000, KisFloatingMessage::Low); return; } if (!selection->outlineCacheValid()) { view->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, false, Qt::transparent)); if (!view->blockUntilOperationsFinished(view->image())) { return; } } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); /** * Mark a shape that it belongs to a shape selection */ if(!shape->userData()) { shape->setUserData(new KisShapeSelectionMarker); } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisSelectionToRasterActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->hasShapeSelection()) { view->showFloatingMessage(i18nc("floating message", "Selection is already in a raster format "), QIcon(), 2000, KisFloatingMessage::Low); return; } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); struct RasterizeSelection : public KisTransactionBasedCommand { RasterizeSelection(KisSelectionSP sel) : m_sel(sel) {} KisSelectionSP m_sel; KUndo2Command* paint() override { // just create an empty transaction: it will rasterize the // selection and emit the necessary signals KisTransaction transaction(m_sel->pixelSelection()); return transaction.endAndTake(); } }; ap->applyCommand(new RasterizeSelection(selection), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisShapesToVectorSelectionActionFactory::run(KisViewManager* view) { const QList originalShapes = view->canvasBase()->shapeManager()->selection()->selectedShapes(); bool hasSelectionShapes = false; QList clonedShapes; Q_FOREACH (KoShape *shape, originalShapes) { if (dynamic_cast(shape->userData())) { hasSelectionShapes = true; continue; } clonedShapes << shape->cloneShape(); } if (clonedShapes.isEmpty()) { if (hasSelectionShapes) { view->showFloatingMessage(i18nc("floating message", "The shape already belongs to a selection"), QIcon(), 2000, KisFloatingMessage::Low); } return; } KisSelectionToolHelper helper(view->canvasBase(), kundo2_i18n("Convert shapes to vector selection")); helper.addSelectionShapes(clonedShapes); } void KisSelectionToShapeActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->outlineCacheValid()) { return; } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); KoColor fgColor = view->canvasBase()->resourceManager()->resource(KoCanvasResourceProvider::ForegroundColor).value(); KoShapeStrokeSP border(new KoShapeStroke(1.0, fgColor.toQColor())); shape->setStroke(border); view->document()->shapeController()->addShape(shape); } void KisStrokeSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } int size = params.lineSize; KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } QPainterPath outline = pixelSelection->outlineCache(); QColor color = params.color.toQColor(); KisNodeSP currentNode = view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->paintDevice()) { KoCanvasResourceProvider * rManager = view->canvasResourceProvider()->resourceManager(); - KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; - KisPainter::FillStyle fillStyle = params.fillStyle(); + KisToolShapeUtils::StrokeStyle strokeStyle = KisToolShapeUtils::StrokeStyleForeground; + KisToolShapeUtils::FillStyle fillStyle = params.fillStyle(); KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager , strokeStyle, fillStyle); helper.setFGColorOverride(params.color); helper.setSelectionOverride(0); QPen pen(Qt::red, size); pen.setJoinStyle(Qt::RoundJoin); if (fillStyle != KisPainter::FillStyleNone) { helper.paintPainterPathQPenFill(outline, pen, params.fillColor); } else { helper.paintPainterPathQPen(outline, pen, params.fillColor); } } else if (currentNode->inherits("KisShapeLayer")) { QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(outline)); shape->setShapeId(KoPathShapeId); KoShapeStrokeSP border(new KoShapeStroke(size, color)); shape->setStroke(border); view->document()->shapeController()->addShape(shape); } image->setModified(); } void KisStrokeBrushSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } KisNodeSP currentNode = view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->paintDevice()) { KoCanvasResourceProvider * rManager = view->canvasResourceProvider()->resourceManager(); QPainterPath outline = pixelSelection->outlineCache(); - KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; - KisPainter::FillStyle fillStyle = KisPainter::FillStyleNone; + KisToolShapeUtils::StrokeStyle strokeStyle = KisToolShapeUtils::StrokeStyleForeground; + KisToolShapeUtils::FillStyle fillStyle = KisToolShapeUtils::FillStyleNone; KoColor color = params.color; KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager, strokeStyle, fillStyle); helper.setFGColorOverride(color); helper.setSelectionOverride(0); helper.paintPainterPath(outline); image->setModified(); } } diff --git a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp index 88f71cf267..289ab3d268 100644 --- a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp +++ b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp @@ -1,395 +1,395 @@ /* * Copyright (c) 2016 Kapustin Alexey * * 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_dlg_stroke_selection_properties.h" #include #include #include #include #include #include #include #include #include #include #include #include "KoColorProfile.h" #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorConversionTransformation.h" #include "KoColorPopupAction.h" #include "kis_icon_utils.h" #include "KoID.h" #include "kis_image.h" #include "kis_annotation.h" #include "kis_config.h" #include "kis_signal_compressor.h" #include "widgets/kis_cmb_idlist.h" #include #include "kis_layer_utils.h" #include #include "kis_canvas_resource_provider.h" #include "KoUnit.h" #include "kis_display_color_converter.h" KisDlgStrokeSelection::KisDlgStrokeSelection(KisImageWSP image, KisViewManager *view, bool isVectorLayer) : KoDialog(view->mainWindow()) { m_resourceManager = view->mainWindow()->resourceManager(); converter = view->canvasBase()->displayColorConverter(); setButtons(Ok | Cancel); setDefaultButton(Ok); setCaption(i18nc("@title:window", "Stroke Selection Properties")); m_page = new WdgStrokeSelection(this); m_image = image; setMainWidget(m_page); resize(m_page->sizeHint()); KisPropertiesConfigurationSP cfg = KisConfig(true).exportConfiguration("StrokeSelection"); auto &m_options = m_page->m_options; m_options.color = cfg->getColor("color"); m_options.lineColorSource = cfg->getInt("lineColorSource"); m_page->lineColorBox->setCurrentIndex(m_options.lineColorSource); m_page->colorSelector->setColor(getSelectedColor().toQColor()); m_options.brushSelected = cfg->getBool("useBrush", 0); m_page->typeBox->setCurrentIndex(m_options.brushSelected? 0 : 1); m_options._colorFillSource = cfg->getInt("colorFillSource", 0); m_page->fillBox->setCurrentIndex(m_options._colorFillSource); m_options.customColor = cfg->getColor("customColor"); if (m_options._colorFillSource == static_cast(colorFillSource::CustomColor)) { m_page->colorFillSelector->setColor(m_options.customColor.toQColor()); } else { m_page->colorFillSelector->setColor(getFillSelectedColor().toQColor()); } m_options.fillColor = cfg->getColor("fillColor"); if (m_options._colorFillSource == static_cast(colorFillSource::None)) { m_page->colorFillSelector->setDisabled(true); } else { m_page->colorFillSelector->setDisabled(false); } m_options.lineSize = cfg->getInt("lineSize", 1); m_page->lineSize->setValue(m_options.lineSize); if (m_options.brushSelected) { m_page->lineSize->setDisabled(true); m_page->fillBox->setDisabled(true); m_page->colorFillSelector->setDisabled(true); m_page->sizeBox->setDisabled(true); } m_options.lineDimension = cfg->getInt("lineDimension", 0); m_page->sizeBox->setCurrentIndex(m_options.lineDimension); connect(m_page, SIGNAL(colorSelectorChanged()), SLOT(setColorButton())); connect(m_page, SIGNAL(colorFillSelectorChanged()), SLOT(setColorFillButton())); connect(m_page->colorFillSelector, SIGNAL(changed(QColor)), SLOT(colorFillChanged(QColor))); connect(m_page->colorSelector, SIGNAL(changed(QColor)), SLOT(colorChanged(QColor))); if (isVectorLayer) { lockVectorLayerFunctions(); } } KisDlgStrokeSelection::~KisDlgStrokeSelection() { auto &m_options = m_page->m_options; m_options.lineSize = m_page->lineSize->value(); m_options.lineDimension = m_page->sizeBox->currentIndex(); m_options.lineColorSource = m_page->lineColorBox->currentIndex(); KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); cfg->setProperty("lineSize", m_options.lineSize); cfg->setProperty("colorFillSource", m_options._colorFillSource); cfg->setProperty("useBrush", m_options.brushSelected); cfg->setProperty("lineDimension", m_options.lineDimension); cfg->setProperty("lineColorSource", m_options.lineColorSource); QVariant colorVariant("KoColor"); colorVariant.setValue(m_options.customColor); cfg->setProperty("customColor", colorVariant); QVariant _colorVariant("KoColor"); _colorVariant.setValue(m_options.color); cfg->setProperty("color", _colorVariant); QVariant _cVariant("KoColor"); _cVariant.setValue(m_options.fillColor); cfg->setProperty("fillColor", _cVariant); KisConfig(false).setExportConfiguration("StrokeSelection", cfg); delete m_page; } KoColor KisDlgStrokeSelection::getSelectedColor() const { KoColor color; QString currentSource = m_page->lineColorBox->currentText(); if (currentSource == "Foreground color") { color = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value(); } else if (currentSource == "Background color") { color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); } else { color = m_page->m_options.color; } return color; } KoColor KisDlgStrokeSelection::getFillSelectedColor() const { KoColor color; colorFillSource currentSource = static_cast(m_page->fillBox->currentIndex()); if (currentSource == colorFillSource::FGColor) { color = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value(); } else if (currentSource == colorFillSource::BGColor) { color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); } else if (currentSource == colorFillSource::PaintColor) { color = converter->approximateFromRenderedQColor(m_page->colorSelector->color()); } else { color = m_page->m_options.customColor; } return color; } bool KisDlgStrokeSelection::isBrushSelected() const { int index = m_page->typeBox->currentIndex(); drawType type = static_cast(index); if (type == drawType::brushDraw){ return true; } else { return false; } } StrokeSelectionOptions KisDlgStrokeSelection::getParams() const { StrokeSelectionOptions params; params.lineSize = getLineSize(); params.color = getSelectedColor(); params.brushSelected = isBrushSelected(); params.fillColor = getFillSelectedColor(); params._colorFillSource = m_page->m_options._colorFillSource; return params; } void KisDlgStrokeSelection::lockVectorLayerFunctions() { m_page->colorFillSelector->setEnabled(false); m_page->lineSize->setEnabled(false); m_page->sizeBox->setEnabled(false); m_page->fillBox->setEnabled(false); m_page->typeBox->setEnabled(false); } void KisDlgStrokeSelection::unlockVectorLayerFunctions() { m_page->colorFillSelector->setEnabled(true); m_page->lineSize->setEnabled(true); m_page->sizeBox->setEnabled(true); m_page->fillBox->setEnabled(true); m_page->typeBox->setEnabled(true); } void KisDlgStrokeSelection::setColorFillButton() { m_page->colorFillSelector->setColor(getFillSelectedColor().toQColor()); } void KisDlgStrokeSelection::setColorButton() { m_page->colorSelector->setColor(getSelectedColor().toQColor()); } int KisDlgStrokeSelection::getLineSize() const { int value = m_page->lineSize->value(); if (m_page->sizeBox->currentText() == i18n("px")) { return value; } else if (m_page->sizeBox->currentText() == i18n("mm")) { int pixels = static_cast(KoUnit::convertFromUnitToUnit(value,KoUnit(KoUnit::Millimeter), KoUnit(KoUnit::Pixel))); return pixels; } else { int pixels = static_cast(KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Pixel))); return pixels; } } linePosition KisDlgStrokeSelection::getLinePosition() const {/* TODO int index = m_page->linePosition->currentIndex(); switch(index) { case(0): return linePosition::OUTSIDE; case(1): return linePosition::INSIDE; case(2): return linePosition::CENTER; default: return linePosition::CENTER; }*/ return linePosition::CENTER; } void KisDlgStrokeSelection::colorChanged(const QColor &newColor) { if (m_page->fillBox->currentText() == "Paint color") { m_page->colorFillSelector->setColor(newColor); } QColor BGColor = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value().toQColor(); QColor FGColor = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value().toQColor(); KoColor tempColor= converter->approximateFromRenderedQColor(newColor); if (!(newColor == BGColor) && !(newColor == FGColor)) { m_page->m_options.color = tempColor; m_page->lineColorBox->setCurrentIndex(2); //custom color } } void KisDlgStrokeSelection::colorFillChanged(const QColor &newColor) { QColor PaintColor = m_page->colorSelector->color(); QColor BGcolor = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value().toQColor(); QColor FGColor = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value().toQColor(); KoColor tempColor= converter->approximateFromRenderedQColor(newColor); if (!(newColor == FGColor) && !(newColor == BGcolor) && !(newColor == PaintColor)) { m_page->m_options.customColor = tempColor; m_page->fillBox->setCurrentIndex(static_cast(colorFillSource::CustomColor)); } m_page->m_options.fillColor = tempColor; } WdgStrokeSelection::WdgStrokeSelection(QWidget *parent) : QWidget(parent) { setupUi(this); } void WdgStrokeSelection::on_fillBox_currentIndexChanged(int index) { if (index == static_cast(colorFillSource::None)) { colorFillSelector->setDisabled(true); } else { colorFillSelector->setDisabled(false); emit colorFillSelectorChanged(); } m_options._colorFillSource = index; } void WdgStrokeSelection::on_typeBox_currentIndexChanged(const QString &arg1) { if (arg1 == "Current Brush") { m_options.brushSelected = true; lineSize->setDisabled(true); fillBox->setDisabled(true); colorFillSelector->setDisabled(true); sizeBox->setDisabled(true); } else { m_options.brushSelected = false; lineSize->setDisabled(false); fillBox->setDisabled(false); colorFillSelector->setDisabled(false); sizeBox->setDisabled(false); } } void WdgStrokeSelection::on_lineColorBox_currentIndexChanged(const QString &/*arg1*/) { emit colorSelectorChanged(); } StrokeSelectionOptions ::StrokeSelectionOptions(): lineSize(1), brushSelected(false), _colorFillSource(0), lineColorSource(0), lineDimension(0) { color.fromQColor(Qt::black); fillColor.fromQColor(Qt::black); customColor.fromQColor(Qt::black); } -KisPainter::FillStyle StrokeSelectionOptions::fillStyle() const +KisToolShapeUtils::FillStyle StrokeSelectionOptions::fillStyle() const { + using namespace KisToolShapeUtils; + colorFillSource tempColor = static_cast(_colorFillSource); - KisPainter::FillStyle style; + FillStyle style; switch (tempColor) { case colorFillSource::PaintColor: - style = KisPainter::FillStyleForegroundColor; + style = FillStyleForegroundColor; break; case colorFillSource::BGColor: - style = KisPainter::FillStyleBackgroundColor; + style = FillStyleBackgroundColor; break; case colorFillSource::CustomColor: - style = KisPainter::FillStyleBackgroundColor; + style = FillStyleBackgroundColor; break; case colorFillSource::None: - style = KisPainter::FillStyleNone; + style = FillStyleNone; break; case colorFillSource::FGColor: - style = KisPainter::FillStyleBackgroundColor; + style = FillStyleBackgroundColor; break; - default: - style = KisPainter::FillStyleBackgroundColor; } return style; } diff --git a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h index c093b80cec..b2f8d6ae02 100644 --- a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h +++ b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h @@ -1,113 +1,113 @@ /* * Copyright (c) 2016 Alexey Kapustin * * 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_DLG_STROKE_SELECTION_PROPERTIES_H_ #define KIS_DLG_STROKE_SELECTION_PROPERTIES_H_ #include #include "KisProofingConfiguration.h" #include #include "KisViewManager.h" #include "KoStrokeConfigWidget.h" #include "ui_wdgstrokeselectionproperties.h" -#include #include +#include class KoColorSpace; class KoColorPopupAction; enum class linePosition { OUTSIDE, INSIDE, CENTER }; enum class drawType{ brushDraw, lineDraw }; enum class colorFillSource { None, PaintColor, BGColor, CustomColor, FGColor }; struct StrokeSelectionOptions { StrokeSelectionOptions (); int lineSize; bool brushSelected; int _colorFillSource; int lineColorSource; int lineDimension; KoColor color; KoColor fillColor; KoColor customColor; - KisPainter::FillStyle fillStyle() const; + KisToolShapeUtils::FillStyle fillStyle() const; void lock(); }; class WdgStrokeSelection : public QWidget, public Ui::WdgStrokeSelection { Q_OBJECT public: WdgStrokeSelection(QWidget *parent) ; StrokeSelectionOptions m_options; Q_SIGNALS: void colorFillSelectorChanged(); void colorSelectorChanged(); private Q_SLOTS: void on_fillBox_currentIndexChanged(int index); void on_typeBox_currentIndexChanged(const QString &arg1); void on_lineColorBox_currentIndexChanged(const QString &arg1); }; class KisDlgStrokeSelection : public KoDialog { Q_OBJECT public: KisDlgStrokeSelection(KisImageWSP image, KisViewManager *view, bool isVectorLayer); ~KisDlgStrokeSelection() override; int getLineSize() const; linePosition getLinePosition() const; KoColor getSelectedColor() const; bool isBrushSelected() const; KoColor getFillSelectedColor() const; StrokeSelectionOptions getParams() const; void lockVectorLayerFunctions(); void unlockVectorLayerFunctions(); private: WdgStrokeSelection * m_page; KisImageWSP m_image; KoCanvasResourceProvider *m_resourceManager; KisDisplayColorConverter *converter; private Q_SLOTS: void setColorFillButton(); void setColorButton(); void colorChanged(const QColor &newColor); void colorFillChanged(const QColor &newColor); }; #endif // KIS_DLG_STROKE_SEL_PROPERTIES_H_ diff --git a/libs/ui/forms/wdggeometryoptions.ui b/libs/ui/forms/wdggeometryoptions.ui index ed4a370673..4787016614 100644 --- a/libs/ui/forms/wdggeometryoptions.ui +++ b/libs/ui/forms/wdggeometryoptions.ui @@ -1,98 +1,111 @@ WdgGeometryOptions + + + 0 + 0 + 185 + 46 + + Geometry Options Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 1 1 0 0 0 0 Outline: 0 0 No Outline Brush + + + Brush (Background Color) + + Fill: 0 0 Not Filled Foreground Color Background Color Pattern diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc index ee035ecaf4..86e63f5bf2 100644 --- a/libs/ui/kis_selection_manager.cc +++ b/libs/ui/kis_selection_manager.cc @@ -1,760 +1,760 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * * The outline algorithm uses the limn algorithm of fontutils by * Karl Berry and Kathryn Hargreaves * * 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_selection_manager.h" #include #include #include #include #include #include #include #include #include #include #include "KoCanvasController.h" #include "KoChannelInfo.h" #include "KoIntegerMaths.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_adjustment_layer.h" #include "kis_node_manager.h" #include "canvas/kis_canvas2.h" #include "kis_config.h" #include "kis_convolution_painter.h" #include "kis_convolution_kernel.h" #include "kis_debug.h" #include "kis_fill_painter.h" #include "kis_group_layer.h" #include "kis_layer.h" #include "kis_statusbar.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_transaction.h" #include "kis_selection.h" #include "kis_types.h" #include "kis_canvas_resource_provider.h" #include "kis_undo_adapter.h" #include "kis_pixel_selection.h" #include "flake/kis_shape_selection.h" #include "commands/kis_selection_commands.h" #include "kis_selection_mask.h" #include "flake/kis_shape_layer.h" #include "kis_selection_decoration.h" #include "canvas/kis_canvas_decoration.h" #include "kis_node_commands_adapter.h" #include "kis_iterator_ng.h" #include "kis_clipboard.h" #include "KisViewManager.h" #include "kis_selection_filters.h" #include "kis_figure_painting_tool_helper.h" #include "KisView.h" #include "dialogs/kis_dlg_stroke_selection_properties.h" #include "actions/kis_selection_action_factories.h" #include "actions/KisPasteActionFactories.h" #include "kis_action.h" #include "kis_action_manager.h" #include "operations/kis_operation_configuration.h" //new #include "kis_node_query_path.h" #include "kis_tool_shape.h" KisSelectionManager::KisSelectionManager(KisViewManager * view) : m_view(view), m_doc(0), m_imageView(0), m_adapter(new KisNodeCommandsAdapter(view)), m_copy(0), m_copyMerged(0), m_cut(0), m_paste(0), m_pasteNew(0), m_cutToNewLayer(0), m_selectAll(0), m_deselect(0), m_clear(0), m_reselect(0), m_invert(0), m_copyToNewLayer(0), m_fillForegroundColor(0), m_fillBackgroundColor(0), m_fillPattern(0), m_imageResizeToSelection(0), m_selectionDecoration(0) { m_clipboard = KisClipboard::instance(); } KisSelectionManager::~KisSelectionManager() { } void KisSelectionManager::setup(KisActionManager* actionManager) { m_cut = actionManager->createStandardAction(KStandardAction::Cut, this, SLOT(cut())); m_copy = actionManager->createStandardAction(KStandardAction::Copy, this, SLOT(copy())); m_paste = actionManager->createStandardAction(KStandardAction::Paste, this, SLOT(paste())); KisAction *action = actionManager->createAction("copy_sharp"); connect(action, SIGNAL(triggered()), this, SLOT(copySharp())); action = actionManager->createAction("cut_sharp"); connect(action, SIGNAL(triggered()), this, SLOT(cutSharp())); m_pasteNew = actionManager->createAction("paste_new"); connect(m_pasteNew, SIGNAL(triggered()), this, SLOT(pasteNew())); m_pasteAt = actionManager->createAction("paste_at"); connect(m_pasteAt, SIGNAL(triggered()), this, SLOT(pasteAt())); m_pasteAsReference = actionManager->createAction("paste_as_reference"); connect(m_pasteAsReference, SIGNAL(triggered()), this, SLOT(pasteAsReference())); m_copyMerged = actionManager->createAction("copy_merged"); connect(m_copyMerged, SIGNAL(triggered()), this, SLOT(copyMerged())); m_selectAll = actionManager->createAction("select_all"); connect(m_selectAll, SIGNAL(triggered()), this, SLOT(selectAll())); m_deselect = actionManager->createAction("deselect"); connect(m_deselect, SIGNAL(triggered()), this, SLOT(deselect())); m_clear = actionManager->createAction("clear"); connect(m_clear, SIGNAL(triggered()), SLOT(clear())); m_reselect = actionManager->createAction("reselect"); connect(m_reselect, SIGNAL(triggered()), this, SLOT(reselect())); m_invert = actionManager->createAction("invert_selection"); m_invert->setOperationID("invertselection"); actionManager->registerOperation(new KisInvertSelectionOperation); m_copyToNewLayer = actionManager->createAction("copy_selection_to_new_layer"); connect(m_copyToNewLayer, SIGNAL(triggered()), this, SLOT(copySelectionToNewLayer())); m_cutToNewLayer = actionManager->createAction("cut_selection_to_new_layer"); connect(m_cutToNewLayer, SIGNAL(triggered()), this, SLOT(cutToNewLayer())); m_fillForegroundColor = actionManager->createAction("fill_selection_foreground_color"); connect(m_fillForegroundColor, SIGNAL(triggered()), this, SLOT(fillForegroundColor())); m_fillBackgroundColor = actionManager->createAction("fill_selection_background_color"); connect(m_fillBackgroundColor, SIGNAL(triggered()), this, SLOT(fillBackgroundColor())); m_fillPattern = actionManager->createAction("fill_selection_pattern"); connect(m_fillPattern, SIGNAL(triggered()), this, SLOT(fillPattern())); m_fillForegroundColorOpacity = actionManager->createAction("fill_selection_foreground_color_opacity"); connect(m_fillForegroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillForegroundColorOpacity())); m_fillBackgroundColorOpacity = actionManager->createAction("fill_selection_background_color_opacity"); connect(m_fillBackgroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillBackgroundColorOpacity())); m_fillPatternOpacity = actionManager->createAction("fill_selection_pattern_opacity"); connect(m_fillPatternOpacity, SIGNAL(triggered()), this, SLOT(fillPatternOpacity())); m_strokeShapes = actionManager->createAction("stroke_shapes"); connect(m_strokeShapes, SIGNAL(triggered()), this, SLOT(paintSelectedShapes())); m_toggleDisplaySelection = actionManager->createAction("toggle_display_selection"); connect(m_toggleDisplaySelection, SIGNAL(triggered()), this, SLOT(toggleDisplaySelection())); m_toggleDisplaySelection->setChecked(true); m_imageResizeToSelection = actionManager->createAction("resizeimagetoselection"); connect(m_imageResizeToSelection, SIGNAL(triggered()), this, SLOT(imageResizeToSelection())); action = actionManager->createAction("edit_selection"); connect(action, SIGNAL(triggered()), SLOT(editSelection())); action = actionManager->createAction("convert_to_vector_selection"); connect(action, SIGNAL(triggered()), SLOT(convertToVectorSelection())); action = actionManager->createAction("convert_to_raster_selection"); connect(action, SIGNAL(triggered()), SLOT(convertToRasterSelection())); action = actionManager->createAction("convert_shapes_to_vector_selection"); connect(action, SIGNAL(triggered()), SLOT(convertShapesToVectorSelection())); action = actionManager->createAction("convert_selection_to_shape"); connect(action, SIGNAL(triggered()), SLOT(convertToShape())); m_toggleSelectionOverlayMode = actionManager->createAction("toggle-selection-overlay-mode"); connect(m_toggleSelectionOverlayMode, SIGNAL(triggered()), SLOT(slotToggleSelectionDecoration())); m_strokeSelected = actionManager->createAction("stroke_selection"); connect(m_strokeSelected, SIGNAL(triggered()), SLOT(slotStrokeSelection())); QClipboard *cb = QApplication::clipboard(); connect(cb, SIGNAL(dataChanged()), SLOT(clipboardDataChanged())); } void KisSelectionManager::setView(QPointerimageView) { if (m_imageView && m_imageView->canvasBase()) { disconnect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(QString)), this, SLOT(clipboardDataChanged())); KoSelection *selection = m_imageView->canvasBase()->globalShapeManager()->selection(); selection->disconnect(this, SLOT(shapeSelectionChanged())); KisSelectionDecoration *decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data()); if (decoration) { disconnect(SIGNAL(currentSelectionChanged()), decoration); } m_imageView->image()->undoAdapter()->disconnect(this); m_selectionDecoration = 0; } m_imageView = imageView; if (m_imageView) { connect(m_imageView->canvasBase()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()), Qt::UniqueConnection); KisSelectionDecoration* decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data()); if (!decoration) { decoration = new KisSelectionDecoration(m_imageView); decoration->setVisible(true); m_imageView->canvasBase()->addDecoration(decoration); } m_selectionDecoration = decoration; connect(this, SIGNAL(currentSelectionChanged()), decoration, SLOT(selectionChanged())); connect(m_imageView->image()->undoAdapter(), SIGNAL(selectionChanged()), SLOT(selectionChanged())); connect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(QString)), SLOT(clipboardDataChanged())); } } void KisSelectionManager::clipboardDataChanged() { m_view->updateGUI(); } bool KisSelectionManager::havePixelsSelected() { KisSelectionSP activeSelection = m_view->selection(); return activeSelection && !activeSelection->selectedRect().isEmpty(); } bool KisSelectionManager::havePixelsInClipboard() { return m_clipboard->hasClip(); } bool KisSelectionManager::haveShapesSelected() { if (m_view && m_view->canvasBase()) { return m_view->canvasBase()->selectedShapesProxy()->selection()->count() > 0; } return false; } bool KisSelectionManager::haveShapesInClipboard() { KoSvgPaste paste; return paste.hasShapes(); } bool KisSelectionManager::haveAnySelectionWithPixels() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasPixelSelection(); } bool KisSelectionManager::haveShapeSelectionWithShapes() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasShapeSelection(); } bool KisSelectionManager::haveRasterSelectionWithPixels() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasPixelSelection() && !selection->hasShapeSelection(); } void KisSelectionManager::updateGUI() { Q_ASSERT(m_view); Q_ASSERT(m_clipboard); if (!m_view || !m_clipboard) return; bool havePixelsSelected = this->havePixelsSelected(); bool havePixelsInClipboard = this->havePixelsInClipboard(); bool haveShapesSelected = this->haveShapesSelected(); bool haveShapesInClipboard = this->haveShapesInClipboard(); bool haveDevice = m_view->activeDevice(); KisLayerSP activeLayer = m_view->activeLayer(); KisImageWSP image = activeLayer ? activeLayer->image() : 0; bool canReselect = image && image->canReselectGlobalSelection(); bool canDeselect = image && image->globalSelection(); m_clear->setEnabled(haveDevice || havePixelsSelected || haveShapesSelected); m_cut->setEnabled(havePixelsSelected || haveShapesSelected); m_copy->setEnabled(havePixelsSelected || haveShapesSelected); m_paste->setEnabled(havePixelsInClipboard || haveShapesInClipboard); m_pasteAt->setEnabled(havePixelsInClipboard || haveShapesInClipboard); // FIXME: how about pasting shapes? m_pasteNew->setEnabled(havePixelsInClipboard); m_pasteAsReference->setEnabled(haveDevice); m_selectAll->setEnabled(true); m_deselect->setEnabled(canDeselect); m_reselect->setEnabled(canReselect); // m_load->setEnabled(true); // m_save->setEnabled(havePixelsSelected); updateStatusBar(); emit signalUpdateGUI(); } void KisSelectionManager::updateStatusBar() { if (m_view && m_view->statusBar()) { m_view->statusBar()->setSelection(m_view->image()); } } void KisSelectionManager::selectionChanged() { m_view->updateGUI(); emit currentSelectionChanged(); } void KisSelectionManager::cut() { KisCutCopyActionFactory factory; factory.run(true, false, m_view); } void KisSelectionManager::copy() { KisCutCopyActionFactory factory; factory.run(false, false, m_view); } void KisSelectionManager::cutSharp() { KisCutCopyActionFactory factory; factory.run(true, true, m_view); } void KisSelectionManager::copySharp() { KisCutCopyActionFactory factory; factory.run(false, true, m_view); } void KisSelectionManager::copyMerged() { KisCopyMergedActionFactory factory; factory.run(m_view); } void KisSelectionManager::paste() { KisPasteActionFactory factory; factory.run(false, m_view); } void KisSelectionManager::pasteAt() { KisPasteActionFactory factory; factory.run(true, m_view); } void KisSelectionManager::pasteAsReference() { KisPasteReferenceActionFactory factory; factory.run(m_view); } void KisSelectionManager::pasteNew() { KisPasteNewActionFactory factory; factory.run(m_view); } void KisSelectionManager::selectAll() { KisSelectAllActionFactory factory; factory.run(m_view); } void KisSelectionManager::deselect() { KisDeselectActionFactory factory; factory.run(m_view); } void KisSelectionManager::invert() { if(m_invert) m_invert->trigger(); } void KisSelectionManager::reselect() { KisReselectActionFactory factory; factory.run(m_view); } #include #include void KisSelectionManager::editSelection() { KisSelectionSP selection = m_view->selection(); if (!selection) return; KisAction *action = m_view->actionManager()->actionByName("show-global-selection-mask"); KIS_SAFE_ASSERT_RECOVER_RETURN(action); if (!action->isChecked()) { action->setChecked(true); emit action->toggled(true); emit action->triggered(true); } KisNodeSP node = selection->parentNode(); KIS_SAFE_ASSERT_RECOVER_RETURN(node); m_view->nodeManager()->slotNonUiActivatedNode(node); if (selection->hasShapeSelection()) { KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection()); KIS_SAFE_ASSERT_RECOVER_RETURN(shapeSelection); KoToolManager::instance()->switchToolRequested(KoInteractionTool_ID); QList shapes = shapeSelection->shapes(); if (shapes.isEmpty()) { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "no shapes"); return; } Q_FOREACH (KoShape *shape, shapes) { m_view->canvasBase()->selectedShapesProxy()->selection()->select(shape); } } else { KoToolManager::instance()->switchToolRequested("KisToolTransform"); } } void KisSelectionManager::convertToVectorSelection() { KisSelectionToVectorActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertToRasterSelection() { KisSelectionToRasterActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertShapesToVectorSelection() { KisShapesToVectorSelectionActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertToShape() { KisSelectionToShapeActionFactory factory; factory.run(m_view); } void KisSelectionManager::clear() { KisClearActionFactory factory; factory.run(m_view); } void KisSelectionManager::fillForegroundColor() { KisFillActionFactory factory; factory.run("fg", m_view); } void KisSelectionManager::fillBackgroundColor() { KisFillActionFactory factory; factory.run("bg", m_view); } void KisSelectionManager::fillPattern() { KisFillActionFactory factory; factory.run("pattern", m_view); } void KisSelectionManager::fillForegroundColorOpacity() { KisFillActionFactory factory; factory.run("fg_opacity", m_view); } void KisSelectionManager::fillBackgroundColorOpacity() { KisFillActionFactory factory; factory.run("bg_opacity", m_view); } void KisSelectionManager::fillPatternOpacity() { KisFillActionFactory factory; factory.run("pattern_opacity", m_view); } void KisSelectionManager::copySelectionToNewLayer() { copy(); paste(); } void KisSelectionManager::cutToNewLayer() { cut(); paste(); } void KisSelectionManager::toggleDisplaySelection() { KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration); m_selectionDecoration->toggleVisibility(); m_toggleDisplaySelection->blockSignals(true); m_toggleDisplaySelection->setChecked(m_selectionDecoration->visible()); m_toggleDisplaySelection->blockSignals(false); emit displaySelectionChanged(); } bool KisSelectionManager::displaySelection() { return m_toggleDisplaySelection->isChecked(); } void KisSelectionManager::shapeSelectionChanged() { KoShapeManager* shapeManager = m_view->canvasBase()->globalShapeManager(); KoSelection * selection = shapeManager->selection(); QList selectedShapes = selection->selectedShapes(); KoShapeStrokeSP border(new KoShapeStroke(0, Qt::lightGray)); Q_FOREACH (KoShape* shape, shapeManager->shapes()) { if (dynamic_cast(shape->parent())) { if (selectedShapes.contains(shape)) shape->setStroke(border); else shape->setStroke(KoShapeStrokeSP()); } } m_view->updateGUI(); } void KisSelectionManager::imageResizeToSelection() { KisImageResizeToSelectionActionFactory factory; factory.run(m_view); } void KisSelectionManager::paintSelectedShapes() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = m_view->activeLayer(); if (!layer) return; QList shapes = m_view->canvasBase()->shapeManager()->selection()->selectedShapes(); KisPaintLayerSP paintLayer = new KisPaintLayer(image, i18n("Stroked Shapes"), OPACITY_OPAQUE_U8); KUndo2MagicString actionName = kundo2_i18n("Stroke Shapes"); m_adapter->beginMacro(actionName); m_adapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); KisFigurePaintingToolHelper helper(actionName, image, paintLayer.data(), m_view->canvasResourceProvider()->resourceManager(), - KisPainter::StrokeStyleBrush, - KisPainter::FillStyleNone); + KisToolShapeUtils::StrokeStyleForeground, + KisToolShapeUtils::FillStyleNone); Q_FOREACH (KoShape* shape, shapes) { QTransform matrix = shape->absoluteTransformation(0) * QTransform::fromScale(image->xRes(), image->yRes()); QPainterPath mapedOutline = matrix.map(shape->outline()); helper.paintPainterPath(mapedOutline); } m_adapter->endMacro(); } void KisSelectionManager::slotToggleSelectionDecoration() { KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration); KisSelectionDecoration::Mode mode = m_selectionDecoration->mode() ? KisSelectionDecoration::Ants : KisSelectionDecoration::Mask; m_selectionDecoration->setMode(mode); emit displaySelectionChanged(); } bool KisSelectionManager::showSelectionAsMask() const { if (m_selectionDecoration) { return m_selectionDecoration->mode() == KisSelectionDecoration::Mask; } return false; } void KisSelectionManager::slotStrokeSelection() { KisImageWSP image = m_view->image(); if (!image ) { return; } KisNodeSP currentNode = m_view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); bool isVectorLayer = false; if (currentNode->inherits("KisShapeLayer")) { isVectorLayer = true; } QPointer dlg = new KisDlgStrokeSelection(image, m_view, isVectorLayer); if (dlg->exec() == QDialog::Accepted) { StrokeSelectionOptions params = dlg->getParams(); if (params.brushSelected){ KisStrokeBrushSelectionActionFactory factory; factory.run(m_view, params); } else { KisStrokeSelectionActionFactory factory; factory.run(m_view, params); } } delete dlg; } #include "kis_image_barrier_locker.h" #include "kis_selection_tool_helper.h" void KisSelectionManager::selectOpaqueOnNode(KisNodeSP node, SelectionAction action) { KisImageSP image = m_view->image(); if (!m_view->blockUntilOperationsFinished(image)) { return; } KUndo2MagicString actionName; KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); KisCanvas2 *canvas = m_view->canvasBase(); { KisImageBarrierLocker locker(image); KisPaintDeviceSP device = node->projection(); if (!device) device = node->paintDevice(); if (!device) device = node->original(); if (!device) return; QRect rc = device->exactBounds(); if (rc.isEmpty()) return; KIS_ASSERT_RECOVER_RETURN(canvas); /** * If there is nothing selected, just create a new selection */ if (!canvas->imageView()->selection()) { action = SELECTION_REPLACE; } switch (action) { case SELECTION_ADD: actionName = kundo2_i18n("Select Opaque (Add)"); break; case SELECTION_SUBTRACT: actionName = kundo2_i18n("Select Opaque (Subtract)"); break; case SELECTION_INTERSECT: actionName = kundo2_i18n("Select Opaque (Intersect)"); break; case SELECTION_SYMMETRICDIFFERENCE: actionName = kundo2_i18n("Select Opaque (Symmetric Difference)"); break; default: actionName = kundo2_i18n("Select Opaque"); break; } qint32 x, y, w, h; rc.getRect(&x, &y, &w, &h); const KoColorSpace * cs = device->colorSpace(); KisHLineConstIteratorSP deviter = device->createHLineConstIteratorNG(x, y, w); KisHLineIteratorSP selIter = tmpSel ->createHLineIteratorNG(x, y, w); for (int row = y; row < h + y; ++row) { do { *selIter->rawData() = cs->opacityU8(deviter->oldRawData()); } while (deviter->nextPixel() && selIter->nextPixel()); deviter->nextRow(); selIter->nextRow(); } } KisSelectionToolHelper helper(canvas, actionName); tmpSel->invalidateOutlineCache(); helper.selectPixelSelection(tmpSel, action); } diff --git a/libs/ui/tool/KisToolShapeUtils.cpp b/libs/ui/tool/KisToolShapeUtils.cpp new file mode 100644 index 0000000000..9c5bd9df6b --- /dev/null +++ b/libs/ui/tool/KisToolShapeUtils.cpp @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2019 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. + */ diff --git a/libs/ui/tool/KisToolShapeUtils.h b/libs/ui/tool/KisToolShapeUtils.h new file mode 100644 index 0000000000..b23c056e2b --- /dev/null +++ b/libs/ui/tool/KisToolShapeUtils.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 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 KISTOOLSHAPEUTILS_H +#define KISTOOLSHAPEUTILS_H + +namespace KisToolShapeUtils +{ + +enum FillStyle { + FillStyleNone, + FillStyleForegroundColor, + FillStyleBackgroundColor, + FillStylePattern, +}; + +enum StrokeStyle { + StrokeStyleNone, + StrokeStyleForeground, + StrokeStyleBackground +}; + +} + +#endif // KISTOOLSHAPEUTILS_H diff --git a/libs/ui/tool/kis_figure_painting_tool_helper.cpp b/libs/ui/tool/kis_figure_painting_tool_helper.cpp index caad575e23..11919b1be9 100644 --- a/libs/ui/tool/kis_figure_painting_tool_helper.cpp +++ b/libs/ui/tool/kis_figure_painting_tool_helper.cpp @@ -1,145 +1,191 @@ /* * Copyright (c) 2011 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_figure_painting_tool_helper.h" #include #include "kis_resources_snapshot.h" #include #include "kis_image.h" #include "kis_painter.h" #include #include "KisAsyncronousStrokeUpdateHelper.h" KisFigurePaintingToolHelper::KisFigurePaintingToolHelper(const KUndo2MagicString &name, KisImageWSP image, KisNodeSP currentNode, KoCanvasResourceProvider *resourceManager, - KisPainter::StrokeStyle strokeStyle, - KisPainter::FillStyle fillStyle) + KisToolShapeUtils::StrokeStyle strokeStyle, + KisToolShapeUtils::FillStyle fillStyle) { m_strokesFacade = image.data(); m_resources = new KisResourcesSnapshot(image, currentNode, resourceManager); - m_resources->setStrokeStyle(strokeStyle); - m_resources->setFillStyle(fillStyle); + setupPaintStyles(m_resources, strokeStyle, fillStyle); KisFreehandStrokeInfo *strokeInfo = new KisFreehandStrokeInfo(); KisStrokeStrategy *stroke = new FreehandStrokeStrategy(m_resources, strokeInfo, name); m_strokeId = m_strokesFacade->startStroke(stroke); } +void KisFigurePaintingToolHelper::setupPaintStyles(KisResourcesSnapshotSP resources, + KisToolShapeUtils::StrokeStyle strokeStyle, + KisToolShapeUtils::FillStyle fillStyle) +{ + using namespace KisToolShapeUtils; + + const KoColor fgColor = resources->currentFgColor(); + const KoColor bgColor = resources->currentBgColor(); + + switch (strokeStyle) { + case StrokeStyleNone: + resources->setStrokeStyle(KisPainter::StrokeStyleNone); + break; + case StrokeStyleForeground: + resources->setStrokeStyle(KisPainter::StrokeStyleBrush); + break; + case StrokeStyleBackground: + resources->setStrokeStyle(KisPainter::StrokeStyleBrush); + + resources->setFGColorOverride(bgColor); + resources->setBGColorOverride(fgColor); + + if (fillStyle == FillStyleForegroundColor) { + fillStyle = FillStyleBackgroundColor; + } else if (fillStyle == FillStyleBackgroundColor) { + fillStyle = FillStyleForegroundColor; + } + + break; + }; + + switch (fillStyle) { + case FillStyleForegroundColor: + resources->setFillStyle(KisPainter::FillStyleForegroundColor); + break; + case FillStyleBackgroundColor: + resources->setFillStyle(KisPainter::FillStyleBackgroundColor); + break; + case FillStylePattern: + resources->setFillStyle(KisPainter::FillStylePattern); + break; + case FillStyleNone: + resources->setFillStyle(KisPainter::FillStyleNone); + break; + } +} + KisFigurePaintingToolHelper::~KisFigurePaintingToolHelper() { m_strokesFacade->addJob(m_strokeId, new KisAsyncronousStrokeUpdateHelper::UpdateData(true)); m_strokesFacade->endStroke(m_strokeId); } void KisFigurePaintingToolHelper::paintLine(const KisPaintInformation &pi0, const KisPaintInformation &pi1) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, pi0, pi1)); } void KisFigurePaintingToolHelper::paintPolyline(const vQPointF &points) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::POLYLINE, points)); } void KisFigurePaintingToolHelper::paintPolygon(const vQPointF &points) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::POLYGON, points)); } void KisFigurePaintingToolHelper::paintRect(const QRectF &rect) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::RECT, rect)); } void KisFigurePaintingToolHelper::paintEllipse(const QRectF &rect) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::ELLIPSE, rect)); } void KisFigurePaintingToolHelper::paintPainterPath(const QPainterPath &path) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::PAINTER_PATH, path)); } void KisFigurePaintingToolHelper::setFGColorOverride(const KoColor &color) { m_resources->setFGColorOverride(color); } void KisFigurePaintingToolHelper::setBGColorOverride(const KoColor &color) { m_resources->setBGColorOverride(color); } void KisFigurePaintingToolHelper::setSelectionOverride(KisSelectionSP m_selection) { m_resources->setSelectionOverride(m_selection); } void KisFigurePaintingToolHelper::setBrush(const KisPaintOpPresetSP &brush) { m_resources->setBrush(brush); } void KisFigurePaintingToolHelper::paintPainterPathQPen(const QPainterPath path, const QPen &pen, const KoColor &color) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::QPAINTER_PATH, path, pen, color)); } void KisFigurePaintingToolHelper::paintPainterPathQPenFill(const QPainterPath path, const QPen &pen, const KoColor &color) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::QPAINTER_PATH_FILL, path, pen, color)); } diff --git a/libs/ui/tool/kis_figure_painting_tool_helper.h b/libs/ui/tool/kis_figure_painting_tool_helper.h index 3dcc334774..5c5348a64e 100644 --- a/libs/ui/tool/kis_figure_painting_tool_helper.h +++ b/libs/ui/tool/kis_figure_painting_tool_helper.h @@ -1,61 +1,67 @@ /* * Copyright (c) 2011 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_FIGURE_PAINTING_TOOL_HELPER_H #define __KIS_FIGURE_PAINTING_TOOL_HELPER_H #include "kis_types.h" #include "kritaui_export.h" #include #include "strokes/freehand_stroke.h" +#include "KisToolShapeUtils.h" class KoCanvasResourceProvider; class KisStrokesFacade; class KRITAUI_EXPORT KisFigurePaintingToolHelper { public: KisFigurePaintingToolHelper(const KUndo2MagicString &name, KisImageWSP image, KisNodeSP currentNode, KoCanvasResourceProvider *resourceManager, - KisPainter::StrokeStyle strokeStyle, - KisPainter::FillStyle fillStyle); + KisToolShapeUtils::StrokeStyle strokeStyle, + KisToolShapeUtils::FillStyle fillStyle); ~KisFigurePaintingToolHelper(); void paintLine(const KisPaintInformation &pi0, const KisPaintInformation &pi1); void paintPolyline(const vQPointF &points); void paintPolygon(const vQPointF &points); void paintRect(const QRectF &rect); void paintEllipse(const QRectF &rect); void paintPainterPath(const QPainterPath &path); void setFGColorOverride(const KoColor &color); void setBGColorOverride(const KoColor &color); void setSelectionOverride(KisSelectionSP m_selection); void setBrush(const KisPaintOpPresetSP &brush); void paintPainterPathQPen(const QPainterPath, const QPen &pen, const KoColor &color); void paintPainterPathQPenFill(const QPainterPath, const QPen &pen, const KoColor &color); +private: + void setupPaintStyles(KisResourcesSnapshotSP resources, + KisToolShapeUtils::StrokeStyle strokeStyle, + KisToolShapeUtils::FillStyle fillStyle); + private: KisStrokeId m_strokeId; KisResourcesSnapshotSP m_resources; KisStrokesFacade *m_strokesFacade; }; #endif /* __KIS_FIGURE_PAINTING_TOOL_HELPER_H */ diff --git a/libs/ui/tool/kis_tool_shape.cc b/libs/ui/tool/kis_tool_shape.cc index bd0b72ee9e..5e071c196e 100644 --- a/libs/ui/tool/kis_tool_shape.cc +++ b/libs/ui/tool/kis_tool_shape.cc @@ -1,257 +1,253 @@ /* * Copyright (c) 2005 Adrian Page * * 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_shape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include #include #include #include #include "kis_selection_mask.h" #include "kis_shape_selection.h" #include "kis_processing_applicator.h" KisToolShape::KisToolShape(KoCanvasBase * canvas, const QCursor & cursor) : KisToolPaint(canvas, cursor) { m_shapeOptionsWidget = 0; } KisToolShape::~KisToolShape() { // in case the widget hasn't been shown if (m_shapeOptionsWidget && !m_shapeOptionsWidget->parent()) { delete m_shapeOptionsWidget; } } void KisToolShape::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolPaint::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } int KisToolShape::flags() const { return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET |KisTool::FLAG_USES_CUSTOM_SIZE; } QWidget * KisToolShape::createOptionWidget() { m_shapeOptionsWidget = new WdgGeometryOptions(0); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(KisPainter::StrokeStyleBrush); //connect two combo box event. Inherited classes can call the slots to make appropriate changes connect(m_shapeOptionsWidget->cmbOutline, SIGNAL(currentIndexChanged(int)), this, SLOT(outlineSettingChanged(int))); connect(m_shapeOptionsWidget->cmbFill, SIGNAL(currentIndexChanged(int)), this, SLOT(fillSettingChanged(int))); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(m_configGroup.readEntry("outlineType", 0)); m_shapeOptionsWidget->cmbFill->setCurrentIndex(m_configGroup.readEntry("fillType", 0)); //if both settings are empty, force the outline to brush so the tool will work when first activated if ( m_shapeOptionsWidget->cmbFill->currentIndex() == 0 && m_shapeOptionsWidget->cmbOutline->currentIndex() == 0) { m_shapeOptionsWidget->cmbOutline->setCurrentIndex(1); // brush } return m_shapeOptionsWidget; } void KisToolShape::outlineSettingChanged(int value) { m_configGroup.writeEntry("outlineType", value); } void KisToolShape::fillSettingChanged(int value) { m_configGroup.writeEntry("fillType", value); } -KisPainter::FillStyle KisToolShape::fillStyle(void) +KisToolShapeUtils::FillStyle KisToolShape::fillStyle() { if (m_shapeOptionsWidget) { - return static_cast(m_shapeOptionsWidget->cmbFill->currentIndex()); + return static_cast(m_shapeOptionsWidget->cmbFill->currentIndex()); } else { - return KisPainter::FillStyleNone; + return KisToolShapeUtils::FillStyleNone; } } -KisPainter::StrokeStyle KisToolShape::strokeStyle(void) +KisToolShapeUtils::StrokeStyle KisToolShape::strokeStyle() { if (m_shapeOptionsWidget) { - return static_cast(m_shapeOptionsWidget->cmbOutline->currentIndex()); + return static_cast(m_shapeOptionsWidget->cmbOutline->currentIndex()); } else { - return KisPainter::StrokeStyleNone; + return KisToolShapeUtils::StrokeStyleNone; } } qreal KisToolShape::currentStrokeWidth() const { const qreal sizeInPx = canvas()->resourceManager()->resource(KisCanvasResourceProvider::Size).toReal(); return canvas()->unit().fromUserValue(sizeInPx); } KisToolShape::ShapeAddInfo KisToolShape::shouldAddShape(KisNodeSP currentNode) const { ShapeAddInfo info; if (currentNode->inherits("KisShapeLayer")) { info.shouldAddShape = true; } else if (KisSelectionMask *mask = dynamic_cast(currentNode.data())) { if (mask->selection()->hasShapeSelection()) { info.shouldAddShape = true; info.shouldAddSelectionShape = true; } } return info; } void KisToolShape::ShapeAddInfo::markAsSelectionShapeIfNeeded(KoShape *shape) const { if (this->shouldAddSelectionShape) { shape->setUserData(new KisShapeSelectionMarker()); } } void KisToolShape::addShape(KoShape* shape) { + using namespace KisToolShapeUtils; + KoImageCollection* imageCollection = canvas()->shapeController()->resourceManager()->imageCollection(); switch(fillStyle()) { - case KisPainter::FillStyleForegroundColor: + case FillStyleForegroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentFgColor().toQColor()))); break; - case KisPainter::FillStyleBackgroundColor: + case FillStyleBackgroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentBgColor().toQColor()))); break; - case KisPainter::FillStylePattern: + case FillStylePattern: if (imageCollection) { QSharedPointer fill(new KoPatternBackground(imageCollection)); if (currentPattern()) { fill->setPattern(currentPattern()->pattern()); shape->setBackground(fill); } } else { shape->setBackground(QSharedPointer(0)); } break; - case KisPainter::FillStyleGradient: - { - QLinearGradient *gradient = new QLinearGradient(QPointF(0, 0), QPointF(1, 1)); - gradient->setCoordinateMode(QGradient::ObjectBoundingMode); - gradient->setStops(currentGradient()->toQGradient()->stops()); - QSharedPointer gradientFill(new KoGradientBackground(gradient)); - shape->setBackground(gradientFill); - } - break; - case KisPainter::FillStyleNone: - default: + case FillStyleNone: shape->setBackground(QSharedPointer(0)); break; } switch (strokeStyle()) { - case KisPainter::StrokeStyleNone: + case KisToolShapeUtils::StrokeStyleNone: shape->setStroke(KoShapeStrokeModelSP()); break; - case KisPainter::StrokeStyleBrush: { + case KisToolShapeUtils::StrokeStyleForeground: + case KisToolShapeUtils::StrokeStyleBackground: { KoShapeStrokeSP stroke(new KoShapeStroke()); stroke->setLineWidth(currentStrokeWidth()); - stroke->setColor(canvas()->resourceManager()->foregroundColor().toQColor()); + const QColor color = strokeStyle() == KisToolShapeUtils::StrokeStyleForeground ? + canvas()->resourceManager()->foregroundColor().toQColor() : + canvas()->resourceManager()->backgroundColor().toQColor(); + stroke->setColor(color); shape->setStroke(stroke); break; } } KUndo2Command *parentCommand = new KUndo2Command(); KoSelection *selection = canvas()->selectedShapesProxy()->selection(); const QList oldSelectedShapes = selection->selectedShapes(); // reset selection on the newly added shape :) // TODO: think about moving this into controller->addShape? new KoKeepShapesSelectedCommand(oldSelectedShapes, {shape}, canvas()->selectedShapesProxy(), false, parentCommand); KUndo2Command *cmd = canvas()->shapeController()->addShape(shape, 0, parentCommand); parentCommand->setText(cmd->text()); new KoKeepShapesSelectedCommand(oldSelectedShapes, {shape}, canvas()->selectedShapesProxy(), true, parentCommand); KisProcessingApplicator::runSingleCommandStroke(image(), cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } void KisToolShape::addPathShape(KoPathShape* pathShape, const KUndo2MagicString& name) { KisNodeSP node = currentNode(); if (!node) { return; } // Compute the outline KisImageSP image = this->image(); QTransform matrix; matrix.scale(image->xRes(), image->yRes()); matrix.translate(pathShape->position().x(), pathShape->position().y()); QPainterPath mapedOutline = matrix.map(pathShape->outline()); if (node->hasEditablePaintDevice()) { KisFigurePaintingToolHelper helper(name, image, node, canvas()->resourceManager(), strokeStyle(), fillStyle()); helper.paintPainterPath(mapedOutline); } else if (node->inherits("KisShapeLayer")) { pathShape->normalize(); addShape(pathShape); } } diff --git a/libs/ui/tool/kis_tool_shape.h b/libs/ui/tool/kis_tool_shape.h index 2cbc72a163..3bae642b14 100644 --- a/libs/ui/tool/kis_tool_shape.h +++ b/libs/ui/tool/kis_tool_shape.h @@ -1,94 +1,95 @@ /* * Copyright (c) 2005 Adrian Page * * 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_TOOL_SHAPE_H_ #define KIS_TOOL_SHAPE_H_ #include #include #include #include #include "kis_tool_paint.h" #include "KisSelectionToolFactoryBase.h" +#include "KisToolShapeUtils.h" #include "ui_wdggeometryoptions.h" class KoCanvasBase; class KoPathShape; class WdgGeometryOptions : public QWidget, public Ui::WdgGeometryOptions { Q_OBJECT public: WdgGeometryOptions(QWidget *parent) : QWidget(parent) { setupUi(this); } }; /** * Base for tools specialized in drawing shapes */ class KRITAUI_EXPORT KisToolShape : public KisToolPaint { Q_OBJECT public: KisToolShape(KoCanvasBase * canvas, const QCursor & cursor); ~KisToolShape() override; int flags() const override; WdgGeometryOptions *m_shapeOptionsWidget; public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; virtual void outlineSettingChanged(int value); virtual void fillSettingChanged(int value); protected: QWidget* createOptionWidget() override; - virtual KisPainter::FillStyle fillStyle(); - KisPainter::StrokeStyle strokeStyle(); + KisToolShapeUtils::FillStyle fillStyle(); + KisToolShapeUtils::StrokeStyle strokeStyle(); qreal currentStrokeWidth() const; struct KRITAUI_EXPORT ShapeAddInfo { bool shouldAddShape = false; bool shouldAddSelectionShape = false; void markAsSelectionShapeIfNeeded(KoShape *shape) const; }; ShapeAddInfo shouldAddShape(KisNodeSP currentNode) const; void addShape(KoShape* shape); void addPathShape(KoPathShape* pathShape, const KUndo2MagicString& name); KConfigGroup m_configGroup; }; #endif // KIS_TOOL_SHAPE_H_ diff --git a/plugins/tools/basictools/kis_tool_pencil.cc b/plugins/tools/basictools/kis_tool_pencil.cc index b4cc46b179..d53db8b32e 100644 --- a/plugins/tools/basictools/kis_tool_pencil.cc +++ b/plugins/tools/basictools/kis_tool_pencil.cc @@ -1,107 +1,107 @@ /* * Copyright (c) 2012 Sven Langkamp * Copyright (c) 2010 Cyrille Berger * * 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_pencil.h" #include #include #include #include #include KisToolPencil::KisToolPencil(KoCanvasBase * canvas) : DelegatedPencilTool(canvas, Qt::ArrowCursor, new __KisToolPencilLocalTool(canvas, this)) { } void KisToolPencil::resetCursorStyle() { DelegatedPencilTool::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolPencil::updatePencilCursor(bool value) { setCursor(value ? Qt::ArrowCursor : Qt::ForbiddenCursor); resetCursorStyle(); } void KisToolPencil::mousePressEvent(KoPointerEvent *event) { if (!nodeEditable()) return; DelegatedPencilTool::mousePressEvent(event); } QList > KisToolPencil::createOptionWidgets() { QList > widgetsList = DelegatedPencilTool::createOptionWidgets(); QList > filteredWidgets; Q_FOREACH (QWidget* widget, widgetsList) { if (widget->objectName() != "Stroke widget") { filteredWidgets.push_back(widget); } } return filteredWidgets; } __KisToolPencilLocalTool::__KisToolPencilLocalTool(KoCanvasBase * canvas, KisToolPencil* parentTool) : KoPencilTool(canvas), m_parentTool(parentTool) {} void __KisToolPencilLocalTool::paint(QPainter &painter, const KoViewConverter &converter) { - if (m_parentTool->strokeStyle() == KisPainter::StrokeStyleNone) { + if (m_parentTool->strokeStyle() == KisToolShapeUtils::StrokeStyleNone) { paintPath(path(), painter, converter); } else { KoPencilTool::paint(painter, converter); } } void __KisToolPencilLocalTool::paintPath(KoPathShape *pathShape, QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); if (!pathShape) { return; } QTransform matrix; matrix.scale(m_parentTool->image()->xRes(), m_parentTool->image()->yRes()); matrix.translate(pathShape->position().x(), pathShape->position().y()); m_parentTool->paintToolOutline(&painter, m_parentTool->pixelToView(matrix.map(pathShape->outline()))); } void __KisToolPencilLocalTool::addPathShape(KoPathShape* pathShape, bool closePath) { if (closePath) { pathShape->close(); pathShape->normalize(); } m_parentTool->addPathShape(pathShape, kundo2_i18n("Draw Freehand Path")); } void __KisToolPencilLocalTool::slotUpdatePencilCursor() { KoShapeStrokeSP stroke = this->createStroke(); m_parentTool->updatePencilCursor(stroke && stroke->isVisible()); } diff --git a/plugins/tools/basictools/kis_tool_rectangle.cc b/plugins/tools/basictools/kis_tool_rectangle.cc index 2f983d336e..cfc11aa60f 100644 --- a/plugins/tools/basictools/kis_tool_rectangle.cc +++ b/plugins/tools/basictools/kis_tool_rectangle.cc @@ -1,98 +1,102 @@ /* * kis_tool_rectangle.cc - part of Krita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2010 Cyrille Berger * * 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_rectangle.h" #include #include #include "KoCanvasBase.h" #include "kis_shape_tool_helper.h" #include "kis_figure_painting_tool_helper.h" #include #include KisToolRectangle::KisToolRectangle(KoCanvasBase * canvas) : KisToolRectangleBase(canvas, KisToolRectangleBase::PAINT, KisCursor::load("tool_rectangle_cursor.png", 6, 6)) { setSupportOutline(true); setObjectName("tool_rectangle"); } KisToolRectangle::~KisToolRectangle() { } void KisToolRectangle::resetCursorStyle() { KisToolRectangleBase::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolRectangle::finishRect(const QRectF &rect, qreal roundCornersX, qreal roundCornersY) { if (rect.isNull()) return; const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); if (!info.shouldAddShape) { KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Rectangle"), image(), currentNode(), canvas()->resourceManager(), strokeStyle(), fillStyle()); QPainterPath path; if (roundCornersX > 0 || roundCornersY > 0) { path.addRoundedRect(rect, roundCornersX, roundCornersY); } else { path.addRect(rect); } helper.paintPainterPath(path); } else { const QRectF r = convertToPt(rect); const qreal docRoundCornersX = convertToPt(roundCornersX); const qreal docRoundCornersY = convertToPt(roundCornersY); KoShape* shape = KisShapeToolHelper::createRectangleShape(r, docRoundCornersX, docRoundCornersY); KoShapeStrokeSP border; - if (strokeStyle() == KisPainter::StrokeStyleBrush) { - border = toQShared(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor())); + if (strokeStyle() != KisToolShapeUtils::StrokeStyleNone) { + const QColor color = strokeStyle() == KisToolShapeUtils::StrokeStyleForeground ? + canvas()->resourceManager()->foregroundColor().toQColor() : + canvas()->resourceManager()->backgroundColor().toQColor(); + + border = toQShared(new KoShapeStroke(currentStrokeWidth(), color)); } shape->setStroke(border); info.markAsSelectionShapeIfNeeded(shape); addShape(shape); } }