mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-17 22:57:11 +02:00
Render webm stickers in StickersListWidget.
This commit is contained in:
parent
20dbf18106
commit
8e749173de
12 changed files with 483 additions and 319 deletions
|
@ -46,6 +46,8 @@ namespace {
|
|||
constexpr auto kSearchRequestDelay = 400;
|
||||
constexpr auto kInlineItemsMaxPerRow = 5;
|
||||
constexpr auto kSearchBotUsername = "gif"_cs;
|
||||
constexpr auto kMinRepaintDelay = crl::time(16);
|
||||
constexpr auto kMinAfterScrollDelay = crl::time(33);
|
||||
|
||||
} // namespace
|
||||
|
||||
|
@ -173,7 +175,9 @@ GifsListWidget::GifsListWidget(
|
|||
, _mosaic(st::emojiPanWidth - st::inlineResultsLeft)
|
||||
, _previewTimer([=] { showPreview(); }) {
|
||||
setMouseTracking(true);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
// Otherwise our optimization on repainting is too aggressive.
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||
|
||||
_inlineRequestTimer.setSingleShot(true);
|
||||
connect(
|
||||
|
@ -239,7 +243,7 @@ void GifsListWidget::visibleTopBottomUpdated(
|
|||
auto top = getVisibleTop();
|
||||
Inner::visibleTopBottomUpdated(visibleTop, visibleBottom);
|
||||
if (top != getVisibleTop()) {
|
||||
_lastScrolled = crl::now();
|
||||
_lastScrolledAt = crl::now();
|
||||
}
|
||||
checkLoadMore();
|
||||
}
|
||||
|
@ -498,7 +502,7 @@ void GifsListWidget::clearSelection() {
|
|||
setCursor(style::cur_default);
|
||||
}
|
||||
_selected = _pressed = -1;
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
TabbedSelector::InnerFooter *GifsListWidget::getFooter() const {
|
||||
|
@ -544,7 +548,7 @@ void GifsListWidget::refreshSavedGifs() {
|
|||
deleteUnusedGifLayouts();
|
||||
|
||||
resizeToWidth(width());
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
if (isVisible()) {
|
||||
|
@ -672,7 +676,7 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result
|
|||
}
|
||||
|
||||
resizeToWidth(width());
|
||||
update();
|
||||
repaintItems();
|
||||
|
||||
_lastMousePos = QCursor::pos();
|
||||
updateSelected();
|
||||
|
@ -711,16 +715,13 @@ void GifsListWidget::inlineItemLayoutChanged(const InlineBots::Layout::ItemBase
|
|||
}
|
||||
}
|
||||
|
||||
void GifsListWidget::inlineItemRepaint(const InlineBots::Layout::ItemBase *layout) {
|
||||
auto ms = crl::now();
|
||||
if (_lastScrolled + 100 <= ms) {
|
||||
update();
|
||||
} else {
|
||||
_updateInlineItems.callOnce(_lastScrolled + 100 - ms);
|
||||
}
|
||||
void GifsListWidget::inlineItemRepaint(
|
||||
const InlineBots::Layout::ItemBase *layout) {
|
||||
updateInlineItems();
|
||||
}
|
||||
|
||||
bool GifsListWidget::inlineItemVisible(const InlineBots::Layout::ItemBase *layout) {
|
||||
bool GifsListWidget::inlineItemVisible(
|
||||
const InlineBots::Layout::ItemBase *layout) {
|
||||
auto position = layout->position();
|
||||
if (position < 0 || !isVisible()) {
|
||||
return false;
|
||||
|
@ -930,12 +931,22 @@ void GifsListWidget::showPreview() {
|
|||
}
|
||||
|
||||
void GifsListWidget::updateInlineItems() {
|
||||
auto ms = crl::now();
|
||||
if (_lastScrolled + 100 <= ms) {
|
||||
update();
|
||||
} else {
|
||||
_updateInlineItems.callOnce(_lastScrolled + 100 - ms);
|
||||
const auto now = crl::now();
|
||||
|
||||
const auto delay = std::max(
|
||||
_lastScrolledAt + kMinAfterScrollDelay - now,
|
||||
_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
|
||||
|
|
|
@ -133,12 +133,14 @@ private:
|
|||
void paintInlineItems(Painter &p, QRect clip);
|
||||
|
||||
void updateInlineItems();
|
||||
void repaintItems(crl::time now = 0);
|
||||
void showPreview();
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
Section _section = Section::Gifs;
|
||||
crl::time _lastScrolled = 0;
|
||||
crl::time _lastScrolledAt = 0;
|
||||
crl::time _lastUpdatedAt = 0;
|
||||
base::Timer _updateInlineItems;
|
||||
bool _inlineWithThumb = false;
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ struct StickerIcon {
|
|||
|
||||
};
|
||||
|
||||
class StickersListWidget::Footer : public TabbedSelector::InnerFooter {
|
||||
class StickersListWidget::Footer final : public TabbedSelector::InnerFooter {
|
||||
public:
|
||||
explicit Footer(
|
||||
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(
|
||||
const QVector<DocumentData*> &pack)
|
||||
-> std::vector<Sticker> {
|
||||
|
@ -284,6 +294,7 @@ void StickersListWidget::Footer::clearHeavyData() {
|
|||
count);
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
auto &icon = _icons[i];
|
||||
icon.webm = nullptr;
|
||||
icon.lottie = nullptr;
|
||||
icon.lifetime.destroy();
|
||||
icon.stickerMedia = nullptr;
|
||||
|
@ -737,6 +748,7 @@ void StickersListWidget::Footer::refreshIcons(
|
|||
if (const auto i = indices.find(now.setId); i != end(indices)) {
|
||||
auto &was = _icons[i->second];
|
||||
if (now.sticker == was.sticker) {
|
||||
now.webm = std::move(was.webm);
|
||||
now.lottie = std::move(was.lottie);
|
||||
now.lifetime = std::move(was.lifetime);
|
||||
now.savedFrame = std::move(was.savedFrame);
|
||||
|
@ -979,7 +991,7 @@ StickersListWidget::StickersListWidget(
|
|||
session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (isVisible()) {
|
||||
update();
|
||||
repaintItems();
|
||||
readVisibleFeatured(getVisibleTop(), getVisibleBottom());
|
||||
}
|
||||
}, lifetime());
|
||||
|
@ -1135,7 +1147,7 @@ void StickersListWidget::preloadMoreOfficial() {
|
|||
}
|
||||
});
|
||||
resizeToWidth(width());
|
||||
update();
|
||||
repaintItems();
|
||||
}).send();
|
||||
}
|
||||
|
||||
|
@ -1499,8 +1511,8 @@ void StickersListWidget::takeHeavyData(Set &to, Set &from) {
|
|||
}
|
||||
}
|
||||
for (const auto &sticker : fromList) {
|
||||
if (sticker.animated) {
|
||||
to.lottiePlayer->remove(sticker.animated);
|
||||
if (sticker.lottie) {
|
||||
to.lottiePlayer->remove(sticker.lottie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1509,7 +1521,8 @@ void StickersListWidget::takeHeavyData(Set &to, Set &from) {
|
|||
void StickersListWidget::takeHeavyData(Sticker &to, Sticker &from) {
|
||||
to.documentMedia = std::move(from.documentMedia);
|
||||
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> & {
|
||||
|
@ -1629,6 +1642,9 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
|||
? &_pressed
|
||||
: &_selected);
|
||||
|
||||
const auto now = crl::now();
|
||||
const auto paused = controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::SavedGifs);
|
||||
if (sets.empty() && _section == Section::Search) {
|
||||
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 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;
|
||||
}
|
||||
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 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;
|
||||
});
|
||||
}
|
||||
|
||||
void StickersListWidget::markLottieFrameShown(Set &set) {
|
||||
if (const auto player = set.lottiePlayer.get()) {
|
||||
const auto paused = controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::SavedGifs);
|
||||
if (!paused) {
|
||||
player->markFrameShown();
|
||||
}
|
||||
player->markFrameShown();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1801,7 +1817,8 @@ void StickersListWidget::clearHeavyIn(Set &set, bool clearSavedFrames) {
|
|||
if (clearSavedFrames) {
|
||||
sticker.savedFrame = QPixmap();
|
||||
}
|
||||
sticker.animated = nullptr;
|
||||
sticker.webm = nullptr;
|
||||
sticker.lottie = nullptr;
|
||||
sticker.documentMedia = nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -1821,7 +1838,7 @@ void StickersListWidget::pauseInvisibleLottieIn(const SectionInfo &info) {
|
|||
if (index >= info.count) {
|
||||
break;
|
||||
}
|
||||
if (const auto animated = set.stickers[index].animated) {
|
||||
if (const auto animated = set.stickers[index].lottie) {
|
||||
player->pause(animated);
|
||||
}
|
||||
}
|
||||
|
@ -1903,16 +1920,13 @@ void StickersListWidget::ensureLottiePlayer(Set &set) {
|
|||
|
||||
raw->updates(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto &sets = shownSets();
|
||||
enumerateSections([&](const SectionInfo &info) {
|
||||
if (shownSets()[info.section].lottiePlayer.get() == raw) {
|
||||
update(
|
||||
0,
|
||||
info.rowsTop,
|
||||
width(),
|
||||
info.rowsBottom - info.rowsTop);
|
||||
return false;
|
||||
if (sets[info.section].lottiePlayer.get() != raw) {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
repaintItems(info);
|
||||
return false;
|
||||
});
|
||||
}, 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.
|
||||
Assert(sticker.documentMedia != nullptr);
|
||||
sticker.animated = LottieAnimationFromDocument(
|
||||
sticker.lottie = LottieAnimationFromDocument(
|
||||
set.lottiePlayer.get(),
|
||||
sticker.documentMedia.get(),
|
||||
StickerLottieSize::StickersPanel,
|
||||
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 {
|
||||
return QSize(
|
||||
_singleSize.width() - 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];
|
||||
sticker.ensureMediaCreated();
|
||||
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 isWebm = document->sticker()->isWebm();
|
||||
if (isLottie
|
||||
&& !sticker.animated
|
||||
&& !sticker.lottie
|
||||
&& media->loaded()) {
|
||||
setupLottie(set, section, index);
|
||||
} else if (isWebm && !sticker.webm && media->loaded()) {
|
||||
setupWebm(set, section, index);
|
||||
}
|
||||
|
||||
int row = (index / _columnCount), col = (index % _columnCount);
|
||||
|
@ -1963,25 +2073,15 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
|
|||
|
||||
media->checkStickerSmall();
|
||||
|
||||
auto w = 1;
|
||||
auto h = 1;
|
||||
if (isLottie && !document->dimensions.isEmpty()) {
|
||||
const auto request = Lottie::FrameRequest{ boundingBoxSize() * cIntRetinaFactor() };
|
||||
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);
|
||||
const auto size = ComputeStickerSize(document, boundingBoxSize());
|
||||
const auto ppos = pos + QPoint(
|
||||
(_singleSize.width() - size.width()) / 2,
|
||||
(_singleSize.height() - size.height()) / 2);
|
||||
|
||||
if (sticker.animated && sticker.animated->ready()) {
|
||||
if (sticker.lottie && sticker.lottie->ready()) {
|
||||
auto request = Lottie::FrameRequest();
|
||||
request.box = boundingBoxSize() * cIntRetinaFactor();
|
||||
const auto frame = sticker.animated->frame(request);
|
||||
const auto frame = sticker.lottie->frame(request);
|
||||
p.drawImage(
|
||||
QRect(ppos, frame.size() / cIntRetinaFactor()),
|
||||
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.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 {
|
||||
const auto image = media->getStickerSmall();
|
||||
const auto pixmap = !sticker.savedFrame.isNull()
|
||||
? sticker.savedFrame
|
||||
: image
|
||||
? image->pixSingle(w, h, { .outer = { w, h } })
|
||||
? image->pixSingle(size, { .outer = size })
|
||||
: QPixmap();
|
||||
if (!pixmap.isNull()) {
|
||||
p.drawPixmapLeft(ppos, width(), pixmap);
|
||||
|
@ -2006,7 +2113,7 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
|
|||
PaintStickerThumbnailPath(
|
||||
p,
|
||||
media.get(),
|
||||
QRect(ppos, QSize{ w, h }),
|
||||
QRect(ppos, size),
|
||||
_pathGradient.get());
|
||||
}
|
||||
}
|
||||
|
@ -2227,7 +2334,7 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
|||
auto pressed = _pressed;
|
||||
setPressed(v::null);
|
||||
if (pressed != _selected) {
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
auto activated = ClickHandler::unpressed();
|
||||
|
@ -2329,7 +2436,7 @@ void StickersListWidget::removeRecentSticker(int section, int index) {
|
|||
if (refresh) {
|
||||
refreshRecentStickers();
|
||||
updateSelected();
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2389,7 +2496,7 @@ void StickersListWidget::enterFromChildEvent(QEvent *e, QWidget *child) {
|
|||
void StickersListWidget::clearSelection() {
|
||||
setPressed(v::null);
|
||||
setSelected(v::null);
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
TabbedSelector::InnerFooter *StickersListWidget::getFooter() const {
|
||||
|
@ -2449,7 +2556,7 @@ void StickersListWidget::refreshStickers() {
|
|||
|
||||
_lastMousePosition = QCursor::pos();
|
||||
updateSelected();
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
void StickersListWidget::refreshMySets() {
|
||||
|
@ -3031,7 +3138,7 @@ void StickersListWidget::showStickerSet(uint64 setId) {
|
|||
if (_footer) {
|
||||
_footer->refreshIcons(ValidateIconAnimations::Scroll);
|
||||
}
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
scrollTo(0);
|
||||
|
@ -3063,7 +3170,7 @@ void StickersListWidget::showStickerSet(uint64 setId) {
|
|||
|
||||
_lastMousePosition = QCursor::pos();
|
||||
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
void StickersListWidget::refreshMegagroupSetGeometry() {
|
||||
|
|
|
@ -39,6 +39,11 @@ class DocumentMedia;
|
|||
class StickersSet;
|
||||
} // namespace Data
|
||||
|
||||
namespace Media::Clip {
|
||||
class ReaderPointer;
|
||||
enum class Notification;
|
||||
} // namespace Media::Clip
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
struct StickerIcon;
|
||||
|
@ -113,6 +118,7 @@ protected:
|
|||
|
||||
private:
|
||||
class Footer;
|
||||
struct Sticker;
|
||||
|
||||
enum class Section {
|
||||
Featured,
|
||||
|
@ -178,15 +184,6 @@ private:
|
|||
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 {
|
||||
Set(
|
||||
uint64 id,
|
||||
|
@ -279,11 +276,27 @@ private:
|
|||
|
||||
void paintStickers(Painter &p, QRect clip);
|
||||
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 ensureLottiePlayer(Set &set);
|
||||
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 checkVisibleLottie();
|
||||
void pauseInvisibleLottieIn(const SectionInfo &info);
|
||||
|
@ -292,6 +305,8 @@ private:
|
|||
void takeHeavyData(Sticker &to, Sticker &from);
|
||||
void clearHeavyIn(Set &set, bool clearSavedFrames = true);
|
||||
void clearHeavyData();
|
||||
void repaintItems();
|
||||
void repaintItems(const SectionInfo &info);
|
||||
|
||||
int stickersRight() const;
|
||||
bool featuredHasAddButton(int index) const;
|
||||
|
@ -302,8 +317,8 @@ private:
|
|||
void refreshMegagroupSetGeometry();
|
||||
QRect megagroupSetButtonRectFinal() const;
|
||||
|
||||
const Data::StickersSetsOrder &defaultSetsOrder() const;
|
||||
Data::StickersSetsOrder &defaultSetsOrderRef();
|
||||
[[nodiscard]] const Data::StickersSetsOrder &defaultSetsOrder() const;
|
||||
[[nodiscard]] Data::StickersSetsOrder &defaultSetsOrderRef();
|
||||
|
||||
enum class AppendSkip {
|
||||
None,
|
||||
|
@ -316,7 +331,6 @@ private:
|
|||
bool externalLayout,
|
||||
AppendSkip skip = AppendSkip::None);
|
||||
|
||||
void selectEmoji(EmojiPtr emoji);
|
||||
int stickersLeft() const;
|
||||
QRect stickerRect(int section, int sel);
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.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 "ui/effects/path_shift_gradient.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
|
||||
|
|
|
@ -113,4 +113,8 @@ bool PaintStickerThumbnailPath(
|
|||
QRect target,
|
||||
not_null<Ui::PathShiftGradient*> gradient);
|
||||
|
||||
[[nodiscard]] QSize ComputeStickerSize(
|
||||
not_null<DocumentData*> document,
|
||||
QSize box);
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
|
|
@ -168,19 +168,20 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
|
|||
}
|
||||
const auto radial = isRadialAnimation();
|
||||
|
||||
int32 height = st::inlineMediaHeight;
|
||||
QSize frame = countFrameSize();
|
||||
|
||||
QRect r(0, 0, _width, height);
|
||||
const auto frame = countFrameSize();
|
||||
const auto r = QRect(0, 0, _width, st::inlineMediaHeight);
|
||||
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()) {
|
||||
_thumb = pixmap;
|
||||
_thumbGood = true;
|
||||
}
|
||||
p.drawPixmap(r.topLeft(), pixmap);
|
||||
} else {
|
||||
prepareThumbnail({ _width, height }, frame);
|
||||
prepareThumbnail(r.size(), frame);
|
||||
if (_thumb.isNull()) {
|
||||
p.fillRect(r, st::overviewPhotoBg);
|
||||
} else {
|
||||
|
@ -211,7 +212,11 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
|
|||
return &st::historyFileInDownload;
|
||||
}();
|
||||
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);
|
||||
if (radial) {
|
||||
p.setOpacity(1);
|
||||
|
@ -404,9 +409,10 @@ void Gif::clipCallback(Media::Clip::Notification notification) {
|
|||
_gif->height());
|
||||
_gif.reset();
|
||||
} else {
|
||||
auto height = st::inlineMediaHeight;
|
||||
auto frame = countFrameSize();
|
||||
_gif->start(frame.width(), frame.height(), _width, height, ImageRoundRadius::None, RectPart::None);
|
||||
_gif->start({
|
||||
.frame = countFrameSize(),
|
||||
.outer = { _width, st::inlineMediaHeight },
|
||||
});
|
||||
}
|
||||
} else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) {
|
||||
unloadHeavyPart();
|
||||
|
@ -1424,7 +1430,10 @@ void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) con
|
|||
radial = isRadialAnimation();
|
||||
|
||||
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()) {
|
||||
_thumb = pixmap;
|
||||
_thumbGood = true;
|
||||
|
@ -1594,13 +1603,10 @@ void Game::clipCallback(Media::Clip::Notification notification) {
|
|||
_gif->height());
|
||||
_gif.reset();
|
||||
} else {
|
||||
_gif->start(
|
||||
_frameSize.width(),
|
||||
_frameSize.height(),
|
||||
st::inlineThumbSize,
|
||||
st::inlineThumbSize,
|
||||
ImageRoundRadius::None,
|
||||
RectPart::None);
|
||||
_gif->start({
|
||||
.frame = _frameSize,
|
||||
.outer = { st::inlineThumbSize, st::inlineThumbSize },
|
||||
});
|
||||
}
|
||||
} else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) {
|
||||
unloadHeavyPart();
|
||||
|
|
|
@ -35,44 +35,49 @@ constexpr auto kClipThreadsCount = 8;
|
|||
constexpr auto kAverageGifSize = 320 * 240;
|
||||
constexpr auto kWaitBeforeGifPause = crl::time(200);
|
||||
|
||||
QVector<QThread*> threads;
|
||||
QVector<Manager*> managers;
|
||||
|
||||
QImage PrepareFrameImage(const FrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) {
|
||||
auto needResize = (original.width() != request.framew) || (original.height() != request.frameh);
|
||||
auto needOuterFill = (request.outerw != request.framew) || (request.outerh != request.frameh);
|
||||
auto needRounding = (request.radius != ImageRoundRadius::None);
|
||||
const auto needResize = (original.size() != request.frame);
|
||||
const auto needOuterFill = request.outer.isValid() && (request.outer != request.frame);
|
||||
const auto needRounding = (request.radius != ImageRoundRadius::None);
|
||||
if (!needResize && !needOuterFill && !hasAlpha && !needRounding) {
|
||||
return original;
|
||||
}
|
||||
|
||||
auto factor = request.factor;
|
||||
auto needNewCache = (cache.width() != request.outerw || cache.height() != request.outerh);
|
||||
const auto factor = request.factor;
|
||||
const auto size = request.outer.isValid() ? request.outer : request.frame;
|
||||
const auto needNewCache = (cache.size() != size);
|
||||
if (needNewCache) {
|
||||
cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied);
|
||||
cache = QImage(size, QImage::Format_ARGB32_Premultiplied);
|
||||
cache.setDevicePixelRatio(factor);
|
||||
}
|
||||
if (hasAlpha && request.keepAlpha) {
|
||||
cache.fill(Qt::transparent);
|
||||
}
|
||||
{
|
||||
Painter p(&cache);
|
||||
if (needNewCache) {
|
||||
if (request.framew < request.outerw) {
|
||||
p.fillRect(0, 0, (request.outerw - request.framew) / (2 * factor), cache.height() / factor, st::imageBg);
|
||||
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 framew = request.frame.width();
|
||||
const auto outerw = size.width();
|
||||
const auto frameh = request.frame.height();
|
||||
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) {
|
||||
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, (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);
|
||||
if (frameh < outerh) {
|
||||
p.fillRect(qMax(0, (outerw - framew) / (2 * factor)), 0, qMin(cache.width(), framew) / factor, (outerh - frameh) / (2 * 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) {
|
||||
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);
|
||||
if (hasAlpha && !request.keepAlpha) {
|
||||
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) {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
auto dst = QRect(position, QSize(request.framew / factor, request.frameh / factor));
|
||||
auto src = QRect(0, 0, original.width(), original.height());
|
||||
const auto dst = QRect(position, QSize(framew / factor, frameh / factor));
|
||||
const auto src = QRect(0, 0, original.width(), original.height());
|
||||
p.drawImage(dst, original, src, Qt::ColorOnly);
|
||||
} else {
|
||||
p.drawImage(position, original);
|
||||
|
@ -93,6 +98,81 @@ QPixmap PrepareFrame(const FrameRequest &request, const QImage &original, bool h
|
|||
|
||||
} // 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(
|
||||
const Core::FileLocation &location,
|
||||
const QByteArray &data,
|
||||
|
@ -112,23 +192,21 @@ Reader::Reader(const QByteArray &data, Callback &&callback)
|
|||
}
|
||||
|
||||
void Reader::init(const Core::FileLocation &location, const QByteArray &data) {
|
||||
if (threads.size() < kClipThreadsCount) {
|
||||
_threadIndex = threads.size();
|
||||
threads.push_back(new QThread());
|
||||
managers.push_back(new Manager(threads.back()));
|
||||
threads.back()->start();
|
||||
if (Workers.size() < kClipThreadsCount) {
|
||||
_threadIndex = Workers.size();
|
||||
Workers.push_back(std::make_unique<Worker>());
|
||||
} else {
|
||||
_threadIndex = int32(base::RandomValue<uint32>() % threads.size());
|
||||
int32 loadLevel = 0x7FFFFFFF;
|
||||
for (int32 i = 0, l = threads.size(); i < l; ++i) {
|
||||
int32 level = managers.at(i)->loadLevel();
|
||||
_threadIndex = base::RandomIndex(Workers.size());
|
||||
auto loadLevel = 0x7FFFFFFF;
|
||||
for (int i = 0, l = int(Workers.size()); i < l; ++i) {
|
||||
const auto level = Workers[i]->manager.loadLevel();
|
||||
if (level < loadLevel) {
|
||||
_threadIndex = i;
|
||||
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
|
||||
|
@ -207,65 +285,74 @@ void Reader::SafeCallback(
|
|||
int threadIndex,
|
||||
Notification notification) {
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
void Reader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, ImageRoundRadius radius, RectParts corners) {
|
||||
if (managers.size() <= _threadIndex) error();
|
||||
if (_state == State::Error) return;
|
||||
|
||||
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);
|
||||
void Reader::start(FrameRequest request) {
|
||||
if (Workers.size() <= _threadIndex) {
|
||||
error();
|
||||
}
|
||||
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) {
|
||||
Expects(outerw > 0);
|
||||
Expects(outerh > 0);
|
||||
QPixmap Reader::current(FrameRequest request, crl::time now) {
|
||||
Expects(!(request.outer.isValid()
|
||||
? request.outer
|
||||
: request.frame).isEmpty());
|
||||
|
||||
auto frame = frameToShow();
|
||||
const auto frame = frameToShow();
|
||||
Assert(frame != nullptr);
|
||||
|
||||
auto shouldBePaused = !ms;
|
||||
const auto shouldBePaused = !now;
|
||||
if (!shouldBePaused) {
|
||||
frame->displayed.storeRelease(1);
|
||||
if (_autoPausedGif.loadAcquire()) {
|
||||
_autoPausedGif.storeRelease(0);
|
||||
if (managers.size() <= _threadIndex) error();
|
||||
if (_state != State::Error) {
|
||||
managers.at(_threadIndex)->update(this);
|
||||
if (Workers.size() <= _threadIndex) {
|
||||
error();
|
||||
} else if (_state != State::Error) {
|
||||
Workers[_threadIndex]->manager.update(this);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
frame->displayed.storeRelease(-1);
|
||||
}
|
||||
|
||||
auto factor = style::DevicePixelRatio();
|
||||
if (frame->pix.width() == outerw * factor
|
||||
&& frame->pix.height() == outerh * factor
|
||||
&& frame->request.radius == radius
|
||||
&& frame->request.corners == corners) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
request.factor = factor;
|
||||
request.frame *= factor;
|
||||
if (request.outer.isValid()) {
|
||||
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();
|
||||
return frame->pix;
|
||||
}
|
||||
|
||||
frame->request.framew = framew * factor;
|
||||
frame->request.frameh = frameh * factor;
|
||||
frame->request.outerw = outerw * factor;
|
||||
frame->request.outerh = outerh * factor;
|
||||
frame->request.frame = request.frame;
|
||||
frame->request.outer = request.outer;
|
||||
|
||||
QImage cacheForResize;
|
||||
frame->original.setDevicePixelRatio(factor);
|
||||
|
@ -277,18 +364,21 @@ QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh,
|
|||
|
||||
moveToNextShow();
|
||||
|
||||
if (managers.size() <= _threadIndex) error();
|
||||
if (_state != State::Error) {
|
||||
managers.at(_threadIndex)->update(this);
|
||||
if (Workers.size() <= _threadIndex) {
|
||||
error();
|
||||
} else if (_state != State::Error) {
|
||||
Workers[_threadIndex]->manager.update(this);
|
||||
}
|
||||
|
||||
return frame->pix;
|
||||
}
|
||||
|
||||
bool Reader::ready() const {
|
||||
if (_width && _height) return true;
|
||||
if (_width && _height) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto frame = frameToShow();
|
||||
const auto frame = frameToShow();
|
||||
if (frame) {
|
||||
_width = frame->original.width();
|
||||
_height = frame->original.height();
|
||||
|
@ -298,7 +388,7 @@ bool Reader::ready() const {
|
|||
}
|
||||
|
||||
crl::time Reader::getPositionMs() const {
|
||||
if (auto frame = frameToShow()) {
|
||||
if (const auto frame = frameToShow()) {
|
||||
return frame->positionMs;
|
||||
}
|
||||
return 0;
|
||||
|
@ -309,11 +399,13 @@ crl::time Reader::getDurationMs() const {
|
|||
}
|
||||
|
||||
void Reader::pauseResumeVideo() {
|
||||
if (managers.size() <= _threadIndex) error();
|
||||
if (Workers.size() <= _threadIndex) {
|
||||
error();
|
||||
}
|
||||
if (_state == State::Error) return;
|
||||
|
||||
_videoPauseRequest.storeRelease(1 - _videoPauseRequest.loadAcquire());
|
||||
managers.at(_threadIndex)->start(this);
|
||||
Workers[_threadIndex]->manager.start(this);
|
||||
}
|
||||
|
||||
bool Reader::videoPaused() const {
|
||||
|
@ -333,9 +425,11 @@ State Reader::state() const {
|
|||
}
|
||||
|
||||
void Reader::stop() {
|
||||
if (managers.size() <= _threadIndex) error();
|
||||
if (Workers.size() <= _threadIndex) {
|
||||
error();
|
||||
}
|
||||
if (_state != State::Error) {
|
||||
managers.at(_threadIndex)->stop(this);
|
||||
Workers[_threadIndex]->manager.stop(this);
|
||||
_width = _height = 0;
|
||||
}
|
||||
}
|
||||
|
@ -461,7 +555,7 @@ public:
|
|||
bool renderFrame() {
|
||||
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;
|
||||
}
|
||||
frame()->original.setDevicePixelRatio(_request.factor);
|
||||
|
@ -574,7 +668,7 @@ private:
|
|||
|
||||
};
|
||||
|
||||
Manager::Manager(QThread *thread) {
|
||||
Manager::Manager(not_null<QThread*> thread) {
|
||||
moveToThread(thread);
|
||||
connect(thread, &QThread::started, this, [=] { process(); });
|
||||
connect(thread, &QThread::finished, this, [=] { finish(); });
|
||||
|
@ -884,17 +978,7 @@ Ui::PreparedFileInformation::Video PrepareForSending(const QString &fname, const
|
|||
}
|
||||
|
||||
void Finish() {
|
||||
if (!threads.isEmpty()) {
|
||||
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();
|
||||
}
|
||||
Workers.clear();
|
||||
}
|
||||
|
||||
Reader *const ReaderPointer::BadPointer = reinterpret_cast<Reader*>(1);
|
||||
|
|
|
@ -30,13 +30,12 @@ struct FrameRequest {
|
|||
bool valid() const {
|
||||
return factor > 0;
|
||||
}
|
||||
QSize frame;
|
||||
QSize outer;
|
||||
int factor = 0;
|
||||
int framew = 0;
|
||||
int frameh = 0;
|
||||
int outerw = 0;
|
||||
int outerh = 0;
|
||||
ImageRoundRadius radius = ImageRoundRadius::None;
|
||||
RectParts corners = RectPart::AllCorners;
|
||||
bool keepAlpha = false;
|
||||
};
|
||||
|
||||
// Before ReaderPrivate read the first image and got the original frame size.
|
||||
|
@ -54,6 +53,7 @@ enum class Notification {
|
|||
Repaint,
|
||||
};
|
||||
|
||||
class Manager;
|
||||
class ReaderPrivate;
|
||||
class Reader {
|
||||
public:
|
||||
|
@ -73,40 +73,40 @@ public:
|
|||
int threadIndex,
|
||||
Notification notification);
|
||||
|
||||
void start(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius, RectParts corners);
|
||||
QPixmap current(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius, RectParts corners, crl::time ms);
|
||||
QPixmap frameOriginal() const {
|
||||
if (auto frame = frameToShow()) {
|
||||
void start(FrameRequest request);
|
||||
[[nodiscard]] QPixmap current(FrameRequest request, crl::time now);
|
||||
[[nodiscard]] QPixmap frameOriginal() const {
|
||||
if (const auto frame = frameToShow()) {
|
||||
auto result = QPixmap::fromImage(frame->original);
|
||||
result.detach();
|
||||
return result;
|
||||
}
|
||||
return QPixmap();
|
||||
}
|
||||
bool currentDisplayed() const {
|
||||
auto frame = frameToShow();
|
||||
return frame ? (frame->displayed.loadAcquire() != 0) : true;
|
||||
[[nodiscard]] bool currentDisplayed() const {
|
||||
const auto frame = frameToShow();
|
||||
return !frame || (frame->displayed.loadAcquire() != 0);
|
||||
}
|
||||
bool autoPausedGif() const {
|
||||
[[nodiscard]] bool autoPausedGif() const {
|
||||
return _autoPausedGif.loadAcquire();
|
||||
}
|
||||
bool videoPaused() const;
|
||||
int threadIndex() const {
|
||||
[[nodiscard]] bool videoPaused() const;
|
||||
[[nodiscard]] int threadIndex() const {
|
||||
return _threadIndex;
|
||||
}
|
||||
|
||||
int width() const;
|
||||
int height() const;
|
||||
[[nodiscard]] int width() const;
|
||||
[[nodiscard]] int height() const;
|
||||
|
||||
State state() const;
|
||||
bool started() const {
|
||||
auto step = _step.loadAcquire();
|
||||
[[nodiscard]] State state() const;
|
||||
[[nodiscard]] bool started() const {
|
||||
const auto step = _step.loadAcquire();
|
||||
return (step == kWaitingForFirstFrameStep) || (step >= 0);
|
||||
}
|
||||
bool ready() const;
|
||||
[[nodiscard]] bool ready() const;
|
||||
|
||||
crl::time getPositionMs() const;
|
||||
crl::time getDurationMs() const;
|
||||
[[nodiscard]] crl::time getPositionMs() const;
|
||||
[[nodiscard]] crl::time getDurationMs() const;
|
||||
void pauseResumeVideo();
|
||||
|
||||
void stop();
|
||||
|
@ -217,62 +217,6 @@ inline ReaderPointer MakeReader(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(
|
||||
const QString &fname,
|
||||
const QByteArray &data);
|
||||
|
|
|
@ -1910,12 +1910,10 @@ void Gif::clipCallback(Media::Clip::Notification notification) {
|
|||
} else {
|
||||
auto height = st::inlineMediaHeight;
|
||||
auto frame = countFrameSize();
|
||||
_gif->start(
|
||||
frame.width(),
|
||||
frame.height(),
|
||||
_width,
|
||||
height,
|
||||
ImageRoundRadius::None, RectPart::None);
|
||||
_gif->start({
|
||||
.frame = countFrameSize(),
|
||||
.outer = { _width, st::inlineMediaHeight },
|
||||
});
|
||||
}
|
||||
} else if (_gif->autoPausedGif()
|
||||
&& !delegate()->itemVisible(this)) {
|
||||
|
@ -1997,26 +1995,20 @@ void Gif::paint(
|
|||
}
|
||||
const auto radial = isRadialAnimation();
|
||||
|
||||
int32 height = st::inlineMediaHeight;
|
||||
QSize frame = countFrameSize();
|
||||
|
||||
QRect r(0, 0, _width, height);
|
||||
const auto frame = countFrameSize();
|
||||
const auto r = QRect(0, 0, _width, st::inlineMediaHeight);
|
||||
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()) {
|
||||
_thumb = pixmap;
|
||||
_thumbGood = true;
|
||||
}
|
||||
p.drawPixmap(r.topLeft(), pixmap);
|
||||
} else {
|
||||
prepareThumbnail({ _width, height }, frame);
|
||||
prepareThumbnail(r.size(), frame);
|
||||
if (_thumb.isNull()) {
|
||||
p.fillRect(r, st::overviewPhotoBg);
|
||||
} else {
|
||||
|
@ -2044,7 +2036,11 @@ void Gif::paint(
|
|||
return &st::historyFileInDownload;
|
||||
}();
|
||||
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);
|
||||
if (radial) {
|
||||
p.setOpacity(1);
|
||||
|
|
|
@ -74,16 +74,10 @@ bool SingleMediaPreview::drawBackground() const {
|
|||
|
||||
bool SingleMediaPreview::tryPaintAnimation(Painter &p) {
|
||||
if (_gifPreview && _gifPreview->started()) {
|
||||
auto s = QSize(previewWidth(), previewHeight());
|
||||
auto paused = _gifPaused();
|
||||
auto frame = _gifPreview->current(
|
||||
s.width(),
|
||||
s.height(),
|
||||
s.width(),
|
||||
s.height(),
|
||||
ImageRoundRadius::None,
|
||||
RectPart::None,
|
||||
paused ? 0 : crl::now());
|
||||
const auto paused = _gifPaused();
|
||||
const auto frame = _gifPreview->current({
|
||||
.frame = QSize(previewWidth(), previewHeight()),
|
||||
}, paused ? 0 : crl::now());
|
||||
p.drawPixmap(previewLeft(), previewTop(), frame);
|
||||
return true;
|
||||
} else if (_lottiePreview && _lottiePreview->ready()) {
|
||||
|
@ -139,14 +133,9 @@ void SingleMediaPreview::clipCallback(
|
|||
}
|
||||
|
||||
if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) {
|
||||
const auto s = QSize(previewWidth(), previewHeight());
|
||||
_gifPreview->start(
|
||||
s.width(),
|
||||
s.height(),
|
||||
s.width(),
|
||||
s.height(),
|
||||
ImageRoundRadius::None,
|
||||
RectPart::None);
|
||||
_gifPreview->start({
|
||||
.frame = QSize(previewWidth(), previewHeight()),
|
||||
});
|
||||
}
|
||||
|
||||
update();
|
||||
|
|
|
@ -285,9 +285,11 @@ QPixmap MediaPreviewWidget::currentImage() const {
|
|||
? _gif
|
||||
: _gifThumbnail;
|
||||
if (gif && gif->started()) {
|
||||
auto s = currentDimensions();
|
||||
auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::MediaPreview);
|
||||
return gif->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : crl::now());
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::MediaPreview);
|
||||
return gif->current(
|
||||
{ .frame = currentDimensions() },
|
||||
paused ? 0 : crl::now());
|
||||
}
|
||||
if (_cacheStatus != CacheThumbLoaded
|
||||
&& _document->hasThumbnail()) {
|
||||
|
@ -335,14 +337,7 @@ QPixmap MediaPreviewWidget::currentImage() const {
|
|||
|
||||
void MediaPreviewWidget::startGifAnimation(
|
||||
const Media::Clip::ReaderPointer &gif) {
|
||||
const auto s = currentDimensions();
|
||||
gif->start(
|
||||
s.width(),
|
||||
s.height(),
|
||||
s.width(),
|
||||
s.height(),
|
||||
ImageRoundRadius::None,
|
||||
RectPart::None);
|
||||
gif->start({ .frame = currentDimensions() });
|
||||
}
|
||||
|
||||
void MediaPreviewWidget::validateGifAnimation() {
|
||||
|
|
Loading…
Add table
Reference in a new issue