/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/stickers_lottie.h" #include "lottie/lottie_single_player.h" #include "lottie/lottie_multi_player.h" #include "data/stickers/data_stickers_set.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_session.h" #include "data/data_file_origin.h" #include "storage/cache/storage_cache_database.h" #include "history/view/media/history_view_media_common.h" #include "media/clip/media_clip_reader.h" #include "ui/effects/path_shift_gradient.h" #include "main/main_session.h" namespace ChatHelpers { namespace { constexpr auto kDontCacheLottieAfterArea = 512 * 512; } // namespace template auto LottieCachedFromContent( Method &&method, Storage::Cache::Key baseKey, uint8 keyShift, not_null session, const QByteArray &content, QSize box) { const auto key = Storage::Cache::Key{ baseKey.high, baseKey.low + keyShift }; const auto get = [=](FnMut handler) { session->data().cacheBigFile().get( key, std::move(handler)); }; const auto weak = base::make_weak(session.get()); const auto put = [=](QByteArray &&cached) { crl::on_main(weak, [=, data = std::move(cached)]() mutable { weak->data().cacheBigFile().put(key, std::move(data)); }); }; return method( get, put, content, Lottie::FrameRequest{ box }); } template auto LottieFromDocument( Method &&method, not_null media, uint8 keyShift, QSize box) { const auto document = media->owner(); const auto data = media->bytes(); const auto filepath = document->filepath(); if (box.width() * box.height() > kDontCacheLottieAfterArea) { // Don't use frame caching for large stickers. return method( Lottie::ReadContent(data, filepath), Lottie::FrameRequest{ box }); } if (const auto baseKey = document->bigFileBaseCacheKey()) { return LottieCachedFromContent( std::forward(method), baseKey, keyShift, &document->session(), Lottie::ReadContent(data, filepath), box); } return method( Lottie::ReadContent(data, filepath), Lottie::FrameRequest{ box }); } std::unique_ptr LottiePlayerFromDocument( not_null media, StickerLottieSize sizeTag, QSize box, Lottie::Quality quality, std::shared_ptr renderer) { return LottiePlayerFromDocument( media, nullptr, sizeTag, box, quality, std::move(renderer)); } std::unique_ptr LottiePlayerFromDocument( not_null media, const Lottie::ColorReplacements *replacements, StickerLottieSize sizeTag, QSize box, Lottie::Quality quality, std::shared_ptr renderer) { const auto method = [&](auto &&...args) { return std::make_unique( std::forward(args)..., quality, replacements, std::move(renderer)); }; const auto tag = replacements ? replacements->tag : uint8(0); const auto keyShift = ((tag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F); return LottieFromDocument(method, media, uint8(keyShift), box); } not_null LottieAnimationFromDocument( not_null player, not_null media, StickerLottieSize sizeTag, QSize box) { const auto method = [&](auto &&...args) { return player->append(std::forward(args)...); }; return LottieFromDocument(method, media, uint8(sizeTag), box); } bool HasLottieThumbnail( Data::StickersSetFlags flags, Data::StickersSetThumbnailView *thumb, Data::DocumentMedia *media) { if (thumb) { return !(flags & Data::StickersSetFlag::Webm) && !thumb->content().isEmpty(); } else if (!media) { return false; } const auto document = media->owner(); if (const auto info = document->sticker()) { if (!info->isLottie()) { return false; } media->automaticLoad(document->stickerSetOrigin(), nullptr); if (!media->loaded()) { return false; } return document->bigFileBaseCacheKey().valid(); } return false; } std::unique_ptr LottieThumbnail( Data::StickersSetThumbnailView *thumb, Data::DocumentMedia *media, StickerLottieSize sizeTag, QSize box, std::shared_ptr renderer) { const auto baseKey = thumb ? thumb->owner()->thumbnailBigFileBaseCacheKey() : media ? media->owner()->bigFileBaseCacheKey() : Storage::Cache::Key(); if (!baseKey) { return nullptr; } const auto content = thumb ? thumb->content() : Lottie::ReadContent(media->bytes(), media->owner()->filepath()); if (content.isEmpty()) { return nullptr; } const auto method = [](auto &&...args) { return std::make_unique( std::forward(args)...); }; const auto session = thumb ? &thumb->owner()->session() : media ? &media->owner()->session() : nullptr; return LottieCachedFromContent( method, baseKey, uint8(sizeTag), session, content, box); } bool HasWebmThumbnail( Data::StickersSetFlags flags, Data::StickersSetThumbnailView *thumb, Data::DocumentMedia *media) { if (thumb) { return (flags & Data::StickersSetFlag::Webm) && !thumb->content().isEmpty(); } else if (!media) { return false; } const auto document = media->owner(); if (const auto info = document->sticker()) { if (!info->isWebm()) { return false; } media->automaticLoad(document->stickerSetOrigin(), nullptr); if (!media->loaded()) { return false; } return document->bigFileBaseCacheKey().valid(); } return false; } Media::Clip::ReaderPointer WebmThumbnail( Data::StickersSetThumbnailView *thumb, Data::DocumentMedia *media, Fn callback) { return thumb ? ::Media::Clip::MakeReader( thumb->content(), std::move(callback)) : ::Media::Clip::MakeReader( media->owner()->location(), media->bytes(), std::move(callback)); } bool PaintStickerThumbnailPath( QPainter &p, not_null media, QRect target, QLinearGradient *gradient) { const auto &path = media->thumbnailPath(); const auto dimensions = media->owner()->dimensions; if (path.isEmpty() || dimensions.isEmpty() || target.isEmpty()) { return false; } p.save(); auto hq = PainterHighQualityEnabler(p); p.setPen(Qt::NoPen); p.translate(target.topLeft()); if (gradient) { const auto scale = dimensions.width() / float64(target.width()); const auto shift = p.worldTransform().dx(); gradient->setStart((gradient->start().x() - shift) * scale, 0); gradient->setFinalStop( (gradient->finalStop().x() - shift) * scale, 0); p.setBrush(*gradient); } p.scale( target.width() / float64(dimensions.width()), target.height() / float64(dimensions.height())); p.drawPath(path); p.restore(); return true; } bool PaintStickerThumbnailPath( QPainter &p, not_null media, QRect target, not_null gradient) { return gradient->paint([&](const Ui::PathShiftGradient::Background &bg) { if (const auto color = std::get_if(&bg)) { p.setBrush(*color); return PaintStickerThumbnailPath(p, media, target); } const auto gradient = v::get(bg); return PaintStickerThumbnailPath(p, media, target, gradient); }); } QSize ComputeStickerSize(not_null document, QSize box) { const auto sticker = document->sticker(); const auto dimensions = document->dimensions; if (!sticker || !sticker->isLottie() || dimensions.isEmpty()) { return HistoryView::DownscaledSize(dimensions, box); } const auto ratio = style::DevicePixelRatio(); const auto request = Lottie::FrameRequest{ box * ratio }; return HistoryView::NonEmptySize(request.size(dimensions, true) / ratio); } } // namespace ChatHelpers