From 4ec3aced2e735f879c0f5ddcea0bbc32212ccb85 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 15 Dec 2022 17:24:36 +0400 Subject: [PATCH] Support colored emoji more widely. --- ui/style/style_core.cpp | 24 ++++++++++++++++++------ ui/style/style_core.h | 30 +++++++++++++++++++++++------- ui/text/custom_emoji_instance.cpp | 29 +++++++++++++++-------------- ui/text/text_custom_emoji.cpp | 13 +++++++------ ui/text/text_custom_emoji.h | 14 ++++++-------- ui/text/text_renderer.cpp | 23 +++++++++++++++++------ ui/text/text_renderer.h | 1 + ui/widgets/input_fields.cpp | 2 +- 8 files changed, 88 insertions(+), 48 deletions(-) diff --git a/ui/style/style_core.cpp b/ui/style/style_core.cpp index 5764612..040313c 100644 --- a/ui/style/style_core.cpp +++ b/ui/style/style_core.cpp @@ -87,7 +87,13 @@ rpl::producer ShortAnimationPlaying() { return internal::ShortAnimationRunning.value(); } -void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect, QPoint dstPoint) { +void colorizeImage( + const QImage &src, + const QColor &color, + not_null outResult, + QRect srcRect, + QPoint dstPoint, + bool useAlpha) { // In background_box ColorizePattern we use the fact that // colorizeImage takes only first byte of the mask, so it // could be used for wallpaper patterns, which have values @@ -99,22 +105,28 @@ void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect } auto width = srcRect.width(); auto height = srcRect.height(); - Assert(outResult && outResult->rect().contains(QRect(dstPoint, srcRect.size()))); + Assert(outResult->rect().contains(QRect(dstPoint, srcRect.size()))); - auto pattern = anim::shifted(c); + auto pattern = anim::shifted(color); constexpr auto resultIntsPerPixel = 1; auto resultIntsPerLine = (outResult->bytesPerLine() >> 2); auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel; - auto resultInts = reinterpret_cast(outResult->bits()) + dstPoint.y() * resultIntsPerLine + dstPoint.x() * resultIntsPerPixel; + auto resultInts = reinterpret_cast(outResult->bits()) + + (dstPoint.y() * resultIntsPerLine) + + (dstPoint.x() * resultIntsPerPixel); Assert(resultIntsAdded >= 0); - Assert(outResult->depth() == static_cast((resultIntsPerPixel * sizeof(uint32)) << 3)); + Assert(outResult->depth() + == static_cast((resultIntsPerPixel * sizeof(uint32)) << 3)); Assert(outResult->bytesPerLine() == (resultIntsPerLine << 2)); auto maskBytesPerPixel = (src.depth() >> 3); auto maskBytesPerLine = src.bytesPerLine(); auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel; - auto maskBytes = src.constBits() + srcRect.y() * maskBytesPerLine + srcRect.x() * maskBytesPerPixel; + auto maskBytes = src.constBits() + + (srcRect.y() * maskBytesPerLine) + + (srcRect.x() * maskBytesPerPixel) + + (useAlpha ? 3 : 0); Assert(maskBytesAdded >= 0); Assert(src.depth() == (maskBytesPerPixel << 3)); for (int y = 0; y != height; ++y) { diff --git a/ui/style/style_core.h b/ui/style/style_core.h index 035c20f..0d517f9 100644 --- a/ui/style/style_core.h +++ b/ui/style/style_core.h @@ -46,17 +46,33 @@ void NotifyPaletteChanged(); // *outResult must be r.width() x r.height(), ARGB32_Premultiplied. // QRect(0, 0, src.width(), src.height()) must contain r. -void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect = QRect(), QPoint dstPoint = QPoint(0, 0)); +void colorizeImage( + const QImage &src, + const QColor &color, + not_null outResult, + QRect srcRect = QRect(), + QPoint dstPoint = QPoint(0, 0), + bool useAlpha = false); -inline QImage colorizeImage(const QImage &src, QColor c, QRect srcRect = QRect()) { - if (srcRect.isNull()) srcRect = src.rect(); - auto result = QImage(srcRect.size(), QImage::Format_ARGB32_Premultiplied); - colorizeImage(src, c, &result, srcRect); +[[nodiscard]] inline QImage colorizeImage( + const QImage &src, + const QColor &color, + QRect srcRect = QRect()) { + if (srcRect.isNull()) { + srcRect = src.rect(); + } + auto result = QImage( + srcRect.size(), + QImage::Format_ARGB32_Premultiplied); + colorizeImage(src, color, &result, srcRect); return result; } -inline QImage colorizeImage(const QImage &src, const color &c, QRect srcRect = QRect()) { - return colorizeImage(src, c->c, srcRect); +[[nodiscard]] inline QImage colorizeImage( + const QImage &src, + const color &color, + QRect srcRect = QRect()) { + return colorizeImage(src, color->c, srcRect); } [[nodiscard]] QImage TransparentPlaceholder(); diff --git a/ui/text/custom_emoji_instance.cpp b/ui/text/custom_emoji_instance.cpp index aa567ab..2b25cbc 100644 --- a/ui/text/custom_emoji_instance.cpp +++ b/ui/text/custom_emoji_instance.cpp @@ -37,14 +37,14 @@ void PaintScaledImage( const QRect &target, const Cache::Frame &frame, const Context &context) { - const auto colored = context.colored; - const auto cache = colored ? &colored->cache : nullptr; + static QImage PaintCache; + const auto cache = context.internal.colorized ? &PaintCache : nullptr; auto q = std::optional(); - if (colored) { + if (cache) { const auto ratio = style::DevicePixelRatio(); if (cache->width() < target.width() * ratio || cache->height() < target.height() * ratio) { - colored->cache = QImage( + *cache = QImage( std::max(cache->width(), target.width() * ratio), std::max(cache->height(), target.height() * ratio), QImage::Format_ARGB32_Premultiplied); @@ -52,7 +52,9 @@ void PaintScaledImage( } q.emplace(cache); q->setCompositionMode(QPainter::CompositionMode_Source); - q->fillRect(QRect(QPoint(), target.size()), Qt::transparent); + if (context.scaled) { + q->fillRect(QRect(QPoint(), target.size()), Qt::transparent); + } q->translate(-target.topLeft()); } const auto to = q ? &*q : &p; @@ -79,7 +81,8 @@ void PaintScaledImage( q.reset(); const auto ratio = style::DevicePixelRatio(); const auto source = QRect(QPoint(), target.size() * ratio); - style::colorizeImage(*cache, colored->color, cache, source); + const auto &color = context.textColor; + style::colorizeImage(*cache, color, cache, source, {}, true); p.drawImage(target, *cache, source); } } @@ -128,7 +131,9 @@ void Preview::paintPath( const Context &context, const ScaledPath &path) { auto hq = PainterHighQualityEnabler(p); - p.setBrush(context.preview); + auto copy = context.textColor.value(); + copy.setAlpha((copy.alpha() + 1) / 8); + p.setBrush(copy); p.setPen(Qt::NoPen); const auto scale = path.scale; const auto required = (scale != 1.) || context.scaled; @@ -374,7 +379,7 @@ PaintFrameResult Cache::paintCurrentFrame( if (!_frames) { return {}; } - const auto first = context.firstFrameOnly; + const auto first = context.internal.forceFirstFrame; if (!first) { const auto now = context.paused ? 0 : context.now; const auto finishes = now ? currentFrameFinishes() : 0; @@ -657,9 +662,8 @@ QString Instance::entityData() const { } void Instance::paint(QPainter &p, const Context &context) { - const auto colored = (context.colored && !_colored) - ? base::take(context.colored) - : nullptr; + context.internal.colorized = _colored; + v::match(_state, [&](Loading &state) { state.paint(p, context); load(state); @@ -684,9 +688,6 @@ void Instance::paint(QPainter &p, const Context &context) { _repaintLater(this, { result.next, result.duration }); } }); - if (colored) { - context.colored = colored; - } } bool Instance::ready() { diff --git a/ui/text/text_custom_emoji.cpp b/ui/text/text_custom_emoji.cpp index 4eb1919..f0da6fd 100644 --- a/ui/text/text_custom_emoji.cpp +++ b/ui/text/text_custom_emoji.cpp @@ -50,9 +50,10 @@ QString FirstFrameEmoji::entityData() { } void FirstFrameEmoji::paint(QPainter &p, const Context &context) { - auto copy = context; - copy.firstFrameOnly = true; - _wrapped->paint(p, copy); + const auto was = context.internal.forceFirstFrame; + context.internal.forceFirstFrame = true; + _wrapped->paint(p, context); + context.internal.forceFirstFrame = was; } void FirstFrameEmoji::unload() { @@ -90,10 +91,10 @@ void LimitedLoopsEmoji::paint(QPainter &p, const Context &context) { } } if (_played == _limit) { - const auto was = context.firstFrameOnly; - context.firstFrameOnly = true; + const auto was = context.internal.forceFirstFrame; + context.internal.forceFirstFrame = true; _wrapped->paint(p, context); - context.firstFrameOnly = was; + context.internal.forceFirstFrame = was; } else { _wrapped->paint(p, context); } diff --git a/ui/text/text_custom_emoji.h b/ui/text/text_custom_emoji.h index a972e8b..fb65685 100644 --- a/ui/text/text_custom_emoji.h +++ b/ui/text/text_custom_emoji.h @@ -19,21 +19,19 @@ namespace Ui::Text { [[nodiscard]] int AdjustCustomEmojiSize(int emojiSize); -struct CustomEmojiColored { - QColor color; - QImage cache; -}; - struct CustomEmojiPaintContext { - QColor preview; - mutable CustomEmojiColored *colored = nullptr; + required textColor; QSize size; // Required only when scaled = true, for path scaling. crl::time now = 0; float64 scale = 0.; QPoint position; - mutable bool firstFrameOnly = false; bool paused = false; bool scaled = false; + + mutable struct { + bool colorized = false; + bool forceFirstFrame = false; + } internal; }; class CustomEmoji { diff --git a/ui/text/text_renderer.cpp b/ui/text/text_renderer.cpp index 2ad871c..756269b 100644 --- a/ui/text/text_renderer.cpp +++ b/ui/text/text_renderer.cpp @@ -827,16 +827,27 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato x, y); } else if (const auto custom = static_cast(currentBlock)->_custom.get()) { + const auto selected = (fillSelect.from <= glyphX) + && (fillSelect.till > glyphX); + const auto color = (selected + ? _currentPenSelected + : _currentPen)->color(); if (!_customEmojiSize) { _customEmojiSize = AdjustCustomEmojiSize(st::emojiSize); _customEmojiSkip = (st::emojiSize - _customEmojiSize) / 2; + _customEmojiContext = CustomEmoji::Context{ + .textColor = color, + .now = now(), + .paused = _paused, + }; + } else { + _customEmojiContext->textColor = color; } - custom->paint(*_p, { - .preview = _palette->spoilerFg->c, - .now = now(), - .position = { x + _customEmojiSkip, y + _customEmojiSkip }, - .paused = _paused, - }); + _customEmojiContext->position = { + x + _customEmojiSkip, + y + _customEmojiSkip, + }; + custom->paint(*_p, *_customEmojiContext); } if (hasSpoiler) { _p->setOpacity(opacity); diff --git a/ui/text/text_renderer.h b/ui/text/text_renderer.h index aa41bb5..4ba28ce 100644 --- a/ui/text/text_renderer.h +++ b/ui/text/text_renderer.h @@ -141,6 +141,7 @@ private: QVarLengthArray _spoilerRects; QVarLengthArray _spoilerSelectedRects; + std::optional _customEmojiContext; int _customEmojiSize = 0; int _customEmojiSkip = 0; int _indexOfElidedBlock = -1; // For spoilers. diff --git a/ui/widgets/input_fields.cpp b/ui/widgets/input_fields.cpp index a3ee55e..7654d7a 100644 --- a/ui/widgets/input_fields.cpp +++ b/ui/widgets/input_fields.cpp @@ -1126,7 +1126,7 @@ void CustomEmojiObject::drawObject( return; } i->second->paint(*painter, { - .preview = st::windowBgRipple->c, + .textColor = format.foreground().color(), .now = _now, .position = QPoint( int(base::SafeRound(rect.x())) + st::emojiPadding + _skip,