Use single emoji sprite and scale + cache it.

This commit is contained in:
John Preston 2018-10-13 20:35:30 +03:00
parent 59a97ffb99
commit b847c8424a
39 changed files with 549 additions and 179 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

View File

@ -1,8 +0,0 @@
<RCC>
<qresource prefix="/gui">
<file alias="art/emoji.webp">../art/emoji.webp</file>
<file alias="art/emoji_125x.webp">../art/emoji_125x.webp</file>
<file alias="art/emoji_150x.webp">../art/emoji_150x.webp</file>
<file alias="art/emoji_200x.webp">../art/emoji_200x.webp</file>
</qresource>
</RCC>

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/gui">
<file alias="emoji/emoji_1.webp">../emoji/emoji_1.webp</file>
</qresource>
</RCC>

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/gui">
<file alias="emoji/emoji_2.webp">../emoji/emoji_2.webp</file>
</qresource>
</RCC>

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/gui">
<file alias="emoji/emoji_3.webp">../emoji/emoji_3.webp</file>
</qresource>
</RCC>

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/gui">
<file alias="emoji/emoji_4.webp">../emoji/emoji_4.webp</file>
</qresource>
</RCC>

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/gui">
<file alias="emoji/emoji_5.webp">../emoji/emoji_5.webp</file>
</qresource>
</RCC>

View File

@ -1,5 +0,0 @@
<RCC>
<qresource prefix="/gui">
<file alias="art/emoji_250x.webp">../art/emoji_250x.webp</file>
</qresource>
</RCC>

View File

@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/message_field.h"
#include "chat_helpers/stickers.h"
#include "ui/text_options.h"
#include "ui/emoji_config.h"
#include "storage/localimageloader.h"
#include "storage/file_download.h"
#include "storage/file_upload.h"

View File

