Repaint skin-toned animated emoji.

This commit is contained in:
John Preston 2019-08-07 15:22:51 +01:00
parent 61c1c10ed9
commit bc83df9d7f
15 changed files with 208 additions and 42 deletions

View File

@ -1115,13 +1115,13 @@ template <typename Method>
auto LottieCachedFromContent(
Method &&method,
Storage::Cache::Key baseKey,
LottieSize sizeTag,
uint8 keyShift,
not_null<Main::Session*> session,
const QByteArray &content,
QSize box) {
const auto key = Storage::Cache::Key{
baseKey.high,
baseKey.low + int(sizeTag)
baseKey.low + keyShift
};
const auto get = [=](FnMut<void(QByteArray &&cached)> handler) {
session->data().cacheBigFile().get(
@ -1145,7 +1145,7 @@ template <typename Method>
auto LottieFromDocument(
Method &&method,
not_null<DocumentData*> document,
LottieSize sizeTag,
uint8 keyShift,
QSize box) {
const auto data = document->data();
const auto filepath = document->filepath();
@ -1159,7 +1159,7 @@ auto LottieFromDocument(
return LottieCachedFromContent(
std::forward<Method>(method),
*baseKey,
sizeTag,
keyShift,
&document->session(),
Lottie::ReadContent(data, filepath),
box);
@ -1175,11 +1175,32 @@ std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
QSize box,
Lottie::Quality quality,
std::shared_ptr<Lottie::FrameRenderer> renderer) {
return LottiePlayerFromDocument(
document,
nullptr,
sizeTag,
box,
quality,
std::move(renderer));
}
std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
not_null<DocumentData*> document,
const Lottie::ColorReplacements *replacements,
LottieSize 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, std::move(renderer));
std::forward<decltype(args)>(args)...,
quality,
replacements,
std::move(renderer));
};
return LottieFromDocument(method, document, sizeTag, box);
const auto tag = replacements ? replacements->tag : uint8(0);
const auto keyShift = ((tag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F);
return LottieFromDocument(method, document, uint8(keyShift), box);
}
not_null<Lottie::Animation*> LottieAnimationFromDocument(
@ -1190,7 +1211,7 @@ not_null<Lottie::Animation*> LottieAnimationFromDocument(
const auto method = [&](auto &&...args) {
return player->append(std::forward<decltype(args)>(args)...);
};
return LottieFromDocument(method, document, sizeTag, box);
return LottieFromDocument(method, document, uint8(sizeTag), box);
}
bool HasLottieThumbnail(
@ -1243,7 +1264,7 @@ std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
return LottieCachedFromContent(
method,
*baseKey,
sizeTag,
uint8(sizeTag),
&sticker->session(),
content,
box);

View File

@ -28,6 +28,7 @@ class MultiPlayer;
class FrameRenderer;
class Animation;
enum class Quality : char;
struct ColorReplacements;
} // namespace Lottie
namespace Stickers {
@ -140,6 +141,13 @@ enum class LottieSize : uchar {
QSize box,
Lottie::Quality quality = Lottie::Quality(),
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
not_null<DocumentData*> document,
const Lottie::ColorReplacements *replacements,
LottieSize sizeTag,
QSize box,
Lottie::Quality quality = Lottie::Quality(),
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
[[nodiscard]] not_null<Lottie::Animation*> LottieAnimationFromDocument(
not_null<Lottie::MultiPlayer*> player,
not_null<DocumentData*> document,

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_emoji_pack.h"
#include "history/history_item.h"
#include "lottie/lottie_common.h"
#include "ui/emoji_config.h"
#include "ui/text/text_isolated_emoji.h"
#include "ui/image/image_source.h"
@ -54,6 +55,64 @@ constexpr auto kClearSourceTimeout = 10 * crl::time(1000);
) * cIntRetinaFactor();
}
[[nodiscard]] const Lottie::ColorReplacements *ColorReplacements(int index) {
Expects(index >= 1 && index <= 5);
static const auto color1 = Lottie::ColorReplacements{
{
{ 0xf77e41U, 0xca907aU },
{ 0xffb139U, 0xedc5a5U },
{ 0xffd140U, 0xf7e3c3U },
{ 0xffdf79U, 0xfbefd6U },
},
1,
};
static const auto color2 = Lottie::ColorReplacements{
{
{ 0xf77e41U, 0xaa7c60U },
{ 0xffb139U, 0xc8a987U },
{ 0xffd140U, 0xddc89fU },
{ 0xffdf79U, 0xe6d6b2U },
},
2,
};
static const auto color3 = Lottie::ColorReplacements{
{
{ 0xf77e41U, 0x8c6148U },
{ 0xffb139U, 0xad8562U },
{ 0xffd140U, 0xc49e76U },
{ 0xffdf79U, 0xd4b188U },
},
3,
};
static const auto color4 = Lottie::ColorReplacements{
{
{ 0xf77e41U, 0x6e3c2cU },
{ 0xffb139U, 0x925a34U },
{ 0xffd140U, 0xa16e46U },
{ 0xffdf79U, 0xac7a52U },
},
4,
};
static const auto color5 = Lottie::ColorReplacements{
{
{ 0xf77e41U, 0x291c12U },
{ 0xffb139U, 0x472a22U },
{ 0xffd140U, 0x573b30U },
{ 0xffdf79U, 0x68493cU },
},
5,
};
static const auto list = std::array{
&color1,
&color2,
&color3,
&color4,
&color5,
};
return list[index - 1];
}
class ImageSource : public Images::Source {
public:
explicit ImageSource(
@ -388,14 +447,26 @@ void EmojiPack::remove(not_null<const HistoryItem*> item) {
}
}
DocumentData *EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) {
auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker {
Expects(!emoji.empty());
if (emoji.items[1] != nullptr) {
return nullptr;
return Sticker();
}
const auto i = _map.find(emoji.items[0]);
return (i != end(_map)) ? i->second.get() : nullptr;
const auto first = emoji.items[0];
const auto i = _map.find(first);
if (i != end(_map)) {
return { i->second.get(), nullptr };
}
if (!first->colored()) {
return Sticker();
}
const auto j = _map.find(first->original());
if (j != end(_map)) {
const auto index = first->variantIndex(first);
return { j->second.get(), details::ColorReplacements(index) };
}
return Sticker();
}
std::shared_ptr<Image> EmojiPack::image(EmojiPtr emoji) {

View File

@ -12,12 +12,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <crl/crl_object_on_queue.h>
class HistoryItem;
class DocumentData;
namespace Main {
class Session;
} // namespace Main
class HistoryItem;
class DocumentData;
namespace Lottie {
struct ColorReplacements;
} // namespace Lottie
namespace Ui {
namespace Text {
@ -37,13 +41,25 @@ using IsolatedEmoji = Ui::Text::IsolatedEmoji;
class EmojiPack final {
public:
struct Sticker {
DocumentData *document = nullptr;
const Lottie::ColorReplacements *replacements = nullptr;
[[nodiscard]] bool empty() const {
return (document == nullptr);
}
[[nodiscard]] explicit operator bool() const {
return !empty();
}
};
explicit EmojiPack(not_null<Main::Session*> session);
~EmojiPack();
bool add(not_null<HistoryItem*> item);
void remove(not_null<const HistoryItem*> item);
[[nodiscard]] DocumentData *stickerForEmoji(const IsolatedEmoji &emoji);
[[nodiscard]] Sticker stickerForEmoji(const IsolatedEmoji &emoji);
[[nodiscard]] std::shared_ptr<Image> image(EmojiPtr emoji);
private:

View File

@ -349,10 +349,13 @@ void Element::refreshMedia() {
&& session->settings().largeEmoji()) {
const auto emoji = _data->isolatedEmoji();
const auto emojiStickers = &session->emojiStickersPack();
if (const auto document = emojiStickers->stickerForEmoji(emoji)) {
if (const auto sticker = emojiStickers->stickerForEmoji(emoji)) {
_media = std::make_unique<UnwrappedMedia>(
this,
std::make_unique<Sticker>(this, document));
std::make_unique<Sticker>(
this,
sticker.document,
sticker.replacements));
} else {
_media = std::make_unique<UnwrappedMedia>(
this,

View File

@ -37,9 +37,11 @@ double GetEmojiStickerZoom(not_null<Main::Session*> session) {
Sticker::Sticker(
not_null<Element*> parent,
not_null<DocumentData*> document)
not_null<DocumentData*> document,
const Lottie::ColorReplacements *replacements)
: _parent(parent)
, _document(document) {
, _document(document)
, _replacements(replacements) {
_document->loadThumbnail(parent->data()->fullId());
}
@ -82,7 +84,7 @@ void Sticker::draw(Painter &p, const QRect &r, bool selected) {
if (_lottie && _lottie->ready()) {
paintLottie(p, r, selected);
} else {
} else if (!_lottie || !_replacements) {
paintPixmap(p, r, selected);
}
}
@ -185,8 +187,9 @@ void Sticker::refreshLink() {
void Sticker::setupLottie() {
_lottie = Stickers::LottiePlayerFromDocument(
_document,
_replacements,
Stickers::LottieSize::MessageHistory,
QSize(st::maxStickerSize, st::maxStickerSize) * cIntRetinaFactor(),
_size * cIntRetinaFactor(),
Lottie::Quality::High);
_parent->data()->history()->owner().registerHeavyViewPart(_parent);

View File

@ -16,6 +16,7 @@ struct FileOrigin;
namespace Lottie {
class SinglePlayer;
struct ColorReplacements;
} // namespace Lottie
namespace HistoryView {
@ -26,7 +27,8 @@ class Sticker final
public:
Sticker(
not_null<Element*> parent,
not_null<DocumentData*> document);
not_null<DocumentData*> document,
const Lottie::ColorReplacements *replacements = nullptr);
~Sticker();
QSize size() override;
@ -57,6 +59,7 @@ private:
const not_null<Element*> _parent;
const not_null<DocumentData*> _document;
const Lottie::ColorReplacements *_replacements = nullptr;
std::unique_ptr<Lottie::SinglePlayer> _lottie;
ClickHandlerPtr _link;
QSize _size;

View File

@ -81,11 +81,12 @@ details::InitData CheckSharedState(std::unique_ptr<SharedState> state) {
details::InitData Init(
const QByteArray &content,
const FrameRequest &request,
Quality quality) {
Quality quality,
const ColorReplacements *replacements) {
if (const auto error = ContentError(content)) {
return *error;
}
auto animation = details::CreateFromContent(content);
auto animation = details::CreateFromContent(content, replacements);
return animation
? CheckSharedState(std::make_unique<SharedState>(
std::move(animation),
@ -99,7 +100,8 @@ details::InitData Init(
FnMut<void(QByteArray &&cached)> put,
const QByteArray &cached,
const FrameRequest &request,
Quality quality) {
Quality quality,
const ColorReplacements *replacements) {
Expects(!request.empty());
if (const auto error = ContentError(content)) {
@ -108,10 +110,13 @@ details::InitData Init(
auto cache = std::make_unique<Cache>(cached, request, std::move(put));
const auto prepare = !cache->framesCount()
|| (cache->framesReady() < cache->framesCount());
auto animation = prepare ? details::CreateFromContent(content) : nullptr;
auto animation = prepare
? details::CreateFromContent(content, replacements)
: nullptr;
return (!prepare || animation)
? CheckSharedState(std::make_unique<SharedState>(
content,
replacements,
std::move(animation),
std::move(cache),
request,
@ -124,7 +129,8 @@ details::InitData Init(
namespace details {
std::unique_ptr<rlottie::Animation> CreateFromContent(
const QByteArray &content) {
const QByteArray &content,
const ColorReplacements *replacements) {
const auto string = UnpackGzip(content);
Assert(string.size() <= kMaxFileSize);
@ -132,7 +138,10 @@ std::unique_ptr<rlottie::Animation> CreateFromContent(
string,
std::string(),
std::string(),
false);
false,
(replacements
? replacements->replacements
: std::vector<std::pair<std::uint32_t, std::uint32_t>>()));
if (!result) {
LOG(("Lottie Error: Parse failed."));
}
@ -146,8 +155,8 @@ std::shared_ptr<FrameRenderer> MakeFrameRenderer() {
}
QImage ReadThumbnail(const QByteArray &content) {
return Init(content, FrameRequest(), Quality::High).match([](
const std::unique_ptr<SharedState> &state) {
return Init(content, FrameRequest(), Quality::High, nullptr).match([](
const std::unique_ptr<SharedState> &state) {
return state->frameForPaint()->original;
}, [](Error) {
return QImage();
@ -158,11 +167,13 @@ Animation::Animation(
not_null<Player*> player,
const QByteArray &content,
const FrameRequest &request,
Quality quality)
Quality quality,
const ColorReplacements *replacements)
: _player(player) {
const auto weak = base::make_weak(this);
crl::async([=] {
crl::on_main(weak, [=, data = Init(content, request, quality)]() mutable {
auto result = Init(content, request, quality, replacements);
crl::on_main(weak, [=, data = std::move(result)]() mutable {
initDone(std::move(data));
});
});
@ -174,12 +185,19 @@ Animation::Animation(
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
const QByteArray &content,
const FrameRequest &request,
Quality quality)
Quality quality,
const ColorReplacements *replacements)
: _player(player) {
const auto weak = base::make_weak(this);
get([=, put = std::move(put)](QByteArray &&cached) mutable {
crl::async([=, put = std::move(put)]() mutable {
auto result = Init(content, std::move(put), cached, request, quality);
auto result = Init(
content,
std::move(put),
cached,
request,
quality,
replacements);
crl::on_main(weak, [=, data = std::move(result)]() mutable {
initDone(std::move(data));
});

View File

@ -34,7 +34,8 @@ namespace details {
using InitData = base::variant<std::unique_ptr<SharedState>, Error>;
std::unique_ptr<rlottie::Animation> CreateFromContent(
const QByteArray &content);
const QByteArray &content,
const ColorReplacements *replacements);
} // namespace details
@ -49,14 +50,16 @@ public:
not_null<Player*> player,
const QByteArray &content,
const FrameRequest &request,
Quality quality);
Quality quality,
const ColorReplacements *replacements = nullptr);
Animation(
not_null<Player*> player,
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
const QByteArray &content,
const FrameRequest &request,
Quality quality);
Quality quality,
const ColorReplacements *replacements = nullptr);
[[nodiscard]] bool ready() const;
[[nodiscard]] QImage frame() const;

View File

@ -55,6 +55,11 @@ enum class Quality : char {
High,
};
struct ColorReplacements {
std::vector<std::pair<std::uint32_t, std::uint32_t>> replacements;
uint8 tag = 0;
};
QByteArray ReadContent(const QByteArray &data, const QString &filepath);
} // namespace Lottie

View File

@ -255,6 +255,7 @@ SharedState::SharedState(
SharedState::SharedState(
const QByteArray &content,
const ColorReplacements *replacements,
std::unique_ptr<rlottie::Animation> animation,
std::unique_ptr<Cache> cache,
const FrameRequest &request,
@ -263,7 +264,8 @@ SharedState::SharedState(
, _quality(quality)
, _cache(std::move(cache))
, _animation(std::move(animation))
, _content(content) {
, _content(content)
, _replacements(replacements) {
construct(request);
}
@ -310,7 +312,7 @@ void SharedState::renderFrame(
if (_cache && _cache->renderFrame(image, request, index)) {
return;
} else if (!_animation) {
_animation = details::CreateFromContent(_content);
_animation = details::CreateFromContent(_content, _replacements);
}
image.fill(Qt::transparent);

View File

@ -57,6 +57,7 @@ public:
Quality quality);
SharedState(
const QByteArray &content,
const ColorReplacements *replacements,
std::unique_ptr<rlottie::Animation> animation,
std::unique_ptr<Cache> cache,
const FrameRequest &request,
@ -129,6 +130,7 @@ private:
std::unique_ptr<rlottie::Animation> _animation;
const QByteArray _content;
const ColorReplacements *_replacements = nullptr;
};

View File

@ -15,8 +15,9 @@ SinglePlayer::SinglePlayer(
const QByteArray &content,
const FrameRequest &request,
Quality quality,
const ColorReplacements *replacements,
std::shared_ptr<FrameRenderer> renderer)
: _animation(this, content, request, quality)
: _animation(this, content, request, quality, replacements)
, _timer([=] { checkNextFrameRender(); })
, _renderer(renderer ? renderer : FrameRenderer::Instance()) {
}
@ -27,8 +28,16 @@ SinglePlayer::SinglePlayer(
const QByteArray &content,
const FrameRequest &request,
Quality quality,
const ColorReplacements *replacements,
std::shared_ptr<FrameRenderer> renderer)
: _animation(this, std::move(get), std::move(put), content, request, quality)
: _animation(
this,
std::move(get),
std::move(put),
content,
request,
quality,
replacements)
, _timer([=] { checkNextFrameRender(); })
, _renderer(renderer ? renderer : FrameRenderer::Instance()) {
}

View File

@ -32,6 +32,7 @@ public:
const QByteArray &content,
const FrameRequest &request,
Quality quality = Quality::Default,
const ColorReplacements *replacements = nullptr,
std::shared_ptr<FrameRenderer> renderer = nullptr);
SinglePlayer(
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
@ -39,6 +40,7 @@ public:
const QByteArray &content,
const FrameRequest &request,
Quality quality = Quality::Default,
const ColorReplacements *replacements = nullptr,
std::shared_ptr<FrameRenderer> renderer = nullptr);
~SinglePlayer();

@ -1 +1 @@
Subproject commit bd9ee5239c8caca95fee017a46c8e9198d7cdb21
Subproject commit d08a03b6508b390af20491f2dbeee3453594afc8