Initial implementation of ExtendedMedia.

This commit is contained in:
John Preston 2022-09-08 13:17:31 +04:00
parent 2523d6e8d8
commit 20b5138e00
14 changed files with 659 additions and 77 deletions

View File

@ -627,6 +627,8 @@ PRIVATE
history/view/media/history_view_dice.h
history/view/media/history_view_document.cpp
history/view/media/history_view_document.h
history/view/media/history_view_extended_preview.cpp
history/view/media/history_view_extended_preview.h
history/view/media/history_view_file.cpp
history/view/media/history_view_file.h
history/view/media/history_view_game.cpp

View File

@ -9,9 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_message.h" // CreateMedia.
#include "history/history_location_manager.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_item_preview.h"
#include "history/view/media/history_view_extended_preview.h"
#include "history/view/media/history_view_photo.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_gif.h"
@ -245,10 +247,10 @@ TextForMimeData WithCaptionClipboardText(
}
Invoice ComputeInvoiceData(
not_null<HistoryItem*> item,
not_null<HistoryMessage*> item,
const MTPDmessageMediaInvoice &data) {
auto description = qs(data.vdescription());
return {
auto result = Invoice{
.receiptMsgId = data.vreceipt_msg_id().value_or_empty(),
.amount = data.vtotal_amount().v,
.currency = qs(data.vcurrency()),
@ -263,6 +265,31 @@ Invoice ComputeInvoiceData(
: nullptr),
.isTest = data.is_test(),
};
if (const auto &media = data.vextended_media()) {
media->match([&](const MTPDmessageExtendedMediaPreview &data) {
auto &preview = result.extendedPreview;
if (const auto &w = data.vw()) {
const auto &h = data.vh();
Assert(h.has_value());
preview.dimensions = QSize(w->v, h->v);
}
if (const auto &thumb = data.vthumb()) {
if (thumb->type() == mtpc_photoStrippedSize) {
preview.inlineThumbnailBytes
= thumb->c_photoStrippedSize().vbytes().v;
}
}
if (const auto &duration = data.vvideo_duration()) {
preview.videoDuration = duration->v;
}
}, [&](const MTPDmessageExtendedMedia &data) {
result.extendedMedia = HistoryMessage::CreateMedia(
item,
data.vmedia());
});
}
return result;
}
Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
@ -1479,7 +1506,19 @@ MediaInvoice::MediaInvoice(
not_null<HistoryItem*> parent,
const Invoice &data)
: Media(parent)
, _invoice(data) {
, _invoice{
.receiptMsgId = data.receiptMsgId,
.amount = data.amount,
.currency = data.currency,
.title = data.title,
.description = data.description,
.extendedPreview = data.extendedPreview,
.extendedMedia = (data.extendedMedia
? data.extendedMedia->clone(parent)
: nullptr),
.photo = data.photo,
.isTest = data.isTest,
} {
}
std::unique_ptr<Media> MediaInvoice::clone(not_null<HistoryItem*> parent) {
@ -1537,6 +1576,16 @@ std::unique_ptr<HistoryView::Media> MediaInvoice::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
if (_invoice.extendedMedia) {
return _invoice.extendedMedia->createView(
message,
realParent,
replacing);
} else if (!_invoice.extendedPreview.dimensions.isEmpty()) {
return std::make_unique<HistoryView::ExtendedPreview>(
message,
&_invoice);
}
return std::make_unique<HistoryView::Invoice>(message, &_invoice);
}

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class Image;
class History;
class HistoryItem;
class HistoryMessage;
namespace base {
template <typename Enum>
@ -58,12 +59,22 @@ struct Call {
bool video = false;
};
struct ExtendedPreview {
QByteArray inlineThumbnailBytes;
QSize dimensions;
TimeId videoDuration = -1;
};
class Media;
struct Invoice {
MsgId receiptMsgId = 0;
uint64 amount = 0;
QString currency;
QString title;
TextWithEntities description;
ExtendedPreview extendedPreview;
std::unique_ptr<Media> extendedMedia;
PhotoData *photo = nullptr;
bool isTest = false;
};
@ -515,7 +526,7 @@ private:
TextForMimeData &&caption);
[[nodiscard]] Invoice ComputeInvoiceData(
not_null<HistoryItem*> item,
not_null<HistoryMessage*> item,
const MTPDmessageMediaInvoice &data);
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);

View File

@ -1063,6 +1063,11 @@ void HistoryMessage::setupForwardedComponent(const CreateConfig &config) {
void HistoryMessage::refreshMedia(const MTPMessageMedia *media) {
const auto was = (_media != nullptr);
if (const auto invoice = was ? _media->invoice() : nullptr) {
if (invoice->extendedMedia) {
return;
}
}
_media = nullptr;
if (media) {
setMedia(*media);

View File

@ -229,10 +229,6 @@ struct Message::CommentsButton {
QImage cachedUserpics;
ClickHandlerPtr link;
QPoint lastPoint;
QString rightActionCountString;
int rightActionCount = 0;
int rightActionCountWidth = 0;
};
struct Message::FromNameStatus {

View File

@ -0,0 +1,403 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/media/history_view_extended_preview.h"
//#include "history/history_item_components.h"
#include "history/history_item.h"
#include "history/history.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/media/history_view_media_common.h"
//#include "main/main_session.h"
//#include "main/main_session_settings.h"
#include "media/streaming/media_streaming_utility.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/image/image.h"
#include "ui/chat/chat_style.h"
//#include "ui/cached_round_corners.h"
#include "data/data_session.h"
//#include "data/data_streaming.h"
//#include "data/data_photo.h"
//#include "data/data_photo_media.h"
//#include "data/data_file_click_handler.h"
//#include "data/data_file_origin.h"
//#include "data/data_auto_download.h"
//#include "core/application.h"
#include "payments/payments_checkout_process.h"
#include "window/window_session_controller.h"
#include "mainwindow.h"
#include "core/click_handler_types.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
[[nodiscard]] ClickHandlerPtr MakeInvoiceLink(not_null<HistoryItem*> item) {
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get();
Payments::CheckoutProcess::Start(
item,
Payments::Mode::Payment,
(controller
? crl::guard(
controller,
[=](auto) { controller->widget()->activate(); })
: Fn<void(Payments::CheckoutResult)>()));
});
}
} // namespace
ExtendedPreview::ExtendedPreview(
not_null<Element*> parent,
not_null<Data::Invoice*> invoice)
: Media(parent)
, _invoice(invoice)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
const auto item = parent->data();
_caption = createCaption(item);
_link = MakeInvoiceLink(item);
}
ExtendedPreview::~ExtendedPreview() = default;
void ExtendedPreview::ensureThumbnailRead() const {
if (!_inlineThumbnail.isNull() || _imageCacheInvalid) {
return;
}
const auto &bytes = _invoice->extendedPreview.inlineThumbnailBytes;
if (bytes.isEmpty()) {
return;
}
_inlineThumbnail = Images::FromInlineBytes(bytes);
if (_inlineThumbnail.isNull()) {
_imageCacheInvalid = 1;
} else {
history()->owner().registerHeavyViewPart(_parent);
}
}
bool ExtendedPreview::hasHeavyPart() const {
return !_inlineThumbnail.isNull();
}
void ExtendedPreview::unloadHeavyPart() {
_inlineThumbnail = _imageCache = QImage();
_caption.unloadCustomEmoji();
}
QSize ExtendedPreview::countOptimalSize() {
if (_parent->media() != this) {
_caption = Ui::Text::String();
} else if (_caption.hasSkipBlock()) {
_caption.updateSkipBlock(
_parent->skipBlockWidth(),
_parent->skipBlockHeight());
}
const auto &preview = _invoice->extendedPreview;
const auto dimensions = preview.dimensions;
const auto minWidth = std::clamp(
_parent->minWidthForMedia(),
(_parent->hasBubble()
? st::historyPhotoBubbleMinWidth
: st::minPhotoSize),
st::maxMediaSize);
const auto scaled = CountDesiredMediaSize(dimensions);
auto maxWidth = qMax(scaled.width(), minWidth);
auto minHeight = qMax(scaled.height(), st::minPhotoSize);
if (preview.videoDuration < 0) {
accumulate_max(maxWidth, scaled.height());
}
if (_parent->hasBubble() && !_caption.isEmpty()) {
maxWidth = qMax(maxWidth, st::msgPadding.left()
+ _caption.maxWidth()
+ st::msgPadding.right());
minHeight += st::mediaCaptionSkip + _caption.minHeight();
if (isBubbleBottom()) {
minHeight += st::msgPadding.bottom();
}
}
return { maxWidth, minHeight };
}
QSize ExtendedPreview::countCurrentSize(int newWidth) {
const auto &preview = _invoice->extendedPreview;
const auto dimensions = preview.dimensions;
const auto minWidth = std::clamp(
_parent->minWidthForMedia(),
(_parent->hasBubble()
? st::historyPhotoBubbleMinWidth
: st::minPhotoSize),
std::min(newWidth, st::maxMediaSize));
const auto scaled = (preview.videoDuration >= 0)
? CountMediaSize(
CountDesiredMediaSize(dimensions),
newWidth)
: CountPhotoMediaSize(
CountDesiredMediaSize(dimensions),
newWidth,
maxWidth());
newWidth = qMax(scaled.width(), minWidth);
auto newHeight = qMax(scaled.height(), st::minPhotoSize);
if (_parent->hasBubble() && !_caption.isEmpty()) {
const auto maxWithCaption = qMin(
st::msgMaxWidth,
(st::msgPadding.left()
+ _caption.maxWidth()
+ st::msgPadding.right()));
newWidth = qMax(newWidth, maxWithCaption);
const auto captionw = newWidth
- st::msgPadding.left()
- st::msgPadding.right();
newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
if (isBubbleBottom()) {
newHeight += st::msgPadding.bottom();
}
}
return { newWidth, newHeight };
}
void ExtendedPreview::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
const auto st = context.st;
const auto sti = context.imageStyle();
const auto stm = context.messageStyle();
auto paintx = 0, painty = 0, paintw = width(), painth = height();
auto bubble = _parent->hasBubble();
auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right();
auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width());
if (bubble) {
if (!_caption.isEmpty()) {
painth -= st::mediaCaptionSkip + _caption.countHeight(captionw);
if (isBubbleBottom()) {
painth -= st::msgPadding.bottom();
}
rthumb = style::rtlrect(paintx, painty, paintw, painth, width());
}
} else {
Ui::FillRoundShadow(p, 0, 0, paintw, painth, sti->msgShadow, sti->msgShadowCorners);
}
const auto inWebPage = (_parent->media() != this);
const auto roundRadius = inWebPage
? ImageRoundRadius::Small
: ImageRoundRadius::Large;
const auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
| ((isRoundedInBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None));
validateImageCache(rthumb.size(), roundRadius, roundCorners);
p.drawImage(rthumb.topLeft(), _imageCache);
fillSpoilerMess(p, rthumb, roundRadius, roundCorners);
if (context.selected()) {
Ui::FillComplexOverlayRect(p, st, rthumb, roundRadius, roundCorners);
}
const auto innerSize = st::msgFileLayout.thumbSize;
QRect inner(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize);
p.setPen(Qt::NoPen);
if (context.selected()) {
p.setBrush(st->msgDateImgBgSelected());
} else {
const auto over = ClickHandler::showAsActive(_link);
p.setBrush(over ? st->msgDateImgBgOver() : st->msgDateImgBg());
}
{
PainterHighQualityEnabler hq(p);
p.drawEllipse(inner);
}
// date
if (!_caption.isEmpty()) {
p.setPen(stm->historyTextFg);
_parent->prepareCustomEmojiPaint(p, _caption);
_caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, context.selection);
} else if (!inWebPage) {
auto fullRight = paintx + paintw;
auto fullBottom = painty + painth;
if (needInfoDisplay()) {
_parent->drawInfo(
p,
context,
fullRight,
fullBottom,
2 * paintx + paintw,
InfoDisplayType::Image);
}
if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) {
auto fastShareLeft = (fullRight + st::historyFastShareLeft);
auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height());
_parent->drawRightAction(p, context, fastShareLeft, fastShareTop, 2 * paintx + paintw);
}
}
}
void ExtendedPreview::validateImageCache(
QSize outer,
ImageRoundRadius radius,
RectParts corners) const {
const auto intRadius = static_cast<int>(radius);
const auto intCorners = static_cast<int>(corners);
const auto ratio = style::DevicePixelRatio();
if (_imageCache.size() == (outer * ratio)
&& _imageCacheRoundRadius == intRadius
&& _imageCacheRoundCorners == intCorners) {
return;
}
_imageCache = prepareImageCache(outer, radius, corners);
_imageCacheRoundRadius = intRadius;
_imageCacheRoundCorners = intCorners;
}
QImage ExtendedPreview::prepareImageCache(
QSize outer,
ImageRoundRadius radius,
RectParts corners) const {
return Images::Round(prepareImageCache(outer), radius, corners);
}
QImage ExtendedPreview::prepareImageCache(QSize outer) const {
ensureThumbnailRead();
return PrepareWithBlurredBackground(outer, {}, {}, _inlineThumbnail);
}
void ExtendedPreview::fillSpoilerMess(
QPainter &p,
QRect rect,
ImageRoundRadius radius,
RectParts corners) const {
const auto size = style::ConvertScale(100);
static const auto test = [&] {
const auto ratio = style::DevicePixelRatio();
return Ui::GenerateSpoilerMess({
.particleFadeInDuration = 200,
.particleFadeOutDuration = 200,
.particleSizeMin = style::ConvertScaleExact(1.5) * ratio,
.particleSizeMax = style::ConvertScaleExact(2.) * ratio,
.particleSpritesCount = 5,
.particlesCount = 2000,
.canvasSize = size * ratio,
.framesCount = 60,
.frameDuration = 33,
});
}();
const auto frame = test.frame();
const auto columns = (rect.width() + size - 1) / size;
const auto rows = (rect.height() + size - 1) / size;
p.setClipRect(rect);
p.translate(rect.topLeft());
for (auto y = 0; y != rows; ++y) {
for (auto x = 0; x != columns; ++x) {
p.drawImage(
QRect(x * size, y * size, size, size),
*frame.image,
frame.source);
}
}
p.translate(-rect.topLeft());
p.setClipping(false);
}
TextState ExtendedPreview::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
return result;
}
auto paintx = 0, painty = 0, paintw = width(), painth = height();
auto bubble = _parent->hasBubble();
if (bubble && !_caption.isEmpty()) {
const auto captionw = paintw
- st::msgPadding.left()
- st::msgPadding.right();
painth -= _caption.countHeight(captionw);
if (isBubbleBottom()) {
painth -= st::msgPadding.bottom();
}
if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) {
result = TextState(_parent, _caption.getState(
point - QPoint(st::msgPadding.left(), painth),
captionw,
request.forText()));
return result;
}
painth -= st::mediaCaptionSkip;
}
if (QRect(paintx, painty, paintw, painth).contains(point)) {
result.link = _link;
}
if (_caption.isEmpty() && _parent->media() == this) {
auto fullRight = paintx + paintw;
auto fullBottom = painty + painth;
const auto bottomInfoResult = _parent->bottomInfoTextState(
fullRight,
fullBottom,
point,
InfoDisplayType::Image);
if (bottomInfoResult.link
|| bottomInfoResult.cursor != CursorState::None) {
return bottomInfoResult;
}
if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) {
auto fastShareLeft = (fullRight + st::historyFastShareLeft);
auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height());
if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) {
result.link = _parent->rightActionLink();
}
}
}
return result;
}
bool ExtendedPreview::toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const {
return p == _link;
}
bool ExtendedPreview::dragItemByHandler(const ClickHandlerPtr &p) const {
return p == _link;
}
bool ExtendedPreview::needInfoDisplay() const {
return _parent->data()->isSending()
|| _parent->data()->hasFailed()
|| _parent->isUnderCursor()
|| _parent->isLastAndSelfMessage();
}
TextForMimeData ExtendedPreview::selectedText(TextSelection selection) const {
return _caption.toTextForMimeData(selection);
}
bool ExtendedPreview::needsBubble() const {
if (!_caption.isEmpty()) {
return true;
}
const auto item = _parent->data();
return !item->isService()
&& (item->repliesAreComments()
|| item->externalReply()
|| item->viaBot()
|| _parent->displayedReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName());
}
QPoint ExtendedPreview::resolveCustomInfoRightBottom() const {
const auto skipx = (st::msgDateImgDelta + st::msgDateImgPadding.x());
const auto skipy = (st::msgDateImgDelta + st::msgDateImgPadding.y());
return QPoint(width() - skipx, height() - skipy);
}
void ExtendedPreview::parentTextUpdated() {
_caption = (_parent->media() == this)
? createCaption(_parent->data())
: Ui::Text::String();
history()->owner().requestViewResize(_parent);
}
} // namespace HistoryView

