Support new rounding for GIFs / videos.

This commit is contained in:
John Preston 2022-10-03 11:35:11 +04:00
parent b2302d35fe
commit 8268e9f872
17 changed files with 170 additions and 193 deletions

View File

@ -626,19 +626,24 @@ struct VideoPreviewDocument {
};
check();
const auto corners = alignToBottom
? (RectPart::TopLeft | RectPart::TopRight)
: (RectPart::BottomLeft | RectPart::BottomRight);
const auto ready = state->instance.player().ready()
&& !state->instance.player().videoSize().isEmpty();
const auto size = QSize(width, height) * style::DevicePixelRatio();
using namespace Images;
auto rounding = CornersMaskRef(
Images::CornersMask(ImageRoundRadius::Large));
if (alignToBottom) {
rounding.p[kBottomLeft] = rounding.p[kBottomRight] = nullptr;
} else {
rounding.p[kTopLeft] = rounding.p[kTopRight] = nullptr;
}
const auto frame = !ready
? state->blurred
: state->instance.frame({
.resize = size,
.outer = size,
.radius = ImageRoundRadius::Large,
.corners = corners,
.rounding = rounding,
});
paintFrame(QColor(0, 0, 0, 128), 12.);
p.drawImage(QRect(left, top, width, height), frame);

View File

