From f8e22210e718be1223583c576adea27d29639d7a Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 4 Aug 2022 13:35:08 +0300 Subject: [PATCH] Move Webm sticker to UnwrappedMedia. --- .../SourceFiles/boxes/sticker_set_box.cpp | 2 +- Telegram/SourceFiles/boxes/stickers_box.cpp | 3 +- .../chat_helpers/field_autocomplete.cpp | 2 +- .../chat_helpers/stickers_list_footer.cpp | 16 +- .../chat_helpers/stickers_list_footer.h | 2 +- .../chat_helpers/stickers_list_widget.cpp | 26 +- .../SourceFiles/data/data_media_types.cpp | 6 +- .../dialogs/ui/dialogs_video_userpic.cpp | 2 +- .../editor/scene/scene_item_sticker.cpp | 19 +- .../editor/scene/scene_item_sticker.h | 4 +- .../history/view/media/history_view_dice.cpp | 4 +- .../history/view/media/history_view_gif.cpp | 91 +----- .../history/view/media/history_view_gif.h | 12 - .../history/view/media/history_view_media.cpp | 4 +- .../history/view/media/history_view_media.h | 4 +- .../media/history_view_media_unwrapped.cpp | 11 +- .../view/media/history_view_media_unwrapped.h | 4 +- .../media/history_view_service_media_gift.cpp | 4 +- .../media/history_view_service_media_gift.h | 2 +- .../view/media/history_view_slot_machine.cpp | 14 +- .../view/media/history_view_sticker.cpp | 284 ++++++++++++++---- .../history/view/media/history_view_sticker.h | 41 ++- .../inline_bot_layout_internal.cpp | 14 +- .../inline_bots/inline_bot_layout_internal.h | 4 +- .../media/clip/media_clip_ffmpeg.cpp | 11 +- .../media/clip/media_clip_ffmpeg.h | 7 +- .../media/clip/media_clip_implementation.h | 6 +- .../media/clip/media_clip_reader.cpp | 110 ++++--- .../media/clip/media_clip_reader.h | 35 ++- .../SourceFiles/overview/overview_layout.cpp | 6 +- .../SourceFiles/overview/overview_layout.h | 2 +- .../attach/attach_single_media_preview.cpp | 2 +- .../window/window_media_preview.cpp | 4 +- 33 files changed, 458 insertions(+), 300 deletions(-) diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index 1a50c24f0..592ba282c 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -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)); diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index 51b089767..eaad3a130 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -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())); diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 8b6e1fcbc..819972b4f 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -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)); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index e2368b3b6..ae71d8720 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -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); } diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h index 2e95b877e..397d15601 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h @@ -63,7 +63,7 @@ struct StickerIcon { Data::StickersSet *set = nullptr; mutable std::unique_ptr lottie; mutable Media::Clip::ReaderPointer webm; - mutable QPixmap savedFrame; + mutable QImage savedFrame; DocumentData *sticker = nullptr; ChannelData *megagroup = nullptr; mutable std::shared_ptr thumbnailMedia; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 3b5e12e51..9ed700e45 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -76,7 +76,7 @@ struct StickersListWidget::Sticker { std::shared_ptr 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) { diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 67a27f741..121206723 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -990,7 +990,7 @@ std::unique_ptr MediaFile::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { - if (const auto info = _document->sticker(); info && !info->isWebm()) { + if (_document->sticker()) { return std::make_unique( message, std::make_unique( @@ -998,9 +998,7 @@ std::unique_ptr MediaFile::createView( _document, _skipPremiumEffect, replacing)); - } else if (_document->isAnimation() - || _document->isVideoFile() - || (info && info->isWebm())) { + } else if (_document->isAnimation() || _document->isVideoFile()) { return std::make_unique( message, realParent, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_video_userpic.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_video_userpic.cpp index 95307efee..faea2ddf6 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_video_userpic.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_video_userpic.cpp @@ -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); } diff --git a/Telegram/SourceFiles/editor/scene/scene_item_sticker.cpp b/Telegram/SourceFiles/editor/scene/scene_item_sticker.cpp index d7128c731..a60f487e0 100644 --- a/Telegram/SourceFiles/editor/scene/scene_item_sticker.cpp +++ b/Telegram/SourceFiles/editor/scene/scene_item_sticker.cpp @@ -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(); } diff --git a/Telegram/SourceFiles/editor/scene/scene_item_sticker.h b/Telegram/SourceFiles/editor/scene/scene_item_sticker.h index bbd0a758a..a7fcf5d16 100644 --- a/Telegram/SourceFiles/editor/scene/scene_item_sticker.h +++ b/Telegram/SourceFiles/editor/scene/scene_item_sticker.h @@ -42,14 +42,14 @@ private: const not_null _document; const std::shared_ptr<::Data::DocumentMedia> _mediaView; - void updatePixmap(QPixmap &&pixmap); + void updatePixmap(QImage &&image); struct { std::unique_ptr player; rpl::lifetime lifetime; } _lottie; ::Media::Clip::ReaderPointer _webm; - QPixmap _pixmap; + QImage _image; rpl::lifetime _loadingLifetime; diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp index 75174f92b..009a34362 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -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; } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 17e9552db..9e6dfb609 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -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()); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index 9fcd92b20..0d771d02c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -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 _data; int _thumbw = 1; int _thumbh = 1; @@ -193,10 +184,7 @@ private: std::unique_ptr _streamed; mutable std::shared_ptr _dataMedia; mutable std::unique_ptr _videoThumbnailFrame; - ClickHandlerPtr _stickerLink; - QString _downloadSize; - mutable bool _stickerOncePlayed = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index ef477d482..beb587202 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -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 Media::stickerTakeLottie( +std::unique_ptr Media::stickerTakePlayer( not_null data, const Lottie::ColorReplacements *replacements) { return nullptr; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index ef97df565..da30a2835 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -25,7 +25,6 @@ using SharedMediaTypesMask = base::enum_mask; } // 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 stickerTakeLottie( + virtual std::unique_ptr stickerTakePlayer( not_null data, const Lottie::ColorReplacements *replacements); virtual void checkAnimation() { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 9f5402c61..3ab29233f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -25,10 +25,9 @@ constexpr auto kMaxForwardedBarLines = 4; } // namespace -auto UnwrappedMedia::Content::stickerTakeLottie( - not_null data, - const Lottie::ColorReplacements *replacements) --> std::unique_ptr { +std::unique_ptr UnwrappedMedia::Content::stickerTakePlayer( + not_null data, + const Lottie::ColorReplacements *replacements) { return nullptr; } @@ -465,10 +464,10 @@ QPoint UnwrappedMedia::resolveCustomInfoRightBottom() const { return QPoint(fullRight - skipx, fullBottom - skipy); } -std::unique_ptr UnwrappedMedia::stickerTakeLottie( +std::unique_ptr UnwrappedMedia::stickerTakePlayer( not_null data, const Lottie::ColorReplacements *replacements) { - return _content->stickerTakeLottie(data, replacements); + return _content->stickerTakePlayer(data, replacements); } //void UnwrappedMedia::externalLottieProgressing(bool external) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h index 6ff2490df..91e4ab973 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h @@ -38,7 +38,7 @@ public: } virtual void stickerClearLoopPlayed() { } - virtual std::unique_ptr stickerTakeLottie( + virtual std::unique_ptr stickerTakePlayer( not_null data, const Lottie::ColorReplacements *replacements); @@ -104,7 +104,7 @@ public: void stickerClearLoopPlayed() override { _content->stickerClearLoopPlayed(); } - std::unique_ptr stickerTakeLottie( + std::unique_ptr stickerTakePlayer( not_null data, const Lottie::ColorReplacements *replacements) override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_service_media_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_service_media_gift.cpp index e1924636a..01a868dfd 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_service_media_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_service_media_gift.cpp @@ -195,11 +195,11 @@ void MediaGift::stickerClearLoopPlayed() { } } -std::unique_ptr MediaGift::stickerTakeLottie( +std::unique_ptr MediaGift::stickerTakePlayer( not_null data, const Lottie::ColorReplacements *replacements) { return _sticker - ? _sticker->stickerTakeLottie(data, replacements) + ? _sticker->stickerTakePlayer(data, replacements) : nullptr; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_service_media_gift.h b/Telegram/SourceFiles/history/view/media/history_view_service_media_gift.h index 4b26d60ba..711290545 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_service_media_gift.h +++ b/Telegram/SourceFiles/history/view/media/history_view_service_media_gift.h @@ -42,7 +42,7 @@ public: bool pressed) override; void stickerClearLoopPlayed() override; - std::unique_ptr stickerTakeLottie( + std::unique_ptr stickerTakePlayer( not_null data, const Lottie::ColorReplacements *replacements) override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp b/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp index 6603dbdcf..8c2e1bfb6 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp @@ -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; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index 606300e36..d8e6c4adb 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -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); + + void setRepaintCallback(Fn 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; + rpl::lifetime _repaintLifetime; + +}; + +LottiePlayer::LottiePlayer(std::unique_ptr lottie) +: _lottie(std::move(lottie)) { +} + +void LottiePlayer::setRepaintCallback(Fn 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 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 _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 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( + ChatHelpers::LottiePlayerFromDocument( + _dataMedia.get(), + _replacements, + _cachingTag, + countOptimalSize() * style::DevicePixelRatio(), + Lottie::Quality::High)); + } else if (_data->sticker()->isWebm()) { + _player = std::make_unique( + _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 Sticker::stickerTakeLottie( +std::unique_ptr Sticker::stickerTakePlayer( not_null data, const Lottie::ColorReplacements *replacements) { return (data == _data && replacements == _replacements) - ? std::move(_lottie) + ? std::move(_player) : nullptr; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.h b/Telegram/SourceFiles/history/view/media/history_view_sticker.h index bb6ef7984..ea8c15107 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.h +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.h @@ -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 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 stickerTakeLottie( + std::unique_ptr stickerTakePlayer( not_null 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 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 _parent; const not_null _data; const Lottie::ColorReplacements *_replacements = nullptr; - std::unique_ptr _lottie; + std::unique_ptr _player; mutable std::shared_ptr _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 diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index a8b9c10d2..ebafdf831 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -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 { diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h index 46e2a524c..83794b1b6 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h @@ -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 _dataMedia; @@ -419,7 +419,7 @@ private: Media::Clip::ReaderPointer _gif; mutable std::shared_ptr _photoMedia; mutable std::shared_ptr _documentMedia; - mutable QPixmap _thumb; + mutable QImage _thumb; mutable bool _thumbGood = false; mutable std::unique_ptr _radial; Ui::Text::String _title, _description; diff --git a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp index 76167428a..cee45b923 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp @@ -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; diff --git a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.h index 4d5493f40..b38fa0381 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.h @@ -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; diff --git a/Telegram/SourceFiles/media/clip/media_clip_implementation.h b/Telegram/SourceFiles/media/clip/media_clip_implementation.h index 21ba4d54b..be5daf47b 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_implementation.h +++ b/Telegram/SourceFiles/media/clip/media_clip_implementation.h @@ -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; diff --git a/Telegram/SourceFiles/media/clip/media_clip_reader.cpp b/Telegram/SourceFiles/media/clip/media_clip_reader.cpp index 26700cc6b..02aee535e 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_reader.cpp @@ -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)); } diff --git a/Telegram/SourceFiles/media/clip/media_clip_reader.h b/Telegram/SourceFiles/media/clip/media_clip_reader.h index ad7c5c9bc..3096dfd71 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_reader.h +++ b/Telegram/SourceFiles/media/clip/media_clip_reader.h @@ -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; diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 060a72352..1b6ce5654 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -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); } } diff --git a/Telegram/SourceFiles/overview/overview_layout.h b/Telegram/SourceFiles/overview/overview_layout.h index cab6005b8..a72671d0d 100644 --- a/Telegram/SourceFiles/overview/overview_layout.h +++ b/Telegram/SourceFiles/overview/overview_layout.h @@ -255,7 +255,7 @@ private: mutable std::shared_ptr _dataMedia; StatusText _status; - QPixmap _thumb; + QImage _thumb; bool _thumbGood = false; }; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp index db22ec37d..720a74f71 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp @@ -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(); diff --git a/Telegram/SourceFiles/window/window_media_preview.cpp b/Telegram/SourceFiles/window/window_media_preview.cpp index 11f3be93b..93a252676 100644 --- a/Telegram/SourceFiles/window/window_media_preview.cpp +++ b/Telegram/SourceFiles/window/window_media_preview.cpp @@ -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()) {