diff --git a/core_lib/src/canvaspainter.cpp b/core_lib/src/canvaspainter.cpp index 47cbef0c0b..529149764e 100644 --- a/core_lib/src/canvaspainter.cpp +++ b/core_lib/src/canvaspainter.cpp @@ -23,23 +23,30 @@ GNU General Public License for more details. #include "layerbitmap.h" #include "layervector.h" #include "bitmapimage.h" -#include "layercamera.h" #include "vectorimage.h" #include "painterutils.h" -CanvasPainter::CanvasPainter() +CanvasPainter::CanvasPainter(QPixmap& canvas) : mCanvas(canvas) { + reset(); } CanvasPainter::~CanvasPainter() { } -void CanvasPainter::setCanvas(QPixmap* canvas) +void CanvasPainter::reset() { - Q_ASSERT(canvas); - mCanvas = canvas; + mPostLayersPixmap = QPixmap(mCanvas.size()); + mPreLayersPixmap = QPixmap(mCanvas.size()); + mPreLayersPixmap.fill(Qt::transparent); + mCanvas.fill(Qt::transparent); + mCurrentLayerPixmap = QPixmap(mCanvas.size()); + mCurrentLayerPixmap.fill(Qt::transparent); + mPostLayersPixmap.fill(Qt::transparent); + mOnionSkinPixmap = QPixmap(mCanvas.size()); + mOnionSkinPixmap.fill(Qt::transparent); } void CanvasPainter::setViewTransform(const QTransform view, const QTransform viewInverse) @@ -71,101 +78,76 @@ void CanvasPainter::ignoreTransformedSelection() mRenderTransform = false; } -void CanvasPainter::paintCached() +void CanvasPainter::paintCached(const QRect& blitRect) { - QPixmap tempPixmap(mCanvas->size()); - tempPixmap.fill(Qt::transparent); - mCanvas->fill(Qt::transparent); - QPainter tempPainter; - QPainter painter; - initializePainter(tempPainter, tempPixmap); - initializePainter(painter, *mCanvas); - - if (!mPreLayersCache) + if (!mPreLayersPixmapCacheValid) { - renderPreLayers(painter); - mPreLayersCache.reset(new QPixmap(*mCanvas)); - } - else - { - painter.setWorldMatrixEnabled(false); - painter.drawPixmap(0, 0, *(mPreLayersCache.get())); - painter.setWorldMatrixEnabled(true); + QPainter preLayerPainter; + initializePainter(preLayerPainter, mPreLayersPixmap, blitRect); + renderPreLayers(preLayerPainter, blitRect); + preLayerPainter.end(); + mPreLayersPixmapCacheValid = true; } - renderCurLayer(painter); + QPainter mainPainter; + initializePainter(mainPainter, mCanvas, blitRect); + mainPainter.setWorldMatrixEnabled(false); + mainPainter.drawPixmap(blitRect, mPreLayersPixmap, blitRect); + mainPainter.setWorldMatrixEnabled(true); - if (!mPostLayersCache) - { - renderPostLayers(tempPainter); - mPostLayersCache.reset(new QPixmap(tempPixmap)); - painter.setWorldMatrixEnabled(false); - painter.drawPixmap(0, 0, tempPixmap); - painter.setWorldMatrixEnabled(true); - } - else + paintCurrentFrame(mainPainter, blitRect, mCurrentLayerIndex, mCurrentLayerIndex); + + if (!mPostLayersPixmapCacheValid) { - painter.setWorldMatrixEnabled(false); - painter.drawPixmap(0, 0, *(mPostLayersCache.get())); - painter.setWorldMatrixEnabled(true); + QPainter postLayerPainter; + initializePainter(postLayerPainter, mPostLayersPixmap, blitRect); + renderPostLayers(postLayerPainter, blitRect); + postLayerPainter.end(); + mPostLayersPixmapCacheValid = true; } + + mainPainter.setWorldMatrixEnabled(false); + mainPainter.drawPixmap(blitRect, mPostLayersPixmap, blitRect); + mainPainter.setWorldMatrixEnabled(true); } void CanvasPainter::resetLayerCache() { - mPreLayersCache.reset(); - mPostLayersCache.reset(); + mPreLayersPixmapCacheValid = false; + mPostLayersPixmapCacheValid = false; } -void CanvasPainter::initializePainter(QPainter& painter, QPixmap& pixmap) +void CanvasPainter::initializePainter(QPainter& painter, QPaintDevice& device, const QRect& blitRect) { - painter.begin(&pixmap); + painter.begin(&device); + + // Clear the area that's about to be painted again, to avoid painting on top of existing pixels + // causing artifacts. + painter.setCompositionMode(QPainter::CompositionMode_Clear); + painter.fillRect(blitRect, Qt::transparent); + + // Surface has been cleared and is ready to be painted on + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); painter.setWorldMatrixEnabled(true); painter.setWorldTransform(mViewTransform); } -void CanvasPainter::renderPreLayers(QPixmap* pixmap) -{ - QPainter painter; - initializePainter(painter, *pixmap); - renderPreLayers(painter); -} - -void CanvasPainter::renderPreLayers(QPainter& painter) +void CanvasPainter::renderPreLayers(QPainter& painter, const QRect& blitRect) { if (mOptions.eLayerVisibility != LayerVisibility::CURRENTONLY || mObject->getLayer(mCurrentLayerIndex)->type() == Layer::CAMERA) { - paintCurrentFrame(painter, 0, mCurrentLayerIndex - 1); + paintCurrentFrame(painter, blitRect, 0, mCurrentLayerIndex - 1); } - paintOnionSkin(painter); + paintOnionSkin(painter, blitRect); painter.setOpacity(1.0); } -void CanvasPainter::renderCurLayer(QPixmap* pixmap) -{ - QPainter painter; - initializePainter(painter, *pixmap); - renderCurLayer(painter); -} - -void CanvasPainter::renderCurLayer(QPainter& painter) -{ - paintCurrentFrame(painter, mCurrentLayerIndex, mCurrentLayerIndex); -} - -void CanvasPainter::renderPostLayers(QPixmap* pixmap) -{ - QPainter painter; - initializePainter(painter, *pixmap); - renderPostLayers(painter); -} - -void CanvasPainter::renderPostLayers(QPainter& painter) +void CanvasPainter::renderPostLayers(QPainter& painter, const QRect& blitRect) { if (mOptions.eLayerVisibility != LayerVisibility::CURRENTONLY || mObject->getLayer(mCurrentLayerIndex)->type() == Layer::CAMERA) { - paintCurrentFrame(painter, mCurrentLayerIndex + 1, mObject->getLayerCount() - 1); + paintCurrentFrame(painter, blitRect, mCurrentLayerIndex + 1, mObject->getLayerCount() - 1); } } @@ -181,22 +163,37 @@ void CanvasPainter::setPaintSettings(const Object* object, int currentLayer, int mBuffer = buffer; } -void CanvasPainter::paint() +void CanvasPainter::paint(const QRect& blitRect) { - QPainter painter; - initializePainter(painter, *mCanvas); + QPainter preLayerPainter; + QPainter mainPainter; + QPainter postLayerPainter; - renderPreLayers(painter); - renderCurLayer(painter); - renderPostLayers(painter); -} + initializePainter(mainPainter, mCanvas, blitRect); -void CanvasPainter::paintBackground() -{ - mCanvas->fill(Qt::transparent); + initializePainter(preLayerPainter, mPreLayersPixmap, blitRect); + renderPreLayers(preLayerPainter, blitRect); + preLayerPainter.end(); + + mainPainter.setWorldMatrixEnabled(false); + mainPainter.drawPixmap(blitRect, mPreLayersPixmap, blitRect); + mainPainter.setWorldMatrixEnabled(true); + + paintCurrentFrame(mainPainter, blitRect, mCurrentLayerIndex, mCurrentLayerIndex); + + initializePainter(postLayerPainter, mPostLayersPixmap, blitRect); + renderPostLayers(postLayerPainter, blitRect); + postLayerPainter.end(); + + mainPainter.setWorldMatrixEnabled(false); + mainPainter.drawPixmap(blitRect, mPostLayersPixmap, blitRect); + mainPainter.setWorldMatrixEnabled(true); + + mPreLayersPixmapCacheValid = true; + mPostLayersPixmapCacheValid = true; } -void CanvasPainter::paintOnionSkin(QPainter& painter) +void CanvasPainter::paintOnionSkin(QPainter& painter, const QRect& blitRect) { Layer* layer = mObject->getLayer(mCurrentLayerIndex); @@ -204,227 +201,177 @@ void CanvasPainter::paintOnionSkin(QPainter& painter) if (state == OnionSkinPaintState::PREV) { switch (layer->type()) { - case Layer::BITMAP: { paintBitmapFrame(painter, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizePrevFrames, false, false); break; } - case Layer::VECTOR: { paintVectorFrame(painter, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizePrevFrames, false, false); break; } + case Layer::BITMAP: { paintBitmapOnionSkinFrame(painter, blitRect, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizePrevFrames); break; } + case Layer::VECTOR: { paintVectorOnionSkinFrame(painter, blitRect, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizePrevFrames); break; } default: break; } } if (state == OnionSkinPaintState::NEXT) { switch (layer->type()) { - case Layer::BITMAP: { paintBitmapFrame(painter, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizeNextFrames, false, false); break; } - case Layer::VECTOR: { paintVectorFrame(painter, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizeNextFrames, false, false); break; } + case Layer::BITMAP: { paintBitmapOnionSkinFrame(painter, blitRect, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizeNextFrames); break; } + case Layer::VECTOR: { paintVectorOnionSkinFrame(painter, blitRect, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizeNextFrames); break; } default: break; } } }); } -void CanvasPainter::paintBitmapFrame(QPainter& painter, - Layer* layer, - int nFrame, - bool colorize, - bool useLastKeyFrame, - bool isCurrentFrame) +void CanvasPainter::paintBitmapOnionSkinFrame(QPainter& painter, const QRect& blitRect, Layer* layer, int nFrame, bool colorize) { -#ifdef _DEBUG - LayerBitmap* bitmapLayer = dynamic_cast(layer); - Q_ASSERT(bitmapLayer); -#else LayerBitmap* bitmapLayer = static_cast(layer); -#endif - CANVASPAINTER_LOG(" Paint Bitmap Frame = %d, UseLastKeyFrame = %d", nFrame, useLastKeyFrame); - BitmapImage* paintedImage = nullptr; - if (useLastKeyFrame) - { - paintedImage = bitmapLayer->getLastBitmapImageAtFrame(nFrame, 0); - CANVASPAINTER_LOG(" Actual frame = %d", paintedImage->pos()); - } - else - { - paintedImage = bitmapLayer->getBitmapImageAtFrame(nFrame); - } + BitmapImage* bitmapImage = bitmapLayer->getBitmapImageAtFrame(nFrame); - if (paintedImage == nullptr) { return; } - paintedImage->loadFile(); // Critical! force the BitmapImage to load the image - CANVASPAINTER_LOG(" Paint Image Size: %dx%d", paintedImage->image()->width(), paintedImage->image()->height()); + if (bitmapImage == nullptr) { return; } + bitmapImage->loadFile(); // Critical! force the BitmapImage to load the image - const bool frameIsEmpty = (paintedImage == nullptr || paintedImage->bounds().isEmpty()); - const bool isDrawing = isCurrentFrame && mBuffer && !mBuffer->bounds().isEmpty(); - if (frameIsEmpty && !isDrawing) - { - CANVASPAINTER_LOG(" Early return frame %d, %d", frameIsEmpty, isDrawing); - return; - } + QPainter onionSkinPainter; + initializePainter(onionSkinPainter, mOnionSkinPixmap, blitRect); - BitmapImage paintToImage = BitmapImage(paintedImage->bounds(), Qt::transparent); - paintToImage.paste(paintedImage); + onionSkinPainter.drawImage(bitmapImage->topLeft(), *bitmapImage->image()); + paintOnionSkinFrame(painter, onionSkinPainter, blitRect, nFrame, colorize, bitmapImage->getOpacity()); +} - painter.setOpacity(paintedImage->getOpacity() - (1.0-painter.opacity())); - if (isCurrentFrame) - { - paintToImage.paste(mBuffer, mOptions.cmBufferBlendMode); - } +void CanvasPainter::paintVectorOnionSkinFrame(QPainter& painter, const QRect& blitRect, Layer* layer, int nFrame, bool colorize) +{ + LayerVector* vectorLayer = static_cast(layer); + CANVASPAINTER_LOG("Paint Onion skin vector, Frame = %d", nFrame); + VectorImage* vectorImage = vectorLayer->getVectorImageAtFrame(nFrame); + if (vectorImage == nullptr) { return; } + + QPainter onionSkinPainter; + initializePainter(onionSkinPainter, mOnionSkinPixmap, blitRect); + + vectorImage->paintImage(onionSkinPainter, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias); + paintOnionSkinFrame(painter, onionSkinPainter, blitRect, nFrame, colorize, vectorImage->getOpacity()); +} + +void CanvasPainter::paintOnionSkinFrame(QPainter& painter, QPainter& onionSkinPainter, const QRect& blitRect, int nFrame, bool colorize, qreal frameOpacity) +{ + // Don't transform the image here as we used the viewTransform in the image output + painter.setWorldMatrixEnabled(false); + // Remember to adjust overall opacity based on opacity value from image + painter.setOpacity(frameOpacity - (1.0-painter.opacity())); if (colorize) { - QBrush colorBrush = QBrush(Qt::transparent); //no color for the current frame + QColor colorBrush = Qt::transparent; //no color for the current frame if (nFrame < mFrameNumber) { - colorBrush = QBrush(Qt::red); + colorBrush = Qt::red; } else if (nFrame > mFrameNumber) { - colorBrush = QBrush(Qt::blue); + colorBrush = Qt::blue; } + onionSkinPainter.setWorldMatrixEnabled(false); - paintToImage.drawRect(paintedImage->bounds(), - Qt::NoPen, - colorBrush, - QPainter::CompositionMode_SourceIn, - false); + onionSkinPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); + onionSkinPainter.setBrush(colorBrush); + onionSkinPainter.drawRect(painter.viewport()); } + painter.drawPixmap(blitRect, mOnionSkinPixmap, blitRect); +} - // If the current frame on the current layer has a transformation, we apply it. - bool shouldPaintTransform = mRenderTransform && nFrame == mFrameNumber && layer == mObject->getLayer(mCurrentLayerIndex); - if (shouldPaintTransform) - { - paintToImage.clear(mSelection); - } +void CanvasPainter::paintCurrentBitmapFrame(QPainter& painter, const QRect& blitRect, Layer* layer, bool isCurrentLayer) +{ + LayerBitmap* bitmapLayer = static_cast(layer); + BitmapImage* paintedImage = bitmapLayer->getLastBitmapImageAtFrame(mFrameNumber); - painter.setWorldMatrixEnabled(true); - prescale(&paintToImage); - paintToImage.paintImage(painter, mScaledBitmap, mScaledBitmap.rect(), paintToImage.bounds()); + if (paintedImage == nullptr) { return; } + paintedImage->loadFile(); // Critical! force the BitmapImage to load the image - if (shouldPaintTransform) - { - paintTransformedSelection(painter); - } -// static int cc = 0; -// QString path = QString("C:/Temp/pencil2d/canvas-%1-%2-%3.png") -// .arg(cc++, 3, 10, QChar('0')) -// .arg(layer->name()) -// .arg(mFrameNumber); -// Q_ASSERT(mCanvas->save(path)); + const bool isDrawing = mBuffer && !mBuffer->bounds().isEmpty(); -} + QPainter currentBitmapPainter; + initializePainter(currentBitmapPainter, mCurrentLayerPixmap, blitRect); -void CanvasPainter::prescale(BitmapImage* bitmapImage) -{ - QImage origImage = bitmapImage->image()->copy(); + painter.setOpacity(paintedImage->getOpacity() - (1.0-painter.opacity())); + painter.setWorldMatrixEnabled(false); - // copy content of our unmodified qimage - // to our (not yet) scaled bitmap - mScaledBitmap = origImage.copy(); + currentBitmapPainter.drawImage(paintedImage->topLeft(), *paintedImage->image()); - if (mOptions.scaling >= 1.0f) - { - // TODO: Qt doesn't handle huge upscaled qimages well... - // possible solution, myPaintLib canvas renderer splits its canvas up in chunks. - } - else - { - // map to correct matrix - QRect mappedOrigImage = mViewTransform.mapRect(bitmapImage->bounds()); - mScaledBitmap = mScaledBitmap.scaled(mappedOrigImage.size(), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + if (isCurrentLayer) { + if (isDrawing) { + // paint the current stroke data which hasn't been applied to a bitmapImage yet + currentBitmapPainter.setCompositionMode(mOptions.cmBufferBlendMode); + currentBitmapPainter.drawImage(mBuffer->topLeft(), *mBuffer->image()); + } else if (mRenderTransform) { + paintTransformedSelection(currentBitmapPainter, paintedImage, mSelection); + } } + + painter.drawPixmap(blitRect, mCurrentLayerPixmap, blitRect); } -void CanvasPainter::paintVectorFrame(QPainter& painter, - Layer* layer, - int nFrame, - bool colorize, - bool useLastKeyFrame, - bool isCurrentFrame) +void CanvasPainter::paintCurrentVectorFrame(QPainter& painter, const QRect& blitRect, Layer* layer, bool isCurrentLayer) { -#ifdef _DEBUG - LayerVector* vectorLayer = dynamic_cast(layer); - Q_ASSERT(vectorLayer); -#else LayerVector* vectorLayer = static_cast(layer); -#endif - - CANVASPAINTER_LOG("Paint Onion skin vector, Frame = %d", nFrame); - VectorImage* vectorImage = nullptr; - if (useLastKeyFrame) - { - vectorImage = vectorLayer->getLastVectorImageAtFrame(nFrame, 0); - } - else - { - vectorImage = vectorLayer->getVectorImageAtFrame(nFrame); - } + VectorImage* vectorImage = vectorLayer->getLastVectorImageAtFrame(mFrameNumber, 0); if (vectorImage == nullptr) { return; } - QImage* strokeImage = new QImage(mCanvas->size(), QImage::Format_ARGB32_Premultiplied); + QPainter currentVectorPainter; + initializePainter(currentVectorPainter, mCurrentLayerPixmap, blitRect); - if (mRenderTransform) { - vectorImage->setSelectionTransformation(mSelectionTransform); - } + const bool isDrawing = mBuffer && !mBuffer->bounds().isEmpty(); - vectorImage->outputImage(strokeImage, mViewTransform, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias); + // Paint existing vector image to the painter + vectorImage->paintImage(currentVectorPainter, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias); - // Go through a Bitmap image to paint the onion skin colour - BitmapImage rasterizedVectorImage; - rasterizedVectorImage.setImage(strokeImage); - - if (colorize) - { - QBrush colorBrush = QBrush(Qt::transparent); //no color for the current frame - - if (nFrame < mFrameNumber) - { - colorBrush = QBrush(Qt::red); - } - else if (nFrame > mFrameNumber) - { - colorBrush = QBrush(Qt::blue); + if (isCurrentLayer) { + if (isDrawing) { + currentVectorPainter.setCompositionMode(mOptions.cmBufferBlendMode); + currentVectorPainter.drawImage(mBuffer->topLeft(), *mBuffer->image()); + } else if (mRenderTransform) { + vectorImage->setSelectionTransformation(mSelectionTransform); } - rasterizedVectorImage.drawRect(strokeImage->rect(), - Qt::NoPen, colorBrush, - QPainter::CompositionMode_SourceIn, false); } // Don't transform the image here as we used the viewTransform in the image output painter.setWorldMatrixEnabled(false); + painter.setTransform(QTransform()); + // Remember to adjust opacity based on addition opacity value from image painter.setOpacity(vectorImage->getOpacity() - (1.0-painter.opacity())); - if (isCurrentFrame) - { - // Paste buffer onto image to see stroke in realtime - rasterizedVectorImage.paste(mBuffer, mOptions.cmBufferBlendMode); - } - - // Paint buffer pasted on top of vector image: - // fixes polyline not being rendered properly - rasterizedVectorImage.paintImage(painter); + painter.drawPixmap(blitRect, mCurrentLayerPixmap, blitRect); } -void CanvasPainter::paintTransformedSelection(QPainter& painter) const +void CanvasPainter::paintTransformedSelection(QPainter& painter, BitmapImage* bitmapImage, const QRect& selection) const { // Make sure there is something selected - if (mSelection.width() == 0 || mSelection.height() == 0) + if (selection.width() == 0 && selection.height() == 0) return; - Layer* layer = mObject->getLayer(mCurrentLayerIndex); + QPixmap transformedPixmap = QPixmap(mSelection.size()); + transformedPixmap.fill(Qt::transparent); - if (layer->type() == Layer::BITMAP) - { - // Get the transformed image - BitmapImage* bitmapImage = static_cast(layer)->getLastBitmapImageAtFrame(mFrameNumber, 0); - if (bitmapImage == nullptr) { return; }; - BitmapImage transformedImage = bitmapImage->transformed(mSelection, mSelectionTransform, mOptions.bAntiAlias); - - // Paint the transformation output - painter.setWorldMatrixEnabled(true); - transformedImage.paintImage(painter); - } + QPainter imagePainter(&transformedPixmap); + imagePainter.translate(-selection.topLeft()); + imagePainter.drawImage(bitmapImage->topLeft(), *bitmapImage->image()); + imagePainter.end(); + + painter.save(); + + painter.setTransform(mViewTransform); + + // Clear the painted area to make it look like the content has been erased + painter.save(); + painter.setCompositionMode(QPainter::CompositionMode_Clear); + painter.fillRect(selection, QColor(255,255,255,255)); + painter.restore(); + + // Multiply the selection and view matrix to get proper rotation and scale values + // Now the image origin will be topleft + painter.setTransform(mSelectionTransform*mViewTransform); + + // Draw the selection image separately and on top + painter.drawPixmap(selection, transformedPixmap); + painter.restore(); } /** Paints layers within the specified range for the current frame. @@ -433,30 +380,30 @@ void CanvasPainter::paintTransformedSelection(QPainter& painter) const * @param startLayer The first layer to paint (inclusive) * @param endLayer The last layer to paint (inclusive) */ -void CanvasPainter::paintCurrentFrame(QPainter& painter, int startLayer, int endLayer) +void CanvasPainter::paintCurrentFrame(QPainter& painter, const QRect& blitRect, int startLayer, int endLayer) { painter.setOpacity(1.0); - bool isCameraLayer = mObject->getLayer(mCurrentLayerIndex)->type() == Layer::CAMERA; for (int i = startLayer; i <= endLayer; ++i) { Layer* layer = mObject->getLayer(i); - if (layer->visible() == false) + if (!layer->visible()) continue; if (mOptions.eLayerVisibility == LayerVisibility::RELATED && !isCameraLayer) { painter.setOpacity(calculateRelativeOpacityForLayer(mCurrentLayerIndex, i, mOptions.fLayerVisibilityThreshold)); } + bool isCurrentLayer = mCurrentLayerIndex == i; CANVASPAINTER_LOG(" Render Layer[%d] %s", i, layer->name()); switch (layer->type()) { - case Layer::BITMAP: { paintBitmapFrame(painter, layer, mFrameNumber, false, true, i == mCurrentLayerIndex); break; } - case Layer::VECTOR: { paintVectorFrame(painter, layer, mFrameNumber, false, true, i == mCurrentLayerIndex); break; } + case Layer::BITMAP: { paintCurrentBitmapFrame(painter, blitRect, layer, isCurrentLayer); break; } + case Layer::VECTOR: { paintCurrentVectorFrame(painter, blitRect, layer, isCurrentLayer); break; } default: break; } } diff --git a/core_lib/src/canvaspainter.h b/core_lib/src/canvaspainter.h index d31ef1210b..58ad6e993d 100644 --- a/core_lib/src/canvaspainter.h +++ b/core_lib/src/canvaspainter.h @@ -50,10 +50,10 @@ class CanvasPainter { Q_DECLARE_TR_FUNCTIONS(CanvasPainter) public: - explicit CanvasPainter(); + explicit CanvasPainter(QPixmap& canvas); virtual ~CanvasPainter(); - void setCanvas(QPixmap* canvas); + void reset(); void setViewTransform(const QTransform view, const QTransform viewInverse); void setOnionSkinOptions(const OnionSkinPainterOptions& onionSkinOptions) { mOnionSkinPainterOptions = onionSkinOptions;} @@ -62,8 +62,8 @@ class CanvasPainter void ignoreTransformedSelection(); void setPaintSettings(const Object* object, int currentLayer, int frame, QRect rect, BitmapImage* buffer); - void paint(); - void paintCached(); + void paint(const QRect& blitRect); + void paintCached(const QRect& blitRect); void resetLayerCache(); private: @@ -73,34 +73,30 @@ class CanvasPainter * Enriches the painter with a context and sets it's initial matrix. * @param painter The in/out painter * @param pixmap The paint device ie. a pixmap + * @param blitRect The rect where the blitting will occur */ - void initializePainter(QPainter& painter, QPixmap& pixmap); + void initializePainter(QPainter& painter, QPaintDevice& device, const QRect& blitRect); - void renderPreLayers(QPainter& painter); - void renderCurLayer(QPainter& painter); - void renderPostLayers(QPainter& painter); + void paintOnionSkin(QPainter& painter, const QRect& blitRect); - void paintBackground(); - void paintOnionSkin(QPainter& painter); + void renderPostLayers(QPainter& painter, const QRect& blitRect); + void renderPreLayers(QPainter& painter, const QRect& blitRect); - void renderPostLayers(QPixmap* pixmap); - void renderCurLayer(QPixmap* pixmap); - void renderPreLayers(QPixmap* pixmap); + void paintCurrentFrame(QPainter& painter, const QRect& blitRect, int startLayer, int endLayer); - void paintCurrentFrame(QPainter& painter, int startLayer, int endLayer); + void paintTransformedSelection(QPainter& painter, BitmapImage* bitmapImage, const QRect& selection) const; - void paintBitmapFrame(QPainter&, Layer* layer, int nFrame, bool colorize, bool useLastKeyFrame, bool isCurrentFrame); - void paintVectorFrame(QPainter&, Layer* layer, int nFrame, bool colorize, bool useLastKeyFrame, bool isCurrentFrame); + void paintBitmapOnionSkinFrame(QPainter& painter, const QRect& blitRect, Layer* layer, int nFrame, bool colorize); + void paintVectorOnionSkinFrame(QPainter& painter, const QRect& blitRect, Layer* layer, int nFrame, bool colorize); + void paintOnionSkinFrame(QPainter& painter, QPainter& onionSkinPainter, const QRect& blitRect, int nFrame, bool colorize, qreal frameOpacity); - void paintTransformedSelection(QPainter& painter) const; - void prescale(BitmapImage* bitmapImage); - -private: + void paintCurrentBitmapFrame(QPainter& painter, const QRect& blitRect, Layer* layer, bool isCurrentLayer); + void paintCurrentVectorFrame(QPainter& painter, const QRect& blitRect, Layer* layer, bool isCurrentLayer); CanvasPainterOptions mOptions; const Object* mObject = nullptr; - QPixmap* mCanvas = nullptr; + QPixmap& mCanvas; QTransform mViewTransform; QTransform mViewInverse; @@ -116,7 +112,13 @@ class CanvasPainter QTransform mSelectionTransform; // Caches specifically for when drawing on the canvas - std::unique_ptr mPreLayersCache, mPostLayersCache; + QPixmap mPostLayersPixmap; + QPixmap mPreLayersPixmap; + QPixmap mCurrentLayerPixmap; + QPixmap mOnionSkinPixmap; + bool mPreLayersPixmapCacheValid = false; + bool mPostLayersPixmapCacheValid = false; + OnionSkinSubPainter mOnionSkinSubPainter; OnionSkinPainterOptions mOnionSkinPainterOptions; diff --git a/core_lib/src/graphics/vector/beziercurve.cpp b/core_lib/src/graphics/vector/beziercurve.cpp index dc49d5331e..5410d5eef4 100644 --- a/core_lib/src/graphics/vector/beziercurve.cpp +++ b/core_lib/src/graphics/vector/beziercurve.cpp @@ -279,7 +279,7 @@ void BezierCurve::setFilled(bool YesOrNo) mFilled = YesOrNo; } -BezierCurve BezierCurve::transformed(QTransform transformation) +BezierCurve BezierCurve::transformed(QTransform transformation) const { BezierCurve transformedCurve = *this; // copy the curve if (isSelected(-1)) { transformedCurve.setOrigin(transformation.map(origin)); } @@ -621,7 +621,8 @@ QPainterPath BezierCurve::getStrokedPath(qreal width, bool usePressure) QRectF BezierCurve::getBoundingRect() { - return getSimplePath().boundingRect(); + qreal radius = getWidth() / 2; + return getSimplePath().boundingRect().adjusted(-radius, -radius, radius, radius); } void BezierCurve::createCurve(const QList& pointList, const QList& pressureList, bool smooth) diff --git a/core_lib/src/graphics/vector/beziercurve.h b/core_lib/src/graphics/vector/beziercurve.h index b68044b572..0f1b67f301 100644 --- a/core_lib/src/graphics/vector/beziercurve.h +++ b/core_lib/src/graphics/vector/beziercurve.h @@ -74,7 +74,7 @@ class BezierCurve void setSelected(int i, bool YesOrNo); void setFilled(bool yesOrNo); - BezierCurve transformed(QTransform transformation); + BezierCurve transformed(QTransform transformation) const; void transform(QTransform transformation); void appendCubic(const QPointF& c1Point, const QPointF& c2Point, const QPointF& vertexPoint, qreal pressureValue); diff --git a/core_lib/src/graphics/vector/vectorimage.cpp b/core_lib/src/graphics/vector/vectorimage.cpp index 3735d39db5..18108742be 100644 --- a/core_lib/src/graphics/vector/vectorimage.cpp +++ b/core_lib/src/graphics/vector/vectorimage.cpp @@ -863,6 +863,21 @@ void VectorImage::setSelectionRect(QRectF rectangle) select(rectangle); } +QRectF VectorImage::getBoundsOfTransformedCurves() const +{ + QRectF bounds; + for (int i = 0; i < mCurves.size(); i++) + { + BezierCurve curve; + if (mCurves.at(i).isPartlySelected()) + { + curve = mCurves[i].transformed(mSelectionTransformation); + bounds |= curve.getBoundingRect(); + } + } + return bounds; +} + /** * @brief VectorImage::calculateSelectionRect */ @@ -1235,26 +1250,6 @@ void VectorImage::paintImage(QPainter& painter, } } -/** - * @brief VectorImage::outputImage - * @param image: QImage* - * @param myView: QTransform - * @param simplified: bool - * @param showThinCurves: bool - * @param antialiasing: bool - */ -void VectorImage::outputImage(QImage* image, - QTransform myView, - bool simplified, - bool showThinCurves, - bool antialiasing) -{ - image->fill(qRgba(0, 0, 0, 0)); - QPainter painter(image); - painter.setTransform(myView); - paintImage(painter, simplified, showThinCurves, antialiasing); -} - /** * @brief VectorImage::clear */ diff --git a/core_lib/src/graphics/vector/vectorimage.h b/core_lib/src/graphics/vector/vectorimage.h index 0a31cccd98..ec97458a53 100644 --- a/core_lib/src/graphics/vector/vectorimage.h +++ b/core_lib/src/graphics/vector/vectorimage.h @@ -28,7 +28,6 @@ class Object; class QPainter; class QImage; - class VectorImage : public KeyFrame { public: @@ -80,6 +79,8 @@ class VectorImage : public KeyFrame void deleteSelectedPoints(); void removeVertex(int curve, int vertex); + QRectF getBoundsOfTransformedCurves() const; + bool isEmpty() const { return mCurves.isEmpty(); } void paste(VectorImage&); @@ -93,7 +94,6 @@ class VectorImage : public KeyFrame void moveColor(int start, int end); void paintImage(QPainter& painter, bool simplified, bool showThinCurves, bool antialiasing); - void outputImage(QImage* image, QTransform myView, bool simplified, bool showThinCurves, bool antialiasing); // uses paintImage void clear(); void clean(); diff --git a/core_lib/src/interface/scribblearea.cpp b/core_lib/src/interface/scribblearea.cpp index 8b65f1f45f..fcd978f421 100644 --- a/core_lib/src/interface/scribblearea.cpp +++ b/core_lib/src/interface/scribblearea.cpp @@ -31,6 +31,7 @@ GNU General Public License for more details. #include "layercamera.h" #include "bitmapimage.h" #include "vectorimage.h" +#include "blitrect.h" #include "onionskinpainteroptions.h" @@ -43,7 +44,7 @@ GNU General Public License for more details. #include "selectionmanager.h" #include "overlaymanager.h" -ScribbleArea::ScribbleArea(QWidget* parent) : QWidget(parent) +ScribbleArea::ScribbleArea(QWidget* parent) : QWidget(parent), mCanvasPainter(mCanvas) { setObjectName("ScribbleArea"); @@ -56,7 +57,6 @@ ScribbleArea::ScribbleArea(QWidget* parent) : QWidget(parent) ScribbleArea::~ScribbleArea() { - delete mBufferImg; } bool ScribbleArea::init() @@ -89,8 +89,6 @@ bool ScribbleArea::init() mLayerVisibility = static_cast(mPrefs->getInt(SETTING::LAYER_VISIBILITY)); - mBufferImg = new BitmapImage; - updateCanvasCursor(); setMouseTracking(true); // reacts to mouse move events, even if the button is not pressed @@ -174,13 +172,13 @@ void ScribbleArea::updateToolCursor() void ScribbleArea::setCurveSmoothing(int newSmoothingLevel) { mCurveSmoothingLevel = newSmoothingLevel / 20.0; - invalidateCaches(); + invalidatePainterCaches(); } void ScribbleArea::setEffect(SETTING e, bool isOn) { mPrefs->set(e, isOn); - invalidateCaches(); + invalidatePainterCaches(); } /************************************************************************************/ @@ -253,7 +251,7 @@ void ScribbleArea::invalidateAllCache() { QPixmapCache::clear(); mPixmapCacheKeys.clear(); - invalidateCaches(); + invalidatePainterCaches(); mEditor->layers()->currentLayer()->clearDirtyFrames(); update(); @@ -270,7 +268,7 @@ void ScribbleArea::invalidateCacheForFrame(int frameNumber) } } -void ScribbleArea::invalidateCaches() +void ScribbleArea::invalidatePainterCaches() { mCameraPainter.resetCache(); mCanvasPainter.resetLayerCache(); @@ -304,7 +302,7 @@ void ScribbleArea::onPlayStateChanged() int currentFrame = mEditor->currentFrame(); if (mPrefs->isOn(SETTING::PREV_ONION) || mPrefs->isOn(SETTING::NEXT_ONION)) { - invalidateCaches(); + invalidatePainterCaches(); } prepOverlays(currentFrame); @@ -315,7 +313,7 @@ void ScribbleArea::onPlayStateChanged() void ScribbleArea::onScrubbed(int frameNumber) { - invalidateCaches(); + invalidatePainterCaches(); updateFrame(frameNumber); } @@ -323,7 +321,7 @@ void ScribbleArea::onFramesModified() { invalidateCacheForDirtyFrames(); if (mPrefs->isOn(SETTING::PREV_ONION) || mPrefs->isOn(SETTING::NEXT_ONION)) { - invalidateCaches(); + invalidatePainterCaches(); } update(); } @@ -332,7 +330,7 @@ void ScribbleArea::onFrameModified(int frameNumber) { if (mPrefs->isOn(SETTING::PREV_ONION) || mPrefs->isOn(SETTING::NEXT_ONION)) { invalidateOnionSkinsCacheAround(frameNumber); - invalidateCaches(); + invalidatePainterCaches(); } invalidateCacheForFrame(frameNumber); updateFrame(frameNumber); @@ -792,12 +790,12 @@ void ScribbleArea::resizeEvent(QResizeEvent* event) QWidget::resizeEvent(event); mDevicePixelRatio = devicePixelRatioF(); mCanvas = QPixmap(QSizeF(size() * mDevicePixelRatio).toSize()); - mCanvas.fill(Qt::transparent); mEditor->view()->setCanvasSize(size()); invalidateCacheForFrame(mEditor->currentFrame()); - invalidateCaches(); + invalidatePainterCaches(); + mCanvasPainter.reset(); } void ScribbleArea::showLayerNotVisibleWarning() @@ -845,10 +843,10 @@ void ScribbleArea::paintBitmapBuffer() default: //nothing break; } - targetImage->paste(mBufferImg, cm); + targetImage->paste(&mBufferImg, cm); } - QRect rect = mEditor->view()->mapCanvasToScreen(mBufferImg->bounds()).toRect(); + QRect rect = mEditor->view()->mapCanvasToScreen(mBufferImg.bounds()).toRect(); drawCanvas(frameNumber, rect.adjusted(-1, -1, 1, 1)); update(rect); @@ -857,83 +855,23 @@ void ScribbleArea::paintBitmapBuffer() updateFrame(frameNumber); layer->setModified(frameNumber, true); - mBufferImg->clear(); -} - -void ScribbleArea::paintBitmapBufferRect(const QRect& rect) -{ - if (mEditor->playback()->isPlaying()) - { - Layer* layer = mEditor->layers()->currentLayer(); - Q_ASSERT(layer); - - BitmapImage* targetImage = currentBitmapImage(layer); - - if (targetImage != nullptr) - { - QPainter::CompositionMode cm = QPainter::CompositionMode_SourceOver; - switch (currentTool()->type()) - { - case ERASER: - cm = QPainter::CompositionMode_DestinationOut; - break; - case BRUSH: - case PEN: - case PENCIL: - if (getTool(currentTool()->type())->properties.preserveAlpha) - { - cm = QPainter::CompositionMode_SourceAtop; - } - break; - default: //nothing - break; - } - targetImage->paste(mBufferImg, cm); - } - - // Clear the buffer - mBufferImg->clear(); - - int frameNumber = mEditor->currentFrame(); - layer->setModified(frameNumber, true); - - updateFrame(frameNumber); - - drawCanvas(frameNumber, rect.adjusted(-1, -1, 1, 1)); - update(rect); - } + mBufferImg.clear(); } void ScribbleArea::clearBitmapBuffer() { - mBufferImg->clear(); + mBufferImg.clear(); } void ScribbleArea::drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm) { - mBufferImg->drawLine(P1, P2, pen, cm, mPrefs->isOn(SETTING::ANTIALIAS)); + mBufferImg.drawLine(P1, P2, pen, cm, mPrefs->isOn(SETTING::ANTIALIAS)); } void ScribbleArea::drawPath(QPainterPath path, QPen pen, QBrush brush, QPainter::CompositionMode cm) { - mBufferImg->drawPath(path, pen, brush, cm, mPrefs->isOn(SETTING::ANTIALIAS)); -} - -void ScribbleArea::refreshBitmap(const QRectF& rect, int rad) -{ - QRectF updatedRect = mEditor->view()->mapCanvasToScreen(rect.normalized().adjusted(-rad, -rad, +rad, +rad)); - update(updatedRect.toRect()); -} - -void ScribbleArea::refreshVector(const QRectF& rect, int rad) -{ - rad += 1; - //QRectF updatedRect = mEditor->view()->mapCanvasToScreen( rect.normalized().adjusted( -rad, -rad, +rad, +rad ) ); - update(rect.normalized().adjusted(-rad, -rad, +rad, +rad).toRect()); - - //qDebug() << "Logical: " << rect; - //qDebug() << "Physical: " << mEditor->view()->mapCanvasToScreen( rect.normalized() ); - //update(); + mBufferImg.drawPath(mEditor->view()->mapScreenToCanvas(path), pen, brush, cm, mPrefs->isOn(SETTING::ANTIALIAS)); + update(mEditor->view()->mapCanvasToScreen(mBufferImg.bounds()).toRect().adjusted(-1, -1, 1, 1)); } void ScribbleArea::paintCanvasCursor(QPainter& painter) @@ -1077,7 +1015,7 @@ void ScribbleArea::paintEvent(QPaintEvent* event) prepCameraPainter(currentFrame); prepOverlays(currentFrame); - mCanvasPainter.paintCached(); + mCanvasPainter.paintCached(event->rect()); mCameraPainter.paintCached(); } @@ -1290,24 +1228,21 @@ void ScribbleArea::prepCanvas(int frame, QRect rect) mCanvasPainter.setOnionSkinOptions(onionSkinOptions); mCanvasPainter.setOptions(o); - mCanvasPainter.setCanvas(&mCanvas); - ViewManager* vm = mEditor->view(); SelectionManager* sm = mEditor->select(); mCanvasPainter.setViewTransform(vm->getView(), vm->getViewInverse()); mCanvasPainter.setTransformedSelection(sm->mySelectionRect().toRect(), sm->selectionTransform()); - mCanvasPainter.setPaintSettings(object, mEditor->layers()->currentLayerIndex(), frame, rect, mBufferImg); + mCanvasPainter.setPaintSettings(object, mEditor->layers()->currentLayerIndex(), frame, rect, &mBufferImg); } void ScribbleArea::drawCanvas(int frame, QRect rect) { - mCanvas.fill(Qt::transparent); mCanvas.setDevicePixelRatio(mDevicePixelRatio); prepCanvas(frame, rect); prepCameraPainter(frame); prepOverlays(frame); - mCanvasPainter.paint(); + mCanvasPainter.paint(rect); mCameraPainter.paint(); } @@ -1335,8 +1270,9 @@ void ScribbleArea::drawPen(QPointF thePoint, qreal brushWidth, QColor fillColor, { QRectF rectangle(thePoint.x() - 0.5 * brushWidth, thePoint.y() - 0.5 * brushWidth, brushWidth, brushWidth); - mBufferImg->drawEllipse(rectangle, Qt::NoPen, QBrush(fillColor, Qt::SolidPattern), + mBufferImg.drawEllipse(rectangle, Qt::NoPen, QBrush(fillColor, Qt::SolidPattern), QPainter::CompositionMode_Source, useAA); + update(mEditor->view()->mapCanvasToScreen(mBufferImg.bounds()).toRect().adjusted(-1, -1, 1, 1)); } void ScribbleArea::drawPencil(QPointF thePoint, qreal brushWidth, qreal fixedBrushFeather, QColor fillColor, qreal opacity) @@ -1353,14 +1289,16 @@ void ScribbleArea::drawBrush(QPointF thePoint, qreal brushWidth, qreal mOffset, QRadialGradient radialGrad(thePoint, 0.5 * brushWidth); setGaussianGradient(radialGrad, fillColor, opacity, mOffset); - mBufferImg->drawEllipse(rectangle, Qt::NoPen, radialGrad, + mBufferImg.drawEllipse(rectangle, Qt::NoPen, radialGrad, QPainter::CompositionMode_SourceOver, false); } else { - mBufferImg->drawEllipse(rectangle, Qt::NoPen, QBrush(fillColor, Qt::SolidPattern), + mBufferImg.drawEllipse(rectangle, Qt::NoPen, QBrush(fillColor, Qt::SolidPattern), QPainter::CompositionMode_SourceOver, useAA); } + + update(mEditor->view()->mapCanvasToScreen(mBufferImg.bounds()).toRect().adjusted(-1, -1, 1, 1)); } void ScribbleArea::flipSelection(bool flipVertical) @@ -1419,7 +1357,9 @@ void ScribbleArea::blurBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF bmiTmpClip.drawRect(srcRect, Qt::NoPen, radialGrad, QPainter::CompositionMode_Source, mPrefs->isOn(SETTING::ANTIALIAS)); bmiSrcClip.bounds().moveTo(trgRect.topLeft().toPoint()); bmiTmpClip.paste(&bmiSrcClip, QPainter::CompositionMode_SourceIn); - mBufferImg->paste(&bmiTmpClip); + mBufferImg.paste(&bmiTmpClip); + + update(mEditor->view()->mapCanvasToScreen(mBufferImg.bounds()).toRect().adjusted(-1, -1, 1, 1)); } void ScribbleArea::liquifyBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF thePoint_, qreal brushWidth_, qreal mOffset_, qreal opacity_) @@ -1471,19 +1411,28 @@ void ScribbleArea::liquifyBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPoi } } } - mBufferImg->paste(&bmiTmpClip); + mBufferImg.paste(&bmiTmpClip); + + update(mEditor->view()->mapCanvasToScreen(mBufferImg.bounds()).toRect().adjusted(-1, -1, 1, 1)); } void ScribbleArea::drawPolyline(QPainterPath path, QPen pen, bool useAA) { - QRectF updateRect = mEditor->view()->mapCanvasToScreen(path.boundingRect().toRect()).adjusted(-1, -1, 1, 1); + BlitRect blitRect; + + // In order to clear what was previous dirty, we need to include the previous buffer bound + // this ensures that we won't see stroke artifacts + blitRect.extend(mEditor->view()->mapCanvasToScreen(mBufferImg.bounds()).toRect()); + + QRect updateRect = mEditor->view()->mapCanvasToScreen(path.boundingRect()).toRect(); + // Now extend with the new path bounds mapped to the local coordinate + blitRect.extend(updateRect); - // Update region outside updateRect - QRectF boundingRect = updateRect.adjusted(-width(), -height(), width(), height()); - mBufferImg->clear(); - mBufferImg->drawPath(path, pen, Qt::NoBrush, QPainter::CompositionMode_SourceOver, useAA); - update(boundingRect.toRect()); + mBufferImg.clear(); + mBufferImg.drawPath(path, pen, Qt::NoBrush, QPainter::CompositionMode_SourceOver, useAA); + // And update only the affected area + update(blitRect.adjusted(-1, -1, 1, 1)); } /************************************************************************************/ diff --git a/core_lib/src/interface/scribblearea.h b/core_lib/src/interface/scribblearea.h index afa3e4e070..9f89da382e 100644 --- a/core_lib/src/interface/scribblearea.h +++ b/core_lib/src/interface/scribblearea.h @@ -189,11 +189,8 @@ public slots: void liquifyBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF thePoint_, qreal brushWidth_, qreal offset_, qreal opacity_); void paintBitmapBuffer(); - void paintBitmapBufferRect(const QRect& rect); void paintCanvasCursor(QPainter& painter); void clearBitmapBuffer(); - void refreshBitmap(const QRectF& rect, int rad); - void refreshVector(const QRectF& rect, int rad); void setGaussianGradient(QGradient &gradient, QColor color, qreal opacity, qreal offset); void pointerPressEvent(PointerEvent*); @@ -206,7 +203,7 @@ public slots: /// on an empty frame, and if so, takes action according to use preference. void handleDrawingOnEmptyFrame(); - BitmapImage* mBufferImg = nullptr; // used to pre-draw vector modifications + BitmapImage mBufferImg; // used to draw strokes for both bitmap and vector QPixmap mCursorImg; QPixmap mTransCursImg; @@ -217,7 +214,7 @@ public slots: * Call this in most situations where the layer rendering order is affected. * Peviously known as setAllDirty. */ - void invalidateCaches(); + void invalidatePainterCaches(); /** Invalidate cache for the given frame */ void invalidateCacheForFrame(int frameNumber); @@ -244,8 +241,6 @@ public slots: MoveMode mMoveMode = MoveMode::NONE; - BitmapImage mBitmapSelection; // used to temporary store a transformed portion of a bitmap image - std::unique_ptr mStrokeManager; Editor* mEditor = nullptr; diff --git a/core_lib/src/tool/brushtool.cpp b/core_lib/src/tool/brushtool.cpp index 6b51ffd15c..8461e285d7 100644 --- a/core_lib/src/tool/brushtool.cpp +++ b/core_lib/src/tool/brushtool.cpp @@ -25,7 +25,6 @@ GNU General Public License for more details. #include "beziercurve.h" #include "vectorimage.h" -#include "layervector.h" #include "editor.h" #include "colormanager.h" #include "strokemanager.h" @@ -33,7 +32,6 @@ GNU General Public License for more details. #include "viewmanager.h" #include "selectionmanager.h" #include "scribblearea.h" -#include "blitrect.h" #include "pointerevent.h" @@ -194,17 +192,12 @@ void BrushTool::paintAt(QPointF point) qreal opacity = (properties.pressure) ? (mCurrentPressure * 0.5) : 1.0; qreal brushWidth = properties.width * pressure; mCurrentWidth = brushWidth; - - BlitRect rect(point.toPoint()); mScribbleArea->drawBrush(point, brushWidth, properties.feather, mEditor->color()->frontColor(), opacity, true); - - int rad = qRound(brushWidth) / 2 + 2; - mScribbleArea->refreshBitmap(rect, rad); } } @@ -217,11 +210,6 @@ void BrushTool::drawStroke() if (layer->type() == Layer::BITMAP) { - for (int i = 0; i < p.size(); i++) - { - p[i] = mEditor->view()->mapScreenToCanvas(p[i]); - } - qreal pressure = (properties.pressure) ? mCurrentPressure : 1.0; qreal opacity = (properties.pressure) ? (mCurrentPressure * 0.5) : 1.0; qreal brushWidth = properties.width * pressure; @@ -230,8 +218,6 @@ void BrushTool::drawStroke() qreal brushStep = (0.5 * brushWidth); brushStep = qMax(1.0, brushStep); - BlitRect rect; - QPointF a = mLastBrushPoint; QPointF b = getCurrentPoint(); @@ -242,7 +228,6 @@ void BrushTool::drawStroke() { QPointF point = mLastBrushPoint + (i + 1) * brushStep * (getCurrentPoint() - mLastBrushPoint) / distance; - rect.extend(point.toPoint()); mScribbleArea->drawBrush(point, brushWidth, properties.feather, @@ -255,11 +240,6 @@ void BrushTool::drawStroke() } } - int rad = qRound(brushWidth / 2 + 2); - - mScribbleArea->paintBitmapBufferRect(rect); - mScribbleArea->refreshBitmap(rect, rad); - // Line visualizer // for debugging // QPainterPath tempPath; @@ -281,10 +261,8 @@ void BrushTool::drawStroke() qreal pressure = (properties.pressure) ? mCurrentPressure : 1; qreal brushWidth = properties.width * pressure; - int rad = qRound((brushWidth / 2 + 2) * mEditor->view()->scaling()); - QPen pen(mEditor->color()->frontColor(), - brushWidth * mEditor->view()->scaling(), + brushWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); @@ -295,7 +273,6 @@ void BrushTool::drawStroke() path.cubicTo(p[1], p[2], p[3]); mScribbleArea->drawPath(path, pen, Qt::NoBrush, QPainter::CompositionMode_Source); - mScribbleArea->refreshVector(path.boundingRect().toRect(), rad); } } } diff --git a/core_lib/src/tool/buckettool.cpp b/core_lib/src/tool/buckettool.cpp index 74d3ed8ea2..2ef4f01aa5 100644 --- a/core_lib/src/tool/buckettool.cpp +++ b/core_lib/src/tool/buckettool.cpp @@ -202,7 +202,7 @@ void BucketTool::pointerPressEvent(PointerEvent* event) // Because we can change layer to on the fly but we do not act reactively on it // it's neccesary to invalidate layer cache on press event, otherwise the cache // will be drawn until a move event has been initiated. - mScribbleArea->invalidateCaches(); + mScribbleArea->invalidatePainterCaches(); } void BucketTool::pointerMoveEvent(PointerEvent* event) @@ -276,7 +276,7 @@ void BucketTool::paintBitmap() // otherwise dragging won't show until release event if (properties.bucketFillToLayerMode == 1) { - mScribbleArea->invalidateCaches(); + mScribbleArea->invalidatePainterCaches(); } } }); @@ -339,7 +339,6 @@ void BucketTool::drawStroke() QPainterPath path(p[0]); path.cubicTo(p[1], p[2], p[3]); mScribbleArea->drawPath(path, pen, Qt::NoBrush, QPainter::CompositionMode_Source); - mScribbleArea->refreshVector(path.boundingRect().toRect(), rad); } } } diff --git a/core_lib/src/tool/erasertool.cpp b/core_lib/src/tool/erasertool.cpp index b2d1744a34..ba7ecf6379 100644 --- a/core_lib/src/tool/erasertool.cpp +++ b/core_lib/src/tool/erasertool.cpp @@ -195,7 +195,6 @@ void EraserTool::paintAt(QPointF point) qreal brushWidth = properties.width * pressure; mCurrentWidth = brushWidth; - BlitRect rect(point.toPoint()); mScribbleArea->drawBrush(point, brushWidth, properties.feather, @@ -203,13 +202,6 @@ void EraserTool::paintAt(QPointF point) opacity, properties.useFeather, properties.useAA == ON); - - int rad = qRound(brushWidth / 2 + 2); - - //continuously update buffer to update stroke behind grid. - mScribbleArea->paintBitmapBufferRect(rect); - - mScribbleArea->refreshBitmap(rect, rad); } } @@ -222,11 +214,6 @@ void EraserTool::drawStroke() if (layer->type() == Layer::BITMAP) { - for (int i = 0; i < p.size(); i++) - { - p[i] = mEditor->view()->mapScreenToCanvas(p[i]); - } - qreal pressure = (properties.pressure) ? mCurrentPressure : 1.0; qreal opacity = (properties.pressure) ? (mCurrentPressure * 0.5) : 1.0; qreal brushWidth = properties.width * pressure; @@ -247,7 +234,6 @@ void EraserTool::drawStroke() { QPointF point = mLastBrushPoint + (i + 1) * brushStep * (getCurrentPoint() - mLastBrushPoint) / distance; - rect.extend(point.toPoint()); mScribbleArea->drawBrush(point, brushWidth, properties.feather, @@ -260,11 +246,6 @@ void EraserTool::drawStroke() mLastBrushPoint = getCurrentPoint(); } } - - int rad = qRound(brushWidth / 2 + 2); - - mScribbleArea->paintBitmapBufferRect(rect); - mScribbleArea->refreshBitmap(rect, rad); } else if (layer->type() == Layer::VECTOR) { @@ -276,7 +257,6 @@ void EraserTool::drawStroke() qreal brushWidth = mCurrentWidth; QPen pen(Qt::white, brushWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); - int rad = qRound(brushWidth) / 2 + 2; if (p.size() == 4) { @@ -284,9 +264,7 @@ void EraserTool::drawStroke() path.cubicTo(p[1], p[2], p[3]); - qDebug() << path; mScribbleArea->drawPath(path, pen, Qt::NoBrush, QPainter::CompositionMode_Source); - mScribbleArea->refreshVector(path.boundingRect().toRect(), rad); } } } diff --git a/core_lib/src/tool/penciltool.cpp b/core_lib/src/tool/penciltool.cpp index 55a1672740..e1249b67f4 100644 --- a/core_lib/src/tool/penciltool.cpp +++ b/core_lib/src/tool/penciltool.cpp @@ -207,16 +207,11 @@ void PencilTool::paintAt(QPointF point) qreal fixedBrushFeather = properties.feather; mCurrentWidth = brushWidth; - - BlitRect rect(point.toPoint()); mScribbleArea->drawPencil(point, brushWidth, fixedBrushFeather, mEditor->color()->frontColor(), opacity); - - int rad = qRound(brushWidth) / 2 + 2; - mScribbleArea->refreshBitmap(rect, rad); } } @@ -238,8 +233,6 @@ void PencilTool::drawStroke() qreal fixedBrushFeather = properties.feather; qreal brushStep = qMax(1.0, (0.5 * brushWidth)); - BlitRect rect; - QPointF a = mLastBrushPoint; QPointF b = getCurrentPoint(); @@ -249,7 +242,6 @@ void PencilTool::drawStroke() for (int i = 0; i < steps; i++) { QPointF point = mLastBrushPoint + (i + 1) * brushStep * (getCurrentPoint() - mLastBrushPoint) / distance; - rect.extend(point.toPoint()); mScribbleArea->drawPencil(point, brushWidth, fixedBrushFeather, @@ -261,11 +253,6 @@ void PencilTool::drawStroke() mLastBrushPoint = getCurrentPoint(); } } - - int rad = qRound(brushWidth) / 2 + 2; - - mScribbleArea->paintBitmapBufferRect(rect); - mScribbleArea->refreshBitmap(rect, rad); } else if (layer->type() == Layer::VECTOR) { @@ -277,8 +264,6 @@ void PencilTool::drawStroke() Qt::RoundCap, Qt::RoundJoin); - int rad = qRound((mCurrentWidth / 2 + 2) * mEditor->view()->scaling()); - if (p.size() == 4) { QPainterPath path(p[0]); @@ -286,7 +271,6 @@ void PencilTool::drawStroke() p[2], p[3]); mScribbleArea->drawPath(path, pen, Qt::NoBrush, QPainter::CompositionMode_Source); - mScribbleArea->refreshVector(path.boundingRect().toRect(), rad); } } } diff --git a/core_lib/src/tool/pentool.cpp b/core_lib/src/tool/pentool.cpp index 5d18117bd1..62f9996591 100644 --- a/core_lib/src/tool/pentool.cpp +++ b/core_lib/src/tool/pentool.cpp @@ -163,8 +163,6 @@ void PenTool::pointerReleaseEvent(PointerEvent *event) // draw a single paint dab at the given location void PenTool::paintAt(QPointF point) { - //qDebug() << "Made a single dab at " << point; - Layer* layer = mEditor->layers()->currentLayer(); if (layer->type() == Layer::BITMAP) { @@ -176,11 +174,6 @@ void PenTool::paintAt(QPointF point) brushWidth, mEditor->color()->frontColor(), properties.useAA); - - int rad = qRound(brushWidth) / 2 + 2; - - BlitRect rect(point.toPoint()); - mScribbleArea->refreshBitmap(rect, rad); } } @@ -193,11 +186,6 @@ void PenTool::drawStroke() if (layer->type() == Layer::BITMAP) { - for (int i = 0; i < p.size(); i++) - { - p[i] = mEditor->view()->mapScreenToCanvas(p[i]); - } - qreal pressure = (properties.pressure) ? mCurrentPressure : 1.0; qreal brushWidth = properties.width * pressure; mCurrentWidth = brushWidth; @@ -207,8 +195,6 @@ void PenTool::drawStroke() qreal brushStep = (0.5 * brushWidth); brushStep = qMax(1.0, brushStep); - BlitRect rect; - QPointF a = mLastBrushPoint; QPointF b = getCurrentPoint(); @@ -218,7 +204,6 @@ void PenTool::drawStroke() for (int i = 0; i < steps; i++) { QPointF point = mLastBrushPoint + (i + 1) * brushStep * (getCurrentPoint() - mLastBrushPoint) / distance; - rect.extend(point.toPoint()); mScribbleArea->drawPen(point, brushWidth, mEditor->color()->frontColor(), @@ -229,21 +214,14 @@ void PenTool::drawStroke() mLastBrushPoint = getCurrentPoint(); } } - - int rad = qRound(brushWidth) / 2 + 2; - - mScribbleArea->paintBitmapBufferRect(rect); - mScribbleArea->refreshBitmap(rect, rad); } else if (layer->type() == Layer::VECTOR) { qreal pressure = (properties.pressure) ? mCurrentPressure : 1.0; qreal brushWidth = properties.width * pressure; - int rad = qRound((brushWidth / 2 + 2) * mEditor->view()->scaling()); - QPen pen(mEditor->color()->frontColor(), - brushWidth * mEditor->view()->scaling(), + brushWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); @@ -253,7 +231,6 @@ void PenTool::drawStroke() QPainterPath path(p[0]); path.cubicTo(p[1], p[2], p[3]); mScribbleArea->drawPath(path, pen, Qt::NoBrush, QPainter::CompositionMode_Source); - mScribbleArea->refreshVector(path.boundingRect().toRect(), rad); } } } diff --git a/core_lib/src/tool/polylinetool.cpp b/core_lib/src/tool/polylinetool.cpp index dbe9297a92..866753e002 100644 --- a/core_lib/src/tool/polylinetool.cpp +++ b/core_lib/src/tool/polylinetool.cpp @@ -21,7 +21,6 @@ GNU General Public License for more details. #include "editor.h" #include "scribblearea.h" -#include "strokemanager.h" #include "layermanager.h" #include "colormanager.h" #include "viewmanager.h" @@ -224,7 +223,6 @@ void PolylineTool::drawPolyline(QList points, QPointF endPoint) { if (mEditor->layers()->currentLayer()->type() == Layer::VECTOR) { - tempPath = mEditor->view()->mapCanvasToScreen(tempPath); if (mScribbleArea->makeInvisible() == true) { pen.setWidth(0); @@ -232,7 +230,7 @@ void PolylineTool::drawPolyline(QList points, QPointF endPoint) } else { - pen.setWidth(properties.width * mEditor->view()->scaling()); + pen.setWidth(properties.width); } } } @@ -278,7 +276,7 @@ void PolylineTool::endPolyline(QList points) drawPolyline(points, points.last()); BitmapImage *bitmapImage = static_cast(layer)->getLastBitmapImageAtFrame(mEditor->currentFrame(), 0); if (bitmapImage == nullptr) { return; } // Can happen if the first frame is deleted while drawing - bitmapImage->paste(mScribbleArea->mBufferImg); + bitmapImage->paste(&mScribbleArea->mBufferImg); } mScribbleArea->clearBitmapBuffer(); diff --git a/core_lib/src/tool/smudgetool.cpp b/core_lib/src/tool/smudgetool.cpp index b3281225ba..d11c56fbf9 100644 --- a/core_lib/src/tool/smudgetool.cpp +++ b/core_lib/src/tool/smudgetool.cpp @@ -215,8 +215,17 @@ void SmudgeTool::pointerMoveEvent(PointerEvent* event) if (vectorImage == nullptr) { return; } // transforms the selection + BlitRect blit; + + // Use the previous dirty bound and extend it with the current dirty bound + // this ensures that we won't get painting artifacts + blit.extend(vectorImage->getBoundsOfTransformedCurves().toRect()); selectMan->setSelectionTransform(QTransform().translate(offsetFromPressPos().x(), offsetFromPressPos().y())); vectorImage->setSelectionTransformation(selectMan->selectionTransform()); + blit.extend(vectorImage->getBoundsOfTransformedCurves().toRect()); + + // And now tell the widget to update the portion in local coordinates + mScribbleArea->update(mEditor->view()->mapCanvasToScreen(blit).toRect().adjusted(-1, -1, 1, 1)); } } } @@ -228,9 +237,9 @@ void SmudgeTool::pointerMoveEvent(PointerEvent* event) if (vectorImage == nullptr) { return; } selectMan->setVertices(vectorImage->getVerticesCloseTo(getCurrentPoint(), selectMan->selectionTolerance())); + mScribbleArea->update(); } } - mEditor->updateCurrentFrame(); } void SmudgeTool::pointerReleaseEvent(PointerEvent* event) @@ -304,14 +313,12 @@ void SmudgeTool::drawStroke() qreal brushStep = 2; qreal distance = QLineF(b, a).length() / 2.0; int steps = qRound(distance / brushStep); - int rad = qRound(brushWidth / 2.0) + 2; QPointF sourcePoint = mLastBrushPoint; for (int i = 0; i < steps; i++) { - targetImage.paste(mScribbleArea->mBufferImg); + targetImage.paste(&mScribbleArea->mBufferImg); QPointF targetPoint = mLastBrushPoint + (i + 1) * (brushStep) * (b - mLastBrushPoint) / distance; - rect.extend(targetPoint.toPoint()); mScribbleArea->liquifyBrush(&targetImage, sourcePoint, targetPoint, @@ -324,8 +331,6 @@ void SmudgeTool::drawStroke() mLastBrushPoint = targetPoint; } sourcePoint = targetPoint; - mScribbleArea->paintBitmapBufferRect(rect); - mScribbleArea->refreshBitmap(rect, rad); } } else // liquify smooth @@ -333,12 +338,11 @@ void SmudgeTool::drawStroke() qreal brushStep = 2.0; qreal distance = QLineF(b, a).length(); int steps = qRound(distance / brushStep); - int rad = qRound(brushWidth / 2.0) + 2; QPointF sourcePoint = mLastBrushPoint; for (int i = 0; i < steps; i++) { - targetImage.paste(mScribbleArea->mBufferImg); + targetImage.paste(&mScribbleArea->mBufferImg); QPointF targetPoint = mLastBrushPoint + (i + 1) * (brushStep) * (b - mLastBrushPoint) / distance; rect.extend(targetPoint.toPoint()); mScribbleArea->blurBrush(&targetImage, @@ -353,8 +357,6 @@ void SmudgeTool::drawStroke() mLastBrushPoint = targetPoint; } sourcePoint = targetPoint; - mScribbleArea->paintBitmapBufferRect(rect); - mScribbleArea->refreshBitmap(rect, rad); } } } diff --git a/core_lib/src/util/blitrect.cpp b/core_lib/src/util/blitrect.cpp index e578bda31f..3b0645cec4 100644 --- a/core_lib/src/util/blitrect.cpp +++ b/core_lib/src/util/blitrect.cpp @@ -49,3 +49,25 @@ void BlitRect::extend(const QPoint p) if (bottom() < p.y()) { setBottom(p.y()); } } } + +void BlitRect::extend(const QRect& rect) +{ + extend(rect.topLeft(), rect.size()); +} + +void BlitRect::extend(const QPoint& p, const QSize& size) +{ + if (mInitialized == false) + { + setTopLeft(p); + setBottomRight(p + QPoint(size.width(), size.height())); + mInitialized = true; + } + else + { + if (left() > p.x()) { setLeft(p.x()); } + if (right() < p.x() + size.width()) { setRight(p.x() + size.width()); } + if (top() > p.y()) { setTop(p.y()); } + if (bottom() < p.y() + size.height()) { setBottom(p.y() + size.height()); } + } +} diff --git a/core_lib/src/util/blitrect.h b/core_lib/src/util/blitrect.h index b0c10cfb35..9c184a4b20 100644 --- a/core_lib/src/util/blitrect.h +++ b/core_lib/src/util/blitrect.h @@ -28,6 +28,8 @@ class BlitRect : public QRect explicit BlitRect(const QPoint p); explicit BlitRect(const QRect rect); void extend(const QPoint p); + void extend(const QPoint& p, const QSize& size); + void extend(const QRect& rect); private: bool mInitialized = false;