Support custom emoji in IsolatedEmoji.

This commit is contained in:
John Preston 2022-07-25 17:54:37 +03:00
parent 9b941bae97
commit edfb7bb65a
10 changed files with 183 additions and 62 deletions

View file

@ -150,22 +150,25 @@ auto EmojiPack::stickerForEmoji(EmojiPtr emoji) -> Sticker {
return { i->second.get(), nullptr };
}
if (!emoji->colored()) {
return Sticker();
return {};
}
const auto j = _map.find(emoji->original());
if (j != end(_map)) {
const auto index = emoji->variantIndex(emoji);
return { j->second.get(), ColorReplacements(index) };
}
return Sticker();
return {};
}
auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker {
Expects(!emoji.empty());
return (emoji.items[1] != nullptr)
? Sticker()
: stickerForEmoji(emoji.items[0]);
if (!v::is_null(emoji.items[1])) {
return {};
} else if (const auto regular = std::get_if<EmojiPtr>(&emoji.items[0])) {
return stickerForEmoji(*regular);
}
return {};
}
std::shared_ptr<LargeEmojiImage> EmojiPack::image(EmojiPtr emoji) {

View file

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_block.h"
#include "ui/ui_utility.h"
#include "apiwrap.h"
#include "styles/style_chat.h"
#include "data/stickers/data_stickers.h"
#include "ui/widgets/input_fields.h"
@ -36,7 +37,8 @@ using SizeTag = CustomEmojiManager::SizeTag;
using LottieSize = ChatHelpers::StickerLottieSize;
switch (tag) {
case SizeTag::Normal: return LottieSize::MessageHistory;
case SizeTag::Large: return LottieSize::EmojiInteraction;
case SizeTag::Large: return LottieSize::StickersPanel;
case SizeTag::Isolated: return LottieSize::EmojiInteraction;
}
Unexpected("SizeTag value in CustomEmojiManager-LottieSizeFromTag.");
}
@ -45,6 +47,9 @@ using SizeTag = CustomEmojiManager::SizeTag;
switch (tag) {
case SizeTag::Normal: return Ui::Emoji::GetSizeNormal();
case SizeTag::Large: return Ui::Emoji::GetSizeLarge();
case SizeTag::Isolated:
return (st::largeEmojiSize + 2 * st::largeEmojiOutline)
* style::DevicePixelRatio();
}
Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag.");
}
@ -396,20 +401,25 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview(
DocumentId documentId,
SizeTag tag) const {
const auto &other = _instances[1 - SizeIndex(tag)];
const auto j = other.find(documentId);
if (j == end(other)) {
return {};
} else if (const auto nonExact = j->second->imagePreview()) {
const auto size = SizeFromTag(tag);
return {
nonExact.image().scaled(
size,
size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation),
false,
};
for (auto i = _instances.size(); i != 0;) {
if (SizeIndex(tag) == --i) {
continue;
}
const auto &other = _instances[i];
const auto j = other.find(documentId);
if (j == end(other)) {
continue;
} else if (const auto nonExact = j->second->imagePreview()) {
const auto size = SizeFromTag(tag);
return {
nonExact.image().scaled(
size,
size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation),
false,
};
}
}
return {};
}
@ -535,7 +545,7 @@ void CustomEmojiManager::requestSetFor(not_null<DocumentData*> document) {
int CustomEmojiManager::SizeIndex(SizeTag tag) {
const auto result = static_cast<int>(tag);
Ensures(result >= 0 && result < 2);
Ensures(result >= 0 && result < kSizeCount);
return result;
}

View file

@ -33,6 +33,9 @@ public:
enum class SizeTag {
Normal,
Large,
Isolated,
kCount,
};
CustomEmojiManager(not_null<Session*> owner);
@ -64,6 +67,8 @@ public:
[[nodiscard]] Session &owner() const;
private:
static constexpr auto kSizeCount = int(SizeTag::kCount);
struct RepaintBunch {
crl::time when = 0;
std::vector<base::weak_ptr<Ui::CustomEmoji::Instance>> instances;
@ -91,12 +96,16 @@ private:
const not_null<Session*> _owner;
base::flat_map<
uint64,
std::unique_ptr<Ui::CustomEmoji::Instance>> _instances[2];
base::flat_map<
uint64,
std::vector<base::weak_ptr<CustomEmojiLoader>>> _loaders[2];
std::array<
base::flat_map<
uint64,
std::unique_ptr<Ui::CustomEmoji::Instance>>,
kSizeCount> _instances;
std::array<
base::flat_map<
uint64,
std::vector<base::weak_ptr<CustomEmojiLoader>>>,
kSizeCount> _loaders;
base::flat_set<uint64> _pendingForRequest;
mtpRequestId _requestId = 0;

View file

@ -1308,7 +1308,7 @@ TextWithEntities HistoryItem::inReplyText() const {
}
Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const {
return Ui::Text::IsolatedEmoji();
return {};
}
HistoryItem::~HistoryItem() {

View file

@ -419,15 +419,18 @@ void Element::customEmojiRepaint() {
}
}
void Element::clearCustomEmojiRepaint() const {
_customEmojiRepaintScheduled = false;
data()->_customEmojiRepaintScheduled = false;
}
void Element::prepareCustomEmojiPaint(
Painter &p,
const Ui::Text::String &text) const {
const auto item = data();
if (!text.hasCustomEmoji()) {
return;
}
_customEmojiRepaintScheduled = false;
item->_customEmojiRepaintScheduled = false;
clearCustomEmojiRepaint();
p.setInactive(delegate()->elementIsGifPaused());
if (!_heavyCustomEmoji) {
_heavyCustomEmoji = true;

View file

@ -431,6 +431,7 @@ public:
void prepareCustomEmojiPaint(
Painter &p,
const Ui::Text::String &text) const;
void clearCustomEmojiRepaint() const;
[[nodiscard]] ClickHandlerPtr fromPhotoLink() const {
return fromLink();

View file

@ -14,20 +14,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "ui/image/image.h"
#include "ui/chat/chat_style.h"
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "data/stickers/data_custom_emoji.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
using EmojiImage = Stickers::LargeEmojiImage;
using Stickers::LargeEmojiImage;
using ImagePtr = std::shared_ptr<Stickers::LargeEmojiImage>;
using CustomPtr = std::unique_ptr<Ui::Text::CustomEmoji>;
auto ResolveImages(
not_null<Main::Session*> session,
Fn<void()> customEmojiRepaint,
const Ui::Text::IsolatedEmoji &emoji)
-> std::array<std::shared_ptr<EmojiImage>, Ui::Text::kIsolatedEmojiLimit> {
const auto single = [&](EmojiPtr emoji) {
return emoji ? session->emojiStickersPack().image(emoji) : nullptr;
-> std::array<LargeEmojiMedia, Ui::Text::kIsolatedEmojiLimit> {
const auto single = [&](Ui::Text::IsolatedEmoji::Item item)
-> LargeEmojiMedia {
if (const auto regular = std::get_if<EmojiPtr>(&item)) {
return session->emojiStickersPack().image(*regular);
} else if (const auto custom = std::get_if<QString>(&item)) {
return session->data().customEmojiManager().create(
*custom,
customEmojiRepaint,
Data::CustomEmojiManager::SizeTag::Isolated);
}
return v::null;
};
return { {
single(emoji.items[0]),
@ -35,28 +49,25 @@ auto ResolveImages(
single(emoji.items[2]) } };
}
auto NonEmpty(const std::array<std::shared_ptr<EmojiImage>, Ui::Text::kIsolatedEmojiLimit> &images) {
using namespace rpl::mappers;
return images | ranges::views::filter(_1 != nullptr);
}
} // namespace
LargeEmoji::LargeEmoji(
not_null<Element*> parent,
const Ui::Text::IsolatedEmoji &emoji)
: _parent(parent)
, _images(ResolveImages(&parent->data()->history()->session(), emoji)) {
, _images(ResolveImages(
&parent->data()->history()->session(),
[=] { parent->customEmojiRepaint(); },
emoji)) {
}
QSize LargeEmoji::size() {
using namespace rpl::mappers;
const auto count = ranges::distance(NonEmpty(_images));
Assert(count > 0);
const auto count = _images.size()
- ranges::count(_images, LargeEmojiMedia());
const auto single = EmojiImage::Size() / cIntRetinaFactor();
const auto single = LargeEmojiImage::Size() / cIntRetinaFactor();
const auto skip = st::largeEmojiSkip - 2 * st::largeEmojiOutline;
const auto inner = count * single.width() + (count - 1) * skip;
const auto &padding = st::largeEmojiPadding;
@ -70,23 +81,91 @@ void LargeEmoji::draw(
Painter &p,
const PaintContext &context,
const QRect &r) {
auto &&images = NonEmpty(_images);
_parent->clearCustomEmojiRepaint();
const auto &padding = st::largeEmojiPadding;
auto x = r.x() + (r.width() - _size.width()) / 2 + padding.left();
const auto y = r.y() + (r.height() - _size.height()) / 2 + padding.top();
const auto skip = st::largeEmojiSkip - 2 * st::largeEmojiOutline;
const auto size = EmojiImage::Size() / cIntRetinaFactor();
for (const auto &image : images) {
if (const auto &prepared = image->image) {
const auto colored = context.selected()
? &context.st->msgStickerOverlay()
: nullptr;
p.drawPixmap(x, y, prepared->pix(size, { .colored = colored }));
} else if (image->load) {
image->load();
const auto size = LargeEmojiImage::Size() / cIntRetinaFactor();
const auto paused = _parent->delegate()->elementIsGifPaused();
const auto selected = context.selected();
if (!selected) {
_selectedFrame = QImage();
}
for (const auto &media : _images) {
if (const auto image = std::get_if<ImagePtr>(&media)) {
if (const auto &prepared = (*image)->image) {
const auto colored = selected
? &context.st->msgStickerOverlay()
: nullptr;
p.drawPixmap(
x,
y,
prepared->pix(size, { .colored = colored }));
} else if ((*image)->load) {
(*image)->load();
}
} else if (const auto custom = std::get_if<CustomPtr>(&media)) {
paintCustom(p, x, y, custom->get(), context, paused);
} else {
continue;
}
x += size.width() + skip;
}
}
void LargeEmoji::paintCustom(
QPainter &p,
int x,
int y,
not_null<Ui::Text::CustomEmoji*> emoji,
const PaintContext &context,
bool paused) {
if (!_hasHeavyPart) {
_hasHeavyPart = true;
_parent->history()->owner().registerHeavyViewPart(_parent);
}
const auto inner = st::largeEmojiSize + 2 * st::largeEmojiOutline;
const auto outer = Ui::Text::AdjustCustomEmojiSize(inner);
const auto skip = (inner - outer) / 2;
const auto preview = context.imageStyle()->msgServiceBg->c;
if (context.selected()) {
const auto factor = style::DevicePixelRatio();
const auto size = QSize(outer, outer) * factor;
if (_selectedFrame.size() != size) {
_selectedFrame = QImage(
size,
QImage::Format_ARGB32_Premultiplied);
_selectedFrame.setDevicePixelRatio(factor);
}
_selectedFrame.fill(Qt::transparent);
auto q = QPainter(&_selectedFrame);
emoji->paint(q, 0, 0, context.now, preview, paused);
q.end();
_selectedFrame = Images::Colored(
std::move(_selectedFrame),
context.st->msgStickerOverlay()->c);
p.drawImage(x + skip, y + skip, _selectedFrame);
} else {
emoji->paint(p, x + skip, y + skip, context.now, preview, paused);
}
}
bool LargeEmoji::hasHeavyPart() const {
return _hasHeavyPart;
}
void LargeEmoji::unloadHeavyPart() {
if (_hasHeavyPart) {
_hasHeavyPart = false;
for (auto &media : _images) {
if (const auto custom = std::get_if<CustomPtr>(&media)) {
(*custom)->unload();
}
}
}
}
} // namespace HistoryView

View file

@ -20,6 +20,11 @@ struct LargeEmojiImage;
namespace HistoryView {
using LargeEmojiMedia = std::variant<
v::null_t,
std::shared_ptr<Stickers::LargeEmojiImage>,
std::unique_ptr<Ui::Text::CustomEmoji>>;
class LargeEmoji final : public UnwrappedMedia::Content {
public:
LargeEmoji(
@ -39,12 +44,23 @@ public:
return true;
}
bool hasHeavyPart() const override;
void unloadHeavyPart() override;
private:
void paintCustom(
QPainter &p,
int x,
int y,
not_null<Ui::Text::CustomEmoji*> emoji,
const PaintContext &context,
bool paused);
const not_null<Element*> _parent;
const std::array<
std::shared_ptr<Stickers::LargeEmojiImage>,
Ui::Text::kIsolatedEmojiLimit> _images;
const std::array<LargeEmojiMedia, Ui::Text::kIsolatedEmojiLimit> _images;
QImage _selectedFrame;
QSize _size;
bool _hasHeavyPart = false;
};

View file

@ -19,7 +19,7 @@ namespace Ui::CustomEmoji {
namespace {
constexpr auto kMaxSize = 128;
constexpr auto kMaxFrames = 512;
constexpr auto kMaxFrames = 180;
constexpr auto kMaxFrameDuration = 86400 * crl::time(1000);
constexpr auto kCacheVersion = 1;
constexpr auto kPreloadFrames = 3;
@ -415,7 +415,7 @@ void Renderer::frameReady(
}
if (const auto count = generator->count()) {
if (!_cache.frames()) {
_cache.reserve(count);
_cache.reserve(std::max(count, kMaxFrames));
}
}
const auto current = _cache.currentFrame();
@ -425,7 +425,7 @@ void Renderer::frameReady(
if (explicitRepaint && _repaint) {
_repaint();
}
if (!duration) {
if (!duration || total + 1 >= kMaxFrames) {
finish();
} else if (current + kPreloadFrames > total) {
renderNext(std::move(generator), std::move(frame));

@ -1 +1 @@
Subproject commit c5b32c53efbc1481f3942d28ef7e4f5eaca5df1f
Subproject commit a5d7b23a638e6c2d9bffa97bf40d5d7559a926c8