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();
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue