lottie_icon.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "lottie/lottie_icon.h"
  8. #include "lottie/lottie_common.h"
  9. #include "ui/image/image_prepare.h"
  10. #include "ui/style/style_core.h"
  11. #include <QtGui/QPainter>
  12. #include <crl/crl_async.h>
  13. #include <crl/crl_semaphore.h>
  14. #include <crl/crl_on_main.h>
  15. #include <rlottie.h>
  16. #if __has_include(<glib.h>)
  17. #include <glib.h>
  18. #endif
  19. namespace Lottie {
  20. namespace {
  21. [[nodiscard]] std::unique_ptr<rlottie::Animation> CreateFromContent(
  22. const QByteArray &content,
  23. QColor replacement) {
  24. auto string = ReadUtf8(Images::UnpackGzip(content));
  25. #ifndef LOTTIE_USE_PACKAGED_RLOTTIE
  26. auto list = std::vector<std::pair<std::uint32_t, std::uint32_t>>();
  27. if (replacement != Qt::white) {
  28. const auto value = (uint32_t(replacement.red()) << 16)
  29. | (uint32_t(replacement.green() << 8))
  30. | (uint32_t(replacement.blue()));
  31. list.push_back({ 0xFFFFFFU, value });
  32. }
  33. auto result = rlottie::Animation::loadFromData(
  34. std::move(string),
  35. std::string(),
  36. std::string(),
  37. false,
  38. std::move(list));
  39. #else
  40. #if __has_include(<glib.h>)
  41. [[maybe_unused]] static auto logged = [&] {
  42. g_warning(
  43. "rlottie is incompatible, expect animations with color issues.");
  44. return true;
  45. }();
  46. #endif
  47. auto result = rlottie::Animation::loadFromData(
  48. std::move(string),
  49. std::string(),
  50. std::string(),
  51. false);
  52. #endif
  53. return result;
  54. }
  55. [[nodiscard]] QColor RealRenderedColor(QColor color) {
  56. #ifndef LOTTIE_USE_PACKAGED_RLOTTIE
  57. return QColor(color.red(), color.green(), color.blue(), 255);
  58. #else
  59. return Qt::white;
  60. #endif
  61. }
  62. [[nodiscard]] QByteArray ReadIconContent(
  63. const QString &name,
  64. const QByteArray &json,
  65. const QString &path) {
  66. return !json.isEmpty()
  67. ? json
  68. : !path.isEmpty()
  69. ? ReadContent(json, path)
  70. : Images::UnpackGzip(
  71. ReadContent({}, u":/animations/"_q + name + u".tgs"_q));
  72. }
  73. } // namespace
  74. struct Icon::Frame {
  75. int index = 0;
  76. QImage resizedImage;
  77. QImage renderedImage;
  78. QImage colorizedImage;
  79. QColor renderedColor;
  80. QColor colorizedColor;
  81. };
  82. class Icon::Inner final : public std::enable_shared_from_this<Inner> {
  83. public:
  84. Inner(int frameIndex, base::weak_ptr<Icon> weak, bool limitFps);
  85. void prepareFromAsync(
  86. const QString &name,
  87. const QString &path,
  88. const QByteArray &json,
  89. QSize sizeOverride,
  90. QColor color);
  91. void waitTillPrepared() const;
  92. [[nodiscard]] bool valid() const;
  93. [[nodiscard]] QSize size() const;
  94. [[nodiscard]] int framesCount() const;
  95. [[nodiscard]] Frame &frame();
  96. [[nodiscard]] const Frame &frame() const;
  97. [[nodiscard]] crl::time animationDuration(
  98. int frameFrom,
  99. int frameTo) const;
  100. void moveToFrame(int frame, QColor color, QSize updatedDesiredSize);
  101. private:
  102. enum class PreloadState {
  103. None,
  104. Preloading,
  105. Ready,
  106. };
  107. // Called from crl::async.
  108. void renderPreloadFrame(const QColor &color);
  109. const bool _limitFps = false;
  110. std::unique_ptr<rlottie::Animation> _rlottie;
  111. Frame _current;
  112. QSize _desiredSize;
  113. std::atomic<PreloadState> _preloadState = PreloadState::None;
  114. Frame _preloaded; // Changed on main or async depending on _preloadState.
  115. QSize _preloadImageSize;
  116. base::weak_ptr<Icon> _weak;
  117. int _framesCount = 0;
  118. int _frameMultiplier = 1;
  119. mutable crl::semaphore _semaphore;
  120. mutable bool _ready = false;
  121. };
  122. Icon::Inner::Inner(int frameIndex, base::weak_ptr<Icon> weak, bool limitFps)
  123. : _limitFps(limitFps)
  124. , _current { .index = frameIndex }
  125. , _weak(weak) {
  126. }
  127. void Icon::Inner::prepareFromAsync(
  128. const QString &name,
  129. const QString &path,
  130. const QByteArray &json,
  131. QSize sizeOverride,
  132. QColor color) {
  133. const auto guard = gsl::finally([&] { _semaphore.release(); });
  134. if (!_weak) {
  135. return;
  136. }
  137. auto rlottie = CreateFromContent(
  138. ReadIconContent(name, json, path),
  139. color);
  140. if (!rlottie || !_weak) {
  141. return;
  142. }
  143. auto width = size_t();
  144. auto height = size_t();
  145. rlottie->size(width, height);
  146. if (_limitFps && rlottie->frameRate() == 60) {
  147. _frameMultiplier = 2;
  148. }
  149. _framesCount = (rlottie->totalFrame() + _frameMultiplier - 1)
  150. / _frameMultiplier;
  151. if (!_framesCount || !width || !height) {
  152. return;
  153. }
  154. _rlottie = std::move(rlottie);
  155. while (_current.index < 0) {
  156. _current.index += _framesCount;
  157. }
  158. const auto size = sizeOverride.isEmpty()
  159. ? style::ConvertScale(QSize{ int(width), int(height) })
  160. : sizeOverride;
  161. auto image = CreateFrameStorage(size * style::DevicePixelRatio());
  162. image.fill(Qt::transparent);
  163. auto surface = rlottie::Surface(
  164. reinterpret_cast<uint32_t*>(image.bits()),
  165. image.width(),
  166. image.height(),
  167. image.bytesPerLine());
  168. _rlottie->renderSync(
  169. _current.index * _frameMultiplier,
  170. std::move(surface));
  171. _current.renderedColor = RealRenderedColor(color);
  172. _current.renderedImage = std::move(image);
  173. _desiredSize = size;
  174. }
  175. void Icon::Inner::waitTillPrepared() const {
  176. if (!_ready) {
  177. _semaphore.acquire();
  178. _ready = true;
  179. }
  180. }
  181. bool Icon::Inner::valid() const {
  182. waitTillPrepared();
  183. return (_rlottie != nullptr);
  184. }
  185. QSize Icon::Inner::size() const {
  186. waitTillPrepared();
  187. return _desiredSize;
  188. }
  189. int Icon::Inner::framesCount() const {
  190. waitTillPrepared();
  191. return _framesCount;
  192. }
  193. Icon::Frame &Icon::Inner::frame() {
  194. waitTillPrepared();
  195. return _current;
  196. }
  197. const Icon::Frame &Icon::Inner::frame() const {
  198. waitTillPrepared();
  199. return _current;
  200. }
  201. crl::time Icon::Inner::animationDuration(int frameFrom, int frameTo) const {
  202. waitTillPrepared();
  203. const auto rate = _rlottie
  204. ? (_rlottie->frameRate() / _frameMultiplier)
  205. : 0.;
  206. const auto frames = std::abs(frameTo - frameFrom);
  207. return (rate >= 1.)
  208. ? crl::time(base::SafeRound(frames / rate * 1000.))
  209. : 0;
  210. }
  211. void Icon::Inner::moveToFrame(
  212. int frame,
  213. QColor color,
  214. QSize updatedDesiredSize) {
  215. waitTillPrepared();
  216. if (frame < 0) {
  217. frame += _framesCount;
  218. }
  219. const auto state = _preloadState.load();
  220. const auto shown = _current.index;
  221. if (!updatedDesiredSize.isEmpty()) {
  222. _desiredSize = updatedDesiredSize;
  223. }
  224. const auto desiredImageSize = _desiredSize * style::DevicePixelRatio();
  225. if (!_rlottie
  226. || state == PreloadState::Preloading
  227. || (shown == frame
  228. && (_current.renderedImage.size() == desiredImageSize))) {
  229. return;
  230. } else if (state == PreloadState::Ready) {
  231. if (_preloaded.index == frame
  232. && (shown != frame
  233. || _preloaded.renderedImage.size() == desiredImageSize)) {
  234. std::swap(_current, _preloaded);
  235. if (_current.renderedImage.size() == desiredImageSize) {
  236. return;
  237. }
  238. } else if ((shown < _preloaded.index && _preloaded.index < frame)
  239. || (shown > _preloaded.index && _preloaded.index > frame)) {
  240. std::swap(_current, _preloaded);
  241. }
  242. }
  243. _preloadImageSize = desiredImageSize;
  244. _preloaded.index = frame;
  245. _preloadState = PreloadState::Preloading;
  246. crl::async([
  247. guard = shared_from_this(),
  248. color = RealRenderedColor(color)
  249. ] {
  250. guard->renderPreloadFrame(color);
  251. });
  252. }
  253. void Icon::Inner::renderPreloadFrame(const QColor &color) {
  254. if (!_weak) {
  255. return;
  256. }
  257. auto &image = _preloaded.renderedImage;
  258. const auto &size = _preloadImageSize;
  259. if (!GoodStorageForFrame(image, size)) {
  260. image = GoodStorageForFrame(_preloaded.resizedImage, size)
  261. ? base::take(_preloaded.resizedImage)
  262. : CreateFrameStorage(size);
  263. }
  264. image.fill(Qt::black);
  265. auto surface = rlottie::Surface(
  266. reinterpret_cast<uint32_t*>(image.bits()),
  267. image.width(),
  268. image.height(),
  269. image.bytesPerLine());
  270. _rlottie->renderSync(
  271. _preloaded.index * _frameMultiplier,
  272. std::move(surface));
  273. _preloaded.renderedColor = color;
  274. _preloaded.resizedImage = QImage();
  275. _preloadState = PreloadState::Ready;
  276. crl::on_main(_weak, [=] {
  277. _weak->frameJumpFinished();
  278. });
  279. }
  280. Icon::Icon(IconDescriptor &&descriptor)
  281. : _inner(std::make_shared<Inner>(
  282. descriptor.frame,
  283. base::make_weak(this),
  284. descriptor.limitFps))
  285. , _color(descriptor.color)
  286. , _animationFrameTo(descriptor.frame) {
  287. crl::async([
  288. inner = _inner,
  289. name = descriptor.name,
  290. path = descriptor.path,
  291. bytes = descriptor.json,
  292. sizeOverride = descriptor.sizeOverride,
  293. color = (_color ? (*_color)->c : Qt::white)
  294. ] {
  295. inner->prepareFromAsync(name, path, bytes, sizeOverride, color);
  296. });
  297. }
  298. void Icon::wait() const {
  299. _inner->waitTillPrepared();
  300. }
  301. bool Icon::valid() const {
  302. return _inner->valid();
  303. }
  304. int Icon::frameIndex() const {
  305. preloadNextFrame();
  306. return _inner->frame().index;
  307. }
  308. int Icon::framesCount() const {
  309. return _inner->framesCount();
  310. }
  311. QImage Icon::frame() const {
  312. return frame(QSize(), nullptr).image;
  313. }
  314. Icon::ResizedFrame Icon::frame(
  315. QSize desiredSize,
  316. Fn<void()> updateWithPerfect) const {
  317. preloadNextFrame(desiredSize);
  318. const auto desired = size() * style::DevicePixelRatio();
  319. auto &frame = _inner->frame();
  320. if (frame.renderedImage.isNull()) {
  321. return { frame.renderedImage };
  322. } else if (!_color) {
  323. if (frame.renderedImage.size() == desired) {
  324. return { frame.renderedImage };
  325. } else if (frame.resizedImage.size() != desired) {
  326. frame.resizedImage = frame.renderedImage.scaled(
  327. desired,
  328. Qt::IgnoreAspectRatio,
  329. Qt::SmoothTransformation);
  330. }
  331. if (updateWithPerfect) {
  332. _repaint = std::move(updateWithPerfect);
  333. }
  334. return { frame.resizedImage, true };
  335. }
  336. Assert(frame.renderedImage.size() == desired);
  337. const auto color = (*_color)->c;
  338. if (color == frame.renderedColor) {
  339. return { frame.renderedImage };
  340. } else if (!frame.colorizedImage.isNull()
  341. && color == frame.colorizedColor) {
  342. return { frame.colorizedImage };
  343. }
  344. if (frame.colorizedImage.isNull()) {
  345. frame.colorizedImage = CreateFrameStorage(desired);
  346. }
  347. frame.colorizedColor = color;
  348. style::colorizeImage(frame.renderedImage, color, &frame.colorizedImage);
  349. return { frame.colorizedImage };
  350. }
  351. int Icon::width() const {
  352. return size().width();
  353. }
  354. int Icon::height() const {
  355. return size().height();
  356. }
  357. QSize Icon::size() const {
  358. return _inner->size();
  359. }
  360. void Icon::paint(
  361. QPainter &p,
  362. int x,
  363. int y,
  364. std::optional<QColor> colorOverride) {
  365. preloadNextFrame();
  366. auto &frame = _inner->frame();
  367. const auto color = colorOverride.value_or(
  368. _color ? (*_color)->c : Qt::white);
  369. if (frame.renderedImage.isNull() || color.alpha() == 0) {
  370. return;
  371. }
  372. const auto rect = QRect{ QPoint(x, y), size() };
  373. if (color == frame.renderedColor || !_color) {
  374. p.drawImage(rect, frame.renderedImage);
  375. } else if (color.alphaF() < 1.
  376. && (QColor(color.red(), color.green(), color.blue())
  377. == frame.renderedColor)) {
  378. const auto o = p.opacity();
  379. p.setOpacity(o * color.alphaF());
  380. p.drawImage(rect, frame.renderedImage);
  381. p.setOpacity(o);
  382. } else if (!frame.colorizedImage.isNull()
  383. && color == frame.colorizedColor) {
  384. p.drawImage(rect, frame.colorizedImage);
  385. } else if (!frame.colorizedImage.isNull()
  386. && color.alphaF() < 1.
  387. && (QColor(color.red(), color.green(), color.blue())
  388. == frame.colorizedColor)) {
  389. const auto o = p.opacity();
  390. p.setOpacity(o * color.alphaF());
  391. p.drawImage(rect, frame.colorizedImage);
  392. p.setOpacity(o);
  393. } else {
  394. if (frame.colorizedImage.isNull()) {
  395. frame.colorizedImage = CreateFrameStorage(
  396. frame.renderedImage.size());
  397. }
  398. frame.colorizedColor = color;
  399. style::colorizeImage(
  400. frame.renderedImage,
  401. color,
  402. &frame.colorizedImage);
  403. p.drawImage(rect, frame.colorizedImage);
  404. }
  405. }
  406. void Icon::paintInCenter(
  407. QPainter &p,
  408. QRect rect,
  409. std::optional<QColor> colorOverride) {
  410. const auto my = size();
  411. paint(
  412. p,
  413. rect.x() + (rect.width() - my.width()) / 2,
  414. rect.y() + (rect.height() - my.height()) / 2,
  415. colorOverride);
  416. }
  417. void Icon::animate(
  418. Fn<void()> update,
  419. int frameFrom,
  420. int frameTo,
  421. std::optional<crl::time> duration) {
  422. jumpTo(frameFrom, std::move(update));
  423. if (frameFrom != frameTo) {
  424. _animationFrameTo = frameTo;
  425. _animation.start(
  426. [=] { preloadNextFrame(); if (_repaint) _repaint(); },
  427. frameFrom,
  428. frameTo,
  429. (duration
  430. ? *duration
  431. : _inner->animationDuration(frameFrom, frameTo)));
  432. }
  433. }
  434. void Icon::jumpTo(int frame, Fn<void()> update) {
  435. _animation.stop();
  436. _repaint = std::move(update);
  437. _animationFrameTo = frame;
  438. preloadNextFrame();
  439. }
  440. void Icon::frameJumpFinished() {
  441. if (_repaint && !animating()) {
  442. _repaint();
  443. _repaint = nullptr;
  444. }
  445. }
  446. int Icon::wantedFrameIndex() const {
  447. return int(base::SafeRound(_animation.value(_animationFrameTo)));
  448. }
  449. void Icon::preloadNextFrame(QSize updatedDesiredSize) const {
  450. _inner->moveToFrame(
  451. wantedFrameIndex(),
  452. _color ? (*_color)->c : Qt::white,
  453. updatedDesiredSize);
  454. if (_animationFrameTo < 0) {
  455. _animationFrameTo += framesCount();
  456. }
  457. }
  458. bool Icon::animating() const {
  459. return _animation.animating();
  460. }
  461. std::unique_ptr<Icon> MakeIcon(IconDescriptor &&descriptor) {
  462. return std::make_unique<Icon>(std::move(descriptor));
  463. }
  464. } // namespace Lottie