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}
85
86void CanvasPainter::paintCached(const QRect& blitRect)
87{
88 if (!mPreLayersPixmapCacheValid)
89 {
90 QPainter preLayerPainter;
91 initializePainter(preLayerPainter, mPreLayersPixmap, blitRect);
92 renderPreLayers(preLayerPainter, blitRect);
93 preLayerPainter.end();
94 mPreLayersPixmapCacheValid = true;
95 }
96
97 QPainter mainPainter;
98 initializePainter(mainPainter, mCanvas, blitRect);
99 mainPainter.setWorldMatrixEnabled(false);
100 mainPainter.drawPixmap(mPointZero, mPreLayersPixmap);
101 mainPainter.setWorldMatrixEnabled(true);
102
103 paintCurrentFrame(mainPainter, blitRect, mCurrentLayerIndex, mCurrentLayerIndex);
104
105 if (!mPostLayersPixmapCacheValid)
106 {
107 QPainter postLayerPainter;
108 initializePainter(postLayerPainter, mPostLayersPixmap, blitRect);
109 renderPostLayers(postLayerPainter, blitRect);
110 postLayerPainter.end();
111 mPostLayersPixmapCacheValid = true;
112 }
113
114 mainPainter.setWorldMatrixEnabled(false);
115 mainPainter.drawPixmap(mPointZero, mPostLayersPixmap);
116 mainPainter.setWorldMatrixEnabled(true);
117}
118
119void CanvasPainter::resetLayerCache()
120{
121 mPreLayersPixmapCacheValid = false;
122 mPostLayersPixmapCacheValid = false;
123}
124
125void CanvasPainter::initializePainter(QPainter& painter, QPaintDevice& device, const QRect& blitRect)
126{
127 painter.begin(&device);
128
129 // Only draw inside the clipped rectangle
130 painter.setClipRect(blitRect);
131
132 // Clear the area that's about to be painted again, to avoid painting on top of existing pixels
133 // causing artifacts.
134 painter.setCompositionMode(QPainter::CompositionMode_Clear);
135 painter.fillRect(blitRect, Qt::transparent);
136
137 // Surface has been cleared and is ready to be painted on
138 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
139 painter.setWorldMatrixEnabled(true);
140 painter.setWorldTransform(mViewTransform);
141}
142
143void CanvasPainter::renderPreLayers(QPainter& painter, const QRect& blitRect)
144{
145 if (mOptions.eLayerVisibility != LayerVisibility::CURRENTONLY || mObject->getLayer(mCurrentLayerIndex)->type() == Layer::CAMERA)
146 {
147 paintCurrentFrame(painter, blitRect, 0, mCurrentLayerIndex - 1);
148 }
149
150 paintOnionSkin(painter, blitRect);
151 painter.setOpacity(1.0);
152}
153
154void CanvasPainter::renderPostLayers(QPainter& painter, const QRect& blitRect)
155{
156 if (mOptions.eLayerVisibility != LayerVisibility::CURRENTONLY || mObject->getLayer(mCurrentLayerIndex)->type() == Layer::CAMERA)
157 {
158 paintCurrentFrame(painter, blitRect, mCurrentLayerIndex + 1, mObject->getLayerCount() - 1);
159 }
160}
161
162void CanvasPainter::setPaintSettings(const Object* object, int currentLayer, int frame, TiledBuffer* tiledBuffer)
163{
164 Q_ASSERT(object);
165 mObject = object;
166
167 CANVASPAINTER_LOG("Set CurrentLayerIndex = %d", currentLayer);
168 mCurrentLayerIndex = currentLayer;
169 mFrameNumber = frame;
170 mTiledBuffer = tiledBuffer;
171}
172
173void CanvasPainter::paint(const QRect& blitRect)
174{
175 QPainter preLayerPainter;
176 QPainter mainPainter;
177 QPainter postLayerPainter;
178
179 initializePainter(mainPainter, mCanvas, blitRect);
180
181 initializePainter(preLayerPainter, mPreLayersPixmap, blitRect);
182 renderPreLayers(preLayerPainter, blitRect);
183 preLayerPainter.end();
184
185 mainPainter.setWorldMatrixEnabled(false);
186 mainPainter.drawPixmap(mPointZero, mPreLayersPixmap);
187 mainPainter.setWorldMatrixEnabled(true);
188
189 paintCurrentFrame(mainPainter, blitRect, mCurrentLayerIndex, mCurrentLayerIndex);
190
191 initializePainter(postLayerPainter, mPostLayersPixmap, blitRect);
192 renderPostLayers(postLayerPainter, blitRect);
193 postLayerPainter.end();
194
195 mainPainter.setWorldMatrixEnabled(false);
196 mainPainter.drawPixmap(mPointZero, mPostLayersPixmap);
197 mainPainter.setWorldMatrixEnabled(true);
198
199 mPreLayersPixmapCacheValid = true;
200 mPostLayersPixmapCacheValid = true;
201}
202
203void CanvasPainter::paintOnionSkinOnLayer(QPainter& painter, const QRect& blitRect, Layer* layer)
204{
205 mOnionSkinSubPainter.paint(painter, layer, mOnionSkinPainterOptions, mFrameNumber, [&] (OnionSkinPaintState state, int onionFrameNumber) {
206 if (state == OnionSkinPaintState::PREV) {
207 switch (layer->type())
208 {
209 case Layer::BITMAP: { paintBitmapOnionSkinFrame(painter, blitRect, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizePrevFrames); break; }
210 case Layer::VECTOR: { paintVectorOnionSkinFrame(painter, blitRect, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizePrevFrames); break; }
211 default: break;
212 }
213 }
214 if (state == OnionSkinPaintState::NEXT) {
215 switch (layer->type())
216 {
217 case Layer::BITMAP: { paintBitmapOnionSkinFrame(painter, blitRect, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizeNextFrames); break; }
218 case Layer::VECTOR: { paintVectorOnionSkinFrame(painter, blitRect, layer, onionFrameNumber, mOnionSkinPainterOptions.colorizeNextFrames); break; }
219 default: break;
220 }
221 }
222 });
223}
224
225void CanvasPainter::paintOnionSkin(QPainter& painter, const QRect& blitRect)
226{
227 if (!mOptions.bOnionSkinMultiLayer || mOptions.eLayerVisibility == LayerVisibility::CURRENTONLY) {
228 Layer* layer = mObject->getLayer(mCurrentLayerIndex);
229 paintOnionSkinOnLayer(painter, blitRect, layer);
230 } else {
231 for (int i = 0; i < mObject->getLayerCount(); i++) {
232 Layer* layer = mObject->getLayer(i);
233 if (layer == nullptr) { continue; }
234
235 paintOnionSkinOnLayer(painter, blitRect, layer);
236 }
237 }
238}
239
240void CanvasPainter::paintBitmapOnionSkinFrame(QPainter& painter, const QRect& blitRect, Layer* layer, int nFrame, bool colorize)
241{
242 LayerBitmap* bitmapLayer = static_cast<LayerBitmap*>(layer);
243
244 BitmapImage* bitmapImage = bitmapLayer->getBitmapImageAtFrame(nFrame);
245
246 if (bitmapImage == nullptr) { return; }
247 bitmapImage->loadFile(); // Critical! force the BitmapImage to load the image
248
249 QPainter onionSkinPainter;
250 initializePainter(onionSkinPainter, mOnionSkinPixmap, blitRect);
251
252 onionSkinPainter.drawImage(bitmapImage->topLeft(), *bitmapImage->image());
253 paintOnionSkinFrame(painter, onionSkinPainter, nFrame, colorize, bitmapImage->getOpacity());
254}
255
256void CanvasPainter::paintVectorOnionSkinFrame(QPainter& painter, const QRect& blitRect, Layer* layer, int nFrame, bool colorize)
257{
258 LayerVector* vectorLayer = static_cast<LayerVector*>(layer);
259
260 CANVASPAINTER_LOG("Paint Onion skin vector, Frame = %d", nFrame);
261 VectorImage* vectorImage = vectorLayer->getVectorImageAtFrame(nFrame);
262 if (vectorImage == nullptr) { return; }
263
264 QPainter onionSkinPainter;
265 initializePainter(onionSkinPainter, mOnionSkinPixmap, blitRect);
266
267 vectorImage->paintImage(onionSkinPainter, *mObject, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias);
268 paintOnionSkinFrame(painter, onionSkinPainter, nFrame, colorize, vectorImage->getOpacity());
269}
270
271void CanvasPainter::paintOnionSkinFrame(QPainter& painter, QPainter& onionSkinPainter, int nFrame, bool colorize, qreal frameOpacity)
272{
273 // Don't transform the image here as we used the viewTransform in the image output
274 painter.setWorldMatrixEnabled(false);
275 // Remember to adjust overall opacity based on opacity value from image
276 painter.setOpacity(frameOpacity - (1.0-painter.opacity()));
277 if (colorize)
278 {
279 QColor colorBrush = Qt::transparent; //no color for the current frame
280
281 if (nFrame < mFrameNumber)
282 {
283 colorBrush = Qt::red;
284 }
285 else if (nFrame > mFrameNumber)
286 {
287 colorBrush = Qt::blue;
288 }
289 onionSkinPainter.setWorldMatrixEnabled(false);
290
291 onionSkinPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
292 onionSkinPainter.setBrush(colorBrush);
293 onionSkinPainter.drawRect(painter.viewport());
294 }
295 painter.drawPixmap(mPointZero, mOnionSkinPixmap);
296}
297
298void CanvasPainter::paintCurrentBitmapFrame(QPainter& painter, const QRect& blitRect, Layer* layer, bool isCurrentLayer)
299{
300 LayerBitmap* bitmapLayer = static_cast<LayerBitmap*>(layer);
301 BitmapImage* paintedImage = bitmapLayer->getLastBitmapImageAtFrame(mFrameNumber);
302
303 if (paintedImage == nullptr) { return; }
304 paintedImage->loadFile(); // Critical! force the BitmapImage to load the image
305
306 const bool isDrawing = mTiledBuffer && !mTiledBuffer->bounds().isEmpty();
307
308 QPainter currentBitmapPainter;
309 initializePainter(currentBitmapPainter, mCurrentLayerPixmap, blitRect);
310
311 painter.setOpacity(paintedImage->getOpacity() - (1.0-painter.opacity()));
312 painter.setWorldMatrixEnabled(false);
313
314 currentBitmapPainter.drawImage(paintedImage->topLeft(), *paintedImage->image());
315
316 if (isCurrentLayer && isDrawing)
317 {
318 currentBitmapPainter.setCompositionMode(mOptions.cmBufferBlendMode);
319 const auto tiles = mTiledBuffer->tiles();
320 for (const Tile* tile : tiles) {
321 currentBitmapPainter.drawPixmap(tile->posF(), tile->pixmap());
322 }
323 }
324
325 // We do not wish to draw selection transformations on anything but the current layer
326 Q_ASSERT(!isDrawing || mSelectionTransform.isIdentity());
327 if (isCurrentLayer && mRenderTransform && !isDrawing) {
328 paintTransformedSelection(currentBitmapPainter, paintedImage, mSelection);
329 }
330
331 painter.drawPixmap(mPointZero, mCurrentLayerPixmap);
332}
333
334void CanvasPainter::paintCurrentVectorFrame(QPainter& painter, const QRect& blitRect, Layer* layer, bool isCurrentLayer)
335{
336 LayerVector* vectorLayer = static_cast<LayerVector*>(layer);
337 VectorImage* vectorImage = vectorLayer->getLastVectorImageAtFrame(mFrameNumber, 0);
338 if (vectorImage == nullptr)
339 {
340 return;
341 }
342
343 QPainter currentVectorPainter;
344 initializePainter(currentVectorPainter, mCurrentLayerPixmap, blitRect);
345
346 const bool isDrawing = mTiledBuffer->isValid();
347
348 if (mRenderTransform) {
349 vectorImage->setSelectionTransformation(mSelectionTransform);
350 }
351
352 // Paint existing vector image to the painter
353 vectorImage->paintImage(currentVectorPainter, *mObject, mOptions.bOutlines, mOptions.bThinLines, mOptions.bAntiAlias);
354
355 if (isCurrentLayer && isDrawing) {
356 currentVectorPainter.setCompositionMode(mOptions.cmBufferBlendMode);
357
358 const auto tiles = mTiledBuffer->tiles();
359 for (const Tile* tile : tiles) {
360 currentVectorPainter.drawPixmap(tile->posF(), tile->pixmap());
361 }
362 }
363
364 // Don't transform the image here as we used the viewTransform in the image output
365 painter.setWorldMatrixEnabled(false);
366 painter.setTransform(QTransform());
367
368 // Remember to adjust opacity based on additional opacity value from the keyframe
369 painter.setOpacity(vectorImage->getOpacity() - (1.0-painter.opacity()));
370 painter.drawPixmap(mPointZero, mCurrentLayerPixmap);
371}
372
373void CanvasPainter::paintTransformedSelection(QPainter& painter, BitmapImage* bitmapImage, const QRect& selection) const
374{
375 // Make sure there is something selected
376 if (selection.width() == 0 && selection.height() == 0)
377 return;
378
379 QPixmap transformedPixmap = QPixmap(mSelection.size());
380 transformedPixmap.fill(Qt::transparent);
381
382 QPainter imagePainter(&transformedPixmap);
383 imagePainter.translate(-selection.topLeft());
384 imagePainter.drawImage(bitmapImage->topLeft(), *bitmapImage->image());
385 imagePainter.end();
386
387 painter.save();
388
389 painter.setTransform(mViewTransform);
390
391 // Clear the painted area to make it look like the content has been erased
392 painter.save();
393 painter.setCompositionMode(QPainter::CompositionMode_Clear);
394 painter.fillRect(selection, QColor(255,255,255,255));
395 painter.restore();
396
397 // Multiply the selection and view matrix to get proper rotation and scale values
398 // Now the image origin will be topleft
399 painter.setTransform(mSelectionTransform*mViewTransform);
400
401 // Draw the selection image separately and on top
402 painter.drawPixmap(selection, transformedPixmap);
403 painter.restore();
404}
405
412void CanvasPainter::paintCurrentFrame(QPainter& painter, const QRect& blitRect, int startLayer, int endLayer)
413{
414 painter.setOpacity(1.0);
415
416 bool isCameraLayer = mObject->getLayer(mCurrentLayerIndex)->type() == Layer::CAMERA;
417
418 for (int i = startLayer; i <= endLayer; ++i)
419 {
420 Layer* layer = mObject->getLayer(i);
421
422 if (!layer->visible())
423 continue;
424
425 if (mOptions.eLayerVisibility == LayerVisibility::RELATED && !isCameraLayer)
426 {
427 painter.setOpacity(calculateRelativeOpacityForLayer(mCurrentLayerIndex, i, mOptions.fLayerVisibilityThreshold));
428 }
429 bool isCurrentLayer = mCurrentLayerIndex == i;
430
431 CANVASPAINTER_LOG(" Render Layer[%d] %s", i, layer->name());
432 switch (layer->type())
433 {
434 case Layer::BITMAP: { paintCurrentBitmapFrame(painter, blitRect, layer, isCurrentLayer); break; }
435 case Layer::VECTOR: { paintCurrentVectorFrame(painter, blitRect, layer, isCurrentLayer); break; }
436 default: break;
437 }
438 }
439}
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:125
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:412
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:49
TiledBuffer::isValid
bool isValid() const
Returns true if there are any tiles, otherwise false.
Definition: tiledbuffer.h:59
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
Generated on Thu Jun 5 2025 14:06:43 for Pencil2D by doxygen 1.9.6 based on revision 4c63407997b2c03e5048716586dec6fbbb755173