View File

@ -0,0 +1,102 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "history/view/media/history_view_media.h"
enum class ImageRoundRadius;
namespace Data {
struct Invoice;
} // namespace Data
namespace HistoryView {
class Element;
class ExtendedPreview final : public Media {
public:
ExtendedPreview(
not_null<Element*> parent,
not_null<Data::Invoice*> invoice);
~ExtendedPreview();
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
[[nodiscard]] bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const override;
[[nodiscard]] bool dragItemByHandler(
const ClickHandlerPtr &p) const override;
[[nodiscard]] TextSelection adjustSelection(
TextSelection selection,
TextSelectType type) const override {
return _caption.adjustSelection(selection, type);
}
uint16 fullSelectionLength() const override {
return _caption.length();
}
bool hasTextForCopy() const override {
return !_caption.isEmpty();
}
TextForMimeData selectedText(TextSelection selection) const override;
TextWithEntities getCaption() const override {
return _caption.toTextWithEntities();
}
bool needsBubble() const override;
bool customInfoLayout() const override {
return _caption.isEmpty();
}
QPoint resolveCustomInfoRightBottom() const override;
bool skipBubbleTail() const override {
return isRoundedInBubbleBottom() && _caption.isEmpty();
}
void parentTextUpdated() override;
bool hasHeavyPart() const override;
void unloadHeavyPart() override;
private:
void ensureThumbnailRead() const;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
bool needInfoDisplay() const;
void validateImageCache(
QSize outer,
ImageRoundRadius radius,
RectParts corners) const;
[[nodiscard]] QImage prepareImageCache(
QSize outer,
ImageRoundRadius radius,
RectParts corners) const;
[[nodiscard]] QImage prepareImageCache(QSize outer) const;
void fillSpoilerMess(
QPainter &p,
QRect rect,
ImageRoundRadius radius,
RectParts corners) const;
const not_null<Data::Invoice*> _invoice;
ClickHandlerPtr _link;
Ui::Text::String _caption;
mutable QImage _inlineThumbnail;
mutable QImage _imageCache;
mutable int _imageCacheRoundRadius : 4 = 0;
mutable int _imageCacheRoundCorners : 12 = 0;
mutable int _imageCacheInvalid : 1 = 0;
};
} // namespace HistoryView

