1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "lottierasterrenderer.h"
5
6#include <QPainter>
7#include <QRectF>
8#include <QBrush>
9#include <QTransform>
10#include <QGradient>
11#include <QPointer>
12
13#include <QtBodymovin/private/bmshape_p.h>
14#include <QtBodymovin/private/bmfill_p.h>
15#include <QtBodymovin/private/bmgfill_p.h>
16#include <QtBodymovin/private/bmimage_p.h>
17#include <QtBodymovin/private/bmbasictransform_p.h>
18#include <QtBodymovin/private/bmshapetransform_p.h>
19#include <QtBodymovin/private/bmrect_p.h>
20#include <QtBodymovin/private/bmellipse_p.h>
21#include <QtBodymovin/private/bmround_p.h>
22#include <QtBodymovin/private/bmfreeformshape_p.h>
23#include <QtBodymovin/private/bmtrimpath_p.h>
24#include <QtBodymovin/private/bmfilleffect_p.h>
25#include <QtBodymovin/private/bmrepeater_p.h>
26
27QT_BEGIN_NAMESPACE
28
29LottieRasterRenderer::LottieRasterRenderer(QPainter *painter)
30 : m_painter(painter)
31{
32 m_painter->setPen(QPen(Qt::NoPen));
33}
34
35void LottieRasterRenderer::saveState()
36{
37 qCDebug(lcLottieQtBodymovinRender) << "Save painter state";
38 m_painter->save();
39 saveTrimmingState();
40 m_pathStack.push_back(t: m_unitedPath);
41 m_fillEffectStack.push_back(t: m_fillEffect);
42 m_unitedPath = QPainterPath();
43}
44
45void LottieRasterRenderer::restoreState()
46{
47 qCDebug(lcLottieQtBodymovinRender) << "Restore painter state";
48 m_painter->restore();
49 restoreTrimmingState();
50 m_unitedPath = m_pathStack.pop();
51 m_fillEffect = m_fillEffectStack.pop();
52}
53
54void LottieRasterRenderer::render(const BMLayer &layer)
55{
56 qCDebug(lcLottieQtBodymovinRender) << "Layer:" << layer.name()
57 << "clip layer" << layer.isClippedLayer();
58
59 if (layer.isMaskLayer())
60 m_buildingClipRegion = true;
61 else if (!m_clipPath.isEmpty()) {
62 if (layer.clipMode() == BMLayer::Alpha)
63 m_painter->setClipPath(path: m_clipPath);
64 else if (layer.clipMode() == BMLayer::InvertedAlpha) {
65 QPainterPath screen;
66 screen.addRect(x: 0, y: 0, w: m_painter->device()->width(),
67 h: m_painter->device()->height());
68 m_painter->setClipPath(path: screen - m_clipPath);
69 }
70 else {
71 // Clipping is not applied to paths that have
72 // not setting clipping parameters
73 m_painter->setClipPath(path: QPainterPath());
74 }
75 m_buildingClipRegion = false;
76 m_clipPath = QPainterPath();
77 }
78}
79
80void LottieRasterRenderer::render(const BMRect &rect)
81{
82 m_painter->save();
83
84 for (int i = 0; i < m_repeatCount; i++) {
85 qCDebug(lcLottieQtBodymovinRender) << rect.name()
86 << rect.position() << rect.size();
87 applyRepeaterTransform(instance: i);
88 if (trimmingState() == LottieRenderer::Individual) {
89 QTransform t = m_painter->transform();
90 QPainterPath tp = t.map(p: rect.path());
91 tp.addPath(path: m_unitedPath);
92 m_unitedPath = tp;
93 } else if (m_buildingClipRegion) {
94 QTransform t = m_painter->transform();
95 QPainterPath tp = t.map(p: rect.path());
96 tp.addPath(path: m_clipPath);
97 m_clipPath = tp;
98 } else
99 m_painter->drawPath(path: rect.path());
100 }
101
102 m_painter->restore();
103}
104
105void LottieRasterRenderer::render(const BMEllipse &ellipse)
106{
107 m_painter->save();
108
109 for (int i = 0; i < m_repeatCount; i++) {
110 qCDebug(lcLottieQtBodymovinRender) << "Ellipse:" << ellipse.name()
111 << ellipse.position()
112 << ellipse.size();
113
114 applyRepeaterTransform(instance: i);
115 if (trimmingState() == LottieRenderer::Individual) {
116 QTransform t = m_painter->transform();
117 QPainterPath tp = t.map(p: ellipse.path());
118 tp.addPath(path: m_unitedPath);
119 m_unitedPath = tp;
120 } else if (m_buildingClipRegion) {
121 QTransform t = m_painter->transform();
122 QPainterPath tp = t.map(p: ellipse.path());
123 tp.addPath(path: m_clipPath);
124 m_clipPath = tp;
125 } else
126 m_painter->drawPath(path: ellipse.path());
127 }
128
129 m_painter->restore();
130}
131
132void LottieRasterRenderer::render(const BMImage &image)
133{
134 m_painter->save();
135
136 for (int i = 0; i < m_repeatCount; i++) {
137 qCDebug(lcLottieQtBodymovinRender) << "Image" << image.name();
138
139 applyRepeaterTransform(instance: i);
140 QPointF center = image.getCenter();
141 m_painter->drawImage(x: center.x(), y: center.y(), image: image.getImage());
142 }
143
144 m_painter->restore();
145}
146
147
148void LottieRasterRenderer::render(const BMRound &round)
149{
150 m_painter->save();
151
152 for (int i = 0; i < m_repeatCount; i++) {
153 qCDebug(lcLottieQtBodymovinRender) << "Round:" << round.name()
154 << round.position() << round.radius();
155
156 if (trimmingState() == LottieRenderer::Individual) {
157 QTransform t = m_painter->transform();
158 QPainterPath tp = t.map(p: round.path());
159 tp.addPath(path: m_unitedPath);
160 m_unitedPath = tp;
161 } else if (m_buildingClipRegion) {
162 QTransform t = m_painter->transform();
163 QPainterPath tp = t.map(p: round.path());
164 tp.addPath(path: m_clipPath);
165 m_clipPath = tp;
166 } else
167 m_painter->drawPath(path: round.path());
168 }
169
170 m_painter->restore();
171}
172
173void LottieRasterRenderer::render(const BMFill &fill)
174{
175 qCDebug(lcLottieQtBodymovinRender) << "Fill:" <<fill.name()
176 << fill.color();
177
178 if (m_fillEffect)
179 return;
180
181 float alpha = fill.color().alphaF() * fill.opacity() / 100.0f;
182 QColor color = fill.color();
183 color.setAlphaF(alpha);
184 m_painter->setBrush(color);
185}
186
187void LottieRasterRenderer::render(const BMGFill &gradient)
188{
189 qCDebug(lcLottieQtBodymovinRender) << "Gradient:" << gradient.name()
190 << gradient.value();
191
192 if (m_fillEffect)
193 return;
194
195 if (gradient.value())
196 m_painter->setBrush(*gradient.value());
197 else
198 qCWarning(lcLottieQtBodymovinRender) << "Gradient:"
199 << gradient.name()
200 << "Cannot draw gradient fill";
201}
202
203void LottieRasterRenderer::render(const BMStroke &stroke)
204{
205 qCDebug(lcLottieQtBodymovinRender) << "Stroke:" << stroke.name()
206 << stroke.pen() << stroke.pen().miterLimit();
207
208 if (m_fillEffect)
209 return;
210
211 m_painter->setPen(stroke.pen());
212}
213
214void applyBMTransform(QTransform *xf, const BMBasicTransform &bmxf, bool isBMShapeTransform = false)
215{
216 QPointF pos = bmxf.position();
217 qreal rot = bmxf.rotation();
218 QPointF sca = bmxf.scale();
219 QPointF anc = bmxf.anchorPoint();
220
221 xf->translate(dx: pos.x(), dy: pos.y());
222
223 if (!qFuzzyIsNull(d: rot))
224 xf->rotate(a: rot);
225
226 if (isBMShapeTransform) {
227 const BMShapeTransform &shxf = static_cast<const BMShapeTransform &>(bmxf);
228 if (!qFuzzyIsNull(d: shxf.skew())) {
229 QTransform t(shxf.shearX(), shxf.shearY(), 0, -shxf.shearY(), shxf.shearX(), 0, 0, 0, 1);
230 t *= QTransform(1, 0, 0, shxf.shearAngle(), 1, 0, 0, 0, 1);
231 t *= QTransform(shxf.shearX(), -shxf.shearY(), 0, shxf.shearY(), shxf.shearX(), 0, 0, 0, 1);
232 *xf = t * (*xf);
233 }
234 }
235
236 xf->scale(sx: sca.x(), sy: sca.y());
237 xf->translate(dx: -anc.x(), dy: -anc.y());
238}
239
240void LottieRasterRenderer::render(const BMBasicTransform &transform)
241{
242 QTransform t = m_painter->transform();
243 applyBMTransform(xf: &t, bmxf: transform);
244 m_painter->setTransform(transform: t);
245 m_painter->setOpacity(m_painter->opacity() * transform.opacity());
246
247 qCDebug(lcLottieQtBodymovinRender) << transform.name()
248 << m_painter->transform()
249 << "opacity:" << m_painter->opacity();
250}
251
252void LottieRasterRenderer::render(const BMShapeTransform &transform)
253{
254 qCDebug(lcLottieQtBodymovinRender) << "Shape transform:" << transform.name()
255 << "of" << transform.parent()->name();
256
257 QTransform t = m_painter->transform();
258 applyBMTransform(xf: &t, bmxf: transform, isBMShapeTransform: true);
259 m_painter->setTransform(transform: t);
260 m_painter->setOpacity(m_painter->opacity() * transform.opacity());
261
262 qCDebug(lcLottieQtBodymovinRender) << transform.name()
263 << m_painter->transform()
264 << m_painter->opacity();
265}
266
267void LottieRasterRenderer::render(const BMFreeFormShape &shape)
268{
269 m_painter->save();
270
271 for (int i = 0; i < m_repeatCount; i ++) {
272 qCDebug(lcLottieQtBodymovinRender) << "Render shape:"
273 << shape.name() << "of"
274 << shape.parent()->name();
275 applyRepeaterTransform(instance: i);
276 if (trimmingState() == LottieRenderer::Individual) {
277 QTransform t = m_painter->transform();
278 QPainterPath tp = t.map(p: shape.path());
279 tp.addPath(path: m_unitedPath);
280 m_unitedPath = tp;
281 } else if (m_buildingClipRegion) {
282 QTransform t = m_painter->transform();
283 QPainterPath tp = t.map(p: shape.path());
284 tp.addPath(path: m_clipPath);
285 m_clipPath = tp;
286 } else
287 m_painter->drawPath(path: shape.path());
288 }
289
290 m_painter->restore();
291}
292
293void LottieRasterRenderer::render(const BMTrimPath &trimPath)
294{
295 // TODO: Remove "Individual" trimming to the prerendering thread
296 // Now it is done in the GUI thread
297
298 m_painter->save();
299
300 for (int i = 0; i < m_repeatCount; i ++) {
301 qCDebug(lcLottieQtBodymovinRender) << "Render shape:"
302 << trimPath.name() << "of"
303 << trimPath.parent()->name();
304 applyRepeaterTransform(instance: i);
305 if (!trimPath.simultaneous() && !qFuzzyCompare(p1: 0.0, p2: m_unitedPath.length())) {
306 qCDebug(lcLottieQtBodymovinRender) << "Render trim path in the GUI thread";
307 QPainterPath tr = trimPath.trim(path: m_unitedPath);
308 // Do not use the applied transform, as the transform
309 // is already included in m_unitedPath
310 m_painter->setTransform(transform: QTransform());
311 m_painter->drawPath(path: tr);
312 }
313 }
314
315 m_painter->restore();
316}
317
318void LottieRasterRenderer::render(const BMFillEffect &effect)
319{
320 qCDebug(lcLottieQtBodymovinRender) << "Fill:" <<effect.name()
321 << effect.color();
322
323 m_fillEffect = &effect;
324 m_painter->setBrush(m_fillEffect->color());
325 m_painter->setOpacity(m_painter->opacity() * m_fillEffect->opacity());
326}
327
328void LottieRasterRenderer::render(const BMRepeater &repeater)
329{
330 qCDebug(lcLottieQtBodymovinRender) << "Repeater:" <<repeater.name()
331 << "count:" << repeater.copies();
332
333 if (m_repeaterTransform) {
334 qCWarning(lcLottieQtBodymovinRender) << "Only one Repeater can be active at a time!";
335 return;
336 }
337
338 m_repeatCount = repeater.copies();
339 m_repeatOffset = repeater.offset();
340
341 // Can store pointer to transform, although the transform
342 // is managed by another thread. The object will be available
343 // until the frame has been rendered
344 m_repeaterTransform = &repeater.transform();
345
346 m_painter->translate(dx: m_repeatOffset * m_repeaterTransform->position().x(),
347 dy: m_repeatOffset * m_repeaterTransform->position().y());
348}
349
350void LottieRasterRenderer::applyRepeaterTransform(int instance)
351{
352 if (!m_repeaterTransform || instance == 0)
353 return;
354
355 QTransform t = m_painter->transform();
356
357 QPointF anchors = -m_repeaterTransform->anchorPoint();
358 QPointF position = m_repeaterTransform->position();
359 QPointF anchoredCenter = anchors + position;
360
361 t.translate(dx: anchoredCenter.x(), dy: anchoredCenter.y());
362 t.rotate(a: m_repeaterTransform->rotation());
363 t.scale(sx: m_repeaterTransform->scale().x(),
364 sy: m_repeaterTransform->scale().y());
365 m_painter->setTransform(transform: t);
366
367 qreal o =m_repeaterTransform->opacityAtInstance(instance);
368
369 m_painter->setOpacity(m_painter->opacity() * o);
370}
371
372QT_END_NAMESPACE
373

source code of qtlottie/src/imports/rasterrenderer/lottierasterrenderer.cpp