Support colored emoji more widely.

This commit is contained in:
John Preston 2022-12-15 17:24:36 +04:00
parent 64d2778914
commit 4ec3aced2e
8 changed files with 88 additions and 48 deletions

View file

@ -87,7 +87,13 @@ rpl::producer<bool> 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<QImage*> 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<uint32*>(outResult->bits()) + dstPoint.y() * resultIntsPerLine + dstPoint.x() * resultIntsPerPixel;
auto resultInts = reinterpret_cast<uint32*>(outResult->bits())
+ (dstPoint.y() * resultIntsPerLine)
+ (dstPoint.x() * resultIntsPerPixel);
Assert(resultIntsAdded >= 0);
Assert(outResult->depth() == static_cast<int>((resultIntsPerPixel * sizeof(uint32)) << 3));
Assert(outResult->depth()
== static_cast<int>((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) {

View file

@ -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<QImage*> 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();

View file

@ -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<QPainter>();
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() {

View file

@ -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);
}

View file

@ -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<QColor> 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 {

View file

@ -827,16 +827,27 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
x,
y);
} else if (const auto custom = static_cast<const CustomEmojiBlock*>(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);

View file

@ -141,6 +141,7 @@ private:
QVarLengthArray<QRect, kSpoilersRectsSize> _spoilerRects;
QVarLengthArray<QRect, kSpoilersRectsSize> _spoilerSelectedRects;
std::optional<CustomEmoji::Context> _customEmojiContext;
int _customEmojiSize = 0;
int _customEmojiSkip = 0;
int _indexOfElidedBlock = -1; // For spoilers.

View file

@ -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,