| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 |
- // This file is part of Desktop App Toolkit,
- // a set of libraries for developing nice desktop applications.
- //
- // For license and copyright information please follow this link:
- // https://github.com/desktop-app/legal/blob/master/LEGAL
- //
- #include "lottie/lottie_icon.h"
- #include "lottie/lottie_common.h"
- #include "ui/image/image_prepare.h"
- #include "ui/style/style_core.h"
- #include <QtGui/QPainter>
- #include <crl/crl_async.h>
- #include <crl/crl_semaphore.h>
- #include <crl/crl_on_main.h>
- #include <rlottie.h>
- #if __has_include(<glib.h>)
- #include <glib.h>
- #endif
- namespace Lottie {
- namespace {
- [[nodiscard]] std::unique_ptr<rlottie::Animation> CreateFromContent(
- const QByteArray &content,
- QColor replacement) {
- auto string = ReadUtf8(Images::UnpackGzip(content));
- #ifndef LOTTIE_USE_PACKAGED_RLOTTIE
- auto list = std::vector<std::pair<std::uint32_t, std::uint32_t>>();
- if (replacement != Qt::white) {
- const auto value = (uint32_t(replacement.red()) << 16)
- | (uint32_t(replacement.green() << 8))
- | (uint32_t(replacement.blue()));
- list.push_back({ 0xFFFFFFU, value });
- }
- auto result = rlottie::Animation::loadFromData(
- std::move(string),
- std::string(),
- std::string(),
- false,
- std::move(list));
- #else
- #if __has_include(<glib.h>)
- [[maybe_unused]] static auto logged = [&] {
- g_warning(
- "rlottie is incompatible, expect animations with color issues.");
- return true;
- }();
- #endif
- auto result = rlottie::Animation::loadFromData(
- std::move(string),
- std::string(),
- std::string(),
- false);
- #endif
- return result;
- }
- [[nodiscard]] QColor RealRenderedColor(QColor color) {
- #ifndef LOTTIE_USE_PACKAGED_RLOTTIE
- return QColor(color.red(), color.green(), color.blue(), 255);
- #else
- return Qt::white;
- #endif
- }
- [[nodiscard]] QByteArray ReadIconContent(
- const QString &name,
- const QByteArray &json,
- const QString &path) {
- return !json.isEmpty()
- ? json
- : !path.isEmpty()
- ? ReadContent(json, path)
- : Images::UnpackGzip(
- ReadContent({}, u":/animations/"_q + name + u".tgs"_q));
- }
- } // namespace
- struct Icon::Frame {
- int index = 0;
- QImage resizedImage;
- QImage renderedImage;
- QImage colorizedImage;
- QColor renderedColor;
- QColor colorizedColor;
- };
- class Icon::Inner final : public std::enable_shared_from_this<Inner> {
- public:
- Inner(int frameIndex, base::weak_ptr<Icon> weak, bool limitFps);
- void prepareFromAsync(
- const QString &name,
- const QString &path,
- const QByteArray &json,
- QSize sizeOverride,
- QColor color);
- void waitTillPrepared() const;
- [[nodiscard]] bool valid() const;
- [[nodiscard]] QSize size() const;
- [[nodiscard]] int framesCount() const;
- [[nodiscard]] Frame &frame();
- [[nodiscard]] const Frame &frame() const;
- [[nodiscard]] crl::time animationDuration(
- int frameFrom,
- int frameTo) const;
- void moveToFrame(int frame, QColor color, QSize updatedDesiredSize);
- private:
- enum class PreloadState {
- None,
- Preloading,
- Ready,
- };
- // Called from crl::async.
- void renderPreloadFrame(const QColor &color);
- const bool _limitFps = false;
- std::unique_ptr<rlottie::Animation> _rlottie;
- Frame _current;
- QSize _desiredSize;
- std::atomic<PreloadState> _preloadState = PreloadState::None;
- Frame _preloaded; // Changed on main or async depending on _preloadState.
- QSize _preloadImageSize;
- base::weak_ptr<Icon> _weak;
- int _framesCount = 0;
- int _frameMultiplier = 1;
- mutable crl::semaphore _semaphore;
- mutable bool _ready = false;
- };
- Icon::Inner::Inner(int frameIndex, base::weak_ptr<Icon> weak, bool limitFps)
- : _limitFps(limitFps)
- , _current { .index = frameIndex }
- , _weak(weak) {
- }
- void Icon::Inner::prepareFromAsync(
- const QString &name,
- const QString &path,
- const QByteArray &json,
- QSize sizeOverride,
- QColor color) {
- const auto guard = gsl::finally([&] { _semaphore.release(); });
- if (!_weak) {
- return;
- }
- auto rlottie = CreateFromContent(
- ReadIconContent(name, json, path),
- color);
- if (!rlottie || !_weak) {
- return;
- }
- auto width = size_t();
- auto height = size_t();
- rlottie->size(width, height);
- if (_limitFps && rlottie->frameRate() == 60) {
- _frameMultiplier = 2;
- }
- _framesCount = (rlottie->totalFrame() + _frameMultiplier - 1)
- / _frameMultiplier;
- if (!_framesCount || !width || !height) {
- return;
- }
- _rlottie = std::move(rlottie);
- while (_current.index < 0) {
- _current.index += _framesCount;
- }
- const auto size = sizeOverride.isEmpty()
- ? style::ConvertScale(QSize{ int(width), int(height) })
- : sizeOverride;
- auto image = CreateFrameStorage(size * style::DevicePixelRatio());
- image.fill(Qt::transparent);
- auto surface = rlottie::Surface(
- reinterpret_cast<uint32_t*>(image.bits()),
- image.width(),
- image.height(),
- image.bytesPerLine());
- _rlottie->renderSync(
- _current.index * _frameMultiplier,
- std::move(surface));
- _current.renderedColor = RealRenderedColor(color);
- _current.renderedImage = std::move(image);
- _desiredSize = size;
- }
- void Icon::Inner::waitTillPrepared() const {
- if (!_ready) {
- _semaphore.acquire();
- _ready = true;
- }
- }
- bool Icon::Inner::valid() const {
- waitTillPrepared();
- return (_rlottie != nullptr);
- }
- QSize Icon::Inner::size() const {
- waitTillPrepared();
- return _desiredSize;
- }
- int Icon::Inner::framesCount() const {
- waitTillPrepared();
- return _framesCount;
- }
- Icon::Frame &Icon::Inner::frame() {
- waitTillPrepared();
- return _current;
- }
- const Icon::Frame &Icon::Inner::frame() const {
- waitTillPrepared();
- return _current;
- }
- crl::time Icon::Inner::animationDuration(int frameFrom, int frameTo) const {
- waitTillPrepared();
- const auto rate = _rlottie
- ? (_rlottie->frameRate() / _frameMultiplier)
- : 0.;
- const auto frames = std::abs(frameTo - frameFrom);
- return (rate >= 1.)
- ? crl::time(base::SafeRound(frames / rate * 1000.))
- : 0;
- }
- void Icon::Inner::moveToFrame(
- int frame,
- QColor color,
- QSize updatedDesiredSize) {
- waitTillPrepared();
- if (frame < 0) {
- frame += _framesCount;
- }
- const auto state = _preloadState.load();
- const auto shown = _current.index;
- if (!updatedDesiredSize.isEmpty()) {
- _desiredSize = updatedDesiredSize;
- }
- const auto desiredImageSize = _desiredSize * style::DevicePixelRatio();
- if (!_rlottie
- || state == PreloadState::Preloading
- || (shown == frame
- && (_current.renderedImage.size() == desiredImageSize))) {
- return;
- } else if (state == PreloadState::Ready) {
- if (_preloaded.index == frame
- && (shown != frame
- || _preloaded.renderedImage.size() == desiredImageSize)) {
- std::swap(_current, _preloaded);
- if (_current.renderedImage.size() == desiredImageSize) {
- return;
- }
- } else if ((shown < _preloaded.index && _preloaded.index < frame)
- || (shown > _preloaded.index && _preloaded.index > frame)) {
- std::swap(_current, _preloaded);
- }
- }
- _preloadImageSize = desiredImageSize;
- _preloaded.index = frame;
- _preloadState = PreloadState::Preloading;
- crl::async([
- guard = shared_from_this(),
- color = RealRenderedColor(color)
- ] {
- guard->renderPreloadFrame(color);
- });
- }
- void Icon::Inner::renderPreloadFrame(const QColor &color) {
- if (!_weak) {
- return;
- }
- auto &image = _preloaded.renderedImage;
- const auto &size = _preloadImageSize;
- if (!GoodStorageForFrame(image, size)) {
- image = GoodStorageForFrame(_preloaded.resizedImage, size)
- ? base::take(_preloaded.resizedImage)
- : CreateFrameStorage(size);
- }
- image.fill(Qt::black);
- auto surface = rlottie::Surface(
- reinterpret_cast<uint32_t*>(image.bits()),
- image.width(),
- image.height(),
- image.bytesPerLine());
- _rlottie->renderSync(
- _preloaded.index * _frameMultiplier,
- std::move(surface));
- _preloaded.renderedColor = color;
- _preloaded.resizedImage = QImage();
- _preloadState = PreloadState::Ready;
- crl::on_main(_weak, [=] {
- _weak->frameJumpFinished();
- });
- }
- Icon::Icon(IconDescriptor &&descriptor)
- : _inner(std::make_shared<Inner>(
- descriptor.frame,
- base::make_weak(this),
- descriptor.limitFps))
- , _color(descriptor.color)
- , _animationFrameTo(descriptor.frame) {
- crl::async([
- inner = _inner,
- name = descriptor.name,
- path = descriptor.path,
- bytes = descriptor.json,
- sizeOverride = descriptor.sizeOverride,
- color = (_color ? (*_color)->c : Qt::white)
- ] {
- inner->prepareFromAsync(name, path, bytes, sizeOverride, color);
- });
- }
- void Icon::wait() const {
- _inner->waitTillPrepared();
- }
- bool Icon::valid() const {
- return _inner->valid();
- }
- int Icon::frameIndex() const {
- preloadNextFrame();
- return _inner->frame().index;
- }
- int Icon::framesCount() const {
- return _inner->framesCount();
- }
- QImage Icon::frame() const {
- return frame(QSize(), nullptr).image;
- }
- Icon::ResizedFrame Icon::frame(
- QSize desiredSize,
- Fn<void()> updateWithPerfect) const {
- preloadNextFrame(desiredSize);
- const auto desired = size() * style::DevicePixelRatio();
- auto &frame = _inner->frame();
- if (frame.renderedImage.isNull()) {
- return { frame.renderedImage };
- } else if (!_color) {
- if (frame.renderedImage.size() == desired) {
- return { frame.renderedImage };
- } else if (frame.resizedImage.size() != desired) {
- frame.resizedImage = frame.renderedImage.scaled(
- desired,
- Qt::IgnoreAspectRatio,
- Qt::SmoothTransformation);
- }
- if (updateWithPerfect) {
- _repaint = std::move(updateWithPerfect);
- }
- return { frame.resizedImage, true };
- }
- Assert(frame.renderedImage.size() == desired);
- const auto color = (*_color)->c;
- if (color == frame.renderedColor) {
- return { frame.renderedImage };
- } else if (!frame.colorizedImage.isNull()
- && color == frame.colorizedColor) {
- return { frame.colorizedImage };
- }
- if (frame.colorizedImage.isNull()) {
- frame.colorizedImage = CreateFrameStorage(desired);
- }
- frame.colorizedColor = color;
- style::colorizeImage(frame.renderedImage, color, &frame.colorizedImage);
- return { frame.colorizedImage };
- }
- int Icon::width() const {
- return size().width();
- }
- int Icon::height() const {
- return size().height();
- }
- QSize Icon::size() const {
- return _inner->size();
- }
- void Icon::paint(
- QPainter &p,
- int x,
- int y,
- std::optional<QColor> colorOverride) {
- preloadNextFrame();
- auto &frame = _inner->frame();
- const auto color = colorOverride.value_or(
- _color ? (*_color)->c : Qt::white);
- if (frame.renderedImage.isNull() || color.alpha() == 0) {
- return;
- }
- const auto rect = QRect{ QPoint(x, y), size() };
- if (color == frame.renderedColor || !_color) {
- p.drawImage(rect, frame.renderedImage);
- } else if (color.alphaF() < 1.
- && (QColor(color.red(), color.green(), color.blue())
- == frame.renderedColor)) {
- const auto o = p.opacity();
- p.setOpacity(o * color.alphaF());
- p.drawImage(rect, frame.renderedImage);
- p.setOpacity(o);
- } else if (!frame.colorizedImage.isNull()
- && color == frame.colorizedColor) {
- p.drawImage(rect, frame.colorizedImage);
- } else if (!frame.colorizedImage.isNull()
- && color.alphaF() < 1.
- && (QColor(color.red(), color.green(), color.blue())
- == frame.colorizedColor)) {
- const auto o = p.opacity();
- p.setOpacity(o * color.alphaF());
- p.drawImage(rect, frame.colorizedImage);
- p.setOpacity(o);
- } else {
- if (frame.colorizedImage.isNull()) {
- frame.colorizedImage = CreateFrameStorage(
- frame.renderedImage.size());
- }
- frame.colorizedColor = color;
- style::colorizeImage(
- frame.renderedImage,
- color,
- &frame.colorizedImage);
- p.drawImage(rect, frame.colorizedImage);
- }
- }
- void Icon::paintInCenter(
- QPainter &p,
- QRect rect,
- std::optional<QColor> colorOverride) {
- const auto my = size();
- paint(
- p,
- rect.x() + (rect.width() - my.width()) / 2,
- rect.y() + (rect.height() - my.height()) / 2,
- colorOverride);
- }
- void Icon::animate(
- Fn<void()> update,
- int frameFrom,
- int frameTo,
- std::optional<crl::time> duration) {
- jumpTo(frameFrom, std::move(update));
- if (frameFrom != frameTo) {
- _animationFrameTo = frameTo;
- _animation.start(
- [=] { preloadNextFrame(); if (_repaint) _repaint(); },
- frameFrom,
- frameTo,
- (duration
- ? *duration
- : _inner->animationDuration(frameFrom, frameTo)));
- }
- }
- void Icon::jumpTo(int frame, Fn<void()> update) {
- _animation.stop();
- _repaint = std::move(update);
- _animationFrameTo = frame;
- preloadNextFrame();
- }
- void Icon::frameJumpFinished() {
- if (_repaint && !animating()) {
- _repaint();
- _repaint = nullptr;
- }
- }
- int Icon::wantedFrameIndex() const {
- return int(base::SafeRound(_animation.value(_animationFrameTo)));
- }
- void Icon::preloadNextFrame(QSize updatedDesiredSize) const {
- _inner->moveToFrame(
- wantedFrameIndex(),
- _color ? (*_color)->c : Qt::white,
- updatedDesiredSize);
- if (_animationFrameTo < 0) {
- _animationFrameTo += framesCount();
- }
- }
- bool Icon::animating() const {
- return _animation.animating();
- }
- std::unique_ptr<Icon> MakeIcon(IconDescriptor &&descriptor) {
- return std::make_unique<Icon>(std::move(descriptor));
- }
- } // namespace Lottie
|