From 4124c2eb57116d6fc654979b9dbdfdbd450ca99e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Jul 2021 13:13:48 +0300 Subject: [PATCH] Show inline path thumbnails for stickers. --- .../SourceFiles/boxes/sticker_set_box.cpp | 6 + .../chat_helpers/field_autocomplete.cpp | 7 + .../chat_helpers/stickers_list_widget.cpp | 6 + .../chat_helpers/stickers_lottie.cpp | 23 ++ .../chat_helpers/stickers_lottie.h | 6 + Telegram/SourceFiles/data/data_document.cpp | 11 +- Telegram/SourceFiles/data/data_document.h | 6 +- .../SourceFiles/data/data_document_media.cpp | 15 +- .../SourceFiles/data/data_document_media.h | 2 + Telegram/SourceFiles/data/data_session.cpp | 36 ++-- Telegram/SourceFiles/data/data_session.h | 4 +- .../history_view_voice_record_bar.cpp | 2 +- .../view/media/history_view_sticker.cpp | 37 +++- .../history/view/media/history_view_sticker.h | 3 +- .../inline_bot_layout_internal.cpp | 15 +- .../storage/serialize_document.cpp | 2 +- .../SourceFiles/storage/storage_account.cpp | 2 +- .../storage/storage_cloud_song_cover.cpp | 2 +- Telegram/SourceFiles/ui/image/image.cpp | 200 ++++++++++++++++++ Telegram/SourceFiles/ui/image/image.h | 3 + .../SourceFiles/ui/image/image_location.h | 5 + 21 files changed, 355 insertions(+), 38 deletions(-) diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index 19db66a31..8cdddb330 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -779,6 +779,12 @@ void StickerSetBox::Inner::paintSticker( ppos, width(), image->pix(w, h)); + } else { + ChatHelpers::PaintStickerThumbnailPath( + p, + media.get(), + QRect(ppos, QSize(w, h)), + st::windowBgRipple->c); } } diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 7a00ccb87..791192c98 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -832,6 +832,13 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) { } else if (const auto image = media->getStickerSmall()) { QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); p.drawPixmapLeft(ppos, width(), image->pix(w, h)); + } else { + QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); + ChatHelpers::PaintStickerThumbnailPath( + p, + media.get(), + QRect(ppos, QSize(w, h)), + st::windowBgRipple->c); } } } diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index f40b7605d..19f51500d 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -1918,6 +1918,12 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section, if (sticker.savedFrame.isNull()) { sticker.savedFrame = pixmap; } + } else { + PaintStickerThumbnailPath( + p, + media.get(), + QRect(ppos, QSize{ w, h }), + st::windowBgRipple->c); } } diff --git a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp index 497c9bfa1..98363781a 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp @@ -188,4 +188,27 @@ std::unique_ptr LottieThumbnail( box); } +bool PaintStickerThumbnailPath( + QPainter &p, + not_null media, + QRect target, + QColor fg) { + const auto &path = media->thumbnailPath(); + const auto dimensions = media->owner()->dimensions; + if (path.isEmpty() || dimensions.isEmpty()) { + return false; + } + p.save(); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(fg); + p.setPen(Qt::NoPen); + p.translate(target.topLeft()); + p.scale( + target.width() / float64(dimensions.width()), + target.height() / float64(dimensions.height())); + p.drawPath(path); + p.restore(); + return true; +} + } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_lottie.h b/Telegram/SourceFiles/chat_helpers/stickers_lottie.h index 43a694e0e..700a9f8f4 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_lottie.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_lottie.h @@ -71,4 +71,10 @@ enum class StickerLottieSize : uchar { QSize box, std::shared_ptr renderer = nullptr); +bool PaintStickerThumbnailPath( + QPainter &p, + not_null media, + QRect target, + QColor fg); + } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 1a1b718a7..5de2b3388 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -442,12 +442,17 @@ bool DocumentData::checkWallPaperProperties() { } void DocumentData::updateThumbnails( - const QByteArray &inlineThumbnailBytes, + const InlineImageLocation &inlineThumbnail, const ImageWithLocation &thumbnail, const ImageWithLocation &videoThumbnail) { - if (!inlineThumbnailBytes.isEmpty() + if (!inlineThumbnail.bytes.isEmpty() && _inlineThumbnailBytes.isEmpty()) { - _inlineThumbnailBytes = inlineThumbnailBytes; + _inlineThumbnailBytes = inlineThumbnail.bytes; + if (inlineThumbnail.isPath) { + _flags |= Flag::InlineThumbnailIsPath; + } else { + _flags &= ~Flag::InlineThumbnailIsPath; + } } Data::UpdateCloudFile( _thumbnail, diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index 706f04743..cd3e89187 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -171,13 +171,16 @@ public: [[nodiscard]] int videoThumbnailByteSize() const; void updateThumbnails( - const QByteArray &inlineThumbnailBytes, + const InlineImageLocation &inlineThumbnail, const ImageWithLocation &thumbnail, const ImageWithLocation &videoThumbnail); [[nodiscard]] QByteArray inlineThumbnailBytes() const { return _inlineThumbnailBytes; } + [[nodiscard]] bool inlineThumbnailIsPath() const { + return (_flags & Flag::InlineThumbnailIsPath); + } void clearInlineThumbnailBytes() { _inlineThumbnailBytes = QByteArray(); } @@ -257,6 +260,7 @@ private: DownloadCancelled = 0x10, LoadedInMediaCache = 0x20, HasAttachedStickers = 0x40, + InlineThumbnailIsPath = 0x80, }; using Flags = base::flags; friend constexpr bool is_flag_type(Flag) { return true; }; diff --git a/Telegram/SourceFiles/data/data_document_media.cpp b/Telegram/SourceFiles/data/data_document_media.cpp index 33fe6d556..e0d9acfe0 100644 --- a/Telegram/SourceFiles/data/data_document_media.cpp +++ b/Telegram/SourceFiles/data/data_document_media.cpp @@ -172,7 +172,7 @@ void DocumentMedia::setGoodThumbnail(QImage thumbnail) { } Image *DocumentMedia::thumbnailInline() const { - if (!_inlineThumbnail) { + if (!_inlineThumbnail && !_owner->inlineThumbnailIsPath()) { const auto bytes = _owner->inlineThumbnailBytes(); if (!bytes.isEmpty()) { auto image = Images::FromInlineBytes(bytes); @@ -186,6 +186,19 @@ Image *DocumentMedia::thumbnailInline() const { return _inlineThumbnail.get(); } +const QPainterPath &DocumentMedia::thumbnailPath() const { + if (_pathThumbnail.isEmpty() && _owner->inlineThumbnailIsPath()) { + const auto bytes = _owner->inlineThumbnailBytes(); + if (!bytes.isEmpty()) { + _pathThumbnail = Images::PathFromInlineBytes(bytes); + if (_pathThumbnail.isEmpty()) { + _owner->clearInlineThumbnailBytes(); + } + } + } + return _pathThumbnail; +} + Image *DocumentMedia::thumbnail() const { return _thumbnail.get(); } diff --git a/Telegram/SourceFiles/data/data_document_media.h b/Telegram/SourceFiles/data/data_document_media.h index d686a9573..9329023c9 100644 --- a/Telegram/SourceFiles/data/data_document_media.h +++ b/Telegram/SourceFiles/data/data_document_media.h @@ -52,6 +52,7 @@ public: void setGoodThumbnail(QImage thumbnail); [[nodiscard]] Image *thumbnailInline() const; + [[nodiscard]] const QPainterPath &thumbnailPath() const; [[nodiscard]] Image *thumbnail() const; [[nodiscard]] QSize thumbnailSize() const; @@ -102,6 +103,7 @@ private: const not_null _owner; std::unique_ptr _goodThumbnail; mutable std::unique_ptr _inlineThumbnail; + mutable QPainterPath _pathThumbnail; std::unique_ptr _thumbnail; std::unique_ptr _sticker; QByteArray _bytes; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index bae367853..5c139ec97 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -134,18 +134,25 @@ std::vector ExtractUnavailableReasons( }) | ranges::to_vector; } -[[nodiscard]] QByteArray FindInlineThumbnail( +[[nodiscard]] InlineImageLocation FindInlineThumbnail( const QVector &sizes) { const auto i = ranges::find( sizes, mtpc_photoStrippedSize, &MTPPhotoSize::type); + const auto j = ranges::find( + sizes, + mtpc_photoPathSize, + &MTPPhotoSize::type); return (i != sizes.end()) - ? i->c_photoStrippedSize().vbytes().v - : QByteArray(); + ? InlineImageLocation{ i->c_photoStrippedSize().vbytes().v, false } + : (j != sizes.end()) + ? InlineImageLocation{ j->c_photoPathSize().vbytes().v, true } + : InlineImageLocation(); } -[[nodiscard]] QByteArray FindDocumentInlineThumbnail(const MTPDdocument &data) { +[[nodiscard]] InlineImageLocation FindDocumentInlineThumbnail( + const MTPDdocument &data) { return FindInlineThumbnail(data.vthumbs().value_or_empty()); } @@ -193,7 +200,8 @@ std::vector ExtractUnavailableReasons( } [[nodiscard]] QByteArray FindPhotoInlineThumbnail(const MTPDphoto &data) { - return FindInlineThumbnail(data.vsizes().v); + const auto thumbnail = FindInlineThumbnail(data.vsizes().v); + return !thumbnail.isPath ? thumbnail.bytes : QByteArray(); } [[nodiscard]] int VideoStartTime(const MTPDvideoSize &data) { @@ -2656,7 +2664,7 @@ not_null Session::processDocument( data.vdate().v, data.vattributes().v, qs(data.vmime_type()), - QByteArray(), + InlineImageLocation(), thumbnail, ImageWithLocation(), data.vdc_id().v, @@ -2673,7 +2681,7 @@ not_null Session::document( TimeId date, const QVector &attributes, const QString &mime, - const QByteArray &inlineThumbnailBytes, + const InlineImageLocation &inlineThumbnail, const ImageWithLocation &thumbnail, const ImageWithLocation &videoThumbnail, int32 dc, @@ -2686,7 +2694,7 @@ not_null Session::document( date, attributes, mime, - inlineThumbnailBytes, + inlineThumbnail, thumbnail, videoThumbnail, dc, @@ -2754,7 +2762,7 @@ DocumentData *Session::documentFromWeb( base::unixtime::now(), data.vattributes().v, data.vmime_type().v, - QByteArray(), + InlineImageLocation(), ImageWithLocation{ .location = thumbnailLocation }, ImageWithLocation{ .location = videoThumbnailLocation }, session().mainDcId(), @@ -2776,7 +2784,7 @@ DocumentData *Session::documentFromWeb( base::unixtime::now(), data.vattributes().v, data.vmime_type().v, - QByteArray(), + InlineImageLocation(), ImageWithLocation{ .location = thumbnailLocation }, ImageWithLocation{ .location = videoThumbnailLocation }, session().mainDcId(), @@ -2796,7 +2804,7 @@ void Session::documentApplyFields( void Session::documentApplyFields( not_null document, const MTPDdocument &data) { - const auto inlineThumbnailBytes = FindDocumentInlineThumbnail(data); + const auto inlineThumbnail = FindDocumentInlineThumbnail(data); const auto thumbnailSize = FindDocumentThumbnail(data); const auto videoThumbnailSize = FindDocumentVideoThumbnail(data); const auto prepared = Images::FromPhotoSize( @@ -2813,7 +2821,7 @@ void Session::documentApplyFields( data.vdate().v, data.vattributes().v, qs(data.vmime_type()), - inlineThumbnailBytes, + inlineThumbnail, prepared, videoThumbnail, data.vdc_id().v, @@ -2827,7 +2835,7 @@ void Session::documentApplyFields( TimeId date, const QVector &attributes, const QString &mime, - const QByteArray &inlineThumbnailBytes, + const InlineImageLocation &inlineThumbnail, const ImageWithLocation &thumbnail, const ImageWithLocation &videoThumbnail, int32 dc, @@ -2838,7 +2846,7 @@ void Session::documentApplyFields( document->date = date; document->setMimeString(mime); document->updateThumbnails( - inlineThumbnailBytes, + inlineThumbnail, thumbnail, videoThumbnail); document->size = size; diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index e7bfc1c86..bda612c6f 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -476,7 +476,7 @@ public: TimeId date, const QVector &attributes, const QString &mime, - const QByteArray &inlineThumbnailBytes, + const InlineImageLocation &inlineThumbnail, const ImageWithLocation &thumbnail, const ImageWithLocation &videoThumbnail, int32 dc, @@ -743,7 +743,7 @@ private: TimeId date, const QVector &attributes, const QString &mime, - const QByteArray &inlineThumbnailBytes, + const InlineImageLocation &inlineThumbnail, const ImageWithLocation &thumbnail, const ImageWithLocation &videoThumbnail, int32 dc, diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 1c21b9968..594b00082 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -106,7 +106,7 @@ enum class FilterType { base::unixtime::now(), QVector(), QString(), - QByteArray(), + InlineImageLocation(), ImageWithLocation(), ImageWithLocation(), owner->session().mainDcId(), diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index c6982bfdc..71704f14e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -150,11 +150,14 @@ QSize Sticker::GetAnimatedEmojiSize( void Sticker::draw(Painter &p, const QRect &r, bool selected) { ensureDataMediaCreated(); + paintPath(p, r, selected); + return; if (readyToDrawLottie()) { paintLottie(p, r, selected); - } else if (_data->sticker() - && (!_data->sticker()->animated || !_replacements)) { - paintPixmap(p, r, selected); + } else if (!_data->sticker() + || (_data->sticker()->animated && _replacements) + || !paintPixmap(p, r, selected)) { + paintPath(p, r, selected); } } @@ -215,15 +218,25 @@ void Sticker::paintLottie(Painter &p, const QRect &r, bool selected) { } } -void Sticker::paintPixmap(Painter &p, const QRect &r, bool selected) { +bool Sticker::paintPixmap(Painter &p, const QRect &r, bool selected) { const auto pixmap = paintedPixmap(selected); - if (!pixmap.isNull()) { - p.drawPixmap( - QPoint( - r.x() + (r.width() - _size.width()) / 2, - r.y() + (r.height() - _size.height()) / 2), - pixmap); + if (pixmap.isNull()) { + return false; } + p.drawPixmap( + QPoint( + r.x() + (r.width() - _size.width()) / 2, + r.y() + (r.height() - _size.height()) / 2), + pixmap); + return true; +} + +void Sticker::paintPath(Painter &p, const QRect &r, bool selected) { + ChatHelpers::PaintStickerThumbnailPath( + p, + _dataMedia.get(), + r, + (selected ? st::msgServiceBgSelected : st::msgServiceBg)->c); } QPixmap Sticker::paintedPixmap(bool selected) const { @@ -306,7 +319,9 @@ void Sticker::dataMediaCreated() const { Expects(_dataMedia != nullptr); _dataMedia->goodThumbnailWanted(); - _dataMedia->thumbnailWanted(_parent->data()->fullId()); + if (_dataMedia->thumbnailPath().isEmpty()) { + _dataMedia->thumbnailWanted(_parent->data()->fullId()); + } _parent->history()->owner().registerHeavyViewPart(_parent); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.h b/Telegram/SourceFiles/history/view/media/history_view_sticker.h index d20520b0f..e2b37fd81 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.h +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.h @@ -84,7 +84,8 @@ public: private: [[nodiscard]] bool isEmojiSticker() const; void paintLottie(Painter &p, const QRect &r, bool selected); - void paintPixmap(Painter &p, const QRect &r, bool selected); + bool paintPixmap(Painter &p, const QRect &r, bool selected); + void paintPath(Painter &p, const QRect &r, bool selected); [[nodiscard]] QPixmap paintedPixmap(bool selected) const; void ensureDataMediaCreated() const; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 0eb54d3d0..02c6f2932 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo_media.h" #include "data/data_document_media.h" #include "data/stickers/data_stickers.h" -#include "chat_helpers/gifs_list_widget.h" // ChatHelpers::AddGifAction +#include "chat_helpers/gifs_list_widget.h" // ChatHelpers::AddGifAction. #include "chat_helpers/stickers_lottie.h" #include "inline_bots/inline_bot_result.h" #include "lottie/lottie_single_player.h" @@ -483,6 +483,19 @@ void Sticker::paint(Painter &p, const QRect &clip, const PaintContext *context) int w = _thumb.width() / cIntRetinaFactor(), h = _thumb.height() / cIntRetinaFactor(); QPoint pos = QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); p.drawPixmap(pos, _thumb); + } else { + const auto thumbSize = getThumbSize(); + const auto w = thumbSize.width(); + const auto h = thumbSize.height(); + ChatHelpers::PaintStickerThumbnailPath( + p, + _dataMedia.get(), + QRect( + (st::stickerPanSize.width() - w) / 2, + (st::stickerPanSize.height() - h) / 2, + w, + h), + st::windowBgRipple->c); } } diff --git a/Telegram/SourceFiles/storage/serialize_document.cpp b/Telegram/SourceFiles/storage/serialize_document.cpp index efbb5c2de..a9058143e 100644 --- a/Telegram/SourceFiles/storage/serialize_document.cpp +++ b/Telegram/SourceFiles/storage/serialize_document.cpp @@ -164,7 +164,7 @@ DocumentData *Document::readFromStreamHelper( date, attributes, mime, - QByteArray(), + InlineImageLocation(), ImageWithLocation{ .location = *thumb, .bytesCount = thumbnailByteSize diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index cfd2349dd..8b2f9d618 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -1995,7 +1995,7 @@ void Account::importOldRecentStickers() { date, attributes, mime, - QByteArray(), + InlineImageLocation(), ImageWithLocation(), ImageWithLocation(), dc, diff --git a/Telegram/SourceFiles/storage/storage_cloud_song_cover.cpp b/Telegram/SourceFiles/storage/storage_cloud_song_cover.cpp index 15727b03a..f6203963e 100644 --- a/Telegram/SourceFiles/storage/storage_cloud_song_cover.cpp +++ b/Telegram/SourceFiles/storage/storage_cloud_song_cover.cpp @@ -98,7 +98,7 @@ void LoadAndApplyThumbnail( }; document->updateThumbnails( - QByteArray(), + InlineImageLocation(), imageWithLocation, ImageWithLocation{ .location = ImageLocation() }); diff --git a/Telegram/SourceFiles/ui/image/image.cpp b/Telegram/SourceFiles/ui/image/image.cpp index dc98c2511..6a8fab40f 100644 --- a/Telegram/SourceFiles/ui/image/image.cpp +++ b/Telegram/SourceFiles/ui/image/image.cpp @@ -93,6 +93,206 @@ QImage FromInlineBytes(const QByteArray &bytes) { return App::readImage(ExpandInlineBytes(bytes)); } +// Thanks TDLib for code. +QByteArray ExpandPathInlineBytes(const QByteArray &bytes) { + auto result = QByteArray(); + result.reserve(3 * (bytes.size() + 1)); + result.append('M'); + for (unsigned char c : bytes) { + if (c >= 128 + 64) { + result.append("AACAAAAHAAALMAAAQASTAVAAAZ" + "aacaaaahaaalmaaaqastava.az0123456789-,"[c - 128 - 64]); + } else { + if (c >= 128) { + result.append(','); + } else if (c >= 64) { + result.append('-'); + } + char buffer[3] = { 0 }; + std::to_chars(buffer, buffer + 3, (c & 63)); + result.append(buffer); + } + } + result.append('z'); + return result; +} + +QPainterPath PathFromInlineBytes(const QByteArray &bytes) { + if (bytes.isEmpty()) { + return QPainterPath(); + } + const auto expanded = ExpandPathInlineBytes(bytes); + const auto path = expanded.data(); // Allows checking for '\0' by index. + auto position = 0; + + const auto isAlpha = [](char c) { + c |= 0x20; + return 'a' <= c && c <= 'z'; + }; + const auto isDigit = [](char c) { + return '0' <= c && c <= '9'; + }; + const auto skipCommas = [&] { + while (path[position] == ',') { + ++position; + } + }; + const auto getNumber = [&] { + skipCommas(); + auto sign = 1; + if (path[position] == '-') { + sign = -1; + ++position; + } + double res = 0; + while (isDigit(path[position])) { + res = res * 10 + path[position++] - '0'; + } + if (path[position] == '.') { + ++position; + double mul = 0.1; + while (isDigit(path[position])) { + res += (path[position] - '0') * mul; + mul *= 0.1; + ++position; + } + } + return sign * res; + }; + + auto result = QPainterPath(); + auto x = 0.; + auto y = 0.; + while (path[position] != '\0') { + skipCommas(); + if (path[position] == '\0') { + break; + } + + while (path[position] == 'm' || path[position] == 'M') { + auto command = path[position++]; + do { + if (command == 'm') { + x += getNumber(); + y += getNumber(); + } else { + x = getNumber(); + y = getNumber(); + } + skipCommas(); + } while (path[position] != '\0' && !isAlpha(path[position])); + } + + auto xStart = x; + auto yStart = y; + result.moveTo(xStart, yStart); + auto haveLastEndControlPoint = false; + auto xLastEndControlPoint = 0.; + auto yLastEndControlPoint = 0.; + auto isClosed = false; + auto command = '-'; + while (!isClosed) { + skipCommas(); + if (path[position] == '\0') { + LOG(("SVG Error: Receive unclosed path: %1" + ).arg(QString::fromLatin1(path))); + return QPainterPath(); + } + if (isAlpha(path[position])) { + command = path[position++]; + } + switch (command) { + case 'l': + case 'L': + case 'h': + case 'H': + case 'v': + case 'V': + if (command == 'l' || command == 'h') { + x += getNumber(); + } else if (command == 'L' || command == 'H') { + x = getNumber(); + } + if (command == 'l' || command == 'v') { + y += getNumber(); + } else if (command == 'L' || command == 'V') { + y = getNumber(); + } + result.lineTo(x, y); + haveLastEndControlPoint = false; + break; + case 'C': + case 'c': + case 'S': + case 's': { + auto xStartControlPoint = 0.; + auto yStartControlPoint = 0.; + if (command == 'S' || command == 's') { + if (haveLastEndControlPoint) { + xStartControlPoint = 2 * x - xLastEndControlPoint; + yStartControlPoint = 2 * y - yLastEndControlPoint; + } else { + xStartControlPoint = x; + yStartControlPoint = y; + } + } else { + xStartControlPoint = getNumber(); + yStartControlPoint = getNumber(); + if (command == 'c') { + xStartControlPoint += x; + yStartControlPoint += y; + } + } + + xLastEndControlPoint = getNumber(); + yLastEndControlPoint = getNumber(); + if (command == 'c' || command == 's') { + xLastEndControlPoint += x; + yLastEndControlPoint += y; + } + haveLastEndControlPoint = true; + + if (command == 'c' || command == 's') { + x += getNumber(); + y += getNumber(); + } else { + x = getNumber(); + y = getNumber(); + } + result.cubicTo( + xStartControlPoint, + yStartControlPoint, + xLastEndControlPoint, + yLastEndControlPoint, + x, + y); + break; + } + case 'm': + case 'M': + --position; + [[fallthrough]]; + case 'z': + case 'Z': + if (x != xStart || y != yStart) { + x = xStart; + y = yStart; + result.lineTo(x, y); + } + isClosed = true; + break; + default: + LOG(("SVG Error: Receive invalid command %1 at pos %2: %3" + ).arg(command + ).arg(position + ).arg(QString::fromLatin1(path))); + return QPainterPath(); + } + } + } + return result; +} + } // namespace Images Image::Image(const QString &path) : Image(ReadContent(path)) { diff --git a/Telegram/SourceFiles/ui/image/image.h b/Telegram/SourceFiles/ui/image/image.h index f18f7bdbb..725d69e1a 100644 --- a/Telegram/SourceFiles/ui/image/image.h +++ b/Telegram/SourceFiles/ui/image/image.h @@ -9,10 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image_prepare.h" +class QPainterPath; + namespace Images { [[nodiscard]] QByteArray ExpandInlineBytes(const QByteArray &bytes); [[nodiscard]] QImage FromInlineBytes(const QByteArray &bytes); +[[nodiscard]] QPainterPath PathFromInlineBytes(const QByteArray &bytes); } // namespace Images diff --git a/Telegram/SourceFiles/ui/image/image_location.h b/Telegram/SourceFiles/ui/image/image_location.h index f406e10a5..21f8a6923 100644 --- a/Telegram/SourceFiles/ui/image/image_location.h +++ b/Telegram/SourceFiles/ui/image/image_location.h @@ -620,6 +620,11 @@ struct ImageWithLocation { int progressivePartSize = 0; }; +struct InlineImageLocation { + QByteArray bytes; + bool isPath = false; +}; + InMemoryKey inMemoryKey(const StorageFileLocation &location); inline InMemoryKey inMemoryKey(const StorageImageLocation &location) {