Move Webm sticker to UnwrappedMedia.

This commit is contained in:
John Preston 2022-08-04 13:35:08 +03:00
parent 5b0d023a88
commit f8e22210e7
33 changed files with 458 additions and 300 deletions

View file

@ -1283,7 +1283,7 @@ void StickerSetBox::Inner::paintSticker(
_lottiePlayer->unpause(element.lottie);
} else if (element.webm && element.webm->started()) {
p.drawPixmap(ppos, element.webm->current({
p.drawImage(ppos, element.webm->current({
.frame = size,
.keepAlpha = true,
}, paused ? 0 : now));

View file

@ -1421,10 +1421,9 @@ void StickersBox::Inner::paintRowThumbnail(
row->lottie->markFrameShown();
}
} else if (row->webm && row->webm->started()) {
p.drawPixmapLeft(
p.drawImage(
x,
y,
width(),
row->webm->current(
{ .frame = { row->pixw, row->pixh }, .keepAlpha = true },
paused ? 0 : crl::now()));

View file

@ -882,7 +882,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
sticker.lottie->markFrameShown();
}
} else if (sticker.webm && sticker.webm->started()) {
p.drawPixmap(ppos, sticker.webm->current({
p.drawImage(ppos, sticker.webm->current({
.frame = size,
.keepAlpha = true,
}, paused ? 0 : now));

View file

@ -208,7 +208,7 @@ void StickersListFooter::clearHeavyData() {
icon.lifetime.destroy();
icon.stickerMedia = nullptr;
if (!info.visible) {
icon.savedFrame = QPixmap();
icon.savedFrame = QImage();
}
return true;
});
@ -1142,7 +1142,7 @@ void StickersListFooter::paintSetIcon(
const auto frame = icon.lottie->frame();
const auto size = frame.size() / cIntRetinaFactor();
if (icon.savedFrame.isNull()) {
icon.savedFrame = QPixmap::fromImage(frame, Qt::ColorOnly);
icon.savedFrame = frame;
icon.savedFrame.setDevicePixelRatio(cRetinaFactor());
}
p.drawImage(
@ -1163,17 +1163,17 @@ void StickersListFooter::paintSetIcon(
icon.savedFrame = frame;
icon.savedFrame.setDevicePixelRatio(cRetinaFactor());
}
p.drawPixmapLeft(x, y, width(), frame);
} else if (!icon.savedFrame.isNull() || thumb) {
const auto pixmap = !icon.savedFrame.isNull()
? icon.savedFrame
: (!icon.lottie && thumb)
p.drawImage(x, y, frame);
} else if (!icon.savedFrame.isNull()) {
p.drawImage(x, y, icon.savedFrame);
} else if (thumb) {
const auto pixmap = (!icon.lottie && thumb)
? thumb->pix(icon.pixw, icon.pixh)
: QPixmap();
if (pixmap.isNull()) {
return;
} else if (icon.savedFrame.isNull()) {
icon.savedFrame = pixmap;
icon.savedFrame = pixmap.toImage();
}
p.drawPixmapLeft(x, y, width(), pixmap);
}

View file

@ -63,7 +63,7 @@ struct StickerIcon {
Data::StickersSet *set = nullptr;
mutable std::unique_ptr<Lottie::SinglePlayer> lottie;
mutable Media::Clip::ReaderPointer webm;
mutable QPixmap savedFrame;
mutable QImage savedFrame;
DocumentData *sticker = nullptr;
ChannelData *megagroup = nullptr;
mutable std::shared_ptr<Data::StickersSetThumbnailView> thumbnailMedia;

View file

@ -76,7 +76,7 @@ struct StickersListWidget::Sticker {
std::shared_ptr<Data::DocumentMedia> documentMedia;
Lottie::Animation *lottie = nullptr;
Media::Clip::ReaderPointer webm;
QPixmap savedFrame;
QImage savedFrame;
QSize savedFrameFor;
QImage premiumLock;
@ -983,7 +983,7 @@ void StickersListWidget::clearHeavyIn(Set &set, bool clearSavedFrames) {
const auto lifetime = base::take(set.lottieLifetime);
for (auto &sticker : set.stickers) {
if (clearSavedFrames) {
sticker.savedFrame = QPixmap();
sticker.savedFrame = QImage();
sticker.savedFrameFor = QSize();
}
sticker.webm = nullptr;
@ -1314,9 +1314,7 @@ void StickersListWidget::paintSticker(
QRect(ppos, lottieFrame.size() / cIntRetinaFactor()),
lottieFrame);
if (sticker.savedFrame.isNull()) {
sticker.savedFrame = QPixmap::fromImage(
lottieFrame,
Qt::ColorOnly);
sticker.savedFrame = lottieFrame;
sticker.savedFrame.setDevicePixelRatio(cRetinaFactor());
sticker.savedFrameFor = _singleSize;
}
@ -1330,20 +1328,22 @@ void StickersListWidget::paintSticker(
sticker.savedFrame.setDevicePixelRatio(cRetinaFactor());
sticker.savedFrameFor = _singleSize;
}
p.drawPixmapLeft(ppos, width(), frame);
p.drawImage(ppos, frame);
} else {
const auto image = media->getStickerSmall();
const auto useSavedFrame = !sticker.savedFrame.isNull()
&& (sticker.savedFrameFor == _singleSize);
const auto pixmap = useSavedFrame
? sticker.savedFrame
: image
? image->pixSingle(size, { .outer = size })
: QPixmap();
if (!pixmap.isNull()) {
if (useSavedFrame) {
p.drawImage(ppos, sticker.savedFrame);
if (premium) {
lottieFrame = sticker.savedFrame;
}
} else if (image) {
const auto pixmap = image->pixSingle(size, { .outer = size });
p.drawPixmapLeft(ppos, width(), pixmap);
if (sticker.savedFrame.isNull()) {
sticker.savedFrame = pixmap;
sticker.savedFrame = pixmap.toImage().convertToFormat(
QImage::Format_ARGB32_Premultiplied);
sticker.savedFrameFor = _singleSize;
}
if (premium) {

View file

@ -990,7 +990,7 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
if (const auto info = _document->sticker(); info && !info->isWebm()) {
if (_document->sticker()) {
return std::make_unique<HistoryView::UnwrappedMedia>(
message,
std::make_unique<HistoryView::Sticker>(
@ -998,9 +998,7 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
_document,
_skipPremiumEffect,
replacing));
} else if (_document->isAnimation()
|| _document->isVideoFile()
|| (info && info->isWebm())) {
} else if (_document->isAnimation() || _document->isVideoFile()) {
return std::make_unique<HistoryView::Gif>(
message,
realParent,

View file

@ -86,7 +86,7 @@ void VideoUserpic::paintLeft(
startReady();
const auto now = paused ? crl::time(0) : crl::now();
p.drawPixmap(x, y, _video->current(request(size), now));
p.drawImage(x, y, _video->current(request(size), now));
} else {
_peer->paintUserpicLeft(p, view, x, y, w, size);
}

View file

@ -34,7 +34,7 @@ ItemSticker::ItemSticker(
}
const auto updateThumbnail = [=] {
const auto guard = gsl::finally([&] {
if (_pixmap.isNull()) {
if (_image.isNull()) {
setAspectRatio(1.);
}
});
@ -47,8 +47,7 @@ ItemSticker::ItemSticker(
Lottie::Quality::High);
_lottie.player->updates(
) | rpl::start_with_next([=] {
updatePixmap(Ui::PixmapFromImage(
_lottie.player->frame()));
updatePixmap(_lottie.player->frame());
_lottie.player = nullptr;
_lottie.lifetime.destroy();
update();
@ -81,7 +80,7 @@ ItemSticker::ItemSticker(
const auto ratio = style::DevicePixelRatio();
auto pixmap = sticker->pixNoCache(sticker->size() * ratio);
pixmap.setDevicePixelRatio(ratio);
updatePixmap(std::move(pixmap));
updatePixmap(pixmap.toImage());
return true;
};
if (!updateThumbnail()) {
@ -95,15 +94,15 @@ ItemSticker::ItemSticker(
}
}
void ItemSticker::updatePixmap(QPixmap &&pixmap) {
_pixmap = std::move(pixmap);
void ItemSticker::updatePixmap(QImage &&image) {
_image = std::move(image);
if (flipped()) {
performFlip();
} else {
update();
}
if (!_pixmap.isNull()) {
setAspectRatio(_pixmap.height() / float64(_pixmap.width()));
if (!_image.isNull()) {
setAspectRatio(_image.height() / float64(_image.width()));
}
}
@ -111,7 +110,7 @@ void ItemSticker::paint(
QPainter *p,
const QStyleOptionGraphicsItem *option,
QWidget *w) {
p->drawPixmap(contentRect().toRect(), _pixmap);
p->drawImage(contentRect().toRect(), _image);
ItemBase::paint(p, option, w);
}
@ -124,7 +123,7 @@ int ItemSticker::type() const {
}
void ItemSticker::performFlip() {
_pixmap = _pixmap.transformed(QTransform().scale(-1, 1));
_image = _image.transformed(QTransform().scale(-1, 1));
update();
}

View file

@ -42,14 +42,14 @@ private:
const not_null<DocumentData*> _document;
const std::shared_ptr<::Data::DocumentMedia> _mediaView;
void updatePixmap(QPixmap &&pixmap);
void updatePixmap(QImage &&image);
struct {
std::unique_ptr<Lottie::SinglePlayer> player;
rpl::lifetime lifetime;
} _lottie;
::Media::Clip::ReaderPointer _webm;
QPixmap _pixmap;
QImage _image;
rpl::lifetime _loadingLifetime;

View file

@ -78,7 +78,9 @@ void Dice::draw(Painter &p, const PaintContext &context, const QRect &r) {
_end->draw(p, context, r);
} else if (_start) {
_start->draw(p, context, r);
if (_end && _end->readyToDrawLottie() && _start->atTheEnd()) {
if (_end
&& _end->readyToDrawAnimationFrame()
&& _start->atTheEnd()) {
_drawingEnd = true;
}
}

View file

@ -24,7 +24,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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 "history/view/media/history_view_sticker.h"
#include "window/window_session_controller.h"
#include "core/application.h" // Application::showDocument.
#include "ui/chat/chat_style.h"
@ -39,7 +38,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_click_handler.h"
#include "data/data_file_origin.h"
#include "data/data_document_media.h"
#include "chat_helpers/stickers_lottie.h" // PaintStickerThumbnailPath.
#include "styles/style_chat.h"
namespace HistoryView {
@ -81,12 +79,7 @@ Gif::Gif(
, _data(document)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right())
, _downloadSize(Ui::FormatSizeText(_data->size)) {
if (const auto info = _data->sticker(); info && info->set) {
_stickerLink = Sticker::ShowSetHandler(_data);
} else {
setDocumentLinks(_data, realParent);
}
setDocumentLinks(_data, realParent);
setStatusSize(Ui::FileStatusSizeReady);
refreshCaption();
@ -132,17 +125,13 @@ QSize Gif::sizeForAspectRatio() const {
}
QSize Gif::countThumbSize(int &inOutWidthMax) const {
const auto maxSize = _data->sticker()
? Sticker::Size().width()
: _data->isVideoFile()
const auto maxSize = _data->isVideoFile()
? st::maxMediaSize
: _data->isVideoMessage()
? st::maxVideoMessageSize
: st::maxGifSize;
const auto useMaxSize = std::max(maxSize, st::minPhotoSize);
const auto size = _data->sticker()
? videoSize()
: style::ConvertScale(videoSize());
const auto size = style::ConvertScale(videoSize());
accumulate_min(inOutWidthMax, useMaxSize);
return DownscaledSize(size, { inOutWidthMax, useMaxSize });
}
@ -275,9 +264,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
ensureDataMediaCreated();
const auto item = _parent->data();
const auto loaded = dataLoaded();
const auto sticker = _data->sticker();
const auto displayLoading = !sticker
&& (item->isSending() || _data->displayLoading());
const auto displayLoading = (item->isSending() || _data->displayLoading());
const auto st = context.st;
const auto sti = context.imageStyle();
const auto stm = context.messageStyle();
@ -330,9 +317,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
}
updateStatusText();
const auto radial = isRadialAnimation()
|| (!sticker
&& streamedForWaiting
&& streamedForWaiting->waitingShown());
|| (streamedForWaiting && streamedForWaiting->waitingShown());
if (bubble) {
if (!_caption.isEmpty()) {
@ -398,10 +383,6 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
auto request = ::Media::Streaming::FrameRequest();
request.outer = QSize(usew, painth) * cIntRetinaFactor();
request.resize = QSize(_thumbw, _thumbh) * cIntRetinaFactor();
request.keepAlpha = (sticker != nullptr);
if (sticker && context.selected()) {
request.colored = context.st->msgStickerOverlay()->c;
}
request.corners = roundCorners;
request.radius = roundRadius;
if (!activeRoundPlaying && activeOwnPlaying->instance.playerLocked()) {
@ -422,19 +403,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
}
const auto frame = streamed->frameWithInfo(request);
const auto playOnce = sticker
&& !Core::App().settings().loopAnimatedStickers();
const auto switchToNext = !playOnce
|| (frame.index != 0)
|| !_stickerOncePlayed;
p.drawImage(rthumb, frame.image);
if (!paused
&& switchToNext
&& streamed->markFrameShown()
&& playOnce
&& !_stickerOncePlayed) {
_stickerOncePlayed = true;
_parent->delegate()->elementStartStickerLoop(_parent);
if (!paused) {
streamed->markFrameShown();
}
}
@ -464,13 +435,8 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
ensureDataMediaCreated();
const auto size = QSize(_thumbw, _thumbh);
const auto args = Images::PrepareArgs{
.colored = ((sticker && context.selected())
? &context.st->msgStickerOverlay()
: nullptr),
.options = (sticker
? Images::Option::TransparentBackground
: Images::RoundOptions(roundRadius, roundCorners)),
.outer = sticker ? QSize() : QSize(usew, painth),
.options = Images::RoundOptions(roundRadius, roundCorners),
.outer = QSize(usew, painth),
};
if (const auto good = _dataMedia->goodThumbnail()) {
p.drawPixmap(rthumb.topLeft(), good->pixSingle(size, args));
@ -507,20 +473,17 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
| (roundBottom ? RectPart::Bottom : RectPart::None);
Ui::FillRoundRect(p, rthumb.marginsAdded({ 0, roundTop ? 0 : margin, 0, roundBottom ? 0 : margin }), st->imageBg(), roundRadius, parts);
}
} else {
paintPath(p, context, rthumb);
}
}
}
}
if (context.selected() && !sticker) {
if (context.selected()) {
Ui::FillComplexOverlayRect(p, st, rthumb, roundRadius, roundCorners);
}
if (radial
|| (!sticker
&& !streamingMode
|| (!streamingMode
&& ((!loaded && !_data->loading()) || !autoplay))) {
const auto radialOpacity = (item->isSending() || _data->uploading())
? 1.
@ -725,28 +688,6 @@ void Gif::validateVideoThumbnail() const {
: info.thumbnail);
}
void Gif::paintPath(
Painter &p,
const PaintContext &context,
const QRect &r) const {
Expects(_dataMedia != nullptr);
const auto pathGradient = _parent->delegate()->elementPathShiftGradient();
if (context.selected()) {
pathGradient->overrideColors(
context.st->msgServiceBgSelected(),
context.st->msgServiceBg());
} else {
pathGradient->clearOverridenColors();
}
p.setBrush(context.imageStyle()->msgServiceBg);
ChatHelpers::PaintStickerThumbnailPath(
p,
_dataMedia.get(),
r,
pathGradient);
}
void Gif::drawCornerStatus(
Painter &p,
const PaintContext &context,
@ -923,9 +864,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
}
if (QRect(usex + paintx, painty, usew, painth).contains(point)) {
ensureDataMediaCreated();
result.link = _data->sticker()
? _stickerLink
: _data->uploading()
result.link = _data->uploading()
? _cancell
: _realParent->isSending()
? nullptr
@ -1241,7 +1180,7 @@ bool Gif::uploading() const {
}
bool Gif::needsBubble() const {
if (_data->sticker() || _data->isVideoMessage()) {
if (_data->isVideoMessage()) {
return false;
} else if (!_caption.isEmpty()) {
return true;
@ -1333,8 +1272,7 @@ int Gif::additionalWidth() const {
}
bool Gif::isUnwrapped() const {
return (_data->sticker() || _data->isVideoMessage())
&& (_parent->media() == this);
return _data->isVideoMessage() && (_parent->media() == this);
}
void Gif::validateGroupedCache(
@ -1684,7 +1622,6 @@ bool Gif::needInfoDisplay() const {
return _parent->data()->isSending()
|| _data->uploading()
|| _parent->isUnderCursor()
|| (_data->sticker() && _parent->rightActionSize())
// Don't show the GIF badge if this message has text.
|| (!_parent->hasBubble() && _parent->isLastAndSelfMessage());
}

View file

@ -101,10 +101,6 @@ public:
QPoint resolveCustomInfoRightBottom() const override;
QString additionalInfoString() const override;
void stickerClearLoopPlayed() override {
_stickerOncePlayed = false;
}
bool skipBubbleTail() const override {
return isRoundedInBubbleBottom() && _caption.isEmpty();
}
@ -181,11 +177,6 @@ private:
StateRequest request,
QPoint position) const;
void paintPath(
Painter &p,
const PaintContext &context,
const QRect &r) const;
const not_null<DocumentData*> _data;
int _thumbw = 1;
int _thumbh = 1;
@ -193,10 +184,7 @@ private:
std::unique_ptr<Streamed> _streamed;
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
mutable std::unique_ptr<Image> _videoThumbnailFrame;
ClickHandlerPtr _stickerLink;
QString _downloadSize;
mutable bool _stickerOncePlayed = false;
};

View file

@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_spoiler_click_handler.h"
#include "lottie/lottie_single_player.h"
#include "history/view/media/history_view_sticker.h"
#include "storage/storage_shared_media.h"
#include "data/data_document.h"
#include "data/data_session.h"
@ -243,7 +243,7 @@ PointState Media::pointState(QPoint point) const {
: PointState::Outside;
}
std::unique_ptr<Lottie::SinglePlayer> Media::stickerTakeLottie(
std::unique_ptr<StickerPlayer> Media::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return nullptr;

View file

@ -25,7 +25,6 @@ using SharedMediaTypesMask = base::enum_mask<SharedMediaType>;
} // namespace Storage
namespace Lottie {
class SinglePlayer;
struct ColorReplacements;
} // namespace Lottie
@ -41,6 +40,7 @@ enum class CursorState : char;
enum class InfoDisplayType : char;
struct TextState;
struct StateRequest;
class StickerPlayer;
class Element;
using PaintContext = Ui::ChatPaintContext;
@ -172,7 +172,7 @@ public:
}
virtual void stickerClearLoopPlayed() {
}
virtual std::unique_ptr<Lottie::SinglePlayer> stickerTakeLottie(
virtual std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements);
virtual void checkAnimation() {

View file

@ -25,10 +25,9 @@ constexpr auto kMaxForwardedBarLines = 4;
} // namespace
auto UnwrappedMedia::Content::stickerTakeLottie(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements)
-> std::unique_ptr<Lottie::SinglePlayer> {
std::unique_ptr<StickerPlayer> UnwrappedMedia::Content::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return nullptr;
}
@ -465,10 +464,10 @@ QPoint UnwrappedMedia::resolveCustomInfoRightBottom() const {
return QPoint(fullRight - skipx, fullBottom - skipy);
}
std::unique_ptr<Lottie::SinglePlayer> UnwrappedMedia::stickerTakeLottie(
std::unique_ptr<StickerPlayer> UnwrappedMedia::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return _content->stickerTakeLottie(data, replacements);
return _content->stickerTakePlayer(data, replacements);
}
//void UnwrappedMedia::externalLottieProgressing(bool external) {

View file

@ -38,7 +38,7 @@ public:
}
virtual void stickerClearLoopPlayed() {
}
virtual std::unique_ptr<Lottie::SinglePlayer> stickerTakeLottie(
virtual std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements);
@ -104,7 +104,7 @@ public:
void stickerClearLoopPlayed() override {
_content->stickerClearLoopPlayed();
}
std::unique_ptr<Lottie::SinglePlayer> stickerTakeLottie(
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;

View file

@ -195,11 +195,11 @@ void MediaGift::stickerClearLoopPlayed() {
}
}
std::unique_ptr<Lottie::SinglePlayer> MediaGift::stickerTakeLottie(
std::unique_ptr<StickerPlayer> MediaGift::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return _sticker
? _sticker->stickerTakeLottie(data, replacements)
? _sticker->stickerTakePlayer(data, replacements)
: nullptr;
}

View file

@ -42,7 +42,7 @@ public:
bool pressed) override;
void stickerClearLoopPlayed() override;
std::unique_ptr<Lottie::SinglePlayer> stickerTakeLottie(
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;

View file

@ -161,16 +161,16 @@ void SlotMachine::draw(
// }
//}
auto switchedToEnd = _drawingEnd;
const auto pullReady = _pull && _pull->readyToDrawLottie();
const auto pullReady = _pull && _pull->readyToDrawAnimationFrame();
const auto paintReady = [&] {
auto result = pullReady;
auto allPlayedEnough = true;
for (auto i = 1; i != 4; ++i) {
if (!_end[i] || !_end[i]->readyToDrawLottie()) {
if (!_end[i] || !_end[i]->readyToDrawAnimationFrame()) {
switchedToEnd[i] = false;
}
if (!switchedToEnd[i]
&& (!_start[i] || !_start[i]->readyToDrawLottie())) {
&& (!_start[i] || !_start[i]->readyToDrawAnimationFrame())) {
result = false;
}
const auto playedTillFrame = !switchedToEnd[i]
@ -180,11 +180,13 @@ void SlotMachine::draw(
allPlayedEnough = false;
}
}
if (!_end[0] || !_end[0]->readyToDrawLottie() || !allPlayedEnough) {
if (!_end[0]
|| !_end[0]->readyToDrawAnimationFrame()
|| !allPlayedEnough) {
switchedToEnd[0] = false;
}
if (ranges::contains(switchedToEnd, false)
&& (!_start[0] || !_start[0]->readyToDrawLottie())) {
&& (!_start[0] || !_start[0]->readyToDrawAnimationFrame())) {
result = false;
}
return result;
@ -200,7 +202,7 @@ void SlotMachine::draw(
} else {
_start[i]->draw(p, context, r);
if (_end[i]
&& _end[i]->readyToDrawLottie()
&& _end[i]->readyToDrawAnimationFrame()
&& _start[i]->atTheEnd()) {
_drawingEnd[i] = true;
}

View file

@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_click_handler.h"
#include "data/data_file_origin.h"
#include "lottie/lottie_single_player.h"
#include "media/clip/media_clip_reader.h"
#include "chat_helpers/stickers_gift_box_pack.h"
#include "chat_helpers/stickers_lottie.h"
#include "styles/style_chat.h"
@ -43,6 +44,8 @@ constexpr auto kMaxEmojiSizeFixed = 256;
constexpr auto kPremiumMultiplier = (1 + 0.245 * 2);
constexpr auto kEmojiMultiplier = 3;
using ClipNotification = ::Media::Clip::Notification;
[[nodiscard]] QImage CacheDiceImage(
const QString &emoji,
int index,
@ -57,6 +60,154 @@ constexpr auto kEmojiMultiplier = 3;
return image;
}
class LottiePlayer final : public StickerPlayer {
public:
explicit LottiePlayer(std::unique_ptr<Lottie::SinglePlayer> lottie);
void setRepaintCallback(Fn<void()> callback) override;
bool ready() override;
int framesCount() override;
FrameInfo frame(
QSize size,
QColor colored,
bool mirrorHorizontal,
crl::time now,
bool paused) override;
bool markFrameShown() override;
private:
std::unique_ptr<Lottie::SinglePlayer> _lottie;
rpl::lifetime _repaintLifetime;
};
LottiePlayer::LottiePlayer(std::unique_ptr<Lottie::SinglePlayer> lottie)
: _lottie(std::move(lottie)) {
}
void LottiePlayer::setRepaintCallback(Fn<void()> callback) {
_repaintLifetime = _lottie->updates(
) | rpl::start_with_next([=](Lottie::Update update) {
v::match(update.data, [&](const Lottie::Information &information) {
callback();
//markFramesTillExternal();
}, [&](const Lottie::DisplayFrameRequest &request) {
callback();
});
});
}
bool LottiePlayer::ready() {
return _lottie->ready();
}
int LottiePlayer::framesCount() {
return _lottie->information().framesCount;
}
LottiePlayer::FrameInfo LottiePlayer::frame(
QSize size,
QColor colored,
bool mirrorHorizontal,
crl::time now,
bool paused) {
auto request = Lottie::FrameRequest();
request.box = size * style::DevicePixelRatio();
request.colored = colored;
request.mirrorHorizontal = mirrorHorizontal;
const auto info = _lottie->frameInfo(request);
return { .image = info.image, .index = info.index };
}
bool LottiePlayer::markFrameShown() {
return _lottie->markFrameShown();
}
class WebmPlayer final : public StickerPlayer {
public:
WebmPlayer(
const Core::FileLocation &location,
const QByteArray &data,
QSize size);
void setRepaintCallback(Fn<void()> callback) override;
bool ready() override;
int framesCount() override;
FrameInfo frame(
QSize size,
QColor colored,
bool mirrorHorizontal,
crl::time now,
bool paused) override;
bool markFrameShown() override;
private:
void clipCallback(ClipNotification notification);
::Media::Clip::ReaderPointer _reader;
Fn<void()> _repaintCallback;
QSize _size;
};
WebmPlayer::WebmPlayer(
const Core::FileLocation &location,
const QByteArray &data,
QSize size)
: _reader(
::Media::Clip::MakeReader(location, data, [=](ClipNotification update) {
clipCallback(update);
}))
, _size(size) {
}
void WebmPlayer::clipCallback(ClipNotification notification) {
switch (notification) {
case ClipNotification::Reinit: {
if (_reader->state() == ::Media::Clip::State::Error) {
_reader.setBad();
} else if (_reader->ready() && !_reader->started()) {
_reader->start({ .frame = _size, .keepAlpha = true });
}
} break;
case ClipNotification::Repaint: break;
}
_repaintCallback();
}
void WebmPlayer::setRepaintCallback(Fn<void()> callback) {
_repaintCallback = std::move(callback);
}
bool WebmPlayer::ready() {
return _reader && _reader->started();
}
int WebmPlayer::framesCount() {
return -1;
}
WebmPlayer::FrameInfo WebmPlayer::frame(
QSize size,
QColor colored,
bool mirrorHorizontal,
crl::time now,
bool paused) {
auto request = ::Media::Clip::FrameRequest();
request.frame = size;
request.factor = style::DevicePixelRatio();
request.keepAlpha = true;
request.colored = colored;
const auto info = _reader->frameInfo(request, paused ? 0 : now);
return { .image = info.image, .index = info.index };
}
bool WebmPlayer::markFrameShown() {
return _reader->moveToNextFrame();
}
} // namespace
Sticker::Sticker(
@ -69,7 +220,7 @@ Sticker::Sticker(
, _data(data)
, _replacements(replacements)
, _cachingTag(ChatHelpers::StickerLottieSize::MessageHistory)
, _lottieOncePlayed(false)
, _oncePlayed(false)
, _premiumEffectPlayed(false)
, _nextLastDiceFrame(false)
, _skipPremiumEffect(skipPremiumEffect) {
@ -82,22 +233,22 @@ Sticker::Sticker(
}
}
if (const auto media = replacing ? replacing->media() : nullptr) {
_lottie = media->stickerTakeLottie(_data, _replacements);
if (_lottie) {
_player = media->stickerTakePlayer(_data, _replacements);
if (_player) {
//_externalInfo = media->externalLottieInfo();
if (hasPremiumEffect() && !_premiumEffectPlayed) {
_premiumEffectPlayed = true;
_parent->delegate()->elementStartPremium(_parent, replacing);
}
lottieCreated();
playerCreated();
}
}
}
Sticker::~Sticker() {
if (_lottie || _dataMedia) {
if (_lottie) {
unloadLottie();
if (_player || _dataMedia) {
if (_player) {
unloadPlayer();
}
if (_dataMedia) {
_data->owner().keepAlive(base::take(_dataMedia));
@ -127,21 +278,22 @@ void Sticker::initSize() {
_size = EmojiSize();
}
if (_diceIndex > 0) {
[[maybe_unused]] bool result = readyToDrawLottie();
[[maybe_unused]] bool result = readyToDrawAnimationFrame();
}
} else {
_size = Size(_data);
}
_size = DownscaledSize(_size, Size());
}
QSize Sticker::countOptimalSize() {
if (_size.isEmpty()) {
initSize();
}
return DownscaledSize(_size, Size());
return _size;
}
bool Sticker::readyToDrawLottie() {
bool Sticker::readyToDrawAnimationFrame() {
if (!_lastDiceFrame.isNull()) {
return true;
}
@ -155,10 +307,10 @@ bool Sticker::readyToDrawLottie() {
const auto loaded = _dataMedia->loaded();
const auto waitingForPremium = hasPremiumEffect()
&& _dataMedia->videoThumbnailContent().isEmpty();
if (sticker->isLottie() && !_lottie && loaded && !waitingForPremium) {
setupLottie();
if (!_player && loaded && !waitingForPremium && sticker->isAnimated()) {
setupPlayer();
}
return (_lottie && _lottie->ready());
return (_player && _player->ready());
}
QSize Sticker::Size() {
@ -192,13 +344,13 @@ void Sticker::draw(
Painter &p,
const PaintContext &context,
const QRect &r) {
if (!customEmojiPart() && isEmojiSticker()) {
if (!customEmojiPart()) {
_parent->clearCustomEmojiRepaint();
}
ensureDataMediaCreated();
if (readyToDrawLottie()) {
paintLottie(p, context, r);
if (readyToDrawAnimationFrame()) {
paintAnimationFrame(p, context, r);
} else if (!_data->sticker()
|| (_data->sticker()->isLottie() && _replacements)
|| !paintPixmap(p, context, r)) {
@ -215,23 +367,28 @@ DocumentData *Sticker::document() {
}
void Sticker::stickerClearLoopPlayed() {
_lottieOncePlayed = false;
_oncePlayed = false;
_premiumEffectPlayed = false;
}
void Sticker::paintLottie(
void Sticker::paintAnimationFrame(
Painter &p,
const PaintContext &context,
const QRect &r) {
auto request = Lottie::FrameRequest();
request.box = _size * cIntRetinaFactor();
if (context.selected() && !_nextLastDiceFrame) {
request.colored = context.st->msgStickerOverlay()->c;
}
request.mirrorHorizontal = mirrorHorizontal();
const auto frame = _lottie
? _lottie->frameInfo(request)
: Lottie::Animation::FrameInfo();
const auto colored = (context.selected() && !_nextLastDiceFrame)
? context.st->msgStickerOverlay()->c
: QColor(0, 0, 0, 0);
const auto paused = /*(_externalInfo.frame >= 0)
? (_frameIndex % _externalInfo.count >= _externalInfo.frame)
: */_parent->delegate()->elementIsGifPaused();
const auto frame = _player
? _player->frame(
_size,
colored,
mirrorHorizontal(),
context.now,
paused)
: StickerPlayer::FrameInfo();
if (_nextLastDiceFrame) {
_nextLastDiceFrame = false;
_lastDiceFrame = CacheDiceImage(_diceEmoji, _diceIndex, frame.image);
@ -256,12 +413,9 @@ void Sticker::paintLottie(
return;
}
const auto count = _lottie->information().framesCount;
const auto count = _player->framesCount();
_frameIndex = frame.index;
_framesCount = count;
const auto paused = /*(_externalInfo.frame >= 0)
? (_frameIndex % _externalInfo.count >= _externalInfo.frame)
: */_parent->delegate()->elementIsGifPaused();
_nextLastDiceFrame = !paused
&& (_diceIndex > 0)
&& (_frameIndex + 2 == count);
@ -274,13 +428,13 @@ void Sticker::paintLottie(
const auto lastDiceFrame = (_diceIndex > 0) && atTheEnd();
const auto switchToNext = /*(_externalInfo.frame >= 0)
|| */!playOnce
|| (!lastDiceFrame && (_frameIndex != 0 || !_lottieOncePlayed));
|| (!lastDiceFrame && (_frameIndex != 0 || !_oncePlayed));
if (!paused
&& switchToNext
&& _lottie->markFrameShown()
&& _player->markFrameShown()
&& playOnce
&& !_lottieOncePlayed) {
_lottieOncePlayed = true;
&& !_oncePlayed) {
_oncePlayed = true;
_parent->delegate()->elementStartStickerLoop(_parent);
}
checkPremiumEffectStart();
@ -419,10 +573,10 @@ void Sticker::refreshLink() {
}
void Sticker::emojiStickerClicked() {
if (_lottie) {
if (_player) {
_parent->delegate()->elementStartInteraction(_parent);
}
_lottieOncePlayed = false;
_oncePlayed = false;
_parent->history()->owner().requestViewRepaint(_parent);
}
@ -464,17 +618,26 @@ void Sticker::setCustomEmojiPart(
_cachingTag = tag;
}
void Sticker::setupLottie() {
void Sticker::setupPlayer() {
Expects(_dataMedia != nullptr);
_lottie = ChatHelpers::LottiePlayerFromDocument(
_dataMedia.get(),
_replacements,
_cachingTag,
countOptimalSize() * style::DevicePixelRatio(),
Lottie::Quality::High);
if (_data->sticker()->isLottie()) {
_player = std::make_unique<LottiePlayer>(
ChatHelpers::LottiePlayerFromDocument(
_dataMedia.get(),
_replacements,
_cachingTag,
countOptimalSize() * style::DevicePixelRatio(),
Lottie::Quality::High));
} else if (_data->sticker()->isWebm()) {
_player = std::make_unique<WebmPlayer>(
_dataMedia->owner()->location(),
_dataMedia->bytes(),
countOptimalSize());
}
checkPremiumEffectStart();
lottieCreated();
playerCreated();
}
void Sticker::checkPremiumEffectStart() {
@ -484,51 +647,42 @@ void Sticker::checkPremiumEffectStart() {
}
}
void Sticker::lottieCreated() {
Expects(_lottie != nullptr);
void Sticker::playerCreated() {
Expects(_player != nullptr);
_parent->history()->owner().registerHeavyViewPart(_parent);
_lottie->updates(
) | rpl::start_with_next([=](Lottie::Update update) {
v::match(update.data, [&](const Lottie::Information &information) {
_parent->customEmojiRepaint();
//markFramesTillExternal();
}, [&](const Lottie::DisplayFrameRequest &request) {
_parent->customEmojiRepaint();
});
}, _lifetime);
_player->setRepaintCallback([=] { _parent->customEmojiRepaint(); });
}
bool Sticker::hasHeavyPart() const {
return _lottie || _dataMedia;
return _player || _dataMedia;
}
void Sticker::unloadHeavyPart() {
unloadLottie();
unloadPlayer();
_dataMedia = nullptr;
}
void Sticker::unloadLottie() {
if (!_lottie) {
void Sticker::unloadPlayer() {
if (!_player) {
return;
}
if (_diceIndex > 0 && _lastDiceFrame.isNull()) {
_nextLastDiceFrame = false;
_lottieOncePlayed = false;
_oncePlayed = false;
}
_lottie = nullptr;
_player = nullptr;
if (hasPremiumEffect()) {
_parent->delegate()->elementCancelPremium(_parent);
}
_parent->checkHeavyPart();
}
std::unique_ptr<Lottie::SinglePlayer> Sticker::stickerTakeLottie(
std::unique_ptr<StickerPlayer> Sticker::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return (data == _data && replacements == _replacements)
? std::move(_lottie)
? std::move(_player)
: nullptr;
}

View file

@ -30,6 +30,26 @@ enum class StickerLottieSize : uint8;
namespace HistoryView {
class StickerPlayer {
public:
virtual ~StickerPlayer() = default;
struct FrameInfo {
QImage image;
int index = 0;
};
virtual void setRepaintCallback(Fn<void()> callback) = 0;
[[nodiscard]] virtual bool ready() = 0;
[[nodiscard]] virtual int framesCount() = 0;
[[nodiscard]] virtual FrameInfo frame(
QSize size,
QColor colored,
bool mirrorHorizontal,
crl::time now,
bool paused) = 0;
virtual bool markFrameShown() = 0;
};
class Sticker final
: public UnwrappedMedia::Content
, public base::has_weak_ptr {
@ -52,7 +72,7 @@ public:
DocumentData *document() override;
void stickerClearLoopPlayed() override;
std::unique_ptr<Lottie::SinglePlayer> stickerTakeLottie(
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;
@ -83,7 +103,7 @@ public:
? std::make_optional(_framesCount)
: std::nullopt;
}
[[nodiscard]] bool readyToDrawLottie();
[[nodiscard]] bool readyToDrawAnimationFrame();
[[nodiscard]] static QSize Size();
[[nodiscard]] static QSize Size(not_null<DocumentData*> document);
@ -99,7 +119,10 @@ private:
[[nodiscard]] bool hasPremiumEffect() const;
[[nodiscard]] bool customEmojiPart() const;
[[nodiscard]] bool isEmojiSticker() const;
void paintLottie(Painter &p, const PaintContext &context, const QRect &r);
void paintAnimationFrame(
Painter &p,
const PaintContext &context,
const QRect &r);
bool paintPixmap(Painter &p, const PaintContext &context, const QRect &r);
void paintPath(Painter &p, const PaintContext &context, const QRect &r);
[[nodiscard]] QPixmap paintedPixmap(const PaintContext &context) const;
@ -108,9 +131,9 @@ private:
void ensureDataMediaCreated() const;
void dataMediaCreated() const;
void setupLottie();
void lottieCreated();
void unloadLottie();
void setupPlayer();
void playerCreated();
void unloadPlayer();
void emojiStickerClicked();
void premiumStickerClicked();
void checkPremiumEffectStart();
@ -119,7 +142,7 @@ private:
const not_null<Element*> _parent;
const not_null<DocumentData*> _data;
const Lottie::ColorReplacements *_replacements = nullptr;
std::unique_ptr<Lottie::SinglePlayer> _lottie;
std::unique_ptr<StickerPlayer> _player;
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
ClickHandlerPtr _link;
QSize _size;
@ -130,13 +153,11 @@ private:
mutable int _frameIndex = -1;
mutable int _framesCount = -1;
ChatHelpers::StickerLottieSize _cachingTag = {};
mutable bool _lottieOncePlayed : 1;
mutable bool _oncePlayed : 1;
mutable bool _premiumEffectPlayed : 1;
mutable bool _nextLastDiceFrame : 1;
bool _skipPremiumEffect : 1;
rpl::lifetime _lifetime;
};
} // namespace HistoryView

View file

@ -190,13 +190,13 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
_thumb = pixmap;
_thumbGood = true;
}
p.drawPixmap(r.topLeft(), pixmap);
p.drawImage(r.topLeft(), pixmap);
} else {
prepareThumbnail(r.size(), frame);
if (_thumb.isNull()) {
p.fillRect(r, st::overviewPhotoBg);
} else {
p.drawPixmap(r.topLeft(), _thumb);
p.drawImage(r.topLeft(), _thumb);
}
}
@ -340,7 +340,7 @@ void Gif::validateThumbnail(
.options = (Images::Option::TransparentBackground
| (good ? Images::Option() : Images::Option::Blur)),
.outer = size,
});
}).toImage();
}
void Gif::prepareThumbnail(QSize size, QSize frame) const {
@ -518,7 +518,7 @@ void Sticker::paint(Painter &p, const QRect &clip, const PaintContext *context)
.frame = size,
.keepAlpha = true,
}, context->paused ? 0 : context->ms);
p.drawPixmap(
p.drawImage(
(st::stickerPanSize.width() - size.width()) / 2,
(st::stickerPanSize.height() - size.width()) / 2,
frame);
@ -1519,7 +1519,7 @@ void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) con
_thumb = pixmap;
_thumbGood = true;
}
p.drawPixmapLeft(rthumb.topLeft(), _width, pixmap);
p.drawImage(rthumb.topLeft(), pixmap);
thumbDisplayed = true;
}
}
@ -1529,7 +1529,7 @@ void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) con
if (_thumb.isNull()) {
p.fillRect(rthumb, st::overviewPhotoBg);
} else {
p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb);
p.drawImage(rthumb.topLeft(), _thumb);
}
}
@ -1630,7 +1630,7 @@ void Game::validateThumbnail(Image *image, QSize size, bool good) const {
.options = (Images::Option::TransparentBackground
| (good ? Images::Option() : Images::Option::Blur)),
.outer = size,
});
}).toImage();
}
bool Game::isRadialAnimation() const {

View file

@ -126,7 +126,7 @@ private:
Media::Clip::ReaderPointer _gif;
ClickHandlerPtr _delete;
mutable QPixmap _thumb;
mutable QImage _thumb;
mutable bool _thumbGood = false;
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
@ -419,7 +419,7 @@ private:
Media::Clip::ReaderPointer _gif;
mutable std::shared_ptr<Data::PhotoMedia> _photoMedia;
mutable std::shared_ptr<Data::DocumentMedia> _documentMedia;
mutable QPixmap _thumb;
mutable QImage _thumb;
mutable bool _thumbGood = false;
mutable std::unique_ptr<Ui::RadialAnimation> _radial;
Ui::Text::String _title, _description;

View file

@ -91,6 +91,7 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() {
_frameMs = 0;
_lastReadVideoMs = _lastReadAudioMs = 0;
_skippedInvalidDataPackets = 0;
_frameIndex = -1;
continue;
} else if (res != AVERROR(EAGAIN)) {
@ -162,6 +163,7 @@ void FFMpegReaderImplementation::processReadFrame() {
_hadFrame = _frameRead = true;
_frameTime += _currentFrameDelay;
++_frameIndex;
}
ReaderImplementation::ReadResult FFMpegReaderImplementation::readFramesTill(crl::time frameMs, crl::time systemMs) {
@ -200,10 +202,15 @@ crl::time FFMpegReaderImplementation::durationMs() const {
return 0;
}
bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) {
bool FFMpegReaderImplementation::renderFrame(
QImage &to,
bool &hasAlpha,
int &index,
const QSize &size) {
Expects(_frameRead);
_frameRead = false;
_frameRead = false;
index = _frameIndex;
if (!_width || !_height) {
_width = _frame->width;
_height = _frame->height;

View file

@ -33,7 +33,11 @@ public:
crl::time frameRealTime() const override;
crl::time framePresentationTime() const override;
bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override;
bool renderFrame(
QImage &to,
bool &hasAlpha,
int &index,
const QSize &size) override;
crl::time durationMs() const override;
@ -85,6 +89,7 @@ private:
AVCodecContext *_codecContext = nullptr;
int _streamId = 0;
FFmpeg::FramePointer _frame;
int _frameIndex = -1;
bool _opened = false;
bool _hadFrame = false;
bool _frameRead = false;

View file

@ -42,7 +42,11 @@ public:
virtual crl::time framePresentationTime() const = 0;
// Render current frame to an image with specific size.
virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0;
virtual bool renderFrame(
QImage &to,
bool &hasAlpha,
int &index,
const QSize &size) = 0;
virtual crl::time durationMs() const = 0;

View file

@ -35,11 +35,21 @@ constexpr auto kClipThreadsCount = 8;
constexpr auto kAverageGifSize = 320 * 240;
constexpr auto kWaitBeforeGifPause = crl::time(200);
QImage PrepareFrameImage(const FrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) {
QImage PrepareFrame(
const FrameRequest &request,
const QImage &original,
bool hasAlpha,
QImage &cache) {
const auto needResize = (original.size() != request.frame);
const auto needOuterFill = request.outer.isValid() && (request.outer != request.frame);
const auto needOuterFill = request.outer.isValid()
&& (request.outer != request.frame);
const auto needRounding = (request.radius != ImageRoundRadius::None);
if (!needResize && !needOuterFill && !hasAlpha && !needRounding) {
const auto colorizing = (request.colored.alpha() != 0);
if (!needResize
&& !needOuterFill
&& !hasAlpha
&& !needRounding
&& !colorizing) {
return original;
}
@ -89,13 +99,12 @@ QImage PrepareFrameImage(const FrameRequest &request, const QImage &original, bo
request.radius,
request.corners);
}
if (colorizing) {
cache = Images::Colored(std::move(cache), request.colored);
}
return cache;
}
QPixmap PrepareFrame(const FrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) {
return QPixmap::fromImage(PrepareFrameImage(request, original, hasAlpha, cache), Qt::ColorOnly);
}
} // namespace
enum class ProcessResult {
@ -254,15 +263,18 @@ Reader::Frame *Reader::frameToWriteNext(bool checkNotWriting, int32 *index) cons
return _frames + i;
}
void Reader::moveToNextShow() const {
int32 step = _step.loadAcquire();
bool Reader::moveToNextShow() const {
const auto step = _step.loadAcquire();
if (step == kWaitingForDimensionsStep) {
} else if (step == kWaitingForRequestStep) {
_step.storeRelease(kWaitingForFirstFrameStep);
return true;
} else if (step == kWaitingForFirstFrameStep) {
} else if (!(step % 2)) {
_step.storeRelease(step + 1);
return true;
}
return false;
}
void Reader::moveToNextWrite() const {
@ -311,7 +323,7 @@ void Reader::start(FrameRequest request) {
Workers[_threadIndex]->manager.start(this);
}
QPixmap Reader::current(FrameRequest request, crl::time now) {
Reader::FrameInfo Reader::frameInfo(FrameRequest request, crl::time now) {
Expects(!(request.outer.isValid()
? request.outer
: request.frame).isEmpty());
@ -346,31 +358,32 @@ QPixmap Reader::current(FrameRequest request, crl::time now) {
Assert(frame->request.radius == request.radius
&& frame->request.corners == request.corners
&& frame->request.keepAlpha == request.keepAlpha);
if (frame->pix.size() == size) {
moveToNextShow();
return frame->pix;
if (frame->prepared.size() != size
|| frame->preparedColored != request.colored) {
frame->request.frame = request.frame;
frame->request.outer = request.outer;
frame->request.colored = request.colored;
QImage cacheForResize;
frame->original.setDevicePixelRatio(factor);
frame->prepared = QImage();
frame->prepared = PrepareFrame(
frame->request,
frame->original,
true,
cacheForResize);
frame->preparedColored = request.colored;
auto other = frameToWriteNext(true);
if (other) other->request = frame->request;
if (Workers.size() <= _threadIndex) {
error();
} else if (_state != State::Error) {
Workers[_threadIndex]->manager.update(this);
}
}
frame->request.frame = request.frame;
frame->request.outer = request.outer;
QImage cacheForResize;
frame->original.setDevicePixelRatio(factor);
frame->pix = QPixmap();
frame->pix = PrepareFrame(frame->request, frame->original, true, cacheForResize);
auto other = frameToWriteNext(true);
if (other) other->request = frame->request;
moveToNextShow();
if (Workers.size() <= _threadIndex) {
error();
} else if (_state != State::Error) {
Workers[_threadIndex]->manager.update(this);
}
return frame->pix;
return { frame->prepared, frame->index };
}
bool Reader::ready() const {
@ -478,7 +491,7 @@ public:
if (reader->start(internal::ReaderImplementation::Mode::Silent, firstFramePositionMs)) {
auto firstFrameReadResult = reader->readFramesTill(-1, ms);
if (firstFrameReadResult == internal::ReaderImplementation::ReadResult::Success) {
if (reader->renderFrame(frame()->original, frame()->alpha, QSize())) {
if (reader->renderFrame(frame()->original, frame()->alpha, frame()->index, QSize())) {
frame()->original.fill(QColor(0, 0, 0));
frame()->positionMs = _seekPositionMs;
@ -495,7 +508,7 @@ public:
} else if (readResult != internal::ReaderImplementation::ReadResult::Success) { // Read the first frame.
return error();
}
if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) {
if (!_implementation->renderFrame(frame()->original, frame()->alpha, frame()->index, QSize())) {
return error();
}
frame()->positionMs = _implementation->frameRealTime();
@ -555,12 +568,17 @@ public:
bool renderFrame() {
Expects(_request.valid());
if (!_implementation->renderFrame(frame()->original, frame()->alpha, _request.frame)) {
if (!_implementation->renderFrame(frame()->original, frame()->alpha, frame()->index, _request.frame)) {
return false;
}
frame()->original.setDevicePixelRatio(_request.factor);
frame()->pix = QPixmap();
frame()->pix = PrepareFrame(_request, frame()->original, frame()->alpha, frame()->cache);
frame()->prepared = QImage();
frame()->prepared = PrepareFrame(
_request,
frame()->original,
frame()->alpha,
frame()->cache);
frame()->preparedColored = _request.colored;
frame()->when = _nextFrameWhen;
frame()->positionMs = _nextFramePositionMs;
return true;
@ -638,8 +656,11 @@ private:
FrameRequest _request;
struct Frame {
QPixmap pix;
QImage original, cache;
QImage prepared;
QColor preparedColored = QColor(0, 0, 0, 0);
QImage original;
QImage cache;
int index = 0;
bool alpha = true;
crl::time when = 0;
@ -780,8 +801,10 @@ bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, c
Assert(reader->_frame >= 0);
auto frame = it.key()->_frames + reader->_frame;
frame->clear();
frame->pix = reader->frame()->pix;
frame->prepared = reader->frame()->prepared;
frame->preparedColored = reader->frame()->preparedColored;
frame->original = reader->frame()->original;
frame->index = reader->frame()->index;
frame->displayed.storeRelease(0);
frame->positionMs = reader->frame()->positionMs;
if (result == ProcessResult::Started) {
@ -959,10 +982,11 @@ Ui::PreparedFileInformation::Video PrepareForSending(const QString &fname, const
// return result;
// }
//}
auto index = 0;
auto hasAlpha = false;
auto readResult = reader->readFramesTill(-1, crl::now());
auto readFrame = (readResult == internal::ReaderImplementation::ReadResult::Success);
if (readFrame && reader->renderFrame(result.thumbnail, hasAlpha, QSize())) {
if (readFrame && reader->renderFrame(result.thumbnail, hasAlpha, index, QSize())) {
if (hasAlpha && !result.isWebmSticker) {
result.thumbnail = Images::Opaque(std::move(result.thumbnail));
}

View file

@ -27,14 +27,16 @@ enum class State {
};
struct FrameRequest {
bool valid() const {
[[nodiscard]] bool valid() const {
return factor > 0;
}
QSize frame;
QSize outer;
int factor = 0;
ImageRoundRadius radius = ImageRoundRadius::None;
RectParts corners = RectPart::AllCorners;
QColor colored = QColor(0, 0, 0, 0);
bool keepAlpha = false;
};
@ -74,14 +76,27 @@ public:
Notification notification);
void start(FrameRequest request);
[[nodiscard]] QPixmap current(FrameRequest request, crl::time now);
[[nodiscard]] QPixmap frameOriginal() const {
struct FrameInfo {
QImage image;
int index = 0;
};
[[nodiscard]] FrameInfo frameInfo(FrameRequest request, crl::time now);
[[nodiscard]] QImage current(FrameRequest request, crl::time now) {
auto result = frameInfo(request, now).image;
moveToNextFrame();
return result;
}
[[nodiscard]] QImage frameOriginal() const {
if (const auto frame = frameToShow()) {
auto result = QPixmap::fromImage(frame->original);
auto result = frame->original;
result.detach();
return result;
}
return QPixmap();
return QImage();
}
bool moveToNextFrame() {
return moveToNextShow();
}
[[nodiscard]] bool currentDisplayed() const {
const auto frame = frameToShow();
@ -130,13 +145,17 @@ private:
mutable QAtomicInt _step = kWaitingForDimensionsStep;
struct Frame {
void clear() {
pix = QPixmap();
prepared = QImage();
preparedColored = QColor(0, 0, 0, 0);
original = QImage();
}
QPixmap pix;
QImage prepared;
QColor preparedColored = QColor(0, 0, 0, 0);
QImage original;
FrameRequest request;
QAtomicInt displayed = 0;
int index = 0;
// Should be counted from the end,
// so that positionMs <= _durationMs.
@ -146,7 +165,7 @@ private:
Frame *frameToShow(int *index = nullptr) const; // 0 means not ready
Frame *frameToWrite(int *index = nullptr) const; // 0 means not ready
Frame *frameToWriteNext(bool check, int *index = nullptr) const;
void moveToNextShow() const;
bool moveToNextShow() const;
void moveToNextWrite() const;
QAtomicInt _autoPausedGif = 0;

View file

@ -1974,7 +1974,7 @@ void Gif::validateThumbnail(
{
.options = (good ? Images::Option() : Images::Option::Blur),
.outer = size,
});
}).toImage();
}
void Gif::prepareThumbnail(QSize size, QSize frame) {
@ -2031,13 +2031,13 @@ void Gif::paint(
_thumb = pixmap;
_thumbGood = true;
}
p.drawPixmap(r.topLeft(), pixmap);
p.drawImage(r.topLeft(), pixmap);
} else {
prepareThumbnail(r.size(), frame);
if (_thumb.isNull()) {
p.fillRect(r, st::overviewPhotoBg);
} else {
p.drawPixmap(r.topLeft(), _thumb);
p.drawImage(r.topLeft(), _thumb);
}
}

View file

@ -255,7 +255,7 @@ private:
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
StatusText _status;
QPixmap _thumb;
QImage _thumb;
bool _thumbGood = false;
};

View file

@ -80,7 +80,7 @@ bool SingleMediaPreview::tryPaintAnimation(Painter &p) {
const auto frame = _gifPreview->current({
.frame = QSize(previewWidth(), previewHeight()),
}, paused ? 0 : crl::now());
p.drawPixmap(previewLeft(), previewTop(), frame);
p.drawImage(previewLeft(), previewTop(), frame);
return true;
} else if (_lottiePreview && _lottiePreview->ready()) {
const auto frame = _lottiePreview->frame();

View file

@ -381,9 +381,9 @@ QPixmap MediaPreviewWidget::currentImage() const {
if (gif && gif->started()) {
const auto paused = _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::MediaPreview);
return gif->current(
return QPixmap::fromImage(gif->current(
{ .frame = currentDimensions(), .keepAlpha = webm },
paused ? 0 : crl::now());
paused ? 0 : crl::now()), Qt::ColorOnly);
}
if (_cacheStatus != CacheThumbLoaded
&& _document->hasThumbnail()) {