mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Use FrameGenerator-based AnimatedIcon for reactions.
This commit is contained in:
parent
d9a6d5f508
commit
ed3f246510
24 changed files with 225 additions and 1181 deletions
|
@ -82,7 +82,6 @@ PRIVATE
|
|||
desktop-app::lib_webview
|
||||
desktop-app::lib_ffmpeg
|
||||
desktop-app::lib_stripe
|
||||
desktop-app::external_lz4
|
||||
desktop-app::external_rlottie
|
||||
desktop-app::external_zlib
|
||||
desktop-app::external_kcoreaddons
|
||||
|
|
|
@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "boxes/peers/edit_peer_reactions.h"
|
||||
|
||||
#include "boxes/reactions_settings_box.h" // AddReactionLottieIcon
|
||||
#include "boxes/reactions_settings_box.h" // AddReactionAnimatedIcon
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_chat.h"
|
||||
|
@ -58,7 +58,7 @@ void EditAllowedReactionsBox(
|
|||
tr::lng_manage_peer_reactions_enable(),
|
||||
st::manageGroupButton.button);
|
||||
if (!list.empty()) {
|
||||
AddReactionLottieIcon(
|
||||
AddReactionAnimatedIcon(
|
||||
enabled,
|
||||
enabled->sizeValue(
|
||||
) | rpl::map([=](const QSize &size) {
|
||||
|
@ -101,7 +101,7 @@ void EditAllowedReactionsBox(
|
|||
container,
|
||||
rpl::single(entry.title),
|
||||
st::manageGroupButton.button);
|
||||
AddReactionLottieIcon(
|
||||
AddReactionAnimatedIcon(
|
||||
button,
|
||||
button->sizeValue(
|
||||
) | rpl::map([=](const QSize &size) {
|
||||
|
|
|
@ -19,7 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/reactions/history_view_reactions_strip.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h"
|
||||
|
@ -33,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/animated_icon.h"
|
||||
#include "window/section_widget.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
@ -230,7 +230,7 @@ void AddMessage(
|
|||
}
|
||||
const auto index = state->icons.flag ? 1 : 0;
|
||||
state->icons.lifetimes[index] = rpl::lifetime();
|
||||
AddReactionLottieIcon(
|
||||
AddReactionAnimatedIcon(
|
||||
container,
|
||||
widget->geometryValue(
|
||||
) | rpl::map([=](const QRect &r) {
|
||||
|
@ -253,7 +253,7 @@ void AddMessage(
|
|||
|
||||
} // namespace
|
||||
|
||||
void AddReactionLottieIcon(
|
||||
void AddReactionAnimatedIcon(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<QPoint> iconPositionValue,
|
||||
int iconSize,
|
||||
|
@ -261,11 +261,10 @@ void AddReactionLottieIcon(
|
|||
rpl::producer<> &&selects,
|
||||
rpl::producer<> &&destroys,
|
||||
not_null<rpl::lifetime*> stateLifetime) {
|
||||
|
||||
struct State {
|
||||
struct Entry {
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
std::shared_ptr<Lottie::Icon> icon;
|
||||
std::shared_ptr<Ui::AnimatedIcon> icon;
|
||||
};
|
||||
Entry appear;
|
||||
Entry select;
|
||||
|
@ -332,7 +331,7 @@ void AddReactionLottieIcon(
|
|||
p.scale(progress, progress);
|
||||
}
|
||||
|
||||
const auto paintFrame = [&](not_null<Lottie::Icon*> animation) {
|
||||
const auto paintFrame = [&](not_null<Ui::AnimatedIcon*> animation) {
|
||||
const auto frame = animation->frame();
|
||||
p.drawImage(
|
||||
QRect(
|
||||
|
@ -346,7 +345,7 @@ void AddReactionLottieIcon(
|
|||
const auto appear = state->appear.icon.get();
|
||||
if (appear && !state->appearAnimated) {
|
||||
state->appearAnimated = true;
|
||||
appear->animate(update, 0, appear->framesCount() - 1);
|
||||
appear->animate(update);
|
||||
}
|
||||
if (appear && appear->animating()) {
|
||||
paintFrame(appear);
|
||||
|
@ -360,7 +359,7 @@ void AddReactionLottieIcon(
|
|||
) | rpl::start_with_next([=] {
|
||||
const auto select = state->select.icon.get();
|
||||
if (select && !select->animating()) {
|
||||
select->animate(update, 0, select->framesCount() - 1);
|
||||
select->animate(update);
|
||||
}
|
||||
}, widget->lifetime());
|
||||
|
||||
|
@ -437,7 +436,7 @@ void ReactionsSettingsBox(
|
|||
}
|
||||
|
||||
const auto iconSize = st::settingsReactionSize;
|
||||
AddReactionLottieIcon(
|
||||
AddReactionAnimatedIcon(
|
||||
button,
|
||||
button->sizeValue(
|
||||
) | rpl::map([=, left = button->st().iconLeft](const QSize &s) {
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace Data {
|
|||
struct Reaction;
|
||||
} // namespace Data
|
||||
|
||||
void AddReactionLottieIcon(
|
||||
void AddReactionAnimatedIcon(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<QPoint> iconPositionValue,
|
||||
int iconSize,
|
||||
|
|
|
@ -17,6 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "lottie/lottie_animation.h"
|
||||
#include "lottie/lottie_frame_generator.h"
|
||||
#include "ffmpeg/ffmpeg_frame_generator.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "window/themes/window_theme_preview.h"
|
||||
|
@ -505,4 +507,47 @@ void DocumentMedia::ReadOrGenerateThumbnail(
|
|||
document->owner().cache().get(document->goodThumbnailCacheKey(), got);
|
||||
}
|
||||
|
||||
auto DocumentIconFrameGenerator(not_null<DocumentMedia*> media)
|
||||
-> FnMut<std::unique_ptr<Ui::FrameGenerator>()> {
|
||||
if (!media->loaded()) {
|
||||
return nullptr;
|
||||
}
|
||||
using Type = StickerType;
|
||||
const auto document = media->owner();
|
||||
const auto content = media->bytes();
|
||||
const auto fromFile = content.isEmpty();
|
||||
const auto type = document->sticker()
|
||||
? document->sticker()->type
|
||||
: (document->isVideoFile() || document->isAnimation())
|
||||
? Type::Webm
|
||||
: Type::Webp;
|
||||
const auto &location = media->owner()->location(true);
|
||||
if (fromFile && !location.accessEnable()) {
|
||||
return nullptr;
|
||||
}
|
||||
return [=]() -> std::unique_ptr<Ui::FrameGenerator> {
|
||||
const auto bytes = Lottie::ReadContent(content, location.name());
|
||||
if (fromFile) {
|
||||
location.accessDisable();
|
||||
}
|
||||
if (bytes.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
switch (type) {
|
||||
case Type::Tgs:
|
||||
return std::make_unique<Lottie::FrameGenerator>(bytes);
|
||||
case Type::Webm:
|
||||
return std::make_unique<FFmpeg::FrameGenerator>(bytes);
|
||||
case Type::Webp:
|
||||
return std::make_unique<Ui::ImageFrameGenerator>(bytes);
|
||||
}
|
||||
Unexpected("Document type in DocumentIconFrameGenerator.");
|
||||
};
|
||||
}
|
||||
|
||||
auto DocumentIconFrameGenerator(const std::shared_ptr<DocumentMedia> &media)
|
||||
-> FnMut<std::unique_ptr<Ui::FrameGenerator>()> {
|
||||
return DocumentIconFrameGenerator(media.get());
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
class Image;
|
||||
class FileLoader;
|
||||
|
||||
namespace Ui {
|
||||
class FrameGenerator;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Media {
|
||||
namespace Clip {
|
||||
enum class Notification;
|
||||
|
@ -112,4 +116,11 @@ private:
|
|||
|
||||
};
|
||||
|
||||
[[nodiscard]] auto DocumentIconFrameGenerator(not_null<DocumentMedia*> media)
|
||||
-> FnMut<std::unique_ptr<Ui::FrameGenerator>()>;
|
||||
|
||||
[[nodiscard]] auto DocumentIconFrameGenerator(
|
||||
const std::shared_ptr<DocumentMedia> &media)
|
||||
-> FnMut<std::unique_ptr<Ui::FrameGenerator>()>;
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -20,9 +20,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_document_media.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "ui/animated_icon.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "base/call_delayed.h"
|
||||
|
@ -300,7 +300,7 @@ void Reactions::preloadImageFor(const ReactionId &id) {
|
|||
? nullptr
|
||||
: i->centerIcon
|
||||
? i->centerIcon
|
||||
: i->appearAnimation.get();
|
||||
: i->selectAnimation.get();
|
||||
if (document) {
|
||||
loadImage(set, document, !i->centerIcon);
|
||||
} else if (!_waitingForList) {
|
||||
|
@ -337,7 +337,7 @@ QImage Reactions::resolveImageFor(
|
|||
auto &set = (i != end(_images)) ? i->second : _images[emoji];
|
||||
const auto resolve = [&](QImage &image, int size) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto frameSize = set.fromAppearAnimation
|
||||
const auto frameSize = set.fromSelectAnimation
|
||||
? (size / 2)
|
||||
: size;
|
||||
image = set.icon->frame().scaled(
|
||||
|
@ -345,7 +345,7 @@ QImage Reactions::resolveImageFor(
|
|||
frameSize * factor,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
if (set.fromAppearAnimation) {
|
||||
if (set.fromSelectAnimation) {
|
||||
auto result = QImage(
|
||||
size * factor,
|
||||
size * factor,
|
||||
|
@ -385,7 +385,7 @@ void Reactions::resolveImages() {
|
|||
? nullptr
|
||||
: i->centerIcon
|
||||
? i->centerIcon
|
||||
: i->appearAnimation.get();
|
||||
: i->selectAnimation.get();
|
||||
if (document) {
|
||||
loadImage(set, document, !i->centerIcon);
|
||||
} else {
|
||||
|
@ -398,16 +398,16 @@ void Reactions::resolveImages() {
|
|||
void Reactions::loadImage(
|
||||
ImageSet &set,
|
||||
not_null<DocumentData*> document,
|
||||
bool fromAppearAnimation) {
|
||||
bool fromSelectAnimation) {
|
||||
if (!set.bottomInfo.isNull() || set.icon) {
|
||||
return;
|
||||
} else if (!set.media) {
|
||||
set.fromAppearAnimation = fromAppearAnimation;
|
||||
set.fromSelectAnimation = fromSelectAnimation;
|
||||
set.media = document->createMediaView();
|
||||
set.media->checkStickerLarge();
|
||||
}
|
||||
if (set.media->loaded()) {
|
||||
setLottie(set);
|
||||
setAnimatedIcon(set);
|
||||
} else if (!_imagesLoadLifetime) {
|
||||
document->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
|
@ -416,13 +416,11 @@ void Reactions::loadImage(
|
|||
}
|
||||
}
|
||||
|
||||
void Reactions::setLottie(ImageSet &set) {
|
||||
void Reactions::setAnimatedIcon(ImageSet &set) {
|
||||
const auto size = style::ConvertScale(kSizeForDownscale);
|
||||
set.icon = Lottie::MakeIcon({
|
||||
.path = set.media->owner()->filepath(true),
|
||||
.json = set.media->bytes(),
|
||||
set.icon = Ui::MakeAnimatedIcon({
|
||||
.generator = DocumentIconFrameGenerator(set.media),
|
||||
.sizeOverride = QSize(size, size),
|
||||
.frame = -1,
|
||||
});
|
||||
set.media = nullptr;
|
||||
}
|
||||
|
@ -433,7 +431,7 @@ void Reactions::downloadTaskFinished() {
|
|||
if (!set.media) {
|
||||
continue;
|
||||
} else if (set.media->loaded()) {
|
||||
setLottie(set);
|
||||
setAnimatedIcon(set);
|
||||
} else {
|
||||
hasOne = true;
|
||||
}
|
||||
|
@ -644,8 +642,6 @@ std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
|
|||
if (!known) {
|
||||
LOG(("API Error: Unknown emoji in reactions: %1").arg(emoji));
|
||||
}
|
||||
const auto selectAnimation = _owner->processDocument(
|
||||
data.vselect_animation());
|
||||
return known
|
||||
? std::make_optional(Reaction{
|
||||
.id = ReactionId{ emoji },
|
||||
|
@ -653,7 +649,8 @@ std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
|
|||
//.staticIcon = _owner->processDocument(data.vstatic_icon()),
|
||||
.appearAnimation = _owner->processDocument(
|
||||
data.vappear_animation()),
|
||||
.selectAnimation = selectAnimation,
|
||||
.selectAnimation = _owner->processDocument(
|
||||
data.vselect_animation()),
|
||||
//.activateAnimation = _owner->processDocument(
|
||||
// data.vactivate_animation()),
|
||||
//.activateEffects = _owner->processDocument(
|
||||
|
|
|
@ -11,14 +11,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_message_reaction_id.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
|
||||
namespace Ui {
|
||||
class AnimatedIcon;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Text {
|
||||
class CustomEmoji;
|
||||
} // namespace Ui::Text
|
||||
|
||||
namespace Lottie {
|
||||
class Icon;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace Data {
|
||||
|
||||
class DocumentMedia;
|
||||
|
@ -117,8 +117,8 @@ private:
|
|||
QImage bottomInfo;
|
||||
QImage inlineList;
|
||||
std::shared_ptr<DocumentMedia> media;
|
||||
std::unique_ptr<Lottie::Icon> icon;
|
||||
bool fromAppearAnimation = false;
|
||||
std::unique_ptr<Ui::AnimatedIcon> icon;
|
||||
bool fromSelectAnimation = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] not_null<CustomEmojiManager::Listener*> resolveListener();
|
||||
|
@ -148,8 +148,8 @@ private:
|
|||
void loadImage(
|
||||
ImageSet &set,
|
||||
not_null<DocumentData*> document,
|
||||
bool fromAppearAnimation);
|
||||
void setLottie(ImageSet &set);
|
||||
bool fromSelectAnimation);
|
||||
void setAnimatedIcon(ImageSet &set);
|
||||
void resolveImages();
|
||||
void downloadTaskFinished();
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_message_reactions.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "lottie/lottie_common.h"
|
||||
#include "lottie/lottie_emoji.h"
|
||||
#include "ffmpeg/ffmpeg_emoji.h"
|
||||
#include "lottie/lottie_frame_generator.h"
|
||||
#include "ffmpeg/ffmpeg_frame_generator.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/text/text_custom_emoji.h"
|
||||
|
@ -321,9 +321,9 @@ void CustomEmojiLoader::check() {
|
|||
-> std::unique_ptr<Ui::FrameGenerator> {
|
||||
switch (type) {
|
||||
case StickerType::Tgs:
|
||||
return std::make_unique<Lottie::EmojiGenerator>(bytes);
|
||||
return std::make_unique<Lottie::FrameGenerator>(bytes);
|
||||
case StickerType::Webm:
|
||||
return std::make_unique<FFmpeg::EmojiGenerator>(bytes);
|
||||
return std::make_unique<FFmpeg::FrameGenerator>(bytes);
|
||||
case StickerType::Webp:
|
||||
return std::make_unique<Ui::ImageFrameGenerator>(bytes);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ 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 "ffmpeg/ffmpeg_emoji.h"
|
||||
#include "ffmpeg/ffmpeg_frame_generator.h"
|
||||
|
||||
#include "ffmpeg/ffmpeg_utility.h"
|
||||
#include "base/debug_log.h"
|
||||
|
@ -17,7 +17,7 @@ constexpr auto kMaxArea = 1920 * 1080 * 4;
|
|||
|
||||
} // namespace
|
||||
|
||||
class EmojiGenerator::Impl final {
|
||||
class FrameGenerator::Impl final {
|
||||
public:
|
||||
explicit Impl(const QByteArray &bytes);
|
||||
|
||||
|
@ -25,6 +25,11 @@ public:
|
|||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode);
|
||||
[[nodiscard]] Frame renderCurrent(
|
||||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode);
|
||||
void jumpToStart();
|
||||
|
||||
private:
|
||||
struct ReadFrame {
|
||||
|
@ -35,10 +40,6 @@ private:
|
|||
|
||||
void readNextFrame();
|
||||
void resolveNextFrameTiming();
|
||||
[[nodiscard]] Frame renderCurrent(
|
||||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode);
|
||||
|
||||
[[nodiscard]] QString wrapError(int result) const;
|
||||
|
||||
|
@ -76,13 +77,13 @@ private:
|
|||
|
||||
};
|
||||
|
||||
EmojiGenerator::Impl::Impl(const QByteArray &bytes)
|
||||
FrameGenerator::Impl::Impl(const QByteArray &bytes)
|
||||
: _bytes(bytes) {
|
||||
_format = MakeFormatPointer(
|
||||
static_cast<void*>(this),
|
||||
&EmojiGenerator::Impl::Read,
|
||||
&FrameGenerator::Impl::Read,
|
||||
nullptr,
|
||||
&EmojiGenerator::Impl::Seek);
|
||||
&FrameGenerator::Impl::Seek);
|
||||
|
||||
auto error = 0;
|
||||
if ((error = avformat_find_stream_info(_format.get(), nullptr))) {
|
||||
|
@ -105,11 +106,11 @@ EmojiGenerator::Impl::Impl(const QByteArray &bytes)
|
|||
_codec = MakeCodecPointer({ .stream = info });
|
||||
}
|
||||
|
||||
int EmojiGenerator::Impl::Read(void *opaque, uint8_t *buf, int buf_size) {
|
||||
int FrameGenerator::Impl::Read(void *opaque, uint8_t *buf, int buf_size) {
|
||||
return static_cast<Impl*>(opaque)->read(buf, buf_size);
|
||||
}
|
||||
|
||||
int EmojiGenerator::Impl::read(uint8_t *buf, int buf_size) {
|
||||
int FrameGenerator::Impl::read(uint8_t *buf, int buf_size) {
|
||||
const auto available = _bytes.size() - _deviceOffset;
|
||||
if (available <= 0) {
|
||||
return -1;
|
||||
|
@ -120,14 +121,14 @@ int EmojiGenerator::Impl::read(uint8_t *buf, int buf_size) {
|
|||
return fill;
|
||||
}
|
||||
|
||||
int64_t EmojiGenerator::Impl::Seek(
|
||||
int64_t FrameGenerator::Impl::Seek(
|
||||
void *opaque,
|
||||
int64_t offset,
|
||||
int whence) {
|
||||
return static_cast<Impl*>(opaque)->seek(offset, whence);
|
||||
}
|
||||
|
||||
int64_t EmojiGenerator::Impl::seek(int64_t offset, int whence) {
|
||||
int64_t FrameGenerator::Impl::seek(int64_t offset, int whence) {
|
||||
if (whence == AVSEEK_SIZE) {
|
||||
return _bytes.size();
|
||||
}
|
||||
|
@ -146,7 +147,7 @@ int64_t EmojiGenerator::Impl::seek(int64_t offset, int whence) {
|
|||
return now;
|
||||
}
|
||||
|
||||
EmojiGenerator::Frame EmojiGenerator::Impl::renderCurrent(
|
||||
FrameGenerator::Frame FrameGenerator::Impl::renderCurrent(
|
||||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode) {
|
||||
|
@ -238,18 +239,18 @@ EmojiGenerator::Frame EmojiGenerator::Impl::renderCurrent(
|
|||
transform.rotate(_rotation);
|
||||
storage = storage.transformed(transform);
|
||||
}
|
||||
ClearFrameMemory(_current.frame.get());
|
||||
|
||||
const auto duration = _next.frame
|
||||
? (_next.position - _current.position)
|
||||
: _current.duration;
|
||||
return {
|
||||
.image = std::move(storage),
|
||||
.duration = duration,
|
||||
.image = std::move(storage),
|
||||
.last = !_next.frame,
|
||||
};
|
||||
}
|
||||
|
||||
EmojiGenerator::Frame EmojiGenerator::Impl::renderNext(
|
||||
FrameGenerator::Frame FrameGenerator::Impl::renderNext(
|
||||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode) {
|
||||
|
@ -264,7 +265,27 @@ EmojiGenerator::Frame EmojiGenerator::Impl::renderNext(
|
|||
return renderCurrent(std::move(storage), size, mode);
|
||||
}
|
||||
|
||||
void EmojiGenerator::Impl::resolveNextFrameTiming() {
|
||||
void FrameGenerator::Impl::jumpToStart() {
|
||||
auto result = 0;
|
||||
if ((result = avformat_seek_file(_format.get(), _streamId, std::numeric_limits<int64_t>::min(), 0, std::numeric_limits<int64_t>::max(), 0)) < 0) {
|
||||
if ((result = av_seek_frame(_format.get(), _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) {
|
||||
if ((result = av_seek_frame(_format.get(), _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) {
|
||||
if ((result = av_seek_frame(_format.get(), _streamId, 0, 0)) < 0) {
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
LOG(("Webm Error: Unable to av_seek_frame() to the start, ") + wrapError(result));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
avcodec_flush_buffers(_codec.get());
|
||||
_current = ReadFrame();
|
||||
_next = ReadFrame();
|
||||
_currentFrameDelay = _nextFrameDelay = 0;
|
||||
_framePosition = 0;
|
||||
}
|
||||
|
||||
void FrameGenerator::Impl::resolveNextFrameTiming() {
|
||||
const auto base = _format->streams[_streamId]->time_base;
|
||||
const auto duration = _next.frame->pkt_duration;
|
||||
const auto framePts = _next.frame->pts;
|
||||
|
@ -287,7 +308,7 @@ void EmojiGenerator::Impl::resolveNextFrameTiming() {
|
|||
_next.duration = _nextFrameDelay;
|
||||
}
|
||||
|
||||
void EmojiGenerator::Impl::readNextFrame() {
|
||||
void FrameGenerator::Impl::readNextFrame() {
|
||||
auto frame = _next.frame ? base::take(_next.frame) : MakeFramePointer();
|
||||
while (true) {
|
||||
auto result = avcodec_receive_frame(_codec.get(), frame.get());
|
||||
|
@ -347,28 +368,43 @@ void EmojiGenerator::Impl::readNextFrame() {
|
|||
}
|
||||
}
|
||||
|
||||
QString EmojiGenerator::Impl::wrapError(int result) const {
|
||||
QString FrameGenerator::Impl::wrapError(int result) const {
|
||||
auto error = std::array<char, AV_ERROR_MAX_STRING_SIZE>{};
|
||||
return u"error %1, %2"_q
|
||||
.arg(result)
|
||||
.arg(av_make_error_string(error.data(), error.size(), result));
|
||||
}
|
||||
|
||||
EmojiGenerator::EmojiGenerator(const QByteArray &bytes)
|
||||
FrameGenerator::FrameGenerator(const QByteArray &bytes)
|
||||
: _impl(std::make_unique<Impl>(bytes)) {
|
||||
}
|
||||
|
||||
EmojiGenerator::~EmojiGenerator() = default;
|
||||
FrameGenerator::~FrameGenerator() = default;
|
||||
|
||||
int EmojiGenerator::count() {
|
||||
int FrameGenerator::count() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
EmojiGenerator::Frame EmojiGenerator::renderNext(
|
||||
double FrameGenerator::rate() {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
FrameGenerator::Frame FrameGenerator::renderNext(
|
||||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode) {
|
||||
return _impl->renderNext(std::move(storage), size, mode);
|
||||
}
|
||||
|
||||
FrameGenerator::Frame FrameGenerator::renderCurrent(
|
||||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode) {
|
||||
return _impl->renderCurrent(std::move(storage), size, mode);
|
||||
}
|
||||
|
||||
void FrameGenerator::jumpToStart() {
|
||||
_impl->jumpToStart();
|
||||
}
|
||||
|
||||
} // namespace FFmpeg
|
|
@ -14,16 +14,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace FFmpeg {
|
||||
|
||||
class EmojiGenerator final : public Ui::FrameGenerator {
|
||||
class FrameGenerator final : public Ui::FrameGenerator {
|
||||
public:
|
||||
explicit EmojiGenerator(const QByteArray &bytes);
|
||||
~EmojiGenerator();
|
||||
explicit FrameGenerator(const QByteArray &bytes);
|
||||
~FrameGenerator();
|
||||
|
||||
int count() override;
|
||||
double rate() override;
|
||||
Frame renderNext(
|
||||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio) override;
|
||||
Frame renderCurrent(
|
||||
QImage storage,
|
||||
QSize size,
|
||||
Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio) override;
|
||||
void jumpToStart() override;
|
||||
|
||||
private:
|
||||
class Impl;
|
|
@ -14,16 +14,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace Ui {
|
||||
struct ChatPaintContext;
|
||||
class AnimatedIcon;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
class Reactions;
|
||||
} // namespace Data
|
||||
|
||||
namespace Lottie {
|
||||
class Icon;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace HistoryView {
|
||||
namespace Reactions {
|
||||
class Animation;
|
||||
|
@ -31,7 +28,7 @@ class Animation;
|
|||
|
||||
struct ReactionAnimationArgs {
|
||||
::Data::ReactionId id;
|
||||
std::shared_ptr<Lottie::Icon> flyIcon;
|
||||
std::shared_ptr<Ui::AnimatedIcon> flyIcon;
|
||||
QRect flyFrom;
|
||||
|
||||
[[nodiscard]] ReactionAnimationArgs translated(QPoint point) const;
|
||||
|
|
|
@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/history_view_bottom_info.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "ui/animated_icon.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
|
@ -36,7 +36,7 @@ Animation::Animation(
|
|||
return;
|
||||
}
|
||||
const auto resolve = [&](
|
||||
std::unique_ptr<Lottie::Icon> &icon,
|
||||
std::unique_ptr<Ui::AnimatedIcon> &icon,
|
||||
DocumentData *document,
|
||||
int size) {
|
||||
if (!document) {
|
||||
|
@ -46,9 +46,8 @@ Animation::Animation(
|
|||
if (!media || !media->loaded()) {
|
||||
return false;
|
||||
}
|
||||
icon = Lottie::MakeIcon({
|
||||
.path = document->filepath(true),
|
||||
.json = media->bytes(),
|
||||
icon = Ui::MakeAnimatedIcon({
|
||||
.generator = DocumentIconFrameGenerator(media),
|
||||
.sizeOverride = QSize(size, size),
|
||||
});
|
||||
return true;
|
||||
|
@ -142,8 +141,8 @@ int Animation::computeParabolicTop(
|
|||
}
|
||||
|
||||
void Animation::startAnimations() {
|
||||
_center->animate([=] { callback(); }, 0, _center->framesCount() - 1);
|
||||
_effect->animate([=] { callback(); }, 0, _effect->framesCount() - 1);
|
||||
_center->animate([=] { callback(); });
|
||||
_effect->animate([=] { callback(); });
|
||||
}
|
||||
|
||||
void Animation::flyCallback() {
|
||||
|
|
|
@ -9,8 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
namespace Lottie {
|
||||
class Icon;
|
||||
namespace Ui {
|
||||
class AnimatedIcon;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace Data {
|
||||
|
@ -47,9 +47,9 @@ private:
|
|||
|
||||
const not_null<::Data::Reactions*> _owner;
|
||||
Fn<void()> _repaint;
|
||||
std::shared_ptr<Lottie::Icon> _flyIcon;
|
||||
std::unique_ptr<Lottie::Icon> _center;
|
||||
std::unique_ptr<Lottie::Icon> _effect;
|
||||
std::shared_ptr<Ui::AnimatedIcon> _flyIcon;
|
||||
std::unique_ptr<Ui::AnimatedIcon> _center;
|
||||
std::unique_ptr<Ui::AnimatedIcon> _effect;
|
||||
Ui::Animations::Simple _fly;
|
||||
QRect _flyFrom;
|
||||
bool _valid = false;
|
||||
|
|
|
@ -262,8 +262,4 @@ void SetupManagerList(
|
|||
not_null<Manager*> manager,
|
||||
rpl::producer<HistoryItem*> items);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Lottie::Icon> DefaultIconFactory(
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
int size);
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -11,8 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/effects/frame_generator.h"
|
||||
#include "ui/animated_icon.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
|
@ -27,21 +28,31 @@ constexpr auto kHoverScale = 1.24;
|
|||
return style::ConvertScale(kSizeForDownscale);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Lottie::Icon> CreateIcon(
|
||||
[[nodiscard]] std::shared_ptr<Ui::AnimatedIcon> CreateIcon(
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
int size,
|
||||
int frame) {
|
||||
int size) {
|
||||
Expects(media->loaded());
|
||||
|
||||
return std::make_shared<Lottie::Icon>(Lottie::IconDescriptor{
|
||||
.path = media->owner()->filepath(true),
|
||||
.json = media->bytes(),
|
||||
return std::make_shared<Ui::AnimatedIcon>(Ui::AnimatedIconDescriptor{
|
||||
.generator = DocumentIconFrameGenerator(media),
|
||||
.sizeOverride = QSize(size, size),
|
||||
.frame = frame,
|
||||
.limitFps = true,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Ui::AnimatedIcon> CreateIconSnapshot(
|
||||
not_null<DocumentData*> document,
|
||||
not_null<Ui::AnimatedIcon*> existing) {
|
||||
const auto frame = existing->frame();
|
||||
return frame.isNull()
|
||||
? CreateIcon(
|
||||
document->activeMediaView().get(),
|
||||
existing->width())
|
||||
: std::make_shared<Ui::AnimatedIcon>(Ui::AnimatedIconDescriptor{
|
||||
.generator = [=] { return std::make_unique<Ui::ImageFrameGenerator>(frame); },
|
||||
.sizeOverride = existing->size(),
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Strip::Strip(
|
||||
|
@ -157,7 +168,7 @@ void Strip::paintOne(
|
|||
} else if (icon.added == AddedButton::Expand) {
|
||||
paintExpandIcon(p, position, target);
|
||||
} else {
|
||||
const auto paintFrame = [&](not_null<Lottie::Icon*> animation) {
|
||||
const auto paintFrame = [&](not_null<Ui::AnimatedIcon*> animation) {
|
||||
const auto size = int(std::floor(target.width() + 0.01));
|
||||
const auto frame = animation->frame({ size, size }, _update);
|
||||
p.drawImage(target, frame.image);
|
||||
|
@ -166,7 +177,7 @@ void Strip::paintOne(
|
|||
const auto appear = icon.appear.get();
|
||||
if (appear && !icon.appearAnimated && allowAppearStart) {
|
||||
icon.appearAnimated = true;
|
||||
appear->animate(_update, 0, appear->framesCount() - 1);
|
||||
appear->animate(_update);
|
||||
}
|
||||
if (appear && appear->animating()) {
|
||||
paintFrame(appear);
|
||||
|
@ -220,15 +231,9 @@ int Strip::fillChosenIconGetIndex(ChosenReaction &chosen) const {
|
|||
}
|
||||
const auto &icon = *i;
|
||||
if (const auto &appear = icon.appear; appear && appear->animating()) {
|
||||
chosen.icon = CreateIcon(
|
||||
icon.appearAnimation->activeMediaView().get(),
|
||||
appear->width(),
|
||||
appear->frameIndex());
|
||||
chosen.icon = CreateIconSnapshot(icon.appearAnimation, appear.get());
|
||||
} else if (const auto &select = icon.select) {
|
||||
chosen.icon = CreateIcon(
|
||||
icon.selectAnimation->activeMediaView().get(),
|
||||
select->width(),
|
||||
select->frameIndex());
|
||||
chosen.icon = CreateIconSnapshot(icon.selectAnimation, select.get());
|
||||
}
|
||||
return (i - begin(_icons));
|
||||
}
|
||||
|
@ -307,7 +312,7 @@ void Strip::setSelected(int index) const {
|
|||
const auto select = skipAnimation ? nullptr : icon.select.get();
|
||||
if (select && !icon.selectAnimated) {
|
||||
icon.selectAnimated = true;
|
||||
select->animate(_update, 0, select->framesCount() - 1);
|
||||
select->animate(_update);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -342,13 +347,13 @@ void Strip::clearAppearAnimations(bool mainAppeared) {
|
|||
}
|
||||
icon.selectedScale.stop();
|
||||
if (const auto select = icon.select.get()) {
|
||||
select->jumpTo(0, nullptr);
|
||||
select->jumpToStart(nullptr);
|
||||
}
|
||||
icon.selectAnimated = false;
|
||||
}
|
||||
if (icon.appearAnimated != main) {
|
||||
if (const auto appear = icon.appear.get()) {
|
||||
appear->jumpTo(0, nullptr);
|
||||
appear->jumpToStart(nullptr);
|
||||
}
|
||||
icon.appearAnimated = main;
|
||||
}
|
||||
|
@ -358,7 +363,7 @@ void Strip::clearAppearAnimations(bool mainAppeared) {
|
|||
|
||||
void Strip::clearStateForHidden(ReactionIcons &icon) {
|
||||
if (const auto appear = icon.appear.get()) {
|
||||
appear->jumpTo(0, nullptr);
|
||||
appear->jumpToStart(nullptr);
|
||||
}
|
||||
if (icon.selected) {
|
||||
setSelected(-1);
|
||||
|
@ -366,7 +371,7 @@ void Strip::clearStateForHidden(ReactionIcons &icon) {
|
|||
icon.appearAnimated = false;
|
||||
icon.selectAnimated = false;
|
||||
if (const auto select = icon.select.get()) {
|
||||
select->jumpTo(0, nullptr);
|
||||
select->jumpToStart(nullptr);
|
||||
}
|
||||
icon.selectedScale.stop();
|
||||
}
|
||||
|
@ -424,8 +429,8 @@ void Strip::loadIcons() {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (all && !_icons.empty() && _icons.front().appearAnimation) {
|
||||
auto &data = _icons.front().appearAnimation->owner().reactions();
|
||||
if (all && !_icons.empty() && _icons.front().selectAnimation) {
|
||||
auto &data = _icons.front().selectAnimation->owner().reactions();
|
||||
for (const auto &icon : _icons) {
|
||||
data.preloadAnimationsFor(icon.id);
|
||||
}
|
||||
|
@ -562,10 +567,10 @@ IconFactory CachedIconFactory::createMethod() {
|
|||
};
|
||||
}
|
||||
|
||||
std::shared_ptr<Lottie::Icon> DefaultIconFactory(
|
||||
std::shared_ptr<Ui::AnimatedIcon> DefaultIconFactory(
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
int size) {
|
||||
return CreateIcon(media, size, 0);
|
||||
return CreateIcon(media, size);
|
||||
}
|
||||
|
||||
} // namespace HistoryView::Reactions
|
||||
|
|
|
@ -18,16 +18,16 @@ struct Reaction;
|
|||
class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Lottie {
|
||||
class Icon;
|
||||
} // namespace Lottie
|
||||
namespace Ui {
|
||||
class AnimatedIcon;
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
|
||||
struct ChosenReaction {
|
||||
FullMsgId context;
|
||||
Data::ReactionId id;
|
||||
std::shared_ptr<Lottie::Icon> icon;
|
||||
std::shared_ptr<Ui::AnimatedIcon> icon;
|
||||
QRect geometry;
|
||||
|
||||
explicit operator bool() const {
|
||||
|
@ -35,7 +35,7 @@ struct ChosenReaction {
|
|||
}
|
||||
};
|
||||
|
||||
using IconFactory = Fn<std::shared_ptr<Lottie::Icon>(
|
||||
using IconFactory = Fn<std::shared_ptr<Ui::AnimatedIcon>(
|
||||
not_null<Data::DocumentMedia*>,
|
||||
int)>;
|
||||
|
||||
|
@ -84,14 +84,14 @@ private:
|
|||
|
||||
struct ReactionDocument {
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
std::shared_ptr<Lottie::Icon> icon;
|
||||
std::shared_ptr<Ui::AnimatedIcon> icon;
|
||||
};
|
||||
struct ReactionIcons {
|
||||
ReactionId id;
|
||||
DocumentData *appearAnimation = nullptr;
|
||||
DocumentData *selectAnimation = nullptr;
|
||||
std::shared_ptr<Lottie::Icon> appear;
|
||||
std::shared_ptr<Lottie::Icon> select;
|
||||
std::shared_ptr<Ui::AnimatedIcon> appear;
|
||||
std::shared_ptr<Ui::AnimatedIcon> select;
|
||||
mutable Ui::Animations::Simple selectedScale;
|
||||
AddedButton added = AddedButton::None;
|
||||
bool appearAnimated = false;
|
||||
|
@ -133,7 +133,7 @@ private:
|
|||
mutable int _selectedIcon = -1;
|
||||
|
||||
std::shared_ptr<Data::DocumentMedia> _mainReactionMedia;
|
||||
std::shared_ptr<Lottie::Icon> _mainReactionIcon;
|
||||
std::shared_ptr<Ui::AnimatedIcon> _mainReactionIcon;
|
||||
QImage _mainReactionImage;
|
||||
rpl::lifetime _mainReactionLifetime;
|
||||
|
||||
|
@ -153,11 +153,11 @@ public:
|
|||
private:
|
||||
base::flat_map<
|
||||
std::shared_ptr<Data::DocumentMedia>,
|
||||
std::shared_ptr<Lottie::Icon>> _cache;
|
||||
std::shared_ptr<Ui::AnimatedIcon>> _cache;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Lottie::Icon> DefaultIconFactory(
|
||||
[[nodiscard]] std::shared_ptr<Ui::AnimatedIcon> DefaultIconFactory(
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
int size);
|
||||
|
||||
|
|
|
@ -927,7 +927,7 @@ void SetupMessages(
|
|||
const auto index = state->icons.flag ? 1 : 0;
|
||||
state->icons.lifetimes[index] = rpl::lifetime();
|
||||
const auto iconSize = st::settingsReactionRightIcon;
|
||||
AddReactionLottieIcon(
|
||||
AddReactionAnimatedIcon(
|
||||
inner,
|
||||
buttonRight->geometryValue(
|
||||
) | rpl::map([=](const QRect &r) {
|
||||
|
|
|
@ -1,773 +0,0 @@
|
|||
/*
|
||||
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 "ui/text/custom_emoji_instance.h"
|
||||
|
||||
#include "ui/effects/frame_generator.h"
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
#include <crl/crl_async.h>
|
||||
#include <lz4.h>
|
||||
|
||||
class QPainter;
|
||||
|
||||
namespace Ui::CustomEmoji {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxSize = 128;
|
||||
constexpr auto kMaxFrames = 180;
|
||||
constexpr auto kCacheVersion = 1;
|
||||
constexpr auto kPreloadFrames = 3;
|
||||
|
||||
struct CacheHeader {
|
||||
int version = 0;
|
||||
int size = 0;
|
||||
int frames = 0;
|
||||
int length = 0;
|
||||
};
|
||||
|
||||
void PaintScaledImage(
|
||||
QPainter &p,
|
||||
const QRect &target,
|
||||
const Cache::Frame &frame,
|
||||
const Context &context) {
|
||||
if (context.scaled) {
|
||||
const auto sx = anim::interpolate(
|
||||
target.width() / 2,
|
||||
0,
|
||||
context.scale);
|
||||
const auto sy = (target.height() == target.width())
|
||||
? sx
|
||||
: anim::interpolate(target.height() / 2, 0, context.scale);
|
||||
const auto scaled = target.marginsRemoved({ sx, sy, sx, sy });
|
||||
if (frame.source.isNull()) {
|
||||
p.drawImage(scaled, *frame.image);
|
||||
} else {
|
||||
p.drawImage(scaled, *frame.image, frame.source);
|
||||
}
|
||||
} else if (frame.source.isNull()) {
|
||||
p.drawImage(target, *frame.image);
|
||||
} else {
|
||||
p.drawImage(target, *frame.image, frame.source);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Preview::Preview(QPainterPath path, float64 scale)
|
||||
: _data(ScaledPath{ std::move(path), scale }) {
|
||||
}
|
||||
|
||||
Preview::Preview(QImage image, bool exact)
|
||||
: _data(Image{ .data = std::move(image), .exact = exact }) {
|
||||
}
|
||||
|
||||
void Preview::paint(QPainter &p, const Context &context) {
|
||||
if (const auto path = std::get_if<ScaledPath>(&_data)) {
|
||||
paintPath(p, context, *path);
|
||||
} else if (const auto image = std::get_if<Image>(&_data)) {
|
||||
const auto &data = image->data;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto rect = QRect(context.position, data.size() / factor);
|
||||
PaintScaledImage(p, rect, { .image = &data }, context);
|
||||
}
|
||||
}
|
||||
|
||||
bool Preview::isImage() const {
|
||||
return v::is<Image>(_data);
|
||||
}
|
||||
|
||||
bool Preview::isExactImage() const {
|
||||
if (const auto image = std::get_if<Image>(&_data)) {
|
||||
return image->exact;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage Preview::image() const {
|
||||
if (const auto image = std::get_if<Image>(&_data)) {
|
||||
return image->data;
|
||||
}
|
||||
return QImage();
|
||||
}
|
||||
|
||||
void Preview::paintPath(
|
||||
QPainter &p,
|
||||
const Context &context,
|
||||
const ScaledPath &path) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setBrush(context.preview);
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto scale = path.scale;
|
||||
const auto required = (scale != 1.) || context.scaled;
|
||||
if (required) {
|
||||
p.save();
|
||||
}
|
||||
p.translate(context.position);
|
||||
if (required) {
|
||||
p.scale(scale, scale);
|
||||
const auto center = QPoint(
|
||||
context.size.width() / 2,
|
||||
context.size.height() / 2);
|
||||
if (context.scaled) {
|
||||
p.translate(center);
|
||||
p.scale(context.scale, context.scale);
|
||||
p.translate(-center);
|
||||
}
|
||||
}
|
||||
p.drawPath(path.path);
|
||||
if (required) {
|
||||
p.restore();
|
||||
} else {
|
||||
p.translate(-context.position);
|
||||
}
|
||||
}
|
||||
|
||||
Cache::Cache(int size) : _size(size) {
|
||||
}
|
||||
|
||||
std::optional<Cache> Cache::FromSerialized(
|
||||
const QByteArray &serialized,
|
||||
int requestedSize) {
|
||||
Expects(requestedSize > 0 && requestedSize <= kMaxSize);
|
||||
|
||||
if (serialized.size() <= sizeof(CacheHeader)) {
|
||||
return {};
|
||||
}
|
||||
auto header = CacheHeader();
|
||||
memcpy(&header, serialized.data(), sizeof(header));
|
||||
const auto size = header.size;
|
||||
if (size != requestedSize
|
||||
|| header.frames <= 0
|
||||
|| header.frames >= kMaxFrames
|
||||
|| header.length <= 0
|
||||
|| header.length > (size * size * header.frames * sizeof(int32))
|
||||
|| (serialized.size() != sizeof(CacheHeader)
|
||||
+ header.length
|
||||
+ (header.frames * sizeof(Cache(0)._durations[0])))) {
|
||||
return {};
|
||||
}
|
||||
const auto rows = (header.frames + kPerRow - 1) / kPerRow;
|
||||
const auto columns = std::min(header.frames, kPerRow);
|
||||
auto durations = std::vector<uint16>(header.frames, 0);
|
||||
auto full = QImage(
|
||||
columns * size,
|
||||
rows * size,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
Assert(full.bytesPerLine() == full.width() * sizeof(int32));
|
||||
|
||||
const auto decompressed = LZ4_decompress_safe(
|
||||
serialized.data() + sizeof(CacheHeader),
|
||||
reinterpret_cast<char*>(full.bits()),
|
||||
header.length,
|
||||
full.bytesPerLine() * full.height());
|
||||
if (decompressed <= 0) {
|
||||
return {};
|
||||
}
|
||||
memcpy(
|
||||
durations.data(),
|
||||
serialized.data() + sizeof(CacheHeader) + header.length,
|
||||
header.frames * sizeof(durations[0]));
|
||||
|
||||
auto result = Cache(size);
|
||||
result._finished = true;
|
||||
result._full = std::move(full);
|
||||
result._frames = header.frames;
|
||||
result._durations = std::move(durations);
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray Cache::serialize() {
|
||||
Expects(_finished);
|
||||
Expects(_durations.size() == _frames);
|
||||
Expects(_full.bytesPerLine() == sizeof(int32) * _full.width());
|
||||
|
||||
auto header = CacheHeader{
|
||||
.version = kCacheVersion,
|
||||
.size = _size,
|
||||
.frames = _frames,
|
||||
};
|
||||
const auto input = _full.width() * _full.height() * sizeof(int32);
|
||||
const auto max = sizeof(CacheHeader)
|
||||
+ LZ4_compressBound(input)
|
||||
+ (_frames * sizeof(_durations[0]));
|
||||
auto result = QByteArray(max, Qt::Uninitialized);
|
||||
header.length = LZ4_compress_default(
|
||||
reinterpret_cast<const char*>(_full.constBits()),
|
||||
result.data() + sizeof(CacheHeader),
|
||||
input,
|
||||
result.size() - sizeof(CacheHeader));
|
||||
Assert(header.length > 0);
|
||||
memcpy(result.data(), &header, sizeof(CacheHeader));
|
||||
memcpy(
|
||||
result.data() + sizeof(CacheHeader) + header.length,
|
||||
_durations.data(),
|
||||
_frames * sizeof(_durations[0]));
|
||||
result.resize(sizeof(CacheHeader)
|
||||
+ header.length
|
||||
+ _frames * sizeof(_durations[0]));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int Cache::frames() const {
|
||||
return _frames;
|
||||
}
|
||||
|
||||
Cache::Frame Cache::frame(int index) const {
|
||||
Expects(index < _frames);
|
||||
|
||||
const auto row = index / kPerRow;
|
||||
const auto inrow = index % kPerRow;
|
||||
if (_finished) {
|
||||
return { &_full, { inrow * _size, row * _size, _size, _size } };
|
||||
}
|
||||
return { &_images[row], { 0, inrow * _size, _size, _size } };
|
||||
}
|
||||
|
||||
int Cache::size() const {
|
||||
return _size;
|
||||
}
|
||||
|
||||
Preview Cache::makePreview() const {
|
||||
Expects(_frames > 0);
|
||||
|
||||
const auto first = frame(0);
|
||||
return { first.image->copy(first.source), true };
|
||||
}
|
||||
|
||||
void Cache::reserve(int frames) {
|
||||
Expects(!_finished);
|
||||
|
||||
const auto rows = (frames + kPerRow - 1) / kPerRow;
|
||||
if (const auto add = rows - int(_images.size()); add > 0) {
|
||||
_images.resize(rows);
|
||||
for (auto e = end(_images), i = e - add; i != e; ++i) {
|
||||
(*i) = QImage(
|
||||
_size,
|
||||
_size * kPerRow,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
}
|
||||
}
|
||||
_durations.reserve(frames);
|
||||
}
|
||||
|
||||
int Cache::frameRowByteSize() const {
|
||||
return _size * 4;
|
||||
}
|
||||
|
||||
int Cache::frameByteSize() const {
|
||||
return _size * frameRowByteSize();
|
||||
}
|
||||
|
||||
void Cache::add(crl::time duration, const QImage &frame) {
|
||||
Expects(!_finished);
|
||||
Expects(frame.size() == QSize(_size, _size));
|
||||
Expects(frame.format() == QImage::Format_ARGB32_Premultiplied);
|
||||
|
||||
const auto row = (_frames / kPerRow);
|
||||
const auto inrow = (_frames % kPerRow);
|
||||
const auto rows = row + 1;
|
||||
while (_images.size() < rows) {
|
||||
_images.emplace_back();
|
||||
_images.back() = QImage(
|
||||
_size,
|
||||
_size * kPerRow,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
}
|
||||
const auto srcPerLine = frame.bytesPerLine();
|
||||
const auto dstPerLine = _images[row].bytesPerLine();
|
||||
const auto perLine = std::min(srcPerLine, dstPerLine);
|
||||
auto dst = _images[row].bits() + inrow * _size * dstPerLine;
|
||||
auto src = frame.constBits();
|
||||
for (auto y = 0; y != _size; ++y) {
|
||||
memcpy(dst, src, perLine);
|
||||
dst += dstPerLine;
|
||||
src += srcPerLine;
|
||||
}
|
||||
++_frames;
|
||||
_durations.push_back(std::clamp(
|
||||
duration,
|
||||
crl::time(0),
|
||||
crl::time(std::numeric_limits<uint16>::max())));
|
||||
}
|
||||
|
||||
void Cache::finish() {
|
||||
_finished = true;
|
||||
if (_frame == _frames) {
|
||||
_frame = 0;
|
||||
}
|
||||
const auto rows = (_frames + kPerRow - 1) / kPerRow;
|
||||
const auto columns = std::min(_frames, kPerRow);
|
||||
const auto zero = (rows * columns) - _frames;
|
||||
_full = QImage(
|
||||
columns * _size,
|
||||
rows * _size,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
auto dstData = _full.bits();
|
||||
const auto perLine = _size * 4;
|
||||
const auto dstPerLine = _full.bytesPerLine();
|
||||
for (auto y = 0; y != rows; ++y) {
|
||||
auto &row = _images[y];
|
||||
auto src = row.bits();
|
||||
const auto srcPerLine = row.bytesPerLine();
|
||||
const auto till = columns - ((y + 1 == rows) ? zero : 0);
|
||||
for (auto x = 0; x != till; ++x) {
|
||||
auto dst = dstData + y * dstPerLine * _size + x * perLine;
|
||||
for (auto line = 0; line != _size; ++line) {
|
||||
memcpy(dst, src, perLine);
|
||||
src += srcPerLine;
|
||||
dst += dstPerLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (const auto perLine = zero * _size) {
|
||||
auto dst = dstData
|
||||
+ (rows - 1) * dstPerLine * _size
|
||||
+ (columns - zero) * _size * 4;
|
||||
for (auto left = 0; left != _size; ++left) {
|
||||
memset(dst, 0, perLine);
|
||||
dst += dstPerLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PaintFrameResult Cache::paintCurrentFrame(
|
||||
QPainter &p,
|
||||
const Context &context) {
|
||||
if (!_frames) {
|
||||
return {};
|
||||
}
|
||||
const auto first = context.firstFrameOnly;
|
||||
if (!first) {
|
||||
const auto now = context.paused ? 0 : context.now;
|
||||
const auto finishes = now ? currentFrameFinishes() : 0;
|
||||
if (finishes && now >= finishes) {
|
||||
++_frame;
|
||||
if (_finished && _frame == _frames) {
|
||||
_frame = 0;
|
||||
}
|
||||
_shown = now;
|
||||
} else if (!_shown) {
|
||||
_shown = now;
|
||||
}
|
||||
}
|
||||
const auto index = first ? 0 : std::min(_frame, _frames - 1);
|
||||
const auto info = frame(index);
|
||||
const auto size = _size / style::DevicePixelRatio();
|
||||
const auto rect = QRect(context.position, QSize(size, size));
|
||||
PaintScaledImage(p, rect, info, context);
|
||||
const auto next = first ? 0 : currentFrameFinishes();
|
||||
return {
|
||||
.painted = true,
|
||||
.next = next,
|
||||
.duration = next ? (next - _shown) : 0,
|
||||
};
|
||||
}
|
||||
|
||||
int Cache::currentFrame() const {
|
||||
return _frame;
|
||||
}
|
||||
|
||||
crl::time Cache::currentFrameFinishes() const {
|
||||
if (!_shown || _frame >= _durations.size()) {
|
||||
return 0;
|
||||
} else if (const auto duration = _durations[_frame]) {
|
||||
return _shown + duration;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Cached::Cached(
|
||||
const QString &entityData,
|
||||
Fn<std::unique_ptr<Loader>()> unloader,
|
||||
Cache cache)
|
||||
: _unloader(std::move(unloader))
|
||||
, _cache(std::move(cache))
|
||||
, _entityData(entityData) {
|
||||
}
|
||||
|
||||
QString Cached::entityData() const {
|
||||
return _entityData;
|
||||
}
|
||||
|
||||
PaintFrameResult Cached::paint(QPainter &p, const Context &context) {
|
||||
return _cache.paintCurrentFrame(p, context);
|
||||
}
|
||||
|
||||
Preview Cached::makePreview() const {
|
||||
return _cache.makePreview();
|
||||
}
|
||||
|
||||
Loading Cached::unload() {
|
||||
return Loading(_unloader(), makePreview());
|
||||
}
|
||||
|
||||
Renderer::Renderer(RendererDescriptor &&descriptor)
|
||||
: _cache(descriptor.size)
|
||||
, _put(std::move(descriptor.put))
|
||||
, _loader(std::move(descriptor.loader)) {
|
||||
Expects(_loader != nullptr);
|
||||
|
||||
const auto size = _cache.size();
|
||||
const auto guard = base::make_weak(this);
|
||||
crl::async([=, factory = std::move(descriptor.generator)]() mutable {
|
||||
auto generator = factory();
|
||||
auto rendered = generator->renderNext(
|
||||
QImage(),
|
||||
QSize(size, size),
|
||||
Qt::KeepAspectRatio);
|
||||
if (rendered.image.isNull()) {
|
||||
return;
|
||||
}
|
||||
crl::on_main(guard, [
|
||||
=,
|
||||
frame = std::move(rendered),
|
||||
generator = std::move(generator)
|
||||
]() mutable {
|
||||
frameReady(
|
||||
std::move(generator),
|
||||
frame.duration,
|
||||
std::move(frame.image));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Renderer::~Renderer() = default;
|
||||
|
||||
void Renderer::frameReady(
|
||||
std::unique_ptr<Ui::FrameGenerator> generator,
|
||||
crl::time duration,
|
||||
QImage frame) {
|
||||
if (frame.isNull()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
if (const auto count = generator->count()) {
|
||||
if (!_cache.frames()) {
|
||||
_cache.reserve(std::max(count, kMaxFrames));
|
||||
}
|
||||
}
|
||||
const auto current = _cache.currentFrame();
|
||||
const auto total = _cache.frames();
|
||||
const auto explicitRepaint = (current == total);
|
||||
_cache.add(duration, frame);
|
||||
if (explicitRepaint && _repaint) {
|
||||
_repaint();
|
||||
}
|
||||
if (!duration || total + 1 >= kMaxFrames) {
|
||||
finish();
|
||||
} else if (current + kPreloadFrames > total) {
|
||||
renderNext(std::move(generator), std::move(frame));
|
||||
} else {
|
||||
_generator = std::move(generator);
|
||||
_storage = std::move(frame);
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::renderNext(
|
||||
std::unique_ptr<Ui::FrameGenerator> generator,
|
||||
QImage storage) {
|
||||
const auto size = _cache.size();
|
||||
const auto guard = base::make_weak(this);
|
||||
crl::async([
|
||||
=,
|
||||
storage = std::move(storage),
|
||||
generator = std::move(generator)
|
||||
]() mutable {
|
||||
auto rendered = generator->renderNext(
|
||||
std::move(storage),
|
||||
QSize(size, size),
|
||||
Qt::KeepAspectRatio);
|
||||
crl::on_main(guard, [
|
||||
=,
|
||||
frame = std::move(rendered),
|
||||
generator = std::move(generator)
|
||||
]() mutable {
|
||||
frameReady(
|
||||
std::move(generator),
|
||||
frame.duration,
|
||||
std::move(frame.image));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void Renderer::finish() {
|
||||
_finished = true;
|
||||
_cache.finish();
|
||||
if (_put) {
|
||||
_put(_cache.serialize());
|
||||
}
|
||||
}
|
||||
|
||||
PaintFrameResult Renderer::paint(QPainter &p, const Context &context) {
|
||||
const auto result = _cache.paintCurrentFrame(p, context);
|
||||
if (_generator
|
||||
&& (!result.painted
|
||||
|| _cache.currentFrame() + kPreloadFrames >= _cache.frames())) {
|
||||
renderNext(std::move(_generator), std::move(_storage));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<Cached> Renderer::ready(const QString &entityData) {
|
||||
return _finished
|
||||
? Cached{ entityData, std::move(_loader), std::move(_cache) }
|
||||
: std::optional<Cached>();
|
||||
}
|
||||
|
||||
std::unique_ptr<Loader> Renderer::cancel() {
|
||||
return _loader();
|
||||
}
|
||||
|
||||
bool Renderer::canMakePreview() const {
|
||||
return _cache.frames() > 0;
|
||||
}
|
||||
|
||||
Preview Renderer::makePreview() const {
|
||||
return _cache.makePreview();
|
||||
}
|
||||
|
||||
void Renderer::setRepaintCallback(Fn<void()> repaint) {
|
||||
_repaint = std::move(repaint);
|
||||
}
|
||||
|
||||
Cache Renderer::takeCache() {
|
||||
return std::move(_cache);
|
||||
}
|
||||
|
||||
Loading::Loading(std::unique_ptr<Loader> loader, Preview preview)
|
||||
: _loader(std::move(loader))
|
||||
, _preview(std::move(preview)) {
|
||||
}
|
||||
|
||||
QString Loading::entityData() const {
|
||||
return _loader->entityData();
|
||||
}
|
||||
|
||||
void Loading::load(Fn<void(Loader::LoadResult)> done) {
|
||||
_loader->load(crl::guard(this, [this, done = std::move(done)](
|
||||
Loader::LoadResult result) mutable {
|
||||
if (const auto caching = std::get_if<Caching>(&result)) {
|
||||
caching->preview = _preview
|
||||
? std::move(_preview)
|
||||
: _loader->preview();
|
||||
}
|
||||
done(std::move(result));
|
||||
}));
|
||||
}
|
||||
|
||||
bool Loading::loading() const {
|
||||
return _loader->loading();
|
||||
}
|
||||
|
||||
void Loading::paint(QPainter &p, const Context &context) {
|
||||
if (!_preview) {
|
||||
if (auto preview = _loader->preview()) {
|
||||
_preview = std::move(preview);
|
||||
}
|
||||
}
|
||||
_preview.paint(p, context);
|
||||
}
|
||||
|
||||
bool Loading::hasImagePreview() const {
|
||||
return _preview.isImage();
|
||||
}
|
||||
|
||||
Preview Loading::imagePreview() const {
|
||||
return _preview.isImage() ? _preview : Preview();
|
||||
}
|
||||
|
||||
void Loading::updatePreview(Preview preview) {
|
||||
if (!_preview.isImage() && preview.isImage()) {
|
||||
_preview = std::move(preview);
|
||||
} else if (!_preview) {
|
||||
if (auto loaderPreview = _loader->preview()) {
|
||||
_preview = std::move(loaderPreview);
|
||||
} else if (preview) {
|
||||
_preview = std::move(preview);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Loading::cancel() {
|
||||
_loader->cancel();
|
||||
invalidate_weak_ptrs(this);
|
||||
}
|
||||
|
||||
Instance::Instance(
|
||||
Loading loading,
|
||||
Fn<void(not_null<Instance*>, RepaintRequest)> repaintLater)
|
||||
: _state(std::move(loading))
|
||||
, _repaintLater(std::move(repaintLater)) {
|
||||
}
|
||||
|
||||
QString Instance::entityData() const {
|
||||
return v::match(_state, [](const Loading &state) {
|
||||
return state.entityData();
|
||||
}, [](const Caching &state) {
|
||||
return state.entityData;
|
||||
}, [](const Cached &state) {
|
||||
return state.entityData();
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::paint(QPainter &p, const Context &context) {
|
||||
v::match(_state, [&](Loading &state) {
|
||||
state.paint(p, context);
|
||||
load(state);
|
||||
}, [&](Caching &state) {
|
||||
auto result = state.renderer->paint(p, context);
|
||||
if (!result.painted) {
|
||||
state.preview.paint(p, context);
|
||||
} else {
|
||||
if (!state.preview.isExactImage()) {
|
||||
state.preview = state.renderer->makePreview();
|
||||
}
|
||||
if (result.next > context.now) {
|
||||
_repaintLater(this, { result.next, result.duration });
|
||||
}
|
||||
}
|
||||
if (auto cached = state.renderer->ready(state.entityData)) {
|
||||
_state = std::move(*cached);
|
||||
}
|
||||
}, [&](Cached &state) {
|
||||
const auto result = state.paint(p, context);
|
||||
if (result.next > context.now) {
|
||||
_repaintLater(this, { result.next, result.duration });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool Instance::ready() {
|
||||
return v::match(_state, [&](Loading &state) {
|
||||
if (state.hasImagePreview()) {
|
||||
return true;
|
||||
}
|
||||
load(state);
|
||||
return false;
|
||||
}, [](Caching &state) {
|
||||
return state.renderer->canMakePreview();
|
||||
}, [](Cached &state) {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::load(Loading &state) {
|
||||
state.load([=](Loader::LoadResult result) {
|
||||
if (auto caching = std::get_if<Caching>(&result)) {
|
||||
caching->renderer->setRepaintCallback([=] { repaint(); });
|
||||
_state = std::move(*caching);
|
||||
} else if (auto cached = std::get_if<Cached>(&result)) {
|
||||
_state = std::move(*cached);
|
||||
repaint();
|
||||
} else {
|
||||
Unexpected("Value in Loader::LoadResult.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool Instance::hasImagePreview() const {
|
||||
return v::match(_state, [](const Loading &state) {
|
||||
return state.hasImagePreview();
|
||||
}, [](const Caching &state) {
|
||||
return state.preview.isImage();
|
||||
}, [](const Cached &state) {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
Preview Instance::imagePreview() const {
|
||||
return v::match(_state, [](const Loading &state) {
|
||||
return state.imagePreview();
|
||||
}, [](const Caching &state) {
|
||||
return state.preview.isImage() ? state.preview : Preview();
|
||||
}, [](const Cached &state) {
|
||||
return state.makePreview();
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::updatePreview(Preview preview) {
|
||||
v::match(_state, [&](Loading &state) {
|
||||
state.updatePreview(std::move(preview));
|
||||
}, [&](Caching &state) {
|
||||
if ((!state.preview.isImage() && preview.isImage())
|
||||
|| (!state.preview && preview)) {
|
||||
state.preview = std::move(preview);
|
||||
}
|
||||
}, [](const Cached &) {});
|
||||
}
|
||||
|
||||
void Instance::repaint() {
|
||||
for (const auto &object : _usage) {
|
||||
object->repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::incrementUsage(not_null<Object*> object) {
|
||||
_usage.emplace(object);
|
||||
}
|
||||
|
||||
void Instance::decrementUsage(not_null<Object*> object) {
|
||||
_usage.remove(object);
|
||||
if (!_usage.empty()) {
|
||||
return;
|
||||
}
|
||||
v::match(_state, [](Loading &state) {
|
||||
state.cancel();
|
||||
}, [&](Caching &state) {
|
||||
_state = Loading{
|
||||
state.renderer->cancel(),
|
||||
std::move(state.preview),
|
||||
};
|
||||
}, [&](Cached &state) {
|
||||
_state = state.unload();
|
||||
});
|
||||
_repaintLater(this, RepaintRequest());
|
||||
}
|
||||
|
||||
Object::Object(not_null<Instance*> instance, Fn<void()> repaint)
|
||||
: _instance(instance)
|
||||
, _repaint(std::move(repaint)) {
|
||||
}
|
||||
|
||||
Object::~Object() {
|
||||
unload();
|
||||
}
|
||||
|
||||
QString Object::entityData() {
|
||||
return _instance->entityData();
|
||||
}
|
||||
|
||||
void Object::paint(QPainter &p, const Context &context) {
|
||||
if (!_using) {
|
||||
_using = true;
|
||||
_instance->incrementUsage(this);
|
||||
}
|
||||
_instance->paint(p, context);
|
||||
}
|
||||
|
||||
void Object::unload() {
|
||||
if (_using) {
|
||||
_using = false;
|
||||
_instance->decrementUsage(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool Object::ready() {
|
||||
if (!_using) {
|
||||
_using = true;
|
||||
_instance->incrementUsage(this);
|
||||
}
|
||||
return _instance->ready();
|
||||
}
|
||||
|
||||
void Object::repaint() {
|
||||
_repaint();
|
||||
}
|
||||
|
||||
} // namespace Ui::CustomEmoji
|
|
@ -1,271 +0,0 @@
|
|||
/*
|
||||
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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/text/text_custom_emoji.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/bytes.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
class QColor;
|
||||
class QPainter;
|
||||
|
||||
namespace Ui {
|
||||
class FrameGenerator;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::CustomEmoji {
|
||||
|
||||
using Context = Ui::Text::CustomEmoji::Context;
|
||||
|
||||
class Preview final {
|
||||
public:
|
||||
Preview() = default;
|
||||
Preview(QImage image, bool exact);
|
||||
Preview(QPainterPath path, float64 scale);
|
||||
|
||||
void paint(QPainter &p, const Context &context);
|
||||
[[nodiscard]] bool isImage() const;
|
||||
[[nodiscard]] bool isExactImage() const;
|
||||
[[nodiscard]] QImage image() const;
|
||||
|
||||
[[nodiscard]] explicit operator bool() const {
|
||||
return !v::is_null(_data);
|
||||
}
|
||||
|
||||
private:
|
||||
struct ScaledPath {
|
||||
QPainterPath path;
|
||||
float64 scale = 1.;
|
||||
};
|
||||
struct Image {
|
||||
QImage data;
|
||||
bool exact = false;
|
||||
};
|
||||
|
||||
void paintPath(
|
||||
QPainter &p,
|
||||
const Context &context,
|
||||
const ScaledPath &path);
|
||||
|
||||
std::variant<v::null_t, ScaledPath, Image> _data;
|
||||
|
||||
};
|
||||
|
||||
struct PaintFrameResult {
|
||||
bool painted = false;
|
||||
crl::time next = 0;
|
||||
crl::time duration = 0;
|
||||
};
|
||||
|
||||
class Cache final {
|
||||
public:
|
||||
Cache(int size);
|
||||
|
||||
struct Frame {
|
||||
not_null<const QImage*> image;
|
||||
QRect source;
|
||||
};
|
||||
|
||||
[[nodiscard]] static std::optional<Cache> FromSerialized(
|
||||
const QByteArray &serialized,
|
||||
int requestedSize);
|
||||
[[nodiscard]] QByteArray serialize();
|
||||
|
||||
[[nodiscard]] int size() const;
|
||||
[[nodiscard]] int frames() const;
|
||||
[[nodiscard]] Frame frame(int index) const;
|
||||
void reserve(int frames);
|
||||
void add(crl::time duration, const QImage &frame);
|
||||
void finish();
|
||||
|
||||
[[nodiscard]] Preview makePreview() const;
|
||||
|
||||
PaintFrameResult paintCurrentFrame(QPainter &p, const Context &context);
|
||||
[[nodiscard]] int currentFrame() const;
|
||||
|
||||
private:
|
||||
static constexpr auto kPerRow = 16;
|
||||
|
||||
[[nodiscard]] int frameRowByteSize() const;
|
||||
[[nodiscard]] int frameByteSize() const;
|
||||
[[nodiscard]] crl::time currentFrameFinishes() const;
|
||||
|
||||
std::vector<QImage> _images;
|
||||
std::vector<uint16> _durations;
|
||||
QImage _full;
|
||||
crl::time _shown = 0;
|
||||
int _frame = 0;
|
||||
int _size = 0;
|
||||
int _frames = 0;
|
||||
bool _finished = false;
|
||||
|
||||
};
|
||||
|
||||
class Loader;
|
||||
class Loading;
|
||||
|
||||
class Cached final {
|
||||
public:
|
||||
Cached(
|
||||
const QString &entityData,
|
||||
Fn<std::unique_ptr<Loader>()> unloader,
|
||||
Cache cache);
|
||||
|
||||
[[nodiscard]] QString entityData() const;
|
||||
[[nodiscard]] Preview makePreview() const;
|
||||
PaintFrameResult paint(QPainter &p, const Context &context);
|
||||
[[nodiscard]] Loading unload();
|
||||
|
||||
private:
|
||||
Fn<std::unique_ptr<Loader>()> _unloader;
|
||||
Cache _cache;
|
||||
QString _entityData;
|
||||
|
||||
};
|
||||
|
||||
struct RendererDescriptor {
|
||||
Fn<std::unique_ptr<Ui::FrameGenerator>()> generator;
|
||||
Fn<void(QByteArray)> put;
|
||||
Fn<std::unique_ptr<Loader>()> loader;
|
||||
int size = 0;
|
||||
};
|
||||
|
||||
class Renderer final : public base::has_weak_ptr {
|
||||
public:
|
||||
explicit Renderer(RendererDescriptor &&descriptor);
|
||||
virtual ~Renderer();
|
||||
|
||||
PaintFrameResult paint(QPainter &p, const Context &context);
|
||||
[[nodiscard]] std::optional<Cached> ready(const QString &entityData);
|
||||
[[nodiscard]] std::unique_ptr<Loader> cancel();
|
||||
|
||||
[[nodiscard]] bool canMakePreview() const;
|
||||
[[nodiscard]] Preview makePreview() const;
|
||||
|
||||
void setRepaintCallback(Fn<void()> repaint);
|
||||
[[nodiscard]] Cache takeCache();
|
||||
|
||||
private:
|
||||
void frameReady(
|
||||
std::unique_ptr<Ui::FrameGenerator> generator,
|
||||
crl::time duration,
|
||||
QImage frame);
|
||||
void renderNext(
|
||||
std::unique_ptr<Ui::FrameGenerator> generator,
|
||||
QImage storage);
|
||||
void finish();
|
||||
|
||||
Cache _cache;
|
||||
std::unique_ptr<Ui::FrameGenerator> _generator;
|
||||
QImage _storage;
|
||||
Fn<void(QByteArray)> _put;
|
||||
Fn<void()> _repaint;
|
||||
Fn<std::unique_ptr<Loader>()> _loader;
|
||||
bool _finished = false;
|
||||
|
||||
};
|
||||
|
||||
struct Caching {
|
||||
std::unique_ptr<Renderer> renderer;
|
||||
QString entityData;
|
||||
Preview preview;
|
||||
};
|
||||
|
||||
class Loader {
|
||||
public:
|
||||
using LoadResult = std::variant<Caching, Cached>;
|
||||
[[nodiscard]] virtual QString entityData() = 0;
|
||||
virtual void load(Fn<void(LoadResult)> loaded) = 0;
|
||||
[[nodiscard]] virtual bool loading() = 0;
|
||||
virtual void cancel() = 0;
|
||||
[[nodiscard]] virtual Preview preview() = 0;
|
||||
virtual ~Loader() = default;
|
||||
};
|
||||
|
||||
class Loading final : public base::has_weak_ptr {
|
||||
public:
|
||||
Loading(std::unique_ptr<Loader> loader, Preview preview);
|
||||
|
||||
[[nodiscard]] QString entityData() const;
|
||||
|
||||
void load(Fn<void(Loader::LoadResult)> done);
|
||||
[[nodiscard]] bool loading() const;
|
||||
void paint(QPainter &p, const Context &context);
|
||||
[[nodiscard]] bool hasImagePreview() const;
|
||||
[[nodiscard]] Preview imagePreview() const;
|
||||
void updatePreview(Preview preview);
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Loader> _loader;
|
||||
Preview _preview;
|
||||
|
||||
};
|
||||
|
||||
struct RepaintRequest {
|
||||
crl::time when = 0;
|
||||
crl::time duration = 0;
|
||||
};
|
||||
|
||||
class Object;
|
||||
class Instance final : public base::has_weak_ptr {
|
||||
public:
|
||||
Instance(
|
||||
Loading loading,
|
||||
Fn<void(not_null<Instance*>, RepaintRequest)> repaintLater);
|
||||
Instance(const Instance&) = delete;
|
||||
Instance &operator=(const Instance&) = delete;
|
||||
|
||||
[[nodiscard]] QString entityData() const;
|
||||
void paint(QPainter &p, const Context &context);
|
||||
[[nodiscard]] bool ready();
|
||||
[[nodiscard]] bool hasImagePreview() const;
|
||||
[[nodiscard]] Preview imagePreview() const;
|
||||
void updatePreview(Preview preview);
|
||||
|
||||
void incrementUsage(not_null<Object*> object);
|
||||
void decrementUsage(not_null<Object*> object);
|
||||
|
||||
void repaint();
|
||||
|
||||
private:
|
||||
void load(Loading &state);
|
||||
|
||||
std::variant<Loading, Caching, Cached> _state;
|
||||
base::flat_set<not_null<Object*>> _usage;
|
||||
Fn<void(not_null<Instance*> that, RepaintRequest)> _repaintLater;
|
||||
|
||||
};
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
[[nodiscard]] virtual bool paused() = 0;
|
||||
virtual ~Delegate() = default;
|
||||
};
|
||||
|
||||
class Object final : public Ui::Text::CustomEmoji {
|
||||
public:
|
||||
Object(not_null<Instance*> instance, Fn<void()> repaint);
|
||||
~Object();
|
||||
|
||||
QString entityData() override;
|
||||
void paint(QPainter &p, const Context &context) override;
|
||||
void unload() override;
|
||||
bool ready() override;
|
||||
|
||||
void repaint();
|
||||
|
||||
private:
|
||||
const not_null<Instance*> _instance;
|
||||
Fn<void()> _repaint;
|
||||
bool _using = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::CustomEmoji
|
|
@ -10,8 +10,8 @@ init_target(lib_ffmpeg)
|
|||
|
||||
nice_target_sources(lib_ffmpeg ${src_loc}
|
||||
PRIVATE
|
||||
ffmpeg/ffmpeg_emoji.cpp
|
||||
ffmpeg/ffmpeg_emoji.h
|
||||
ffmpeg/ffmpeg_frame_generator.cpp
|
||||
ffmpeg/ffmpeg_frame_generator.h
|
||||
ffmpeg/ffmpeg_utility.cpp
|
||||
ffmpeg/ffmpeg_utility.h
|
||||
)
|
||||
|
|
|
@ -242,8 +242,6 @@ PRIVATE
|
|||
ui/effects/round_checkbox.h
|
||||
ui/effects/scroll_content_shadow.cpp
|
||||
ui/effects/scroll_content_shadow.h
|
||||
ui/text/custom_emoji_instance.cpp
|
||||
ui/text/custom_emoji_instance.h
|
||||
ui/text/format_song_name.cpp
|
||||
ui/text/format_song_name.h
|
||||
ui/text/format_values.cpp
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit d4764ed8bf0ba2309847bca99f7f8b3d58db25b4
|
||||
Subproject commit 960473ef42f92a4e0f0f2ac37a2c7b225f1527fc
|
|
@ -1 +1 @@
|
|||
Subproject commit f876d15eedbce39a445b020b92f45d137952ed2a
|
||||
Subproject commit 3287bf45c607e980268090ae27dfe4fb69d037e4
|
Loading…
Add table
Reference in a new issue