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 | |
27 | QT_BEGIN_NAMESPACE |
28 | |
29 | LottieRasterRenderer::LottieRasterRenderer(QPainter *painter) |
30 | : m_painter(painter) |
31 | { |
32 | m_painter->setPen(QPen(Qt::NoPen)); |
33 | } |
34 | |
35 | void 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 | |
45 | void 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 | |
54 | void 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 | |
80 | void 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 | |
105 | void 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 | |
132 | void 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 | |
148 | void 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 | |
173 | void 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 | |
187 | void 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 | |
203 | void 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 | |
214 | void 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 | |
240 | void 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 | |
252 | void 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 | |
267 | void 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 | |
293 | void 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 | |
318 | void 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 | |
328 | void 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 | |
350 | void 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 | |
372 | QT_END_NAMESPACE |
373 | |