@ -77,7 +77,6 @@ namespace {
*pressedLinkItem = nullptr,
*mousedItem = nullptr;
QPixmap *emoji = nullptr, *emojiLarge = nullptr;
style::font monofont;
struct CornersPixmaps {
@ -88,10 +87,6 @@ namespace {
CornersMap cornersMap;
QImage cornersMaskLarge[4], cornersMaskSmall[4];
using EmojiImagesMap = QMap<int, QPixmap>;
EmojiImagesMap MainEmojiMap;
QMap<int, EmojiImagesMap> OtherEmojiMap;
int32 serviceImageCacheSize = 0;
} // namespace
@ -1440,15 +1435,6 @@ namespace App {
if (family.isEmpty()) family = QFontDatabase::systemFont(QFontDatabase::FixedFont).family();
::monofont = style::font(st::normalFont->f.pixelSize(), 0, family);
}
Ui::Emoji::Init();
if (!::emoji) {
::emoji = new QPixmap(Ui::Emoji::Filename(Ui::Emoji::Index()));
if (cRetina()) ::emoji->setDevicePixelRatio(cRetinaFactor());
}
if (!::emojiLarge) {
::emojiLarge = new QPixmap(Ui::Emoji::Filename(Ui::Emoji::Index() + 1));
if (cRetina()) ::emojiLarge->setDevicePixelRatio(cRetinaFactor());
}
createCorners();
@ -1491,16 +1477,8 @@ namespace App {
}
void deinitMedia() {
delete ::emoji;
::emoji = nullptr;
delete ::emojiLarge;
::emojiLarge = nullptr;
clearCorners();
MainEmojiMap.clear();
OtherEmojiMap.clear();
Data::clearGlobalStructures();
clearAllImages();
@ -1558,30 +1536,6 @@ namespace App {
return ::monofont;
}
const QPixmap &emoji() {
return *::emoji;
}
const QPixmap &emojiLarge() {
return *::emojiLarge;
}
const QPixmap &emojiSingle(EmojiPtr emoji, int32 fontHeight) {
auto &map = (fontHeight == st::msgFont->height) ? MainEmojiMap : OtherEmojiMap[fontHeight];
auto i = map.constFind(emoji->index());
if (i == map.cend()) {
auto image = QImage(Ui::Emoji::Size() + st::emojiPadding * cIntRetinaFactor() * 2, fontHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
if (cRetina()) image.setDevicePixelRatio(cRetinaFactor());
image.fill(Qt::transparent);
{
QPainter p(&image);
emojiDraw(p, emoji, st::emojiPadding * cIntRetinaFactor(), (fontHeight * cIntRetinaFactor() - Ui::Emoji::Size()) / 2);
}
i = map.insert(emoji->index(), App::pixmapFromImageInPlace(std::move(image)));
}
return i.value();
}
void checkImageCacheSize() {
int64 nowImageCacheSize = imageCacheSize();
if (nowImageCacheSize > serviceImageCacheSize + MemoryForImageCache) {

View File

@ -186,9 +186,6 @@ namespace App {
void clearMousedItems();
const style::font &monofont();
const QPixmap &emoji();
const QPixmap &emojiLarge();
const QPixmap &emojiSingle(EmojiPtr emoji, int32 fontHeight);
void clearHistories();

View File

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat_helpers.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
#include "ui/emoji_config.h"
#include "auth_session.h"
#include "messenger.h"

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_emoji_fingerprint.h"
#include "calls/calls_call.h"
#include "ui/emoji_config.h"
namespace Calls {
namespace {

View File

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/ripple_animation.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/empty_userpic.h"
#include "ui/emoji_config.h"
#include "messenger.h"
#include "mainwindow.h"
#include "lang/lang_keys.h"
@ -725,12 +726,12 @@ void Panel::paintEvent(QPaintEvent *e) {
if (!_fingerprint.empty()) {
App::roundRect(p, _fingerprintArea, st::callFingerprintBg, ImageRoundRadius::Small);
auto realSize = Ui::Emoji::Size(Ui::Emoji::Index() + 1);
auto size = realSize / cIntRetinaFactor();
const auto realSize = Ui::Emoji::GetSizeLarge();
const auto size = realSize / cIntRetinaFactor();
auto left = _fingerprintArea.left() + st::callFingerprintPadding.left();
auto top = _fingerprintArea.top() + st::callFingerprintPadding.top();
for (auto emoji : _fingerprint) {
p.drawPixmap(QPoint(left, top), App::emojiLarge(), QRect(emoji->x() * realSize, emoji->y() * realSize, realSize, realSize));
const auto top = _fingerprintArea.top() + st::callFingerprintPadding.top();
for (const auto emoji : _fingerprint) {
Ui::Emoji::Draw(p, emoji, realSize, left, top);
left += st::callFingerprintSkip + size;
}
}
@ -868,7 +869,7 @@ void Panel::fillFingerprint() {
Expects(_call != nullptr);
_fingerprint = ComputeEmojiFingerprint(_call);
auto realSize = Ui::Emoji::Size(Ui::Emoji::Index() + 1);
auto realSize = Ui::Emoji::GetSizeLarge();
auto size = realSize / cIntRetinaFactor();
auto count = _fingerprint.size();
auto rectWidth = count * size + (count - 1) * st::callFingerprintSkip;

View File

@ -8,12 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/emoji_list_widget.h"
#include "ui/widgets/buttons.h"
#include "styles/style_chat_helpers.h"
#include "ui/widgets/shadow.h"
#include "ui/emoji_config.h"
#include "lang/lang_keys.h"
#include "emoji_suggestions_data.h"
#include "emoji_suggestions_helper.h"
#include "facades.h"
#include "styles/style_chat_helpers.h"
namespace ChatHelpers {
@ -319,18 +320,26 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) {
if (rtl()) tl.setX(width() - tl.x() - _singleSize.width());
App::roundRect(p, QRect(tl, _singleSize), st::emojiPanHover, StickerHoverCorners);
}
auto esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1);
p.drawPixmapLeft(w.x() + (_singleSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (_singleSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_variants[variant]->x() * esize, _variants[variant]->y() * esize, esize, esize));
const auto esize = Ui::Emoji::GetSizeLarge();
Ui::Emoji::Draw(
p,
_variants[variant],
esize,
w.x() + (_singleSize.width() - (esize / cIntRetinaFactor())) / 2,
w.y() + (_singleSize.height() - (esize / cIntRetinaFactor())) / 2);
}
EmojiListWidget::EmojiListWidget(QWidget *parent, not_null<Window::Controller*> controller) : Inner(parent, controller)
EmojiListWidget::EmojiListWidget(
QWidget *parent,
not_null<Window::Controller*> controller)
: Inner(parent, controller)
, _picker(this) {
setMouseTracking(true);
setAttribute(Qt::WA_OpaquePaintEvent);
_picker->hide();
_esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1);
_esize = Ui::Emoji::GetSizeLarge();
for (auto i = 0; i != kEmojiSectionCount; ++i) {
_counts[i] = Ui::Emoji::GetSectionCount(static_cast<Section>(i));
@ -482,10 +491,12 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
if (rtl()) tl.setX(width() - tl.x() - _singleSize.width());
App::roundRect(p, QRect(tl, _singleSize), st::emojiPanHover, StickerHoverCorners);
}
auto sourceRect = QRect(_emoji[info.section][index]->x() * _esize, _emoji[info.section][index]->y() * _esize, _esize, _esize);
auto imageLeft = w.x() + (_singleSize.width() - (_esize / cIntRetinaFactor())) / 2;
auto imageTop = w.y() + (_singleSize.height() - (_esize / cIntRetinaFactor())) / 2;
p.drawPixmapLeft(imageLeft, imageTop, width(), App::emojiLarge(), sourceRect);
Ui::Emoji::Draw(
p,
_emoji[info.section][index],
_esize,
w.x() + (_singleSize.width() - (_esize / cIntRetinaFactor())) / 2,
w.y() + (_singleSize.height() - (_esize / cIntRetinaFactor())) / 2);
}
}
}

View File

@ -10,6 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/tabbed_selector.h"
#include "ui/widgets/tooltip.h"
namespace Ui {
namespace Emoji {
enum class Section;
} // namespace Emoji
} // namespace Ui
namespace Window {
class Controller;
} // namespace Window

View File

@ -10,9 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/emoji_suggestions_helper.h"
#include "ui/effects/ripple_animation.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/inner_dropdown.h"
#include "ui/emoji_config.h"
#include "platform/platform_specific.h"
#include "styles/style_chat_helpers.h"
#include "ui/widgets/inner_dropdown.h"
namespace Ui {
namespace Emoji {
@ -176,24 +177,29 @@ void SuggestionsWidget::paintEvent(QPaintEvent *e) {
if (clip.intersects(topskip)) p.fillRect(clip.intersected(topskip), _st->itemBg);
if (clip.intersects(bottomskip)) p.fillRect(clip.intersected(bottomskip), _st->itemBg);
auto top = _st->skip;
const auto top = _st->skip;
p.setFont(_st->itemFont);
auto from = floorclamp(clip.top() - top, _rowHeight, 0, _rows.size());
auto to = ceilclamp(clip.top() + clip.height() - top, _rowHeight, 0, _rows.size());
const auto from = floorclamp(clip.top() - top, _rowHeight, 0, _rows.size());
const auto to = ceilclamp(clip.top() + clip.height() - top, _rowHeight, 0, _rows.size());
p.translate(0, top + from * _rowHeight);
for (auto i = from; i != to; ++i) {
auto &row = _rows[i];
auto selected = (i == _selected || i == _pressed);
const auto selected = (i == _selected || i == _pressed);
p.fillRect(0, 0, width(), _rowHeight, selected ? _st->itemBgOver : _st->itemBg);
if (auto ripple = row.ripple()) {
if (const auto ripple = row.ripple()) {
ripple->paint(p, 0, 0, width(), ms);
if (ripple->empty()) {
row.resetRipple();
}
}
auto emoji = row.emoji();
auto esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1);
p.drawPixmapLeft((_st->itemPadding.left() - (esize / cIntRetinaFactor())) / 2, (_rowHeight - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(emoji->x() * esize, emoji->y() * esize, esize, esize));
const auto emoji = row.emoji();
const auto esize = Ui::Emoji::GetSizeLarge();
Ui::Emoji::Draw(
p,
emoji,
esize,
(_st->itemPadding.left() - (esize / cIntRetinaFactor())) / 2,
(_rowHeight - (esize / cIntRetinaFactor())) / 2);
p.setPen(selected ? _st->itemFgOver : _st->itemFg);
p.drawTextLeft(_st->itemPadding.left(), _st->itemPadding.top(), width(), row.label());
p.translate(0, _rowHeight);

View File

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "auth_session.h"
#include "mainwindow.h"
#include "ui/toast/toast.h"
#include "ui/emoji_config.h"
#include "styles/style_chat_helpers.h"
namespace Stickers {

View File

@ -321,7 +321,7 @@ bool Generator::writeImages() {
bool Generator::writeSource() {
source_ = std::make_unique<common::CppFile>(outputPath_ + ".cpp", project_);
source_->include("emoji_suggestions_data.h").newline();
source_->include("emoji_suggestions_data.h").include("ui/emoji_config.h").newline();
source_->pushNamespace("Ui").pushNamespace("Emoji").pushNamespace();
source_->stream() << "\
\n\
@ -342,6 +342,10 @@ std::vector<One> Items;\n\
source_->popNamespace().newline().pushNamespace("internal");
source_->stream() << "\
\n\
int FullCount() {\n\
return Items.size();\n\
}\n\
\n\
EmojiPtr ByIndex(int index) {\n\
return (index >= 0 && index < Items.size()) ? &Items[index] : nullptr;\n\
}\n\
@ -370,7 +374,13 @@ void Init() {\n\
\n\
Items.reserve(base::array_size(Data));\n\
for (auto &data : Data) {\n\
Items.emplace_back(takeString(data.idSize), uint16(data.column), uint16(data.row), bool(data.postfixed), bool(data.variated), data.original ? &Items[data.original - 1] : nullptr, One::CreationTag());\n\
Items.emplace_back(\n\
takeString(data.idSize),\n\
data.original ? &Items[data.original - 1] : nullptr,\n\
uint32(Items.size()),\n\
data.postfixed ? true : false,\n\
data.variated ? true : false,\n\
One::CreationTag());\n\
}\n\
InitReplacements();\n\
}\n\
@ -391,6 +401,7 @@ bool Generator::writeHeader() {
\n\
void Init();\n\
\n\
int FullCount();\n\
EmojiPtr ByIndex(int index);\n\
\n\
EmojiPtr Find(const QChar *ch, const QChar *end, int *outLength = nullptr);\n\
@ -414,6 +425,8 @@ EmojiPtr FindReplace(const QChar *ch, const QChar *end, int *outLength = nullptr
\n";
header->popNamespace().stream() << "\
\n\
constexpr auto kPostfix = static_cast<ushort>(0xFE0F);\n\
\n\
enum class Section {\n\
Recent,\n\
People,\n\
@ -425,8 +438,6 @@ enum class Section {\n\
Symbols,\n\
};\n\
\n\
int Index();\n\
\n\
int GetSectionCount(Section section);\n\
EmojiPack GetSection(Section section);\n\
\n";
@ -435,13 +446,11 @@ EmojiPack GetSection(Section section);\n\
template <typename Callback>
bool Generator::enumerateWholeList(Callback callback) {
auto column = 0;
auto row = 0;
auto index = 0;
auto variated = -1;
auto coloredCount = 0;
for (auto &item : data_.list) {
if (!callback(item.id, column, row, item.postfixed, item.variated, item.colored, variated)) {
if (!callback(item.id, item.postfixed, item.variated, item.colored, variated)) {
return false;
}
if (coloredCount > 0 && (item.variated || !item.colored)) {
@ -464,10 +473,6 @@ bool Generator::enumerateWholeList(Callback callback) {
} else if (variated >= 0) {
variated = -1;
}
if (++column == kEmojiInRow) {
column = 0;
++row;
}
++index;
}
return true;
@ -476,17 +481,15 @@ bool Generator::enumerateWholeList(Callback callback) {
bool Generator::writeInitCode() {
source_->stream() << "\
struct DataStruct {\n\
ushort original : " << kOriginalBits << ";\n\
uchar idSize : " << kIdSizeBits << ";\n\
uchar column : " << kColumnBits << ";\n\
uchar row : " << kRowBits << ";\n\
bool postfixed : 1;\n\
bool variated : 1;\n\
uint32 original : " << kOriginalBits << ";\n\
uint32 idSize : " << kIdSizeBits << ";\n\
uint32 postfixed : 1;\n\
uint32 variated : 1;\n\
};\n\
\n\
const ushort IdData[] = {";
startBinary();
if (!enumerateWholeList([this](Id id, int column, int row, bool isPostfixed, bool isVariated, bool isColored, int original) {
if (!enumerateWholeList([this](Id id, bool isPostfixed, bool isVariated, bool isColored, int original) {
return writeStringBinary(source_.get(), id);
})) {
return false;
@ -498,7 +501,7 @@ const ushort IdData[] = {";
source_->stream() << " };\n\
\n\
const DataStruct Data[] = {\n";
if (!enumerateWholeList([this](Id id, int column, int row, bool isPostfixed, bool isVariated, bool isColored, int original) {
if (!enumerateWholeList([this](Id id, bool isPostfixed, bool isVariated, bool isColored, int original) {
if (original + 1 >= (1 << kOriginalBits)) {
logDataError() << "Too many entries.";
return false;
@ -507,12 +510,8 @@ const DataStruct Data[] = {\n";
logDataError() << "Too large id.";
return false;
}
if (column >= (1 << kColumnBits) || row >= (1 << kRowBits)) {
logDataError() << "Bad row-column.";
return false;
}
source_->stream() << "\
{ ushort(" << (isColored ? (original + 1) : 0) << "), uchar(" << id.size() << "), uchar(" << column << "), uchar(" << row << "), " << (isPostfixed ? "true" : "false") << ", " << (isVariated ? "true" : "false") << " },\n";
{ uint32(" << (isColored ? (original + 1) : 0) << "), uint32(" << id.size() << "), uint32(" << (isPostfixed ? "1" : "0") << "), uint32(" << (isVariated ? "1" : "0") << ") },\n";
return true;
})) {
return false;

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_location_manager.h"
#include "history/view/history_view_element.h"
#include "ui/text_options.h"
#include "ui/emoji_config.h"
#include "storage/storage_shared_media.h"
#include "storage/localstorage.h"
#include "data/data_session.h"

View File

@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/empty_userpic.h"
#include "ui/grouped_layout.h"
#include "ui/text_options.h"
#include "ui/emoji_config.h"
#include "data/data_session.h"
#include "data/data_media_types.h"

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/file_utilities.h"
#include "ui/toast/toast.h"
#include "ui/special_buttons.h"
#include "ui/emoji_config.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/inner_dropdown.h"
#include "ui/widgets/dropdown_menu.h"

View File

@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/focus_persister.h"
#include "ui/resize_area.h"
#include "ui/text_options.h"
#include "ui/emoji_config.h"
#include "ui/toast/toast.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/stickers.h"

View File

@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_location_manager.h"
#include "ui/widgets/tooltip.h"
#include "ui/text_options.h"
#include "ui/emoji_config.h"
#include "storage/serialize_common.h"
#include "window/window_controller.h"
#include "base/qthelp_regex.h"
@ -118,6 +119,7 @@ Messenger::Messenger(not_null<Core::Launcher*> launcher)
style::startManager();
anim::startManager();
Ui::InitTextOptions();
Ui::Emoji::Init();
Media::Player::start();
DEBUG_LOG(("Application Info: inited..."));
@ -1025,6 +1027,8 @@ Messenger::~Messenger() {
Shortcuts::finish();
Ui::Emoji::Clear();
anim::stopManager();
stopWebLoadManager();

View File

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/send_files_box.h"
#include "window/themes/window_theme.h"
#include "ui/widgets/input_fields.h"
#include "ui/emoji_config.h"
#include "export/export_settings.h"
#include "core/crash_reports.h"
#include "core/update_checker.h"

View File

@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "emoji_config.h"
#include "chat_helpers/emoji_suggestions_helper.h"
#include "base/bytes.h"
#include "base/openssl_help.h"
#include "auth_session.h"
namespace Ui {
@ -18,8 +20,210 @@ namespace Emoji {
namespace {
constexpr auto kSaveRecentEmojiTimeout = 3000;
constexpr auto kUniversalSize = 72;
constexpr auto kImagesPerRow = 32;
constexpr auto kImageRowsPerSprite = 16;
auto WorkingIndex = -1;
constexpr auto kVersion = 1;
class UniversalImages {
public:
void ensureLoaded();
void clear();
void draw(QPainter &p, EmojiPtr emoji, int size, int x, int y) const;
QImage generate(int size, int index) const;
private:
std::vector<QImage> _sprites;
};
auto Scale = -1.;
auto SizeNormal = -1;
auto SizeLarge = -1;
auto SpritesCount = -1;
std::unique_ptr<Instance> InstanceNormal;
std::unique_ptr<Instance> InstanceLarge;
UniversalImages Universal;
std::map<int, QPixmap> MainEmojiMap;
std::map<int, std::map<int, QPixmap>> OtherEmojiMap;
int RowsCount(int index) {
if (index + 1 < SpritesCount) {
return kImageRowsPerSprite;
}
const auto count = internal::FullCount()
- (index * kImagesPerRow * kImageRowsPerSprite);
return (count / kImagesPerRow)
+ ((count % kImagesPerRow) ? 1 : 0);
}
QString CacheFileFolder() {
return cWorkingDir() + "tdata/emoji";
}
QString CacheFilePath(int size, int index) {
return CacheFileFolder()
+ "/cache_"
+ QString::number(size)
+ '_'
+ QString::number(index);
}
void SaveToFile(const QImage &image, int size, int index) {
Expects(image.bytesPerLine() == image.width() * 4);
QFile f(CacheFilePath(size, index));
if (!f.open(QIODevice::WriteOnly)) {
if (!QDir::current().mkpath(CacheFileFolder())
|| !f.open(QIODevice::WriteOnly)) {
LOG(("App Error: Could not open emoji cache '%1' for size %2_%3"
).arg(f.fileName()
).arg(size
).arg(index));
return;
}
}
const auto write = [&](bytes::const_span data) {
return f.write(
reinterpret_cast<const char*>(data.data()),
data.size()
) == data.size();
};
const uint32 header[] = {
uint32(kVersion),
uint32(size),
uint32(image.width()),
uint32(image.height()),
};
const auto data = bytes::const_span(
reinterpret_cast<const bytes::type*>(image.bits()),
image.width() * image.height() * 4);
if (!write(bytes::make_span(header))
|| !write(data)
|| !write(openssl::Sha256(bytes::make_span(header), data))
|| false) {
LOG(("App Error: Could not write emoji cache '%1' for size %2"
).arg(f.fileName()
).arg(size));
}
}
QImage LoadFromFile(int size, int index) {
const auto rows = RowsCount(index);
const auto width = kImagesPerRow * size;
const auto height = rows * size;
const auto fileSize = 4 * sizeof(uint32)
+ (width * height * 4)
+ openssl::kSha256Size;
QFile f(CacheFilePath(size, index));
if (!f.exists()
|| f.size() != fileSize
|| !f.open(QIODevice::ReadOnly)) {
return QImage();
}
const auto read = [&](bytes::span data) {
return f.read(
reinterpret_cast<char*>(data.data()),
data.size()
) == data.size();
};
uint32 header[4] = { 0 };
if (!read(bytes::make_span(header))
|| header[0] != kVersion
|| header[1] != size
|| header[2] != width
|| header[3] != height) {
return QImage();
}
auto result = QImage(
width,
height,
QImage::Format_ARGB32_Premultiplied);
Assert(result.bytesPerLine() == width * 4);
auto data = bytes::make_span(
reinterpret_cast<bytes::type*>(result.bits()),
width * height * 4);
bytes::type signature[openssl::kSha256Size] = { bytes::type() };
if (!read(data)
|| !read(signature)
|| (bytes::compare(
signature,
openssl::Sha256(bytes::make_span(header), data)) != 0)
|| false) {
return QImage();
}
return result;
}
void UniversalImages::ensureLoaded() {
Expects(SpritesCount > 0);
if (!_sprites.empty()) {
return;
}
_sprites.reserve(SpritesCount);
const auto base = qsl(":/gui/emoji/emoji_");
for (auto i = 0; i != SpritesCount; ++i) {
auto image = QImage();
image.load(base + QString::number(i + 1) + ".webp", "WEBP");
_sprites.push_back(std::move(image));
}
}
void UniversalImages::clear() {
_sprites.clear();
}
void UniversalImages::draw(
QPainter &p,
EmojiPtr emoji,
int size,
int x,
int y) const {
Expects(emoji->sprite() < _sprites.size());
const auto factored = (size / p.device()->devicePixelRatio());
const auto large = kUniversalSize;
PainterHighQualityEnabler hq(p);
p.drawImage(
QRect(x, y, factored, factored),
_sprites[emoji->sprite()],
QRect(emoji->column() * large, emoji->row() * large, large, large));
}
QImage UniversalImages::generate(int size, int index) const {
Expects(size > 0);
Expects(index < _sprites.size());
const auto rows = RowsCount(index);
const auto large = kUniversalSize;
const auto &original = _sprites[index];
auto result = QImage(
size * kImagesPerRow,
size * rows,
QImage::Format_ARGB32_Premultiplied);
result.fill(Qt::transparent);
{
QPainter p(&result);
PainterHighQualityEnabler hq(p);
for (auto y = 0; y != rows; ++y) {
for (auto x = 0; x != kImagesPerRow; ++x) {
p.drawImage(
QRect(x * size, y * size, size, size),
original,
QRect(x * large, y * large, large, large));
}
}
}
SaveToFile(result, size, index);
return result;
}
void AppendPartToResult(TextWithEntities &result, const QChar *start, const QChar *from, const QChar *to) {
if (to <= from) {
@ -62,23 +266,65 @@ EmojiPtr FindReplacement(const QChar *start, const QChar *end, int *outLength) {
return internal::FindReplace(start, end, outLength);
}
void ClearUniversalChecked() {
Expects(InstanceNormal != nullptr && InstanceLarge != nullptr);
if (InstanceNormal->cached() && InstanceLarge->cached()) {
Universal.clear();
}
}
} // namespace
void Init() {
auto scaleForEmoji = cRetina() ? dbisTwo : cScale();
switch (scaleForEmoji) {
case dbisOne: WorkingIndex = 0; break;
case dbisOneAndQuarter: WorkingIndex = 1; break;
case dbisOneAndHalf: WorkingIndex = 2; break;
case dbisTwo: WorkingIndex = 3; break;
};
internal::Init();
Scale = [] {
if (cRetina()) {
return 2.;
}
switch (cScale()) {
case dbisOne: return 1.;
case dbisOneAndQuarter: return 1.25;
case dbisOneAndHalf: return 1.5;
case dbisTwo: return 2.;
}
Unexpected("cScale() in Ui::Emoji::Init.");
}();
SizeNormal = int(std::round(Scale * 18));
SizeLarge = int(std::round(Scale * 18 * 4 / 3.));
const auto count = internal::FullCount();
const auto persprite = kImagesPerRow * kImageRowsPerSprite;
SpritesCount = (count / persprite) + ((count % persprite) ? 1 : 0);
InstanceNormal = std::make_unique<Instance>(SizeNormal);
InstanceLarge = std::make_unique<Instance>(SizeLarge);
}
int Index() {
return WorkingIndex;
void Clear() {
MainEmojiMap.clear();
OtherEmojiMap.clear();
InstanceNormal = nullptr;
InstanceLarge = nullptr;
}
int GetSizeNormal() {
Expects(SizeNormal > 0);
return SizeNormal;
}
int GetSizeLarge() {
Expects(SizeLarge > 0);
return SizeLarge;
}
float64 GetScale() {
Expects(Scale > 0.);
return Scale;
}
int One::variantsCount() const {
@ -93,10 +339,6 @@ EmojiPtr One::variant(int index) const {
return (index >= 0 && index <= variantsCount()) ? (original() + index) : this;
}
int One::index() const {
return (this - internal::ByIndex(0));
}
QString IdFromOldKey(uint64 oldKey) {
auto code = uint32(oldKey >> 32);
auto code2 = uint32(oldKey & 0xFFFFFFFFLLU);
@ -307,5 +549,110 @@ void AddRecent(EmojiPtr emoji) {
}
}
const QPixmap &SinglePixmap(EmojiPtr emoji, int fontHeight) {
auto &map = (fontHeight == st::msgFont->height)
? MainEmojiMap
: OtherEmojiMap[fontHeight];
auto i = map.find(emoji->index());
if (i == end(map)) {
auto image = QImage(
SizeNormal + st::emojiPadding * cIntRetinaFactor() * 2,
fontHeight * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied);
if (cRetina()) {
image.setDevicePixelRatio(cRetinaFactor());
}
image.fill(Qt::transparent);
{
QPainter p(&image);
Draw(
p,
emoji,
SizeNormal,
st::emojiPadding * cIntRetinaFactor(),
(fontHeight * cIntRetinaFactor() - SizeNormal) / 2);
}
i = map.emplace(
emoji->index(),
App::pixmapFromImageInPlace(std::move(image))).first;
}
return i->second;
}
void Draw(QPainter &p, EmojiPtr emoji, int size, int x, int y) {
if (size == SizeNormal) {
InstanceNormal->draw(p, emoji, x, y);
} else if (size == SizeLarge) {
InstanceLarge->draw(p, emoji, x, y);
} else {
Unexpected("Size in Ui::Emoji::Draw.");
}
}
Instance::Instance(int size) : _size(size) {
readCache();
if (!cached()) {
Universal.ensureLoaded();
generateCache();
}
}
bool Instance::cached() const {
return (_sprites.size() == SpritesCount);
}
void Instance::draw(QPainter &p, EmojiPtr emoji, int x, int y) {
const auto sprite = emoji->sprite();
if (sprite >= _sprites.size()) {
Universal.draw(p, emoji, _size, x, y);
return;
}
p.drawPixmap(
QPoint(x, y),
_sprites[sprite],
QRect(emoji->column() * _size, emoji->row() * _size, _size, _size));
}
void Instance::readCache() {
for (auto i = 0; i != SpritesCount; ++i) {
auto image = LoadFromFile(_size, i);
if (image.isNull()) {
return;
}
pushSprite(std::move(image));
}
}
void Instance::generateCache() {
const auto size = _size;
const auto index = _sprites.size();
auto [left, right] = base::make_binary_guard();
_generating = std::move(left);
crl::async([=, guard = std::move(right)]() mutable {
crl::on_main([
this,
image = Universal.generate(size, index),
guard = std::move(guard)
]() mutable {
if (!guard.alive()) {
return;
}
pushSprite(std::move(image));
if (cached()) {
ClearUniversalChecked();
} else {
generateCache();
}
});
});
}
void Instance::pushSprite(QImage &&data) {
_sprites.push_back(App::pixmapFromImageInPlace(std::move(data)));
if (cRetina()) {
_sprites.back().setDevicePixelRatio(cRetinaFactor());
}
}
} // namespace Emoji
} // namespace Ui

View File

@ -7,16 +7,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/text/text.h"
#include "base/binary_guard.h"
#include "emoji.h"
namespace Ui {
namespace Emoji {
constexpr auto kPostfix = static_cast<ushort>(0xFE0F);
constexpr auto kRecentLimit = 42;
void Init();
void Clear();
int GetSizeNormal();
int GetSizeLarge();
float64 GetScale();
class One {
struct CreationTag {
@ -24,13 +28,12 @@ class One {
public:
One(One &&other) = default;
One(const QString &id, uint16 x, uint16 y, bool hasPostfix, bool colorizable, EmojiPtr original, const CreationTag &)
One(const QString &id, EmojiPtr original, uint32 index, bool hasPostfix, bool colorizable, const CreationTag &)
: _id(id)
, _x(x)
, _y(y)
, _original(original)
, _index(index)
, _hasPostfix(hasPostfix)
, _colorizable(colorizable)
, _original(original) {
, _colorizable(colorizable) {
Expects(!_colorizable || !colored());
}
@ -62,25 +65,29 @@ public:
int variantIndex(EmojiPtr variant) const;
EmojiPtr variant(int index) const;
int index() const;
int index() const {
return _index;
}
int sprite() const {
return int(_index >> 9);
}
int row() const {
return int((_index >> 5) & 0x0FU);
}
int column() const {
return int(_index & 0x1FU);
}
QString toUrl() const {
return qsl("emoji://e.") + QString::number(index());
}
int x() const {
return _x;
}
int y() const {
return _y;
}
private:
const QString _id;
const uint16 _x = 0;
const uint16 _y = 0;
const EmojiPtr _original = nullptr;
const uint32 _index = 0;
const bool _hasPostfix = false;
const bool _colorizable = false;
const EmojiPtr _original = nullptr;
friend void internal::Init();
@ -123,25 +130,30 @@ inline int ColorIndexFromOldKey(uint64 oldKey) {
return ColorIndexFromCode(uint32(oldKey & 0xFFFFFFFFLLU));
}
inline int Size(int index = Index()) {
int sizes[] = { 18, 22, 27, 36, 45 };
return sizes[index];
}
inline QString Filename(int index = Index()) {
const char *EmojiNames[] = {
":/gui/art/emoji.webp",
":/gui/art/emoji_125x.webp",
":/gui/art/emoji_150x.webp",
":/gui/art/emoji_200x.webp",
":/gui/art/emoji_250x.webp",
};
return QString::fromLatin1(EmojiNames[index]);
}
void ReplaceInText(TextWithEntities &result);
RecentEmojiPack &GetRecent();
void AddRecent(EmojiPtr emoji);
const QPixmap &SinglePixmap(EmojiPtr emoji, int fontHeight);
void Draw(QPainter &p, EmojiPtr emoji, int size, int x, int y);
class Instance {
public:
explicit Instance(int size);
bool cached() const;
void draw(QPainter &p, EmojiPtr emoji, int x, int y);
private:
void readCache();
void generateCache();
void pushSprite(QImage &&data);
int _size = 0;
std::vector<QPixmap> _sprites;
base::binary_guard _generating;
};
} // namespace Emoji
} // namespace Ui

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/empty_userpic.h"
#include "data/data_peer.h"
#include "ui/emoji_config.h"
#include "styles/style_history.h"
namespace Ui {

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/click_handler_types.h"
#include "core/crash_reports.h"
#include "ui/text/text_block.h"
#include "ui/emoji_config.h"
#include "lang/lang_keys.h"
#include "platform/platform_specific.h"
#include "boxes/confirm_box.h"
@ -1441,7 +1442,12 @@ private:
}
}
}
emojiDraw(*_p, static_cast<EmojiBlock*>(currentBlock)->emoji, (glyphX + st::emojiPadding).toInt(), _y + _yDelta + emojiY);
Ui::Emoji::Draw(
*_p,
static_cast<EmojiBlock*>(currentBlock)->emoji,
Ui::Emoji::GetSizeNormal(),
(glyphX + st::emojiPadding).toInt(),
_y + _yDelta + emojiY);
// } else if (_p && currentBlock->type() == TextBlockSkip) { // debug
// _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast<SkipBlock*>(currentBlock)->height()), QColor(0, 0, 0, 32));
}
@ -3124,8 +3130,3 @@ void Text::clearFields() {
}
Text::~Text() = default;
void emojiDraw(QPainter &p, EmojiPtr e, int x, int y) {
auto size = Ui::Emoji::Size();
p.drawPixmap(QPoint(x, y), App::emoji(), QRect(e->x() * size, e->y() * size, size, size));
}

View File

@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/click_handler.h"
#include "ui/text/text_entity.h"
#include "ui/emoji_config.h"
#include "base/flags.h"
static const QChar TextCommand(0x0010);
@ -376,5 +375,3 @@ inline bool chIsParagraphSeparator(QChar ch) {
}
return false;
}
void emojiDraw(QPainter &p, EmojiPtr e, int x, int y);

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "auth_session.h"
#include "lang/lang_tag.h"
#include "base/qthelp_url.h"
#include "ui/emoji_config.h"
namespace TextUtilities {
namespace {

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/popup_menu.h"
#include "ui/countryinput.h"
#include "ui/emoji_config.h"
#include "emoji_suggestions_data.h"
#include "chat_helpers/emoji_suggestions_helper.h"
#include "window/themes/window_theme.h"
@ -529,7 +530,8 @@ QString AccumulateText(Iterator begin, Iterator end) {
QTextImageFormat PrepareEmojiFormat(EmojiPtr emoji, const QFont &font) {
const auto factor = cIntRetinaFactor();
const auto width = Ui::Emoji::Size() + st::emojiPadding * factor * 2;
const auto width = Ui::Emoji::GetSizeNormal()
+ st::emojiPadding * factor * 2;
const auto height = QFontMetrics(font).height() * factor;
auto result = QTextImageFormat();
result.setWidth(width / factor);
@ -1236,7 +1238,7 @@ bool InputField::viewportEventInner(QEvent *e) {
QVariant InputField::loadResource(int type, const QUrl &name) {
const auto imageName = name.toDisplayString();
if (const auto emoji = Ui::Emoji::FromUrl(imageName)) {
return QVariant(App::emojiSingle(emoji, _st.font->height));
return QVariant(Ui::Emoji::SinglePixmap(emoji, _st.font->height));
}
return _inner->QTextEdit::loadResource(type, name);
}

View File

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_widgets.h"
#include "styles/style_chat_helpers.h"
#include "ui/widgets/shadow.h"
#include "ui/emoji_config.h"
#include "window/window_main_menu.h"
#include "auth_session.h"
#include "chat_helpers/stickers.h"
@ -834,7 +835,7 @@ LayerStackWidget::~LayerStackWidget() {
MediaPreviewWidget::MediaPreviewWidget(QWidget *parent, not_null<Window::Controller*> controller) : TWidget(parent)
, _controller(controller)
, _emojiSize(Ui::Emoji::Size(Ui::Emoji::Index() + 1) / cIntRetinaFactor()) {
, _emojiSize(Ui::Emoji::GetSizeLarge() / cIntRetinaFactor()) {
setAttribute(Qt::WA_TransparentForMouseEvents);
subscribe(Auth().downloaderTaskFinished(), [this] { update(); });
}
@ -860,12 +861,17 @@ void MediaPreviewWidget::paintEvent(QPaintEvent *e) {
p.fillRect(r, st::stickerPreviewBg);
p.drawPixmap((width() - w) / 2, (height() - h) / 2, image);
if (!_emojiList.empty()) {
auto emojiCount = _emojiList.size();
auto emojiWidth = (emojiCount * _emojiSize) + (emojiCount - 1) * st::stickerEmojiSkip;
const auto emojiCount = _emojiList.size();
const auto emojiWidth = (emojiCount * _emojiSize) + (emojiCount - 1) * st::stickerEmojiSkip;
auto emojiLeft = (width() - emojiWidth) / 2;
auto esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1);
for (auto emoji : _emojiList) {
p.drawPixmapLeft(emojiLeft, (height() - h) / 2 - (_emojiSize * 2), width(), App::emojiLarge(), QRect(emoji->x() * esize, emoji->y() * esize, esize, esize));
const auto esize = Ui::Emoji::GetSizeLarge();
for (const auto emoji : _emojiList) {
Ui::Emoji::Draw(
p,
emoji,
esize,
emojiLeft,
(height() - h) / 2 - (_emojiSize * 2));
emojiLeft += _emojiSize + st::stickerEmojiSkip;
}
}

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/themes/window_theme_preview.h"
#include "window/themes/window_theme.h"
#include "ui/emoji_config.h"
#include "lang/lang_keys.h"
#include "platform/platform_window_title.h"
#include "ui/text_options.h"

View File

@ -8,8 +8,11 @@
'variables': {
'qrc_files': [
'<(res_loc)/qrc/telegram.qrc',
'<(res_loc)/qrc/telegram_emoji.qrc',
'<(res_loc)/qrc/telegram_emoji_large.qrc',
'<(res_loc)/qrc/telegram_emoji_1.qrc',
'<(res_loc)/qrc/telegram_emoji_2.qrc',
'<(res_loc)/qrc/telegram_emoji_3.qrc',
'<(res_loc)/qrc/telegram_emoji_4.qrc',
'<(res_loc)/qrc/telegram_emoji_5.qrc',
'<(res_loc)/qrc/telegram_sounds.qrc',
],
},