View File

@ -22,6 +22,7 @@ namespace HistoryView {
bool File::toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const {
return p == _openl || p == _savel || p == _cancell;
}
bool File::dragItemByHandler(const ClickHandlerPtr &p) const {
return p == _openl || p == _savel || p == _cancell;
}

View File

@ -131,10 +131,9 @@ QSize Gif::countThumbSize(int &inOutWidthMax) const {
: _data->isVideoMessage()
? st::maxVideoMessageSize
: st::maxGifSize;
const auto useMaxSize = std::max(maxSize, st::minPhotoSize);
const auto size = style::ConvertScale(videoSize());
accumulate_min(inOutWidthMax, useMaxSize);
return DownscaledSize(size, { inOutWidthMax, useMaxSize });
accumulate_min(inOutWidthMax, maxSize);
return DownscaledSize(size, { inOutWidthMax, maxSize });
}
QSize Gif::countOptimalSize() {
@ -146,20 +145,22 @@ QSize Gif::countOptimalSize() {
_parent->skipBlockHeight());
}
const auto minWidth = std::clamp(
_parent->minWidthForMedia(),
(_parent->hasBubble()
? st::historyPhotoBubbleMinWidth
: st::minPhotoSize),
st::maxMediaSize);
auto thumbMaxWidth = st::msgMaxWidth;
const auto scaled = countThumbSize(thumbMaxWidth);
const auto minWidthByInfo = _parent->infoWidth()
+ 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x());
auto maxWidth = std::clamp(
std::max(scaled.width(), minWidthByInfo),
st::minPhotoSize,
auto maxWidth = std::min(
std::max(scaled.width(), minWidth),
thumbMaxWidth);
auto minHeight = qMax(scaled.height(), st::minPhotoSize);
if (!activeCurrentStreamed()) {
accumulate_max(maxWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
}
if (_parent->hasBubble()) {
accumulate_max(maxWidth, _parent->minWidthForMedia());
if (!_caption.isEmpty()) {
maxWidth = qMax(maxWidth, st::msgPadding.left()
+ _caption.maxWidth()

View File

@ -98,9 +98,21 @@ QImage PrepareWithBlurredBackground(
::Media::Streaming::ExpandDecision resize,
Image *large,
Image *blurred) {
return PrepareWithBlurredBackground(
outer,
resize,
large ? large->original() : QImage(),
blurred ? blurred->original() : QImage());
}
QImage PrepareWithBlurredBackground(
QSize outer,
::Media::Streaming::ExpandDecision resize,
QImage large,
QImage blurred) {
const auto ratio = style::DevicePixelRatio();
if (resize.expanding) {
return Images::Prepare(large->original(), resize.result * ratio, {
return Images::Prepare(std::move(large), resize.result * ratio, {
.outer = outer,
});
}
@ -108,19 +120,19 @@ QImage PrepareWithBlurredBackground(
outer * ratio,
QImage::Format_ARGB32_Premultiplied);
background.setDevicePixelRatio(ratio);
if (!blurred) {
if (blurred.isNull()) {
background.fill(Qt::black);
if (!large) {
if (large.isNull()) {
return background;
}
}
auto p = QPainter(&background);
if (blurred) {
if (!blurred.isNull()) {
using namespace ::Media::Streaming;
FillBlurredBackground(p, outer, blurred->original());
FillBlurredBackground(p, outer, std::move(blurred));
}
if (large) {
auto image = large->original().scaled(
if (!large.isNull()) {
auto image = large.scaled(
resize.result * ratio,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
@ -134,4 +146,30 @@ QImage PrepareWithBlurredBackground(
return background;
}
QSize CountDesiredMediaSize(QSize original) {
return DownscaledSize(
style::ConvertScale(original),
{ st::maxMediaSize, st::maxMediaSize });
}
QSize CountMediaSize(QSize desired, int newWidth) {
Expects(!desired.isEmpty());
return (desired.width() <= newWidth)
? desired
: NonEmptySize(
desired.scaled(newWidth, desired.height(), Qt::KeepAspectRatio));
}
QSize CountPhotoMediaSize(
QSize desired,
int newWidth,
int maxWidth) {
const auto media = CountMediaSize(desired, qMin(newWidth, maxWidth));
return (media.height() <= newWidth)
? media
: NonEmptySize(
media.scaled(media.width(), newWidth, Qt::KeepAspectRatio));
}
} // namespace HistoryView

View File

@ -62,5 +62,17 @@ void PaintInterpolatedIcon(
::Media::Streaming::ExpandDecision resize,
Image *large,
Image *blurred);
[[nodiscard]] QImage PrepareWithBlurredBackground(
QSize outer,
::Media::Streaming::ExpandDecision resize,
QImage large,
QImage blurred);
[[nodiscard]] QSize CountDesiredMediaSize(QSize original);
[[nodiscard]] QSize CountMediaSize(QSize desired, int newWidth);
[[nodiscard]] QSize CountPhotoMediaSize(
QSize desired,
int newWidth,
int maxWidth);
} // namespace HistoryView

View File

@ -139,6 +139,10 @@ void Photo::unloadHeavyPart() {
}
QSize Photo::countOptimalSize() {
if (_serviceWidth > 0) {
return { _serviceWidth, _serviceWidth };
}
if (_parent->media() != this) {
_caption = Ui::Text::String();
} else if (_caption.hasSkipBlock()) {
@ -147,33 +151,15 @@ QSize Photo::countOptimalSize() {
_parent->skipBlockHeight());
}
auto maxWidth = 0;
auto minHeight = 0;
auto tw = style::ConvertScale(_data->width());
auto th = style::ConvertScale(_data->height());
if (!tw || !th) {
tw = th = 1;
}
if (tw > st::maxMediaSize) {
th = (st::maxMediaSize * th) / tw;
tw = st::maxMediaSize;
}
if (th > st::maxMediaSize) {
tw = (st::maxMediaSize * tw) / th;
th = st::maxMediaSize;
}
if (_serviceWidth > 0) {
return { _serviceWidth, _serviceWidth };
}
const auto scaled = CountDesiredMediaSize(
{ _data->width(), _data->height() });
const auto minWidth = std::clamp(
_parent->minWidthForMedia(),
(_parent->hasBubble() ? st::historyPhotoBubbleMinWidth : st::minPhotoSize),
st::maxMediaSize);
const auto maxActualWidth = qMax(tw, minWidth);
maxWidth = qMax(maxActualWidth, th);
minHeight = qMax(th, st::minPhotoSize);
const auto maxActualWidth = qMax(scaled.width(), minWidth);
auto maxWidth = qMax(maxActualWidth, scaled.height());
auto minHeight = qMax(scaled.height(), st::minPhotoSize);
if (_parent->hasBubble() && !_caption.isEmpty()) {
maxWidth = qMax(maxWidth, st::msgPadding.left()
+ _caption.maxWidth()
@ -186,32 +172,6 @@ QSize Photo::countOptimalSize() {
return { maxWidth, minHeight };
}
QSize Photo::pixmapSizeFromData(int newWidth) const {
auto tw = style::ConvertScale(_data->width());
auto th = style::ConvertScale(_data->height());
if (tw > st::maxMediaSize) {
th = (st::maxMediaSize * th) / tw;
tw = st::maxMediaSize;
}
if (th > st::maxMediaSize) {
tw = (st::maxMediaSize * tw) / th;
th = st::maxMediaSize;
}
auto pixw = qMin(newWidth, maxWidth());
auto pixh = th;
if (tw > pixw) {
pixh = (pixw * pixh / tw);
} else {
pixw = tw;
}
if (pixh > newWidth) {
pixw = (pixw * newWidth) / pixh;
pixh = newWidth;
}
return { pixw, pixh };
}
QSize Photo::countCurrentSize(int newWidth) {
if (_serviceWidth) {
return { _serviceWidth, _serviceWidth };
@ -220,7 +180,10 @@ QSize Photo::countCurrentSize(int newWidth) {
_parent->minWidthForMedia(),
(_parent->hasBubble() ? st::historyPhotoBubbleMinWidth : st::minPhotoSize),
std::min(newWidth, st::maxMediaSize));
auto pix = pixmapSizeFromData(newWidth);
auto pix = CountPhotoMediaSize(
CountDesiredMediaSize({ _data->width(), _data->height() }),
newWidth,
maxWidth());
newWidth = qMax(pix.width(), minWidth);
auto newHeight = qMax(pix.height(), st::minPhotoSize);
if (_parent->hasBubble() && !_caption.isEmpty()) {

View File

@ -116,7 +116,6 @@ private:
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
[[nodiscard]] QSize pixmapSizeFromData(int newWidth) const;
bool needInfoDisplay() const;
void validateGroupedCache(

View File

@ -590,8 +590,8 @@ bool FFMpegLoader::seekTo(crl::time positionMs) {
}
AudioPlayerLoader::ReadResult FFMpegLoader::readMore(
QByteArray & result,
int64 & samplesAdded) {
QByteArray &result,
int64 &samplesAdded) {
const auto readResult = readFromReadyContext(
_codecContext,
result,