Support colored emoji more widely.
This commit is contained in:
parent
64d2778914
commit
4ec3aced2e
8 changed files with 88 additions and 48 deletions
|
|
@ -87,7 +87,13 @@ rpl::producer<bool> ShortAnimationPlaying() {
|
||||||
return internal::ShortAnimationRunning.value();
|
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
|
// In background_box ColorizePattern we use the fact that
|
||||||
// colorizeImage takes only first byte of the mask, so it
|
// colorizeImage takes only first byte of the mask, so it
|
||||||
// could be used for wallpaper patterns, which have values
|
// 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 width = srcRect.width();
|
||||||
auto height = srcRect.height();
|
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;
|
constexpr auto resultIntsPerPixel = 1;
|
||||||
auto resultIntsPerLine = (outResult->bytesPerLine() >> 2);
|
auto resultIntsPerLine = (outResult->bytesPerLine() >> 2);
|
||||||
auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel;
|
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(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));
|
Assert(outResult->bytesPerLine() == (resultIntsPerLine << 2));
|
||||||
|
|
||||||
auto maskBytesPerPixel = (src.depth() >> 3);
|
auto maskBytesPerPixel = (src.depth() >> 3);
|
||||||
auto maskBytesPerLine = src.bytesPerLine();
|
auto maskBytesPerLine = src.bytesPerLine();
|
||||||
auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel;
|
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(maskBytesAdded >= 0);
|
||||||
Assert(src.depth() == (maskBytesPerPixel << 3));
|
Assert(src.depth() == (maskBytesPerPixel << 3));
|
||||||
for (int y = 0; y != height; ++y) {
|
for (int y = 0; y != height; ++y) {
|
||||||
|
|
|
||||||
|
|
@ -46,17 +46,33 @@ void NotifyPaletteChanged();
|
||||||
|
|
||||||
// *outResult must be r.width() x r.height(), ARGB32_Premultiplied.
|
// *outResult must be r.width() x r.height(), ARGB32_Premultiplied.
|
||||||
// QRect(0, 0, src.width(), src.height()) must contain r.
|
// 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()) {
|
[[nodiscard]] inline QImage colorizeImage(
|
||||||
if (srcRect.isNull()) srcRect = src.rect();
|
const QImage &src,
|
||||||
auto result = QImage(srcRect.size(), QImage::Format_ARGB32_Premultiplied);
|
const QColor &color,
|
||||||
colorizeImage(src, c, &result, srcRect);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline QImage colorizeImage(const QImage &src, const color &c, QRect srcRect = QRect()) {
|
[[nodiscard]] inline QImage colorizeImage(
|
||||||
return colorizeImage(src, c->c, srcRect);
|
const QImage &src,
|
||||||
|
const color &color,
|
||||||
|
QRect srcRect = QRect()) {
|
||||||
|
return colorizeImage(src, color->c, srcRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] QImage TransparentPlaceholder();
|
[[nodiscard]] QImage TransparentPlaceholder();
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,14 @@ void PaintScaledImage(
|
||||||
const QRect &target,
|
const QRect &target,
|
||||||
const Cache::Frame &frame,
|
const Cache::Frame &frame,
|
||||||
const Context &context) {
|
const Context &context) {
|
||||||
const auto colored = context.colored;
|
static QImage PaintCache;
|
||||||
const auto cache = colored ? &colored->cache : nullptr;
|
const auto cache = context.internal.colorized ? &PaintCache : nullptr;
|
||||||
auto q = std::optional<QPainter>();
|
auto q = std::optional<QPainter>();
|
||||||
if (colored) {
|
if (cache) {
|
||||||
const auto ratio = style::DevicePixelRatio();
|
const auto ratio = style::DevicePixelRatio();
|
||||||
if (cache->width() < target.width() * ratio
|
if (cache->width() < target.width() * ratio
|
||||||
|| cache->height() < target.height() * ratio) {
|
|| cache->height() < target.height() * ratio) {
|
||||||
colored->cache = QImage(
|
*cache = QImage(
|
||||||
std::max(cache->width(), target.width() * ratio),
|
std::max(cache->width(), target.width() * ratio),
|
||||||
std::max(cache->height(), target.height() * ratio),
|
std::max(cache->height(), target.height() * ratio),
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
|
@ -52,7 +52,9 @@ void PaintScaledImage(
|
||||||
}
|
}
|
||||||
q.emplace(cache);
|
q.emplace(cache);
|
||||||
q->setCompositionMode(QPainter::CompositionMode_Source);
|
q->setCompositionMode(QPainter::CompositionMode_Source);
|
||||||
|
if (context.scaled) {
|
||||||
q->fillRect(QRect(QPoint(), target.size()), Qt::transparent);
|
q->fillRect(QRect(QPoint(), target.size()), Qt::transparent);
|
||||||
|
}
|
||||||
q->translate(-target.topLeft());
|
q->translate(-target.topLeft());
|
||||||
}
|
}
|
||||||
const auto to = q ? &*q : &p;
|
const auto to = q ? &*q : &p;
|
||||||
|
|
@ -79,7 +81,8 @@ void PaintScaledImage(
|
||||||
q.reset();
|
q.reset();
|
||||||
const auto ratio = style::DevicePixelRatio();
|
const auto ratio = style::DevicePixelRatio();
|
||||||
const auto source = QRect(QPoint(), target.size() * ratio);
|
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);
|
p.drawImage(target, *cache, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +131,9 @@ void Preview::paintPath(
|
||||||
const Context &context,
|
const Context &context,
|
||||||
const ScaledPath &path) {
|
const ScaledPath &path) {
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
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);
|
p.setPen(Qt::NoPen);
|
||||||
const auto scale = path.scale;
|
const auto scale = path.scale;
|
||||||
const auto required = (scale != 1.) || context.scaled;
|
const auto required = (scale != 1.) || context.scaled;
|
||||||
|
|
@ -374,7 +379,7 @@ PaintFrameResult Cache::paintCurrentFrame(
|
||||||
if (!_frames) {
|
if (!_frames) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const auto first = context.firstFrameOnly;
|
const auto first = context.internal.forceFirstFrame;
|
||||||
if (!first) {
|
if (!first) {
|
||||||
const auto now = context.paused ? 0 : context.now;
|
const auto now = context.paused ? 0 : context.now;
|
||||||
const auto finishes = now ? currentFrameFinishes() : 0;
|
const auto finishes = now ? currentFrameFinishes() : 0;
|
||||||
|
|
@ -657,9 +662,8 @@ QString Instance::entityData() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Instance::paint(QPainter &p, const Context &context) {
|
void Instance::paint(QPainter &p, const Context &context) {
|
||||||
const auto colored = (context.colored && !_colored)
|
context.internal.colorized = _colored;
|
||||||
? base::take(context.colored)
|
|
||||||
: nullptr;
|
|
||||||
v::match(_state, [&](Loading &state) {
|
v::match(_state, [&](Loading &state) {
|
||||||
state.paint(p, context);
|
state.paint(p, context);
|
||||||
load(state);
|
load(state);
|
||||||
|
|
@ -684,9 +688,6 @@ void Instance::paint(QPainter &p, const Context &context) {
|
||||||
_repaintLater(this, { result.next, result.duration });
|
_repaintLater(this, { result.next, result.duration });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (colored) {
|
|
||||||
context.colored = colored;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Instance::ready() {
|
bool Instance::ready() {
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,10 @@ QString FirstFrameEmoji::entityData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FirstFrameEmoji::paint(QPainter &p, const Context &context) {
|
void FirstFrameEmoji::paint(QPainter &p, const Context &context) {
|
||||||
auto copy = context;
|
const auto was = context.internal.forceFirstFrame;
|
||||||
copy.firstFrameOnly = true;
|
context.internal.forceFirstFrame = true;
|
||||||
_wrapped->paint(p, copy);
|
_wrapped->paint(p, context);
|
||||||
|
context.internal.forceFirstFrame = was;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FirstFrameEmoji::unload() {
|
void FirstFrameEmoji::unload() {
|
||||||
|
|
@ -90,10 +91,10 @@ void LimitedLoopsEmoji::paint(QPainter &p, const Context &context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_played == _limit) {
|
if (_played == _limit) {
|
||||||
const auto was = context.firstFrameOnly;
|
const auto was = context.internal.forceFirstFrame;
|
||||||
context.firstFrameOnly = true;
|
context.internal.forceFirstFrame = true;
|
||||||
_wrapped->paint(p, context);
|
_wrapped->paint(p, context);
|
||||||
context.firstFrameOnly = was;
|
context.internal.forceFirstFrame = was;
|
||||||
} else {
|
} else {
|
||||||
_wrapped->paint(p, context);
|
_wrapped->paint(p, context);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,21 +19,19 @@ namespace Ui::Text {
|
||||||
|
|
||||||
[[nodiscard]] int AdjustCustomEmojiSize(int emojiSize);
|
[[nodiscard]] int AdjustCustomEmojiSize(int emojiSize);
|
||||||
|
|
||||||
struct CustomEmojiColored {
|
|
||||||
QColor color;
|
|
||||||
QImage cache;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CustomEmojiPaintContext {
|
struct CustomEmojiPaintContext {
|
||||||
QColor preview;
|
required<QColor> textColor;
|
||||||
mutable CustomEmojiColored *colored = nullptr;
|
|
||||||
QSize size; // Required only when scaled = true, for path scaling.
|
QSize size; // Required only when scaled = true, for path scaling.
|
||||||
crl::time now = 0;
|
crl::time now = 0;
|
||||||
float64 scale = 0.;
|
float64 scale = 0.;
|
||||||
QPoint position;
|
QPoint position;
|
||||||
mutable bool firstFrameOnly = false;
|
|
||||||
bool paused = false;
|
bool paused = false;
|
||||||
bool scaled = false;
|
bool scaled = false;
|
||||||
|
|
||||||
|
mutable struct {
|
||||||
|
bool colorized = false;
|
||||||
|
bool forceFirstFrame = false;
|
||||||
|
} internal;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CustomEmoji {
|
class CustomEmoji {
|
||||||
|
|
|
||||||
|
|
@ -827,16 +827,27 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
x,
|
x,
|
||||||
y);
|
y);
|
||||||
} else if (const auto custom = static_cast<const CustomEmojiBlock*>(currentBlock)->_custom.get()) {
|
} 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) {
|
if (!_customEmojiSize) {
|
||||||
_customEmojiSize = AdjustCustomEmojiSize(st::emojiSize);
|
_customEmojiSize = AdjustCustomEmojiSize(st::emojiSize);
|
||||||
_customEmojiSkip = (st::emojiSize - _customEmojiSize) / 2;
|
_customEmojiSkip = (st::emojiSize - _customEmojiSize) / 2;
|
||||||
}
|
_customEmojiContext = CustomEmoji::Context{
|
||||||
custom->paint(*_p, {
|
.textColor = color,
|
||||||
.preview = _palette->spoilerFg->c,
|
|
||||||
.now = now(),
|
.now = now(),
|
||||||
.position = { x + _customEmojiSkip, y + _customEmojiSkip },
|
|
||||||
.paused = _paused,
|
.paused = _paused,
|
||||||
});
|
};
|
||||||
|
} else {
|
||||||
|
_customEmojiContext->textColor = color;
|
||||||
|
}
|
||||||
|
_customEmojiContext->position = {
|
||||||
|
x + _customEmojiSkip,
|
||||||
|
y + _customEmojiSkip,
|
||||||
|
};
|
||||||
|
custom->paint(*_p, *_customEmojiContext);
|
||||||
}
|
}
|
||||||
if (hasSpoiler) {
|
if (hasSpoiler) {
|
||||||
_p->setOpacity(opacity);
|
_p->setOpacity(opacity);
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,7 @@ private:
|
||||||
QVarLengthArray<QRect, kSpoilersRectsSize> _spoilerRects;
|
QVarLengthArray<QRect, kSpoilersRectsSize> _spoilerRects;
|
||||||
QVarLengthArray<QRect, kSpoilersRectsSize> _spoilerSelectedRects;
|
QVarLengthArray<QRect, kSpoilersRectsSize> _spoilerSelectedRects;
|
||||||
|
|
||||||
|
std::optional<CustomEmoji::Context> _customEmojiContext;
|
||||||
int _customEmojiSize = 0;
|
int _customEmojiSize = 0;
|
||||||
int _customEmojiSkip = 0;
|
int _customEmojiSkip = 0;
|
||||||
int _indexOfElidedBlock = -1; // For spoilers.
|
int _indexOfElidedBlock = -1; // For spoilers.
|
||||||
|
|
|
||||||
|
|
@ -1126,7 +1126,7 @@ void CustomEmojiObject::drawObject(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
i->second->paint(*painter, {
|
i->second->paint(*painter, {
|
||||||
.preview = st::windowBgRipple->c,
|
.textColor = format.foreground().color(),
|
||||||
.now = _now,
|
.now = _now,
|
||||||
.position = QPoint(
|
.position = QPoint(
|
||||||
int(base::SafeRound(rect.x())) + st::emojiPadding + _skip,
|
int(base::SafeRound(rect.x())) + st::emojiPadding + _skip,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue