Pencil2D Animation
Download Community News Docs Contribute
  • Overview
  • Articles
  • Code
  •  
  • Class List
  • Class Index
  • Class Hierarchy
  • Class Members
  • File List
Loading...
Searching...
No Matches
  • core_lib
  • src
canvaspainter.cpp
1/*
2
3Pencil2D - Traditional Animation Software
4Copyright (C) 2012-2020 Matthew Chiawen Chang
5
6This program is free software; you can redistribute it and/or
7modify it under the terms of the GNU General Public License
8as published by the Free Software Foundation; version 2 of the License.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13GNU General Public License for more details.
14
15*/
16
17#include "canvaspainter.h"
18
19#include <QtMath>
20
21#include "object.h"
22#include "layerbitmap.h"
23#include "layervector.h"
24#include "bitmapimage.h"
25#include "tile.h"
26#include "tiledbuffer.h"
27#include "vectorimage.h"
28
29#include "painterutils.h"
30
31CanvasPainter::CanvasPainter(QPixmap& canvas) : mCanvas(canvas)
32{
33 reset();
34}
35
36CanvasPainter::~CanvasPainter()
37{
38}
39
40void CanvasPainter::reset()
41{
42 mPostLayersPixmap = QPixmap(mCanvas.size());
43 mPreLayersPixmap = QPixmap(mCanvas.size());
44 mCurrentLayerPixmap = QPixmap(mCanvas.size());
45 mOnionSkinPixmap = QPixmap(mCanvas.size());
46 mPreLayersPixmap.fill(Qt::transparent);
47 mCanvas.fill(Qt::transparent);
48 mCurrentLayerPixmap.fill(Qt::transparent);
49 mPostLayersPixmap.fill(Qt::transparent);
50 mOnionSkinPixmap.fill(Qt::transparent);
51 mCurrentLayerPixmap.setDevicePixelRatio(mCanvas.devicePixelRatioF());
52 mPreLayersPixmap.setDevicePixelRatio(mCanvas.devicePixelRatioF());
53 mPostLayersPixmap.setDevicePixelRatio(mCanvas.devicePixelRatioF());
54 mOnionSkinPixmap.setDevicePixelRatio(mCanvas.devicePixelRatioF());
55}
56
57void CanvasPainter::setViewTransform(const QTransform view, const QTransform viewInverse)
58{
59 if (mViewTransform != view || mViewInverse != viewInverse) {
60 mViewTransform = view;
61 mViewInverse = viewInverse;
62 }
63}
64
65void CanvasPainter::setTransformedSelection(QRect selection, QTransform transform)
66{
67 // Make sure that the selection is not empty
68 if (selection.width() > 0 && selection.height() > 0)
69 {
70 mSelection = selection;
71 mSelectionTransform = transform;
72 mRenderTransform = true;
73 }
74 else
75 {
76 // Otherwise we shouldn't be in transformation mode
77 ignoreTransformedSelection();
78 }
79}
80
81void CanvasPainter::ignoreTransformedSelection()
82{
83 mRenderTransform = false;
84 mSelectionTransform.reset();
85 mSelection = QRect();
86}
87
88void CanvasPainter::paintCached(const QRect& blitRect)
89{
90 if (!mPreLayersPixmapCacheValid)
91 {
92 QPainter preLayerPainter;
93 initializePainter(preLayerPainter, mPreLayersPixmap, blitRect);
94 renderPreLayers(preLayerPainter, blitRect);
95 preLayerPainter.end();
96 mPreLayersPixmapCacheValid = true;
97 }
98
99 QPainter mainPainter;
100 initializePainter(mainPainter, mCanvas, blitRect);
101 mainPainter.setWorldMatrixEnabled(false);
102 mainPainter.drawPixmap(mPointZero, mPreLayersPixmap);
103 mainPainter.setWorldMatrixEnabled(true);
104
105 paintCurrentFrame(mainPainter, blitRect, mCurrentLayerIndex, mCurrentLayerIndex);
106
107 if (!mPostLayersPixmapCacheValid)
108 {
109 QPainter postLayerPainter;
110 initializePainter(postLayerPainter, mPostLayersPixmap, blitRect);
111 renderPostLayers(postLayerPainter, blitRect);
112 postLayerPainter.end();
113 mPostLayersPixmapCacheValid = true;
114 }
115
116 mainPainter.setWorldMatrixEnabled(false);
117 mainPainter.drawPixmap(mPointZero, mPostLayersPixmap);
118 mainPainter.setWorldMatrixEnabled(true);
119}
120
121void CanvasPainter::resetLayerCache()
122{
123 mPreLayersPixmapCacheValid = false;
124 mPostLayersPixmapCacheValid = false;
125}
126
127void CanvasPainter::initializePainter(QPainter& painter, QPaintDevice& device, const QRect& blitRect)
128{
129 painter.begin(&device);
130
131 // Only draw inside the clipped rectangle
132 painter.setClipRect(blitRect);
133
134 // Clear the area that's about to be painted again, to avoid painting on top of existing pixels
135 // causing artifacts.
136 painter.setCompositionMode(QPainter::CompositionMode_Clear);
137 painter.fillRect(blitRect, Qt::transparent);
138
139 // Surface has been cleared and is ready to be painted on
140 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
141 painter.setWorldMatrixEnabled(true);
142 painter.setWorldTransform(mViewTransform);
143}
144
145void CanvasPainter::renderPreLayers(QPainter& painter, const QRect& blitRect)
146{
147 if (mOptions.eLayerVisibility != LayerVisibility::CURRENTONLY || mObject->getLayer(mCurrentLayerIndex)->type() == Layer::CAMERA)
148 {
149 paintCurrentFrame(painter, blitRect, 0, mCurrentLayerIndex - 1);
150 }
151
152 paintOnionSkin(painter, blitRect);
153 painter.setOpacity(1.0);
154}
155
156void CanvasPainter::renderPostLayers(QPainter& painter, const QRect& blitRect)
157{
158 if (mOptions.eLayerVisibility != LayerVisibility::CURRENTONLY || mObject->getLayer(mCurrentLayerIndex)->type() == Layer::CAMERA)
159 {
160 paintCurrentFrame(painter, blitRect, mCurrentLayerIndex + 1, mObject->getLayerCount() - 1);
161 }
162}
163
164void CanvasPainter::setPaintSettings(const Object* object, int currentLayer, int frame, TiledBuffer* tiledBuffer)
165{
166 Q_ASSERT(object);
167 mObject = object;
168
169 CANVASPAINTER_LOG("Set CurrentLayerIndex = %d", currentLayer);
170 mCurrentLayerIndex = currentLayer;
171 mFrameNumber = frame;
172 mTiledBuffer = tiledBuffer;
173}
174
175void CanvasPainter::paint(const QRect& blitRect)
176{
177 QPainter preLayerPainter;
178 QPainter mainPainter;
179 QPainter postLayerPainter;
180
181 initializePainter(mainPainter, mCanvas, blitRect);
182
183 initializePainter(preLayerPainter, mPreLayersPixmap, blitRect);
184 renderPreLayers(preLayerPainter, blitRect);
185 preLayerPainter.end();
186
187 mainPainter.setWorldMatrixEnabled(false);
188 mainPainter.drawPixmap(mPointZero, mPreLayersPixmap);
189 mainPainter.setWorldMatrixEnabled(true);
190
191 paintCurrentFrame(mainPainter, blitRect, mCurrentLayerIndex, mCurrentLayerIndex);
192
193 initializePainter(postLayerPainter, mPostLayersPixmap, blitRect);
194 renderPostLayers(postLayerPainter, blitRect);
195 postLayerPainter.end();
196
197 mainPainter.setWorldMatrixEnabled(false);
198 mainPainter.drawPixmap(mPointZero, mPostLayersPixmap);
199 mainPainter.setWorldMatrixEnabled(true);
200
201 mPreLayersPixmapCacheValid = true;
202 mPostLayersPixmapCacheValid = true;
203}
204
205void CanvasPainter::paintOnionSkinOnLayer(QPainter& painter, const QRect& blitRect, Layer* layer)
206{
207 mOnionSkinSubPainter.paint(painter, layer, mOnionSkinPainterOptions, mFrameNumber, [&] (OnionSkinPaintState state, int onionFrameNumber) {
208 if (state == OnionSkinPaintState::PREV) {
209 switch (layer->type())
210 {
211 case Layer::BITMAP: { paintBitmapOnionSkinFrame(painter, blitRect, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizePrevFrames); break; }
212 case Layer::VECTOR: { paintVectorOnionSkinFrame(painter, blitRect, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizePrevFrames); break; }
213 default: break;
214 }
215 }
216 if (state == OnionSkinPaintState::NEXT) {
217 switch (layer->type())
218 {
219 case Layer::BITMAP: { paintBitmapOnionSkinFrame(painter, blitRect, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizeNextFrames); break; }
220 case Layer::VECTOR: { paintVectorOnionSkinFrame(painter, blitRect, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizeNextFrames); break; }
221 default: break;
222 }
223 }
224 });
225}
226
227void CanvasPainter::paintOnionSkin(QPainter& painter, const QRect& blitRect)
228{
229 if (!mOptions.bOnionSkinMultiLayer || mOptions.eLayerVisibility == LayerVisibility::CURRENTONLY) {
230 Layer* layer = mObject->getLayer(mCurrentLayerIndex);
231 paintOnionSkinOnLayer(painter, blitRect, layer);
232 } else {
233 for (int i = 0; i < mObject->getLayerCount(); i++) {
234 Layer* layer = mObject->getLayer(i);
235 if (layer == nullptr) { continue; }
236
237 paintOnionSkinOnLayer(painter, blitRect, layer);
238 }
239 }
240}
241
242void CanvasPainter::paintBitmapOnionSkinFrame(QPainter& painter, const QRect& blitRect, Layer* layer, int nFrame, bool colorize)
243{
244 LayerBitmap* bitmapLayer = static_cast<LayerBitmap*>(layer);
245
246 BitmapImage* bitmapImage = bitmapLayer->getBitmapImageAtFrame(nFrame);
247
248 if (bitmapImage == nullptr) { return; }
249 bitmapImage->loadFile(); // Critical! force the BitmapImage to load the image
250
251 QPainter onionSkinPainter;
252 initializePainter(onionSkinPainter, mOnionSkinPixmap, blitRect);
253
254 onionSkinPainter.drawImage(bitmapImage->topLeft(), *bitmapImage->image());
255 paintOnionSkinFrame(painter, onionSkinPainter, nFrame, colorize, bitmapImage->getOpacity());
256}
257
258void CanvasPainter::paintVectorOnionSkinFrame(QPainter& painter, const QRect& blitRect, Layer* layer, int nFrame, bool colorize)
259{
260 LayerVector* vectorLayer = static_cast<LayerVector*>(layer);
261
262 CANVASPAINTER_LOG("Paint Onion skin vector, Frame = %d", nFrame);
263 VectorImage* vectorImage = vectorLayer->getVectorImageAtFrame(nFrame);
264 if (vectorImage == nullptr) { return; }
265
266 QPainter onionSkinPainter;
267 initializePainter(onionSkinPainter, mOnionSkinPixmap, blitRect);
268
269 vectorImage->paintImage(onionSkinPainter, *mObject, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias);
270 paintOnionSkinFrame(painter, onionSkinPainter, nFrame, colorize, vectorImage->getOpacity());
271}
272
273void CanvasPainter::paintOnionSkinFrame(QPainter& painter, QPainter& onionSkinPainter, int nFrame, bool colorize, qreal frameOpacity)
274{
275 // Don't transform the image here as we used the viewTransform in the image output
276 painter.setWorldMatrixEnabled(false);
277 // Remember to adjust overall opacity based on opacity value from image
278 onionSkinPainter.setOpacity(frameOpacity - (1.0-painter.opacity()));
279 if (colorize)
280 {
281 QColor colorBrush = Qt::transparent; //no color for the current frame
282
283 if (nFrame < mFrameNumber)
284 {
285 colorBrush = Qt::red;
286 }
287 else if (nFrame > mFrameNumber)
288 {
289 colorBrush = Qt::blue;
290 }
291 onionSkinPainter.setWorldMatrixEnabled(false);
292
293 onionSkinPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
294 onionSkinPainter.setBrush(colorBrush);
295 onionSkinPainter.drawRect(painter.viewport());
296 }
297 painter.drawPixmap(mPointZero, mOnionSkinPixmap);
298}
299
300void CanvasPainter::paintCurrentBitmapFrame(QPainter& painter, const QRect& blitRect, Layer* layer, bool isCurrentLayer)
301{
302 LayerBitmap* bitmapLayer = static_cast<LayerBitmap*>(layer);
303 BitmapImage* paintedImage = bitmapLayer->getLastBitmapImageAtFrame(mFrameNumber);
304
305 if (paintedImage == nullptr) { return; }
306 paintedImage->loadFile(); // Critical! force the BitmapImage to load the image
307
308 const bool isDrawing = mTiledBuffer && !mTiledBuffer->bounds().isEmpty();
309
310 QPainter currentBitmapPainter;
311 initializePainter(currentBitmapPainter, mCurrentLayerPixmap, blitRect);
312
313 painter.setWorldMatrixEnabled(false);
314
315 currentBitmapPainter.setOpacity(paintedImage->getOpacity() - (1.0-painter.opacity()));
316 currentBitmapPainter.drawImage(paintedImage->topLeft(), *paintedImage->image());
317
318 if (isCurrentLayer && isDrawing)
319 {
320 currentBitmapPainter.setCompositionMode(mOptions.cmBufferBlendMode);
321 const auto tiles = mTiledBuffer->tiles();
322 for (const Tile* tile : tiles) {
323 currentBitmapPainter.drawPixmap(tile->posF(), tile->pixmap());
324 }
325 }
326
327 // We do not wish to draw selection transformations on anything but the current layer
328 Q_ASSERT(!isDrawing || mSelectionTransform.isIdentity());
329 if (isCurrentLayer && mRenderTransform && !isDrawing) {
330 paintTransformedSelection(currentBitmapPainter, paintedImage, mSelection);
331 }
332
333 painter.drawPixmap(mPointZero, mCurrentLayerPixmap);
334}
335
336void CanvasPainter::paintCurrentVectorFrame(QPainter& painter, const QRect& blitRect, Layer* layer, bool isCurrentLayer)
337{
338 LayerVector* vectorLayer = static_cast<LayerVector*>(layer);
339 VectorImage* vectorImage = vectorLayer->getLastVectorImageAtFrame(mFrameNumber);
340 if (vectorImage == nullptr)
341 {
342 return;
343 }
344
345 QPainter currentVectorPainter;
346 initializePainter(currentVectorPainter, mCurrentLayerPixmap, blitRect);
347
348 const bool isDrawing = mTiledBuffer->isValid();
349
350 if (mRenderTransform) {
351 vectorImage->setSelectionTransformation(mSelectionTransform);
352 }
353
354 // Paint existing vector image to the painter
355 // Remember to adjust opacity based on additional opacity value from the keyframe
356 currentVectorPainter.setOpacity(vectorImage->getOpacity() - (1.0-painter.opacity()));
357 vectorImage->paintImage(currentVectorPainter, *mObject, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias);
358
359 if (isCurrentLayer && isDrawing) {
360 currentVectorPainter.setCompositionMode(mOptions.cmBufferBlendMode);
361
362 const auto tiles = mTiledBuffer->tiles();
363 for (const Tile* tile : tiles) {
364 currentVectorPainter.drawPixmap(tile->posF(), tile->pixmap());
365 }
366 }
367
368 // Don't transform the image here as we used the viewTransform in the image output
369 painter.setWorldMatrixEnabled(false);
370 painter.setTransform(QTransform());
371
372 painter.drawPixmap(mPointZero, mCurrentLayerPixmap);
373}
374
375void CanvasPainter::paintTransformedSelection(QPainter& painter, BitmapImage* bitmapImage, const QRect& selection) const
376{
377 // Make sure there is something selected
378 if (selection.width() == 0 && selection.height() == 0)
379 return;
380
381 QPixmap transformedPixmap = QPixmap(mSelection.size());
382 transformedPixmap.fill(Qt::transparent);
383
384 QPainter imagePainter(&transformedPixmap);
385 imagePainter.translate(-selection.topLeft());
386 imagePainter.drawImage(bitmapImage->topLeft(), *bitmapImage->image());
387 imagePainter.end();
388
389 painter.save();
390
391 painter.setTransform(mViewTransform);
392
393 // Clear the painted area to make it look like the content has been erased
394 painter.save();
395 painter.setCompositionMode(QPainter::CompositionMode_Clear);
396 painter.fillRect(selection, QColor(255,255,255,255));
397 painter.restore();
398
399 // Multiply the selection and view matrix to get proper rotation and scale values
400 // Now the image origin will be topleft
401 painter.setTransform(mSelectionTransform*mViewTransform);
402
403 // Draw the selection image separately and on top
404 painter.drawPixmap(selection, transformedPixmap);
405 painter.restore();
406}
407
414void CanvasPainter::paintCurrentFrame(QPainter& painter, const QRect& blitRect, int startLayer, int endLayer)
415{
416 painter.setOpacity(1.0);
417
418 bool isCameraLayer = mObject->getLayer(mCurrentLayerIndex)->type() == Layer::CAMERA;
419
420 for (int i = startLayer; i <= endLayer; ++i)
421 {
422 Layer* layer = mObject->getLayer(i);
423
424 if (!layer->visible())
425 continue;
426
427 if (mOptions.eLayerVisibility == LayerVisibility::RELATED && !isCameraLayer)
428 {
429 painter.setOpacity(calculateRelativeOpacityForLayer(mCurrentLayerIndex, i, mOptions.fLayerVisibilityThreshold));
430 }
431 bool isCurrentLayer = mCurrentLayerIndex == i;
432
433 CANVASPAINTER_LOG(" Render Layer[%d] %s", i, layer->name());
434 switch (layer->type())
435 {
436 case Layer::BITMAP: { paintCurrentBitmapFrame(painter, blitRect, layer, isCurrentLayer); break; }
437 case Layer::VECTOR: { paintCurrentVectorFrame(painter, blitRect, layer, isCurrentLayer); break; }
438 default: break;
439 }
440 }
441}
BitmapImage
Definition: bitmapimage.h:28
CanvasPainter::initializePainter
void initializePainter(QPainter &painter, QPaintDevice &device, const QRect &blitRect)
CanvasPainter::initializePainter Enriches the painter with a context and sets it's initial matrix.
Definition: canvaspainter.cpp:127
CanvasPainter::paintCurrentFrame
void paintCurrentFrame(QPainter &painter, const QRect &blitRect, int startLayer, int endLayer)
Paints layers within the specified range for the current frame.
Definition: canvaspainter.cpp:414
LayerBitmap
Definition: layerbitmap.h:26
Layer
Definition: layer.h:33
LayerVector
Definition: layervector.h:26
Object
Definition: object.h:42
Tile
Definition: tile.h:24
TiledBuffer
Definition: tiledbuffer.h:50
TiledBuffer::isValid
bool isValid() const
Returns true if there are any tiles, otherwise false.
Definition: tiledbuffer.h:60
VectorImage
Definition: vectorimage.h:32
VectorImage::paintImage
void paintImage(QPainter &painter, const Object &object, bool simplified, bool showThinCurves, bool antialiasing)
VectorImage::paintImage.
Definition: vectorimage.cpp:1191
VectorImage::setSelectionTransformation
void setSelectionTransformation(QTransform transform)
VectorImage::setSelectionTransformation.
Definition: vectorimage.cpp:897
QColor
QPaintDevice
QPaintDevice::devicePixelRatioF
qreal devicePixelRatioF() const const
QPainter
QPainter::CompositionMode_Clear
CompositionMode_Clear
QPainter::begin
bool begin(QPaintDevice *device)
QPainter::drawImage
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags)
QPainter::drawPixmap
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
QPainter::drawRect
void drawRect(const QRectF &rectangle)
QPainter::end
bool end()
QPainter::fillRect
void fillRect(const QRectF &rectangle, const QBrush &brush)
QPainter::opacity
qreal opacity() const const
QPainter::restore
void restore()
QPainter::save
void save()
QPainter::setBrush
void setBrush(const QBrush &brush)
QPainter::setClipRect
void setClipRect(const QRectF &rectangle, Qt::ClipOperation operation)
QPainter::setCompositionMode
void setCompositionMode(QPainter::CompositionMode mode)
QPainter::setOpacity
void setOpacity(qreal opacity)
QPainter::setTransform
void setTransform(const QTransform &transform, bool combine)
QPainter::setWorldMatrixEnabled
void setWorldMatrixEnabled(bool enable)
QPainter::setWorldTransform
void setWorldTransform(const QTransform &matrix, bool combine)
QPainter::viewport
QRect viewport() const const
QPixmap
QPixmap::fill
void fill(const QColor &color)
QPixmap::setDevicePixelRatio
void setDevicePixelRatio(qreal scaleFactor)
QPixmap::size
QSize size() const const
QRect
QRect::height
int height() const const
QRect::isEmpty
bool isEmpty() const const
QRect::size
QSize size() const const
QRect::topLeft
QPoint topLeft() const const
QRect::width
int width() const const
Qt::transparent
transparent
Qt::reset
QTextStream & reset(QTextStream &stream)
QTransform
QTransform::isIdentity
bool isIdentity() const const
QTransform::reset
void reset()
Generated on Mon May 4 2026 07:50:47 for Pencil2D by doxygen 1.9.6 based on revision 3ed50cdd696e72315cedf30508d3572536c3876e