Revision control

++ // Copyright (C) 2018 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include <QtLottie/private/qlottierasterrenderer_p.h>
++
++ #include <QPainter>
++ #include <QRectF>
++ #include <QBrush>
++ #include <QTransform>
++ #include <QGradient>
++ #include <QPointer>
++
++ #include <QtLottie/private/qlottieshape_p.h>
++ #include <QtLottie/private/qlottiefill_p.h>
++ #include <QtLottie/private/qlottiegfill_p.h>
++ #include <QtLottie/private/qlottieimage_p.h>
++ #include <QtLottie/private/qlottiebasictransform_p.h>
++ #include <QtLottie/private/qlottieshapetransform_p.h>
++ #include <QtLottie/private/qlottierect_p.h>
++ #include <QtLottie/private/qlottieellipse_p.h>
++ #include <QtLottie/private/qlottiepolystar_p.h>
++ #include <QtLottie/private/qlottieround_p.h>
++ #include <QtLottie/private/qlottiefreeformshape_p.h>
++ #include <QtLottie/private/qlottietrimpath_p.h>
++ #include <QtLottie/private/qlottiefilleffect_p.h>
++ #include <QtLottie/private/qlottierepeater_p.h>
++ #include <QtLottie/private/qlottieflatlayers_p.h>
++ #include <QtLottie/private/qlottieprecomposition_p.h>
++ #include <QtLottie/private/qlottietextlayer_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ QLottieRasterRenderer::QLottieRasterRenderer(QPainter *painter)
++ : m_painter(painter)
++ {
++ m_painter->setPen(QPen(Qt::NoPen));
++ m_unitedPath.setCachingEnabled(true);
++ }
++
++ void QLottieRasterRenderer::saveState()
++ {
++ qCDebug(lcLottieQtLottieRender) << "Save painter state";
++ m_painter->save();
++ saveTrimmingState();
++ m_pathStack.push_back(m_unitedPath);
++ m_fillEffectStack.push_back(m_fillEffect);
++ m_inverseFillTransformStack.push(m_inverseFillTransform);
++ m_unitedPath.clear();
++ }
++
++ void QLottieRasterRenderer::restoreState()
++ {
++ qCDebug(lcLottieQtLottieRender) << "Restore painter state";
++ m_painter->restore();
++ restoreTrimmingState();
++ m_unitedPath = m_pathStack.pop();
++ m_renderPath.setFillRule(m_unitedPath.fillRule());
++ m_clipPath.setFillRule(m_unitedPath.fillRule());
++ m_fillEffect = m_fillEffectStack.pop();
++ m_inverseFillTransform = m_inverseFillTransformStack.pop();
++ }
++
++ void QLottieRasterRenderer::render(const QLottiePrecomposition &precomp)
++ {
++ Q_UNUSED(precomp);
++ // Nothing to do: no graphical properties at this level
++ }
++
++ void QLottieRasterRenderer::render(const QLottieLayer &layer)
++ {
++ qCDebug(lcLottieQtLottieRender) << "Layer:" << layer.name()
++ << "is matte:" << layer.isMatteLayer()
++ << "using matte:" << layer.isUsingMatteLayer();
++ if (layer.isMatteLayer())
++ m_buildingClipRegion = true;
++ else if (!m_clipPath.isEmpty()) {
++ QTransform inv = m_painter->transform().inverted();
++ if (layer.matteMode() == QLottieLayer::Alpha)
++ m_painter->setClipPath(inv.map(m_clipPath));
++ else if (layer.matteMode() == QLottieLayer::InvertedAlpha) {
++ QPainterPath screen;
++ screen.addRect(0, 0, m_painter->device()->width(),
++ m_painter->device()->height());
++ m_painter->setClipPath(inv.map(screen - m_clipPath));
++ }
++ else {
++ // Clipping is not applied to paths that have
++ // not setting clipping parameters
++ m_painter->setClipping(false);
++ }
++ m_buildingClipRegion = false;
++ m_clipPath = QPainterPath();
++ }
++ }
++
++ void QLottieRasterRenderer::render(const QLottieSolidLayer &layer)
++ {
++ render(static_cast<const QLottieLayer &>(layer));
++ m_painter->fillRect(QRect(QPoint(), layer.layerSize()), layer.color());
++ }
++
++ void QLottieRasterRenderer::render(const QLottieTextLayer &layer)
++ {
++ render(static_cast<const QLottieLayer &>(layer));
++
++ for (const QPainterPath &path : layer.buildGlyphPaths())
++ m_painter->fillPath(path, QBrush(layer.textDocument().fillColor));
++ }
++
++ void QLottieRasterRenderer::render(const QLottieRect &rect)
++ {
++ qCDebug(lcLottieQtLottieRender) << "Rect:" << rect.name()
++ << rect.position() << rect.size();
++ m_renderPath.addPath(rect.path());
++ }
++
++ void QLottieRasterRenderer::render(const QLottieEllipse &ellipse)
++ {
++ qCDebug(lcLottieQtLottieRender) << "Ellipse:" << ellipse.name()
++ << ellipse.position()
++ << ellipse.size();
++ m_renderPath.addPath(ellipse.path());
++ }
++
++ void QLottieRasterRenderer::render(const QLottiePolyStar &star)
++ {
++ qCDebug(lcLottieQtLottieRender) << "PolyStar:" << star.name()
++ << star.position() << star.pointCount()
++ << star.outerRadius() << star.innerRadius();
++ m_renderPath.addPath(star.path());
++ }
++
++ void QLottieRasterRenderer::render(const QLottieImage &image)
++ {
++ const QImage img = image.image();
++ if (img.isNull())
++ return;
++
++ m_painter->save();
++
++ for (int i = 0; i < m_repeatCount; i++) {
++ qCDebug(lcLottieQtLottieRender) << "Image" << image.name();
++
++ applyRepeaterTransform(i);
++
++ m_painter->drawImage(QRectF(QPointF(), image.size()), img);
++ }
++
++ m_painter->restore();
++ }
++
++
++ void QLottieRasterRenderer::render(const QLottieRound &round)
++ {
++ Q_UNUSED(round);
++ // Rounded Corners modifier, not implemented
++ }
++
++ void QLottieRasterRenderer::render(const QLottieFill &fill)
++ {
++ qCDebug(lcLottieQtLottieRender) << "Fill:" <<fill.name()
++ << fill.color();
++
++ if (m_fillEffect)
++ return;
++
++ float alpha = fill.color().alphaF() * fill.opacity() / 100.0f;
++ QColor color = fill.color();
++ color.setAlphaF(alpha);
++ m_painter->setBrush(color);
++ m_renderPath.setFillRule(fill.fillRule());
++ m_unitedPath.setFillRule(fill.fillRule());
++ m_clipPath.setFillRule(fill.fillRule());
++ }
++
++ void QLottieRasterRenderer::render(const QLottieGFill &gradient)
++ {
++ qCDebug(lcLottieQtLottieRender) << "Gradient:" << gradient.name()
++ << gradient.value();
++
++ if (m_fillEffect)
++ return;
++
++ m_inverseFillTransform = QTransform();
++
++ if (gradient.value())
++ m_painter->setBrush(*gradient.value());
++ else
++ qCWarning(lcLottieQtLottieRender) << "Gradient:"
++ << gradient.name()
++ << "Cannot draw gradient fill";
++ m_renderPath.setFillRule(gradient.fillRule());
++ m_unitedPath.setFillRule(gradient.fillRule());
++ m_clipPath.setFillRule(gradient.fillRule());
++ }
++
++ void QLottieRasterRenderer::render(const QLottieStroke &stroke)
++ {
++ qCDebug(lcLottieQtLottieRender) << "Stroke:" << stroke.name()
++ << stroke.pen() << stroke.pen().miterLimit();
++
++ if (m_fillEffect)
++ return;
++
++ m_painter->setPen(stroke.pen());
++ }
++
++ void QLottieRasterRenderer::render(const QLottieBasicTransform &transform)
++ {
++ QTransform t = m_painter->transform();
++ applyTransform(&t, transform);
++ m_painter->setTransform(t);
++ if (transform.hasLinkedLayerTransform())
++ m_painter->setOpacity(1.0);
++ m_painter->setOpacity(m_painter->opacity() * transform.opacity());
++
++ qCDebug(lcLottieQtLottieRender) << transform.name()
++ << m_painter->transform()
++ << "opacity:" << m_painter->opacity();
++ }
++
++ void QLottieRasterRenderer::render(const QLottieShapeTransform &transform)
++ {
++ qCDebug(lcLottieQtLottieRender) << "Shape transform:" << transform.name()
++ << "of" << transform.parent()->name();
++
++ QTransform t = m_painter->transform();
++ applyTransform(&t, transform, true);
++ m_painter->setTransform(t);
++ m_painter->setOpacity(m_painter->opacity() * transform.opacity());
++
++ if (m_painter->brush().gradient())
++ applyTransform(&m_inverseFillTransform, transform, true);
++
++ qCDebug(lcLottieQtLottieRender) << transform.name()
++ << m_painter->transform()
++ << m_painter->opacity();
++ }
++
++ void QLottieRasterRenderer::render(const QLottieFreeFormShape &shape)
++ {
++ qCDebug(lcLottieQtLottieRender) << "Render shape:"
++ << shape.name() << "of"
++ << shape.parent()->name();
++ m_renderPath.addPath(shape.path());
++ }
++
++ void QLottieRasterRenderer::render(const QLottieTrimPath &trimPath)
++ {
++ // TODO: Move "Sequential" trimming to the prerendering thread
++ // Now it is done in the GUI thread
++
++ m_painter->save();
++
++ for (int i = 0; i < m_repeatCount; i ++) {
++ qCDebug(lcLottieQtLottieRender) << "Render shape:"
++ << trimPath.name() << "of"
++ << trimPath.parent()->name();
++ applyRepeaterTransform(i);
++ if (!trimPath.isParallel() && !qFuzzyCompare(qreal(0.0), m_unitedPath.length())) {
++ qCDebug(lcLottieQtLottieRender) << "Render trim path in the GUI thread";
++ QPainterPath tr = trimPath.trim(m_unitedPath);
++ // Do not use the applied transform, as the transform
++ // is already included in m_unitedPath
++ m_painter->setTransform(QTransform());
++ m_painter->drawPath(tr);
++ }
++ }
++
++ m_painter->restore();
++ }
++
++ void QLottieRasterRenderer::render(const QLottieFillEffect &effect)
++ {
++ qCDebug(lcLottieQtLottieRender) << "Fill:" <<effect.name()
++ << effect.color();
++
++ m_fillEffect = &effect;
++ m_painter->setBrush(m_fillEffect->color());
++ m_painter->setOpacity(m_painter->opacity() * m_fillEffect->opacity());
++ }
++
++ void QLottieRasterRenderer::render(const QLottieRepeater &repeater)
++ {
++ qCDebug(lcLottieQtLottieRender) << "Repeater:" <<repeater.name()
++ << "count:" << repeater.copies();
++
++ if (m_repeaterTransform) {
++ qCWarning(lcLottieQtLottieRender) << "Only one Repeater can be active at a time!";
++ return;
++ }
++
++ m_repeatCount = repeater.copies();
++ m_repeatOffset = repeater.offset();
++
++ // Can store pointer to transform, although the transform
++ // is managed by another thread. The object will be available
++ // until the frame has been rendered
++ m_repeaterTransform = &repeater.transform();
++
++ m_painter->translate(m_repeatOffset * m_repeaterTransform->position().x(),
++ m_repeatOffset * m_repeaterTransform->position().y());
++ }
++
++ void QLottieRasterRenderer::renderPathElements(const QList<QLottieBase *> &pathElements)
++ {
++ qCDebug(lcLottieQtLottieRender) << "PathElements: count:" << pathElements.size();
++
++ m_painter->save();
++
++ m_renderPath.clear();
++ for (const QLottieBase *element : pathElements)
++ element->render(*this); // Accumulates element paths in m_renderPath
++
++ for (int i = 0; i < m_repeatCount; i++) {
++ if (i > 0)
++ qCDebug(lcLottieQtLottieRender) << "Render repeated, combined path, iteration" << i + 1;
++ applyRepeaterTransform(i);
++ if (trimmingState() == QLottieRenderer::Sequential) {
++ QTransform t = m_painter->transform();
++ QPainterPath tp = t.map(m_renderPath);
++ tp.addPath(m_unitedPath);
++ m_unitedPath = tp;
++ } else if (m_buildingClipRegion) {
++ QTransform t = m_painter->transform();
++ QPainterPath tp = t.map(m_renderPath);
++ tp.addPath(m_clipPath);
++ m_clipPath = tp;
++ } else {
++ if (!m_inverseFillTransform.isIdentity() && m_painter->brush().gradient()) {
++ bool fill_ok = false;
++ const QTransform fillTransform = m_inverseFillTransform.inverted(&fill_ok);
++ if (fill_ok) {
++ QBrush adjustedBrush = m_painter->brush();
++ adjustedBrush.setTransform(fillTransform);
++ m_painter->save();
++ m_painter->setBrush(adjustedBrush);
++ m_painter->drawPath(m_renderPath);
++ m_painter->restore();
++ } else {
++ m_painter->drawPath(m_renderPath);
++ }
++ } else {
++ m_painter->drawPath(m_renderPath);
++ }
++ }
++ }
++
++ m_painter->restore();
++ }
++
++ void QLottieRasterRenderer::applyRepeaterTransform(int instance)
++ {
++ if (!m_repeaterTransform || instance == 0)
++ return;
++
++ QTransform t = m_painter->transform();
++
++ QPointF anchors = -m_repeaterTransform->anchorPoint();
++ QPointF position = m_repeaterTransform->position();
++ QPointF anchoredCenter = anchors + position;
++
++ t.translate(anchoredCenter.x(), anchoredCenter.y());
++ t.rotate(m_repeaterTransform->rotation());
++ t.scale(m_repeaterTransform->scale().x(),
++ m_repeaterTransform->scale().y());
++ m_painter->setTransform(t);
++
++ qreal o =m_repeaterTransform->opacityAtInstance(instance);
++
++ m_painter->setOpacity(m_painter->opacity() * o);
++ }
++
++ QT_END_NAMESPACE