Use outlined large emoji.

This commit is contained in:
John Preston 2019-08-05 12:44:40 +01:00
parent 51d350c356
commit e479daca03
4 changed files with 99 additions and 59 deletions

View File

@ -28,7 +28,7 @@ public:
crl::weak_on_queue<EmojiImageLoader> weak,
int id);
[[nodiscard]] QImage prepare(const IsolatedEmoji &emoji);
[[nodiscard]] QImage prepare(EmojiPtr emoji);
void switchTo(int id);
private:
@ -44,16 +44,11 @@ namespace {
constexpr auto kRefreshTimeout = TimeId(7200);
constexpr auto kUnloadTimeout = 86400 * crl::time(1000);
[[nodiscard]] QSize CalculateSize(const IsolatedEmoji &emoji) {
using namespace rpl::mappers;
[[nodiscard]] QSize SingleSize() {
const auto single = st::largeEmojiSize;
const auto skip = st::largeEmojiSkip;
const auto outline = st::largeEmojiOutline;
const auto count = ranges::count_if(emoji.items, _1 != nullptr);
const auto items = single * count + skip * (count - 1);
return QSize(
2 * outline + items,
2 * outline + single,
2 * outline + single
) * cIntRetinaFactor();
}
@ -61,7 +56,7 @@ constexpr auto kUnloadTimeout = 86400 * crl::time(1000);
class ImageSource : public Images::Source {
public:
explicit ImageSource(
const IsolatedEmoji &emoji,
EmojiPtr emoji,
not_null<crl::object_on_queue<EmojiImageLoader>*> loader);
void load(Data::FileOrigin origin) override;
@ -100,7 +95,7 @@ private:
// While HistoryView::Element-s are almost never destroyed
// we make loading of the image lazy.
not_null<crl::object_on_queue<EmojiImageLoader>*> _loader;
IsolatedEmoji _emoji;
EmojiPtr _emoji = nullptr;
QImage _data;
QByteArray _format;
QByteArray _bytes;
@ -110,11 +105,11 @@ private:
};
ImageSource::ImageSource(
const IsolatedEmoji &emoji,
EmojiPtr emoji,
not_null<crl::object_on_queue<EmojiImageLoader>*> loader)
: _loader(loader)
, _emoji(emoji)
, _size(CalculateSize(emoji)) {
, _size(SingleSize()) {
}
void ImageSource::load(Data::FileOrigin origin) {
@ -256,31 +251,54 @@ EmojiImageLoader::EmojiImageLoader(
, _unloadTimer(_weak.runner(), [=] { _images->clear(); }) {
}
QImage EmojiImageLoader::prepare(const IsolatedEmoji &emoji) {
QImage EmojiImageLoader::prepare(EmojiPtr emoji) {
Expects(_images.has_value());
_images->ensureLoaded();
auto result = QImage(
CalculateSize(emoji),
QImage::Format_ARGB32_Premultiplied);
const auto factor = cIntRetinaFactor();
const auto side = st::largeEmojiSize + 2 * st::largeEmojiOutline;
auto tinted = QImage(
QSize(st::largeEmojiSize, st::largeEmojiSize) * factor,
QImage::Format_ARGB32_Premultiplied);
tinted.fill(Qt::white);
{
QPainter p(&tinted);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
_images->draw(
p,
emoji,
st::largeEmojiSize * factor,
0,
0);
}
auto result = QImage(
QSize(side, side) * factor,
QImage::Format_ARGB32_Premultiplied);
result.fill(Qt::transparent);
{
QPainter p(&result);
auto x = st::largeEmojiOutline;
const auto y = st::largeEmojiOutline;
for (const auto &single : emoji.items) {
if (!single) {
break;
const auto delta = st::largeEmojiOutline * factor;
const auto shifts = std::array<QPoint, 8>{ {
{ -1, -1 },
{ 0, -1 },
{ 1, -1 },
{ -1, 0 },
{ 1, 0 },
{ -1, 1 },
{ 0, 1 },
{ 1, 1 },
} };
for (const auto &shift : shifts) {
for (auto i = 0; i != delta; ++i) {
p.drawImage(QPoint(delta, delta) + shift * (i + 1), tinted);
}
_images->draw(
p,
single,
st::largeEmojiSize * factor,
x * factor,
y * factor);
x += st::largeEmojiSize + st::largeEmojiSkip;
}
_images->draw(
p,
emoji,
st::largeEmojiSize * factor,
delta,
delta);
}
_unloadTimer.callOnce(kUnloadTimeout);
return result;
@ -356,7 +374,7 @@ DocumentData *EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) {
return (i != end(_map)) ? i->second.get() : nullptr;
}
std::shared_ptr<Image> EmojiPack::image(const IsolatedEmoji &emoji) {
std::shared_ptr<Image> EmojiPack::image(EmojiPtr emoji) {
const auto i = _images.emplace(emoji, std::weak_ptr<Image>()).first;
if (const auto result = i->second.lock()) {
return result;

View File

@ -40,7 +40,7 @@ public:
void remove(not_null<const HistoryItem*> item);
[[nodiscard]] DocumentData *stickerForEmoji(const IsolatedEmoji &emoji);
[[nodiscard]] std::shared_ptr<Image> image(const IsolatedEmoji &emoji);
[[nodiscard]] std::shared_ptr<Image> image(EmojiPtr emoji);
private:
class ImageLoader;
@ -62,7 +62,7 @@ private:
base::flat_map<
IsolatedEmoji,
base::flat_set<not_null<HistoryItem*>>> _items;
base::flat_map<IsolatedEmoji, std::weak_ptr<Image>> _images;
base::flat_map<EmojiPtr, std::weak_ptr<Image>> _images;
mtpRequestId _requestId = 0;
crl::object_on_queue<details::EmojiImageLoader> _imageLoader;

View File

@ -20,49 +20,70 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView {
namespace {
std::shared_ptr<Image> ResolveImage(
not_null<Main::Session*> session,
const Ui::Text::IsolatedEmoji &emoji) {
return session->emojiStickersPack().image(emoji);
auto ResolveImages(
not_null<Main::Session*> session,
const Ui::Text::IsolatedEmoji &emoji)
-> std::array<std::shared_ptr<Image>, Ui::Text::kIsolatedEmojiLimit> {
const auto single = [&](EmojiPtr emoji) {
return emoji ? session->emojiStickersPack().image(emoji) : nullptr;
};
return { {
single(emoji.items[0]),
single(emoji.items[1]),
single(emoji.items[2]) } };
}
auto NonEmpty(const std::array<std::shared_ptr<Image>, Ui::Text::kIsolatedEmojiLimit> &images) {
using namespace rpl::mappers;
return images | ranges::view::filter(_1 != nullptr);
}
} // namespace
LargeEmoji::LargeEmoji(
not_null<Element*> parent,
Ui::Text::IsolatedEmoji emoji)
const Ui::Text::IsolatedEmoji &emoji)
: _parent(parent)
, _emoji(emoji)
, _image(ResolveImage(&parent->data()->history()->session(), emoji)) {
, _images(ResolveImages(&parent->data()->history()->session(), emoji)) {
}
QSize LargeEmoji::size() {
const auto size = _image->size() / cIntRetinaFactor();
using namespace rpl::mappers;
const auto count = ranges::distance(NonEmpty(_images));
Assert(count > 0);
const auto single = _images[0]->size() / cIntRetinaFactor();
const auto skip = st::largeEmojiSkip - 2 * st::largeEmojiOutline;
const auto inner = count * single.width() + (count - 1) * skip;
const auto &padding = st::largeEmojiPadding;
_size = QSize(
padding.left() + size.width() + padding.right(),
padding.top() + size.height() + padding.bottom());
padding.left() + inner + padding.right(),
padding.top() + single.height() + padding.bottom());
return _size;
}
void LargeEmoji::draw(Painter &p, const QRect &r, bool selected) {
_image->load(Data::FileOrigin());
if (!_image->loaded()) {
return;
}
auto &&images = NonEmpty(_images);
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 o = Data::FileOrigin();
const auto w = _size.width() - padding.left() - padding.right();
const auto h = _size.height() - padding.top() - padding.bottom();
const auto &c = st::msgStickerOverlay;
const auto pixmap = selected
? _image->pixColored(o, c, w, h)
: _image->pix(o, w, h);
p.drawPixmap(
QPoint(
r.x() + (r.width() - _size.width()) / 2,
r.y() + (r.height() - _size.height()) / 2),
pixmap);
const auto skip = st::largeEmojiSkip - 2 * st::largeEmojiOutline;
for (const auto &image : images) {
image->load(Data::FileOrigin());
const auto w = image->width() / cIntRetinaFactor();
if (image->loaded()) {
const auto h = image->height() / cIntRetinaFactor();
const auto &c = st::msgStickerOverlay;
const auto pixmap = selected
? image->pixColored(o, c, w, h)
: image->pix(o, w, h);
p.drawPixmap(x, y, pixmap);
}
x += w + skip;
}
}
} // namespace HistoryView

View File

@ -24,15 +24,16 @@ class LargeEmoji final : public UnwrappedMedia::Content {
public:
LargeEmoji(
not_null<Element*> parent,
Ui::Text::IsolatedEmoji emoji);
const Ui::Text::IsolatedEmoji &emoji);
QSize size() override;
void draw(Painter &p, const QRect &r, bool selected) override;
private:
const not_null<Element*> _parent;
const Ui::Text::IsolatedEmoji _emoji;
std::shared_ptr<Image> _image;
const std::array<
std::shared_ptr<Image>,
Ui::Text::kIsolatedEmojiLimit> _images;
QSize _size;
};