| 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->setClipping(false); | 
| 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: qreal(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 |  |