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 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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in a new issue