Support complex-rounded image spoilers.

This commit is contained in:
John Preston 2022-09-30 23:03:06 +04:00
parent f49ec866c1
commit cec09b0260
4 changed files with 150 additions and 74 deletions

View file

@ -501,48 +501,52 @@ void FillSpoilerRect(
void FillSpoilerRect(
QPainter &p,
QRect rect,
ImageRoundRadius radius,
RectParts corners,
Images::CornersMaskRef mask,
const SpoilerMessFrame &frame,
QImage &cornerCache,
QPoint originShift) {
if (radius == ImageRoundRadius::None || !corners) {
FillSpoilerRect(p, rect, frame, originShift);
return;
}
const auto &mask = Images::CornersMask(radius);
const auto side = mask[0].width() / style::DevicePixelRatio();
const auto xFillFrom = (corners & RectPart::FullLeft) ? side : 0;
const auto xFillTo = rect.width()
- ((corners & RectPart::FullRight) ? side : 0);
const auto yFillFrom = (corners & RectPart::FullTop) ? side : 0;
const auto yFillTo = rect.height()
- ((corners & RectPart::FullBottom) ? side : 0);
const auto xFill = xFillTo - xFillFrom;
const auto yFill = yFillTo - yFillFrom;
if (xFill < 0 || yFill < 0) {
// Unexpected.. but maybe not fatal, just glitchy.
constexpr auto kTopLeft = 0;
constexpr auto kTopRight = 1;
constexpr auto kBottomLeft = 2;
constexpr auto kBottomRight = 3;
if ((!mask.p[kTopLeft] || mask.p[kTopLeft]->isNull())
&& (!mask.p[kTopRight] || mask.p[kTopRight]->isNull())
&& (!mask.p[kBottomLeft] || mask.p[kBottomLeft]->isNull())
&& (!mask.p[kBottomRight] || mask.p[kBottomRight]->isNull())) {
FillSpoilerRect(p, rect, frame, originShift);
return;
}
const auto ratio = style::DevicePixelRatio();
const auto paintPart = [&](int x, int y, int w, int h) {
const auto cornerSize = [&](int index) {
const auto corner = mask.p[index];
return (!corner || corner->isNull()) ? 0 : (corner->width() / ratio);
};
const auto verticalSkip = [&](int left, int right) {
return std::max(cornerSize(left), cornerSize(right));
};
const auto fillBg = [&](QRect part) {
FillSpoilerRect(
p,
{ rect.x() + x, rect.y() + y, w, h },
part.translated(rect.topLeft()),
frame,
originShift - QPoint(x, y));
originShift - part.topLeft());
};
const auto paintCorner = [&](QPoint position, const QImage &mask) {
if (cornerCache.width() < mask.width()
|| cornerCache.height() < mask.height()) {
const auto fillCorner = [&](int x, int y, int index) {
const auto position = QPoint(x, y);
const auto corner = mask.p[index];
if (!corner || corner->isNull()) {
return;
}
if (cornerCache.width() < corner->width()
|| cornerCache.height() < corner->height()) {
cornerCache = QImage(
std::max(cornerCache.width(), mask.width()),
std::max(cornerCache.height(), mask.height()),
std::max(cornerCache.width(), corner->width()),
std::max(cornerCache.height(), corner->height()),
QImage::Format_ARGB32_Premultiplied);
cornerCache.setDevicePixelRatio(ratio);
}
const auto size = mask.size() / ratio;
const auto size = corner->size() / ratio;
const auto target = QRect(QPoint(), size);
auto q = QPainter(&cornerCache);
q.setCompositionMode(QPainter::CompositionMode_Source);
@ -552,36 +556,83 @@ void FillSpoilerRect(
frame,
originShift - rect.topLeft() - position);
q.setCompositionMode(QPainter::CompositionMode_DestinationIn);
q.drawImage(target, mask);
q.drawImage(target, *corner);
q.end();
p.drawImage(
QRect(rect.topLeft() + position, size),
cornerCache,
QRect(QPoint(), mask.size()));
QRect(QPoint(), corner->size()));
};
if (yFillFrom > 0) {
if (xFillFrom > 0) {
paintCorner({}, mask[0]);
}
if (xFill) {
paintPart(xFillFrom, 0, xFill, yFillFrom);
}
if (xFillTo < rect.width()) {
paintCorner({ xFillTo, 0 }, mask[1]);
const auto top = verticalSkip(kTopLeft, kTopRight);
const auto bottom = verticalSkip(kBottomLeft, kBottomRight);
if (top) {
const auto left = cornerSize(kTopLeft);
const auto right = cornerSize(kTopRight);
if (left) {
fillCorner(rect.left(), rect.top(), kTopLeft);
if (const auto add = top - left) {
fillBg({ rect.left(), rect.top() + left, left, add });
}
}
if (yFill) {
paintPart(0, yFillFrom, rect.width(), yFill);
if (const auto fill = rect.width() - left - right; fill > 0) {
fillBg({ rect.left() + left, rect.top(), fill, top });
}
if (yFillTo < rect.height()) {
if (xFillFrom > 0) {
paintCorner({ 0, yFillTo }, mask[2]);
if (right) {
fillCorner(
rect.left() + rect.width() - right,
rect.top(),
kTopRight);
if (const auto add = top - right) {
fillBg({
rect.left() + rect.width() - right,
rect.top() + right,
right,
add,
});
}
if (xFill) {
paintPart(xFillFrom, yFillTo, xFill, rect.height() - yFillTo);
}
if (xFillTo < rect.width()) {
paintCorner({ xFillTo, yFillTo }, mask[3]);
}
if (const auto h = rect.height() - top - bottom; h > 0) {
fillBg({ rect.left(), rect.top() + top, rect.width(), h });
}
if (bottom) {
const auto left = cornerSize(kBottomLeft);
const auto right = cornerSize(kBottomRight);
if (left) {
fillCorner(
rect.left(),
rect.top() + rect.height() - left,
kBottomLeft);
if (const auto add = bottom - left) {
fillBg({
rect.left(),
rect.top() + rect.height() - bottom,
left,
add,
});
}
}
if (const auto fill = rect.width() - left - right; fill > 0) {
fillBg({
rect.left() + left,
rect.top() + rect.height() - bottom,
fill,
bottom,
});
}
if (right) {
fillCorner(
rect.left() + rect.width() - right,
rect.top() + rect.height() - right,
kBottomRight);
if (const auto add = bottom - right) {
fillBg({
rect.left() + rect.width() - right,
rect.top() + rect.height() - bottom,
right,
add,
});
}
}
}
}

View file

@ -8,15 +8,9 @@
#include <crl/crl_time.h>
enum class ImageRoundRadius;
namespace base {
template <typename EnumType>
class flags;
} // namespace base
enum class RectPart;
using RectParts = base::flags<RectPart>;
namespace Images {
struct CornersMaskRef;
} // namespace Images
namespace Ui {
@ -49,8 +43,7 @@ void FillSpoilerRect(
void FillSpoilerRect(
QPainter &p,
QRect rect,
ImageRoundRadius radius,
RectParts corners,
Images::CornersMaskRef mask,
const SpoilerMessFrame &frame,
QImage &cornerCache,
QPoint originShift = {});

View file

@ -1009,8 +1009,7 @@ QImage Circle(QImage &&image, QRect target) {
QImage Round(
QImage &&image,
gsl::span<const QImage, 4> cornerMasks,
RectParts corners,
CornersMaskRef mask,
QRect target) {
if (target.isNull()) {
target = QRect(QPoint(), image.size());
@ -1035,23 +1034,24 @@ QImage Round(
Assert(image.depth() == ((kImageIntsPerPixel * sizeof(uint32)) << 3));
Assert(image.bytesPerLine() == (imageIntsPerLine << 2));
const auto maskCorner = [&](
const QImage &mask,
const QImage *mask,
bool right = false,
bool bottom = false) {
const auto maskWidth = mask.width();
const auto maskHeight = mask.height();
if (mask.isNull()
const auto maskWidth = mask ? mask->width() : 0;
const auto maskHeight = mask ? mask->height() : 0;
if (!maskWidth
|| !maskHeight
|| targetWidth < maskWidth
|| targetHeight < maskHeight) {
return;
}
const auto maskBytesPerPixel = (mask.depth() >> 3);
const auto maskBytesPerLine = mask.bytesPerLine();
const auto maskBytesPerPixel = (mask->depth() >> 3);
const auto maskBytesPerLine = mask->bytesPerLine();
const auto maskBytesAdded = maskBytesPerLine
- maskWidth * maskBytesPerPixel;
Assert(maskBytesAdded >= 0);
Assert(mask.depth() == (maskBytesPerPixel << 3));
Assert(mask->depth() == (maskBytesPerPixel << 3));
const auto imageIntsAdded = imageIntsPerLine
- maskWidth * kImageIntsPerPixel;
Assert(imageIntsAdded >= 0);
@ -1062,7 +1062,7 @@ QImage Round(
if (bottom) {
imageInts += (targetHeight - maskHeight) * imageIntsPerLine;
}
auto maskBytes = mask.constBits();
auto maskBytes = mask->constBits();
for (auto y = 0; y != maskHeight; ++y) {
for (auto x = 0; x != maskWidth; ++x) {
auto opacity = static_cast<anim::ShiftedMultiplier>(*maskBytes) + 1;
@ -1075,18 +1075,27 @@ QImage Round(
}
};
if (corners & RectPart::TopLeft) maskCorner(cornerMasks[0]);
if (corners & RectPart::TopRight) maskCorner(cornerMasks[1], true);
if (corners & RectPart::BottomLeft) {
maskCorner(cornerMasks[2], false, true);
}
if (corners & RectPart::BottomRight) {
maskCorner(cornerMasks[3], true, true);
}
maskCorner(mask.p[0]);
maskCorner(mask.p[1], true);
maskCorner(mask.p[2], false, true);
maskCorner(mask.p[3], true, true);
return std::move(image);
}
QImage Round(
QImage &&image,
gsl::span<const QImage, 4> cornerMasks,
RectParts corners,
QRect target) {
return Round(std::move(image), CornersMaskRef({
(corners & RectPart::TopLeft) ? &cornerMasks[0] : nullptr,
(corners & RectPart::TopRight) ? &cornerMasks[1] : nullptr,
(corners & RectPart::BottomLeft) ? &cornerMasks[2] : nullptr,
(corners & RectPart::BottomRight) ? &cornerMasks[3] : nullptr,
}), target);
}
QImage Round(
QImage &&image,
ImageRoundRadius radius,

View file

@ -46,6 +46,24 @@ namespace Images {
int bottomAlpha,
QColor color = QColor(0, 0, 0));
struct CornersMaskRef {
CornersMaskRef() = default;
explicit CornersMaskRef(gsl::span<const QImage, 4> masks)
: p{ &masks[0], &masks[1], &masks[2], &masks[3] } {
}
explicit CornersMaskRef(std::array<const QImage, 4> masks)
: p{ &masks[0], &masks[1], &masks[2], &masks[3] } {
}
explicit CornersMaskRef(gsl::span<const QImage*, 4> masks)
: p{ masks[0], masks[1], masks[2], masks[3] } {
}
explicit CornersMaskRef(std::array<const QImage*, 4> masks)
: p{ masks[0], masks[1], masks[2], masks[3] } {
}
std::array<const QImage*, 4> p{};
};
[[nodiscard]] const std::array<QImage, 4> &CornersMask(
ImageRoundRadius radius);
[[nodiscard]] std::array<QImage, 4> PrepareCorners(
@ -100,6 +118,11 @@ inline constexpr auto is_flag_type(Option) { return true; };
ImageRoundRadius radius,
RectParts corners = RectPart::AllCorners);
[[nodiscard]] QImage Round(
QImage &&image,
CornersMaskRef mask,
QRect target = QRect());
[[nodiscard]] QImage Blur(QImage &&image, bool ignoreAlpha = false);
[[nodiscard]] QImage Round(
QImage &&image,