From 3797753d16330a9fb61a8e15e8c0ad58488da98b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 16 Apr 2020 16:37:26 +0400 Subject: [PATCH] Support different location types for thumbnails. --- Telegram/CMakeLists.txt | 2 + .../chat_helpers/stickers_dice_pack.cpp | 3 +- Telegram/SourceFiles/data/data_document.cpp | 21 +-- Telegram/SourceFiles/data/data_document.h | 8 +- Telegram/SourceFiles/data/data_session.cpp | 93 +++++------- Telegram/SourceFiles/data/data_session.h | 12 +- .../inline_bots/inline_bot_result.cpp | 9 +- .../SourceFiles/storage/file_download.cpp | 138 +++++++++++++++++- Telegram/SourceFiles/storage/file_download.h | 11 ++ Telegram/SourceFiles/storage/file_upload.cpp | 27 +++- Telegram/SourceFiles/storage/localstorage.cpp | 2 +- .../SourceFiles/storage/serialize_common.cpp | 28 ++++ .../SourceFiles/storage/serialize_common.h | 9 ++ .../storage/serialize_document.cpp | 19 ++- Telegram/SourceFiles/ui/image/image.cpp | 91 ++++++------ Telegram/SourceFiles/ui/image/image.h | 4 + .../SourceFiles/ui/image/image_location.h | 11 +- .../ui/image/image_location_factory.cpp | 124 ++++++++++++++++ .../ui/image/image_location_factory.h | 27 ++++ 19 files changed, 491 insertions(+), 148 deletions(-) create mode 100644 Telegram/SourceFiles/ui/image/image_location_factory.cpp create mode 100644 Telegram/SourceFiles/ui/image/image_location_factory.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 38ea4b989..d5e0d70c4 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -924,6 +924,8 @@ PRIVATE ui/image/image.h ui/image/image_location.cpp ui/image/image_location.h + ui/image/image_location_factory.cpp + ui/image/image_location_factory.h ui/image/image_source.cpp ui/image/image_source.h ui/widgets/continuous_sliders.cpp diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp index d98fd9c85..30b3c7c8e 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "data/data_session.h" #include "data/data_document.h" +#include "ui/image/image_location_factory.h" #include "storage/localimageloader.h" #include "base/unixtime.h" #include "apiwrap.h" @@ -112,7 +113,7 @@ void DicePack::tryGenerateLocalZero() { Assert(result != nullptr); const auto document = _session->data().processDocument( result->document, - std::move(result->thumb)); + Images::FromImageInMemory(result->thumb, "PNG")); document->setLocation(FileLocation(path)); _map.emplace(0, document); diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 892f5bf7a..323fd8cce 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -606,16 +606,17 @@ bool DocumentData::checkWallPaperProperties() { void DocumentData::updateThumbnails( const QByteArray &inlineThumbnailBytes, - const StorageImageLocation &thumbnail) { + const ImageWithLocation &thumbnail) { if (!inlineThumbnailBytes.isEmpty() && _inlineThumbnailBytes.isEmpty()) { _inlineThumbnailBytes = inlineThumbnailBytes; } - if (thumbnail.valid() + if (thumbnail.location.valid() && (!_thumbnailLocation.valid() - || _thumbnailLocation.width() < thumbnail.width() - || _thumbnailLocation.height() < thumbnail.height())) { - _thumbnailLocation = thumbnail; + || _thumbnailLocation.width() < thumbnail.location.width() + || _thumbnailLocation.height() < thumbnail.location.height())) { + _thumbnailLocation = thumbnail.location; + _thumbnailByteSize = thumbnail.bytesCount; if (_thumbnailLoader) { const auto origin = base::take(_thumbnailLoader)->fileOrigin(); loadThumbnail(origin); @@ -624,7 +625,7 @@ void DocumentData::updateThumbnails( } } -const StorageImageLocation &DocumentData::thumbnailLocation() const { +const ImageLocation &DocumentData::thumbnailLocation() const { return _thumbnailLocation; } @@ -659,16 +660,17 @@ void DocumentData::loadThumbnail(Data::FileOrigin origin) { } } const auto autoLoading = false; - _thumbnailLoader = std::make_unique( + _thumbnailLoader = CreateFileLoader( _thumbnailLocation.file(), origin, - UnknownFileLocation, QString(), - _thumbnailSize, + _thumbnailByteSize, + UnknownFileLocation, LoadToCacheAsWell, LoadFromCloudOrLocal, autoLoading, Data::kImageCacheTag); + _thumbnailLoader->updates( ) | rpl::start_with_error_done([=](bool started) { _thumbnailLoader = nullptr; @@ -683,6 +685,7 @@ void DocumentData::loadThumbnail(Data::FileOrigin origin) { } _thumbnailLoader = nullptr; }) | rpl::release(); + _thumbnailLoader->start(); } diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index c89eeabe9..16dab1872 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -161,8 +161,8 @@ public: void loadThumbnail(Data::FileOrigin origin); void updateThumbnails( const QByteArray &inlineThumbnailBytes, - const StorageImageLocation &thumbnail); - const StorageImageLocation &thumbnailLocation() const; + const ImageWithLocation &thumbnail); + const ImageLocation &thumbnailLocation() const; [[nodiscard]] QByteArray inlineThumbnailBytes() const { return _inlineThumbnailBytes; @@ -301,9 +301,9 @@ private: WebFileLocation _urlLocation; QByteArray _inlineThumbnailBytes; - StorageImageLocation _thumbnailLocation; + ImageLocation _thumbnailLocation; std::unique_ptr _thumbnailLoader; - int _thumbnailSize = 0; + int _thumbnailByteSize = 0; std::unique_ptr _replyPreview; std::weak_ptr _media; PhotoData *_goodThumbnailPhoto = nullptr; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 67c69ce02..a4281b3f4 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/crash_reports.h" // CrashReports::SetAnnotation #include "ui/image/image.h" #include "ui/image/image_source.h" // Images::LocalFileSource +#include "ui/image/image_location_factory.h" // Images::FromPhotoSize #include "export/export_controller.h" #include "export/view/export_view_panel_controller.h" #include "window/notifications_manager.h" @@ -2355,14 +2356,11 @@ not_null Session::document(DocumentId id) { } not_null Session::processDocument(const MTPDocument &data) { - switch (data.type()) { - case mtpc_document: - return processDocument(data.c_document()); - - case mtpc_documentEmpty: - return document(data.c_documentEmpty().vid().v); - } - Unexpected("Type in Session::document()."); + return data.match([&](const MTPDdocument &data) { + return processDocument(data); + }, [&](const MTPDdocumentEmpty &data) { + return document(data.vid().v); + }); } not_null Session::processDocument(const MTPDdocument &data) { @@ -2373,33 +2371,22 @@ not_null Session::processDocument(const MTPDdocument &data) { not_null Session::processDocument( const MTPdocument &data, - QImage &&thumb) { - switch (data.type()) { - case mtpc_documentEmpty: - return document(data.c_documentEmpty().vid().v); - - case mtpc_document: { - const auto &fields = data.c_document(); - const auto mime = qs(fields.vmime_type()); - // #TODO optimize - const auto format = Core::IsMimeSticker(mime) - ? "WEBP" - : "JPG"; - Images::Create(std::move(thumb), format); + const ImageWithLocation &thumbnail) { + return data.match([&](const MTPDdocument &data) { return document( - fields.vid().v, - fields.vaccess_hash().v, - fields.vfile_reference().v, - fields.vdate().v, - fields.vattributes().v, - mime, + data.vid().v, + data.vaccess_hash().v, + data.vfile_reference().v, + data.vdate().v, + data.vattributes().v, + qs(data.vmime_type()), QByteArray(), - StorageImageLocation(), - fields.vdc_id().v, - fields.vsize().v); - } break; - } - Unexpected("Type in Session::document() with thumb."); + thumbnail, + data.vdc_id().v, + data.vsize().v); + }, [&](const MTPDdocumentEmpty &data) { + return document(data.vid().v); + }); } not_null Session::document( @@ -2410,7 +2397,7 @@ not_null Session::document( const QVector &attributes, const QString &mime, const QByteArray &inlineThumbnailBytes, - const StorageImageLocation &thumbnailLocation, + const ImageWithLocation &thumbnail, int32 dc, int32 size) { const auto result = document(id); @@ -2422,7 +2409,7 @@ not_null Session::document( attributes, mime, inlineThumbnailBytes, - thumbnailLocation, + thumbnail, dc, size); return result; @@ -2473,22 +2460,15 @@ void Session::documentConvert( DocumentData *Session::documentFromWeb( const MTPWebDocument &data, - ImagePtr thumb) { - switch (data.type()) { - case mtpc_webDocument: - return documentFromWeb(data.c_webDocument(), thumb); - - case mtpc_webDocumentNoProxy: - return documentFromWeb(data.c_webDocumentNoProxy(), thumb); - - } - Unexpected("Type in Session::documentFromWeb."); + const ImageLocation &thumbnailLocation) { + return data.match([&](const auto &data) { + return documentFromWeb(data, thumbnailLocation); + }); } DocumentData *Session::documentFromWeb( const MTPDwebDocument &data, - ImagePtr thumb) { - // #TODO optimize thumb + const ImageLocation &thumbnailLocation) { const auto result = document( rand_value(), uint64(0), @@ -2497,7 +2477,7 @@ DocumentData *Session::documentFromWeb( data.vattributes().v, data.vmime_type().v, QByteArray(), - StorageImageLocation(), + ImageWithLocation{ .location = thumbnailLocation }, MTP::maindc(), int32(0)); // data.vsize().v result->setWebLocation(WebFileLocation( @@ -2508,8 +2488,7 @@ DocumentData *Session::documentFromWeb( DocumentData *Session::documentFromWeb( const MTPDwebDocumentNoProxy &data, - ImagePtr thumb) { - // #TODO optimize thumb + const ImageLocation &thumbnailLocation) { const auto result = document( rand_value(), uint64(0), @@ -2518,7 +2497,7 @@ DocumentData *Session::documentFromWeb( data.vattributes().v, data.vmime_type().v, QByteArray(), - StorageImageLocation(), + ImageWithLocation{ .location = thumbnailLocation }, MTP::maindc(), int32(0)); // data.vsize().v result->setContentUrl(qs(data.vurl())); @@ -2538,8 +2517,10 @@ void Session::documentApplyFields( const MTPDdocument &data) { const auto inlineThumbnailBytes = FindDocumentInlineThumbnail(data); const auto thumbnailSize = FindDocumentThumbnail(data); - // #TODO optimize - const auto thumbnail = Images::Create(data, thumbnailSize)->location(); + const auto prepared = Images::FromPhotoSize( + _session, + data, + thumbnailSize); documentApplyFields( document, data.vaccess_hash().v, @@ -2548,7 +2529,7 @@ void Session::documentApplyFields( data.vattributes().v, qs(data.vmime_type()), inlineThumbnailBytes, - thumbnail, + prepared, data.vdc_id().v, data.vsize().v); } @@ -2561,7 +2542,7 @@ void Session::documentApplyFields( const QVector &attributes, const QString &mime, const QByteArray &inlineThumbnailBytes, - const StorageImageLocation &thumbnailLocation, + const ImageWithLocation &thumbnail, int32 dc, int32 size) { if (!date) { @@ -2569,7 +2550,7 @@ void Session::documentApplyFields( } document->date = date; document->setMimeString(mime); - document->updateThumbnails(inlineThumbnailBytes, thumbnailLocation); + document->updateThumbnails(inlineThumbnailBytes, thumbnail); document->size = size; document->setattributes(attributes); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index c654b40f3..99ec73988 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -490,7 +490,7 @@ public: not_null processDocument(const MTPDdocument &data); not_null processDocument( const MTPdocument &data, - QImage &&thumb); + const ImageWithLocation &thumbnail); [[nodiscard]] not_null document( DocumentId id, const uint64 &access, @@ -499,7 +499,7 @@ public: const QVector &attributes, const QString &mime, const QByteArray &inlineThumbnailBytes, - const StorageImageLocation &thumbnailLocation, + const ImageWithLocation &thumbnail, int32 dc, int32 size); void documentConvert( @@ -507,7 +507,7 @@ public: const MTPDocument &data); [[nodiscard]] DocumentData *documentFromWeb( const MTPWebDocument &data, - ImagePtr thumb); + const ImageLocation &thumbnailLocation); [[nodiscard]] not_null webpage(WebPageId id); not_null processWebpage(const MTPWebPage &data); @@ -753,15 +753,15 @@ private: const QVector &attributes, const QString &mime, const QByteArray &inlineThumbnailBytes, - const StorageImageLocation &thumbnailLocation, + const ImageWithLocation &thumbnail, int32 dc, int32 size); DocumentData *documentFromWeb( const MTPDwebDocument &data, - ImagePtr thumb); + const ImageLocation &thumbnailLocation); DocumentData *documentFromWeb( const MTPDwebDocumentNoProxy &data, - ImagePtr thumb); + const ImageLocation &thumbnailLocation); void webpageApplyFields( not_null page, diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp index eccf932f0..6e7379893 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/file_utilities.h" #include "core/mime_type.h" #include "ui/image/image.h" +#include "ui/image/image_location_factory.h" #include "mainwidget.h" #include "main/main_session.h" @@ -40,7 +41,9 @@ QString GetContentUrl(const MTPWebDocument &document) { Result::Result(const Creator &creator) : _queryId(creator.queryId), _type(creator.type) { } -std::unique_ptr Result::create(uint64 queryId, const MTPBotInlineResult &mtpData) { +std::unique_ptr Result::create( + uint64 queryId, + const MTPBotInlineResult &mtpData) { using StringToTypeMap = QMap; static StaticNeverFreedPointer stringToTypeMap{ ([]() -> StringToTypeMap* { auto result = std::make_unique(); @@ -94,7 +97,9 @@ std::unique_ptr Result::create(uint64 queryId, const MTPBotInlineResult } else { result->_document = Auth().data().documentFromWeb( result->adjustAttributes(*content), - result->_thumb); + (r.vthumb() + ? Images::FromWebDocument(*r.vthumb()) + : ImageLocation())); } } message = &r.vsend_message(); diff --git a/Telegram/SourceFiles/storage/file_download.cpp b/Telegram/SourceFiles/storage/file_download.cpp index 8a64b833e..4ccce49d6 100644 --- a/Telegram/SourceFiles/storage/file_download.cpp +++ b/Telegram/SourceFiles/storage/file_download.cpp @@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "core/application.h" #include "storage/localstorage.h" +#include "storage/file_download_mtproto.h" +#include "storage/file_download_web.h" #include "platform/platform_file_utilities.h" #include "main/main_session.h" #include "apiwrap.h" @@ -23,6 +25,67 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "facades.h" #include "app.h" +namespace { + +class FromMemoryLoader final : public FileLoader { +public: + FromMemoryLoader( + const QByteArray &data, + const QString &toFile, + int32 size, + LocationType locationType, + LoadToCacheSetting toCache, + LoadFromCloudSetting fromCloud, + bool autoLoading, + uint8 cacheTag); + +private: + Storage::Cache::Key cacheKey() const override; + std::optional fileLocationKey() const override; + void cancelHook() override; + void startLoading() override; + + QByteArray _data; + +}; + +FromMemoryLoader::FromMemoryLoader( + const QByteArray &data, + const QString &toFile, + int32 size, + LocationType locationType, + LoadToCacheSetting toCache, + LoadFromCloudSetting fromCloud, + bool autoLoading, + uint8 cacheTag +) : FileLoader( + toFile, + size, + locationType, + toCache, + fromCloud, + autoLoading, + cacheTag) +, _data(data) { +} + +Storage::Cache::Key FromMemoryLoader::cacheKey() const { + return {}; +} + +std::optional FromMemoryLoader::fileLocationKey() const { + return std::nullopt; +} + +void FromMemoryLoader::cancelHook() { +} + +void FromMemoryLoader::startLoading() { + finishWithBytes(_data); +} + +} // namespace + FileLoader::FileLoader( const QString &toFile, int32 size, @@ -220,14 +283,14 @@ bool FileLoader::tryLoadLocal() { return true; } - const auto weak = base::make_weak(this); if (_toCache == LoadToCacheAsWell) { - loadLocal(cacheKey()); - notifyAboutProgress(); + const auto key = cacheKey(); + if (key.low || key.high) { + loadLocal(key); + notifyAboutProgress(); + } } - if (!weak) { - return false; - } else if (_localStatus != LocalStatus::NotTried) { + if (_localStatus != LocalStatus::NotTried) { return _finished; } else if (_localLoading) { _localStatus = LocalStatus::Loading; @@ -361,8 +424,10 @@ bool FileLoader::finalizeResult() { Local::writeFileLocation(*key, FileLocation(_filename)); } } + const auto key = cacheKey(); if ((_toCache == LoadToCacheAsWell) - && (_data.size() <= Storage::kMaxFileInMemory)) { + && (_data.size() <= Storage::kMaxFileInMemory) + && (key.low || key.high)) { _session->data().cache().put( cacheKey(), Storage::Cache::Database::TaggedValue( @@ -374,3 +439,62 @@ bool FileLoader::finalizeResult() { _updates.fire_done(); return true; } + +std::unique_ptr CreateFileLoader( + const DownloadLocation &location, + Data::FileOrigin origin, + const QString &toFile, + int size, + LocationType locationType, + LoadToCacheSetting toCache, + LoadFromCloudSetting fromCloud, + bool autoLoading, + uint8 cacheTag) { + auto result = std::unique_ptr(); + location.data.match([&](const StorageFileLocation &data) { + result = std::make_unique( + data, + origin, + locationType, + toFile, + size, + toCache, + fromCloud, + autoLoading, + cacheTag); + }, [&](const WebFileLocation &data) { + result = std::make_unique( + data, + size, + fromCloud, + autoLoading, + cacheTag); + }, [&](const GeoPointLocation &data) { + result = std::make_unique( + data, + size, + fromCloud, + autoLoading, + cacheTag); + }, [&](const PlainUrlLocation &data) { + result = std::make_unique( + data.url, + toFile, + fromCloud, + autoLoading, + cacheTag); + }, [&](const InMemoryLocation &data) { + result = std::make_unique( + data.bytes, + toFile, + size, + locationType, + toCache, + fromCloud, + autoLoading, + cacheTag); + }); + + Ensures(result != nullptr); + return result; +} diff --git a/Telegram/SourceFiles/storage/file_download.h b/Telegram/SourceFiles/storage/file_download.h index d8d0a667b..af597046f 100644 --- a/Telegram/SourceFiles/storage/file_download.h +++ b/Telegram/SourceFiles/storage/file_download.h @@ -164,3 +164,14 @@ protected: mutable QImage _imageData; }; + +[[nodiscard]] std::unique_ptr CreateFileLoader( + const DownloadLocation &location, + Data::FileOrigin origin, + const QString &toFile, + int size, + LocationType locationType, + LoadToCacheSetting toCache, + LoadFromCloudSetting fromCloud, + bool autoLoading, + uint8 cacheTag); diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp index 66d06db5e..28282bd41 100644 --- a/Telegram/SourceFiles/storage/file_upload.cpp +++ b/Telegram/SourceFiles/storage/file_upload.cpp @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document_media.h" #include "data/data_photo.h" #include "data/data_session.h" +#include "ui/image/image_location_factory.h" +#include "core/mime_type.h" #include "main/main_session.h" namespace Storage { @@ -44,6 +46,10 @@ constexpr auto kUploadRequestInterval = crl::time(500); // How much time without upload causes additional session kill. constexpr auto kKillSessionTimeout = 15 * crl::time(000); +[[nodiscard]] const char *ThumbnailFormat(const QString &mime) { + return Core::IsMimeSticker(mime) ? "WEBP" : "JPG"; +} + } // namespace struct Uploader::File { @@ -160,7 +166,9 @@ void Uploader::uploadMedia( ? Auth().data().processDocument(media.document) : Auth().data().processDocument( media.document, - base::duplicate(media.photoThumbs.front().second)); + Images::FromImageInMemory( + media.photoThumbs.front().second, + "JPG")); if (!media.data.isEmpty()) { document->setDataAndCache(media.data); if (media.type == SendMediaType::ThemeFile) { @@ -191,13 +199,18 @@ void Uploader::upload( ? Auth().data().processDocument(file->document) : Auth().data().processDocument( file->document, - std::move(file->thumb)); + Images::FromImageInMemory( + file->thumb, + ThumbnailFormat(file->filemime))); document->uploadingData = std::make_unique( document->size); - if (!file->goodThumbnail.isNull()) { - if (const auto active = document->activeMediaView()) { + if (const auto active = document->activeMediaView()) { + if (!file->goodThumbnail.isNull()) { active->setGoodThumbnail(std::move(file->goodThumbnail)); } + if (!file->thumb.isNull()) { + active->setThumbnail(file->thumb); + } } if (!file->goodThumbnailBytes.isEmpty()) { document->owner().cache().putIfEmpty( @@ -208,13 +221,13 @@ void Uploader::upload( } if (!file->content.isEmpty()) { document->setDataAndCache(file->content); - if (file->type == SendMediaType::ThemeFile) { - document->checkWallPaperProperties(); - } } if (!file->filepath.isEmpty()) { document->setLocation(FileLocation(file->filepath)); } + if (file->type == SendMediaType::ThemeFile) { + document->checkWallPaperProperties(); + } } queue.emplace(msgId, File(file)); sendNext(); diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index bfc611b03..b4421ae6e 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -3945,7 +3945,7 @@ void importOldRecentStickers() { attributes, mime, QByteArray(), - StorageImageLocation(), + ImageWithLocation(), dc, size); if (!doc->sticker()) { diff --git a/Telegram/SourceFiles/storage/serialize_common.cpp b/Telegram/SourceFiles/storage/serialize_common.cpp index ad8aadc07..d8d38fe0c 100644 --- a/Telegram/SourceFiles/storage/serialize_common.cpp +++ b/Telegram/SourceFiles/storage/serialize_common.cpp @@ -96,6 +96,34 @@ std::optional readStorageImageLocation( : std::nullopt; } +int imageLocationSize(const ImageLocation &location) { + // Modern image location tag + (size + content) of the serialization. + return sizeof(qint32) * 2 + location.serializeSize(); +} + +void writeImageLocation(QDataStream &stream, const ImageLocation &location) { + stream << kModernImageLocationTag << location.serialize(); +} + +std::optional readImageLocation( + int streamAppVersion, + QDataStream &stream) { + const auto legacy = readLegacyStorageImageLocationOrTag( + streamAppVersion, + stream); + if (legacy) { + return ImageLocation( + DownloadLocation{ legacy->file() }, + legacy->width(), + legacy->height()); + } + auto serialized = QByteArray(); + stream >> serialized; + return (stream.status() == QDataStream::Ok) + ? ImageLocation::FromSerialized(serialized) + : std::nullopt; +} + uint32 peerSize(not_null peer) { uint32 result = sizeof(quint64) + sizeof(quint64) diff --git a/Telegram/SourceFiles/storage/serialize_common.h b/Telegram/SourceFiles/storage/serialize_common.h index a1acb9b6b..b0aa7867b 100644 --- a/Telegram/SourceFiles/storage/serialize_common.h +++ b/Telegram/SourceFiles/storage/serialize_common.h @@ -102,6 +102,15 @@ std::optional readStorageImageLocation( int streamAppVersion, QDataStream &stream); +int imageLocationSize(const ImageLocation &location); +void writeImageLocation(QDataStream &stream, const ImageLocation &location); + +// NB! This method can return StorageFileLocation with Type::Generic! +// The reader should discard it or convert to one of the valid modern types. +std::optional readImageLocation( + int streamAppVersion, + QDataStream &stream); + template inline T read(QDataStream &stream) { auto result = T(); diff --git a/Telegram/SourceFiles/storage/serialize_document.cpp b/Telegram/SourceFiles/storage/serialize_document.cpp index c29391514..6fae8eb9e 100644 --- a/Telegram/SourceFiles/storage/serialize_document.cpp +++ b/Telegram/SourceFiles/storage/serialize_document.cpp @@ -46,11 +46,10 @@ void Document::writeToStream(QDataStream &stream, DocumentData *document) { stream << qint32(StickerSetTypeEmpty); } break; } - writeStorageImageLocation(stream, document->_thumbnailLocation); } else { stream << qint32(document->getDuration()); - writeStorageImageLocation(stream, document->thumbnailLocation()); } + writeImageLocation(stream, document->thumbnailLocation()); } DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream &stream, const StickerSetInfo *info) { @@ -77,14 +76,11 @@ DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream & } qint32 duration = -1; - std::optional thumb; + std::optional thumb; if (type == StickerDocument) { QString alt; qint32 typeOfSet; stream >> alt >> typeOfSet; - - thumb = readStorageImageLocation(streamAppVersion, stream); - if (typeOfSet == StickerSetTypeEmpty) { attributes.push_back(MTP_documentAttributeSticker(MTP_flags(0), MTP_string(alt), MTP_inputStickerSetEmpty(), MTPMaskCoords())); } else if (info) { @@ -113,8 +109,8 @@ DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream & if (type == AnimatedDocument) { attributes.push_back(MTP_documentAttributeAnimated()); } - thumb = readStorageImageLocation(streamAppVersion, stream); } + thumb = readImageLocation(streamAppVersion, stream); if (width > 0 && height > 0) { if (duration >= 0) { auto flags = MTPDdocumentAttributeVideo::Flags(0); @@ -127,9 +123,12 @@ DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream & } } + const auto storage = base::get_if( + &thumb->file().data); if ((!dc && !access) || !thumb - || (thumb->valid() && !thumb->file().isDocumentThumbnail())) { + || (thumb->valid() + && (!storage || !storage->isDocumentThumbnail()))) { stream.setStatus(QDataStream::ReadCorruptData); // We can't convert legacy thumbnail location to modern, because // size letter ('s' or 'm') is lost, it was not saved in legacy. @@ -143,7 +142,7 @@ DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream & attributes, mime, QByteArray(), - *thumb, + ImageWithLocation{ .location = *thumb }, dc, size); } @@ -176,7 +175,7 @@ int Document::sizeInStream(DocumentData *document) { result += sizeof(qint32); } // + thumb loc - result += Serialize::storageImageLocationSize(document->thumbnailLocation()); + result += Serialize::imageLocationSize(document->thumbnailLocation()); return result; } diff --git a/Telegram/SourceFiles/ui/image/image.cpp b/Telegram/SourceFiles/ui/image/image.cpp index 7d3e09fce..406bd9537 100644 --- a/Telegram/SourceFiles/ui/image/image.cpp +++ b/Telegram/SourceFiles/ui/image/image.cpp @@ -61,54 +61,57 @@ uint64 SinglePixKey(Options options) { } // namespace -QImage FromInlineBytes(const QByteArray &bytes) { +QByteArray ExpandInlineBytes(const QByteArray &bytes) { if (bytes.size() < 3 || bytes[0] != '\x01') { - return QImage(); + return QByteArray(); } const char header[] = "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49" -"\x46\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xdb\x00\x43\x00\x28\x1c" -"\x1e\x23\x1e\x19\x28\x23\x21\x23\x2d\x2b\x28\x30\x3c\x64\x41\x3c\x37\x37" -"\x3c\x7b\x58\x5d\x49\x64\x91\x80\x99\x96\x8f\x80\x8c\x8a\xa0\xb4\xe6\xc3" -"\xa0\xaa\xda\xad\x8a\x8c\xc8\xff\xcb\xda\xee\xf5\xff\xff\xff\x9b\xc1\xff" -"\xff\xff\xfa\xff\xe6\xfd\xff\xf8\xff\xdb\x00\x43\x01\x2b\x2d\x2d\x3c\x35" -"\x3c\x76\x41\x41\x76\xf8\xa5\x8c\xa5\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8" -"\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8" -"\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8" -"\xf8\xf8\xf8\xf8\xf8\xff\xc0\x00\x11\x08\x00\x00\x00\x00\x03\x01\x22\x00" -"\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01" -"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08" -"\x09\x0a\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05" -"\x04\x04\x00\x00\x01\x7d\x01\x02\x03\x00\x04\x11\x05\x12\x21\x31\x41\x06" -"\x13\x51\x61\x07\x22\x71\x14\x32\x81\x91\xa1\x08\x23\x42\xb1\xc1\x15\x52" -"\xd1\xf0\x24\x33\x62\x72\x82\x09\x0a\x16\x17\x18\x19\x1a\x25\x26\x27\x28" -"\x29\x2a\x34\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49\x4a\x53" -"\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74\x75" -"\x76\x77\x78\x79\x7a\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96" -"\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6" -"\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6" -"\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4" -"\xf5\xf6\xf7\xf8\xf9\xfa\xff\xc4\x00\x1f\x01\x00\x03\x01\x01\x01\x01\x01" -"\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08" -"\x09\x0a\x0b\xff\xc4\x00\xb5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05" -"\x04\x04\x00\x01\x02\x77\x00\x01\x02\x03\x11\x04\x05\x21\x31\x06\x12\x41" -"\x51\x07\x61\x71\x13\x22\x32\x81\x08\x14\x42\x91\xa1\xb1\xc1\x09\x23\x33" -"\x52\xf0\x15\x62\x72\xd1\x0a\x16\x24\x34\xe1\x25\xf1\x17\x18\x19\x1a\x26" -"\x27\x28\x29\x2a\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49\x4a" -"\x53\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74" -"\x75\x76\x77\x78\x79\x7a\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94" -"\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4" -"\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4" -"\xd5\xd6\xd7\xd8\xd9\xda\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4" -"\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00" -"\x3f\x00"; + "\x46\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xdb\x00\x43\x00\x28\x1c" + "\x1e\x23\x1e\x19\x28\x23\x21\x23\x2d\x2b\x28\x30\x3c\x64\x41\x3c\x37\x37" + "\x3c\x7b\x58\x5d\x49\x64\x91\x80\x99\x96\x8f\x80\x8c\x8a\xa0\xb4\xe6\xc3" + "\xa0\xaa\xda\xad\x8a\x8c\xc8\xff\xcb\xda\xee\xf5\xff\xff\xff\x9b\xc1\xff" + "\xff\xff\xfa\xff\xe6\xfd\xff\xf8\xff\xdb\x00\x43\x01\x2b\x2d\x2d\x3c\x35" + "\x3c\x76\x41\x41\x76\xf8\xa5\x8c\xa5\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8" + "\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8" + "\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8" + "\xf8\xf8\xf8\xf8\xf8\xff\xc0\x00\x11\x08\x00\x00\x00\x00\x03\x01\x22\x00" + "\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01" + "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08" + "\x09\x0a\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05" + "\x04\x04\x00\x00\x01\x7d\x01\x02\x03\x00\x04\x11\x05\x12\x21\x31\x41\x06" + "\x13\x51\x61\x07\x22\x71\x14\x32\x81\x91\xa1\x08\x23\x42\xb1\xc1\x15\x52" + "\xd1\xf0\x24\x33\x62\x72\x82\x09\x0a\x16\x17\x18\x19\x1a\x25\x26\x27\x28" + "\x29\x2a\x34\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49\x4a\x53" + "\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74\x75" + "\x76\x77\x78\x79\x7a\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96" + "\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6" + "\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6" + "\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4" + "\xf5\xf6\xf7\xf8\xf9\xfa\xff\xc4\x00\x1f\x01\x00\x03\x01\x01\x01\x01\x01" + "\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08" + "\x09\x0a\x0b\xff\xc4\x00\xb5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05" + "\x04\x04\x00\x01\x02\x77\x00\x01\x02\x03\x11\x04\x05\x21\x31\x06\x12\x41" + "\x51\x07\x61\x71\x13\x22\x32\x81\x08\x14\x42\x91\xa1\xb1\xc1\x09\x23\x33" + "\x52\xf0\x15\x62\x72\xd1\x0a\x16\x24\x34\xe1\x25\xf1\x17\x18\x19\x1a\x26" + "\x27\x28\x29\x2a\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49\x4a" + "\x53\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74" + "\x75\x76\x77\x78\x79\x7a\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94" + "\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4" + "\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4" + "\xd5\xd6\xd7\xd8\xd9\xda\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4" + "\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00" + "\x3f\x00"; const char footer[] = "\xff\xd9"; auto real = QByteArray(header, sizeof(header) - 1); real[164] = bytes[1]; real[166] = bytes[2]; - const auto ready = real + return real + bytes.mid(3) + QByteArray::fromRawData(footer, sizeof(footer) - 1); - return App::readImage(ready); +} + +QImage FromInlineBytes(const QByteArray &bytes) { + return App::readImage(ExpandInlineBytes(bytes)); } void ClearRemote() { @@ -364,7 +367,7 @@ ImagePtr Create(const MTPDdocument &document, const MTPPhotoSize &size) { return CreateFromPhotoSize(create, size); } -QSize getImageSize(const QVector &attributes) { +QSize GetSizeForDocument(const QVector &attributes) { for (const auto &attribute : attributes) { if (attribute.type() == mtpc_documentAttributeImageSize) { auto &size = attribute.c_documentAttributeImageSize(); @@ -375,7 +378,7 @@ QSize getImageSize(const QVector &attributes) { } ImagePtr Create(const MTPDwebDocument &document) { - const auto size = getImageSize(document.vattributes().v); + const auto size = GetSizeForDocument(document.vattributes().v); if (size.isEmpty()) { return ImagePtr(); } @@ -393,7 +396,7 @@ ImagePtr Create(const MTPDwebDocument &document) { } ImagePtr Create(const MTPDwebDocumentNoProxy &document) { - const auto size = getImageSize(document.vattributes().v); + const auto size = GetSizeForDocument(document.vattributes().v); if (size.isEmpty()) { return ImagePtr(); } @@ -402,7 +405,7 @@ ImagePtr Create(const MTPDwebDocumentNoProxy &document) { } ImagePtr Create(const MTPDwebDocument &document, QSize box) { - //const auto size = getImageSize(document.vattributes().v); + //const auto size = GetSizeForDocument(document.vattributes().v); //if (size.isEmpty()) { // return ImagePtr(); //} @@ -419,7 +422,7 @@ ImagePtr Create(const MTPDwebDocument &document, QSize box) { } ImagePtr Create(const MTPDwebDocumentNoProxy &document, QSize box) { - //const auto size = getImageSize(document.vattributes().v); + //const auto size = GetSizeForDocument(document.vattributes().v); //if (size.isEmpty()) { // return ImagePtr(); //} diff --git a/Telegram/SourceFiles/ui/image/image.h b/Telegram/SourceFiles/ui/image/image.h index cc548f271..2d49bd33b 100644 --- a/Telegram/SourceFiles/ui/image/image.h +++ b/Telegram/SourceFiles/ui/image/image.h @@ -13,11 +13,15 @@ class HistoryItem; namespace Images { +[[nodiscard]] QByteArray ExpandInlineBytes(const QByteArray &bytes); [[nodiscard]] QImage FromInlineBytes(const QByteArray &bytes); void ClearRemote(); void ClearAll(); +[[nodiscard]] QSize GetSizeForDocument( + const QVector &attributes); + ImagePtr Create(const QString &file, QByteArray format); ImagePtr Create(const QString &url, QSize box); ImagePtr Create(const QString &url, int width, int height); diff --git a/Telegram/SourceFiles/ui/image/image_location.h b/Telegram/SourceFiles/ui/image/image_location.h index 9022a9173..1eeac47d4 100644 --- a/Telegram/SourceFiles/ui/image/image_location.h +++ b/Telegram/SourceFiles/ui/image/image_location.h @@ -504,6 +504,13 @@ private: }; +struct ImageWithLocation { + ImageLocation location; + int bytesCount = 0; + QByteArray bytes; + QImage preloaded; +}; + class Image; class ImagePtr { public: @@ -599,7 +606,9 @@ private: }; inline bool operator==(const FileLocation &a, const FileLocation &b) { - return (a.name() == b.name()) && (a.modified == b.modified) && (a.size == b.size); + return (a.name() == b.name()) + && (a.modified == b.modified) + && (a.size == b.size); } inline bool operator!=(const FileLocation &a, const FileLocation &b) { return !(a == b); diff --git a/Telegram/SourceFiles/ui/image/image_location_factory.cpp b/Telegram/SourceFiles/ui/image/image_location_factory.cpp new file mode 100644 index 000000000..220c1adeb --- /dev/null +++ b/Telegram/SourceFiles/ui/image/image_location_factory.cpp @@ -0,0 +1,124 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/image/image_location_factory.h" + +#include "ui/image/image.h" +#include "main/main_session.h" + +#include + +namespace Images { + +ImageWithLocation FromPhotoSize( + not_null session, + const MTPDdocument &document, + const MTPPhotoSize &size) { + return size.match([&](const MTPDphotoSize &data) { + return ImageWithLocation{ + .location = ImageLocation( + DownloadLocation{ StorageFileLocation( + document.vdc_id().v, + session->userId(), + MTP_inputDocumentFileLocation( + document.vid(), + document.vaccess_hash(), + document.vfile_reference(), + data.vtype())) }, + data.vw().v, + data.vh().v), + .bytesCount = data.vsize().v + }; + }, [&](const MTPDphotoCachedSize &data) { + const auto bytes = qba(data.vbytes()); + return ImageWithLocation{ + .location = ImageLocation( + DownloadLocation{ StorageFileLocation( + document.vdc_id().v, + session->userId(), + MTP_inputDocumentFileLocation( + document.vid(), + document.vaccess_hash(), + document.vfile_reference(), + data.vtype())) }, + data.vw().v, + data.vh().v), + .bytesCount = bytes.size(), + .bytes = bytes + }; + }, [&](const MTPDphotoStrippedSize &data) { + return ImageWithLocation(); + //const auto bytes = ExpandInlineBytes(qba(data.vbytes())); + //return ImageWithLocation{ + // .location = ImageLocation( + // DownloadLocation{ StorageFileLocation( + // document.vdc_id().v, + // session->userId(), + // MTP_inputDocumentFileLocation( + // document.vid(), + // document.vaccess_hash(), + // document.vfile_reference(), + // data.vtype())) }, + // width, // ??? + // height), // ??? + // .bytesCount = bytes.size(), + // .bytes = bytes + //}; + }, [&](const MTPDphotoSizeEmpty &) { + return ImageWithLocation(); + }); +} + +ImageWithLocation FromImageInMemory( + const QImage &image, + const char *format) { + if (image.isNull()) { + return ImageWithLocation(); + } + auto bytes = QByteArray(); + auto buffer = QBuffer(&bytes); + image.save(&buffer, format); + return ImageWithLocation{ + .location = ImageLocation( + DownloadLocation{ InMemoryLocation{ bytes } }, + image.width(), + image.height()), + .bytesCount = bytes.size(), + .bytes = bytes, + .preloaded = image + }; +} + +ImageLocation FromWebDocument(const MTPWebDocument &document) { + return document.match([](const MTPDwebDocument &data) { + const auto size = GetSizeForDocument(data.vattributes().v); + + // We don't use size from WebDocument, because it is not reliable. + // It can be > 0 and different from the real size + // that we get in upload.WebFile result. + //auto filesize = 0; // data.vsize().v; + return ImageLocation( + DownloadLocation{ WebFileLocation( + data.vurl().v, + data.vaccess_hash().v) }, + size.width(), + size.height()); + }, [](const MTPDwebDocumentNoProxy &data) { + const auto size = GetSizeForDocument(data.vattributes().v); + + // We don't use size from WebDocument, because it is not reliable. + // It can be > 0 and different from the real size + // that we get in upload.WebFile result. + //auto filesize = 0; // data.vsize().v; + return ImageLocation( + DownloadLocation{ PlainUrlLocation{ qs(data.vurl()) } }, + size.width(), + size.height()); + }); +} + +} // namespace Images diff --git a/Telegram/SourceFiles/ui/image/image_location_factory.h b/Telegram/SourceFiles/ui/image/image_location_factory.h new file mode 100644 index 000000000..82dfc9c09 --- /dev/null +++ b/Telegram/SourceFiles/ui/image/image_location_factory.h @@ -0,0 +1,27 @@ +/* +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 "ui/image/image_location.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Images { + +[[nodiscard]] ImageWithLocation FromPhotoSize( + not_null session, + const MTPDdocument &document, + const MTPPhotoSize &size); +[[nodiscard]] ImageWithLocation FromImageInMemory( + const QImage &image, + const char *format); +[[nodiscard]] ImageLocation FromWebDocument(const MTPWebDocument &document); + +} // namespace Images