@ -274,6 +274,32 @@ QSize Gif::videoSize() const {
}
}
void Gif::validateRoundingMask(QSize size) const {
if (_roundingMask.size() != size) {
const auto ratio = style::DevicePixelRatio();
_roundingMask = Images::EllipseMask(size / ratio);
}
}
Images::CornersMaskRef Gif::prepareRoundingRef(
std::optional<Ui::BubbleRounding> rounding) const {
using namespace Ui;
using namespace Images;
if (!rounding) {
return CornersMaskRef(CachedCornersMasks(CachedCornerRadius::Small));
}
auto result = CornersMaskRef();
for (auto i = 0; i != 4; ++i) {
const auto corner = (*rounding)[i];
result.p[i] = (corner == BubbleCornerRounding::Large)
? &CachedCornersMasks(CachedCornerRadius::BubbleLarge)[i]
: (corner == BubbleCornerRounding::Small)
? &CachedCornersMasks(CachedCornerRadius::BubbleSmall)[i]
: nullptr;
}
return result;
}
bool Gif::downloadInCorner() const {
return _data->isVideoFile()
&& (_data->loading() || !autoplayEnabled())
@ -349,6 +375,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
const auto radial = isRadialAnimation()
|| (streamedForWaiting && streamedForWaiting->waitingShown());
const auto rounding = inWebPage
? std::optional<Ui::BubbleRounding>()
: adjustedBubbleRoundingWithCaption(_caption);
if (bubble) {
if (!_caption.isEmpty()) {
painth -= st::mediaCaptionSkip + _caption.countHeight(captionw);
@ -356,9 +385,6 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
painth -= st::msgPadding.bottom();
}
}
} else if (!unwrapped) {
// #TODO rounding
Ui::FillRoundShadow(p, 0, 0, paintw, height(), sti->msgShadow, sti->msgShadowCornersSmall);
}
auto usex = 0, usew = paintw;
@ -381,42 +407,30 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
QRect rthumb(style::rtlrect(usex + paintx, painty, usew, painth, width()));
const auto roundRadius = isRound
? ImageRoundRadius::Ellipse
: unwrapped
? ImageRoundRadius::None
: inWebPage
? ImageRoundRadius::Small
: ImageRoundRadius::Large;
const auto roundCorners = isRound
? RectPart::AllCorners
: unwrapped
? RectPart::None
: inWebPage
? RectPart::AllCorners
: ((isBubbleTop()
? (RectPart::TopLeft | RectPart::TopRight)
: RectPart::None)
| ((isRoundedInBubbleBottom() && _caption.isEmpty())
? (RectPart::BottomLeft | RectPart::BottomRight)
: RectPart::None));
if (!bubble && !unwrapped) {
Assert(rounding.has_value());
fillImageShadow(p, rthumb, *rounding, context);
}
const auto skipDrawingContent = context.skipDrawingParts
== PaintContext::SkipDrawingParts::Content;
if (streamed && !skipDrawingContent) {
auto paused = context.paused;
auto request = ::Media::Streaming::FrameRequest{
.outer = QSize(usew, painth) * cIntRetinaFactor(),
.blurredBackground = true,
};
if (isRound) {
if (activeRoundStreamed()) {
paused = false;
} else {
displayMute = true;
}
validateRoundingMask(request.outer);
request.mask = _roundingMask;
} else {
request.rounding = prepareRoundingRef(rounding);
}
auto request = ::Media::Streaming::FrameRequest{
.outer = QSize(usew, painth) * cIntRetinaFactor(),
.radius = roundRadius,
.corners = roundCorners,
.blurredBackground = true,
};
if (!activeRoundPlaying && activeOwnPlaying->instance.playerLocked()) {
if (activeOwnPlaying->frozenFrame.isNull()) {
activeOwnPlaying->frozenRequest = request;
@ -465,12 +479,16 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
}
} else if (!skipDrawingContent) {
ensureDataMediaCreated();
validateThumbCache({ usew, painth }, roundRadius, roundCorners);
validateThumbCache({ usew, painth }, isRound, rounding);
p.drawImage(rthumb, _thumbCache);
}
if (context.selected()) {
Ui::FillComplexOverlayRect(p, st, rthumb, roundRadius, roundCorners);
if (isRound) {
Ui::FillComplexEllipse(p, st, rthumb);
} else {
fillImageOverlay(p, rthumb, rounding, context);
}
}
if (radial
@ -691,10 +709,8 @@ void Gif::validateVideoThumbnail() const {
void Gif::validateThumbCache(
QSize outer,
ImageRoundRadius radius,
RectParts corners) const {
const auto intRadius = static_cast<int>(radius);
const auto intCorners = static_cast<int>(corners);
bool isEllipse,
std::optional<Ui::BubbleRounding> rounding) const {
const auto good = _dataMedia->goodThumbnail();
const auto normal = good ? good : _dataMedia->thumbnail();
if (!normal) {
@ -708,24 +724,18 @@ void Gif::validateThumbCache(
&& (normal->height() < kUseNonBlurredThreshold))
: !videothumb;
const auto ratio = style::DevicePixelRatio();
const auto shouldBeBlurred = blurred ? 1 : 0;
if (_thumbCache.size() == (outer * ratio)
&& _thumbCacheRoundRadius == intRadius
&& _thumbCacheRoundCorners == intCorners
&& _thumbCacheBlurred == shouldBeBlurred) {
&& _thumbCacheRounding == rounding
&& _thumbCacheBlurred == blurred
&& _thumbIsEllipse == isEllipse) {
return;
}
_thumbCache = prepareThumbCache(outer, radius, corners);
_thumbCacheRoundRadius = intRadius;
_thumbCacheRoundCorners = intCorners;
_thumbCacheBlurred = shouldBeBlurred;
}
QImage Gif::prepareThumbCache(
QSize outer,
ImageRoundRadius radius,
RectParts corners) const {
return Images::Round(prepareThumbCache(outer), radius, corners);
auto cache = prepareThumbCache(outer);
_thumbCache = isEllipse
? Images::Circle(std::move(cache))
: Images::Round(std::move(cache), MediaRoundingMask(rounding));
_thumbCacheRounding = rounding;
_thumbCacheBlurred = blurred;
}
QImage Gif::prepareThumbCache(QSize outer) const {
@ -1071,8 +1081,8 @@ void Gif::drawGrouped(
auto request = ::Media::Streaming::FrameRequest{
.resize = pixSize * cIntRetinaFactor(),
.outer = geometry.size() * cIntRetinaFactor(),
.radius = roundRadius,
.corners = corners,
//.radius = roundRadius, // #TODO rounding
//.corners = corners,
};
if (activeOwnPlaying->instance.playerLocked()) {
if (activeOwnPlaying->frozenFrame.isNull()) {
@ -1104,10 +1114,11 @@ void Gif::drawGrouped(
: highlightOpacity;
if (overlayOpacity > 0.) {
p.setOpacity(overlayOpacity);
Ui::FillComplexOverlayRect(p, st, geometry, roundRadius, corners);
if (!context.selected()) {
Ui::FillComplexOverlayRect(p, st, geometry, roundRadius, corners);
}
// #TODO rounding
//Ui::FillComplexOverlayRect(p, st, geometry, roundRadius, corners);
//if (!context.selected()) {
// Ui::FillComplexOverlayRect(p, st, geometry, roundRadius, corners);
//}
p.setOpacity(1.);
}

View File

@ -163,12 +163,8 @@ private:
void validateThumbCache(
QSize outer,
ImageRoundRadius radius,
RectParts corners) const;
[[nodiscard]] QImage prepareThumbCache(
QSize outer,
ImageRoundRadius radius,
RectParts corners) const;
bool isEllipse,
std::optional<Ui::BubbleRounding> rounding) const;
[[nodiscard]] QImage prepareThumbCache(QSize outer) const;
void validateGroupedCache(
@ -180,6 +176,10 @@ private:
void updateStatusText() const;
[[nodiscard]] QSize sizeForAspectRatio() const;
void validateRoundingMask(QSize size) const;
[[nodiscard]] Images::CornersMaskRef prepareRoundingRef(
std::optional<Ui::BubbleRounding> rounding) const;
[[nodiscard]] bool downloadInCorner() const;
void drawCornerStatus(
Painter &p,
@ -197,9 +197,10 @@ private:
mutable std::unique_ptr<Image> _videoThumbnailFrame;
QString _downloadSize;
mutable QImage _thumbCache;
mutable int _thumbCacheRoundRadius : 4 = 0;
mutable int _thumbCacheRoundCorners : 12 = 0;
mutable int _thumbCacheBlurred : 1 = 0;
mutable QImage _roundingMask;
mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding;
mutable bool _thumbCacheBlurred = false;
mutable bool _thumbIsEllipse = false;
};

View File

@ -32,7 +32,6 @@ struct ColorReplacements;
namespace Ui {
struct BubbleSelectionInterval;
struct ChatPaintContext;
struct CornersMaskRef;
} // namespace Ui
namespace Images {

View File

@ -44,6 +44,7 @@ using Data::PhotoSize;
struct Photo::Streamed {
explicit Streamed(std::shared_ptr<::Media::Streaming::Document> shared);
::Media::Streaming::Instance instance;
QImage roundingMask;
QImage frozenFrame;
};
@ -410,7 +411,10 @@ void Photo::paintUserpicFrame(
auto request = ::Media::Streaming::FrameRequest();
request.outer = size * cIntRetinaFactor();
request.resize = size * cIntRetinaFactor();
request.radius = ImageRoundRadius::Ellipse;
if (_streamed->roundingMask.size() != request.outer) {
_streamed->roundingMask = Images::EllipseMask(size);
}
request.mask = _streamed->roundingMask;
if (_streamed->instance.playerLocked()) {
if (_streamed->frozenFrame.isNull()) {
_streamed->frozenFrame = _streamed->instance.frame(request);
@ -569,10 +573,11 @@ void Photo::drawGrouped(
if (overlayOpacity > 0.) {
p.setOpacity(overlayOpacity);
const auto roundRadius = ImageRoundRadius::Large;
Ui::FillComplexOverlayRect(p, st, geometry, roundRadius, corners);
if (!context.selected()) {
Ui::FillComplexOverlayRect(p, st, geometry, roundRadius, corners);
}
// #TODO rounding
//Ui::FillComplexOverlayRect(p, st, geometry, roundRadius, corners);
//if (!context.selected()) {
// Ui::FillComplexOverlayRect(p, st, geometry, roundRadius, corners);
//}
p.setOpacity(1.);
}

View File

@ -244,7 +244,10 @@ bool Float::fillFrame() {
if (const auto streamed = getStreamed()) {
auto request = Streaming::FrameRequest::NonStrict();
request.outer = request.resize = _frame.size();
request.radius = ImageRoundRadius::Ellipse;
if (_roundingMask.size() != request.outer) {
_roundingMask = Images::EllipseMask(frameInner().size());
}
request.mask = _roundingMask;
auto frame = streamed->frame(request);
if (!frame.isNull()) {
_frame.fill(Qt::transparent);

View File

@ -96,6 +96,7 @@ private:
float64 _opacity = 1.;
QPixmap _shadow;
QImage _roundingMask;
QImage _frame;
bool _down = false;
QPoint _downPoint;

View File

@ -8,10 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "data/data_audio_msg_id.h"
#include "ui/image/image_prepare.h"
#include "ui/rect_part.h"
enum class ImageRoundRadius;
namespace Media {
inline constexpr auto kTimeUnknown = std::numeric_limits<crl::time>::min();
@ -120,8 +119,8 @@ enum class Error {
struct FrameRequest {
QSize resize;
QSize outer;
ImageRoundRadius radius = ImageRoundRadius();
RectParts corners = RectPart::AllCorners;
Images::CornersMaskRef rounding;
QImage mask;
QColor colored = QColor(0, 0, 0, 0);
bool blurredBackground = false;
bool requireARGB32 = true;
@ -141,8 +140,8 @@ struct FrameRequest {
[[nodiscard]] bool operator==(const FrameRequest &other) const {
return (resize == other.resize)
&& (outer == other.outer)
&& (radius == other.radius)
&& (corners == other.corners)
&& (rounding == other.rounding)
&& (mask.constBits() == other.mask.constBits())
&& (colored == other.colored)
&& (keepAlpha == other.keepAlpha)
&& (requireARGB32 == other.requireARGB32)

View File

@ -101,8 +101,7 @@ bool GoodForRequest(
return true;
} else if (rotation != 0) {
return false;
} else if ((request.radius != ImageRoundRadius::None)
&& ((request.corners & RectPart::AllCorners) != 0)) {
} else if (!request.rounding.empty() || !request.mask.isNull()) {
return false;
}
const auto size = request.blurredBackground
@ -348,14 +347,15 @@ void PaintFrameContent(
}
void ApplyFrameRounding(QImage &storage, const FrameRequest &request) {
if (!(request.corners & RectPart::AllCorners)
|| (request.radius == ImageRoundRadius::None)) {
return;
if (!request.mask.isNull()) {
auto p = QPainter(&storage);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.drawImage(
QRect(QPoint(), storage.size() / storage.devicePixelRatio()),
request.mask);
} else if (!request.rounding.empty()) {
storage = Images::Round(std::move(storage), request.rounding);
}
storage = Images::Round(
std::move(storage),
request.radius,
request.corners);
}
ExpandDecision DecideFrameResize(

View File

@ -21,45 +21,43 @@ namespace {
if (!rotation) {
return request;
}
const auto unrotatedCorner = [&](RectPart corner) {
if (!(request.corners & corner)) {
return RectPart(0);
}
switch (corner) {
case RectPart::TopLeft:
const auto unrotatedCorner = [&](int index) {
using namespace Images;
switch (index) {
case kTopLeft:
return (rotation == 90)
? RectPart::BottomLeft
? kBottomLeft
: (rotation == 180)
? RectPart::BottomRight
: RectPart::TopRight;
case RectPart::TopRight:
? kBottomRight
: kTopRight;
case kTopRight:
return (rotation == 90)
? RectPart::TopLeft
? kTopLeft
: (rotation == 180)
? RectPart::BottomLeft
: RectPart::BottomRight;
case RectPart::BottomRight:
? kBottomLeft
: kBottomRight;
case kBottomRight:
return (rotation == 90)
? RectPart::TopRight
? kTopRight
: (rotation == 180)
? RectPart::TopLeft
: RectPart::BottomLeft;
case RectPart::BottomLeft:
? kTopLeft
: kBottomLeft;
case kBottomLeft:
return (rotation == 90)
? RectPart::BottomRight
? kBottomRight
: (rotation == 180)
? RectPart::TopRight
: RectPart::TopLeft;
? kTopRight
: kTopLeft;
}
Unexpected("Corner in rotateCorner.");
};
auto result = request;
result.outer = FlipSizeByRotation(request.outer, rotation);
result.resize = FlipSizeByRotation(request.resize, rotation);
result.corners = unrotatedCorner(RectPart::TopLeft)
| unrotatedCorner(RectPart::TopRight)
| unrotatedCorner(RectPart::BottomRight)
| unrotatedCorner(RectPart::BottomLeft);
auto rounding = result.rounding;
for (auto i = 0; i != 4; ++i) {
result.rounding.p[unrotatedCorner(i)] = rounding.p[i];
}
return result;
}
@ -137,23 +135,23 @@ void Pip::RendererSW::paintButton(
Pip::FrameRequest Pip::RendererSW::frameRequest(
ContentGeometry geometry) const {
using namespace Images;
auto result = FrameRequest();
result.outer = geometry.inner.size() * style::DevicePixelRatio();
result.resize = result.outer;
result.corners = RectPart(0)
| ((geometry.attached & (RectPart::Left | RectPart::Top))
? RectPart(0)
: RectPart::TopLeft)
| ((geometry.attached & (RectPart::Top | RectPart::Right))
? RectPart(0)
: RectPart::TopRight)
| ((geometry.attached & (RectPart::Right | RectPart::Bottom))
? RectPart(0)
: RectPart::BottomRight)
| ((geometry.attached & (RectPart::Bottom | RectPart::Left))
? RectPart(0)
: RectPart::BottomLeft);
result.radius = ImageRoundRadius::Large;
result.rounding = CornersMaskRef(CornersMask(ImageRoundRadius::Large));
if (geometry.attached & (RectPart::Top | RectPart::Left)) {
result.rounding.p[kTopLeft] = nullptr;
}
if (geometry.attached & (RectPart::Top | RectPart::Right)) {
result.rounding.p[kTopRight] = nullptr;
}
if (geometry.attached & (RectPart::Bottom | RectPart::Left)) {
result.rounding.p[kBottomLeft] = nullptr;
}
if (geometry.attached & (RectPart::Bottom | RectPart::Right)) {
result.rounding.p[kBottomRight] = nullptr;
}
return UnrotateRequest(result, geometry.rotation);
}
@ -175,19 +173,11 @@ QImage Pip::RendererSW::staticContentByRequest(
// _instance.info().video.rotation,
// request,
// std::move(_preparedCoverStorage));
using Option = Images::Option;
const auto corner = [&](RectPart part, Option skip) {
return !(request.corners & part) ? skip : Option();
};
const auto options = Option::RoundLarge
| corner(RectPart::TopLeft, Option::RoundSkipTopLeft)
| corner(RectPart::TopRight, Option::RoundSkipTopRight)
| corner(RectPart::BottomLeft, Option::RoundSkipBottomLeft)
| corner(RectPart::BottomRight, Option::RoundSkipBottomRight);
_preparedStaticContent = Images::Prepare(
_preparedStaticContent = Images::Round(Images::Prepare(
image,
request.resize,
{ .options = options, .outer = request.outer });
{ .outer = request.outer }), request.rounding);
return _preparedStaticContent;
}

View File

@ -161,10 +161,6 @@ void FillRoundRect(QPainter &p, int32 x, int32 y, int32 w, int32 h, style::color
FillRoundRect(p, x, y, w, h, bg, Corners[index], shadow, parts);
}
void FillRoundShadow(QPainter &p, int32 x, int32 y, int32 w, int32 h, style::color shadow, CachedRoundCorners index) {
FillRoundShadow(p, x, y, w, h, shadow, Corners[index]);
}
void FillRoundShadow(QPainter &p, int32 x, int32 y, int32 w, int32 h, style::color shadow, const CornersPixmaps &corners) {
constexpr auto kLeft = 2;
constexpr auto kRight = 3;

View File

@ -40,10 +40,6 @@ void FillRoundRect(QPainter &p, int32 x, int32 y, int32 w, int32 h, style::color
inline void FillRoundRect(QPainter &p, const QRect &rect, style::color bg, CachedRoundCorners index, const style::color *shadow = nullptr, RectParts parts = RectPart::Full) {
FillRoundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, index, shadow, parts);
}
void FillRoundShadow(QPainter &p, int32 x, int32 y, int32 w, int32 h, style::color shadow, CachedRoundCorners index);
inline void FillRoundShadow(QPainter &p, const QRect &rect, style::color shadow, CachedRoundCorners index) {
FillRoundShadow(p, rect.x(), rect.y(), rect.width(), rect.height(), shadow, index);
}
void FillRoundRect(QPainter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, ImageRoundRadius radius, RectParts parts = RectPart::Full);
inline void FillRoundRect(QPainter &p, const QRect &rect, style::color bg, ImageRoundRadius radius, RectParts parts = RectPart::Full) {
FillRoundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, radius, parts);

View File

@ -693,10 +693,7 @@ void FillComplexOverlayRect(
QRect rect,
const style::color &color,
const CornersPixmaps &corners) {
constexpr auto kTopLeft = 0;
constexpr auto kTopRight = 1;
constexpr auto kBottomLeft = 2;
constexpr auto kBottomRight = 3;
using namespace Images;
const auto pix = corners.p;
const auto fillRect = [&](QRect rect) {
@ -795,36 +792,14 @@ void FillComplexOverlayRect(
}
}
void FillComplexOverlayRect(
void FillComplexEllipse(
QPainter &p,
not_null<const ChatStyle*> st,
QRect rect,
ImageRoundRadius radius,
RectParts roundCorners) {
const auto bg = st->msgSelectOverlay();
if (radius == ImageRoundRadius::Ellipse) {
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(bg);
p.drawEllipse(rect);
} else {
// #TODO rounding
//const auto &corners = (radius == ImageRoundRadius::Small)
// ? st->msgSelectOverlayCornersSmall()
// : st->msgSelectOverlayCornersLarge();
//RectWithCorners(p, rect, bg, corners, roundCorners);
}
}
void FillComplexLocationRect(
QPainter &p,
not_null<const ChatStyle*> st,
QRect rect,
ImageRoundRadius radius,
RectParts roundCorners) {
const auto stm = &st->messageStyle(false, false);
// #TODO rounding
RectWithCorners(p, rect, stm->msgBg, stm->msgBgCornersSmall, roundCorners);
QRect rect) {
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(st->msgSelectOverlay());
p.drawEllipse(rect);
}
} // namespace Ui

View File

@ -360,17 +360,9 @@ void FillComplexOverlayRect(
const style::color &color,
const CornersPixmaps &corners);
void FillComplexOverlayRect(
void FillComplexEllipse(
QPainter &p,
not_null<const ChatStyle*> st,
QRect rect,
ImageRoundRadius radius,
RectParts roundCorners);
void FillComplexLocationRect(
QPainter &p,
not_null<const ChatStyle*> st,
QRect rect,
ImageRoundRadius radius,
RectParts roundCorners);
QRect rect);
} // namespace Ui

View File

@ -478,7 +478,10 @@ void UserpicButton::paintUserpicFrame(Painter &p, QPoint photoPosition) {
auto size = QSize{ _st.photoSize, _st.photoSize };
request.outer = size * cIntRetinaFactor();
request.resize = size * cIntRetinaFactor();
request.radius = ImageRoundRadius::Ellipse;
if (_ellipseMask.size() != request.outer) {
_ellipseMask = Images::EllipseMask(size);
}
request.mask = _ellipseMask;
p.drawImage(QRect(photoPosition, size), _streamed->frame(request));
if (!paused) {
_streamed->markFrameShown();

View File

@ -170,6 +170,7 @@ private:
InMemoryKey _userpicUniqueKey;
Ui::Animations::Simple _a_appearance;
QImage _result;
QImage _ellipseMask;
std::unique_ptr<Media::Streaming::Instance> _streamed;
PhotoData *_streamedPhoto = nullptr;

@ -1 +1 @@
Subproject commit cec09b0260ba19639bf9abc7df373569aa1509e7
Subproject commit 2c2a7887e644dff7130d764c5a7b04aaa0463056