diff --git a/app/src/actioncommands.cpp b/app/src/actioncommands.cpp index 1b8102e6a7..35727c69e0 100644 --- a/app/src/actioncommands.cpp +++ b/app/src/actioncommands.cpp @@ -885,7 +885,7 @@ void ActionCommands::changeKeyframeLineColor() QRgb color = mEditor->color()->frontColor().rgb(); LayerBitmap* layer = static_cast(mEditor->layers()->currentLayer()); layer->getBitmapImageAtFrame(mEditor->currentFrame())->fillNonAlphaPixels(color); - mEditor->updateFrame(mEditor->currentFrame()); + mEditor->updateFrame(); } } @@ -900,7 +900,7 @@ void ActionCommands::changeallKeyframeLineColor() if (layer->keyExists(i)) layer->getBitmapImageAtFrame(i)->fillNonAlphaPixels(color); } - mEditor->updateFrame(mEditor->currentFrame()); + mEditor->updateFrame(); } } diff --git a/app/src/colorpalettewidget.cpp b/app/src/colorpalettewidget.cpp index 7f86fcd602..b65b656dc0 100644 --- a/app/src/colorpalettewidget.cpp +++ b/app/src/colorpalettewidget.cpp @@ -636,7 +636,7 @@ void ColorPaletteWidget::clickRemoveColorButton() { fitSwatchSize(); } - mEditor->updateCurrentFrame(); + mEditor->updateFrame(); } bool ColorPaletteWidget::showPaletteWarning() diff --git a/app/src/mainwindow2.cpp b/app/src/mainwindow2.cpp index 14fec8f622..f59aaa32ce 100644 --- a/app/src/mainwindow2.cpp +++ b/app/src/mainwindow2.cpp @@ -879,7 +879,7 @@ void MainWindow2::importImage() return; } - ui->scribbleArea->updateCurrentFrame(); + ui->scribbleArea->updateFrame(); mTimeLine->updateContent(); } @@ -1449,7 +1449,7 @@ void MainWindow2::makeConnections(Editor* editor, ColorInspector* colorInspector void MainWindow2::makeConnections(Editor* editor, ScribbleArea* scribbleArea) { - connect(editor->tools(), &ToolManager::toolChanged, scribbleArea, &ScribbleArea::setCurrentTool); + connect(editor->tools(), &ToolManager::toolChanged, scribbleArea, &ScribbleArea::updateToolCursor); connect(editor->tools(), &ToolManager::toolChanged, mToolBox, &ToolBoxWidget::onToolSetActive); connect(editor->tools(), &ToolManager::toolPropertyChanged, scribbleArea, &ScribbleArea::updateToolCursor); diff --git a/core_lib/core_lib.pro b/core_lib/core_lib.pro index 2b25e8d863..69bd7df0fd 100644 --- a/core_lib/core_lib.pro +++ b/core_lib/core_lib.pro @@ -34,6 +34,8 @@ HEADERS += \ src/corelib-pch.h \ src/graphics/bitmap/bitmapbucket.h \ src/graphics/bitmap/bitmapimage.h \ + src/graphics/bitmap/tile.h \ + src/graphics/bitmap/tiledbuffer.h \ src/graphics/vector/bezierarea.h \ src/graphics/vector/beziercurve.h \ src/graphics/vector/colorref.h \ @@ -119,6 +121,8 @@ HEADERS += \ SOURCES += src/graphics/bitmap/bitmapimage.cpp \ src/graphics/bitmap/bitmapbucket.cpp \ + src/graphics/bitmap/tile.cpp \ + src/graphics/bitmap/tiledbuffer.cpp \ src/graphics/vector/bezierarea.cpp \ src/graphics/vector/beziercurve.cpp \ src/graphics/vector/colorref.cpp \ diff --git a/core_lib/src/canvaspainter.cpp b/core_lib/src/canvaspainter.cpp index 529149764e..ef13240650 100644 --- a/core_lib/src/canvaspainter.cpp +++ b/core_lib/src/canvaspainter.cpp @@ -17,12 +17,13 @@ GNU General Public License for more details. #include "canvaspainter.h" #include -#include #include "object.h" #include "layerbitmap.h" #include "layervector.h" #include "bitmapimage.h" +#include "tile.h" +#include "tiledbuffer.h" #include "vectorimage.h" #include "painterutils.h" @@ -40,13 +41,17 @@ void CanvasPainter::reset() { mPostLayersPixmap = QPixmap(mCanvas.size()); mPreLayersPixmap = QPixmap(mCanvas.size()); + mCurrentLayerPixmap = QPixmap(mCanvas.size()); + mOnionSkinPixmap = 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); + mCurrentLayerPixmap.setDevicePixelRatio(mCanvas.devicePixelRatioF()); + mPreLayersPixmap.setDevicePixelRatio(mCanvas.devicePixelRatioF()); + mPostLayersPixmap.setDevicePixelRatio(mCanvas.devicePixelRatioF()); + mOnionSkinPixmap.setDevicePixelRatio(mCanvas.devicePixelRatioF()); } void CanvasPainter::setViewTransform(const QTransform view, const QTransform viewInverse) @@ -92,7 +97,7 @@ void CanvasPainter::paintCached(const QRect& blitRect) QPainter mainPainter; initializePainter(mainPainter, mCanvas, blitRect); mainPainter.setWorldMatrixEnabled(false); - mainPainter.drawPixmap(blitRect, mPreLayersPixmap, blitRect); + mainPainter.drawPixmap(mPointZero, mPreLayersPixmap); mainPainter.setWorldMatrixEnabled(true); paintCurrentFrame(mainPainter, blitRect, mCurrentLayerIndex, mCurrentLayerIndex); @@ -107,7 +112,7 @@ void CanvasPainter::paintCached(const QRect& blitRect) } mainPainter.setWorldMatrixEnabled(false); - mainPainter.drawPixmap(blitRect, mPostLayersPixmap, blitRect); + mainPainter.drawPixmap(mPointZero, mPostLayersPixmap); mainPainter.setWorldMatrixEnabled(true); } @@ -121,6 +126,9 @@ void CanvasPainter::initializePainter(QPainter& painter, QPaintDevice& device, c { painter.begin(&device); + // Only draw inside the clipped rectangle + painter.setClipRect(blitRect); + // 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); @@ -151,7 +159,7 @@ void CanvasPainter::renderPostLayers(QPainter& painter, const QRect& blitRect) } } -void CanvasPainter::setPaintSettings(const Object* object, int currentLayer, int frame, QRect rect, BitmapImage* buffer) +void CanvasPainter::setPaintSettings(const Object* object, int currentLayer, int frame, QRect rect, TiledBuffer* tiledBuffer) { Q_UNUSED(rect) Q_ASSERT(object); @@ -160,7 +168,7 @@ void CanvasPainter::setPaintSettings(const Object* object, int currentLayer, int CANVASPAINTER_LOG("Set CurrentLayerIndex = %d", currentLayer); mCurrentLayerIndex = currentLayer; mFrameNumber = frame; - mBuffer = buffer; + mTiledBuffer = tiledBuffer; } void CanvasPainter::paint(const QRect& blitRect) @@ -176,7 +184,7 @@ void CanvasPainter::paint(const QRect& blitRect) preLayerPainter.end(); mainPainter.setWorldMatrixEnabled(false); - mainPainter.drawPixmap(blitRect, mPreLayersPixmap, blitRect); + mainPainter.drawPixmap(mPointZero, mPreLayersPixmap); mainPainter.setWorldMatrixEnabled(true); paintCurrentFrame(mainPainter, blitRect, mCurrentLayerIndex, mCurrentLayerIndex); @@ -186,7 +194,7 @@ void CanvasPainter::paint(const QRect& blitRect) postLayerPainter.end(); mainPainter.setWorldMatrixEnabled(false); - mainPainter.drawPixmap(blitRect, mPostLayersPixmap, blitRect); + mainPainter.drawPixmap(mPointZero, mPostLayersPixmap); mainPainter.setWorldMatrixEnabled(true); mPreLayersPixmapCacheValid = true; @@ -230,7 +238,7 @@ void CanvasPainter::paintBitmapOnionSkinFrame(QPainter& painter, const QRect& bl initializePainter(onionSkinPainter, mOnionSkinPixmap, blitRect); onionSkinPainter.drawImage(bitmapImage->topLeft(), *bitmapImage->image()); - paintOnionSkinFrame(painter, onionSkinPainter, blitRect, nFrame, colorize, bitmapImage->getOpacity()); + paintOnionSkinFrame(painter, onionSkinPainter, nFrame, colorize, bitmapImage->getOpacity()); } void CanvasPainter::paintVectorOnionSkinFrame(QPainter& painter, const QRect& blitRect, Layer* layer, int nFrame, bool colorize) @@ -245,10 +253,10 @@ void CanvasPainter::paintVectorOnionSkinFrame(QPainter& painter, const QRect& bl initializePainter(onionSkinPainter, mOnionSkinPixmap, blitRect); vectorImage->paintImage(onionSkinPainter, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias); - paintOnionSkinFrame(painter, onionSkinPainter, blitRect, nFrame, colorize, vectorImage->getOpacity()); + paintOnionSkinFrame(painter, onionSkinPainter, nFrame, colorize, vectorImage->getOpacity()); } -void CanvasPainter::paintOnionSkinFrame(QPainter& painter, QPainter& onionSkinPainter, const QRect& blitRect, int nFrame, bool colorize, qreal frameOpacity) +void CanvasPainter::paintOnionSkinFrame(QPainter& painter, QPainter& onionSkinPainter, int nFrame, bool colorize, qreal frameOpacity) { // Don't transform the image here as we used the viewTransform in the image output painter.setWorldMatrixEnabled(false); @@ -272,7 +280,7 @@ void CanvasPainter::paintOnionSkinFrame(QPainter& painter, QPainter& onionSkinPa onionSkinPainter.setBrush(colorBrush); onionSkinPainter.drawRect(painter.viewport()); } - painter.drawPixmap(blitRect, mOnionSkinPixmap, blitRect); + painter.drawPixmap(mPointZero, mOnionSkinPixmap); } void CanvasPainter::paintCurrentBitmapFrame(QPainter& painter, const QRect& blitRect, Layer* layer, bool isCurrentLayer) @@ -283,7 +291,7 @@ void CanvasPainter::paintCurrentBitmapFrame(QPainter& painter, const QRect& blit if (paintedImage == nullptr) { return; } paintedImage->loadFile(); // Critical! force the BitmapImage to load the image - const bool isDrawing = mBuffer && !mBuffer->bounds().isEmpty(); + const bool isDrawing = mTiledBuffer && !mTiledBuffer->bounds().isEmpty(); QPainter currentBitmapPainter; initializePainter(currentBitmapPainter, mCurrentLayerPixmap, blitRect); @@ -291,19 +299,35 @@ void CanvasPainter::paintCurrentBitmapFrame(QPainter& painter, const QRect& blit painter.setOpacity(paintedImage->getOpacity() - (1.0-painter.opacity())); painter.setWorldMatrixEnabled(false); - currentBitmapPainter.drawImage(paintedImage->topLeft(), *paintedImage->image()); + if (isCurrentLayer && isDrawing) + { + // Certain tools require being painted continuously, for example, the Polyline tool. + // The tiled buffer does not update the area outside which it paints, + // so in that case, in order to see the previously laid-down polyline stroke, + // the surrounding area must be drawn again before + // applying the new tiled output on top + if (!blitRect.contains(mTiledBuffer->bounds()) || mOptions.bIgnoreCanvasBuffer) { + currentBitmapPainter.setCompositionMode(QPainter::CompositionMode_Source); + currentBitmapPainter.drawImage(paintedImage->topLeft(), *paintedImage->image()); + } - 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); + const auto tiles = mTiledBuffer->tiles(); + for (const Tile* tile : tiles) { + currentBitmapPainter.drawPixmap(tile->posF(), tile->pixmap()); } + } else { + // When we're drawing using a tool, the surface will be painted by the tiled buffer, + // and thus we don't want to paint the current image again + // When we're on another layer though, the tiled buffer is not used + currentBitmapPainter.drawImage(paintedImage->topLeft(), *paintedImage->image()); } - painter.drawPixmap(blitRect, mCurrentLayerPixmap, blitRect); + // We do not wish to draw selection transformations on anything but the current layer + if (isCurrentLayer && mRenderTransform) { + paintTransformedSelection(currentBitmapPainter, paintedImage, mSelection); + } + + painter.drawPixmap(mPointZero, mCurrentLayerPixmap); } void CanvasPainter::paintCurrentVectorFrame(QPainter& painter, const QRect& blitRect, Layer* layer, bool isCurrentLayer) @@ -318,7 +342,7 @@ void CanvasPainter::paintCurrentVectorFrame(QPainter& painter, const QRect& blit QPainter currentVectorPainter; initializePainter(currentVectorPainter, mCurrentLayerPixmap, blitRect); - const bool isDrawing = mBuffer && !mBuffer->bounds().isEmpty(); + const bool isDrawing = mTiledBuffer->isValid(); // Paint existing vector image to the painter vectorImage->paintImage(currentVectorPainter, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias); @@ -326,7 +350,11 @@ void CanvasPainter::paintCurrentVectorFrame(QPainter& painter, const QRect& blit if (isCurrentLayer) { if (isDrawing) { currentVectorPainter.setCompositionMode(mOptions.cmBufferBlendMode); - currentVectorPainter.drawImage(mBuffer->topLeft(), *mBuffer->image()); + + const auto tiles = mTiledBuffer->tiles(); + for (const Tile* tile : tiles) { + currentVectorPainter.drawPixmap(tile->posF(), tile->pixmap()); + } } else if (mRenderTransform) { vectorImage->setSelectionTransformation(mSelectionTransform); } @@ -336,9 +364,9 @@ void CanvasPainter::paintCurrentVectorFrame(QPainter& painter, const QRect& blit painter.setWorldMatrixEnabled(false); painter.setTransform(QTransform()); - // Remember to adjust opacity based on addition opacity value from image + // Remember to adjust opacity based on additional opacity value from the keyframe painter.setOpacity(vectorImage->getOpacity() - (1.0-painter.opacity())); - painter.drawPixmap(blitRect, mCurrentLayerPixmap, blitRect); + painter.drawPixmap(mPointZero, mCurrentLayerPixmap); } void CanvasPainter::paintTransformedSelection(QPainter& painter, BitmapImage* bitmapImage, const QRect& selection) const diff --git a/core_lib/src/canvaspainter.h b/core_lib/src/canvaspainter.h index 58ad6e993d..66c6e1d983 100644 --- a/core_lib/src/canvaspainter.h +++ b/core_lib/src/canvaspainter.h @@ -30,6 +30,8 @@ GNU General Public License for more details. #include "onionskinpainteroptions.h" #include "onionskinsubpainter.h" + +class TiledBuffer; class Object; class BitmapImage; class ViewManager; @@ -39,6 +41,10 @@ struct CanvasPainterOptions bool bAntiAlias = false; bool bThinLines = false; bool bOutlines = false; + + /// When using a tool that can't rely on canvas buffer, + /// for example Polyline because we're continously clearing the surface + bool bIgnoreCanvasBuffer = false; LayerVisibility eLayerVisibility = LayerVisibility::RELATED; float fLayerVisibilityThreshold = 0.f; float scaling = 1.0f; @@ -61,7 +67,7 @@ class CanvasPainter void setTransformedSelection(QRect selection, QTransform transform); void ignoreTransformedSelection(); - void setPaintSettings(const Object* object, int currentLayer, int frame, QRect rect, BitmapImage* buffer); + void setPaintSettings(const Object* object, int currentLayer, int frame, QRect rect, TiledBuffer* tilledBuffer); void paint(const QRect& blitRect); void paintCached(const QRect& blitRect); void resetLayerCache(); @@ -88,7 +94,7 @@ class CanvasPainter 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 paintOnionSkinFrame(QPainter& painter, QPainter& onionSkinPainter, int nFrame, bool colorize, qreal frameOpacity); void paintCurrentBitmapFrame(QPainter& painter, const QRect& blitRect, Layer* layer, bool isCurrentLayer); void paintCurrentVectorFrame(QPainter& painter, const QRect& blitRect, Layer* layer, bool isCurrentLayer); @@ -102,7 +108,7 @@ class CanvasPainter int mCurrentLayerIndex = 0; int mFrameNumber = 0; - BitmapImage* mBuffer = nullptr; + TiledBuffer* mTiledBuffer = nullptr; QImage mScaledBitmap; @@ -119,6 +125,10 @@ class CanvasPainter bool mPreLayersPixmapCacheValid = false; bool mPostLayersPixmapCacheValid = false; + // There's a considerable amount of overhead in simply allocating a QPointF on the fly. + // Since we just need to draw it at 0,0, we might as well make a const value for that purpose + const QPointF mPointZero; + OnionSkinSubPainter mOnionSkinSubPainter; OnionSkinPainterOptions mOnionSkinPainterOptions; diff --git a/core_lib/src/graphics/bitmap/bitmapimage.cpp b/core_lib/src/graphics/bitmap/bitmapimage.cpp index c4be6a77c2..e2be3641e0 100644 --- a/core_lib/src/graphics/bitmap/bitmapimage.cpp +++ b/core_lib/src/graphics/bitmap/bitmapimage.cpp @@ -24,6 +24,8 @@ GNU General Public License for more details. #include "util.h" #include "blitrect.h" +#include "tile.h" +#include "tiledbuffer.h" BitmapImage::BitmapImage() { @@ -98,7 +100,7 @@ BitmapImage* BitmapImage::clone() const b->setFileName(""); // don't link to the file of the source bitmap image const bool validKeyFrame = !fileName().isEmpty(); - if (validKeyFrame && !isLoaded()) + if (validKeyFrame && !isLoaded()) { // This bitmapImage is temporarily unloaded. // since it's not in the memory, we need to copy the linked png file to prevent data loss. @@ -204,6 +206,28 @@ void BitmapImage::paste(BitmapImage* bitmapImage, QPainter::CompositionMode cm) modification(); } +void BitmapImage::paste(const TiledBuffer* tiledBuffer, QPainter::CompositionMode cm) +{ + if(tiledBuffer->bounds().width() <= 0 || tiledBuffer->bounds().height() <= 0) + { + return; + } + extend(tiledBuffer->bounds()); + + QPainter painter(image()); + + painter.setCompositionMode(cm); + auto const tiles = tiledBuffer->tiles(); + for (const Tile* item : tiles) { + const QPixmap& tilePixmap = item->pixmap(); + const QPoint& tilePos = item->pos(); + painter.drawPixmap(tilePos-mBounds.topLeft(), tilePixmap); + } + painter.end(); + + modification(); +} + void BitmapImage::moveTopLeft(QPoint point) { mBounds.moveTopLeft(point); @@ -624,7 +648,10 @@ void BitmapImage::drawRect(QRectF rectangle, QPen pen, QBrush brush, QPainter::C painter.setRenderHint(QPainter::Antialiasing, antialiasing); painter.setPen(pen); painter.setBrush(brush); - painter.drawRect(rectangle.translated(-mBounds.topLeft())); + + // Adjust the brush rectangle to be bigger than the bounds itself, + // otherwise there will be artifacts shown in some cases when smudging + painter.drawRect(rectangle.translated(-mBounds.topLeft()).adjusted(-1, -1, 1, 1)); painter.end(); } modification(); diff --git a/core_lib/src/graphics/bitmap/bitmapimage.h b/core_lib/src/graphics/bitmap/bitmapimage.h index 6122b92559..ead411bfbb 100644 --- a/core_lib/src/graphics/bitmap/bitmapimage.h +++ b/core_lib/src/graphics/bitmap/bitmapimage.h @@ -22,6 +22,8 @@ GNU General Public License for more details. #include #include +class TiledBuffer; + class BitmapImage : public KeyFrame { @@ -50,6 +52,7 @@ class BitmapImage : public KeyFrame BitmapImage copy(); BitmapImage copy(QRect rectangle); void paste(BitmapImage*, QPainter::CompositionMode cm = QPainter::CompositionMode_SourceOver); + void paste(const TiledBuffer* tiledBuffer, QPainter::CompositionMode cm = QPainter::CompositionMode_SourceOver); void moveTopLeft(QPoint point); void moveTopLeft(QPointF point) { moveTopLeft(point.toPoint()); } diff --git a/core_lib/src/graphics/bitmap/tile.cpp b/core_lib/src/graphics/bitmap/tile.cpp new file mode 100644 index 0000000000..7727e0517e --- /dev/null +++ b/core_lib/src/graphics/bitmap/tile.cpp @@ -0,0 +1,47 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2012-2020 Matthew Chiawen Chang + +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; version 2 of the License. + +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. + +*/ + +#include "tile.h" + +#include + +Tile::Tile(const QPoint& pos, QSize size): + mTilePixmap(size), + mPosF(pos), + mPos(pos), + mBounds(pos, size), + mSize(size) +{ + clear(); //Default tiles are transparent +} + +Tile::~Tile() +{ +} + +void Tile::load(const QImage& image, const QPoint& topLeft) +{ + QPainter painter(&mTilePixmap); + + painter.translate(-mPos); + painter.drawImage(topLeft, image); + painter.end(); +} + +void Tile::clear() +{ + mTilePixmap.fill(Qt::transparent); +} diff --git a/core_lib/src/graphics/bitmap/tile.h b/core_lib/src/graphics/bitmap/tile.h new file mode 100644 index 0000000000..b94bf3dc49 --- /dev/null +++ b/core_lib/src/graphics/bitmap/tile.h @@ -0,0 +1,50 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2012-2020 Matthew Chiawen Chang + +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; version 2 of the License. + +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. + +*/ + +#ifndef TILE_H +#define TILE_H + +#include +#include + +class Tile +{ +public: + + explicit Tile (const QPoint& pos, QSize size); + ~Tile(); + + const QPixmap& pixmap() const { return mTilePixmap; } + QPixmap& pixmap() { return mTilePixmap; } + + const QPoint& pos() const { return mPos; } + const QPointF& posF() const { return mPosF; } + const QRect& bounds() const { return mBounds; } + const QSize& size() const { return mSize; } + + /** Loads the input image into the tile */ + void load(const QImage& image, const QPoint& topLeft); + void clear(); + +private: + QPixmap mTilePixmap; + QPointF mPosF; + QPoint mPos; + QRect mBounds; + QSize mSize; +}; + +#endif // TILE_H diff --git a/core_lib/src/graphics/bitmap/tiledbuffer.cpp b/core_lib/src/graphics/bitmap/tiledbuffer.cpp new file mode 100644 index 0000000000..5d4929e64c --- /dev/null +++ b/core_lib/src/graphics/bitmap/tiledbuffer.cpp @@ -0,0 +1,166 @@ +/* + +Pencil - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2018 Matthew Chiawen Chang + +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; version 2 of the License. + +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. + +*/ +#include "tiledbuffer.h" + +#include +#include + +#include "tile.h" + +TiledBuffer::TiledBuffer(QObject* parent) : QObject(parent) +{ +} + +TiledBuffer::~TiledBuffer() +{ + clear(); +} + +Tile* TiledBuffer::getTileFromIndex(const TileIndex& tileIndex) +{ + Tile* selectedTile = mTiles.value(tileIndex, nullptr); + + if (!selectedTile) { + // Time to allocate it, update table: + const QPoint& tilePos (getTilePos(tileIndex)); + selectedTile = new Tile(tilePos, QSize(UNIFORM_TILE_SIZE, UNIFORM_TILE_SIZE)); + mTiles.insert(tileIndex, selectedTile); + + emit this->tileCreated(this, selectedTile); + } else { + emit this->tileUpdated(this, selectedTile); + } + + return selectedTile; +} + +void TiledBuffer::drawBrush(const QPointF& point, int brushWidth, int brushCursorWidth, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing) { + const QRectF brushRect(point.x() - 0.5 * brushWidth, point.y() - 0.5 * brushWidth, brushWidth, brushWidth); + const float tileSize = UNIFORM_TILE_SIZE; + const int width = qMax(brushCursorWidth,brushWidth); + + // Gather the number of tiles that fits the size of the brush width + const int xLeft = qFloor((qFloor(point.x() - width)) / tileSize); + const int xRight = qFloor((qFloor(point.x() + width)) / tileSize); + const int yTop = qFloor(qFloor(point.y() - width) / tileSize); + const int yBottom = qFloor(qFloor(point.y() + width) / tileSize); + + for (int tileY = yTop; tileY <= yBottom; tileY++) { + for (int tileX = xLeft; tileX <= xRight; tileX++) { + + Tile* tile = getTileFromIndex({tileX, tileY}); + + QPainter painter(&tile->pixmap()); + + painter.translate(-tile->pos()); + painter.setRenderHint(QPainter::Antialiasing, antialiasing); + painter.setPen(pen); + painter.setBrush(brush); + painter.setCompositionMode(cm); + painter.drawEllipse(brushRect); + painter.end(); + + mTileBounds.extend(tile->bounds()); + } + } +} + +void TiledBuffer::drawImage(const QImage& image, const QRect& imageBounds, QPainter::CompositionMode cm, bool antialiasing) { + const float tileSize = UNIFORM_TILE_SIZE; + const float imageXRad = image.width(); + const float imageYRad = image.height(); + // Gather the number of tiles that fits the size of the brush width + const int xLeft = qFloor((qFloor(imageBounds.left() - imageXRad)) / tileSize); + const int xRight = qFloor((qFloor(imageBounds.right() + imageXRad)) / tileSize); + const int yTop = qFloor(qFloor(imageBounds.top() - imageYRad) / tileSize); + const int yBottom = qFloor(qFloor(imageBounds.bottom() + imageYRad) / tileSize); + + for (int tileY = yTop; tileY <= yBottom; tileY++) { + for (int tileX = xLeft; tileX <= xRight; tileX++) { + + Tile* tile = getTileFromIndex({tileX, tileY}); + + QPainter painter(&tile->pixmap()); + + painter.translate(-tile->pos()); + painter.setRenderHint(QPainter::Antialiasing, antialiasing); + painter.setCompositionMode(cm); + painter.drawImage(imageBounds.topLeft(), image); + painter.end(); + + mTileBounds.extend(tile->bounds()); + } + } +} + + +void TiledBuffer::drawPath(QPainterPath path, int cursorWidth, QPen pen, QBrush brush, + QPainter::CompositionMode cm, bool antialiasing) +{ + const int pathWidth = pen.width(); + const int width = (qMax(pathWidth,cursorWidth) + 1); + const float tileSize = UNIFORM_TILE_SIZE; + const QRectF pathRect = path.boundingRect(); + + // Gather the number of tiles that fits the size of the brush width + const int xLeft = qFloor((qFloor(pathRect.left() - width)) / tileSize); + const int xRight = qFloor((qFloor(pathRect.right() + width)) / tileSize); + const int yTop = qFloor(qFloor(pathRect.top() - width) / tileSize); + const int yBottom = qFloor(qFloor(pathRect.bottom() + width) / tileSize); + + for (int tileY = yTop; tileY <= yBottom; tileY++) { + for (int tileX = xLeft; tileX <= xRight; tileX++) { + + Tile* tile = getTileFromIndex({tileX, tileY}); + + QPainter painter(&tile->pixmap()); + + painter.translate(-tile->pos()); + painter.setRenderHint(QPainter::Antialiasing, antialiasing); + painter.setPen(pen); + painter.setBrush(brush); + painter.setCompositionMode(cm); + painter.drawPath(path); + painter.end(); + + mTileBounds.extend(tile->bounds()); + } + } +} + +void TiledBuffer::clear() +{ + QHashIterator i(mTiles); + + while (i.hasNext()) { + i.next(); + Tile* tile = i.value(); + if (tile) + { + mTiles.remove(i.key()); + delete tile; + } + } + + mTileBounds = BlitRect(); +} + +QPoint TiledBuffer::getTilePos(const TileIndex& index) const +{ + return QPoint { qRound(UNIFORM_TILE_SIZE*static_cast(index.x)), + qRound(UNIFORM_TILE_SIZE*static_cast(index.y)) }; +} diff --git a/core_lib/src/graphics/bitmap/tiledbuffer.h b/core_lib/src/graphics/bitmap/tiledbuffer.h new file mode 100644 index 0000000000..7439c628cd --- /dev/null +++ b/core_lib/src/graphics/bitmap/tiledbuffer.h @@ -0,0 +1,86 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2012-2020 Matthew Chiawen Chang + +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; version 2 of the License. + +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. + +*/ +#ifndef TILEDBUFFER_H +#define TILEDBUFFER_H + +#include +#include + +#include "blitrect.h" + +class QImage; +class QRect; +class Tile; + +struct TileIndex { + int x; + int y; +}; + +inline uint qHash(const TileIndex &key, uint seed) +{ + return qHash(key.x, seed) ^ key.y; +} + +inline bool operator==(const TileIndex &e1, const TileIndex &e2) +{ + return e1.x == e2.x + && e1.y == e2.y; +} + +class TiledBuffer: public QObject +{ + Q_OBJECT +public: + TiledBuffer(QObject* parent = nullptr); + ~TiledBuffer(); + + /** Clears the content of the tiled buffer */ + void clear(); + + /** Returns true if there are any tiles, otherwise false */ + bool isValid() const { return !mTiles.isEmpty(); } + + /** Draws a brush with the specified parameters to the tiled buffer */ + void drawBrush(const QPointF& point, int brushWidth, int brushCursorWidth, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing); + /** Draws a path with the specified parameters to the tiled buffer */ + void drawPath(QPainterPath path, int cursorWidth, QPen pen, QBrush brush, + QPainter::CompositionMode cm, bool antialiasing); + /** Draws a image with the specified parameters to the tiled buffer */ + void drawImage(const QImage& image, const QRect& imageBounds, QPainter::CompositionMode cm, bool antialiasing); + + QHash tiles() const { return mTiles; } + + const QRect& bounds() const { return mTileBounds; } + +signals: + void tileUpdated(TiledBuffer* tiledBuffer, Tile* tile); + void tileCreated(TiledBuffer* tiledBuffer, Tile* tile); + +private: + + Tile* getTileFromIndex(const TileIndex& tileIndex); + + inline QPoint getTilePos(const TileIndex& index) const; + + const int UNIFORM_TILE_SIZE = 64; + + BlitRect mTileBounds; + + QHash mTiles; +}; + +#endif // TILEDBUFFER_H diff --git a/core_lib/src/graphics/vector/vectorimage.cpp b/core_lib/src/graphics/vector/vectorimage.cpp index 18108742be..9dfe0980c7 100644 --- a/core_lib/src/graphics/vector/vectorimage.cpp +++ b/core_lib/src/graphics/vector/vectorimage.cpp @@ -1203,6 +1203,7 @@ void VectorImage::paintImage(QPainter& painter, bool showThinCurves, bool antialiasing) { + painter.save(); painter.setRenderHint(QPainter::Antialiasing, antialiasing); painter.setClipping(false); @@ -1248,6 +1249,7 @@ void VectorImage::paintImage(QPainter& painter, curve.drawPath(painter, mObject, mSelectionTransformation, simplified, showThinCurves); painter.setClipping(false); } + painter.restore(); } /** diff --git a/core_lib/src/interface/editor.cpp b/core_lib/src/interface/editor.cpp index b6d3d44bfc..7c4d5bc602 100644 --- a/core_lib/src/interface/editor.cpp +++ b/core_lib/src/interface/editor.cpp @@ -1109,14 +1109,9 @@ void Editor::deselectAll() const } } -void Editor::updateFrame(int frameNumber) +void Editor::updateFrame() { - mScribbleArea->updateFrame(frameNumber); -} - -void Editor::updateCurrentFrame() -{ - mScribbleArea->updateCurrentFrame(); + mScribbleArea->updateFrame(); } void Editor::setCurrentLayerIndex(int i) diff --git a/core_lib/src/interface/editor.h b/core_lib/src/interface/editor.h index 0b5723d3db..eef05b2665 100644 --- a/core_lib/src/interface/editor.h +++ b/core_lib/src/interface/editor.h @@ -166,14 +166,8 @@ class Editor : public QObject /** Will call update() and update the canvas * Only call this directly If you need the cache to be intact and require the frame to be repainted - * Convenient method that does the same as updateFrame but for the current frame */ - void updateCurrentFrame(); - - /** Will call update() and update the canvas - * Only call this directly If you need the cache to be intact and require the frame to be repainted - */ - void updateFrame(int frameNumber); + void updateFrame(); void setModified(int layerNumber, int frameNumber); diff --git a/core_lib/src/interface/scribblearea.cpp b/core_lib/src/interface/scribblearea.cpp index 998c398978..028068eda7 100644 --- a/core_lib/src/interface/scribblearea.cpp +++ b/core_lib/src/interface/scribblearea.cpp @@ -32,6 +32,7 @@ GNU General Public License for more details. #include "bitmapimage.h" #include "vectorimage.h" #include "blitrect.h" +#include "tile.h" #include "onionskinpainteroptions.h" @@ -75,6 +76,9 @@ bool ScribbleArea::init() connect(mEditor->select(), &SelectionManager::selectionChanged, this, &ScribbleArea::onSelectionChanged); connect(mEditor->select(), &SelectionManager::needDeleteSelection, this, &ScribbleArea::deleteSelection); + connect(&mTiledBuffer, &TiledBuffer::tileUpdated, this, &ScribbleArea::onTileUpdated); + connect(&mTiledBuffer, &TiledBuffer::tileCreated, this, &ScribbleArea::onTileCreated); + mDoubleClickTimer->setInterval(50); mMouseFilterTimer->setInterval(50); @@ -84,7 +88,6 @@ bool ScribbleArea::init() mQuickSizing = mPrefs->isOn(SETTING::QUICK_SIZING); mMakeInvisible = false; - mIsSimplified = mPrefs->isOn(SETTING::OUTLINES); mMultiLayerOnionSkin = mPrefs->isOn(SETTING::MULTILAYER_ONION); mLayerVisibility = static_cast(mPrefs->getInt(SETTING::LAYER_VISIBILITY)); @@ -189,14 +192,36 @@ void ScribbleArea::setEffect(SETTING e, bool isOn) /************************************************************************************/ // update methods -void ScribbleArea::updateCurrentFrame() +void ScribbleArea::onTileUpdated(TiledBuffer* tiledBuffer, Tile* tile) +{ + Q_UNUSED(tiledBuffer); + const QRectF& mappedRect = mEditor->view()->getView().mapRect(QRectF(tile->bounds())); + update(mappedRect.toAlignedRect()); +} + +void ScribbleArea::onTileCreated(TiledBuffer* tiledBuffer, Tile* tile) { - updateFrame(mEditor->currentFrame()); + Q_UNUSED(tiledBuffer) + Layer::LAYER_TYPE layerType = mEditor->layers()->currentLayer()->type(); + if (layerType == Layer::BITMAP) { + const auto& bitmapImage = currentBitmapImage(mEditor->layers()->currentLayer()); + const QImage& image = *bitmapImage->image(); + tile->load(image, bitmapImage->topLeft()); + } else if (layerType == Layer::VECTOR) { + + // Not used, we only use the buffer to paint the stroke before painting the real vector stroke + } + + const QRectF& mappedRect = mEditor->view()->getView().mapRect(QRectF(tile->bounds())); + update(mappedRect.toAlignedRect()); } -void ScribbleArea::updateFrame(int frame) +void ScribbleArea::updateFrame() { - Q_ASSERT(frame >= 0); + if (currentTool()->isActive() && currentTool()->isDrawingTool()) { + return; + } + update(); } @@ -254,12 +279,14 @@ void ScribbleArea::invalidateOnionSkinsCacheAround(int frameNumber) void ScribbleArea::invalidateAllCache() { + if (currentTool()->isDrawingTool() && currentTool()->isActive()) { return; } + QPixmapCache::clear(); mPixmapCacheKeys.clear(); invalidatePainterCaches(); mEditor->layers()->currentLayer()->clearDirtyFrames(); - update(); + updateFrame(); } void ScribbleArea::invalidateCacheForFrame(int frameNumber) @@ -277,7 +304,7 @@ void ScribbleArea::invalidatePainterCaches() { mCameraPainter.resetCache(); mCanvasPainter.resetLayerCache(); - update(); + updateFrame(); } void ScribbleArea::onToolPropertyUpdated(ToolType, ToolPropertyType type) @@ -298,7 +325,7 @@ void ScribbleArea::onToolChanged(ToolType) prepOverlays(frame); prepCameraPainter(frame); invalidateCacheForFrame(frame); - updateCurrentFrame(); + updateFrame(); } @@ -313,13 +340,14 @@ void ScribbleArea::onPlayStateChanged() prepOverlays(currentFrame); prepCameraPainter(currentFrame); invalidateCacheForFrame(currentFrame); - updateFrame(currentFrame); + updateFrame(); } void ScribbleArea::onScrubbed(int frameNumber) { + Q_UNUSED(frameNumber) invalidatePainterCaches(); - updateFrame(frameNumber); + updateFrame(); } void ScribbleArea::onFramesModified() @@ -328,7 +356,7 @@ void ScribbleArea::onFramesModified() if (mPrefs->isOn(SETTING::PREV_ONION) || mPrefs->isOn(SETTING::NEXT_ONION)) { invalidatePainterCaches(); } - update(); + updateFrame(); } void ScribbleArea::onFrameModified(int frameNumber) @@ -338,7 +366,7 @@ void ScribbleArea::onFrameModified(int frameNumber) invalidatePainterCaches(); } invalidateCacheForFrame(frameNumber); - updateFrame(frameNumber); + updateFrame(); } void ScribbleArea::onViewChanged() @@ -355,7 +383,7 @@ void ScribbleArea::onSelectionChanged() { int currentFrame = mEditor->currentFrame(); invalidateCacheForFrame(currentFrame); - updateFrame(currentFrame); + updateFrame(); } void ScribbleArea::onOnionSkinTypeChanged() @@ -386,8 +414,6 @@ void ScribbleArea::keyPressEvent(QKeyEvent *event) // Don't handle this event on auto repeat if (event->isAutoRepeat()) { return; } - mKeyboardInUse = true; - if (isPointerInUse()) { return; } // prevents shortcuts calls while drawing if (currentTool()->keyPressEvent(event)) @@ -501,8 +527,6 @@ void ScribbleArea::keyReleaseEvent(QKeyEvent *event) return; } - mKeyboardInUse = false; - if (event->key() == 0) { editor()->tools()->tryClearTemporaryTool(Qt::Key_unknown); @@ -525,7 +549,7 @@ void ScribbleArea::keyReleaseEvent(QKeyEvent *event) // mouse and tablet event handlers void ScribbleArea::wheelEvent(QWheelEvent* event) { - // Don't change view if tool is in use + // Don't change view if the tool is in use if (isPointerInUse()) return; static const bool isX11 = QGuiApplication::platformName() == "xcb"; @@ -664,7 +688,6 @@ void ScribbleArea::pointerPressEvent(PointerEvent* event) const bool isPressed = event->buttons() & Qt::LeftButton; if (isPressed && mQuickSizing) { - //qDebug() << "Start Adjusting" << event->buttons(); if (currentTool()->startAdjusting(event->modifiers(), 1)) { return; @@ -704,13 +727,6 @@ void ScribbleArea::pointerReleaseEvent(PointerEvent* event) return; // [SHIFT]+drag OR [CTRL]+drag } - if (event->buttons() & (Qt::RightButton | Qt::MiddleButton)) - { - mMouseRightButtonInUse = false; - return; - } - - //qDebug() << "release event"; currentTool()->pointerReleaseEvent(event); editor()->tools()->tryClearTemporaryTool(event->button()); @@ -730,11 +746,11 @@ void ScribbleArea::handleDoubleClick() void ScribbleArea::tabletReleaseEventFired() { - // Under certain circumstances a mouse press event will fire after a tablet release event. - // This causes unexpected behaviours for some of the tools, eg. the bucket. - // The problem only seems to occur on windows and only when tapping. - // prior to this fix, the event queue would look like this: - // eg: TabletPress -> TabletRelease -> MousePress + // Under certain circumstances, a mouse press event will fire after a tablet release event. + // This causes unexpected behaviors for some tools, e.g., the bucket tool. + // The problem only seems to occur on Windows and only when tapping. + // Prior to this fix, the event queue would look like this: + // e.g.: TabletPress -> TabletRelease -> MousePress // The following will filter mouse events created after a tablet release event. mTabletReleaseMillisAgo += 50; @@ -803,7 +819,7 @@ void ScribbleArea::resizeEvent(QResizeEvent* event) QWidget::resizeEvent(event); mDevicePixelRatio = devicePixelRatioF(); mCanvas = QPixmap(QSizeF(size() * mDevicePixelRatio).toSize()); - + mCanvas.setDevicePixelRatio(mDevicePixelRatio); mEditor->view()->setCanvasSize(size()); invalidateCacheForFrame(mEditor->currentFrame()); @@ -831,60 +847,27 @@ void ScribbleArea::paintBitmapBuffer() // just return (since we have nothing to paint on). if (layer->getLastKeyFrameAtPosition(frameNumber) == nullptr) { - updateCurrentFrame(); + updateFrame(); return; } - // Clear the temporary pixel path 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_SourceOver; - } - break; - default: //nothing - break; - } - targetImage->paste(&mBufferImg, cm); + targetImage->paste(&mTiledBuffer, QPainter::CompositionMode_Source); } - QRect rect = mEditor->view()->mapCanvasToScreen(mBufferImg.bounds()).toRect(); + QRect rect = mEditor->view()->mapCanvasToScreen(mTiledBuffer.bounds()).toRect(); - drawCanvas(frameNumber, rect.adjusted(-1, -1, 1, 1)); update(rect); - // Update the cache for the last key-frame. - updateFrame(frameNumber); layer->setModified(frameNumber, true); - - mBufferImg.clear(); + mTiledBuffer.clear(); } -void ScribbleArea::clearBitmapBuffer() +void ScribbleArea::clearDrawingBuffer() { - mBufferImg.clear(); -} - -void ScribbleArea::drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm) -{ - mBufferImg.drawLine(P1, P2, pen, cm, mPrefs->isOn(SETTING::ANTIALIAS)); -} - -void ScribbleArea::drawPath(QPainterPath path, QPen pen, QBrush brush, QPainter::CompositionMode cm) -{ - 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)); + mTiledBuffer.clear(); } void ScribbleArea::paintCanvasCursor(QPainter& painter) @@ -906,9 +889,6 @@ void ScribbleArea::paintCanvasCursor(QPainter& painter) static_cast(mTransformedCursorPos.y() - mCursorCenterPos.y())), mCursorImg); - // update center of transformed img for rect only - mTransCursImg = mCursorImg.transformed(view); - mCursorCenterPos.setX(centerCal); mCursorCenterPos.setY(centerCal); } @@ -929,16 +909,17 @@ void ScribbleArea::updateCanvasCursor() } else { - mCursorImg = QPixmap(); // if above does not comply, deallocate image + mCursorImg = QPixmap(); // if the above does not comply, deallocate image } - // update cursor rect - QPoint translatedPos = QPoint(static_cast(mTransformedCursorPos.x() - mCursorCenterPos.x()), - static_cast(mTransformedCursorPos.y() - mCursorCenterPos.y())); - - update(mTransCursImg.rect().adjusted(-1, -1, 1, 1) - .translated(translatedPos)); - + // When we're using a tool, the TiledBuffer will take care of this; + // we don't want to cause needless updates + if (!currentTool()->isActive()) { + // update cursor rect + QPoint translatedPos(static_cast(mTransformedCursorPos.x() - mCursorCenterPos.x()), + static_cast(mTransformedCursorPos.y() - mCursorCenterPos.y())); + update(mCursorImg.rect().adjusted(-1, -1, 1, 1).translated(translatedPos)); + } } void ScribbleArea::handleDrawingOnEmptyFrame() @@ -1050,14 +1031,18 @@ void ScribbleArea::paintEvent(QPaintEvent* event) // paints the canvas painter.setWorldMatrixEnabled(false); - painter.drawPixmap(QPoint(0, 0), mCanvas); - currentTool()->paint(painter); + // In other places we use the blitRect to paint the buffer pixmap, however + // the main pixmap which needs to be scaled accordingly to DPI, which is not accounted for when using the event rect + // instead we can set a clipRect to avoid the area being updated needlessly + painter.setClipRect(event->rect()); + painter.drawPixmap(QPointF(), mCanvas); - Layer* layer = mEditor->layers()->currentLayer(); + currentTool()->paint(painter); if (!editor()->playback()->isPlaying()) // we don't need to display the following when the animation is playing { + Layer* layer = mEditor->layers()->currentLayer(); if (layer->type() == Layer::VECTOR) { VectorImage* vectorImage = currentVectorImage(layer); @@ -1134,7 +1119,6 @@ void ScribbleArea::paintEvent(QPaintEvent* event) mOverlayPainter.paint(painter); - // paints the selection outline if (mEditor->select()->somethingSelected()) { @@ -1224,6 +1208,7 @@ void ScribbleArea::prepCanvas(int frame, QRect rect) o.fLayerVisibilityThreshold = mPrefs->getFloat(SETTING::LAYER_VISIBILITY_THRESHOLD); o.scaling = mEditor->view()->scaling(); o.cmBufferBlendMode = mEditor->tools()->currentTool()->type() == ToolType::ERASER ? QPainter::CompositionMode_DestinationOut : QPainter::CompositionMode_SourceOver; + o.bIgnoreCanvasBuffer = currentTool()->type() == POLYLINE; OnionSkinPainterOptions onionSkinOptions; onionSkinOptions.enabledWhilePlaying = mPrefs->getInt(SETTING::ONION_WHILE_PLAYBACK); @@ -1246,12 +1231,11 @@ void ScribbleArea::prepCanvas(int frame, QRect rect) 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, &mTiledBuffer); } void ScribbleArea::drawCanvas(int frame, QRect rect) { - mCanvas.setDevicePixelRatio(mDevicePixelRatio); prepCanvas(frame, rect); prepCameraPainter(frame); prepOverlays(frame); @@ -1279,39 +1263,63 @@ void ScribbleArea::setGaussianGradient(QGradient &gradient, QColor color, qreal gradient.setColorAt(1.0 - (offset / 100.0), QColor(r, g, b, mainColorAlpha - alphaAdded)); } -void ScribbleArea::drawPen(QPointF thePoint, qreal brushWidth, QColor fillColor, bool useAA) +void ScribbleArea::drawPath(QPainterPath path, QPen pen, QBrush brush, QPainter::CompositionMode cm) { - QRectF rectangle(thePoint.x() - 0.5 * brushWidth, thePoint.y() - 0.5 * brushWidth, brushWidth, brushWidth); + mTiledBuffer.drawPath(mEditor->view()->mapScreenToCanvas(path), mEditor->view()->mapScreenToCanvas(mCursorImg.rect()).width(), pen, brush, cm, mPrefs->isOn(SETTING::ANTIALIAS)); +} - 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::drawPen(QPointF thePoint, qreal brushWidth, QColor fillColor, bool useAA) +{ + mTiledBuffer.drawBrush(thePoint, brushWidth, mEditor->view()->mapScreenToCanvas(mCursorImg.rect()).width(), Qt::NoPen, QBrush(fillColor, Qt::SolidPattern), QPainter::CompositionMode_SourceOver, useAA); } void ScribbleArea::drawPencil(QPointF thePoint, qreal brushWidth, qreal fixedBrushFeather, QColor fillColor, qreal opacity) { - drawBrush(thePoint, brushWidth, fixedBrushFeather, fillColor, opacity, true); + drawBrush(thePoint, brushWidth, fixedBrushFeather, fillColor, QPainter::CompositionMode_SourceOver, opacity, true); } -void ScribbleArea::drawBrush(QPointF thePoint, qreal brushWidth, qreal mOffset, QColor fillColor, qreal opacity, bool usingFeather, bool useAA) +void ScribbleArea::drawBrush(QPointF thePoint, qreal brushWidth, qreal mOffset, QColor fillColor, QPainter::CompositionMode compMode, qreal opacity, bool usingFeather, bool useAA) { - QRectF rectangle(thePoint.x() - 0.5 * brushWidth, thePoint.y() - 0.5 * brushWidth, brushWidth, brushWidth); - + QBrush brush; if (usingFeather) { QRadialGradient radialGrad(thePoint, 0.5 * brushWidth); setGaussianGradient(radialGrad, fillColor, opacity, mOffset); - - mBufferImg.drawEllipse(rectangle, Qt::NoPen, radialGrad, - QPainter::CompositionMode_SourceOver, false); + brush = radialGrad; } else { - mBufferImg.drawEllipse(rectangle, Qt::NoPen, QBrush(fillColor, Qt::SolidPattern), - QPainter::CompositionMode_SourceOver, useAA); + brush = QBrush(fillColor, Qt::SolidPattern); } + mTiledBuffer.drawBrush(thePoint, brushWidth, mEditor->view()->mapScreenToCanvas(mCursorImg.rect()).width(), Qt::NoPen, brush, compMode, useAA); +} - update(mEditor->view()->mapCanvasToScreen(mBufferImg.bounds()).toRect().adjusted(-1, -1, 1, 1)); +void ScribbleArea::drawPolyline(QPainterPath path, QPen pen, bool useAA) +{ + BlitRect blitRect; + + // In order to clear what was previously dirty, we need to include the previous buffer bound + // this ensures that we won't see stroke artifacts + blitRect.extend(mEditor->view()->mapCanvasToScreen(mTiledBuffer.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); + + mTiledBuffer.clear(); + mTiledBuffer.drawPath(path, mEditor->view()->mapScreenToCanvas(mCursorImg.rect()).width(), pen, Qt::NoBrush, QPainter::CompositionMode_SourceOver, useAA); + + // And update only the affected area + update(blitRect.adjusted(-1, -1, 1, 1)); +} + +void ScribbleArea::endStroke() +{ + if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP) { + paintBitmapBuffer(); + } + + onFrameModified(mEditor->currentFrame()); } void ScribbleArea::flipSelection(bool flipVertical) @@ -1367,12 +1375,10 @@ void ScribbleArea::blurBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF BitmapImage bmiSrcClip = bmiSource_->copy(srcRect.toAlignedRect()); BitmapImage bmiTmpClip = bmiSrcClip; // TODO: find a shorter way - bmiTmpClip.drawRect(srcRect, Qt::NoPen, radialGrad, QPainter::CompositionMode_Source, mPrefs->isOn(SETTING::ANTIALIAS)); + bmiTmpClip.drawRect(srcRect, Qt::NoPen, radialGrad, QPainter::CompositionMode_Source, true); bmiSrcClip.bounds().moveTo(trgRect.topLeft().toPoint()); bmiTmpClip.paste(&bmiSrcClip, QPainter::CompositionMode_SourceIn); - mBufferImg.paste(&bmiTmpClip); - - update(mEditor->view()->mapCanvasToScreen(mBufferImg.bounds()).toRect().adjusted(-1, -1, 1, 1)); + mTiledBuffer.drawImage(*bmiTmpClip.image(), bmiTmpClip.bounds(), QPainter::CompositionMode_SourceOver, mPrefs->isOn(SETTING::ANTIALIAS)); } void ScribbleArea::liquifyBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF thePoint_, qreal brushWidth_, qreal mOffset_, qreal opacity_) @@ -1424,28 +1430,7 @@ void ScribbleArea::liquifyBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPoi } } } - mBufferImg.paste(&bmiTmpClip); - - update(mEditor->view()->mapCanvasToScreen(mBufferImg.bounds()).toRect().adjusted(-1, -1, 1, 1)); -} - -void ScribbleArea::drawPolyline(QPainterPath path, QPen pen, bool useAA) -{ - 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); - - 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)); + mTiledBuffer.drawImage(*bmiTmpClip.image(), bmiTmpClip.bounds(), QPainter::CompositionMode_SourceOver, mPrefs->isOn(SETTING::ANTIALIAS)); } /************************************************************************************/ @@ -1493,7 +1478,7 @@ void ScribbleArea::applyTransformedSelection() mEditor->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame()); } - update(); + updateFrame(); } void ScribbleArea::cancelTransformedSelection() @@ -1521,37 +1506,7 @@ void ScribbleArea::cancelTransformedSelection() mOriginalPolygonF = QPolygonF(); mEditor->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame()); - updateCurrentFrame(); - } -} - -void ScribbleArea::displaySelectionProperties() -{ - Layer* layer = mEditor->layers()->currentLayer(); - if (layer == nullptr) { return; } - if (layer->type() == Layer::VECTOR) - { - VectorImage* vectorImage = currentVectorImage(layer); - if (vectorImage == nullptr) { return; } - //vectorImage->applySelectionTransformation(); - if (currentTool()->type() == MOVE) - { - int selectedCurve = vectorImage->getFirstSelectedCurve(); - if (selectedCurve != -1) - { - mEditor->tools()->setWidth(vectorImage->curve(selectedCurve).getWidth()); - mEditor->tools()->setFeather(vectorImage->curve(selectedCurve).getFeather()); - mEditor->tools()->setInvisibility(vectorImage->curve(selectedCurve).isInvisible()); - mEditor->tools()->setPressure(vectorImage->curve(selectedCurve).getVariableWidth()); - mEditor->color()->setColorNumber(vectorImage->curve(selectedCurve).getColorNumber()); - } - - int selectedArea = vectorImage->getFirstSelectedArea(); - if (selectedArea != -1) - { - mEditor->color()->setColorNumber(vectorImage->mArea[selectedArea].mColorNumber); - } - } + updateFrame(); } } @@ -1561,12 +1516,6 @@ void ScribbleArea::toggleThinLines() setEffect(SETTING::INVISIBLE_LINES, !previousValue); } -void ScribbleArea::toggleOutlines() -{ - mIsSimplified = !mIsSimplified; - setEffect(SETTING::OUTLINES, mIsSimplified); -} - void ScribbleArea::setLayerVisibility(LayerVisibility visibility) { mLayerVisibility = visibility; @@ -1599,21 +1548,6 @@ BaseTool* ScribbleArea::currentTool() const return editor()->tools()->currentTool(); } -BaseTool* ScribbleArea::getTool(ToolType eToolType) -{ - return editor()->tools()->getTool(eToolType); -} - -void ScribbleArea::setCurrentTool(ToolType eToolMode) -{ - Q_UNUSED(eToolMode) - - // change cursor - setCursor(currentTool()->cursor()); - updateCanvasCursor(); - updateCurrentFrame(); -} - void ScribbleArea::deleteSelection() { auto selectMan = mEditor->select(); @@ -1694,21 +1628,3 @@ void ScribbleArea::paletteColorChanged(QColor color) invalidateAllCache(); } - -void ScribbleArea::floodFillError(int errorType) -{ - QString message, error; - if (errorType == 1) { message = tr("There is a gap in your drawing (or maybe you have zoomed too much)."); } - if (errorType == 2 || errorType == 3) - { - message = tr("Sorry! This doesn't always work." - "Please try again (zoom a bit, click at another location... )
" - "if it doesn't work, zoom a bit and check that your paths are connected by pressing F1.)."); - } - - if (errorType == 1) { error = tr("Out of bound.", "Bucket tool fill error message"); } - if (errorType == 2) { error = tr("Could not find a closed path.", "Bucket tool fill error message"); } - if (errorType == 3) { error = tr("Could not find the root index.", "Bucket tool fill error message"); } - QMessageBox::warning(this, tr("Flood fill error"), tr("%1

Error: %2").arg(message, error), QMessageBox::Ok, QMessageBox::Ok); - mEditor->deselectAll(); -} diff --git a/core_lib/src/interface/scribblearea.h b/core_lib/src/interface/scribblearea.h index 6b443ee2fc..41c62ca6a1 100644 --- a/core_lib/src/interface/scribblearea.h +++ b/core_lib/src/interface/scribblearea.h @@ -39,6 +39,7 @@ GNU General Public License for more details. #include "strokemanager.h" #include "selectionpainter.h" #include "camerapainter.h" +#include "tiledbuffer.h" class Layer; class Editor; @@ -53,7 +54,6 @@ class ScribbleArea : public QWidget Q_OBJECT friend class MoveTool; - friend class EditTool; friend class SmudgeTool; friend class BucketTool; @@ -67,31 +67,23 @@ class ScribbleArea : public QWidget Editor* editor() const { return mEditor; } void deleteSelection(); - void displaySelectionProperties(); void applyTransformedSelection(); void cancelTransformedSelection(); bool isLayerPaintable() const; - QVector calcSelectionCenterPoints(); - void setEffect(SETTING e, bool isOn); LayerVisibility getLayerVisibility() const { return mLayerVisibility; } qreal getCurveSmoothing() const { return mCurveSmoothingLevel; } - bool usePressure() const { return mUsePressure; } bool makeInvisible() const { return mMakeInvisible; } - QRect getCameraRect(); QPointF getCentralPoint(); - /** Update current frame. - * calls update() behind the scene and update cache if necessary */ - void updateCurrentFrame(); /** Update frame. * calls update() behind the scene and update cache if necessary */ - void updateFrame(int frame); + void updateFrame(); /** Frame scrubbed, invalidate relevant cache */ void onScrubbed(int frameNumber); @@ -127,17 +119,11 @@ class ScribbleArea : public QWidget /** Tool changed, invalidate cache and frame if needed */ void onToolChanged(ToolType); - /** Set frame on layer to modified and invalidate current frame cache */ - void setModified(int layerNumber, int frameNumber); - void setModified(const Layer* layer, int frameNumber); + void endStroke(); void flipSelection(bool flipVertical); BaseTool* currentTool() const; - BaseTool* getTool(ToolType eToolMode); - void setCurrentTool(ToolType eToolMode); - - void floodFillError(int errorType); bool isMouseInUse() const { return mMouseInUse; } bool isTabletInUse() const { return mTabletInUse; } @@ -148,14 +134,12 @@ class ScribbleArea : public QWidget signals: void multiLayerOnionSkinChanged(bool); - void refreshPreview(); void selectionUpdated(); public slots: void clearImage(); void setCurveSmoothing(int); void toggleThinLines(); - void toggleOutlines(); void increaseLayerVisibilityIndex(); void decreaseLayerVisibilityIndex(); void setLayerVisibility(LayerVisibility visibility); @@ -164,6 +148,8 @@ public slots: void paletteColorChanged(QColor); void showLayerNotVisibleWarning(); + void onTileUpdated(TiledBuffer* tiledBuffer, Tile* tile); + void onTileCreated(TiledBuffer* tiledBuffer, Tile* tile); protected: bool event(QEvent *event) override; @@ -180,17 +166,16 @@ public slots: public: void drawPolyline(QPainterPath path, QPen pen, bool useAA); - void drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm); void drawPath(QPainterPath path, QPen pen, QBrush brush, QPainter::CompositionMode cm); void drawPen(QPointF thePoint, qreal brushWidth, QColor fillColor, bool useAA = true); void drawPencil(QPointF thePoint, qreal brushWidth, qreal fixedBrushFeather, QColor fillColor, qreal opacity); - void drawBrush(QPointF thePoint, qreal brushWidth, qreal offset, QColor fillColor, qreal opacity, bool usingFeather = true, bool useAA = false); + void drawBrush(QPointF thePoint, qreal brushWidth, qreal offset, QColor fillColor, QPainter::CompositionMode compMode, qreal opacity, bool usingFeather = true, bool useAA = false); void blurBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF thePoint_, qreal brushWidth_, qreal offset_, qreal opacity_); void liquifyBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF thePoint_, qreal brushWidth_, qreal offset_, qreal opacity_); void paintBitmapBuffer(); void paintCanvasCursor(QPainter& painter); - void clearBitmapBuffer(); + void clearDrawingBuffer(); void setGaussianGradient(QGradient &gradient, QColor color, qreal opacity, qreal offset); void pointerPressEvent(PointerEvent*); @@ -203,10 +188,9 @@ public slots: /// on an empty frame, and if so, takes action according to use preference. void handleDrawingOnEmptyFrame(); - BitmapImage mBufferImg; // used to draw strokes for both bitmap and vector + TiledBuffer mTiledBuffer; QPixmap mCursorImg; - QPixmap mTransCursImg; private: @@ -239,27 +223,17 @@ public slots: BitmapImage* currentBitmapImage(Layer* layer) const; VectorImage* currentVectorImage(Layer* layer) const; - MoveMode mMoveMode = MoveMode::NONE; - std::unique_ptr mStrokeManager; Editor* mEditor = nullptr; - - bool mIsSimplified = false; - bool mShowThinLines = false; bool mQuickSizing = true; LayerVisibility mLayerVisibility = LayerVisibility::ALL; - bool mUsePressure = true; bool mMakeInvisible = false; - bool mToolCursors = true; qreal mCurveSmoothingLevel = 0.0; - bool mMultiLayerOnionSkin = false; // future use. If required, just add a checkbox to updated it. - QColor mOnionColor; + bool mMultiLayerOnionSkin = false; // Future use. If required, just add a checkbox to update it. int mDeltaFactor = 1; -private: - /* Under certain circumstances a mouse press event will fire after a tablet release event. This causes unexpected behaviours for some of the tools, eg. the bucket. The problem only seems to occur on windows and only when tapping. @@ -268,9 +242,7 @@ public slots: The following will filter mouse events created after a tablet release event. */ void tabletReleaseEventFired(); - bool mKeyboardInUse = false; bool mMouseInUse = false; - bool mMouseRightButtonInUse = false; bool mTabletInUse = false; qreal mDevicePixelRatio = 1.; diff --git a/core_lib/src/managers/toolmanager.cpp b/core_lib/src/managers/toolmanager.cpp index add88969db..f319c59de9 100644 --- a/core_lib/src/managers/toolmanager.cpp +++ b/core_lib/src/managers/toolmanager.cpp @@ -103,6 +103,9 @@ void ToolManager::setDefaultTool() void ToolManager::setCurrentTool(ToolType eToolType) { + // We're already using this tool + if (mCurrentTool == getTool(eToolType)) { return; } + if (mCurrentTool != nullptr) { mCurrentTool->leavingThisTool(); @@ -334,6 +337,9 @@ int ToolManager::propertySwitch(bool condition, int tool) void ToolManager::tabletSwitchToEraser() { mTabletEraserTool = getTool(ERASER); + + // We should only notify a tool change if we're positive that the state has changed and it should only happen once + // if the user for some reason is using another temporary tool at the same time, that takes first priority if (mTemporaryTool == nullptr) { emit toolChanged(ERASER); @@ -342,9 +348,9 @@ void ToolManager::tabletSwitchToEraser() void ToolManager::tabletRestorePrevTool() { - mTabletEraserTool = nullptr; - if (mTemporaryTool == nullptr) + if (mTemporaryTool == nullptr && mTabletEraserTool != nullptr) { + mTabletEraserTool = nullptr; emit toolChanged(currentTool()->type()); } } diff --git a/core_lib/src/tool/basetool.cpp b/core_lib/src/tool/basetool.cpp index c6029edce8..759d49c8e0 100644 --- a/core_lib/src/tool/basetool.cpp +++ b/core_lib/src/tool/basetool.cpp @@ -111,7 +111,7 @@ void BaseTool::pointerDoubleClickEvent(PointerEvent* event) */ bool BaseTool::isDrawingTool() { - if (type() == ToolType::HAND || type() == ToolType::MOVE || type() == ToolType::SELECT ) + if (type() == ToolType::HAND || type() == ToolType::MOVE || type() == ToolType::CAMERA || type() == ToolType::SELECT ) { return false; } diff --git a/core_lib/src/tool/brushtool.cpp b/core_lib/src/tool/brushtool.cpp index 8461e285d7..88b6b6e22e 100644 --- a/core_lib/src/tool/brushtool.cpp +++ b/core_lib/src/tool/brushtool.cpp @@ -173,10 +173,9 @@ void BrushTool::pointerReleaseEvent(PointerEvent *event) drawStroke(); } - if (layer->type() == Layer::BITMAP) - paintBitmapStroke(); - else if (layer->type() == Layer::VECTOR) - paintVectorStroke(); + if (layer->type() == Layer::VECTOR) { + paintVectorStroke(layer); + } endStroke(); } @@ -196,6 +195,7 @@ void BrushTool::paintAt(QPointF point) brushWidth, properties.feather, mEditor->color()->frontColor(), + QPainter::CompositionMode_SourceOver, opacity, true); } @@ -232,6 +232,7 @@ void BrushTool::drawStroke() brushWidth, properties.feather, mEditor->color()->frontColor(), + QPainter::CompositionMode_SourceOver, opacity, true); if (i == (steps - 1)) @@ -277,25 +278,17 @@ void BrushTool::drawStroke() } } -void BrushTool::paintBitmapStroke() -{ - mScribbleArea->paintBitmapBuffer(); - mScribbleArea->clearBitmapBuffer(); -} - // This function uses the points from DrawStroke // and turns them into vector lines. -void BrushTool::paintVectorStroke() +void BrushTool::paintVectorStroke(Layer* layer) { if (mStrokePoints.empty()) return; - Layer* layer = mEditor->layers()->currentLayer(); - if (layer->type() == Layer::VECTOR && mStrokePoints.size() > -1) { // Clear the temporary pixel path - mScribbleArea->clearBitmapBuffer(); + mScribbleArea->clearDrawingBuffer(); qreal tol = mScribbleArea->getCurveSmoothing() / mEditor->view()->scaling(); BezierCurve curve(mStrokePoints, mStrokePressures, tol); diff --git a/core_lib/src/tool/brushtool.h b/core_lib/src/tool/brushtool.h index eb1e2413be..0e82d65101 100644 --- a/core_lib/src/tool/brushtool.h +++ b/core_lib/src/tool/brushtool.h @@ -21,6 +21,7 @@ GNU General Public License for more details. #include "stroketool.h" #include +class Layer; class BrushTool : public StrokeTool { @@ -38,8 +39,7 @@ class BrushTool : public StrokeTool void pointerReleaseEvent(PointerEvent*) override; void drawStroke(); - void paintVectorStroke(); - void paintBitmapStroke(); + void paintVectorStroke(Layer* layer); void paintAt(QPointF point); void setWidth(const qreal width) override; diff --git a/core_lib/src/tool/buckettool.cpp b/core_lib/src/tool/buckettool.cpp index 2ef4f01aa5..69d4d17767 100644 --- a/core_lib/src/tool/buckettool.cpp +++ b/core_lib/src/tool/buckettool.cpp @@ -284,7 +284,7 @@ void BucketTool::paintBitmap() void BucketTool::paintVector(Layer* layer) { - mScribbleArea->clearBitmapBuffer(); + mScribbleArea->clearDrawingBuffer(); VectorImage* vectorImage = static_cast(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0); if (vectorImage == nullptr) { return; } // Can happen if the first frame is deleted while drawing @@ -324,8 +324,6 @@ void BucketTool::drawStroke() if (layer->type() == Layer::VECTOR) { mCurrentWidth = 30; - int rad = qRound((mCurrentWidth / 2 + 2) * mEditor->view()->scaling()); - QColor pathColor = qPremultiply(mEditor->color()->frontColor().rgba()); QPen pen(pathColor, diff --git a/core_lib/src/tool/cameratool.cpp b/core_lib/src/tool/cameratool.cpp index f4fd3c0309..feb0e54621 100644 --- a/core_lib/src/tool/cameratool.cpp +++ b/core_lib/src/tool/cameratool.cpp @@ -229,7 +229,7 @@ void CameraTool::resetCameraPath() Q_ASSERT(layer->type() == Layer::CAMERA); layer->setPathMovedAtFrame(mEditor->currentFrame(), false); - mEditor->updateCurrentFrame(); + mEditor->updateFrame(); } void CameraTool::resetTransform(CameraFieldOption option) @@ -261,7 +261,7 @@ void CameraTool::transformCamera(Qt::KeyboardModifiers keyMod) transformView(layer, mCamMoveMode, getCurrentPoint(), mTransformOffset, -angleDeg, mEditor->currentFrame()); - mEditor->updateCurrentFrame(); + mEditor->updateFrame(); mTransformOffset = getCurrentPoint(); } @@ -271,7 +271,7 @@ void CameraTool::transformCameraPath() LayerCamera* layer = static_cast(editor()->layers()->currentLayer()); layer->updatePathControlPointAtFrame(getCurrentPoint(), mDragPathFrame); - mEditor->updateCurrentFrame(); + mEditor->updateFrame(); } int CameraTool::constrainedRotation(const qreal rotatedAngle, const int rotationIncrement) const @@ -304,7 +304,7 @@ void CameraTool::pointerMoveEvent(PointerEvent* event) } mScribbleArea->updateToolCursor(); mEditor->view()->forceUpdateViewTransform(); - mEditor->updateCurrentFrame(); + mEditor->updateFrame(); } void CameraTool::pointerReleaseEvent(PointerEvent* event) diff --git a/core_lib/src/tool/erasertool.cpp b/core_lib/src/tool/erasertool.cpp index ba7ecf6379..0ae1005201 100644 --- a/core_lib/src/tool/erasertool.cpp +++ b/core_lib/src/tool/erasertool.cpp @@ -180,6 +180,7 @@ void EraserTool::pointerReleaseEvent(PointerEvent *event) { drawStroke(); } + removeVectorPaint(); endStroke(); } @@ -199,6 +200,7 @@ void EraserTool::paintAt(QPointF point) brushWidth, properties.feather, QColor(255, 255, 255, 255), + QPainter::CompositionMode_DestinationOut, opacity, properties.useFeather, properties.useAA == ON); @@ -238,6 +240,7 @@ void EraserTool::drawStroke() brushWidth, properties.feather, Qt::white, + QPainter::CompositionMode_DestinationOut, opacity, properties.useFeather, properties.useAA == ON); @@ -272,14 +275,9 @@ void EraserTool::drawStroke() void EraserTool::removeVectorPaint() { Layer* layer = mEditor->layers()->currentLayer(); - if (layer->type() == Layer::BITMAP) - { - mScribbleArea->paintBitmapBuffer(); - mScribbleArea->clearBitmapBuffer(); - } - else if (layer->type() == Layer::VECTOR) + if (layer->type() == Layer::VECTOR) { - mScribbleArea->clearBitmapBuffer(); + mScribbleArea->clearDrawingBuffer(); VectorImage* vectorImage = static_cast(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0); if (vectorImage == nullptr) { return; } // Can happen if the first frame is deleted while drawing // Clear the area containing the last point diff --git a/core_lib/src/tool/movetool.cpp b/core_lib/src/tool/movetool.cpp index 8ddc3a3da0..ec02bafc07 100644 --- a/core_lib/src/tool/movetool.cpp +++ b/core_lib/src/tool/movetool.cpp @@ -119,7 +119,7 @@ void MoveTool::pointerPressEvent(PointerEvent* event) mEditor->overlays()->updatePerspective(mapped); } - mEditor->updateCurrentFrame(); + mEditor->updateFrame(); } void MoveTool::pointerMoveEvent(PointerEvent* event) @@ -154,7 +154,7 @@ void MoveTool::pointerMoveEvent(PointerEvent* event) storeClosestVectorCurve(mCurrentLayer); } } - mEditor->updateCurrentFrame(); + mEditor->updateFrame(); } void MoveTool::pointerReleaseEvent(PointerEvent*) diff --git a/core_lib/src/tool/penciltool.cpp b/core_lib/src/tool/penciltool.cpp index e1249b67f4..5e5e04636d 100644 --- a/core_lib/src/tool/penciltool.cpp +++ b/core_lib/src/tool/penciltool.cpp @@ -29,7 +29,6 @@ GNU General Public License for more details. #include "editor.h" #include "scribblearea.h" -#include "blitrect.h" #include "layervector.h" #include "vectorimage.h" @@ -187,10 +186,9 @@ void PencilTool::pointerReleaseEvent(PointerEvent *event) } Layer* layer = mEditor->layers()->currentLayer(); - if (layer->type() == Layer::BITMAP) - paintBitmapStroke(); - else if (layer->type() == Layer::VECTOR) + if (layer->type() == Layer::VECTOR) { paintVectorStroke(layer); + } endStroke(); } @@ -275,20 +273,13 @@ void PencilTool::drawStroke() } } - -void PencilTool::paintBitmapStroke() -{ - mScribbleArea->paintBitmapBuffer(); - mScribbleArea->clearBitmapBuffer(); -} - void PencilTool::paintVectorStroke(Layer* layer) { if (mStrokePoints.empty()) return; // Clear the temporary pixel path - mScribbleArea->clearBitmapBuffer(); + mScribbleArea->clearDrawingBuffer(); qreal tol = mScribbleArea->getCurveSmoothing() / mEditor->view()->scaling(); BezierCurve curve(mStrokePoints, mStrokePressures, tol); diff --git a/core_lib/src/tool/penciltool.h b/core_lib/src/tool/penciltool.h index adb5019da3..0b15fb719b 100644 --- a/core_lib/src/tool/penciltool.h +++ b/core_lib/src/tool/penciltool.h @@ -40,7 +40,6 @@ class PencilTool : public StrokeTool void drawStroke(); void paintAt(QPointF point); void paintVectorStroke(Layer* layer); - void paintBitmapStroke(); void setWidth(const qreal width) override; void setFeather(const qreal feather) override; diff --git a/core_lib/src/tool/pentool.cpp b/core_lib/src/tool/pentool.cpp index 62f9996591..482d5c277f 100644 --- a/core_lib/src/tool/pentool.cpp +++ b/core_lib/src/tool/pentool.cpp @@ -153,10 +153,9 @@ void PenTool::pointerReleaseEvent(PointerEvent *event) drawStroke(); } - if (layer->type() == Layer::BITMAP) - paintBitmapStroke(); - else if (layer->type() == Layer::VECTOR) + if (layer->type() == Layer::VECTOR) { paintVectorStroke(layer); + } endStroke(); } @@ -235,19 +234,13 @@ void PenTool::drawStroke() } } -void PenTool::paintBitmapStroke() -{ - mScribbleArea->paintBitmapBuffer(); - mScribbleArea->clearBitmapBuffer(); -} - void PenTool::paintVectorStroke(Layer* layer) { if (mStrokePoints.empty()) return; // Clear the temporary pixel path - mScribbleArea->clearBitmapBuffer(); + mScribbleArea->clearDrawingBuffer(); qreal tol = mScribbleArea->getCurveSmoothing() / mEditor->view()->scaling(); BezierCurve curve(mStrokePoints, mStrokePressures, tol); diff --git a/core_lib/src/tool/pentool.h b/core_lib/src/tool/pentool.h index ff58abc11c..a904944adc 100644 --- a/core_lib/src/tool/pentool.h +++ b/core_lib/src/tool/pentool.h @@ -39,7 +39,6 @@ class PenTool : public StrokeTool void drawStroke(); void paintAt(QPointF point); void paintVectorStroke(Layer *layer); - void paintBitmapStroke(); void setWidth(const qreal width) override; void setPressure(const bool pressure) override; diff --git a/core_lib/src/tool/polylinetool.cpp b/core_lib/src/tool/polylinetool.cpp index 866753e002..c404e343c6 100644 --- a/core_lib/src/tool/polylinetool.cpp +++ b/core_lib/src/tool/polylinetool.cpp @@ -243,14 +243,13 @@ void PolylineTool::drawPolyline(QList points, QPointF endPoint) void PolylineTool::cancelPolyline() { // Clear the in-progress polyline from the bitmap buffer. - mScribbleArea->clearBitmapBuffer(); - mScribbleArea->updateCurrentFrame(); + mScribbleArea->clearDrawingBuffer(); + mScribbleArea->updateFrame(); } void PolylineTool::endPolyline(QList points) { Layer* layer = mEditor->layers()->currentLayer(); - mScribbleArea->clearBitmapBuffer(); if (layer->type() == Layer::VECTOR) { @@ -274,11 +273,7 @@ void PolylineTool::endPolyline(QList points) if (layer->type() == Layer::BITMAP) { 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); } - - mScribbleArea->clearBitmapBuffer(); + mScribbleArea->endStroke(); mEditor->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame()); } diff --git a/core_lib/src/tool/selecttool.cpp b/core_lib/src/tool/selecttool.cpp index df24930ae2..52d57fa684 100644 --- a/core_lib/src/tool/selecttool.cpp +++ b/core_lib/src/tool/selecttool.cpp @@ -116,7 +116,7 @@ void SelectTool::beginSelection() mAnchorOriginPoint = getLastPoint(); } - mScribbleArea->updateCurrentFrame(); + mScribbleArea->updateFrame(); } void SelectTool::pointerPressEvent(PointerEvent* event) @@ -160,7 +160,7 @@ void SelectTool::pointerMoveEvent(PointerEvent*) } } - mScribbleArea->updateCurrentFrame(); + mScribbleArea->updateFrame(); } void SelectTool::pointerReleaseEvent(PointerEvent* event) @@ -189,7 +189,7 @@ void SelectTool::pointerReleaseEvent(PointerEvent* event) mSelectionRect = mEditor->select()->mapToSelection(mEditor->select()->mySelectionRect()).boundingRect(); mScribbleArea->updateToolCursor(); - mScribbleArea->updateCurrentFrame(); + mScribbleArea->updateFrame(); } bool SelectTool::maybeDeselect() diff --git a/core_lib/src/tool/smudgetool.cpp b/core_lib/src/tool/smudgetool.cpp index d11c56fbf9..f555380009 100644 --- a/core_lib/src/tool/smudgetool.cpp +++ b/core_lib/src/tool/smudgetool.cpp @@ -257,7 +257,7 @@ void SmudgeTool::pointerReleaseEvent(PointerEvent* event) { drawStroke(); mScribbleArea->paintBitmapBuffer(); - mScribbleArea->clearBitmapBuffer(); + mScribbleArea->clearDrawingBuffer(); endStroke(); } else if (layer->type() == Layer::VECTOR) @@ -317,7 +317,7 @@ void SmudgeTool::drawStroke() QPointF sourcePoint = mLastBrushPoint; for (int i = 0; i < steps; i++) { - targetImage.paste(&mScribbleArea->mBufferImg); + targetImage.paste(&mScribbleArea->mTiledBuffer); QPointF targetPoint = mLastBrushPoint + (i + 1) * (brushStep) * (b - mLastBrushPoint) / distance; mScribbleArea->liquifyBrush(&targetImage, sourcePoint, @@ -342,9 +342,8 @@ void SmudgeTool::drawStroke() QPointF sourcePoint = mLastBrushPoint; for (int i = 0; i < steps; i++) { - targetImage.paste(&mScribbleArea->mBufferImg); + targetImage.paste(&mScribbleArea->mTiledBuffer); QPointF targetPoint = mLastBrushPoint + (i + 1) * (brushStep) * (b - mLastBrushPoint) / distance; - rect.extend(targetPoint.toPoint()); mScribbleArea->blurBrush(&targetImage, sourcePoint, targetPoint, diff --git a/core_lib/src/tool/stroketool.cpp b/core_lib/src/tool/stroketool.cpp index cf1b4b6e7c..6b8e8c1122 100644 --- a/core_lib/src/tool/stroketool.cpp +++ b/core_lib/src/tool/stroketool.cpp @@ -101,6 +101,7 @@ void StrokeTool::endStroke() enableCoalescing(); mEditor->setModified(mEditor->currentLayerIndex(), mEditor->currentFrame()); + mScribbleArea->endStroke(); } void StrokeTool::drawStroke() diff --git a/core_lib/src/tool/stroketool.h b/core_lib/src/tool/stroketool.h index e4b5c7d924..7f97c793b5 100644 --- a/core_lib/src/tool/stroketool.h +++ b/core_lib/src/tool/stroketool.h @@ -57,7 +57,7 @@ class StrokeTool : public BaseTool virtual bool emptyFrameActionEnabled(); private: - QPointF mLastPixel { 0, 0 }; + QPointF mLastPixel { 0, 0 }; }; #endif // STROKETOOL_H diff --git a/core_lib/src/util/blitrect.cpp b/core_lib/src/util/blitrect.cpp index 3b0645cec4..f378fd93fe 100644 --- a/core_lib/src/util/blitrect.cpp +++ b/core_lib/src/util/blitrect.cpp @@ -52,7 +52,12 @@ void BlitRect::extend(const QPoint p) void BlitRect::extend(const QRect& rect) { - extend(rect.topLeft(), rect.size()); + // For historical reasons the values returned by the bottom() and + // right() functions deviate from the true bottom-right corner of the rectangle: + // The right() function returns left() + width() - 1 and the bottom() + // function returns top() + height() - 1 + // In order to counter that, we subtract 1 from width and height + extend(rect.topLeft(), QSize(rect.width() - 1, rect.height() - 1)); } void BlitRect::extend(const QPoint& p, const QSize& size) @@ -66,8 +71,8 @@ void BlitRect::extend(const QPoint& p, const QSize& size) 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()); } + if (right() - size.width() < p.x()) { setRight(p.x() + size.width()); } + if (bottom() - size.height() < p.y()) { setBottom(p.y() + size.height()); } } }