spoiler_mess.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  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 "ui/effects/spoiler_mess.h"
  8. #include "ui/effects/animations.h"
  9. #include "ui/image/image_prepare.h"
  10. #include "ui/painter.h"
  11. #include "ui/integration.h"
  12. #include "base/random.h"
  13. #include "base/flags.h"
  14. #include <QtCore/QBuffer>
  15. #include <QtCore/QFile>
  16. #include <QtCore/QDir>
  17. #include <crl/crl_async.h>
  18. #include <xxhash.h>
  19. #include <mutex>
  20. #include <condition_variable>
  21. namespace Ui {
  22. namespace {
  23. constexpr auto kVersion = 2;
  24. constexpr auto kFramesPerRow = 10;
  25. constexpr auto kImageSpoilerDarkenAlpha = 32;
  26. constexpr auto kMaxCacheSize = 5 * 1024 * 1024;
  27. constexpr auto kDefaultFrameDuration = crl::time(33);
  28. constexpr auto kDefaultFramesCount = 60;
  29. constexpr auto kAutoPauseTimeout = crl::time(1000);
  30. [[nodiscard]] SpoilerMessDescriptor DefaultDescriptorText() {
  31. const auto ratio = style::DevicePixelRatio();
  32. const auto size = style::ConvertScale(128) * ratio;
  33. return {
  34. .particleFadeInDuration = crl::time(200),
  35. .particleShownDuration = crl::time(200),
  36. .particleFadeOutDuration = crl::time(200),
  37. .particleSizeMin = style::ConvertScaleExact(1.5) * ratio,
  38. .particleSizeMax = style::ConvertScaleExact(2.) * ratio,
  39. .particleSpeedMin = style::ConvertScaleExact(4.),
  40. .particleSpeedMax = style::ConvertScaleExact(8.),
  41. .particleSpritesCount = 5,
  42. .particlesCount = 9000,
  43. .canvasSize = size,
  44. .framesCount = kDefaultFramesCount,
  45. .frameDuration = kDefaultFrameDuration,
  46. };
  47. }
  48. [[nodiscard]] SpoilerMessDescriptor DefaultDescriptorImage() {
  49. const auto ratio = style::DevicePixelRatio();
  50. const auto size = style::ConvertScale(128) * ratio;
  51. return {
  52. .particleFadeInDuration = crl::time(300),
  53. .particleShownDuration = crl::time(0),
  54. .particleFadeOutDuration = crl::time(300),
  55. .particleSizeMin = style::ConvertScaleExact(1.5) * ratio,
  56. .particleSizeMax = style::ConvertScaleExact(2.) * ratio,
  57. .particleSpeedMin = style::ConvertScaleExact(10.),
  58. .particleSpeedMax = style::ConvertScaleExact(20.),
  59. .particleSpritesCount = 5,
  60. .particlesCount = 3000,
  61. .canvasSize = size,
  62. .framesCount = kDefaultFramesCount,
  63. .frameDuration = kDefaultFrameDuration,
  64. };
  65. }
  66. } // namespace
  67. class SpoilerAnimationManager final {
  68. public:
  69. explicit SpoilerAnimationManager(not_null<SpoilerAnimation*> animation);
  70. void add(not_null<SpoilerAnimation*> animation);
  71. void remove(not_null<SpoilerAnimation*> animation);
  72. private:
  73. void destroyIfEmpty();
  74. Ui::Animations::Basic _animation;
  75. base::flat_set<not_null<SpoilerAnimation*>> _list;
  76. };
  77. namespace {
  78. struct DefaultSpoilerWaiter {
  79. std::condition_variable variable;
  80. std::mutex mutex;
  81. };
  82. struct DefaultSpoiler {
  83. std::atomic<const SpoilerMessCached*> cached/* = nullptr*/;
  84. std::atomic<DefaultSpoilerWaiter*> waiter/* = nullptr*/;
  85. };
  86. DefaultSpoiler DefaultTextMask;
  87. DefaultSpoiler DefaultImageCached;
  88. SpoilerAnimationManager *DefaultAnimationManager/* = nullptr*/;
  89. struct Header {
  90. uint32 version = 0;
  91. uint32 dataLength = 0;
  92. uint32 dataHash = 0;
  93. int32 framesCount = 0;
  94. int32 canvasSize = 0;
  95. int32 frameDuration = 0;
  96. };
  97. struct Particle {
  98. crl::time start = 0;
  99. int spriteIndex = 0;
  100. int x = 0;
  101. int y = 0;
  102. float64 dx = 0.;
  103. float64 dy = 0.;
  104. };
  105. [[nodiscard]] std::pair<float64, float64> RandomSpeed(
  106. const SpoilerMessDescriptor &descriptor,
  107. base::BufferedRandom<uint32> &random) {
  108. const auto count = descriptor.particlesCount;
  109. const auto speedMax = descriptor.particleSpeedMax;
  110. const auto speedMin = descriptor.particleSpeedMin;
  111. const auto value = RandomIndex(2 * count + 2, random);
  112. const auto negative = (value < count + 1);
  113. const auto module = (negative ? value : (value - count - 1));
  114. const auto speed = speedMin + (((speedMax - speedMin) * module) / count);
  115. const auto lifetime = descriptor.particleFadeInDuration
  116. + descriptor.particleShownDuration
  117. + descriptor.particleFadeOutDuration;
  118. const auto max = int(std::ceil(speedMax * lifetime));
  119. const auto k = speed / lifetime;
  120. const auto x = (speedMax > 0)
  121. ? ((RandomIndex(2 * max + 1, random) - max) / float64(max))
  122. : 0.;
  123. const auto y = (speedMax > 0)
  124. ? (sqrt(1 - x * x) * (negative ? -1 : 1))
  125. : 0.;
  126. return { k * x, k * y };
  127. }
  128. [[nodiscard]] Particle GenerateParticle(
  129. const SpoilerMessDescriptor &descriptor,
  130. int index,
  131. base::BufferedRandom<uint32> &random) {
  132. const auto speed = RandomSpeed(descriptor, random);
  133. return {
  134. .start = (index * descriptor.framesCount * descriptor.frameDuration
  135. / descriptor.particlesCount),
  136. .spriteIndex = RandomIndex(descriptor.particleSpritesCount, random),
  137. .x = RandomIndex(descriptor.canvasSize, random),
  138. .y = RandomIndex(descriptor.canvasSize, random),
  139. .dx = speed.first,
  140. .dy = speed.second,
  141. };
  142. }
  143. [[nodiscard]] QImage GenerateSprite(
  144. const SpoilerMessDescriptor &descriptor,
  145. int index,
  146. int size,
  147. base::BufferedRandom<uint32> &random) {
  148. Expects(index >= 0 && index < descriptor.particleSpritesCount);
  149. const auto count = descriptor.particleSpritesCount;
  150. const auto middle = count / 2;
  151. const auto min = descriptor.particleSizeMin;
  152. const auto delta = descriptor.particleSizeMax - min;
  153. const auto width = (index < middle)
  154. ? (min + delta * (middle - index) / float64(middle))
  155. : min;
  156. const auto height = (index > middle)
  157. ? (min + delta * (index - middle) / float64(count - 1 - middle))
  158. : min;
  159. const auto radius = min / 2.;
  160. auto result = QImage(size, size, QImage::Format_ARGB32_Premultiplied);
  161. result.fill(Qt::transparent);
  162. auto p = QPainter(&result);
  163. auto hq = PainterHighQualityEnabler(p);
  164. p.setPen(Qt::NoPen);
  165. p.setBrush(Qt::white);
  166. QPainterPath path;
  167. path.addRoundedRect(1., 1., width, height, radius, radius);
  168. p.drawPath(path);
  169. p.end();
  170. return result;
  171. }
  172. [[nodiscard]] QString DefaultMaskCacheFolder() {
  173. const auto base = Integration::Instance().emojiCacheFolder();
  174. return base.isEmpty() ? QString() : (base + "/spoiler");
  175. }
  176. [[nodiscard]] std::optional<SpoilerMessCached> ReadDefaultMask(
  177. const QString &name,
  178. std::optional<SpoilerMessCached::Validator> validator) {
  179. const auto folder = DefaultMaskCacheFolder();
  180. if (folder.isEmpty()) {
  181. return {};
  182. }
  183. auto file = QFile(folder + '/' + name);
  184. return (file.open(QIODevice::ReadOnly) && file.size() <= kMaxCacheSize)
  185. ? SpoilerMessCached::FromSerialized(file.readAll(), validator)
  186. : std::nullopt;
  187. }
  188. void WriteDefaultMask(
  189. const QString &name,
  190. const SpoilerMessCached &mask) {
  191. const auto folder = DefaultMaskCacheFolder();
  192. if (!QDir().mkpath(folder)) {
  193. return;
  194. }
  195. const auto bytes = mask.serialize();
  196. auto file = QFile(folder + '/' + name);
  197. if (file.open(QIODevice::WriteOnly) && bytes.size() <= kMaxCacheSize) {
  198. file.write(bytes);
  199. }
  200. }
  201. void Register(not_null<SpoilerAnimation*> animation) {
  202. if (DefaultAnimationManager) {
  203. DefaultAnimationManager->add(animation);
  204. } else {
  205. new SpoilerAnimationManager(animation);
  206. }
  207. }
  208. void Unregister(not_null<SpoilerAnimation*> animation) {
  209. Expects(DefaultAnimationManager != nullptr);
  210. DefaultAnimationManager->remove(animation);
  211. }
  212. // DescriptorFactory: (void) -> SpoilerMessDescriptor.
  213. // Postprocess: (unique_ptr<MessCached>) -> unique_ptr<MessCached>.
  214. template <typename DescriptorFactory, typename Postprocess>
  215. void PrepareDefaultSpoiler(
  216. DefaultSpoiler &spoiler,
  217. const char *nameFactory,
  218. DescriptorFactory descriptorFactory,
  219. Postprocess postprocess) {
  220. if (spoiler.waiter.load()) {
  221. return;
  222. }
  223. const auto waiter = new DefaultSpoilerWaiter();
  224. auto expected = (DefaultSpoilerWaiter*)nullptr;
  225. if (!spoiler.waiter.compare_exchange_strong(expected, waiter)) {
  226. delete waiter;
  227. return;
  228. }
  229. const auto name = QString::fromUtf8(nameFactory);
  230. crl::async([=, &spoiler] {
  231. const auto descriptor = descriptorFactory();
  232. auto cached = ReadDefaultMask(name, SpoilerMessCached::Validator{
  233. .frameDuration = descriptor.frameDuration,
  234. .framesCount = descriptor.framesCount,
  235. .canvasSize = descriptor.canvasSize,
  236. });
  237. spoiler.cached = postprocess(cached
  238. ? std::make_unique<SpoilerMessCached>(std::move(*cached))
  239. : std::make_unique<SpoilerMessCached>(
  240. GenerateSpoilerMess(descriptor))
  241. ).release();
  242. auto lock = std::unique_lock(waiter->mutex);
  243. waiter->variable.notify_all();
  244. if (!cached) {
  245. WriteDefaultMask(name, *spoiler.cached);
  246. }
  247. });
  248. }
  249. [[nodiscard]] const SpoilerMessCached &WaitDefaultSpoiler(
  250. DefaultSpoiler &spoiler) {
  251. const auto &cached = spoiler.cached;
  252. if (const auto result = cached.load()) {
  253. return *result;
  254. }
  255. const auto waiter = spoiler.waiter.load();
  256. Assert(waiter != nullptr);
  257. while (true) {
  258. auto lock = std::unique_lock(waiter->mutex);
  259. if (const auto result = cached.load()) {
  260. return *result;
  261. }
  262. waiter->variable.wait(lock);
  263. }
  264. }
  265. } // namespace
  266. SpoilerAnimationManager::SpoilerAnimationManager(
  267. not_null<SpoilerAnimation*> animation)
  268. : _animation([=](crl::time now) {
  269. for (auto i = begin(_list); i != end(_list);) {
  270. if ((*i)->repaint(now)) {
  271. ++i;
  272. } else {
  273. i = _list.erase(i);
  274. }
  275. }
  276. destroyIfEmpty();
  277. })
  278. , _list{ { animation } } {
  279. Expects(!DefaultAnimationManager);
  280. DefaultAnimationManager = this;
  281. _animation.start();
  282. }
  283. void SpoilerAnimationManager::add(not_null<SpoilerAnimation*> animation) {
  284. _list.emplace(animation);
  285. }
  286. void SpoilerAnimationManager::remove(not_null<SpoilerAnimation*> animation) {
  287. _list.remove(animation);
  288. destroyIfEmpty();
  289. }
  290. void SpoilerAnimationManager::destroyIfEmpty() {
  291. if (_list.empty()) {
  292. Assert(DefaultAnimationManager == this);
  293. delete base::take(DefaultAnimationManager);
  294. }
  295. }
  296. SpoilerMessCached GenerateSpoilerMess(
  297. const SpoilerMessDescriptor &descriptor) {
  298. Expects(descriptor.framesCount > 0);
  299. Expects(descriptor.frameDuration > 0);
  300. Expects(descriptor.particlesCount > 0);
  301. Expects(descriptor.canvasSize > 0);
  302. Expects(descriptor.particleSizeMax >= descriptor.particleSizeMin);
  303. Expects(descriptor.particleSizeMin > 0.);
  304. const auto frames = descriptor.framesCount;
  305. const auto rows = (frames + kFramesPerRow - 1) / kFramesPerRow;
  306. const auto columns = std::min(frames, kFramesPerRow);
  307. const auto size = descriptor.canvasSize;
  308. const auto count = descriptor.particlesCount;
  309. const auto width = size * columns;
  310. const auto height = size * rows;
  311. const auto spriteSize = 2 + int(std::ceil(descriptor.particleSizeMax));
  312. const auto singleDuration = descriptor.particleFadeInDuration
  313. + descriptor.particleShownDuration
  314. + descriptor.particleFadeOutDuration;
  315. const auto fullDuration = frames * descriptor.frameDuration;
  316. Assert(fullDuration > singleDuration);
  317. auto random = base::BufferedRandom<uint32>(count * 5);
  318. auto particles = std::vector<Particle>();
  319. particles.reserve(descriptor.particlesCount);
  320. for (auto i = 0; i != descriptor.particlesCount; ++i) {
  321. particles.push_back(GenerateParticle(descriptor, i, random));
  322. }
  323. auto sprites = std::vector<QImage>();
  324. sprites.reserve(descriptor.particleSpritesCount);
  325. for (auto i = 0; i != descriptor.particleSpritesCount; ++i) {
  326. sprites.push_back(GenerateSprite(descriptor, i, spriteSize, random));
  327. }
  328. auto frame = 0;
  329. auto image = QImage(width, height, QImage::Format_ARGB32_Premultiplied);
  330. image.fill(Qt::transparent);
  331. auto p = QPainter(&image);
  332. const auto paintOneAt = [&](const Particle &particle, crl::time now) {
  333. if (now <= 0 || now >= singleDuration) {
  334. return;
  335. }
  336. const auto clamp = [&](int value) {
  337. return ((value % size) + size) % size;
  338. };
  339. const auto x = clamp(
  340. particle.x + int(base::SafeRound(now * particle.dx)));
  341. const auto y = clamp(
  342. particle.y + int(base::SafeRound(now * particle.dy)));
  343. const auto opacity = (now < descriptor.particleFadeInDuration)
  344. ? (now / float64(descriptor.particleFadeInDuration))
  345. : (now > singleDuration - descriptor.particleFadeOutDuration)
  346. ? ((singleDuration - now)
  347. / float64(descriptor.particleFadeOutDuration))
  348. : 1.;
  349. p.setOpacity(opacity);
  350. const auto &sprite = sprites[particle.spriteIndex];
  351. p.drawImage(x, y, sprite);
  352. if (x + spriteSize > size) {
  353. p.drawImage(x - size, y, sprite);
  354. if (y + spriteSize > size) {
  355. p.drawImage(x, y - size, sprite);
  356. p.drawImage(x - size, y - size, sprite);
  357. }
  358. } else if (y + spriteSize > size) {
  359. p.drawImage(x, y - size, sprite);
  360. }
  361. };
  362. const auto paintOne = [&](const Particle &particle, crl::time now) {
  363. paintOneAt(particle, now - particle.start);
  364. paintOneAt(particle, now + fullDuration - particle.start);
  365. };
  366. for (auto y = 0; y != rows; ++y) {
  367. for (auto x = 0; x != columns; ++x) {
  368. const auto rect = QRect(x * size, y * size, size, size);
  369. p.setClipRect(rect);
  370. p.translate(rect.topLeft());
  371. const auto time = frame * descriptor.frameDuration;
  372. for (auto index = 0; index != count; ++index) {
  373. paintOne(particles[index], time);
  374. }
  375. p.translate(-rect.topLeft());
  376. if (++frame >= frames) {
  377. break;
  378. }
  379. }
  380. }
  381. return SpoilerMessCached(
  382. std::move(image),
  383. frames,
  384. descriptor.frameDuration,
  385. size);
  386. }
  387. void FillSpoilerRect(
  388. QPainter &p,
  389. QRect rect,
  390. const SpoilerMessFrame &frame,
  391. QPoint originShift) {
  392. if (rect.isEmpty()) {
  393. return;
  394. }
  395. const auto &image = *frame.image;
  396. const auto source = frame.source;
  397. const auto ratio = style::DevicePixelRatio();
  398. const auto origin = rect.topLeft() + originShift;
  399. const auto size = source.width() / ratio;
  400. const auto xSkipFrames = (origin.x() <= rect.x())
  401. ? ((rect.x() - origin.x()) / size)
  402. : -((origin.x() - rect.x() + size - 1) / size);
  403. const auto ySkipFrames = (origin.y() <= rect.y())
  404. ? ((rect.y() - origin.y()) / size)
  405. : -((origin.y() - rect.y() + size - 1) / size);
  406. const auto xFrom = origin.x() + size * xSkipFrames;
  407. const auto yFrom = origin.y() + size * ySkipFrames;
  408. Assert((xFrom <= rect.x())
  409. && (yFrom <= rect.y())
  410. && (xFrom + size > rect.x())
  411. && (yFrom + size > rect.y()));
  412. const auto xTill = rect.x() + rect.width();
  413. const auto yTill = rect.y() + rect.height();
  414. const auto xCount = (xTill - xFrom + size - 1) / size;
  415. const auto yCount = (yTill - yFrom + size - 1) / size;
  416. Assert(xCount > 0 && yCount > 0);
  417. const auto xFullFrom = (xFrom < rect.x()) ? 1 : 0;
  418. const auto yFullFrom = (yFrom < rect.y()) ? 1 : 0;
  419. const auto xFullTill = xCount - (xFrom + xCount * size > xTill ? 1 : 0);
  420. const auto yFullTill = yCount - (yFrom + yCount * size > yTill ? 1 : 0);
  421. const auto targetRect = [&](int x, int y) {
  422. return QRect(xFrom + x * size, yFrom + y * size, size, size);
  423. };
  424. const auto drawFull = [&](int x, int y) {
  425. p.drawImage(targetRect(x, y), image, source);
  426. };
  427. const auto drawPart = [&](int x, int y) {
  428. const auto target = targetRect(x, y);
  429. const auto fill = target.intersected(rect);
  430. Assert(!fill.isEmpty());
  431. p.drawImage(fill, image, QRect(
  432. source.topLeft() + ((fill.topLeft() - target.topLeft()) * ratio),
  433. fill.size() * ratio));
  434. };
  435. if (yFullFrom) {
  436. for (auto x = 0; x != xCount; ++x) {
  437. drawPart(x, 0);
  438. }
  439. }
  440. if (yFullFrom < yFullTill) {
  441. if (xFullFrom) {
  442. for (auto y = yFullFrom; y != yFullTill; ++y) {
  443. drawPart(0, y);
  444. }
  445. }
  446. if (xFullFrom < xFullTill) {
  447. for (auto y = yFullFrom; y != yFullTill; ++y) {
  448. for (auto x = xFullFrom; x != xFullTill; ++x) {
  449. drawFull(x, y);
  450. }
  451. }
  452. }
  453. if (xFullFrom <= xFullTill && xFullTill < xCount) {
  454. for (auto y = yFullFrom; y != yFullTill; ++y) {
  455. drawPart(xFullTill, y);
  456. }
  457. }
  458. }
  459. if (yFullFrom <= yFullTill && yFullTill < yCount) {
  460. for (auto x = 0; x != xCount; ++x) {
  461. drawPart(x, yFullTill);
  462. }
  463. }
  464. }
  465. void FillSpoilerRect(
  466. QPainter &p,
  467. QRect rect,
  468. Images::CornersMaskRef mask,
  469. const SpoilerMessFrame &frame,
  470. QImage &cornerCache,
  471. QPoint originShift) {
  472. using namespace Images;
  473. if ((!mask.p[kTopLeft] || mask.p[kTopLeft]->isNull())
  474. && (!mask.p[kTopRight] || mask.p[kTopRight]->isNull())
  475. && (!mask.p[kBottomLeft] || mask.p[kBottomLeft]->isNull())
  476. && (!mask.p[kBottomRight] || mask.p[kBottomRight]->isNull())) {
  477. FillSpoilerRect(p, rect, frame, originShift);
  478. return;
  479. }
  480. const auto ratio = style::DevicePixelRatio();
  481. const auto cornerSize = [&](int index) {
  482. const auto corner = mask.p[index];
  483. return (!corner || corner->isNull()) ? 0 : (corner->width() / ratio);
  484. };
  485. const auto verticalSkip = [&](int left, int right) {
  486. return std::max(cornerSize(left), cornerSize(right));
  487. };
  488. const auto fillBg = [&](QRect part) {
  489. FillSpoilerRect(
  490. p,
  491. part.translated(rect.topLeft()),
  492. frame,
  493. originShift - rect.topLeft() - part.topLeft());
  494. };
  495. const auto fillCorner = [&](int x, int y, int index) {
  496. const auto position = QPoint(x, y);
  497. const auto corner = mask.p[index];
  498. if (!corner || corner->isNull()) {
  499. return;
  500. }
  501. if (cornerCache.width() < corner->width()
  502. || cornerCache.height() < corner->height()) {
  503. cornerCache = QImage(
  504. std::max(cornerCache.width(), corner->width()),
  505. std::max(cornerCache.height(), corner->height()),
  506. QImage::Format_ARGB32_Premultiplied);
  507. cornerCache.setDevicePixelRatio(ratio);
  508. }
  509. const auto size = corner->size() / ratio;
  510. const auto target = QRect(QPoint(), size);
  511. auto q = QPainter(&cornerCache);
  512. q.setCompositionMode(QPainter::CompositionMode_Source);
  513. FillSpoilerRect(
  514. q,
  515. target,
  516. frame,
  517. originShift - rect.topLeft() - position);
  518. q.setCompositionMode(QPainter::CompositionMode_DestinationIn);
  519. q.drawImage(target, *corner);
  520. q.end();
  521. p.drawImage(
  522. QRect(rect.topLeft() + position, size),
  523. cornerCache,
  524. QRect(QPoint(), corner->size()));
  525. };
  526. const auto top = verticalSkip(kTopLeft, kTopRight);
  527. const auto bottom = verticalSkip(kBottomLeft, kBottomRight);
  528. if (top) {
  529. const auto left = cornerSize(kTopLeft);
  530. const auto right = cornerSize(kTopRight);
  531. if (left) {
  532. fillCorner(0, 0, kTopLeft);
  533. if (const auto add = top - left) {
  534. fillBg({ 0, left, left, add });
  535. }
  536. }
  537. if (const auto fill = rect.width() - left - right; fill > 0) {
  538. fillBg({ left, 0, fill, top });
  539. }
  540. if (right) {
  541. fillCorner(rect.width() - right, 0, kTopRight);
  542. if (const auto add = top - right) {
  543. fillBg({ rect.width() - right, right, right, add });
  544. }
  545. }
  546. }
  547. if (const auto h = rect.height() - top - bottom; h > 0) {
  548. fillBg({ 0, top, rect.width(), h });
  549. }
  550. if (bottom) {
  551. const auto left = cornerSize(kBottomLeft);
  552. const auto right = cornerSize(kBottomRight);
  553. if (left) {
  554. fillCorner(0, rect.height() - left, kBottomLeft);
  555. if (const auto add = bottom - left) {
  556. fillBg({ 0, rect.height() - bottom, left, add });
  557. }
  558. }
  559. if (const auto fill = rect.width() - left - right; fill > 0) {
  560. fillBg({ left, rect.height() - bottom, fill, bottom });
  561. }
  562. if (right) {
  563. fillCorner(
  564. rect.width() - right,
  565. rect.height() - right,
  566. kBottomRight);
  567. if (const auto add = bottom - right) {
  568. fillBg({
  569. rect.width() - right,
  570. rect.height() - bottom,
  571. right,
  572. add,
  573. });
  574. }
  575. }
  576. }
  577. }
  578. SpoilerMessCached::SpoilerMessCached(
  579. QImage image,
  580. int framesCount,
  581. crl::time frameDuration,
  582. int canvasSize)
  583. : _image(std::move(image))
  584. , _frameDuration(frameDuration)
  585. , _framesCount(framesCount)
  586. , _canvasSize(canvasSize) {
  587. Expects(_frameDuration > 0);
  588. Expects(_framesCount > 0);
  589. Expects(_canvasSize > 0);
  590. Expects(_image.size() == QSize(
  591. std::min(_framesCount, kFramesPerRow) * _canvasSize,
  592. ((_framesCount + kFramesPerRow - 1) / kFramesPerRow) * _canvasSize));
  593. }
  594. SpoilerMessCached::SpoilerMessCached(
  595. const SpoilerMessCached &mask,
  596. const QColor &color)
  597. : SpoilerMessCached(
  598. style::colorizeImage(*mask.frame(0).image, color),
  599. mask.framesCount(),
  600. mask.frameDuration(),
  601. mask.canvasSize()) {
  602. }
  603. SpoilerMessFrame SpoilerMessCached::frame(int index) const {
  604. const auto row = index / kFramesPerRow;
  605. const auto column = index - row * kFramesPerRow;
  606. return {
  607. .image = &_image,
  608. .source = QRect(
  609. column * _canvasSize,
  610. row * _canvasSize,
  611. _canvasSize,
  612. _canvasSize),
  613. };
  614. }
  615. SpoilerMessFrame SpoilerMessCached::frame() const {
  616. return frame((crl::now() / _frameDuration) % _framesCount);
  617. }
  618. crl::time SpoilerMessCached::frameDuration() const {
  619. return _frameDuration;
  620. }
  621. int SpoilerMessCached::framesCount() const {
  622. return _framesCount;
  623. }
  624. int SpoilerMessCached::canvasSize() const {
  625. return _canvasSize;
  626. }
  627. QByteArray SpoilerMessCached::serialize() const {
  628. Expects(_frameDuration < std::numeric_limits<int32>::max());
  629. const auto skip = sizeof(Header);
  630. auto result = QByteArray(skip, Qt::Uninitialized);
  631. auto header = Header{
  632. .version = kVersion,
  633. .framesCount = _framesCount,
  634. .canvasSize = _canvasSize,
  635. .frameDuration = int32(_frameDuration),
  636. };
  637. const auto width = int(_image.width());
  638. const auto height = int(_image.height());
  639. auto grayscale = QImage(width, height, QImage::Format_Grayscale8);
  640. {
  641. auto tobytes = grayscale.bits();
  642. auto frombytes = _image.constBits();
  643. const auto toadd = grayscale.bytesPerLine() - width;
  644. const auto fromadd = _image.bytesPerLine() - (width * 4);
  645. for (auto y = 0; y != height; ++y) {
  646. for (auto x = 0; x != width; ++x) {
  647. *tobytes++ = *frombytes;
  648. frombytes += 4;
  649. }
  650. tobytes += toadd;
  651. frombytes += fromadd;
  652. }
  653. }
  654. auto device = QBuffer(&result);
  655. device.open(QIODevice::WriteOnly);
  656. device.seek(skip);
  657. grayscale.save(&device, "PNG");
  658. device.close();
  659. header.dataLength = result.size() - skip;
  660. header.dataHash = XXH32(result.data() + skip, header.dataLength, 0);
  661. memcpy(result.data(), &header, skip);
  662. return result;
  663. }
  664. std::optional<SpoilerMessCached> SpoilerMessCached::FromSerialized(
  665. QByteArray data,
  666. std::optional<Validator> validator) {
  667. const auto skip = sizeof(Header);
  668. const auto length = data.size();
  669. const auto bytes = reinterpret_cast<const uchar*>(data.constData());
  670. if (length <= skip) {
  671. return {};
  672. }
  673. auto header = Header();
  674. memcpy(&header, bytes, skip);
  675. if (header.version != kVersion
  676. || header.canvasSize <= 0
  677. || header.framesCount <= 0
  678. || header.frameDuration <= 0
  679. || (validator
  680. && (validator->frameDuration != header.frameDuration
  681. || validator->framesCount != header.framesCount
  682. || validator->canvasSize != header.canvasSize))
  683. || (skip + header.dataLength != length)
  684. || (XXH32(bytes + skip, header.dataLength, 0) != header.dataHash)) {
  685. return {};
  686. }
  687. auto grayscale = QImage();
  688. if (!grayscale.loadFromData(bytes + skip, header.dataLength, "PNG")
  689. || (grayscale.format() != QImage::Format_Grayscale8)) {
  690. return {};
  691. }
  692. const auto count = header.framesCount;
  693. const auto rows = (count + kFramesPerRow - 1) / kFramesPerRow;
  694. const auto columns = std::min(count, kFramesPerRow);
  695. const auto width = grayscale.width();
  696. const auto height = grayscale.height();
  697. if (QSize(width, height) != QSize(columns, rows) * header.canvasSize) {
  698. return {};
  699. }
  700. auto image = QImage(width, height, QImage::Format_ARGB32_Premultiplied);
  701. {
  702. Assert(image.bytesPerLine() % 4 == 0);
  703. auto toints = reinterpret_cast<uint32*>(image.bits());
  704. auto frombytes = grayscale.constBits();
  705. const auto toadd = (image.bytesPerLine() / 4) - width;
  706. const auto fromadd = grayscale.bytesPerLine() - width;
  707. for (auto y = 0; y != height; ++y) {
  708. for (auto x = 0; x != width; ++x) {
  709. const auto byte = uint32(*frombytes++);
  710. *toints++ = (byte << 24) | (byte << 16) | (byte << 8) | byte;
  711. }
  712. toints += toadd;
  713. frombytes += fromadd;
  714. }
  715. }
  716. return SpoilerMessCached(
  717. std::move(image),
  718. count,
  719. header.frameDuration,
  720. header.canvasSize);
  721. }
  722. SpoilerAnimation::SpoilerAnimation(Fn<void()> repaint)
  723. : _repaint(std::move(repaint)) {
  724. Expects(_repaint != nullptr);
  725. }
  726. SpoilerAnimation::~SpoilerAnimation() {
  727. if (_animating) {
  728. _animating = false;
  729. Unregister(this);
  730. }
  731. }
  732. int SpoilerAnimation::index(crl::time now, bool paused) {
  733. _scheduled = false;
  734. const auto add = std::min(now - _last, kDefaultFrameDuration);
  735. if (anim::Disabled()) {
  736. paused = true;
  737. }
  738. if (!paused || _last) {
  739. _accumulated += add;
  740. _last = paused ? 0 : now;
  741. }
  742. const auto absolute = (_accumulated / kDefaultFrameDuration);
  743. if (!paused && !_animating) {
  744. _animating = true;
  745. Register(this);
  746. } else if (paused && _animating) {
  747. _animating = false;
  748. Unregister(this);
  749. }
  750. return absolute % kDefaultFramesCount;
  751. }
  752. Fn<void()> SpoilerAnimation::repaintCallback() const {
  753. return _repaint;
  754. }
  755. bool SpoilerAnimation::repaint(crl::time now) {
  756. if (!_scheduled) {
  757. _scheduled = true;
  758. _repaint();
  759. } else if (_animating && _last && _last + kAutoPauseTimeout <= now) {
  760. _animating = false;
  761. return false;
  762. }
  763. return true;
  764. }
  765. void PreloadTextSpoilerMask() {
  766. PrepareDefaultSpoiler(
  767. DefaultTextMask,
  768. "text",
  769. DefaultDescriptorText,
  770. [](std::unique_ptr<SpoilerMessCached> cached) { return cached; });
  771. }
  772. const SpoilerMessCached &DefaultTextSpoilerMask() {
  773. [[maybe_unused]] static const auto once = [&] {
  774. PreloadTextSpoilerMask();
  775. return 0;
  776. }();
  777. return WaitDefaultSpoiler(DefaultTextMask);
  778. }
  779. void PreloadImageSpoiler() {
  780. const auto postprocess = [](std::unique_ptr<SpoilerMessCached> cached) {
  781. Expects(cached != nullptr);
  782. const auto frame = cached->frame(0);
  783. auto image = QImage(
  784. frame.image->size(),
  785. QImage::Format_ARGB32_Premultiplied);
  786. image.fill(QColor(0, 0, 0, kImageSpoilerDarkenAlpha));
  787. auto p = QPainter(&image);
  788. p.drawImage(0, 0, *frame.image);
  789. p.end();
  790. return std::make_unique<SpoilerMessCached>(
  791. std::move(image),
  792. cached->framesCount(),
  793. cached->frameDuration(),
  794. cached->canvasSize());
  795. };
  796. PrepareDefaultSpoiler(
  797. DefaultImageCached,
  798. "image",
  799. DefaultDescriptorImage,
  800. postprocess);
  801. }
  802. const SpoilerMessCached &DefaultImageSpoiler() {
  803. [[maybe_unused]] static const auto once = [&] {
  804. PreloadImageSpoiler();
  805. return 0;
  806. }();
  807. return WaitDefaultSpoiler(DefaultImageCached);
  808. }
  809. } // namespace Ui