2020-06-08 17:24:36 +00:00
|
|
|
/*
|
|
|
|
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"
|
2022-01-24 21:40:26 +00:00
|
|
|
#include "history/view/media/history_view_media_common.h"
|
2022-01-24 12:33:31 +00:00
|
|
|
#include "media/clip/media_clip_reader.h"
|
2021-07-02 15:29:13 +00:00
|
|
|
#include "ui/effects/path_shift_gradient.h"
|
2022-09-16 20:23:27 +00:00
|
|
|
#include "ui/painter.h"
|
2020-06-08 17:24:36 +00:00
|
|
|
#include "main/main_session.h"
|
|
|
|
|
|
|
|
namespace ChatHelpers {
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
constexpr auto kDontCacheLottieAfterArea = 512 * 512;
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2022-06-28 13:13:20 +00:00
|
|
|
uint8 LottieCacheKeyShift(uint8 replacementsTag, StickerLottieSize sizeTag) {
|
|
|
|
return ((replacementsTag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F);
|
|
|
|
}
|
|
|
|
|
2020-06-08 17:24:36 +00:00
|
|
|
template <typename Method>
|
|
|
|
auto LottieCachedFromContent(
|
|
|
|
Method &&method,
|
|
|
|
Storage::Cache::Key baseKey,
|
|
|
|
uint8 keyShift,
|
|
|
|
not_null<Main::Session*> session,
|
|
|
|
const QByteArray &content,
|
|
|
|
QSize box) {
|
|
|
|
const auto key = Storage::Cache::Key{
|
|
|
|
baseKey.high,
|
|
|
|
baseKey.low + keyShift
|
|
|
|
};
|
|
|
|
const auto get = [=](FnMut<void(QByteArray &&cached)> 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 <typename Method>
|
|
|
|
auto LottieFromDocument(
|
|
|
|
Method &&method,
|
|
|
|
not_null<Data::DocumentMedia*> media,
|
|
|
|
uint8 keyShift,
|
2021-09-17 16:23:52 +00:00
|
|
|
QSize box) {
|
2020-06-08 17:24:36 +00:00
|
|
|
const auto document = media->owner();
|
|
|
|
const auto data = media->bytes();
|
|
|
|
const auto filepath = document->filepath();
|
2021-09-17 16:23:52 +00:00
|
|
|
if (box.width() * box.height() > kDontCacheLottieAfterArea) {
|
2020-06-08 17:24:36 +00:00
|
|
|
// 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>(method),
|
|
|
|
baseKey,
|
|
|
|
keyShift,
|
|
|
|
&document->session(),
|
|
|
|
Lottie::ReadContent(data, filepath),
|
|
|
|
box);
|
|
|
|
}
|
|
|
|
return method(
|
|
|
|
Lottie::ReadContent(data, filepath),
|
|
|
|
Lottie::FrameRequest{ box });
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
|
|
|
not_null<Data::DocumentMedia*> media,
|
|
|
|
StickerLottieSize sizeTag,
|
|
|
|
QSize box,
|
|
|
|
Lottie::Quality quality,
|
|
|
|
std::shared_ptr<Lottie::FrameRenderer> renderer) {
|
|
|
|
return LottiePlayerFromDocument(
|
|
|
|
media,
|
|
|
|
nullptr,
|
|
|
|
sizeTag,
|
|
|
|
box,
|
|
|
|
quality,
|
|
|
|
std::move(renderer));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
|
|
|
not_null<Data::DocumentMedia*> media,
|
|
|
|
const Lottie::ColorReplacements *replacements,
|
|
|
|
StickerLottieSize sizeTag,
|
|
|
|
QSize box,
|
|
|
|
Lottie::Quality quality,
|
|
|
|
std::shared_ptr<Lottie::FrameRenderer> renderer) {
|
|
|
|
const auto method = [&](auto &&...args) {
|
|
|
|
return std::make_unique<Lottie::SinglePlayer>(
|
|
|
|
std::forward<decltype(args)>(args)...,
|
|
|
|
quality,
|
|
|
|
replacements,
|
|
|
|
std::move(renderer));
|
|
|
|
};
|
2022-06-28 13:13:20 +00:00
|
|
|
const auto keyShift = LottieCacheKeyShift(
|
|
|
|
replacements ? replacements->tag : uint8(0),
|
|
|
|
sizeTag);
|
2021-09-17 16:23:52 +00:00
|
|
|
return LottieFromDocument(method, media, uint8(keyShift), box);
|
2020-06-08 17:24:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
not_null<Lottie::Animation*> LottieAnimationFromDocument(
|
|
|
|
not_null<Lottie::MultiPlayer*> player,
|
|
|
|
not_null<Data::DocumentMedia*> media,
|
|
|
|
StickerLottieSize sizeTag,
|
|
|
|
QSize box) {
|
|
|
|
const auto method = [&](auto &&...args) {
|
|
|
|
return player->append(std::forward<decltype(args)>(args)...);
|
|
|
|
};
|
2021-09-17 16:23:52 +00:00
|
|
|
return LottieFromDocument(method, media, uint8(sizeTag), box);
|
2020-06-08 17:24:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool HasLottieThumbnail(
|
2022-01-24 12:33:31 +00:00
|
|
|
Data::StickersSetFlags flags,
|
2020-06-08 17:24:36 +00:00
|
|
|
Data::StickersSetThumbnailView *thumb,
|
|
|
|
Data::DocumentMedia *media) {
|
|
|
|
if (thumb) {
|
2022-01-24 12:33:31 +00:00
|
|
|
return !(flags & Data::StickersSetFlag::Webm)
|
|
|
|
&& !thumb->content().isEmpty();
|
2020-06-08 17:24:36 +00:00
|
|
|
} else if (!media) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto document = media->owner();
|
|
|
|
if (const auto info = document->sticker()) {
|
2022-01-19 14:45:51 +00:00
|
|
|
if (!info->isLottie()) {
|
2020-06-08 17:24:36 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
media->automaticLoad(document->stickerSetOrigin(), nullptr);
|
|
|
|
if (!media->loaded()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return document->bigFileBaseCacheKey().valid();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
|
|
|
|
Data::StickersSetThumbnailView *thumb,
|
|
|
|
Data::DocumentMedia *media,
|
|
|
|
StickerLottieSize sizeTag,
|
|
|
|
QSize box,
|
|
|
|
std::shared_ptr<Lottie::FrameRenderer> renderer) {
|
|
|
|
const auto baseKey = thumb
|
2021-10-21 10:58:15 +00:00
|
|
|
? thumb->owner()->thumbnailBigFileBaseCacheKey()
|
2020-06-08 17:24:36 +00:00
|
|
|
: 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<Lottie::SinglePlayer>(
|
|
|
|
std::forward<decltype(args)>(args)...);
|
|
|
|
};
|
|
|
|
const auto session = thumb
|
|
|
|
? &thumb->owner()->session()
|
|
|
|
: media
|
|
|
|
? &media->owner()->session()
|
|
|
|
: nullptr;
|
|
|
|
return LottieCachedFromContent(
|
|
|
|
method,
|
|
|
|
baseKey,
|
|
|
|
uint8(sizeTag),
|
|
|
|
session,
|
|
|
|
content,
|
|
|
|
box);
|
|
|
|
}
|
|
|
|
|
2022-01-24 12:33:31 +00:00
|
|
|
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<void(Media::Clip::Notification)> callback) {
|
|
|
|
return thumb
|
|
|
|
? ::Media::Clip::MakeReader(
|
|
|
|
thumb->content(),
|
|
|
|
std::move(callback))
|
|
|
|
: ::Media::Clip::MakeReader(
|
|
|
|
media->owner()->location(),
|
|
|
|
media->bytes(),
|
|
|
|
std::move(callback));
|
|
|
|
}
|
|
|
|
|
2021-07-02 10:13:48 +00:00
|
|
|
bool PaintStickerThumbnailPath(
|
|
|
|
QPainter &p,
|
|
|
|
not_null<Data::DocumentMedia*> media,
|
|
|
|
QRect target,
|
2022-04-25 06:31:34 +00:00
|
|
|
QLinearGradient *gradient,
|
|
|
|
bool mirrorHorizontal) {
|
2021-07-02 10:13:48 +00:00
|
|
|
const auto &path = media->thumbnailPath();
|
|
|
|
const auto dimensions = media->owner()->dimensions;
|
2021-07-02 15:29:13 +00:00
|
|
|
if (path.isEmpty() || dimensions.isEmpty() || target.isEmpty()) {
|
2021-07-02 10:13:48 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
p.save();
|
|
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
|
|
p.setPen(Qt::NoPen);
|
|
|
|
p.translate(target.topLeft());
|
2021-07-02 15:29:13 +00:00
|
|
|
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);
|
|
|
|
}
|
2022-04-25 06:31:34 +00:00
|
|
|
if (mirrorHorizontal) {
|
|
|
|
const auto c = QPointF(target.width() / 2., target.height() / 2.);
|
|
|
|
p.translate(c);
|
|
|
|
p.scale(-1., 1.);
|
|
|
|
p.translate(-c);
|
|
|
|
}
|
2021-07-02 10:13:48 +00:00
|
|
|
p.scale(
|
|
|
|
target.width() / float64(dimensions.width()),
|
|
|
|
target.height() / float64(dimensions.height()));
|
|
|
|
p.drawPath(path);
|
|
|
|
p.restore();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-07-02 15:29:13 +00:00
|
|
|
bool PaintStickerThumbnailPath(
|
|
|
|
QPainter &p,
|
|
|
|
not_null<Data::DocumentMedia*> media,
|
|
|
|
QRect target,
|
2022-04-25 06:31:34 +00:00
|
|
|
not_null<Ui::PathShiftGradient*> gradient,
|
|
|
|
bool mirrorHorizontal) {
|
2021-07-02 15:29:13 +00:00
|
|
|
return gradient->paint([&](const Ui::PathShiftGradient::Background &bg) {
|
|
|
|
if (const auto color = std::get_if<style::color>(&bg)) {
|
|
|
|
p.setBrush(*color);
|
2022-04-25 06:31:34 +00:00
|
|
|
return PaintStickerThumbnailPath(
|
|
|
|
p,
|
|
|
|
media,
|
|
|
|
target,
|
|
|
|
nullptr,
|
|
|
|
mirrorHorizontal);
|
2021-07-02 15:29:13 +00:00
|
|
|
}
|
|
|
|
const auto gradient = v::get<QLinearGradient*>(bg);
|
2022-04-25 06:31:34 +00:00
|
|
|
return PaintStickerThumbnailPath(
|
|
|
|
p,
|
|
|
|
media,
|
|
|
|
target,
|
|
|
|
gradient,
|
|
|
|
mirrorHorizontal);
|
2021-07-02 15:29:13 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-01-24 21:40:26 +00:00
|
|
|
QSize ComputeStickerSize(not_null<DocumentData*> 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 };
|
2022-03-31 10:56:35 +00:00
|
|
|
return HistoryView::NonEmptySize(request.size(dimensions, 8) / ratio);
|
2022-01-24 21:40:26 +00:00
|
|
|
}
|
|
|
|
|
2020-06-08 17:24:36 +00:00
|
|
|
} // namespace ChatHelpers
|