[img-editor] Moved animated overlay logic to separated module.

This commit is contained in:
23rd
2026-09-06 14:23:11 +03:00
committed by John Preston
parent 59be872700
commit adcea79c19
7 changed files with 171 additions and 105 deletions

View File

@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "editor/photo_editor_common.h"
#include "editor/scene/scene.h"
#include "editor/scene/scene_item_sticker.h"
#include "editor/scene/scene_item_animated.h"
#include "ui/painter.h"
#include "ui/userpic_view.h"
@@ -188,21 +188,19 @@ Media::Encode::Job ComposeAnimatedJob(
auto longest = crl::time(0);
for (const auto item : normal) {
const auto sticker = (item->type() == ItemSticker::Type)
? static_cast<ItemSticker*>(item)
: nullptr;
if (!sticker || !sticker->animated()) {
const auto animated = dynamic_cast<ItemAnimated*>(item);
if (!animated || !animated->animated()) {
run.push_back(item);
continue;
}
auto entity = sticker->animatedEntity(sceneToCanvas);
auto entity = animated->animatedEntity(sceneToCanvas);
if (entity.bytes.isEmpty()) {
run.push_back(item);
continue;
}
flushRun();
job.overlay.push_back(std::move(entity));
const auto duration = sticker->loopDuration();
const auto duration = animated->loopDuration();
longest = std::max(
longest,
duration ? duration : kAnimatedMaxDuration);

View File

@@ -7,10 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "editor/scene/scene.h"
#include "editor/scene/scene_item_animated.h"
#include "editor/scene/scene_item_canvas.h"
#include "editor/scene/scene_item_line.h"
#include "editor/scene/scene_item_shape.h"
#include "editor/scene/scene_item_sticker.h"
#include "editor/scene/scene_item_text.h"
#include "editor/scene/scene_text_editing.h"
#include "ui/image/image_prepare.h"
@@ -686,12 +686,13 @@ std::vector<ItemPtr> Scene::items(
bool Scene::hasAnimatedItems() const {
for (const auto &item : _items) {
if (item->isNormalStatus()
&& (item->type() == ItemSticker::Type)) {
const auto sticker = static_cast<ItemSticker*>(item.get());
if (sticker->animated() && !sticker->content().isEmpty()) {
return true;
}
const auto animated = item->isNormalStatus()
? dynamic_cast<ItemAnimated*>(item.get())
: nullptr;
if (animated
&& animated->animated()
&& !animated->content().isEmpty()) {
return true;
}
}
return false;
@@ -699,8 +700,8 @@ bool Scene::hasAnimatedItems() const {
void Scene::releaseAnimations() {
for (const auto &item : _items) {
if (item->type() == ItemSticker::Type) {
static_cast<ItemSticker*>(item.get())->releasePlayers();
if (const auto animated = dynamic_cast<ItemAnimated*>(item.get())) {
animated->releasePlayers();
}
}
}

View File

@@ -0,0 +1,91 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "editor/scene/scene_item_animated.h"
#include "ui/painter.h"
namespace Editor {
Media::Encode::AnimatedEntity ItemAnimated::animatedEntity(
const QTransform &sceneToCanvas) const {
const auto composed = QTransform().scale(flipped() ? -1. : 1., 1.)
* sceneTransform()
* sceneToCanvas;
const auto inner = contentRect();
const auto m11 = composed.m11();
const auto m12 = composed.m12();
const auto m21 = composed.m21();
const auto m22 = composed.m22();
const auto scale = std::hypot(m11, m12);
const auto mirrored = ((m11 * m22 - m12 * m21) < 0);
const auto rotation = mirrored
? (std::atan2(-m12, m22) * 180. / M_PI)
: (std::atan2(m12, m11) * 180. / M_PI);
const auto size = inner.size() * scale;
const auto center = composed.map(inner.center());
return {
.kind = entityKind(),
.bytes = content(),
.geometry = QRectF(
center - QPointF(size.width() / 2., size.height() / 2.),
size),
.rotation = rotation,
.flipped = mirrored,
};
}
void ItemAnimated::paintFrame(
QPainter *p,
const QImage &frame,
bool live,
bool mirror) {
if (frame.isNull()) {
return;
}
const auto rect = contentRect();
const auto ratio = style::DevicePixelRatio();
const auto fitted = QSizeF(frame.size())
.scaled(rect.size(), Qt::KeepAspectRatio);
const auto resultRect = QRectF(rect.topLeft(), fitted).translated(
(rect.width() - fitted.width()) / 2.,
(rect.height() - fitted.height()) / 2.);
if (live) {
p->save();
p->setRenderHint(QPainter::SmoothPixmapTransform);
if (mirror) {
p->translate(resultRect.center().x(), 0);
p->scale(-1., 1.);
p->translate(-resultRect.center().x(), 0);
}
p->drawImage(resultRect, frame);
p->restore();
return;
}
auto pixelSize = (fitted * ratio).toSize();
if (pixelSize.width() > frame.width()) {
pixelSize = frame.size();
}
if ((_preview.key != frame.cacheKey())
|| (_preview.size != pixelSize)
|| (_preview.flipped != mirror)) {
_preview.image = frame.scaled(
pixelSize,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
if (mirror) {
_preview.image = _preview.image.mirrored(true, false);
}
_preview.image.setDevicePixelRatio(ratio);
_preview.key = frame.cacheKey();
_preview.size = pixelSize;
_preview.flipped = mirror;
}
p->drawImage(resultRect, _preview.image);
}
} // namespace Editor

View File

@@ -0,0 +1,47 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "editor/scene/scene_item_base.h"
#include "media/media_video_encode.h"
namespace Editor {
class ItemAnimated : public ItemBase {
public:
using ItemBase::ItemBase;
[[nodiscard]] virtual bool animated() const = 0;
[[nodiscard]] virtual QByteArray content() const = 0;
[[nodiscard]] virtual crl::time loopDuration() const = 0;
virtual void releasePlayers() = 0;
[[nodiscard]] Media::Encode::AnimatedEntity animatedEntity(
const QTransform &sceneToCanvas) const;
protected:
[[nodiscard]] virtual Media::Encode::AnimatedEntity::Kind entityKind()
const = 0;
void paintFrame(
QPainter *p,
const QImage &frame,
bool live,
bool mirror);
private:
struct {
QImage image;
qint64 key = 0;
QSize size;
bool flipped = false;
} _preview;
};
} // namespace Editor

View File

@@ -24,7 +24,7 @@ namespace {
ItemSticker::ItemSticker(
not_null<DocumentData*> document,
ItemBase::Data data)
: ItemBase(std::move(data))
: ItemAnimated(std::move(data))
, _document(document)
, _mediaView(_document->createMediaView()) {
const auto stickerData = document->sticker();
@@ -150,35 +150,11 @@ bool ItemSticker::animated() const {
return (_lottie.player != nullptr) || _webm.valid() || _releasedAnimation;
}
Media::Encode::AnimatedEntity ItemSticker::animatedEntity(
const QTransform &sceneToCanvas) const {
Media::Encode::AnimatedEntity::Kind ItemSticker::entityKind() const {
const auto data = _document->sticker();
const auto composed = QTransform().scale(flipped() ? -1. : 1., 1.)
* sceneTransform()
* sceneToCanvas;
const auto inner = contentRect();
const auto m11 = composed.m11();
const auto m12 = composed.m12();
const auto m21 = composed.m21();
const auto m22 = composed.m22();
const auto scale = std::hypot(m11, m12);
const auto mirrored = ((m11 * m22 - m12 * m21) < 0);
const auto rotation = mirrored
? (std::atan2(-m12, m22) * 180. / M_PI)
: (std::atan2(m12, m11) * 180. / M_PI);
const auto size = inner.size() * scale;
const auto center = composed.map(inner.center());
return {
.kind = ((data && data->isWebm())
? Media::Encode::AnimatedEntity::Kind::Webm
: Media::Encode::AnimatedEntity::Kind::Lottie),
.bytes = content(),
.geometry = QRectF(
center - QPointF(size.width() / 2., size.height() / 2.),
size),
.rotation = rotation,
.flipped = mirrored,
};
return (data && data->isWebm())
? Media::Encode::AnimatedEntity::Kind::Webm
: Media::Encode::AnimatedEntity::Kind::Lottie;
}
QByteArray ItemSticker::content() const {
@@ -230,51 +206,9 @@ void ItemSticker::paint(
_pendingRecreate = false;
createPlayer();
}
const auto rect = contentRect();
const auto image = currentFrame();
if (!image.isNull()) {
const auto ratio = style::DevicePixelRatio();
const auto fitted = QSizeF(image.size())
.scaled(rect.size(), Qt::KeepAspectRatio);
const auto resultRect = QRectF(rect.topLeft(), fitted).translated(
(rect.width() - fitted.width()) / 2.,
(rect.height() - fitted.height()) / 2.);
const auto live = (_lottie.player && _lottie.player->ready())
|| (_webm && _webm->started());
if (live) {
p->save();
p->setRenderHint(QPainter::SmoothPixmapTransform);
if (_webm.valid() && flipped()) {
p->translate(resultRect.center().x(), 0);
p->scale(-1., 1.);
p->translate(-resultRect.center().x(), 0);
}
p->drawImage(resultRect, image);
p->restore();
} else {
auto pixelSize = (fitted * ratio).toSize();
if (pixelSize.width() > image.width()) {
pixelSize = image.size();
}
const auto mirror = _webm.valid() && flipped();
if ((_preview.key != image.cacheKey())
|| (_preview.size != pixelSize)
|| (_preview.flipped != mirror)) {
_preview.image = image.scaled(
pixelSize,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
if (mirror) {
_preview.image = _preview.image.mirrored(true, false);
}
_preview.image.setDevicePixelRatio(ratio);
_preview.key = image.cacheKey();
_preview.size = pixelSize;
_preview.flipped = mirror;
}
p->drawImage(resultRect, _preview.image);
}
}
const auto live = (_lottie.player && _lottie.player->ready())
|| (_webm && _webm->started());
paintFrame(p, currentFrame(), live, _webm.valid() && flipped());
ItemBase::paint(p, option, w);
}

View File

@@ -7,9 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "editor/scene/scene_item_base.h"
#include "editor/scene/scene_item_animated.h"
#include "media/clip/media_clip_reader.h"
#include "media/media_video_encode.h"
namespace Data {
class DocumentMedia;
@@ -21,7 +20,7 @@ class DocumentData;
namespace Editor {
class ItemSticker : public ItemBase {
class ItemSticker final : public ItemAnimated {
public:
enum { Type = ItemBase::Type + 1 };
@@ -33,16 +32,16 @@ public:
const QStyleOptionGraphicsItem *option,
QWidget *widget) override;
[[nodiscard]] not_null<DocumentData*> sticker() const;
[[nodiscard]] bool animated() const;
[[nodiscard]] Media::Encode::AnimatedEntity animatedEntity(
const QTransform &sceneToCanvas) const;
[[nodiscard]] QByteArray content() const;
[[nodiscard]] crl::time loopDuration() const;
void releasePlayers();
[[nodiscard]] bool animated() const override;
[[nodiscard]] QByteArray content() const override;
[[nodiscard]] crl::time loopDuration() const override;
void releasePlayers() override;
void setStatus(Status status) override;
int type() const override;
protected:
[[nodiscard]] Media::Encode::AnimatedEntity::Kind entityKind()
const override;
void performFlip() override;
std::shared_ptr<ItemBase> duplicate(ItemBase::Data data) const override;
@@ -61,12 +60,6 @@ private:
} _lottie;
::Media::Clip::ReaderPointer _webm;
QImage _image;
struct {
QImage image;
qint64 key = 0;
QSize size;
bool flipped = false;
} _preview;
crl::time _loopDuration = 0;
bool _releasedAnimation = false;

View File

@@ -183,6 +183,8 @@ PRIVATE
editor/photo_editor_inner_common.h
editor/scene/scene.cpp
editor/scene/scene.h
editor/scene/scene_item_animated.cpp
editor/scene/scene_item_animated.h
editor/scene/scene_item_base.cpp
editor/scene/scene_item_base.h
editor/scene/scene_item_canvas.cpp