mirror of https://github.com/procxx/kepka.git
				
				
				
			Parse and render lottie in the background.
This commit is contained in:
		
							parent
							
								
									61b6effccc
								
							
						
					
					
						commit
						cfff744cb1
					
				|  | @ -9,9 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| 
 | 
 | ||||||
| #include <QtCore/QObject> | #include <QtCore/QObject> | ||||||
| #include <QtCore/QThread> | #include <QtCore/QThread> | ||||||
| #include "base/observer.h" |  | ||||||
| #include "base/flat_map.h" | #include "base/flat_map.h" | ||||||
| 
 | 
 | ||||||
|  | #include <crl/crl_time.h> | ||||||
|  | 
 | ||||||
| namespace base { | namespace base { | ||||||
| 
 | 
 | ||||||
| class Timer final : private QObject { | class Timer final : private QObject { | ||||||
|  |  | ||||||
|  | @ -32,8 +32,7 @@ HistorySticker::HistorySticker( | ||||||
| 	not_null<DocumentData*> document) | 	not_null<DocumentData*> document) | ||||||
| : HistoryMedia(parent) | : HistoryMedia(parent) | ||||||
| , _data(document) | , _data(document) | ||||||
| , _emoji(_data->sticker()->alt) | , _emoji(_data->sticker()->alt) { | ||||||
| , _timer([=] { parent->data()->history()->owner().requestViewRepaint(parent); }) { |  | ||||||
| 	_data->loadThumbnail(parent->data()->fullId()); | 	_data->loadThumbnail(parent->data()->fullId()); | ||||||
| 	if (const auto emoji = Ui::Emoji::Find(_emoji)) { | 	if (const auto emoji = Ui::Emoji::Find(_emoji)) { | ||||||
| 		_emoji = emoji->text(); | 		_emoji = emoji->text(); | ||||||
|  | @ -93,12 +92,25 @@ QSize HistorySticker::countCurrentSize(int newWidth) { | ||||||
| 	return { newWidth, minHeight() }; | 	return { newWidth, minHeight() }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void HistorySticker::setupLottie() { | ||||||
|  | 	_lottie = _data->data().isEmpty() | ||||||
|  | 		? Lottie::FromFile(_data->filepath()) | ||||||
|  | 		: Lottie::FromData(_data->data()); | ||||||
|  | 	_lottie->updates( | ||||||
|  | 	) | rpl::start_with_next_error([=](Lottie::Update update) { | ||||||
|  | 		update.data.match([&](const Lottie::Information &information) { | ||||||
|  | 			_parent->data()->history()->owner().requestViewResize(_parent); | ||||||
|  | 		}, [&](const Lottie::DisplayFrameRequest &request) { | ||||||
|  | 			_parent->data()->history()->owner().requestViewRepaint(_parent); | ||||||
|  | 		}); | ||||||
|  | 	}, [=](Lottie::Error error) { | ||||||
|  | 	}, _lifetime); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const { | void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const { | ||||||
| 	if (!_lottie && _data->filename().endsWith(qstr(".json"))) { | 	if (!_lottie && _data->filename().endsWith(qstr(".json"))) { | ||||||
| 		if (_data->loaded()) { | 		if (_data->loaded()) { | ||||||
| 			_lottie = _data->data().isEmpty() | 			const_cast<HistorySticker*>(this)->setupLottie(); | ||||||
| 				? Lottie::FromFile(_data->filepath()) |  | ||||||
| 				: Lottie::FromData(_data->data()); |  | ||||||
| 		} else { | 		} else { | ||||||
| 			_data->automaticLoad(_parent->data()->fullId(), _parent->data()); | 			_data->automaticLoad(_parent->data()->fullId(), _parent->data()); | ||||||
| 		} | 		} | ||||||
|  | @ -129,16 +141,17 @@ void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, c | ||||||
| 	if (rtl()) usex = width() - usex - usew; | 	if (rtl()) usex = width() - usex - usew; | ||||||
| 
 | 
 | ||||||
| 	if (_lottie) { | 	if (_lottie) { | ||||||
| 		auto frame = _lottie->frame(crl::now()); | 		if (_lottie->ready()) { | ||||||
| 		if (selected) { | 			auto request = Lottie::FrameRequest(); | ||||||
| 			frame = Images::prepareColored( | 			request.resize = QSize(_pixw, _pixh) * cIntRetinaFactor(); | ||||||
| 				st::msgStickerOverlay, | 			if (selected) { | ||||||
| 				std::move(frame)); | 				request.colored = st::msgStickerOverlay->c; | ||||||
|  | 			} | ||||||
|  | 			_lottie->markFrameShown(); | ||||||
|  | 			p.drawImage( | ||||||
|  | 				QRect(usex + (usew - _pixw) / 2, (minHeight() - _pixh) / 2, _pixw, _pixh), | ||||||
|  | 				_lottie->frame(request)); | ||||||
| 		} | 		} | ||||||
| 		p.drawImage( |  | ||||||
| 			QRect(usex + (usew - _pixw) / 2, (minHeight() - _pixh) / 2, _pixw, _pixh), |  | ||||||
| 			frame); |  | ||||||
| 		_timer.callOnce(crl::time(1000) / _lottie->frameRate()); |  | ||||||
| 	} else { | 	} else { | ||||||
| 		const auto &pixmap = [&]() -> const QPixmap & { | 		const auto &pixmap = [&]() -> const QPixmap & { | ||||||
| 			const auto o = item->fullId(); | 			const auto o = item->fullId(); | ||||||
|  |  | ||||||
|  | @ -63,12 +63,14 @@ private: | ||||||
| 	int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const; | 	int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const; | ||||||
| 	int additionalWidth() const; | 	int additionalWidth() const; | ||||||
| 
 | 
 | ||||||
|  | 	void setupLottie(); | ||||||
|  | 
 | ||||||
| 	int _pixw = 1; | 	int _pixw = 1; | ||||||
| 	int _pixh = 1; | 	int _pixh = 1; | ||||||
| 	ClickHandlerPtr _packLink; | 	ClickHandlerPtr _packLink; | ||||||
| 	not_null<DocumentData*> _data; | 	not_null<DocumentData*> _data; | ||||||
| 	QString _emoji; | 	QString _emoji; | ||||||
| 	mutable base::Timer _timer; | 	std::unique_ptr<Lottie::Animation> _lottie; | ||||||
| 	mutable std::unique_ptr<Lottie::Animation> _lottie; | 	rpl::lifetime _lifetime; | ||||||
| 
 | 
 | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -10,23 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| #include "lottie/lottie_frame_renderer.h" | #include "lottie/lottie_frame_renderer.h" | ||||||
| #include "base/algorithm.h" | #include "base/algorithm.h" | ||||||
| 
 | 
 | ||||||
| #include <range/v3/view/reverse.hpp> | #include <crl/crl_async.h> | ||||||
| #include <QtMath> | #include <crl/crl_on_main.h> | ||||||
| #include <QJsonDocument> | #include <QJsonDocument> | ||||||
| #include <QJsonObject> |  | ||||||
| #include <QJsonArray> |  | ||||||
| #include <QJsonValue> |  | ||||||
| #include <QFile> | #include <QFile> | ||||||
| #include <QPointF> |  | ||||||
| #include <QPainter> |  | ||||||
| #include <QImage> |  | ||||||
| #include <QTimer> |  | ||||||
| #include <QMetaObject> |  | ||||||
| #include <QLoggingCategory> |  | ||||||
| #include <QThread> |  | ||||||
| #include <math.h> |  | ||||||
| 
 |  | ||||||
| #include <QtBodymovin/private/bmscene_p.h> |  | ||||||
| 
 | 
 | ||||||
| #include "rasterrenderer/lottierasterrenderer.h" | #include "rasterrenderer/lottierasterrenderer.h" | ||||||
| 
 | 
 | ||||||
|  | @ -58,71 +45,117 @@ std::unique_ptr<Animation> FromData(const QByteArray &data) { | ||||||
| 	return std::make_unique<Lottie::Animation>(data); | 	return std::make_unique<Lottie::Animation>(data); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Animation::Animation(const QByteArray &content) { | Animation::Animation(const QByteArray &content) | ||||||
| 	parse(content); | : _timer([=] { checkNextFrame(); }) { | ||||||
|  | 	const auto weak = base::make_weak(this); | ||||||
|  | 	crl::async([=] { | ||||||
|  | 		auto error = QJsonParseError(); | ||||||
|  | 		const auto document = QJsonDocument::fromJson(content, &error); | ||||||
|  | 		if (error.error != QJsonParseError::NoError) { | ||||||
|  | 			qCWarning(lcLottieQtBodymovinParser) | ||||||
|  | 				<< "Lottie Error: Parse failed with code " | ||||||
|  | 				<< error.error | ||||||
|  | 				<< "( " << error.errorString() << ")"; | ||||||
|  | 			crl::on_main(weak, [=] { | ||||||
|  | 				parseFailed(); | ||||||
|  | 			}); | ||||||
|  | 		} else { | ||||||
|  | 			auto state = std::make_unique<SharedState>(document.object()); | ||||||
|  | 			crl::on_main(weak, [this, result = std::move(state)]() mutable { | ||||||
|  | 				parseDone(std::move(result)); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Animation::~Animation() { | Animation::~Animation() { | ||||||
|  | 	if (_renderer) { | ||||||
|  | 		Assert(_state != nullptr); | ||||||
|  | 		_renderer->remove(_state); | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| QImage Animation::frame(crl::time now) const { | void Animation::parseDone(std::unique_ptr<SharedState> state) { | ||||||
| 	if (_scene->startFrame() == _scene->endFrame() | 	Expects(state != nullptr); | ||||||
| 		|| _scene->width() <= 0 | 
 | ||||||
| 		|| _scene->height() <= 0) { | 	auto information = state->information(); | ||||||
| 		return QImage(); | 	if (!information.frameRate | ||||||
|  | 		|| information.framesCount <= 0 | ||||||
|  | 		|| information.size.isEmpty()) { | ||||||
|  | 		_updates.fire_error(Error::NotSupported); | ||||||
|  | 	} else { | ||||||
|  | 		_state = state.get(); | ||||||
|  | 		_state->start(this, crl::now()); | ||||||
|  | 		_renderer = FrameRenderer::Instance(); | ||||||
|  | 		_renderer->append(std::move(state)); | ||||||
|  | 		_updates.fire({ std::move(information) }); | ||||||
| 	} | 	} | ||||||
| 	auto result = QImage( | } | ||||||
| 		_scene->width(), |  | ||||||
| 		_scene->height(), |  | ||||||
| 		QImage::Format_ARGB32_Premultiplied); |  | ||||||
| 	result.fill(Qt::transparent); |  | ||||||
| 
 | 
 | ||||||
| 	{ | void Animation::parseFailed() { | ||||||
| 		QPainter p(&result); | 	_updates.fire_error(Error::ParseFailed); | ||||||
| 		p.setRenderHints(QPainter::Antialiasing); | } | ||||||
| 		p.setRenderHints(QPainter::SmoothPixmapTransform); |  | ||||||
| 
 | 
 | ||||||
| 		const auto position = now; | QImage Animation::frame(const FrameRequest &request) const { | ||||||
| 		const auto elapsed = int((_scene->frameRate() * position + 500) / 1000); | 	Expects(_renderer != nullptr); | ||||||
| 		const auto from = _scene->startFrame(); |  | ||||||
| 		const auto till = _scene->endFrame(); |  | ||||||
| 		const auto frames = (till - from); |  | ||||||
| 		const auto frame = _options.loop |  | ||||||
| 			? (from + (elapsed % frames)) |  | ||||||
| 			: std::min(from + elapsed, till); |  | ||||||
| 
 | 
 | ||||||
| 		_scene->updateProperties(frame); | 	const auto frame = _state->frameForPaint(); | ||||||
| 
 | 	const auto changed = (frame->request != request) | ||||||
| 		LottieRasterRenderer renderer(&p); | 		&& (request.strict || !frame->request.strict); | ||||||
| 		_scene->render(renderer, frame); | 	if (changed) { | ||||||
|  | 		frame->request = request; | ||||||
|  | 		_renderer->updateFrameRequest(_state, request); | ||||||
| 	} | 	} | ||||||
|  | 	return PrepareFrameByRequest(frame, !changed); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | rpl::producer<Update, Error> Animation::updates() const { | ||||||
|  | 	return _updates.events(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool Animation::ready() const { | ||||||
|  | 	return (_renderer != nullptr); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | crl::time Animation::markFrameDisplayed(crl::time now) { | ||||||
|  | 	Expects(_renderer != nullptr); | ||||||
|  | 
 | ||||||
|  | 	const auto result = _state->markFrameDisplayed(now); | ||||||
|  | 
 | ||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int Animation::frameRate() const { | crl::time Animation::markFrameShown() { | ||||||
| 	return _scene->frameRate(); | 	Expects(_renderer != nullptr); | ||||||
|  | 
 | ||||||
|  | 	const auto result = _state->markFrameShown(); | ||||||
|  | 	_renderer->frameShown(_state); | ||||||
|  | 
 | ||||||
|  | 	return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| crl::time Animation::duration() const { | void Animation::checkNextFrame() { | ||||||
| 	return (_scene->endFrame() - _scene->startFrame()) * crl::time(1000) / _scene->frameRate(); | 	Expects(_renderer != nullptr); | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| void Animation::play(const PlaybackOptions &options) { | 	const auto time = _state->nextFrameDisplayTime(); | ||||||
| 	_options = options; | 	if (time == kTimeUnknown) { | ||||||
| 	_started = crl::now(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void Animation::parse(const QByteArray &content) { |  | ||||||
| 	const auto document = QJsonDocument::fromJson(content); |  | ||||||
| 	const auto root = document.object(); |  | ||||||
| 
 |  | ||||||
| 	if (root.empty()) { |  | ||||||
| 		_failed = true; |  | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	_scene = std::make_unique<BMScene>(); | 	const auto now = crl::now(); | ||||||
| 	_scene->parse(root); | 	if (time > now) { | ||||||
|  | 		_timer.callOnce(time - now); | ||||||
|  | 	} else { | ||||||
|  | 		_timer.cancel(); | ||||||
|  | 
 | ||||||
|  | 		const auto position = markFrameDisplayed(now); | ||||||
|  | 		_updates.fire({ DisplayFrameRequest{ position } }); | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | //void Animation::play(const PlaybackOptions &options) {
 | ||||||
|  | //	_options = options;
 | ||||||
|  | //	_started = crl::now();
 | ||||||
|  | //}
 | ||||||
|  | 
 | ||||||
| } // namespace Lottie
 | } // namespace Lottie
 | ||||||
|  |  | ||||||
|  | @ -9,69 +9,62 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| 
 | 
 | ||||||
| #include "base/basic_types.h" | #include "base/basic_types.h" | ||||||
| #include "base/flat_map.h" | #include "base/flat_map.h" | ||||||
|  | #include "base/weak_ptr.h" | ||||||
|  | #include "base/timer.h" | ||||||
| 
 | 
 | ||||||
|  | #include "lottie/lottie_common.h" | ||||||
|  | 
 | ||||||
|  | #include <QSize> | ||||||
| #include <crl/crl_time.h> | #include <crl/crl_time.h> | ||||||
|  | #include <rpl/event_stream.h> | ||||||
| #include <vector> | #include <vector> | ||||||
|  | #include <optional> | ||||||
| 
 | 
 | ||||||
| class QImage; | class QImage; | ||||||
| class QString; | class QString; | ||||||
| class QByteArray; | class QByteArray; | ||||||
| 
 | 
 | ||||||
| class BMScene; |  | ||||||
| 
 |  | ||||||
| namespace Lottie { | namespace Lottie { | ||||||
| 
 | 
 | ||||||
| class Animation; | class Animation; | ||||||
|  | class SharedState; | ||||||
|  | class FrameRenderer; | ||||||
| 
 | 
 | ||||||
| bool ValidateFile(const QString &path); | bool ValidateFile(const QString &path); | ||||||
| std::unique_ptr<Animation> FromFile(const QString &path); | std::unique_ptr<Animation> FromFile(const QString &path); | ||||||
| std::unique_ptr<Animation> FromData(const QByteArray &data); | std::unique_ptr<Animation> FromData(const QByteArray &data); | ||||||
| 
 | 
 | ||||||
| struct PlaybackOptions { | class Animation final : public base::has_weak_ptr { | ||||||
| 	float64 speed = 1.; |  | ||||||
| 	bool loop = true; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class Animation final { |  | ||||||
| public: | public: | ||||||
| 	explicit Animation(const QByteArray &content); | 	explicit Animation(const QByteArray &content); | ||||||
| 	~Animation(); | 	~Animation(); | ||||||
| 
 | 
 | ||||||
| 	void play(const PlaybackOptions &options); | 	//void play(const PlaybackOptions &options);
 | ||||||
| 
 | 
 | ||||||
| 	QImage frame(crl::time now) const; | 	[[nodiscard]] QImage frame(const FrameRequest &request) const; | ||||||
| 
 | 
 | ||||||
| 	int frameRate() const; | 	[[nodiscard]] rpl::producer<Update, Error> updates() const; | ||||||
| 	crl::time duration() const; |  | ||||||
| 
 | 
 | ||||||
| 	void play(); |  | ||||||
| 	void pause(); |  | ||||||
| 	void resume(); |  | ||||||
| 	void stop(); |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] bool active() const; |  | ||||||
| 	[[nodiscard]] bool ready() const; | 	[[nodiscard]] bool ready() const; | ||||||
| 	[[nodiscard]] bool unsupported() const; |  | ||||||
| 
 | 
 | ||||||
| 	[[nodiscard]] float64 speed() const; | 	// Returns frame position, if any frame was marked as displayed.
 | ||||||
| 	void setSpeed(float64 speed); // 0.5 <= speed <= 2.
 | 	crl::time markFrameDisplayed(crl::time now); | ||||||
|  | 	crl::time markFrameShown(); | ||||||
| 
 | 
 | ||||||
| 	[[nodiscard]] bool playing() const; | 	void checkNextFrame(); | ||||||
| 	[[nodiscard]] bool buffering() const; |  | ||||||
| 	[[nodiscard]] bool paused() const; |  | ||||||
| 	[[nodiscard]] bool finished() const; |  | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	void parse(const QByteArray &content); | 	void parseDone(std::unique_ptr<SharedState> state); | ||||||
|  | 	void parseFailed(); | ||||||
| 
 | 
 | ||||||
| 	bool _initialized = false; | 	//crl::time _started = 0;
 | ||||||
| 	bool _unsupported = false; | 	//PlaybackOptions _options;
 | ||||||
| 	bool _failed = false; |  | ||||||
| 	bool _paused = false; |  | ||||||
| 	crl::time _started = 0; |  | ||||||
| 	PlaybackOptions _options; |  | ||||||
| 
 | 
 | ||||||
| 	std::unique_ptr<BMScene> _scene; | 	base::Timer _timer; | ||||||
|  | 	SharedState *_state = nullptr; | ||||||
|  | 	std::shared_ptr<FrameRenderer> _renderer; | ||||||
|  | 	rpl::event_stream<Update, Error> _updates; | ||||||
|  | 	rpl::lifetime _lifetime; | ||||||
| 
 | 
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,71 @@ | ||||||
|  | /*
 | ||||||
|  | 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 "base/basic_types.h" | ||||||
|  | #include "base/variant.h" | ||||||
|  | 
 | ||||||
|  | #include <QSize> | ||||||
|  | #include <QColor> | ||||||
|  | #include <crl/crl_time.h> | ||||||
|  | 
 | ||||||
|  | namespace Lottie { | ||||||
|  | 
 | ||||||
|  | class Animation; | ||||||
|  | 
 | ||||||
|  | struct PlaybackOptions { | ||||||
|  | 	float64 speed = 1.; | ||||||
|  | 	bool loop = true; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct Information { | ||||||
|  | 	int frameRate = 0; | ||||||
|  | 	int framesCount = 0; | ||||||
|  | 	QSize size; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct DisplayFrameRequest { | ||||||
|  | 	crl::time time = 0; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct Update { | ||||||
|  | 	base::variant< | ||||||
|  | 		Information, | ||||||
|  | 		DisplayFrameRequest> data; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | enum class Error { | ||||||
|  | 	ParseFailed, | ||||||
|  | 	NotSupported, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct FrameRequest { | ||||||
|  | 	QSize resize; | ||||||
|  | 	std::optional<QColor> colored; | ||||||
|  | 	bool strict = true; | ||||||
|  | 
 | ||||||
|  | 	static FrameRequest NonStrict() { | ||||||
|  | 		auto result = FrameRequest(); | ||||||
|  | 		result.strict = false; | ||||||
|  | 		return result; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	bool empty() const { | ||||||
|  | 		return resize.isEmpty(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	bool operator==(const FrameRequest &other) const { | ||||||
|  | 		return (resize == other.resize) | ||||||
|  | 			&& (colored == other.colored); | ||||||
|  | 	} | ||||||
|  | 	bool operator!=(const FrameRequest &other) const { | ||||||
|  | 		return !(*this == other); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace Lottie
 | ||||||
|  | @ -8,222 +8,439 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| #include "lottie/lottie_frame_renderer.h" | #include "lottie/lottie_frame_renderer.h" | ||||||
| 
 | 
 | ||||||
| #include "lottie/lottie_animation.h" | #include "lottie/lottie_animation.h" | ||||||
| 
 |  | ||||||
| #include <QImage> |  | ||||||
| #include <QPainter> |  | ||||||
| #include <QHash> |  | ||||||
| #include <QMutexLocker> |  | ||||||
| #include <QLoggingCategory> |  | ||||||
| #include <QThread> |  | ||||||
| 
 |  | ||||||
| #include <QJsonDocument> |  | ||||||
| #include <QJsonArray> |  | ||||||
| 
 |  | ||||||
| #include <QtBodymovin/private/bmconstants_p.h> |  | ||||||
| #include <QtBodymovin/private/bmbase_p.h> |  | ||||||
| #include <QtBodymovin/private/bmlayer_p.h> |  | ||||||
| 
 |  | ||||||
| #include "rasterrenderer/lottierasterrenderer.h" | #include "rasterrenderer/lottierasterrenderer.h" | ||||||
|  | #include "logs.h" | ||||||
| 
 | 
 | ||||||
| Q_LOGGING_CATEGORY(lcLottieQtBodymovinRenderThread, "qt.lottieqt.bodymovin.render.thread"); | #include <range/v3/algorithm/find.hpp> | ||||||
|  | #include <range/v3/algorithm/count_if.hpp> | ||||||
|  | #include <QJsonDocument> | ||||||
|  | 
 | ||||||
|  | namespace Images { | ||||||
|  | QImage prepareColored(QColor add, QImage image); | ||||||
|  | } // namespace Images
 | ||||||
| 
 | 
 | ||||||
| namespace Lottie { | namespace Lottie { | ||||||
| //
 | namespace { | ||||||
| //FrameRenderer *FrameRenderer::_rendererInstance = nullptr;
 | 
 | ||||||
| //
 | constexpr auto kDisplaySkipped = crl::time(-1); | ||||||
| //FrameRenderer::~FrameRenderer()
 | 
 | ||||||
| //{
 | std::weak_ptr<FrameRenderer> GlobalInstance; | ||||||
| //	QMutexLocker mlocker(&_mutex);
 | 
 | ||||||
| //    qDeleteAll(_animData);
 | constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied; | ||||||
| //    qDeleteAll(_frameCache);
 | 
 | ||||||
| //}
 | bool GoodStorageForFrame(const QImage &storage, QSize size) { | ||||||
| //
 | 	return !storage.isNull() | ||||||
| //FrameRenderer *FrameRenderer::instance()
 | 		&& (storage.format() == kImageFormat) | ||||||
| //{
 | 		&& (storage.size() == size) | ||||||
| //    if (!_rendererInstance)
 | 		&& storage.isDetached(); | ||||||
| //        _rendererInstance = new FrameRenderer;
 | } | ||||||
| //
 | 
 | ||||||
| //    return _rendererInstance;
 | QImage CreateFrameStorage(QSize size) { | ||||||
| //}
 | 	return QImage(size, kImageFormat); | ||||||
| //
 | } | ||||||
| //void FrameRenderer::deleteInstance()
 | 
 | ||||||
| //{
 | } // namespace
 | ||||||
| //    delete _rendererInstance;
 | 
 | ||||||
| //    _rendererInstance = nullptr;
 | class FrameRendererObject final { | ||||||
| //}
 | public: | ||||||
| //
 | 	explicit FrameRendererObject( | ||||||
| //void FrameRenderer::registerAnimator(Animation *animator)
 | 		crl::weak_on_queue<FrameRendererObject> weak); | ||||||
| //{
 | 
 | ||||||
| //    QMutexLocker mlocker(&_mutex);
 | 	void append(std::unique_ptr<SharedState> entry); | ||||||
| //
 | 	void frameShown(not_null<SharedState*> entry); | ||||||
| //    qCDebug(lcLottieQtBodymovinRenderThread) << "Register Animator:"
 | 	void updateFrameRequest( | ||||||
| //                                       << static_cast<void*>(animator);
 | 		not_null<SharedState*> entry, | ||||||
| //
 | 		const FrameRequest &request); | ||||||
| //    Entry *entry = new Entry;
 | 	void remove(not_null<SharedState*> entry); | ||||||
| //    entry->animator = animator;
 | 
 | ||||||
| //    entry->startFrame = animator->startFrame();
 | private: | ||||||
| //    entry->endFrame = animator->endFrame();
 | 	struct Entry { | ||||||
| //    entry->currentFrame = animator->startFrame();
 | 		std::unique_ptr<SharedState> state; | ||||||
| //	entry->animDir = animator->direction();
 | 		FrameRequest request; | ||||||
| //    entry->bmTreeBlueprint = new BMBase;
 | 	}; | ||||||
| //    parse(entry->bmTreeBlueprint, animator->jsonSource());
 | 
 | ||||||
| //    _animData.insert(animator, entry);
 | 	static not_null<SharedState*> StateFromEntry(const Entry &entry) { | ||||||
| //    _waitCondition.wakeAll();
 | 		return entry.state.get(); | ||||||
| //}
 | 	} | ||||||
| //
 | 
 | ||||||
| //void FrameRenderer::deregisterAnimator(Animation *animator)
 | 	void queueGenerateFrames(); | ||||||
| //{
 | 	void generateFrames(); | ||||||
| //    QMutexLocker mlocker(&_mutex);
 | 
 | ||||||
| //
 | 	crl::weak_on_queue<FrameRendererObject> _weak; | ||||||
| //    qCDebug(lcLottieQtBodymovinRenderThread) << "Deregister Animator:"
 | 	std::vector<Entry> _entries; | ||||||
| //                                       << static_cast<void*>(animator);
 | 	bool _queued = false; | ||||||
| //
 | 
 | ||||||
| //    Entry *entry = _animData.value(animator, nullptr);
 | }; | ||||||
| //    if (entry) {
 | 
 | ||||||
| //        qDeleteAll(entry->frameCache);
 | [[nodiscard]] bool GoodForRequest( | ||||||
| //        delete entry->bmTreeBlueprint;
 | 		const QImage &image, | ||||||
| //        delete entry;
 | 		const FrameRequest &request) { | ||||||
| //        _animData.remove(animator);
 | 	if (request.resize.isEmpty()) { | ||||||
| //    }
 | 		return true; | ||||||
| //}
 | 	} else if (request.colored.has_value()) { | ||||||
| //
 | 		return false; | ||||||
| //bool FrameRenderer::gotoFrame(Animation *animator, int frame)
 | 	} | ||||||
| //{
 | 	return (request.resize == image.size()); | ||||||
| //	QMutexLocker mlocker(&_mutex);
 | } | ||||||
| //    Entry *entry = _animData.value(animator, nullptr);
 | 
 | ||||||
| //    if (entry) {
 | [[nodiscard]] QImage PrepareByRequest( | ||||||
| //        qCDebug(lcLottieQtBodymovinRenderThread) << "Animator:"
 | 		const QImage &original, | ||||||
| //                                           << static_cast<void*>(animator)
 | 		const FrameRequest &request, | ||||||
| //                                           << "Goto frame:" << frame;
 | 		QImage storage) { | ||||||
| //        entry->currentFrame = frame;
 | 	Expects(!request.resize.isEmpty()); | ||||||
| //		entry->animDir = animator->direction();
 | 
 | ||||||
| //        pruneFrameCache(entry);
 | 	if (!GoodStorageForFrame(storage, request.resize)) { | ||||||
| //        _waitCondition.wakeAll();
 | 		storage = CreateFrameStorage(request.resize); | ||||||
| //        return true;
 | 	} | ||||||
| //    }
 | 	{ | ||||||
| //    return false;
 | 		QPainter p(&storage); | ||||||
| //}
 | 		p.setRenderHint(QPainter::Antialiasing); | ||||||
| //
 | 		p.setRenderHint(QPainter::SmoothPixmapTransform); | ||||||
| //FrameRenderer::FrameRenderer() : QThread() {
 | 		p.setRenderHint(QPainter::HighQualityAntialiasing); | ||||||
| //	const QByteArray cacheStr = qgetenv("QLOTTIE_RENDER_CACHE_SIZE");
 | 		p.drawImage(QRect(QPoint(), request.resize), original); | ||||||
| //	int cacheSize = cacheStr.toInt();
 | 	} | ||||||
| //	if (cacheSize > 0) {
 | 	if (request.colored.has_value()) { | ||||||
| //		qCDebug(lcLottieQtBodymovinRenderThread) << "Setting frame cache size to" << cacheSize;
 | 		storage = Images::prepareColored(*request.colored, std::move(storage)); | ||||||
| //		_cacheSize = cacheSize;
 | 	} | ||||||
| //	}
 | 	return storage; | ||||||
| //}
 | } | ||||||
| //
 | 
 | ||||||
| //void FrameRenderer::pruneFrameCache(Entry* e)
 | QImage PrepareFrameByRequest( | ||||||
| //{
 | 		not_null<Frame*> frame, | ||||||
| //    QHash<int, BMBase*>::iterator it = e->frameCache.begin();
 | 		bool useExistingPrepared = false) { | ||||||
| //
 | 	Expects(!frame->original.isNull()); | ||||||
| //    while (it != e->frameCache.end()) {
 | 
 | ||||||
| //        if (it.key() == e->currentFrame) {
 | 	if (GoodForRequest(frame->original, frame->request)) { | ||||||
| //            ++it;
 | 		return frame->original; | ||||||
| //        } else {
 | 	} else if (frame->prepared.isNull() || !useExistingPrepared) { | ||||||
| //            delete it.value();
 | 		frame->prepared = PrepareByRequest( | ||||||
| //            it = e->frameCache.erase(it);
 | 			frame->original, | ||||||
| //        }
 | 			frame->request, | ||||||
| //    }
 | 			std::move(frame->prepared)); | ||||||
| //}
 | 	} | ||||||
| //
 | 	return frame->prepared; | ||||||
| //BMBase *FrameRenderer::getFrame(Animation *animator, int frameNumber)
 | } | ||||||
| //{
 | 
 | ||||||
| //    QMutexLocker mlocker(&_mutex);
 | FrameRendererObject::FrameRendererObject( | ||||||
| //
 | 	crl::weak_on_queue<FrameRendererObject> weak) | ||||||
| //    Entry *entry = _animData.value(animator, nullptr);
 | : _weak(std::move(weak)) { | ||||||
| //    if (entry)
 | } | ||||||
| //        return entry->frameCache.value(frameNumber, nullptr);
 | 
 | ||||||
| //    else
 | void FrameRendererObject::append(std::unique_ptr<SharedState> state) { | ||||||
| //        return nullptr;
 | 	_entries.push_back({ std::move(state) }); | ||||||
| //}
 | 	queueGenerateFrames(); | ||||||
| //
 | } | ||||||
| //void FrameRenderer::prerender(Entry *animEntry)
 | 
 | ||||||
| //{
 | void FrameRendererObject::frameShown(not_null<SharedState*> entry) { | ||||||
| //    while (animEntry->frameCache.count() < _cacheSize) {
 | 	queueGenerateFrames(); | ||||||
| //        if (!animEntry->frameCache.contains(animEntry->currentFrame)) {
 | } | ||||||
| //            BMBase *bmTree = new BMBase(*animEntry->bmTreeBlueprint);
 | 
 | ||||||
| //
 | void FrameRendererObject::updateFrameRequest( | ||||||
| //            for (BMBase *elem : bmTree->children()) {
 | 		not_null<SharedState*> entry, | ||||||
| //                if (elem->active(animEntry->currentFrame))
 | 		const FrameRequest &request) { | ||||||
| //                    elem->updateProperties( animEntry->currentFrame);
 | 	const auto i = ranges::find(_entries, entry, &StateFromEntry); | ||||||
| //            }
 | 	Assert(i != end(_entries)); | ||||||
| //
 | 	i->request = request; | ||||||
| //            animEntry->frameCache.insert( animEntry->currentFrame, bmTree);
 | } | ||||||
| //        }
 | 
 | ||||||
| //
 | void FrameRendererObject::remove(not_null<SharedState*> entry) { | ||||||
| //        qCDebug(lcLottieQtBodymovinRenderThread) << "Animator:"
 | 	const auto i = ranges::find(_entries, entry, &StateFromEntry); | ||||||
| //                                           << static_cast<void*>(animEntry->animator)
 | 	Assert(i != end(_entries)); | ||||||
| //                                           << "Frame drawn to cache. FN:"
 | 	_entries.erase(i); | ||||||
| //                                           << animEntry->currentFrame;
 | } | ||||||
| //        emit frameReady(animEntry->animator,  animEntry->currentFrame);
 | 
 | ||||||
| //
 | void FrameRendererObject::generateFrames() { | ||||||
| //        animEntry->currentFrame += animEntry->animDir;
 | 	const auto renderOne = [&](const Entry & entry) { | ||||||
| //
 | 		return entry.state->renderNextFrame(entry.request); | ||||||
| //        if (animEntry->currentFrame > animEntry->endFrame) {
 | 	}; | ||||||
| //            animEntry->currentFrame = animEntry->startFrame;
 | 	if (ranges::count_if(_entries, renderOne) > 0) { | ||||||
| //        } else if (animEntry->currentFrame < animEntry->startFrame) {
 | 		queueGenerateFrames(); | ||||||
| //            animEntry->currentFrame = animEntry->endFrame;
 | 	} | ||||||
| //        }
 | } | ||||||
| //    }
 | 
 | ||||||
| //}
 | void FrameRendererObject::queueGenerateFrames() { | ||||||
| //
 | 	if (_queued) { | ||||||
| //void FrameRenderer::frameRendered(Animation *animator, int frameNumber)
 | 		return; | ||||||
| //{
 | 	} | ||||||
| //	QMutexLocker mlocker(&_mutex);
 | 	_queued = true; | ||||||
| //	Entry *entry = _animData.value(animator, nullptr);
 | 	_weak.with([](FrameRendererObject &that) { | ||||||
| //    if (entry) {
 | 		that._queued = false; | ||||||
| //        qCDebug(lcLottieQtBodymovinRenderThread) << "Animator:" << static_cast<void*>(animator)
 | 		that.generateFrames(); | ||||||
| //                                           << "Remove frame from cache" << frameNumber;
 | 	}); | ||||||
| //
 | } | ||||||
| //        BMBase *root = entry->frameCache.value(frameNumber, nullptr);
 | 
 | ||||||
| //        delete root;
 | SharedState::SharedState(const QJsonObject &definition) | ||||||
| //        entry->frameCache.remove(frameNumber);
 | : _scene(definition) { | ||||||
| //        _waitCondition.wakeAll();
 | 	if (_scene.endFrame() > _scene.startFrame()) { | ||||||
| //    }
 | 		auto cover = QImage(); | ||||||
| //}
 | 		renderFrame(cover, FrameRequest::NonStrict(), 0); | ||||||
| //
 | 		init(std::move(cover)); | ||||||
| //void FrameRenderer::run()
 | 	} | ||||||
| //{
 | } | ||||||
| //    qCDebug(lcLottieQtBodymovinRenderThread) << "rendering thread" << QThread::currentThread();
 | 
 | ||||||
| //
 | void SharedState::renderFrame( | ||||||
| //	while (!isInterruptionRequested()) {
 | 		QImage &image, | ||||||
| //		QMutexLocker mlocker(&_mutex);
 | 		const FrameRequest &request, | ||||||
| //
 | 		int index) { | ||||||
| //		for (Entry *e : qAsConst(_animData))
 | 	const auto realSize = QSize(_scene.width(), _scene.height()); | ||||||
| //			prerender(e);
 | 	if (realSize.isEmpty() || _scene.endFrame() <= _scene.startFrame()) { | ||||||
| //
 | 		return; | ||||||
| //		_waitCondition.wait(&_mutex);
 | 	} | ||||||
| //	}
 | 
 | ||||||
| //}
 | 	const auto size = request.resize.isEmpty() ? realSize : request.resize; | ||||||
| //
 | 	if (!GoodStorageForFrame(image, size)) { | ||||||
| //int FrameRenderer::parse(BMBase* rootElement, const QByteArray &jsonSource)
 | 		image = CreateFrameStorage(size); | ||||||
| //{
 | 	} | ||||||
| //	QJsonDocument doc = QJsonDocument::fromJson(jsonSource);
 | 	image.fill(Qt::transparent); | ||||||
| //	QJsonObject rootObj = doc.object();
 | 
 | ||||||
| //
 | 	QPainter p(&image); | ||||||
| //	if (rootObj.empty())
 | 	p.setRenderHints(QPainter::Antialiasing); | ||||||
| //		return -1;
 | 	p.setRenderHints(QPainter::SmoothPixmapTransform); | ||||||
| //
 | 	p.setRenderHint(QPainter::TextAntialiasing); | ||||||
| //	QJsonArray jsonLayers = rootObj.value(QLatin1String("layers")).toArray();
 | 	p.setRenderHints(QPainter::HighQualityAntialiasing); | ||||||
| //	QJsonArray::const_iterator jsonLayerIt = jsonLayers.constEnd();
 | 	if (realSize != size) { | ||||||
| //	while (jsonLayerIt != jsonLayers.constBegin()) {
 | 		p.scale( | ||||||
| //		jsonLayerIt--;
 | 			size.width() / float64(realSize.width()), | ||||||
| //		QJsonObject jsonLayer = (*jsonLayerIt).toObject();
 | 			size.height() / float64(realSize.height())); | ||||||
| //		BMLayer *layer = BMLayer::construct(jsonLayer);
 | 	} | ||||||
| //		if (layer) {
 | 
 | ||||||
| //			layer->setParent(rootElement);
 | 	const auto frame = std::clamp( | ||||||
| //			// Mask layers must be rendered before the layers they affect to
 | 		_scene.startFrame() + index, | ||||||
| //			// although they appear before in layer hierarchy. For this reason
 | 		_scene.startFrame(), | ||||||
| //			// move a mask after the affected layers, so it will be rendered first
 | 		_scene.endFrame() - 1); | ||||||
| //			if (layer->isMaskLayer())
 | 	_scene.updateProperties(frame); | ||||||
| //				rootElement->prependChild(layer);
 | 
 | ||||||
| //			else
 | 	LottieRasterRenderer renderer(&p); | ||||||
| //				rootElement->appendChild(layer);
 | 	_scene.render(renderer, frame); | ||||||
| //		}
 | } | ||||||
| //	}
 | 
 | ||||||
| //
 | void SharedState::init(QImage cover) { | ||||||
| //	return 0;
 | 	Expects(!initialized()); | ||||||
| //}
 | 
 | ||||||
|  | 	_frames[0].original = std::move(cover); | ||||||
|  | 	_frames[0].position = 0; | ||||||
|  | 
 | ||||||
|  | 	// Usually main thread sets displayed time before _counter increment.
 | ||||||
|  | 	// But in this case we update _counter, so we set a fake displayed time.
 | ||||||
|  | 	_frames[0].displayed = kDisplaySkipped; | ||||||
|  | 
 | ||||||
|  | 	_counter.store(0, std::memory_order_release); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SharedState::start(not_null<Animation*> owner, crl::time now) { | ||||||
|  | 	_owner = owner; | ||||||
|  | 	_started = now; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool IsRendered(not_null<const Frame*> frame) { | ||||||
|  | 	return (frame->position != kTimeUnknown) | ||||||
|  | 		&& (frame->displayed == kTimeUnknown); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SharedState::renderNextFrame( | ||||||
|  | 		not_null<Frame*> frame, | ||||||
|  | 		const FrameRequest &request) { | ||||||
|  | 	const auto framesCount = (_scene.endFrame() - _scene.startFrame()); | ||||||
|  | 	Assert(framesCount > 0); | ||||||
|  | 
 | ||||||
|  | 	renderFrame(frame->original, request, (++_frameIndex) % framesCount); | ||||||
|  | 	PrepareFrameByRequest(frame); | ||||||
|  | 	frame->position = crl::time(1000) * _frameIndex / _scene.frameRate(); | ||||||
|  | 	frame->displayed = kTimeUnknown; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool SharedState::renderNextFrame(const FrameRequest &request) { | ||||||
|  | 	const auto prerender = [&](int index) { | ||||||
|  | 		const auto frame = getFrame(index); | ||||||
|  | 		const auto next = getFrame((index + 1) % kFramesCount); | ||||||
|  | 		if (!IsRendered(frame)) { | ||||||
|  | 			renderNextFrame(frame, request); | ||||||
|  | 			return true; | ||||||
|  | 		} else if (!IsRendered(next)) { | ||||||
|  | 			renderNextFrame(next, request); | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 		return false; | ||||||
|  | 	}; | ||||||
|  | 	const auto present = [&](int counter, int index) { | ||||||
|  | 		const auto frame = getFrame(index); | ||||||
|  | 		if (!IsRendered(frame)) { | ||||||
|  | 			renderNextFrame(frame, request); | ||||||
|  | 		} | ||||||
|  | 		frame->display = _started + frame->position; | ||||||
|  | 
 | ||||||
|  | 		// Release this frame to the main thread for rendering.
 | ||||||
|  | 		_counter.store( | ||||||
|  | 			(counter + 1) % (2 * kFramesCount), | ||||||
|  | 			std::memory_order_release); | ||||||
|  | 		crl::on_main(_owner, [=] { | ||||||
|  | 			_owner->checkNextFrame(); | ||||||
|  | 		}); | ||||||
|  | 		return true; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	switch (counter()) { | ||||||
|  | 	case 0: return present(0, 1); | ||||||
|  | 	case 1: return prerender(2); | ||||||
|  | 	case 2: return present(2, 2); | ||||||
|  | 	case 3: return prerender(3); | ||||||
|  | 	case 4: return present(4, 3); | ||||||
|  | 	case 5: return prerender(0); | ||||||
|  | 	case 6: return present(6, 0); | ||||||
|  | 	case 7: return prerender(1); | ||||||
|  | 	} | ||||||
|  | 	Unexpected("Counter value in VideoTrack::Shared::prepareState."); | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int SharedState::counter() const { | ||||||
|  | 	return _counter.load(std::memory_order_acquire); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool SharedState::initialized() const { | ||||||
|  | 	return (counter() != kCounterUninitialized); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | not_null<Frame*> SharedState::getFrame(int index) { | ||||||
|  | 	Expects(index >= 0 && index < kFramesCount); | ||||||
|  | 
 | ||||||
|  | 	return &_frames[index]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | not_null<const Frame*> SharedState::getFrame(int index) const { | ||||||
|  | 	Expects(index >= 0 && index < kFramesCount); | ||||||
|  | 
 | ||||||
|  | 	return &_frames[index]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Information SharedState::information() const { | ||||||
|  | 	auto result = Information(); | ||||||
|  | 	result.frameRate = _scene.frameRate(); | ||||||
|  | 	result.size = QSize(_scene.width(), _scene.height()); | ||||||
|  | 	result.framesCount = _scene.endFrame() - _scene.startFrame(); | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | not_null<Frame*> SharedState::frameForPaint() { | ||||||
|  | 	const auto result = getFrame(counter() / 2); | ||||||
|  | 	Assert(!result->original.isNull()); | ||||||
|  | 	Assert(result->position != kTimeUnknown); | ||||||
|  | 	Assert(result->displayed != kTimeUnknown); | ||||||
|  | 
 | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | crl::time SharedState::nextFrameDisplayTime() const { | ||||||
|  | 	const auto frameDisplayTime = [&](int counter) { | ||||||
|  | 		const auto next = (counter + 1) % (2 * kFramesCount); | ||||||
|  | 		const auto index = next / 2; | ||||||
|  | 		const auto frame = getFrame(index); | ||||||
|  | 		Assert(IsRendered(frame)); | ||||||
|  | 		Assert(frame->display != kTimeUnknown); | ||||||
|  | 
 | ||||||
|  | 		return frame->display; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	switch (counter()) { | ||||||
|  | 	case 0: return kTimeUnknown; | ||||||
|  | 	case 1: return frameDisplayTime(1); | ||||||
|  | 	case 2: return kTimeUnknown; | ||||||
|  | 	case 3: return frameDisplayTime(3); | ||||||
|  | 	case 4: return kTimeUnknown; | ||||||
|  | 	case 5: return frameDisplayTime(5); | ||||||
|  | 	case 6: return kTimeUnknown; | ||||||
|  | 	case 7: return frameDisplayTime(7); | ||||||
|  | 	} | ||||||
|  | 	Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime."); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | crl::time SharedState::markFrameDisplayed(crl::time now) { | ||||||
|  | 	const auto mark = [&](int counter) { | ||||||
|  | 		const auto next = (counter + 1) % (2 * kFramesCount); | ||||||
|  | 		const auto index = next / 2; | ||||||
|  | 		const auto frame = getFrame(index); | ||||||
|  | 		Assert(frame->position != kTimeUnknown); | ||||||
|  | 		Assert(frame->displayed == kTimeUnknown); | ||||||
|  | 
 | ||||||
|  | 		frame->displayed = now; | ||||||
|  | 		return frame->position; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	switch (counter()) { | ||||||
|  | 	case 0: Unexpected("Value 0 in SharedState::markFrameDisplayed."); | ||||||
|  | 	case 1: return mark(1); | ||||||
|  | 	case 2: Unexpected("Value 2 in SharedState::markFrameDisplayed."); | ||||||
|  | 	case 3: return mark(3); | ||||||
|  | 	case 4: Unexpected("Value 4 in SharedState::markFrameDisplayed."); | ||||||
|  | 	case 5: return mark(5); | ||||||
|  | 	case 6: Unexpected("Value 6 in SharedState::markFrameDisplayed."); | ||||||
|  | 	case 7: return mark(7); | ||||||
|  | 	} | ||||||
|  | 	Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed."); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | crl::time SharedState::markFrameShown() { | ||||||
|  | 	const auto jump = [&](int counter) { | ||||||
|  | 		const auto next = (counter + 1) % (2 * kFramesCount); | ||||||
|  | 		const auto index = next / 2; | ||||||
|  | 		const auto frame = getFrame(index); | ||||||
|  | 		Assert(frame->position != kTimeUnknown); | ||||||
|  | 		if (frame->displayed == kTimeUnknown) { | ||||||
|  | 			return kTimeUnknown; | ||||||
|  | 		} | ||||||
|  | 		_counter.store( | ||||||
|  | 			next, | ||||||
|  | 			std::memory_order_release); | ||||||
|  | 		return frame->position; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	switch (counter()) { | ||||||
|  | 	case 0: return kTimeUnknown; | ||||||
|  | 	case 1: return jump(1); | ||||||
|  | 	case 2: return kTimeUnknown; | ||||||
|  | 	case 3: return jump(3); | ||||||
|  | 	case 4: return kTimeUnknown; | ||||||
|  | 	case 5: return jump(5); | ||||||
|  | 	case 6: return kTimeUnknown; | ||||||
|  | 	case 7: return jump(7); | ||||||
|  | 	} | ||||||
|  | 	Unexpected("Counter value in Lottie::SharedState::markFrameShown."); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<FrameRenderer> FrameRenderer::Instance() { | ||||||
|  | 	if (auto result = GlobalInstance.lock()) { | ||||||
|  | 		return result; | ||||||
|  | 	} | ||||||
|  | 	auto result = std::make_shared<FrameRenderer>(); | ||||||
|  | 	GlobalInstance = result; | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FrameRenderer::append(std::unique_ptr<SharedState> entry) { | ||||||
|  | 	_wrapped.with([entry = std::move(entry)]( | ||||||
|  | 			FrameRendererObject &unwrapped) mutable { | ||||||
|  | 		unwrapped.append(std::move(entry)); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FrameRenderer::frameShown(not_null<SharedState*> entry) { | ||||||
|  | 	_wrapped.with([=](FrameRendererObject &unwrapped) { | ||||||
|  | 		unwrapped.frameShown(entry); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FrameRenderer::updateFrameRequest( | ||||||
|  | 		not_null<SharedState*> entry, | ||||||
|  | 		const FrameRequest &request) { | ||||||
|  | 	_wrapped.with([=](FrameRendererObject &unwrapped) { | ||||||
|  | 		unwrapped.updateFrameRequest(entry, request); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FrameRenderer::remove(not_null<SharedState*> entry) { | ||||||
|  | 	_wrapped.with([=](FrameRendererObject &unwrapped) { | ||||||
|  | 		unwrapped.remove(entry); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| } // namespace Lottie
 | } // namespace Lottie
 | ||||||
|  |  | ||||||
|  | @ -7,78 +7,99 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| */ | */ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <QHash> | #include "base/basic_types.h" | ||||||
| #include <QThread> | #include "base/weak_ptr.h" | ||||||
| #include <QMutex> | 
 | ||||||
| #include <QWaitCondition> | #include "lottie/lottie_common.h" | ||||||
|  | 
 | ||||||
|  | #include <QtBodymovin/private/bmscene_p.h> | ||||||
|  | #include <QImage> | ||||||
|  | 
 | ||||||
|  | #include <crl/crl_time.h> | ||||||
|  | #include <crl/crl_object_on_queue.h> | ||||||
|  | 
 | ||||||
|  | #include <limits> | ||||||
| 
 | 
 | ||||||
| class BMBase; | class BMBase; | ||||||
| class QImage; | class QImage; | ||||||
| 
 | 
 | ||||||
| namespace Lottie { | namespace Lottie { | ||||||
| 
 | 
 | ||||||
|  | constexpr auto kTimeUnknown = std::numeric_limits<crl::time>::min(); | ||||||
|  | 
 | ||||||
| class Animation; | class Animation; | ||||||
| //
 | 
 | ||||||
| //class FrameRenderer : public QThread {
 | struct Frame { | ||||||
| //    Q_OBJECT
 | 	QImage original; | ||||||
| //
 | 	crl::time position = kTimeUnknown; | ||||||
| //    struct Entry {
 | 	crl::time displayed = kTimeUnknown; | ||||||
| //        Animation* animator = nullptr;
 | 	crl::time display = kTimeUnknown; | ||||||
| //        BMBase *bmTreeBlueprint = nullptr;
 | 
 | ||||||
| //        int startFrame = 0;
 | 	FrameRequest request = FrameRequest::NonStrict(); | ||||||
| //        int endFrame = 0;
 | 	QImage prepared; | ||||||
| //        int currentFrame = 0;
 | }; | ||||||
| //        int animDir = 1;
 | 
 | ||||||
| //        QHash<int, BMBase*> frameCache;
 | QImage PrepareFrameByRequest( | ||||||
| //    };
 | 	not_null<Frame*> frame, | ||||||
| //
 | 	bool useExistingPrepared); | ||||||
| //public:
 | 
 | ||||||
| //    ~FrameRenderer();
 | class SharedState { | ||||||
| //
 | public: | ||||||
| //	FrameRenderer(const FrameRenderer &other) = delete;
 | 	explicit SharedState(const QJsonObject &definition); | ||||||
| //    void operator=(const FrameRenderer &other) = delete;
 | 
 | ||||||
| //
 | 	void start(not_null<Animation*> owner, crl::time now); | ||||||
| //    static FrameRenderer *instance();
 | 
 | ||||||
| //    static void deleteInstance();
 | 	[[nodiscard]] Information information() const; | ||||||
| //
 | 	[[nodiscard]] bool initialized() const; | ||||||
| //    BMBase *getFrame(Animation *animator, int frameNumber);
 | 
 | ||||||
| //
 | 	[[nodiscard]] not_null<Frame*> frameForPaint(); | ||||||
| //signals:
 | 	[[nodiscard]] crl::time nextFrameDisplayTime() const; | ||||||
| //    void frameReady(Animation *animator, int frameNumber);
 | 	crl::time markFrameDisplayed(crl::time now); | ||||||
| //
 | 	crl::time markFrameShown(); | ||||||
| //public slots:
 | 
 | ||||||
| //    void registerAnimator(Animation *animator);
 | 	void renderFrame(QImage &image, const FrameRequest &request, int index); | ||||||
| //    void deregisterAnimator(Animation *animator);
 | 	[[nodiscard]] bool renderNextFrame(const FrameRequest &request); | ||||||
| //
 | 
 | ||||||
| //    bool gotoFrame(Animation *animator, int frame);
 | private: | ||||||
| //
 | 	void init(QImage cover); | ||||||
| //    void frameRendered(Animation *animator, int frameNumber);
 | 	void renderNextFrame( | ||||||
| //
 | 		not_null<Frame*> frame, | ||||||
| //protected:
 | 		const FrameRequest &request); | ||||||
| //    void run() override;
 | 	[[nodiscard]] not_null<Frame*> getFrame(int index); | ||||||
| //
 | 	[[nodiscard]] not_null<const Frame*> getFrame(int index) const; | ||||||
| //    int parse(BMBase* rootElement, const QByteArray &jsonSource);
 | 	[[nodiscard]] int counter() const; | ||||||
| //
 | 
 | ||||||
| //    void prerender(Entry *animEntry);
 | 	BMScene _scene; | ||||||
| //
 | 
 | ||||||
| //protected:
 | 	static constexpr auto kCounterUninitialized = -1; | ||||||
| //    QHash<Animation*, Entry*> _animData;
 | 	std::atomic<int> _counter = kCounterUninitialized; | ||||||
| //    int _cacheSize = 2;
 | 
 | ||||||
| //    int _currentFrame = 0;
 | 	static constexpr auto kFramesCount = 4; | ||||||
| //
 | 	std::array<Frame, kFramesCount> _frames; | ||||||
| //    Animation *_animation = nullptr;
 | 
 | ||||||
| //    QHash<int, QImage*> _frameCache;
 | 	base::weak_ptr<Animation> _owner; | ||||||
| //
 | 	crl::time _started = kTimeUnknown; | ||||||
| //private:
 | 	int _frameIndex = 0; | ||||||
| //	FrameRenderer();
 | 
 | ||||||
| //
 | }; | ||||||
| //    void pruneFrameCache(Entry* e);
 | 
 | ||||||
| //
 | class FrameRendererObject; | ||||||
| //private:
 | 
 | ||||||
| //    static FrameRenderer *_rendererInstance;
 | class FrameRenderer final { | ||||||
| //
 | public: | ||||||
| //    QMutex _mutex;
 | 	static std::shared_ptr<FrameRenderer> Instance(); | ||||||
| //    QWaitCondition _waitCondition;
 | 
 | ||||||
| //};
 | 	void append(std::unique_ptr<SharedState> entry); | ||||||
|  | 	void updateFrameRequest( | ||||||
|  | 		not_null<SharedState*> entry, | ||||||
|  | 		const FrameRequest &request); | ||||||
|  | 	void frameShown(not_null<SharedState*> entry); | ||||||
|  | 	void remove(not_null<SharedState*> state); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	using Implementation = FrameRendererObject; | ||||||
|  | 	crl::object_on_queue<Implementation> _wrapped; | ||||||
|  | 
 | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| } // namespace Lottie
 | } // namespace Lottie
 | ||||||
|  |  | ||||||
|  | @ -192,13 +192,9 @@ struct OverlayWidget::Streamed { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| struct OverlayWidget::LottieFile { | struct OverlayWidget::LottieFile { | ||||||
| 	template <typename Callback> | 	LottieFile(std::unique_ptr<Lottie::Animation> data); | ||||||
| 	LottieFile( |  | ||||||
| 		std::unique_ptr<Lottie::Animation> data, |  | ||||||
| 		Callback &&animationCallback); |  | ||||||
| 
 | 
 | ||||||
| 	std::unique_ptr<Lottie::Animation> data; | 	std::unique_ptr<Lottie::Animation> data; | ||||||
| 	Ui::Animations::Basic animation; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| template <typename Callback> | template <typename Callback> | ||||||
|  | @ -215,12 +211,9 @@ OverlayWidget::Streamed::Streamed( | ||||||
| 	st::mediaviewStreamingRadial) { | 	st::mediaviewStreamingRadial) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template <typename Callback> |  | ||||||
| OverlayWidget::LottieFile::LottieFile( | OverlayWidget::LottieFile::LottieFile( | ||||||
| 	std::unique_ptr<Lottie::Animation> data, | 	std::unique_ptr<Lottie::Animation> data) | ||||||
| 	Callback &&animationCallback) | : data(std::move(data)) { | ||||||
| : data(std::move(data)) |  | ||||||
| , animation(std::forward<Callback>(animationCallback)) { |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| OverlayWidget::OverlayWidget() | OverlayWidget::OverlayWidget() | ||||||
|  | @ -1851,9 +1844,11 @@ void OverlayWidget::displayDocument(DocumentData *doc, HistoryItem *item) { | ||||||
| 						_current = PrepareStaticImage(path); | 						_current = PrepareStaticImage(path); | ||||||
| 					} else if (auto lottie = Lottie::FromFile(path)) { | 					} else if (auto lottie = Lottie::FromFile(path)) { | ||||||
| 						_lottie = std::make_unique<LottieFile>( | 						_lottie = std::make_unique<LottieFile>( | ||||||
| 							std::move(lottie), | 							std::move(lottie)); | ||||||
| 							[=] { update(); }); | 						_lottie->data->updates( | ||||||
| 						_lottie->animation.start(); | 						) | rpl::start_with_next([=] { | ||||||
|  | 							update(); | ||||||
|  | 						}, lifetime()); | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 				location.accessDisable(); | 				location.accessDisable(); | ||||||
|  | @ -2924,8 +2919,9 @@ void OverlayWidget::paintThemePreview(Painter &p, QRect clip) { | ||||||
| void OverlayWidget::paintLottieFrame(Painter &p, QRect clip) { | void OverlayWidget::paintLottieFrame(Painter &p, QRect clip) { | ||||||
| 	Expects(_lottie != nullptr); | 	Expects(_lottie != nullptr); | ||||||
| 
 | 
 | ||||||
| 	const auto frame = _lottie->data->frame(crl::now()); | 	if (_lottie->data->ready()) { | ||||||
| 	if (!frame.isNull()) { | 		_lottie->data->markFrameShown(); | ||||||
|  | 		const auto frame = _lottie->data->frame(Lottie::FrameRequest()); | ||||||
| 		const auto x = (width() - frame.width()) / 2; | 		const auto x = (width() - frame.width()) / 2; | ||||||
| 		const auto y = (height() - frame.height()) / 2; | 		const auto y = (height() - frame.height()) / 2; | ||||||
| 		const auto background = _lottieDark ? Qt::black : Qt::white; | 		const auto background = _lottieDark ? Qt::black : Qt::white; | ||||||
|  |  | ||||||
|  | @ -476,15 +476,24 @@ void prepareRound( | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| QImage prepareColored(style::color add, QImage image) { | QImage prepareColored(style::color add, QImage image) { | ||||||
| 	auto format = image.format(); | 	return prepareColored(add->c, std::move(image)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | QImage prepareColored(QColor add, QImage image) { | ||||||
|  | 	const auto format = image.format(); | ||||||
| 	if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32_Premultiplied) { | 	if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32_Premultiplied) { | ||||||
| 		image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied); | 		image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (auto pix = image.bits()) { | 	if (const auto pix = image.bits()) { | ||||||
| 		int ca = int(add->c.alphaF() * 0xFF), cr = int(add->c.redF() * 0xFF), cg = int(add->c.greenF() * 0xFF), cb = int(add->c.blueF() * 0xFF); | 		const auto ca = int(add.alphaF() * 0xFF); | ||||||
| 		const int w = image.width(), h = image.height(), size = w * h * 4; | 		const auto cr = int(add.redF() * 0xFF); | ||||||
| 		for (auto i = index_type(); i < size; i += 4) { | 		const auto cg = int(add.greenF() * 0xFF); | ||||||
|  | 		const auto cb = int(add .blueF() * 0xFF); | ||||||
|  | 		const auto w = image.width(); | ||||||
|  | 		const auto h = image.height(); | ||||||
|  | 		const auto size = w * h * 4; | ||||||
|  | 		for (auto i = index_type(); i != size; i += 4) { | ||||||
| 			int b = pix[i], g = pix[i + 1], r = pix[i + 2], a = pix[i + 3], aca = a * ca; | 			int b = pix[i], g = pix[i + 1], r = pix[i + 2], a = pix[i + 3], aca = a * ca; | ||||||
| 			pix[i + 0] = uchar(b + ((aca * (cb - b)) >> 16)); | 			pix[i + 0] = uchar(b + ((aca * (cb - b)) >> 16)); | ||||||
| 			pix[i + 1] = uchar(g + ((aca * (cg - g)) >> 16)); | 			pix[i + 1] = uchar(g + ((aca * (cg - g)) >> 16)); | ||||||
|  |  | ||||||
|  | @ -41,6 +41,7 @@ void prepareRound( | ||||||
| 	QRect target = QRect()); | 	QRect target = QRect()); | ||||||
| void prepareCircle(QImage &image); | void prepareCircle(QImage &image); | ||||||
| QImage prepareColored(style::color add, QImage image); | QImage prepareColored(style::color add, QImage image); | ||||||
|  | QImage prepareColored(QColor add, QImage image); | ||||||
| QImage prepareOpaque(QImage image); | QImage prepareOpaque(QImage image); | ||||||
| 
 | 
 | ||||||
| enum class Option { | enum class Option { | ||||||
|  |  | ||||||
|  | @ -1 +1 @@ | ||||||
| Subproject commit ff75b08c3adabaa33f7f879e12119de8ab1c2153 | Subproject commit 26d3e9ff5f354a20e72b90e2a3d4d57bd73baa8c | ||||||
|  | @ -58,13 +58,12 @@ class BODYMOVIN_EXPORT BMScene : public BMBase | ||||||
| public: | public: | ||||||
|     BMScene(); |     BMScene(); | ||||||
|     BMScene(const BMScene &other) = delete; |     BMScene(const BMScene &other) = delete; | ||||||
| 	BMScene &operator=(const BMScene &other) = delete; |     BMScene &operator=(const BMScene &other) = delete; | ||||||
|  |     explicit BMScene(const QJsonObject &definition); | ||||||
|     virtual ~BMScene(); |     virtual ~BMScene(); | ||||||
| 
 | 
 | ||||||
|     BMBase *clone() const override; |     BMBase *clone() const override; | ||||||
| 
 | 
 | ||||||
| 	void parse(const QJsonObject &definition) override; |  | ||||||
| 
 |  | ||||||
| 	void updateProperties(int frame) override; | 	void updateProperties(int frame) override; | ||||||
| 	void render(LottieRenderer &renderer, int frame) const override; | 	void render(LottieRenderer &renderer, int frame) const override; | ||||||
| 
 | 
 | ||||||
|  | @ -78,6 +77,7 @@ protected: | ||||||
| 	BMScene *resolveTopRoot() const override; | 	BMScene *resolveTopRoot() const override; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  | 	void parse(const QJsonObject &definition); | ||||||
| 	void resolveAllAssets(); | 	void resolveAllAssets(); | ||||||
| 
 | 
 | ||||||
| 	std::vector<std::unique_ptr<BMAsset>> _assets; | 	std::vector<std::unique_ptr<BMAsset>> _assets; | ||||||
|  |  | ||||||
|  | @ -63,6 +63,7 @@ | ||||||
|       # interface for tdesktop |       # interface for tdesktop | ||||||
|       '<(src_loc)/lottie/lottie_animation.cpp', |       '<(src_loc)/lottie/lottie_animation.cpp', | ||||||
|       '<(src_loc)/lottie/lottie_animation.h', |       '<(src_loc)/lottie/lottie_animation.h', | ||||||
|  |       '<(src_loc)/lottie/lottie_common.h', | ||||||
|       '<(src_loc)/lottie/lottie_frame_renderer.cpp', |       '<(src_loc)/lottie/lottie_frame_renderer.cpp', | ||||||
|       '<(src_loc)/lottie/lottie_frame_renderer.h', |       '<(src_loc)/lottie/lottie_frame_renderer.h', | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue