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

source code of qtlottie/src/lottie/renderer/qlottierasterrenderer.cpp