Render webm stickers in StickersListWidget.

This commit is contained in:
John Preston 2022-01-25 00:40:26 +03:00
parent 20dbf18106
commit 8e749173de
12 changed files with 483 additions and 319 deletions

View file

@ -46,6 +46,8 @@ namespace {
constexpr auto kSearchRequestDelay = 400; constexpr auto kSearchRequestDelay = 400;
constexpr auto kInlineItemsMaxPerRow = 5; constexpr auto kInlineItemsMaxPerRow = 5;
constexpr auto kSearchBotUsername = "gif"_cs; constexpr auto kSearchBotUsername = "gif"_cs;
constexpr auto kMinRepaintDelay = crl::time(16);
constexpr auto kMinAfterScrollDelay = crl::time(33);
} // namespace } // namespace
@ -173,7 +175,9 @@ GifsListWidget::GifsListWidget(
, _mosaic(st::emojiPanWidth - st::inlineResultsLeft) , _mosaic(st::emojiPanWidth - st::inlineResultsLeft)
, _previewTimer([=] { showPreview(); }) { , _previewTimer([=] { showPreview(); }) {
setMouseTracking(true); setMouseTracking(true);
setAttribute(Qt::WA_OpaquePaintEvent);
// Otherwise our optimization on repainting is too aggressive.
setAttribute(Qt::WA_OpaquePaintEvent, false);
_inlineRequestTimer.setSingleShot(true); _inlineRequestTimer.setSingleShot(true);
connect( connect(
@ -239,7 +243,7 @@ void GifsListWidget::visibleTopBottomUpdated(
auto top = getVisibleTop(); auto top = getVisibleTop();
Inner::visibleTopBottomUpdated(visibleTop, visibleBottom); Inner::visibleTopBottomUpdated(visibleTop, visibleBottom);
if (top != getVisibleTop()) { if (top != getVisibleTop()) {
_lastScrolled = crl::now(); _lastScrolledAt = crl::now();
} }
checkLoadMore(); checkLoadMore();
} }
@ -498,7 +502,7 @@ void GifsListWidget::clearSelection() {
setCursor(style::cur_default); setCursor(style::cur_default);
} }
_selected = _pressed = -1; _selected = _pressed = -1;
update(); repaintItems();
} }
TabbedSelector::InnerFooter *GifsListWidget::getFooter() const { TabbedSelector::InnerFooter *GifsListWidget::getFooter() const {
@ -544,7 +548,7 @@ void GifsListWidget::refreshSavedGifs() {
deleteUnusedGifLayouts(); deleteUnusedGifLayouts();
resizeToWidth(width()); resizeToWidth(width());
update(); repaintItems();
} }
if (isVisible()) { if (isVisible()) {
@ -672,7 +676,7 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result
} }
resizeToWidth(width()); resizeToWidth(width());
update(); repaintItems();
_lastMousePos = QCursor::pos(); _lastMousePos = QCursor::pos();
updateSelected(); updateSelected();
@ -711,16 +715,13 @@ void GifsListWidget::inlineItemLayoutChanged(const InlineBots::Layout::ItemBase
} }
} }
void GifsListWidget::inlineItemRepaint(const InlineBots::Layout::ItemBase *layout) { void GifsListWidget::inlineItemRepaint(
auto ms = crl::now(); const InlineBots::Layout::ItemBase *layout) {
if (_lastScrolled + 100 <= ms) { updateInlineItems();
update();
} else {
_updateInlineItems.callOnce(_lastScrolled + 100 - ms);
}
} }
bool GifsListWidget::inlineItemVisible(const InlineBots::Layout::ItemBase *layout) { bool GifsListWidget::inlineItemVisible(
const InlineBots::Layout::ItemBase *layout) {
auto position = layout->position(); auto position = layout->position();
if (position < 0 || !isVisible()) { if (position < 0 || !isVisible()) {
return false; return false;
@ -930,12 +931,22 @@ void GifsListWidget::showPreview() {
} }
void GifsListWidget::updateInlineItems() { void GifsListWidget::updateInlineItems() {
auto ms = crl::now(); const auto now = crl::now();
if (_lastScrolled + 100 <= ms) {
update(); const auto delay = std::max(
} else { _lastScrolledAt + kMinAfterScrollDelay - now,
_updateInlineItems.callOnce(_lastScrolled + 100 - ms); _lastUpdatedAt + kMinRepaintDelay - now);
if (delay <= 0) {
repaintItems();
} else if (!_updateInlineItems.isActive()
|| _updateInlineItems.remainingTime() > kMinRepaintDelay) {
_updateInlineItems.callOnce(std::max(delay, kMinRepaintDelay));
} }
} }
void GifsListWidget::repaintItems(crl::time now) {
_lastUpdatedAt = now ? now : crl::now();
update();
}
} // namespace ChatHelpers } // namespace ChatHelpers

View file

@ -133,12 +133,14 @@ private:
void paintInlineItems(Painter &p, QRect clip); void paintInlineItems(Painter &p, QRect clip);
void updateInlineItems(); void updateInlineItems();
void repaintItems(crl::time now = 0);
void showPreview(); void showPreview();
MTP::Sender _api; MTP::Sender _api;
Section _section = Section::Gifs; Section _section = Section::Gifs;
crl::time _lastScrolled = 0; crl::time _lastScrolledAt = 0;
crl::time _lastUpdatedAt = 0;
base::Timer _updateInlineItems; base::Timer _updateInlineItems;
bool _inlineWithThumb = false; bool _inlineWithThumb = false;

View file

@ -111,7 +111,7 @@ struct StickerIcon {
}; };
class StickersListWidget::Footer : public TabbedSelector::InnerFooter { class StickersListWidget::Footer final : public TabbedSelector::InnerFooter {
public: public:
explicit Footer( explicit Footer(
not_null<StickersListWidget*> parent, not_null<StickersListWidget*> parent,
@ -209,6 +209,16 @@ private:
}; };
struct StickersListWidget::Sticker {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
Lottie::Animation *lottie = nullptr;
Media::Clip::ReaderPointer webm;
QPixmap savedFrame;
void ensureMediaCreated();
};
auto StickersListWidget::PrepareStickers( auto StickersListWidget::PrepareStickers(
const QVector<DocumentData*> &pack) const QVector<DocumentData*> &pack)
-> std::vector<Sticker> { -> std::vector<Sticker> {
@ -284,6 +294,7 @@ void StickersListWidget::Footer::clearHeavyData() {
count); count);
for (auto i = 0; i != count; ++i) { for (auto i = 0; i != count; ++i) {
auto &icon = _icons[i]; auto &icon = _icons[i];
icon.webm = nullptr;
icon.lottie = nullptr; icon.lottie = nullptr;
icon.lifetime.destroy(); icon.lifetime.destroy();
icon.stickerMedia = nullptr; icon.stickerMedia = nullptr;
@ -737,6 +748,7 @@ void StickersListWidget::Footer::refreshIcons(
if (const auto i = indices.find(now.setId); i != end(indices)) { if (const auto i = indices.find(now.setId); i != end(indices)) {
auto &was = _icons[i->second]; auto &was = _icons[i->second];
if (now.sticker == was.sticker) { if (now.sticker == was.sticker) {
now.webm = std::move(was.webm);
now.lottie = std::move(was.lottie); now.lottie = std::move(was.lottie);
now.lifetime = std::move(was.lifetime); now.lifetime = std::move(was.lifetime);
now.savedFrame = std::move(was.savedFrame); now.savedFrame = std::move(was.savedFrame);
@ -979,7 +991,7 @@ StickersListWidget::StickersListWidget(
session().downloaderTaskFinished( session().downloaderTaskFinished(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
if (isVisible()) { if (isVisible()) {
update(); repaintItems();
readVisibleFeatured(getVisibleTop(), getVisibleBottom()); readVisibleFeatured(getVisibleTop(), getVisibleBottom());
} }
}, lifetime()); }, lifetime());
@ -1135,7 +1147,7 @@ void StickersListWidget::preloadMoreOfficial() {
} }
}); });
resizeToWidth(width()); resizeToWidth(width());
update(); repaintItems();
}).send(); }).send();
} }
@ -1499,8 +1511,8 @@ void StickersListWidget::takeHeavyData(Set &to, Set &from) {
} }
} }
for (const auto &sticker : fromList) { for (const auto &sticker : fromList) {
if (sticker.animated) { if (sticker.lottie) {
to.lottiePlayer->remove(sticker.animated); to.lottiePlayer->remove(sticker.lottie);
} }
} }
} }
@ -1509,7 +1521,8 @@ void StickersListWidget::takeHeavyData(Set &to, Set &from) {
void StickersListWidget::takeHeavyData(Sticker &to, Sticker &from) { void StickersListWidget::takeHeavyData(Sticker &to, Sticker &from) {
to.documentMedia = std::move(from.documentMedia); to.documentMedia = std::move(from.documentMedia);
to.savedFrame = std::move(from.savedFrame); to.savedFrame = std::move(from.savedFrame);
to.animated = base::take(from.animated); to.lottie = base::take(from.lottie);
to.webm = base::take(from.webm);
} }
auto StickersListWidget::shownSets() const -> const std::vector<Set> & { auto StickersListWidget::shownSets() const -> const std::vector<Set> & {
@ -1629,6 +1642,9 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
? &_pressed ? &_pressed
: &_selected); : &_selected);
const auto now = crl::now();
const auto paused = controller()->isGifPausedAtLeastFor(
Window::GifPauseReason::SavedGifs);
if (sets.empty() && _section == Section::Search) { if (sets.empty() && _section == Section::Search) {
paintEmptySearchResults(p); paintEmptySearchResults(p);
} }
@ -1708,9 +1724,11 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false; auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false;
auto deleteSelected = false; auto deleteSelected = false;
paintSticker(p, set, info.rowsTop, info.section, index, selected, deleteSelected); paintSticker(p, set, info.rowsTop, info.section, index, now, paused, selected, deleteSelected);
}
if (!paused) {
markLottieFrameShown(set);
} }
markLottieFrameShown(set);
return true; return true;
} }
if (setHasTitle(set) && clip.top() < info.rowsTop) { if (setHasTitle(set) && clip.top() < info.rowsTop) {
@ -1754,21 +1772,19 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false; auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false;
auto deleteSelected = selected && selectedSticker->overDelete; auto deleteSelected = selected && selectedSticker->overDelete;
paintSticker(p, set, info.rowsTop, info.section, index, selected, deleteSelected); paintSticker(p, set, info.rowsTop, info.section, index, now, paused, selected, deleteSelected);
} }
} }
markLottieFrameShown(set); if (!paused) {
markLottieFrameShown(set);
}
return true; return true;
}); });
} }
void StickersListWidget::markLottieFrameShown(Set &set) { void StickersListWidget::markLottieFrameShown(Set &set) {
if (const auto player = set.lottiePlayer.get()) { if (const auto player = set.lottiePlayer.get()) {
const auto paused = controller()->isGifPausedAtLeastFor( player->markFrameShown();
Window::GifPauseReason::SavedGifs);
if (!paused) {
player->markFrameShown();
}
} }
} }
@ -1801,7 +1817,8 @@ void StickersListWidget::clearHeavyIn(Set &set, bool clearSavedFrames) {
if (clearSavedFrames) { if (clearSavedFrames) {
sticker.savedFrame = QPixmap(); sticker.savedFrame = QPixmap();
} }
sticker.animated = nullptr; sticker.webm = nullptr;
sticker.lottie = nullptr;
sticker.documentMedia = nullptr; sticker.documentMedia = nullptr;
} }
} }
@ -1821,7 +1838,7 @@ void StickersListWidget::pauseInvisibleLottieIn(const SectionInfo &info) {
if (index >= info.count) { if (index >= info.count) {
break; break;
} }
if (const auto animated = set.stickers[index].animated) { if (const auto animated = set.stickers[index].lottie) {
player->pause(animated); player->pause(animated);
} }
} }
@ -1903,16 +1920,13 @@ void StickersListWidget::ensureLottiePlayer(Set &set) {
raw->updates( raw->updates(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
auto &sets = shownSets();
enumerateSections([&](const SectionInfo &info) { enumerateSections([&](const SectionInfo &info) {
if (shownSets()[info.section].lottiePlayer.get() == raw) { if (sets[info.section].lottiePlayer.get() != raw) {
update( return true;
0,
info.rowsTop,
width(),
info.rowsBottom - info.rowsTop);
return false;
} }
return true; repaintItems(info);
return false;
}); });
}, set.lottieLifetime); }, set.lottieLifetime);
} }
@ -1923,20 +1937,113 @@ void StickersListWidget::setupLottie(Set &set, int section, int index) {
// Document should be loaded already for the animation to be set up. // Document should be loaded already for the animation to be set up.
Assert(sticker.documentMedia != nullptr); Assert(sticker.documentMedia != nullptr);
sticker.animated = LottieAnimationFromDocument( sticker.lottie = LottieAnimationFromDocument(
set.lottiePlayer.get(), set.lottiePlayer.get(),
sticker.documentMedia.get(), sticker.documentMedia.get(),
StickerLottieSize::StickersPanel, StickerLottieSize::StickersPanel,
boundingBoxSize() * cIntRetinaFactor()); boundingBoxSize() * cIntRetinaFactor());
} }
void StickersListWidget::setupWebm(Set &set, int section, int index) {
auto &sticker = set.stickers[index];
// Document should be loaded already for the animation to be set up.
Assert(sticker.documentMedia != nullptr);
const auto setId = set.id;
const auto document = sticker.document;
auto callback = [=](Media::Clip::Notification notification) {
clipCallback(notification, setId, document, index);
};
sticker.webm = Media::Clip::MakeReader(
sticker.documentMedia->owner()->location(),
sticker.documentMedia->bytes(),
std::move(callback));
}
void StickersListWidget::clipCallback(
Media::Clip::Notification notification,
uint64 setId,
not_null<DocumentData*> document,
int indexHint) {
Expects(indexHint >= 0);
auto &sets = shownSets();
enumerateSections([&](const SectionInfo &info) {
auto &set = sets[info.section];
if (set.id != setId) {
return true;
}
using namespace Media::Clip;
switch (notification) {
case Notification::Reinit: {
const auto j = (indexHint < set.stickers.size()
&& set.stickers[indexHint].document == document)
? (begin(set.stickers) + indexHint)
: ranges::find(set.stickers, document, &Sticker::document);
if (j == end(set.stickers) || !j->webm) {
break;
}
const auto index = j - begin(set.stickers);
auto &webm = j->webm;
if (webm->state() == State::Error) {
webm.setBad();
} else if (webm->ready() && !webm->started()) {
const auto size = ComputeStickerSize(
j->document,
boundingBoxSize());
webm->start({ .frame = size, .keepAlpha = true });
} else if (webm->autoPausedGif() && !itemVisible(info, index)) {
webm = nullptr;
}
} break;
case Notification::Repaint: break;
}
repaintItems(info);
return false;
});
}
bool StickersListWidget::itemVisible(
const SectionInfo &info,
int index) const {
const auto visibleTop = getVisibleTop();
const auto visibleBottom = getVisibleBottom();
const auto row = index / _columnCount;
const auto top = info.rowsTop + row * _singleSize.height();
const auto bottom = top + _singleSize.height();
return (visibleTop < bottom) && (visibleBottom > top);
}
void StickersListWidget::repaintItems(const SectionInfo &info) {
update(
0,
info.rowsTop,
width(),
info.rowsBottom - info.rowsTop);
}
void StickersListWidget::repaintItems() {
}
QSize StickersListWidget::boundingBoxSize() const { QSize StickersListWidget::boundingBoxSize() const {
return QSize( return QSize(
_singleSize.width() - st::roundRadiusSmall * 2, _singleSize.width() - st::roundRadiusSmall * 2,
_singleSize.height() - st::roundRadiusSmall * 2); _singleSize.height() - st::roundRadiusSmall * 2);
} }
void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section, int index, bool selected, bool deleteSelected) { void StickersListWidget::paintSticker(
Painter &p,
Set &set,
int y,
int section,
int index,
crl::time now,
bool paused,
bool selected,
bool deleteSelected) {
auto &sticker = set.stickers[index]; auto &sticker = set.stickers[index];
sticker.ensureMediaCreated(); sticker.ensureMediaCreated();
const auto document = sticker.document; const auto document = sticker.document;
@ -1946,10 +2053,13 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
} }
const auto isLottie = document->sticker()->isLottie(); const auto isLottie = document->sticker()->isLottie();
const auto isWebm = document->sticker()->isWebm();
if (isLottie if (isLottie
&& !sticker.animated && !sticker.lottie
&& media->loaded()) { && media->loaded()) {
setupLottie(set, section, index); setupLottie(set, section, index);
} else if (isWebm && !sticker.webm && media->loaded()) {
setupWebm(set, section, index);
} }
int row = (index / _columnCount), col = (index % _columnCount); int row = (index / _columnCount), col = (index % _columnCount);
@ -1963,25 +2073,15 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
media->checkStickerSmall(); media->checkStickerSmall();
auto w = 1; const auto size = ComputeStickerSize(document, boundingBoxSize());
auto h = 1; const auto ppos = pos + QPoint(
if (isLottie && !document->dimensions.isEmpty()) { (_singleSize.width() - size.width()) / 2,
const auto request = Lottie::FrameRequest{ boundingBoxSize() * cIntRetinaFactor() }; (_singleSize.height() - size.height()) / 2);
const auto size = request.size(document->dimensions, true) / cIntRetinaFactor();
w = std::max(size.width(), 1);
h = std::max(size.height(), 1);
} else {
auto coef = qMin((_singleSize.width() - st::roundRadiusSmall * 2) / float64(document->dimensions.width()), (_singleSize.height() - st::roundRadiusSmall * 2) / float64(document->dimensions.height()));
if (coef > 1) coef = 1;
w = std::max(qRound(coef * document->dimensions.width()), 1);
h = std::max(qRound(coef * document->dimensions.height()), 1);
}
auto ppos = pos + QPoint((_singleSize.width() - w) / 2, (_singleSize.height() - h) / 2);
if (sticker.animated && sticker.animated->ready()) { if (sticker.lottie && sticker.lottie->ready()) {
auto request = Lottie::FrameRequest(); auto request = Lottie::FrameRequest();
request.box = boundingBoxSize() * cIntRetinaFactor(); request.box = boundingBoxSize() * cIntRetinaFactor();
const auto frame = sticker.animated->frame(request); const auto frame = sticker.lottie->frame(request);
p.drawImage( p.drawImage(
QRect(ppos, frame.size() / cIntRetinaFactor()), QRect(ppos, frame.size() / cIntRetinaFactor()),
frame); frame);
@ -1989,13 +2089,20 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
sticker.savedFrame = QPixmap::fromImage(frame, Qt::ColorOnly); sticker.savedFrame = QPixmap::fromImage(frame, Qt::ColorOnly);
sticker.savedFrame.setDevicePixelRatio(cRetinaFactor()); sticker.savedFrame.setDevicePixelRatio(cRetinaFactor());
} }
set.lottiePlayer->unpause(sticker.animated); set.lottiePlayer->unpause(sticker.lottie);
} else if (sticker.webm && sticker.webm->ready()) {
p.drawPixmapLeft(
ppos,
width(),
sticker.webm->current(
{ .frame = size, .keepAlpha = true },
now));
} else { } else {
const auto image = media->getStickerSmall(); const auto image = media->getStickerSmall();
const auto pixmap = !sticker.savedFrame.isNull() const auto pixmap = !sticker.savedFrame.isNull()
? sticker.savedFrame ? sticker.savedFrame
: image : image
? image->pixSingle(w, h, { .outer = { w, h } }) ? image->pixSingle(size, { .outer = size })
: QPixmap(); : QPixmap();
if (!pixmap.isNull()) { if (!pixmap.isNull()) {
p.drawPixmapLeft(ppos, width(), pixmap); p.drawPixmapLeft(ppos, width(), pixmap);
@ -2006,7 +2113,7 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
PaintStickerThumbnailPath( PaintStickerThumbnailPath(
p, p,
media.get(), media.get(),
QRect(ppos, QSize{ w, h }), QRect(ppos, size),
_pathGradient.get()); _pathGradient.get());
} }
} }
@ -2227,7 +2334,7 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
auto pressed = _pressed; auto pressed = _pressed;
setPressed(v::null); setPressed(v::null);
if (pressed != _selected) { if (pressed != _selected) {
update(); repaintItems();
} }
auto activated = ClickHandler::unpressed(); auto activated = ClickHandler::unpressed();
@ -2329,7 +2436,7 @@ void StickersListWidget::removeRecentSticker(int section, int index) {
if (refresh) { if (refresh) {
refreshRecentStickers(); refreshRecentStickers();
updateSelected(); updateSelected();
update(); repaintItems();
} }
} }
@ -2389,7 +2496,7 @@ void StickersListWidget::enterFromChildEvent(QEvent *e, QWidget *child) {
void StickersListWidget::clearSelection() { void StickersListWidget::clearSelection() {
setPressed(v::null); setPressed(v::null);
setSelected(v::null); setSelected(v::null);
update(); repaintItems();
} }
TabbedSelector::InnerFooter *StickersListWidget::getFooter() const { TabbedSelector::InnerFooter *StickersListWidget::getFooter() const {
@ -2449,7 +2556,7 @@ void StickersListWidget::refreshStickers() {
_lastMousePosition = QCursor::pos(); _lastMousePosition = QCursor::pos();
updateSelected(); updateSelected();
update(); repaintItems();
} }
void StickersListWidget::refreshMySets() { void StickersListWidget::refreshMySets() {
@ -3031,7 +3138,7 @@ void StickersListWidget::showStickerSet(uint64 setId) {
if (_footer) { if (_footer) {
_footer->refreshIcons(ValidateIconAnimations::Scroll); _footer->refreshIcons(ValidateIconAnimations::Scroll);
} }
update(); repaintItems();
} }
scrollTo(0); scrollTo(0);
@ -3063,7 +3170,7 @@ void StickersListWidget::showStickerSet(uint64 setId) {
_lastMousePosition = QCursor::pos(); _lastMousePosition = QCursor::pos();
update(); repaintItems();
} }
void StickersListWidget::refreshMegagroupSetGeometry() { void StickersListWidget::refreshMegagroupSetGeometry() {

View file

@ -39,6 +39,11 @@ class DocumentMedia;
class StickersSet; class StickersSet;
} // namespace Data } // namespace Data
namespace Media::Clip {
class ReaderPointer;
enum class Notification;
} // namespace Media::Clip
namespace ChatHelpers { namespace ChatHelpers {
struct StickerIcon; struct StickerIcon;
@ -113,6 +118,7 @@ protected:
private: private:
class Footer; class Footer;
struct Sticker;
enum class Section { enum class Section {
Featured, Featured,
@ -178,15 +184,6 @@ private:
int rowsBottom = 0; int rowsBottom = 0;
}; };
struct Sticker {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
Lottie::Animation *animated = nullptr;
QPixmap savedFrame;
void ensureMediaCreated();
};
struct Set { struct Set {
Set( Set(
uint64 id, uint64 id,
@ -279,11 +276,27 @@ private:
void paintStickers(Painter &p, QRect clip); void paintStickers(Painter &p, QRect clip);
void paintMegagroupEmptySet(Painter &p, int y, bool buttonSelected); void paintMegagroupEmptySet(Painter &p, int y, bool buttonSelected);
void paintSticker(Painter &p, Set &set, int y, int section, int index, bool selected, bool deleteSelected); void paintSticker(
Painter &p,
Set &set,
int y,
int section,
int index,
crl::time now,
bool paused,
bool selected,
bool deleteSelected);
void paintEmptySearchResults(Painter &p); void paintEmptySearchResults(Painter &p);
void ensureLottiePlayer(Set &set); void ensureLottiePlayer(Set &set);
void setupLottie(Set &set, int section, int index); void setupLottie(Set &set, int section, int index);
void setupWebm(Set &set, int section, int index);
void clipCallback(
Media::Clip::Notification notification,
uint64 setId,
not_null<DocumentData*> document,
int indexHint);
[[nodiscard]] bool itemVisible(const SectionInfo &info, int index) const;
void markLottieFrameShown(Set &set); void markLottieFrameShown(Set &set);
void checkVisibleLottie(); void checkVisibleLottie();
void pauseInvisibleLottieIn(const SectionInfo &info); void pauseInvisibleLottieIn(const SectionInfo &info);
@ -292,6 +305,8 @@ private:
void takeHeavyData(Sticker &to, Sticker &from); void takeHeavyData(Sticker &to, Sticker &from);
void clearHeavyIn(Set &set, bool clearSavedFrames = true); void clearHeavyIn(Set &set, bool clearSavedFrames = true);
void clearHeavyData(); void clearHeavyData();
void repaintItems();
void repaintItems(const SectionInfo &info);
int stickersRight() const; int stickersRight() const;
bool featuredHasAddButton(int index) const; bool featuredHasAddButton(int index) const;
@ -302,8 +317,8 @@ private:
void refreshMegagroupSetGeometry(); void refreshMegagroupSetGeometry();
QRect megagroupSetButtonRectFinal() const; QRect megagroupSetButtonRectFinal() const;
const Data::StickersSetsOrder &defaultSetsOrder() const; [[nodiscard]] const Data::StickersSetsOrder &defaultSetsOrder() const;
Data::StickersSetsOrder &defaultSetsOrderRef(); [[nodiscard]] Data::StickersSetsOrder &defaultSetsOrderRef();
enum class AppendSkip { enum class AppendSkip {
None, None,
@ -316,7 +331,6 @@ private:
bool externalLayout, bool externalLayout,
AppendSkip skip = AppendSkip::None); AppendSkip skip = AppendSkip::None);
void selectEmoji(EmojiPtr emoji);
int stickersLeft() const; int stickersLeft() const;
QRect stickerRect(int section, int sel); QRect stickerRect(int section, int sel);

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "storage/cache/storage_cache_database.h" #include "storage/cache/storage_cache_database.h"
#include "history/view/media/history_view_media_common.h"
#include "media/clip/media_clip_reader.h" #include "media/clip/media_clip_reader.h"
#include "ui/effects/path_shift_gradient.h" #include "ui/effects/path_shift_gradient.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -276,4 +277,15 @@ bool PaintStickerThumbnailPath(
}); });
} }
QSize ComputeStickerSize(not_null<DocumentData*> document, QSize box) {
const auto sticker = document->sticker();
const auto dimensions = document->dimensions;
if (!sticker || !sticker->isLottie() || dimensions.isEmpty()) {
return HistoryView::DownscaledSize(dimensions, box);
}
const auto ratio = style::DevicePixelRatio();
const auto request = Lottie::FrameRequest{ box * ratio };
return HistoryView::NonEmptySize(request.size(dimensions, true) / ratio);
}
} // namespace ChatHelpers } // namespace ChatHelpers

View file

@ -113,4 +113,8 @@ bool PaintStickerThumbnailPath(
QRect target, QRect target,
not_null<Ui::PathShiftGradient*> gradient); not_null<Ui::PathShiftGradient*> gradient);
[[nodiscard]] QSize ComputeStickerSize(
not_null<DocumentData*> document,
QSize box);
} // namespace ChatHelpers } // namespace ChatHelpers

