mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/telegramdesktop/tdesktop
synced 2026-09-20 08:03:45 +08:00
[img-editor] Moved animated overlay logic to separated module.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
91
Telegram/SourceFiles/editor/scene/scene_item_animated.cpp
Normal file
91
Telegram/SourceFiles/editor/scene/scene_item_animated.cpp
Normal 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
|
||||
47
Telegram/SourceFiles/editor/scene/scene_item_animated.h
Normal file
47
Telegram/SourceFiles/editor/scene/scene_item_animated.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user