| /**************************************************************************** |
| ** |
| ** Copyright (C) 2018 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the lottie-qt module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 or (at your option) any later version |
| ** approved by the KDE Free Qt Foundation. The licenses are as published by |
| ** the Free Software Foundation and appearing in the file LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "lottierasterrenderer.h" |
| |
| #include <QPainter> |
| #include <QRectF> |
| #include <QBrush> |
| #include <QTransform> |
| #include <QGradient> |
| #include <QPointer> |
| |
| #include <QtBodymovin/private/bmshape_p.h> |
| #include <QtBodymovin/private/bmfill_p.h> |
| #include <QtBodymovin/private/bmgfill_p.h> |
| #include <QtBodymovin/private/bmbasictransform_p.h> |
| #include <QtBodymovin/private/bmshapetransform_p.h> |
| #include <QtBodymovin/private/bmrect_p.h> |
| #include <QtBodymovin/private/bmellipse_p.h> |
| #include <QtBodymovin/private/bmround_p.h> |
| #include <QtBodymovin/private/bmfreeformshape_p.h> |
| #include <QtBodymovin/private/bmtrimpath_p.h> |
| #include <QtBodymovin/private/bmfilleffect_p.h> |
| #include <QtBodymovin/private/bmrepeater_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| LottieRasterRenderer::LottieRasterRenderer(QPainter *painter) |
| : m_painter(painter) |
| { |
| m_painter->setPen(QPen(Qt::NoPen)); |
| } |
| |
| void LottieRasterRenderer::saveState() |
| { |
| qCDebug(lcLottieQtBodymovinRender) << "Save painter state"; |
| m_painter->save(); |
| saveTrimmingState(); |
| m_pathStack.push_back(m_unitedPath); |
| m_fillEffectStack.push_back(m_fillEffect); |
| m_unitedPath = QPainterPath(); |
| } |
| |
| void LottieRasterRenderer::restoreState() |
| { |
| qCDebug(lcLottieQtBodymovinRender) << "Restore painter state"; |
| m_painter->restore(); |
| restoreTrimmingState(); |
| m_unitedPath = m_pathStack.pop(); |
| m_fillEffect = m_fillEffectStack.pop(); |
| } |
| |
| void LottieRasterRenderer::render(const BMLayer &layer) |
| { |
| qCDebug(lcLottieQtBodymovinRender) << "Layer:" << layer.name() |
| << "clip layer" << layer.isClippedLayer(); |
| |
| if (layer.isMaskLayer()) |
| m_buildingClipRegion = true; |
| else if (!m_clipPath.isEmpty()) { |
| if (layer.clipMode() == BMLayer::Alpha) |
| m_painter->setClipPath(m_clipPath); |
| else if (layer.clipMode() == BMLayer::InvertedAlpha) { |
| QPainterPath screen; |
| screen.addRect(0, 0, m_painter->device()->width(), |
| m_painter->device()->height()); |
| m_painter->setClipPath(screen - m_clipPath); |
| } |
| else { |
| // Clipping is not applied to paths that have |
| // not setting clipping parameters |
| m_painter->setClipPath(QPainterPath()); |
| } |
| m_buildingClipRegion = false; |
| m_clipPath = QPainterPath(); |
| } |
| } |
| |
| void LottieRasterRenderer::render(const BMRect &rect) |
| { |
| m_painter->save(); |
| |
| for (int i = 0; i < m_repeatCount; i++) { |
| qCDebug(lcLottieQtBodymovinRender) << rect.name() |
| << rect.position() << rect.size(); |
| applyRepeaterTransform(i); |
| if (trimmingState() == LottieRenderer::Individual) { |
| QTransform t = m_painter->transform(); |
| QPainterPath tp = t.map(rect.path()); |
| tp.addPath(m_unitedPath); |
| m_unitedPath = tp; |
| } else if (m_buildingClipRegion) { |
| QTransform t = m_painter->transform(); |
| QPainterPath tp = t.map(rect.path()); |
| tp.addPath(m_clipPath); |
| m_clipPath = tp; |
| } else |
| m_painter->drawPath(rect.path()); |
| } |
| |
| m_painter->restore(); |
| } |
| |
| void LottieRasterRenderer::render(const BMEllipse &ellipse) |
| { |
| m_painter->save(); |
| |
| for (int i = 0; i < m_repeatCount; i++) { |
| qCDebug(lcLottieQtBodymovinRender) << "Ellipse:" << ellipse.name() |
| << ellipse.position() |
| << ellipse.size(); |
| |
| applyRepeaterTransform(i); |
| if (trimmingState() == LottieRenderer::Individual) { |
| QTransform t = m_painter->transform(); |
| QPainterPath tp = t.map(ellipse.path()); |
| tp.addPath(m_unitedPath); |
| m_unitedPath = tp; |
| } else if (m_buildingClipRegion) { |
| QTransform t = m_painter->transform(); |
| QPainterPath tp = t.map(ellipse.path()); |
| tp.addPath(m_clipPath); |
| m_clipPath = tp; |
| } else |
| m_painter->drawPath(ellipse.path()); |
| } |
| |
| m_painter->restore(); |
| } |
| |
| void LottieRasterRenderer::render(const BMRound &round) |
| { |
| m_painter->save(); |
| |
| for (int i = 0; i < m_repeatCount; i++) { |
| qCDebug(lcLottieQtBodymovinRender) << "Round:" << round.name() |
| << round.position() << round.radius(); |
| |
| if (trimmingState() == LottieRenderer::Individual) { |
| QTransform t = m_painter->transform(); |
| QPainterPath tp = t.map(round.path()); |
| tp.addPath(m_unitedPath); |
| m_unitedPath = tp; |
| } else if (m_buildingClipRegion) { |
| QTransform t = m_painter->transform(); |
| QPainterPath tp = t.map(round.path()); |
| tp.addPath(m_clipPath); |
| m_clipPath = tp; |
| } else |
| m_painter->drawPath(round.path()); |
| } |
| |
| m_painter->restore(); |
| } |
| |
| void LottieRasterRenderer::render(const BMFill &fill) |
| { |
| qCDebug(lcLottieQtBodymovinRender) << "Fill:" <<fill.name() |
| << fill.color(); |
| |
| if (m_fillEffect) |
| return; |
| |
| m_painter->setBrush(fill.color()); |
| } |
| |
| void LottieRasterRenderer::render(const BMGFill &gradient) |
| { |
| qCDebug(lcLottieQtBodymovinRender) << "Gradient:" << gradient.name() |
| << gradient.value(); |
| |
| if (m_fillEffect) |
| return; |
| |
| if (gradient.value()) |
| m_painter->setBrush(*gradient.value()); |
| else |
| qCWarning(lcLottieQtBodymovinRender) << "Gradient:" |
| << gradient.name() |
| << "Cannot draw gradient fill"; |
| } |
| |
| void LottieRasterRenderer::render(const BMStroke &stroke) |
| { |
| qCDebug(lcLottieQtBodymovinRender) << "Stroke:" << stroke.name() |
| << stroke.pen() << stroke.pen().miterLimit(); |
| |
| if (m_fillEffect) |
| return; |
| |
| m_painter->setPen(stroke.pen()); |
| } |
| |
| void applyBMTransform(QTransform *xf, const BMBasicTransform &bmxf, bool isBMShapeTransform = false) |
| { |
| QPointF pos = bmxf.position(); |
| qreal rot = bmxf.rotation(); |
| QPointF sca = bmxf.scale(); |
| QPointF anc = bmxf.anchorPoint(); |
| |
| xf->translate(pos.x(), pos.y()); |
| |
| if (!qFuzzyIsNull(rot)) |
| xf->rotate(rot); |
| |
| if (isBMShapeTransform) { |
| const BMShapeTransform &shxf = static_cast<const BMShapeTransform &>(bmxf); |
| if (!qFuzzyIsNull(shxf.skew())) { |
| QTransform t(shxf.shearX(), shxf.shearY(), 0, -shxf.shearY(), shxf.shearX(), 0, 0, 0, 1); |
| t *= QTransform(1, 0, 0, shxf.shearAngle(), 1, 0, 0, 0, 1); |
| t *= QTransform(shxf.shearX(), -shxf.shearY(), 0, shxf.shearY(), shxf.shearX(), 0, 0, 0, 1); |
| *xf = t * (*xf); |
| } |
| } |
| |
| xf->scale(sca.x(), sca.y()); |
| xf->translate(-anc.x(), -anc.y()); |
| } |
| |
| void LottieRasterRenderer::render(const BMBasicTransform &transform) |
| { |
| QTransform t = m_painter->transform(); |
| applyBMTransform(&t, transform); |
| m_painter->setTransform(t); |
| m_painter->setOpacity(m_painter->opacity() * transform.opacity()); |
| |
| qCDebug(lcLottieQtBodymovinRender) << transform.name() |
| << m_painter->transform() |
| << "opacity:" << m_painter->opacity(); |
| } |
| |
| void LottieRasterRenderer::render(const BMShapeTransform &transform) |
| { |
| qCDebug(lcLottieQtBodymovinRender) << "Shape transform:" << transform.name() |
| << "of" << transform.parent()->name(); |
| |
| QTransform t = m_painter->transform(); |
| applyBMTransform(&t, transform, true); |
| m_painter->setTransform(t); |
| m_painter->setOpacity(m_painter->opacity() * transform.opacity()); |
| |
| qCDebug(lcLottieQtBodymovinRender) << transform.name() |
| << m_painter->transform() |
| << m_painter->opacity(); |
| } |
| |
| void LottieRasterRenderer::render(const BMFreeFormShape &shape) |
| { |
| m_painter->save(); |
| |
| for (int i = 0; i < m_repeatCount; i ++) { |
| qCDebug(lcLottieQtBodymovinRender) << "Render shape:" |
| << shape.name() << "of" |
| << shape.parent()->name(); |
| applyRepeaterTransform(i); |
| if (trimmingState() == LottieRenderer::Individual) { |
| QTransform t = m_painter->transform(); |
| QPainterPath tp = t.map(shape.path()); |
| tp.addPath(m_unitedPath); |
| m_unitedPath = tp; |
| } else if (m_buildingClipRegion) { |
| QTransform t = m_painter->transform(); |
| QPainterPath tp = t.map(shape.path()); |
| tp.addPath(m_clipPath); |
| m_clipPath = tp; |
| } else |
| m_painter->drawPath(shape.path()); |
| } |
| |
| m_painter->restore(); |
| } |
| |
| void LottieRasterRenderer::render(const BMTrimPath &trimPath) |
| { |
| // TODO: Remove "Individual" 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(lcLottieQtBodymovinRender) << "Render shape:" |
| << trimPath.name() << "of" |
| << trimPath.parent()->name(); |
| applyRepeaterTransform(i); |
| if (!trimPath.simultaneous() && !qFuzzyCompare(0.0, m_unitedPath.length())) { |
| qCDebug(lcLottieQtBodymovinRender) << "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 LottieRasterRenderer::render(const BMFillEffect &effect) |
| { |
| qCDebug(lcLottieQtBodymovinRender) << "Fill:" <<effect.name() |
| << effect.color(); |
| |
| m_fillEffect = &effect; |
| m_painter->setBrush(m_fillEffect->color()); |
| m_painter->setOpacity(m_painter->opacity() * m_fillEffect->opacity()); |
| } |
| |
| void LottieRasterRenderer::render(const BMRepeater &repeater) |
| { |
| qCDebug(lcLottieQtBodymovinRender) << "Repeater:" <<repeater.name() |
| << "count:" << repeater.copies(); |
| |
| if (m_repeaterTransform) { |
| qCWarning(lcLottieQtBodymovinRender) << "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 LottieRasterRenderer::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 |