View file

@ -168,19 +168,20 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
} }
const auto radial = isRadialAnimation(); const auto radial = isRadialAnimation();
int32 height = st::inlineMediaHeight; const auto frame = countFrameSize();
QSize frame = countFrameSize(); const auto r = QRect(0, 0, _width, st::inlineMediaHeight);
QRect r(0, 0, _width, height);
if (animating) { if (animating) {
const auto pixmap = _gif->current(frame.width(), frame.height(), _width, height, ImageRoundRadius::None, RectPart::None, context->paused ? 0 : context->ms); const auto pixmap = _gif->current({
.frame = frame,
.outer = r.size(),
}, context->paused ? 0 : context->ms);
if (_thumb.isNull()) { if (_thumb.isNull()) {
_thumb = pixmap; _thumb = pixmap;
_thumbGood = true; _thumbGood = true;
} }
p.drawPixmap(r.topLeft(), pixmap); p.drawPixmap(r.topLeft(), pixmap);
} else { } else {
prepareThumbnail({ _width, height }, frame); prepareThumbnail(r.size(), frame);
if (_thumb.isNull()) { if (_thumb.isNull()) {
p.fillRect(r, st::overviewPhotoBg); p.fillRect(r, st::overviewPhotoBg);
} else { } else {
@ -211,7 +212,11 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
return &st::historyFileInDownload; return &st::historyFileInDownload;
}(); }();
const auto size = st::inlineRadialSize; const auto size = st::inlineRadialSize;
QRect inner((_width - size) / 2, (height - size) / 2, size, size); QRect inner(
(r.width() - size) / 2,
(r.height() - size) / 2,
size,
size);
icon->paintInCenter(p, inner); icon->paintInCenter(p, inner);
if (radial) { if (radial) {
p.setOpacity(1); p.setOpacity(1);
@ -404,9 +409,10 @@ void Gif::clipCallback(Media::Clip::Notification notification) {
_gif->height()); _gif->height());
_gif.reset(); _gif.reset();
} else { } else {
auto height = st::inlineMediaHeight; _gif->start({
auto frame = countFrameSize(); .frame = countFrameSize(),
_gif->start(frame.width(), frame.height(), _width, height, ImageRoundRadius::None, RectPart::None); .outer = { _width, st::inlineMediaHeight },
});
} }
} else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) { } else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) {
unloadHeavyPart(); unloadHeavyPart();
@ -1424,7 +1430,10 @@ void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) con
radial = isRadialAnimation(); radial = isRadialAnimation();
if (animating) { if (animating) {
const auto pixmap = _gif->current(_frameSize.width(), _frameSize.height(), st::inlineThumbSize, st::inlineThumbSize, ImageRoundRadius::None, RectPart::None, context->paused ? 0 : context->ms); const auto pixmap = _gif->current({
.frame = _frameSize,
.outer = { st::inlineThumbSize, st::inlineThumbSize },
}, context->paused ? 0 : context->ms);
if (_thumb.isNull()) { if (_thumb.isNull()) {
_thumb = pixmap; _thumb = pixmap;
_thumbGood = true; _thumbGood = true;
@ -1594,13 +1603,10 @@ void Game::clipCallback(Media::Clip::Notification notification) {
_gif->height()); _gif->height());
_gif.reset(); _gif.reset();
} else { } else {
_gif->start( _gif->start({
_frameSize.width(), .frame = _frameSize,
_frameSize.height(), .outer = { st::inlineThumbSize, st::inlineThumbSize },
st::inlineThumbSize, });
st::inlineThumbSize,
ImageRoundRadius::None,
RectPart::None);
} }
} else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) { } else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) {
unloadHeavyPart(); unloadHeavyPart();

View file

@ -35,44 +35,49 @@ constexpr auto kClipThreadsCount = 8;
constexpr auto kAverageGifSize = 320 * 240; constexpr auto kAverageGifSize = 320 * 240;
constexpr auto kWaitBeforeGifPause = crl::time(200); constexpr auto kWaitBeforeGifPause = crl::time(200);
QVector<QThread*> threads;
QVector<Manager*> managers;
QImage PrepareFrameImage(const FrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) { QImage PrepareFrameImage(const FrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) {
auto needResize = (original.width() != request.framew) || (original.height() != request.frameh); const auto needResize = (original.size() != request.frame);
auto needOuterFill = (request.outerw != request.framew) || (request.outerh != request.frameh); const auto needOuterFill = request.outer.isValid() && (request.outer != request.frame);
auto needRounding = (request.radius != ImageRoundRadius::None); const auto needRounding = (request.radius != ImageRoundRadius::None);
if (!needResize && !needOuterFill && !hasAlpha && !needRounding) { if (!needResize && !needOuterFill && !hasAlpha && !needRounding) {
return original; return original;
} }
auto factor = request.factor; const auto factor = request.factor;
auto needNewCache = (cache.width() != request.outerw || cache.height() != request.outerh); const auto size = request.outer.isValid() ? request.outer : request.frame;
const auto needNewCache = (cache.size() != size);
if (needNewCache) { if (needNewCache) {
cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied); cache = QImage(size, QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(factor); cache.setDevicePixelRatio(factor);
} }
if (hasAlpha && request.keepAlpha) {
cache.fill(Qt::transparent);
}
{ {
Painter p(&cache); Painter p(&cache);
if (needNewCache) { const auto framew = request.frame.width();
if (request.framew < request.outerw) { const auto outerw = size.width();
p.fillRect(0, 0, (request.outerw - request.framew) / (2 * factor), cache.height() / factor, st::imageBg); const auto frameh = request.frame.height();
p.fillRect((request.outerw - request.framew) / (2 * factor) + (request.framew / factor), 0, (cache.width() / factor) - ((request.outerw - request.framew) / (2 * factor) + (request.framew / factor)), cache.height() / factor, st::imageBg); const auto outerh = size.height();
if (needNewCache && (!hasAlpha || !request.keepAlpha)) {
if (framew < outerw) {
p.fillRect(0, 0, (outerw - framew) / (2 * factor), cache.height() / factor, st::imageBg);
p.fillRect((outerw - framew) / (2 * factor) + (framew / factor), 0, (cache.width() / factor) - ((outerw - framew) / (2 * factor) + (framew / factor)), cache.height() / factor, st::imageBg);
} }
if (request.frameh < request.outerh) { if (frameh < outerh) {
p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), 0, qMin(cache.width(), request.framew) / factor, (request.outerh - request.frameh) / (2 * factor), st::imageBg); p.fillRect(qMax(0, (outerw - framew) / (2 * factor)), 0, qMin(cache.width(), framew) / factor, (outerh - frameh) / (2 * factor), st::imageBg);
p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), (request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor), qMin(cache.width(), request.framew) / factor, (cache.height() / factor) - ((request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor)), st::imageBg); p.fillRect(qMax(0, (outerw - framew) / (2 * factor)), (outerh - frameh) / (2 * factor) + (frameh / factor), qMin(cache.width(), framew) / factor, (cache.height() / factor) - ((outerh - frameh) / (2 * factor) + (frameh / factor)), st::imageBg);
} }
} }
if (hasAlpha) { if (hasAlpha && !request.keepAlpha) {
p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), qMax(0, (request.outerh - request.frameh) / (2 * factor)), qMin(cache.width(), request.framew) / factor, qMin(cache.height(), request.frameh) / factor, st::imageBgTransparent); p.fillRect(qMax(0, (outerw - framew) / (2 * factor)), qMax(0, (outerh - frameh) / (2 * factor)), qMin(cache.width(), framew) / factor, qMin(cache.height(), frameh) / factor, st::imageBgTransparent);
} }
auto position = QPoint((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor)); const auto position = QPoint((outerw - framew) / (2 * factor), (outerh - frameh) / (2 * factor));
if (needResize) { if (needResize) {
PainterHighQualityEnabler hq(p); PainterHighQualityEnabler hq(p);
auto dst = QRect(position, QSize(request.framew / factor, request.frameh / factor)); const auto dst = QRect(position, QSize(framew / factor, frameh / factor));
auto src = QRect(0, 0, original.width(), original.height()); const auto src = QRect(0, 0, original.width(), original.height());
p.drawImage(dst, original, src, Qt::ColorOnly); p.drawImage(dst, original, src, Qt::ColorOnly);
} else { } else {
p.drawImage(position, original); p.drawImage(position, original);
@ -93,6 +98,81 @@ QPixmap PrepareFrame(const FrameRequest &request, const QImage &original, bool h
} // namespace } // namespace
enum class ProcessResult {
Error,
Started,
Finished,
Paused,
Repaint,
CopyFrame,
Wait,
};
class Manager final : public QObject {
public:
explicit Manager(not_null<QThread*> thread);
~Manager();
int loadLevel() const {
return _loadLevel;
}
void append(Reader *reader, const Core::FileLocation &location, const QByteArray &data);
void start(Reader *reader);
void update(Reader *reader);
void stop(Reader *reader);
bool carries(Reader *reader) const;
private:
void process();
void finish();
void callback(Reader *reader, Notification notification);
void clear();
QAtomicInt _loadLevel;
using ReaderPointers = QMap<Reader*, QAtomicInt>;
ReaderPointers _readerPointers;
mutable QMutex _readerPointersMutex;
ReaderPointers::const_iterator constUnsafeFindReaderPointer(ReaderPrivate *reader) const;
ReaderPointers::iterator unsafeFindReaderPointer(ReaderPrivate *reader);
bool handleProcessResult(ReaderPrivate *reader, ProcessResult result, crl::time ms);
enum ResultHandleState {
ResultHandleRemove,
ResultHandleStop,
ResultHandleContinue,
};
ResultHandleState handleResult(ReaderPrivate *reader, ProcessResult result, crl::time ms);
using Readers = QMap<ReaderPrivate*, crl::time>;
Readers _readers;
QTimer _timer;
QThread *_processingInThread = nullptr;
bool _needReProcess = false;
};
namespace {
struct Worker {
Worker() : manager(&thread) {
thread.start();
}
~Worker() {
thread.quit();
thread.wait();
}
QThread thread;
Manager manager;
};
std::vector<std::unique_ptr<Worker>> Workers;
} // namespace
Reader::Reader( Reader::Reader(
const Core::FileLocation &location, const Core::FileLocation &location,
const QByteArray &data, const QByteArray &data,
@ -112,23 +192,21 @@ Reader::Reader(const QByteArray &data, Callback &&callback)
} }
void Reader::init(const Core::FileLocation &location, const QByteArray &data) { void Reader::init(const Core::FileLocation &location, const QByteArray &data) {
if (threads.size() < kClipThreadsCount) { if (Workers.size() < kClipThreadsCount) {
_threadIndex = threads.size(); _threadIndex = Workers.size();
threads.push_back(new QThread()); Workers.push_back(std::make_unique<Worker>());
managers.push_back(new Manager(threads.back()));
threads.back()->start();
} else { } else {
_threadIndex = int32(base::RandomValue<uint32>() % threads.size()); _threadIndex = base::RandomIndex(Workers.size());
int32 loadLevel = 0x7FFFFFFF; auto loadLevel = 0x7FFFFFFF;
for (int32 i = 0, l = threads.size(); i < l; ++i) { for (int i = 0, l = int(Workers.size()); i < l; ++i) {
int32 level = managers.at(i)->loadLevel(); const auto level = Workers[i]->manager.loadLevel();
if (level < loadLevel) { if (level < loadLevel) {
_threadIndex = i; _threadIndex = i;
loadLevel = level; loadLevel = level;
} }
} }
} }
managers.at(_threadIndex)->append(this, location, data); Workers[_threadIndex]->manager.append(this, location, data);
} }
Reader::Frame *Reader::frameToShow(int32 *index) const { // 0 means not ready Reader::Frame *Reader::frameToShow(int32 *index) const { // 0 means not ready
@ -207,65 +285,74 @@ void Reader::SafeCallback(
int threadIndex, int threadIndex,
Notification notification) { Notification notification) {
// Check if reader is not deleted already // Check if reader is not deleted already
if (managers.size() > threadIndex && managers.at(threadIndex)->carries(reader) && reader->_callback) { if (Workers.size() > threadIndex
&& Workers[threadIndex]->manager.carries(reader)
&& reader->_callback) {
reader->_callback(Notification(notification)); reader->_callback(Notification(notification));
} }
} }
void Reader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, ImageRoundRadius radius, RectParts corners) { void Reader::start(FrameRequest request) {
if (managers.size() <= _threadIndex) error(); if (Workers.size() <= _threadIndex) {
if (_state == State::Error) return; error();
if (_step.loadAcquire() == kWaitingForRequestStep) {
int factor = style::DevicePixelRatio();
FrameRequest request;
request.factor = factor;
request.framew = framew * factor;
request.frameh = frameh * factor;
request.outerw = outerw * factor;
request.outerh = outerh * factor;
request.radius = radius;
request.corners = corners;
_frames[0].request = _frames[1].request = _frames[2].request = request;
moveToNextShow();
managers.at(_threadIndex)->start(this);
} }
if (_state == State::Error
|| (_step.loadAcquire() != kWaitingForRequestStep)) {
return;
}
const auto factor = style::DevicePixelRatio();
request.factor = factor;
request.frame *= factor;
if (request.outer.isValid()) {
request.outer *= factor;
}
_frames[0].request = _frames[1].request = _frames[2].request = request;
moveToNextShow();
Workers[_threadIndex]->manager.start(this);
} }
QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, ImageRoundRadius radius, RectParts corners, crl::time ms) { QPixmap Reader::current(FrameRequest request, crl::time now) {
Expects(outerw > 0); Expects(!(request.outer.isValid()
Expects(outerh > 0); ? request.outer
: request.frame).isEmpty());
auto frame = frameToShow(); const auto frame = frameToShow();
Assert(frame != nullptr); Assert(frame != nullptr);
auto shouldBePaused = !ms; const auto shouldBePaused = !now;
if (!shouldBePaused) { if (!shouldBePaused) {
frame->displayed.storeRelease(1); frame->displayed.storeRelease(1);
if (_autoPausedGif.loadAcquire()) { if (_autoPausedGif.loadAcquire()) {
_autoPausedGif.storeRelease(0); _autoPausedGif.storeRelease(0);
if (managers.size() <= _threadIndex) error(); if (Workers.size() <= _threadIndex) {
if (_state != State::Error) { error();
managers.at(_threadIndex)->update(this); } else if (_state != State::Error) {
Workers[_threadIndex]->manager.update(this);
} }
} }
} else { } else {
frame->displayed.storeRelease(-1); frame->displayed.storeRelease(-1);
} }
auto factor = style::DevicePixelRatio(); const auto factor = style::DevicePixelRatio();
if (frame->pix.width() == outerw * factor request.factor = factor;
&& frame->pix.height() == outerh * factor request.frame *= factor;
&& frame->request.radius == radius if (request.outer.isValid()) {
&& frame->request.corners == corners) { request.outer *= factor;
}
const auto size = request.outer.isValid()
? request.outer
: request.frame;
Assert(frame->request.radius == request.radius
&& frame->request.corners == request.corners
&& frame->request.keepAlpha == request.keepAlpha);
if (frame->pix.size() == size) {
moveToNextShow(); moveToNextShow();
return frame->pix; return frame->pix;
} }
frame->request.framew = framew * factor; frame->request.frame = request.frame;
frame->request.frameh = frameh * factor; frame->request.outer = request.outer;
frame->request.outerw = outerw * factor;
frame->request.outerh = outerh * factor;
QImage cacheForResize; QImage cacheForResize;
frame->original.setDevicePixelRatio(factor); frame->original.setDevicePixelRatio(factor);
@ -277,18 +364,21 @@ QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh,
moveToNextShow(); moveToNextShow();
if (managers.size() <= _threadIndex) error(); if (Workers.size() <= _threadIndex) {
if (_state != State::Error) { error();
managers.at(_threadIndex)->update(this); } else if (_state != State::Error) {
Workers[_threadIndex]->manager.update(this);
} }
return frame->pix; return frame->pix;
} }
bool Reader::ready() const { bool Reader::ready() const {
if (_width && _height) return true; if (_width && _height) {
return true;
}
auto frame = frameToShow(); const auto frame = frameToShow();
if (frame) { if (frame) {
_width = frame->original.width(); _width = frame->original.width();
_height = frame->original.height(); _height = frame->original.height();
@ -298,7 +388,7 @@ bool Reader::ready() const {
} }
crl::time Reader::getPositionMs() const { crl::time Reader::getPositionMs() const {
if (auto frame = frameToShow()) { if (const auto frame = frameToShow()) {
return frame->positionMs; return frame->positionMs;
} }
return 0; return 0;
@ -309,11 +399,13 @@ crl::time Reader::getDurationMs() const {
} }
void Reader::pauseResumeVideo() { void Reader::pauseResumeVideo() {
if (managers.size() <= _threadIndex) error(); if (Workers.size() <= _threadIndex) {
error();
}
if (_state == State::Error) return; if (_state == State::Error) return;
_videoPauseRequest.storeRelease(1 - _videoPauseRequest.loadAcquire()); _videoPauseRequest.storeRelease(1 - _videoPauseRequest.loadAcquire());
managers.at(_threadIndex)->start(this); Workers[_threadIndex]->manager.start(this);
} }
bool Reader::videoPaused() const { bool Reader::videoPaused() const {
@ -333,9 +425,11 @@ State Reader::state() const {
} }
void Reader::stop() { void Reader::stop() {
if (managers.size() <= _threadIndex) error(); if (Workers.size() <= _threadIndex) {
error();
}
if (_state != State::Error) { if (_state != State::Error) {
managers.at(_threadIndex)->stop(this); Workers[_threadIndex]->manager.stop(this);
_width = _height = 0; _width = _height = 0;
} }
} }
@ -461,7 +555,7 @@ public:
bool renderFrame() { bool renderFrame() {
Expects(_request.valid()); Expects(_request.valid());
if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize(_request.framew, _request.frameh))) { if (!_implementation->renderFrame(frame()->original, frame()->alpha, _request.frame)) {
return false; return false;
} }
frame()->original.setDevicePixelRatio(_request.factor); frame()->original.setDevicePixelRatio(_request.factor);
@ -574,7 +668,7 @@ private:
}; };
Manager::Manager(QThread *thread) { Manager::Manager(not_null<QThread*> thread) {
moveToThread(thread); moveToThread(thread);
connect(thread, &QThread::started, this, [=] { process(); }); connect(thread, &QThread::started, this, [=] { process(); });
connect(thread, &QThread::finished, this, [=] { finish(); }); connect(thread, &QThread::finished, this, [=] { finish(); });
@ -884,17 +978,7 @@ Ui::PreparedFileInformation::Video PrepareForSending(const QString &fname, const
} }
void Finish() { void Finish() {
if (!threads.isEmpty()) { Workers.clear();
for (int32 i = 0, l = threads.size(); i < l; ++i) {
threads.at(i)->quit();
DEBUG_LOG(("Waiting for clipThread to finish: %1").arg(i));
threads.at(i)->wait();
delete managers.at(i);
delete threads.at(i);
}
threads.clear();
managers.clear();
}
} }
Reader *const ReaderPointer::BadPointer = reinterpret_cast<Reader*>(1); Reader *const ReaderPointer::BadPointer = reinterpret_cast<Reader*>(1);

View file

@ -30,13 +30,12 @@ struct FrameRequest {
bool valid() const { bool valid() const {
return factor > 0; return factor > 0;
} }
QSize frame;
QSize outer;
int factor = 0; int factor = 0;
int framew = 0;
int frameh = 0;
int outerw = 0;
int outerh = 0;
ImageRoundRadius radius = ImageRoundRadius::None; ImageRoundRadius radius = ImageRoundRadius::None;
RectParts corners = RectPart::AllCorners; RectParts corners = RectPart::AllCorners;
bool keepAlpha = false;
}; };
// Before ReaderPrivate read the first image and got the original frame size. // Before ReaderPrivate read the first image and got the original frame size.
@ -54,6 +53,7 @@ enum class Notification {
Repaint, Repaint,
}; };
class Manager;
class ReaderPrivate; class ReaderPrivate;
class Reader { class Reader {
public: public:
@ -73,40 +73,40 @@ public:
int threadIndex, int threadIndex,
Notification notification); Notification notification);
void start(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius, RectParts corners); void start(FrameRequest request);
QPixmap current(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius, RectParts corners, crl::time ms); [[nodiscard]] QPixmap current(FrameRequest request, crl::time now);
QPixmap frameOriginal() const { [[nodiscard]] QPixmap frameOriginal() const {
if (auto frame = frameToShow()) { if (const auto frame = frameToShow()) {
auto result = QPixmap::fromImage(frame->original); auto result = QPixmap::fromImage(frame->original);
result.detach(); result.detach();
return result; return result;
} }
return QPixmap(); return QPixmap();
} }
bool currentDisplayed() const { [[nodiscard]] bool currentDisplayed() const {
auto frame = frameToShow(); const auto frame = frameToShow();
return frame ? (frame->displayed.loadAcquire() != 0) : true; return !frame || (frame->displayed.loadAcquire() != 0);
} }
bool autoPausedGif() const { [[nodiscard]] bool autoPausedGif() const {
return _autoPausedGif.loadAcquire(); return _autoPausedGif.loadAcquire();
} }
bool videoPaused() const; [[nodiscard]] bool videoPaused() const;
int threadIndex() const { [[nodiscard]] int threadIndex() const {
return _threadIndex; return _threadIndex;
} }
int width() const; [[nodiscard]] int width() const;
int height() const; [[nodiscard]] int height() const;
State state() const; [[nodiscard]] State state() const;
bool started() const { [[nodiscard]] bool started() const {
auto step = _step.loadAcquire(); const auto step = _step.loadAcquire();
return (step == kWaitingForFirstFrameStep) || (step >= 0); return (step == kWaitingForFirstFrameStep) || (step >= 0);
} }
bool ready() const; [[nodiscard]] bool ready() const;
crl::time getPositionMs() const; [[nodiscard]] crl::time getPositionMs() const;
crl::time getDurationMs() const; [[nodiscard]] crl::time getDurationMs() const;
void pauseResumeVideo(); void pauseResumeVideo();
void stop(); void stop();
@ -217,62 +217,6 @@ inline ReaderPointer MakeReader(Args&&... args) {
return ReaderPointer(new Reader(std::forward<Args>(args)...)); return ReaderPointer(new Reader(std::forward<Args>(args)...));
} }
enum class ProcessResult {
Error,
Started,
Finished,
Paused,
Repaint,
CopyFrame,
Wait,
};
class Manager : public QObject {
public:
explicit Manager(QThread *thread);
~Manager();
int loadLevel() const {
return _loadLevel;
}
void append(Reader *reader, const Core::FileLocation &location, const QByteArray &data);
void start(Reader *reader);
void update(Reader *reader);
void stop(Reader *reader);
bool carries(Reader *reader) const;
private:
void process();
void finish();
void callback(Reader *reader, Notification notification);
void clear();
QAtomicInt _loadLevel;
using ReaderPointers = QMap<Reader*, QAtomicInt>;
ReaderPointers _readerPointers;
mutable QMutex _readerPointersMutex;
ReaderPointers::const_iterator constUnsafeFindReaderPointer(ReaderPrivate *reader) const;
ReaderPointers::iterator unsafeFindReaderPointer(ReaderPrivate *reader);
bool handleProcessResult(ReaderPrivate *reader, ProcessResult result, crl::time ms);
enum ResultHandleState {
ResultHandleRemove,
ResultHandleStop,
ResultHandleContinue,
};
ResultHandleState handleResult(ReaderPrivate *reader, ProcessResult result, crl::time ms);
using Readers = QMap<ReaderPrivate*, crl::time>;
Readers _readers;
QTimer _timer;
QThread *_processingInThread = nullptr;
bool _needReProcess = false;
};
[[nodiscard]] Ui::PreparedFileInformation::Video PrepareForSending( [[nodiscard]] Ui::PreparedFileInformation::Video PrepareForSending(
const QString &fname, const QString &fname,
const QByteArray &data); const QByteArray &data);

View file

@ -1910,12 +1910,10 @@ void Gif::clipCallback(Media::Clip::Notification notification) {
} else { } else {
auto height = st::inlineMediaHeight; auto height = st::inlineMediaHeight;
auto frame = countFrameSize(); auto frame = countFrameSize();
_gif->start( _gif->start({
frame.width(), .frame = countFrameSize(),
frame.height(), .outer = { _width, st::inlineMediaHeight },
_width, });
height,
ImageRoundRadius::None, RectPart::None);
} }
} else if (_gif->autoPausedGif() } else if (_gif->autoPausedGif()
&& !delegate()->itemVisible(this)) { && !delegate()->itemVisible(this)) {
@ -1997,26 +1995,20 @@ void Gif::paint(
} }
const auto radial = isRadialAnimation(); const auto radial = isRadialAnimation();
int32 height = st::inlineMediaHeight; const auto frame = countFrameSize();
QSize frame = countFrameSize(); const auto r = QRect(0, 0, _width, st::inlineMediaHeight);
QRect r(0, 0, _width, height);
if (animating) { if (animating) {
const auto pixmap = _gif->current( const auto pixmap = _gif->current({
frame.width(), .frame = frame,
frame.height(), .outer = r.size(),
_width, }, /*context->paused ? 0 : */context->ms);
height,
ImageRoundRadius::None,
RectPart::None,
/*context->paused ? 0 : */context->ms);
if (_thumb.isNull()) { if (_thumb.isNull()) {
_thumb = pixmap; _thumb = pixmap;
_thumbGood = true; _thumbGood = true;
} }
p.drawPixmap(r.topLeft(), pixmap); p.drawPixmap(r.topLeft(), pixmap);
} else { } else {
prepareThumbnail({ _width, height }, frame); prepareThumbnail(r.size(), frame);
if (_thumb.isNull()) { if (_thumb.isNull()) {
p.fillRect(r, st::overviewPhotoBg); p.fillRect(r, st::overviewPhotoBg);
} else { } else {
@ -2044,7 +2036,11 @@ void Gif::paint(
return &st::historyFileInDownload; return &st::historyFileInDownload;
}(); }();
const auto size = st::overviewVideoRadialSize; const auto size = st::overviewVideoRadialSize;
QRect inner((_width - size) / 2, (height - size) / 2, size, size); QRect inner(
(r.width() - size) / 2,
(r.height() - size) / 2,
size,
size);
icon->paintInCenter(p, inner); icon->paintInCenter(p, inner);
if (radial) { if (radial) {
p.setOpacity(1); p.setOpacity(1);

View file

@ -74,16 +74,10 @@ bool SingleMediaPreview::drawBackground() const {
bool SingleMediaPreview::tryPaintAnimation(Painter &p) { bool SingleMediaPreview::tryPaintAnimation(Painter &p) {
if (_gifPreview && _gifPreview->started()) { if (_gifPreview && _gifPreview->started()) {
auto s = QSize(previewWidth(), previewHeight()); const auto paused = _gifPaused();
auto paused = _gifPaused(); const auto frame = _gifPreview->current({
auto frame = _gifPreview->current( .frame = QSize(previewWidth(), previewHeight()),
s.width(), }, paused ? 0 : crl::now());
s.height(),
s.width(),
s.height(),
ImageRoundRadius::None,
RectPart::None,
paused ? 0 : crl::now());
p.drawPixmap(previewLeft(), previewTop(), frame); p.drawPixmap(previewLeft(), previewTop(), frame);
return true; return true;
} else if (_lottiePreview && _lottiePreview->ready()) { } else if (_lottiePreview && _lottiePreview->ready()) {
@ -139,14 +133,9 @@ void SingleMediaPreview::clipCallback(
} }
if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) { if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) {
const auto s = QSize(previewWidth(), previewHeight()); _gifPreview->start({
_gifPreview->start( .frame = QSize(previewWidth(), previewHeight()),
s.width(), });
s.height(),
s.width(),
s.height(),
ImageRoundRadius::None,
RectPart::None);
} }
update(); update();

View file

@ -285,9 +285,11 @@ QPixmap MediaPreviewWidget::currentImage() const {
? _gif ? _gif
: _gifThumbnail; : _gifThumbnail;
if (gif && gif->started()) { if (gif && gif->started()) {
auto s = currentDimensions(); const auto paused = _controller->isGifPausedAtLeastFor(
auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::MediaPreview); Window::GifPauseReason::MediaPreview);
return gif->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : crl::now()); return gif->current(
{ .frame = currentDimensions() },
paused ? 0 : crl::now());
} }
if (_cacheStatus != CacheThumbLoaded if (_cacheStatus != CacheThumbLoaded
&& _document->hasThumbnail()) { && _document->hasThumbnail()) {
@ -335,14 +337,7 @@ QPixmap MediaPreviewWidget::currentImage() const {
void MediaPreviewWidget::startGifAnimation( void MediaPreviewWidget::startGifAnimation(
const Media::Clip::ReaderPointer &gif) { const Media::Clip::ReaderPointer &gif) {
const auto s = currentDimensions(); gif->start({ .frame = currentDimensions() });
gif->start(
s.width(),
s.height(),
s.width(),
s.height(),
ImageRoundRadius::None,
RectPart::None);
} }
void MediaPreviewWidget::validateGifAnimation() { void MediaPreviewWidget::validateGifAnimation() {