diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index fd33b51a3..fc56207bc 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -393,6 +393,8 @@ PRIVATE
     data/data_peer_values.h
     data/data_photo.cpp
     data/data_photo.h
+    data/data_photo_media.cpp
+    data/data_photo_media.h
     data/data_poll.cpp
     data/data_poll.h
     data/data_pts_waiter.cpp
diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp
index 4839c5269..34be632f2 100644
--- a/Telegram/SourceFiles/boxes/confirm_box.cpp
+++ b/Telegram/SourceFiles/boxes/confirm_box.cpp
@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_user.h"
 #include "data/data_file_origin.h"
 #include "data/data_histories.h"
+#include "data/data_photo_media.h"
 #include "base/unixtime.h"
 #include "main/main_session.h"
 #include "observer_peer.h"
@@ -894,12 +895,12 @@ ConfirmInviteBox::ConfirmInviteBox(
 
 	const auto photo = _session->data().processPhoto(data.vphoto());
 	if (!photo->isNull()) {
-		_photo = photo->thumbnail();
-		if (!_photo->loaded()) {
+		_photo = photo->createMediaView();
+		_photo->wanted(Data::PhotoSize::Small, Data::FileOrigin());
+		if (!_photo->image(Data::PhotoSize::Small)) {
 			subscribe(_session->downloaderTaskFinished(), [=] {
 				update();
 			});
-			_photo->load(Data::FileOrigin());
 		}
 	} else {
 		_photoEmpty = std::make_unique<Ui::EmptyUserpic>(
@@ -972,14 +973,16 @@ void ConfirmInviteBox::paintEvent(QPaintEvent *e) {
 	Painter p(this);
 
 	if (_photo) {
-		p.drawPixmap(
-			(width() - st::confirmInvitePhotoSize) / 2,
-			st::confirmInvitePhotoTop,
-			_photo->pixCircled(
-				Data::FileOrigin(),
-				st::confirmInvitePhotoSize,
-				st::confirmInvitePhotoSize));
-	} else {
+		if (const auto image = _photo->image(Data::PhotoSize::Small)) {
+			p.drawPixmap(
+				(width() - st::confirmInvitePhotoSize) / 2,
+				st::confirmInvitePhotoTop,
+				image->pixCircled(
+					Data::FileOrigin(),
+					st::confirmInvitePhotoSize,
+					st::confirmInvitePhotoSize));
+		}
+	} else if (_photoEmpty) {
 		_photoEmpty->paint(
 			p,
 			(width() - st::confirmInvitePhotoSize) / 2,
diff --git a/Telegram/SourceFiles/boxes/confirm_box.h b/Telegram/SourceFiles/boxes/confirm_box.h
index d3c53edda..daf242b16 100644
--- a/Telegram/SourceFiles/boxes/confirm_box.h
+++ b/Telegram/SourceFiles/boxes/confirm_box.h
@@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "boxes/abstract_box.h"
 #include "mtproto/mtproto_rpc_sender.h"
 
+namespace Data {
+class PhotoMedia;
+} // namespace Data
+
 namespace Main {
 class Session;
 } // namespace Main
@@ -230,7 +234,7 @@ private:
 	Fn<void()> _submit;
 	object_ptr<Ui::FlatLabel> _title;
 	object_ptr<Ui::FlatLabel> _status;
-	Image *_photo = nullptr;
+	std::shared_ptr<Data::PhotoMedia> _photo;
 	std::unique_ptr<Ui::EmptyUserpic> _photoEmpty;
 	std::vector<not_null<UserData*>> _participants;
 	bool _isChannel = false;
diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
index 115d247f7..28af6957f 100644
--- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "data/data_streaming.h"
 #include "data/data_file_origin.h"
+#include "data/data_photo_media.h"
 #include "data/data_document_media.h"
 #include "history/history.h"
 #include "history/history_item.h"
@@ -55,6 +56,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 namespace {
 
 using namespace ::Media::Streaming;
+using Data::PhotoSize;
 
 } // namespace
 
@@ -75,9 +77,10 @@ EditCaptionBox::EditCaptionBox(
 
 	const auto media = item->media();
 	if (const auto photo = media->photo()) {
-		_photo = true;
-		dimensions = QSize(photo->width(), photo->height());
-		image = photo->large();
+		_photoMedia = photo->createMediaView();
+		_photoMedia->wanted(PhotoSize::Large, _msgId);
+		image = _photoMedia->image(PhotoSize::Large);
+		dimensions = _photoMedia->size(PhotoSize::Large);
 	} else if (const auto document = media->document()) {
 		_documentMedia = document->createMediaView();
 		_documentMedia->thumbnailWanted(_msgId);
@@ -97,7 +100,10 @@ EditCaptionBox::EditCaptionBox(
 	}
 	const auto editData = PrepareEditText(item);
 
-	if (!_animated && (dimensions.isEmpty() || _documentMedia || !image)) {
+	if (!_animated
+		&& (dimensions.isEmpty()
+			|| _documentMedia
+			|| (!_photoMedia && !image))) {
 		if (!image) {
 			_thumbw = 0;
 		} else {
@@ -139,7 +145,7 @@ EditCaptionBox::EditCaptionBox(
 			_refreshThumbnail();
 		}
 	} else {
-		if (!image) {
+		if (!image && !_photoMedia) {
 			image = Image::BlankMedia();
 		}
 		auto maxW = 0, maxH = 0;
@@ -177,7 +183,10 @@ EditCaptionBox::EditCaptionBox(
 			maxH = dimensions.height();
 			_thumbnailImage = image;
 			_refreshThumbnail = [=] {
-				_thumb = image->pixNoCache(
+				if (!_thumbnailImage) {
+					return;
+				}
+				_thumb = _thumbnailImage->pixNoCache(
 					_msgId,
 					maxW * cIntRetinaFactor(),
 					maxH * cIntRetinaFactor(),
@@ -253,6 +262,8 @@ EditCaptionBox::EditCaptionBox(
 
 	_thumbnailImageLoaded = _thumbnailImage
 		? _thumbnailImage->loaded()
+		: _photoMedia
+		? false
 		: _documentMedia
 		? !_documentMedia->owner()->hasThumbnail()
 		: true;
@@ -260,6 +271,8 @@ EditCaptionBox::EditCaptionBox(
 		subscribe(_controller->session().downloaderTaskFinished(), [=] {
 			if (_thumbnailImageLoaded) {
 				return;
+			} else if (!_thumbnailImage && _photoMedia) {
+				_thumbnailImage = _photoMedia->image(PhotoSize::Large);
 			} else if (!_thumbnailImage
 				&& _documentMedia
 				&& _documentMedia->owner()->hasThumbnail()) {
diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h
index bf83e3323..7cecf946f 100644
--- a/Telegram/SourceFiles/boxes/edit_caption_box.h
+++ b/Telegram/SourceFiles/boxes/edit_caption_box.h
@@ -22,6 +22,7 @@ class SessionController;
 
 namespace Data {
 class Media;
+class PhotoMedia;
 class DocumentMedia;
 } // namespace Data
 
@@ -103,6 +104,7 @@ private:
 
 	not_null<Window::SessionController*> _controller;
 	FullMsgId _msgId;
+	std::shared_ptr<Data::PhotoMedia> _photoMedia;
 	std::shared_ptr<Data::DocumentMedia> _documentMedia;
 	Image *_thumbnailImage = nullptr;
 	bool _thumbnailImageLoaded = false;
diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp
index 59e7bc53b..b13ad2db6 100644
--- a/Telegram/SourceFiles/calls/calls_panel.cpp
+++ b/Telegram/SourceFiles/calls/calls_panel.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "data/data_user.h"
 #include "data/data_file_origin.h"
+#include "data/data_photo_media.h"
 #include "calls/calls_emoji_fingerprint.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/labels.h"
@@ -299,7 +300,7 @@ QImage Panel::Button::prepareRippleMask() const {
 }
 
 Panel::Panel(not_null<Call*> call)
-:RpWidget(App::wnd())
+: RpWidget(App::wnd())
 , _call(call)
 , _user(call->user())
 , _answerHangupRedial(this, st::callAnswer, &st::callHangup)
@@ -319,6 +320,8 @@ Panel::Panel(not_null<Call*> call)
 	showAndActivate();
 }
 
+Panel::~Panel() = default;
+
 void Panel::showAndActivate() {
 	toggleOpacityAnimation(true);
 	raise();
@@ -515,26 +518,28 @@ void Panel::processUserPhoto() {
 		? _user->owner().photo(_user->userpicPhotoId()).get()
 		: nullptr;
 	if (isGoodUserPhoto(photo)) {
-		photo->large()->load(_user->userpicPhotoOrigin());
-	} else if (_user->userpicPhotoUnknown() || (photo && !photo->date)) {
-		_user->session().api().requestFullPeer(_user);
+		_photo = photo->createMediaView();
+		_photo->wanted(Data::PhotoSize::Large, _user->userpicPhotoOrigin());
+	} else {
+		_photo = nullptr;
+		if (_user->userpicPhotoUnknown() || (photo && !photo->date)) {
+			_user->session().api().requestFullPeer(_user);
+		}
 	}
 	refreshUserPhoto();
 }
 
 void Panel::refreshUserPhoto() {
-	const auto photo = _user->userpicPhotoId()
-		? _user->owner().photo(_user->userpicPhotoId()).get()
-		: nullptr;
-	const auto isNewPhoto = [&](not_null<PhotoData*> photo) {
-		return photo->large()->loaded()
-			&& (photo->id != _userPhotoId || !_userPhotoFull);
-	};
-	if (isGoodUserPhoto(photo) && isNewPhoto(photo)) {
-		_userPhotoId = photo->id;
+	const auto isNewBigPhoto = [&] {
+		return _photo
+			&& _photo->loaded()
+			&& (_photo->owner()->id != _userPhotoId || !_userPhotoFull);
+	}();
+	if (isNewBigPhoto) {
+		_userPhotoId = _photo->owner()->id;
 		_userPhotoFull = true;
 		createUserpicCache(
-			photo->isNull() ? nullptr : photo->large().get(),
+			_photo->image(Data::PhotoSize::Large),
 			_user->userpicPhotoOrigin());
 	} else if (_userPhoto.isNull()) {
 		const auto userpic = _user->currentUserpic();
diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h
index c7ba8feee..518d20225 100644
--- a/Telegram/SourceFiles/calls/calls_panel.h
+++ b/Telegram/SourceFiles/calls/calls_panel.h
@@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/effects/animations.h"
 #include "ui/rp_widget.h"
 
+namespace Data {
+class PhotoMedia;
+} // namespace Data
+
 namespace Ui {
 class IconButton;
 class FlatLabel;
@@ -56,6 +60,7 @@ class Panel
 
 public:
 	Panel(not_null<Call*> call);
+	~Panel();
 
 	void showAndActivate();
 	void replaceCall(not_null<Call*> call);
@@ -111,6 +116,7 @@ private:
 
 	Call *_call = nullptr;
 	not_null<UserData*> _user;
+	std::shared_ptr<Data::PhotoMedia> _photo;
 
 	bool _useTransparency = true;
 	style::margins _padding;
diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
index c181144e4..5398d74c4 100644
--- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "data/data_user.h"
 #include "data/data_file_origin.h"
+#include "data/data_photo_media.h"
 #include "data/data_document_media.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/input_fields.h"
@@ -370,18 +371,23 @@ void GifsListWidget::selectInlineResult(int row, int column) {
 		return;
 	}
 
+	const auto ctrl = (QGuiApplication::keyboardModifiers()
+		== Qt::ControlModifier);
 	auto item = _rows[row].items[column];
 	if (const auto photo = item->getPhoto()) {
-		if (photo->thumbnail()->loaded()) {
+		using Data::PhotoSize;
+		const auto media = photo->activeMediaView();
+		if (ctrl
+			|| (media && media->image(PhotoSize::Thumbnail))
+			|| (media && media->image(PhotoSize::Large))) {
 			_photoChosen.fire_copy(photo);
-		} else if (!photo->thumbnail()->loading()) {
-			photo->thumbnail()->loadEvenCancelled(Data::FileOrigin());
+		} else if (!photo->loading(PhotoSize::Thumbnail)) {
+			photo->load(PhotoSize::Thumbnail, Data::FileOrigin());
 		}
 	} else if (const auto document = item->getDocument()) {
 		const auto media = document->activeMediaView();
 		const auto preview = Data::VideoPreviewState(media.get());
-		if ((media && preview.loaded())
-			|| QGuiApplication::keyboardModifiers() == Qt::ControlModifier) {
+		if (ctrl || (media && preview.loaded())) {
 			_fileChosen.fire_copy(document);
 		} else if (!preview.usingThumbnail()) {
 			if (preview.loading()) {
@@ -551,11 +557,13 @@ void GifsListWidget::clearInlineRows(bool resultsDeleted) {
 	_rows.clear();
 }
 
-GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(DocumentData *doc, int32 position) {
-	auto it = _gifLayouts.find(doc);
+GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(
+		not_null<DocumentData*> document,
+		int32 position) {
+	auto it = _gifLayouts.find(document);
 	if (it == _gifLayouts.cend()) {
-		if (auto layout = LayoutItem::createLayoutGif(this, doc)) {
-			it = _gifLayouts.emplace(doc, std::move(layout)).first;
+		if (auto layout = LayoutItem::createLayoutGif(this, document)) {
+			it = _gifLayouts.emplace(document, std::move(layout)).first;
 			it->second->initDimensions();
 		} else {
 			return nullptr;
@@ -567,7 +575,9 @@ GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(DocumentData *
 	return it->second.get();
 }
 
-GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(InlineResult *result, int32 position) {
+GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(
+		not_null<InlineResult*> result,
+		int32 position) {
 	auto it = _inlineLayouts.find(result);
 	if (it == _inlineLayouts.cend()) {
 		if (auto layout = LayoutItem::createLayout(this, result, _inlineWithThumb)) {
diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h
index 49f1b5797..7a67388b3 100644
--- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h
+++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h
@@ -134,11 +134,19 @@ private:
 	QVector<Row> _rows;
 	void clearInlineRows(bool resultsDeleted);
 
-	std::map<DocumentData*, std::unique_ptr<LayoutItem>> _gifLayouts;
-	LayoutItem *layoutPrepareSavedGif(DocumentData *doc, int32 position);
+	std::map<
+		not_null<DocumentData*>,
+		std::unique_ptr<LayoutItem>> _gifLayouts;
+	LayoutItem *layoutPrepareSavedGif(
+		not_null<DocumentData*> document,
+		int32 position);
 
-	std::map<InlineResult*, std::unique_ptr<LayoutItem>> _inlineLayouts;
-	LayoutItem *layoutPrepareInlineResult(InlineResult *result, int32 position);
+	std::map<
+		not_null<InlineResult*>,
+		std::unique_ptr<LayoutItem>> _inlineLayouts;
+	LayoutItem *layoutPrepareInlineResult(
+		not_null<InlineResult*> result,
+		int32 position);
 
 	bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, Row &row, int32 &sumWidth);
 	bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false);
diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp
index 4a60341f4..51b0bb31d 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp
+++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp
@@ -128,11 +128,6 @@ public:
 	QImage takeLoaded() override;
 	void unload() override;
 
-	void automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item) override;
-	void automaticLoadSettingsChanged() override;
-
 	bool loading() override;
 	bool displayLoading() override;
 	void cancel() override;
@@ -222,14 +217,6 @@ void ImageSource::unload() {
 	_data = QImage();
 }
 
-void ImageSource::automaticLoad(
-	Data::FileOrigin origin,
-	const HistoryItem *item) {
-}
-
-void ImageSource::automaticLoadSettingsChanged() {
-}
-
 bool ImageSource::loading() {
 	return _data.isNull() && _bytes.isEmpty();
 }
diff --git a/Telegram/SourceFiles/data/data_auto_download.cpp b/Telegram/SourceFiles/data/data_auto_download.cpp
index 8dcf882fb..db9d863c9 100644
--- a/Telegram/SourceFiles/data/data_auto_download.cpp
+++ b/Telegram/SourceFiles/data/data_auto_download.cpp
@@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_peer.h"
 #include "data/data_photo.h"
 #include "data/data_document.h"
-#include "ui/image/image_source.h"
-#include "ui/image/image.h"
 
 #include <QtCore/QBuffer>
 
@@ -294,11 +292,11 @@ bool Should(
 bool Should(
 		const Full &data,
 		not_null<PeerData*> peer,
-		not_null<Images::Source*> image) {
+		not_null<PhotoData*> photo) {
 	return data.shouldDownload(
 		SourceFromPeer(peer),
 		Type::Photo,
-		image->bytesSize());
+		photo->imageByteSize(PhotoSize::Large));
 }
 
 bool ShouldAutoPlay(
diff --git a/Telegram/SourceFiles/data/data_auto_download.h b/Telegram/SourceFiles/data/data_auto_download.h
index 7da31d8bd..ab2a6fe4e 100644
--- a/Telegram/SourceFiles/data/data_auto_download.h
+++ b/Telegram/SourceFiles/data/data_auto_download.h
@@ -9,10 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include <array>
 
-namespace Images {
-class Source;
-} // namespace Images
-
 namespace Data {
 namespace AutoDownload {
 
@@ -118,7 +114,7 @@ private:
 [[nodiscard]] bool Should(
 	const Full &data,
 	not_null<PeerData*> peer,
-	not_null<Images::Source*> image);
+	not_null<PhotoData*> photo);
 
 [[nodiscard]] bool ShouldAutoPlay(
 	const Full &data,
diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp
index 7f61a9716..7d88f0378 100644
--- a/Telegram/SourceFiles/data/data_document.cpp
+++ b/Telegram/SourceFiles/data/data_document.cpp
@@ -700,10 +700,10 @@ void DocumentData::loadThumbnail(Data::FileOrigin origin) {
 		_flags |= Flag::ThumbnailFailed;
 	}, [=] {
 		if (_thumbnailLoader && !_thumbnailLoader->cancelled()) {
-			if (auto image = _thumbnailLoader->imageData(); image.isNull()) {
+			if (auto read = _thumbnailLoader->imageData(); read.isNull()) {
 				_flags |= Flag::ThumbnailFailed;
 			} else if (const auto active = activeMediaView()) {
-				active->setThumbnail(std::move(image));
+				active->setThumbnail(std::move(read));
 			}
 		}
 		_thumbnailLoader = nullptr;
diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h
index e7bf68c2d..369a6b622 100644
--- a/Telegram/SourceFiles/data/data_document.h
+++ b/Telegram/SourceFiles/data/data_document.h
@@ -159,8 +159,8 @@ public:
 	[[nodiscard]] bool thumbnailLoading() const;
 	[[nodiscard]] bool thumbnailFailed() const;
 	void loadThumbnail(Data::FileOrigin origin);
-	const ImageLocation &thumbnailLocation() const;
-	int thumbnailByteSize() const;
+	[[nodiscard]] const ImageLocation &thumbnailLocation() const;
+	[[nodiscard]] int thumbnailByteSize() const;
 
 	[[nodiscard]] bool hasVideoThumbnail() const;
 	[[nodiscard]] bool videoThumbnailLoading() const;
diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index 16e3ab789..c10b98b82 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -381,98 +381,98 @@ bool MediaPhoto::updateSentMedia(const MTPMessageMedia &media) {
 	}
 	parent()->history()->owner().photoConvert(_photo, *content);
 
-	if (content->type() != mtpc_photo) {
-		return false;
-	}
-	const auto &photo = content->c_photo();
+	//if (content->type() != mtpc_photo) { // #TODO optimize
+	//	return false;
+	//}
+	//const auto &photo = content->c_photo();
 
-	struct SizeData {
-		MTPstring type = MTP_string();
-		int width = 0;
-		int height = 0;
-		QByteArray bytes;
-	};
-	const auto saveImageToCache = [&](
-			not_null<Image*> image,
-			SizeData size) {
-		Expects(!size.type.v.isEmpty());
+	//struct SizeData {
+	//	MTPstring type = MTP_string();
+	//	int width = 0;
+	//	int height = 0;
+	//	QByteArray bytes;
+	//};
+	//const auto saveImageToCache = [&](
+	//		not_null<Image*> image,
+	//		SizeData size) {
+	//	Expects(!size.type.v.isEmpty());
 
-		const auto key = StorageImageLocation(
-			StorageFileLocation(
-				photo.vdc_id().v,
-				_photo->session().userId(),
-				MTP_inputPhotoFileLocation(
-					photo.vid(),
-					photo.vaccess_hash(),
-					photo.vfile_reference(),
-					size.type)),
-			size.width,
-			size.height);
-		if (!key.valid() || image->isNull() || !image->loaded()) {
-			return;
-		}
-		if (size.bytes.isEmpty()) {
-			size.bytes = image->bytesForCache();
-		}
-		const auto length = size.bytes.size();
-		if (!length || length > Storage::kMaxFileInMemory) {
-			LOG(("App Error: Bad photo data for saving to cache."));
-			return;
-		}
-		parent()->history()->owner().cache().putIfEmpty(
-			key.file().cacheKey(),
-			Storage::Cache::Database::TaggedValue(
-				std::move(size.bytes),
-				Data::kImageCacheTag));
-		image->replaceSource(
-			std::make_unique<Images::StorageSource>(key, length));
-	};
-	auto &sizes = photo.vsizes().v;
-	auto max = 0;
-	auto maxSize = SizeData();
-	for (const auto &data : sizes) {
-		const auto size = data.match([](const MTPDphotoSize &data) {
-			return SizeData{
-				data.vtype(),
-				data.vw().v,
-				data.vh().v,
-				QByteArray()
-			};
-		}, [](const MTPDphotoCachedSize &data) {
-			return SizeData{
-				data.vtype(),
-				data.vw().v,
-				data.vh().v,
-				qba(data.vbytes())
-			};
-		}, [](const MTPDphotoSizeEmpty &) {
-			return SizeData();
-		}, [](const MTPDphotoStrippedSize &data) {
-			// No need to save stripped images to local cache.
-			return SizeData();
-		});
-		const auto letter = size.type.v.isEmpty() ? char(0) : size.type.v[0];
-		if (!letter) {
-			continue;
-		}
-		if (letter == 's') {
-			saveImageToCache(_photo->thumbnailSmall(), size);
-		} else if (letter == 'm') {
-			saveImageToCache(_photo->thumbnail(), size);
-		} else if (letter == 'x' && max < 1) {
-			max = 1;
-			maxSize = size;
-		} else if (letter == 'y' && max < 2) {
-			max = 2;
-			maxSize = size;
-		//} else if (letter == 'w' && max < 3) {
-		//	max = 3;
-		//	maxSize = size;
-		}
-	}
-	if (!maxSize.type.v.isEmpty()) {
-		saveImageToCache(_photo->large(), maxSize);
-	}
+	//	const auto key = StorageImageLocation(
+	//		StorageFileLocation(
+	//			photo.vdc_id().v,
+	//			_photo->session().userId(),
+	//			MTP_inputPhotoFileLocation(
+	//				photo.vid(),
+	//				photo.vaccess_hash(),
+	//				photo.vfile_reference(),
+	//				size.type)),
+	//		size.width,
+	//		size.height);
+	//	if (!key.valid() || image->isNull() || !image->loaded()) {
+	//		return;
+	//	}
+	//	if (size.bytes.isEmpty()) {
+	//		size.bytes = image->bytesForCache();
+	//	}
+	//	const auto length = size.bytes.size();
+	//	if (!length || length > Storage::kMaxFileInMemory) {
+	//		LOG(("App Error: Bad photo data for saving to cache."));
+	//		return;
+	//	}
+	//	parent()->history()->owner().cache().putIfEmpty(
+	//		key.file().cacheKey(),
+	//		Storage::Cache::Database::TaggedValue(
+	//			std::move(size.bytes),
+	//			Data::kImageCacheTag));
+	//	image->replaceSource(
+	//		std::make_unique<Images::StorageSource>(key, length));
+	//};
+	//auto &sizes = photo.vsizes().v;
+	//auto max = 0;
+	//auto maxSize = SizeData();
+	//for (const auto &data : sizes) {
+	//	const auto size = data.match([](const MTPDphotoSize &data) {
+	//		return SizeData{
+	//			data.vtype(),
+	//			data.vw().v,
+	//			data.vh().v,
+	//			QByteArray()
+	//		};
+	//	}, [](const MTPDphotoCachedSize &data) {
+	//		return SizeData{
+	//			data.vtype(),
+	//			data.vw().v,
+	//			data.vh().v,
+	//			qba(data.vbytes())
+	//		};
+	//	}, [](const MTPDphotoSizeEmpty &) {
+	//		return SizeData();
+	//	}, [](const MTPDphotoStrippedSize &data) {
+	//		// No need to save stripped images to local cache.
+	//		return SizeData();
+	//	});
+	//	const auto letter = size.type.v.isEmpty() ? char(0) : size.type.v[0];
+	//	if (!letter) {
+	//		continue;
+	//	}
+	//	if (letter == 's') {
+	//		saveImageToCache(_photo->thumbnailSmall(), size);
+	//	} else if (letter == 'm') {
+	//		saveImageToCache(_photo->thumbnail(), size);
+	//	} else if (letter == 'x' && max < 1) {
+	//		max = 1;
+	//		maxSize = size;
+	//	} else if (letter == 'y' && max < 2) {
+	//		max = 2;
+	//		maxSize = size;
+	//	//} else if (letter == 'w' && max < 3) {
+	//	//	max = 3;
+	//	//	maxSize = size;
+	//	}
+	//}
+	//if (!maxSize.type.v.isEmpty()) {
+	//	saveImageToCache(_photo->large(), maxSize);
+	//}
 	return true;
 }
 
diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp
index 4905b2d39..7c0c872a5 100644
--- a/Telegram/SourceFiles/data/data_photo.cpp
+++ b/Telegram/SourceFiles/data/data_photo.cpp
@@ -10,20 +10,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "data/data_file_origin.h"
 #include "data/data_reply_preview.h"
+#include "data/data_photo_media.h"
 #include "ui/image/image.h"
 #include "ui/image/image_source.h"
 #include "main/main_session.h"
 #include "mainwidget.h"
+#include "storage/file_download.h"
 #include "core/application.h"
 #include "facades.h"
 #include "app.h"
 
+namespace {
+
+using Data::PhotoMedia;
+using Data::PhotoSize;
+using Data::PhotoSizeIndex;
+using Data::kPhotoSizeCount;
+
+} // namespace
+
 PhotoData::PhotoData(not_null<Data::Session*> owner, PhotoId id)
 : id(id)
 , _owner(owner) {
 }
 
-PhotoData::~PhotoData() = default;
+PhotoData::~PhotoData() {
+	for (auto &image : _images) {
+		base::take(image.loader).reset();
+	}
+}
 
 Data::Session &PhotoData::owner() const {
 	return *_owner;
@@ -33,55 +48,77 @@ Main::Session &PhotoData::session() const {
 	return _owner->session();
 }
 
-void PhotoData::automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item) {
-	_large->automaticLoad(origin, item);
-}
-
 void PhotoData::automaticLoadSettingsChanged() {
-	_large->automaticLoadSettingsChanged();
-}
-
-void PhotoData::download(Data::FileOrigin origin) {
-	_large->loadEvenCancelled(origin);
-	_owner->notifyPhotoLayoutChanged(this);
-}
-
-bool PhotoData::loaded() const {
-	bool wasLoading = loading();
-	if (_large->loaded()) {
-		if (wasLoading) {
-			_owner->notifyPhotoLayoutChanged(this);
-		}
-		return true;
+	const auto index = PhotoSizeIndex(PhotoSize::Large);
+	if (!(_images[index].flags & ImageFlag::Cancelled)) {
+		return;
 	}
-	return false;
+	_images[index].loader = nullptr;
+	_images[index].flags &= ~ImageFlag::Cancelled;
+}
+
+void PhotoData::load(
+		Data::FileOrigin origin,
+		LoadFromCloudSetting fromCloud,
+		bool autoLoading) {
+	load(PhotoSize::Large, origin, fromCloud, autoLoading);
 }
 
 bool PhotoData::loading() const {
-	return _large->loading();
+	return loading(PhotoSize::Large);
+}
+
+bool PhotoData::loading(PhotoSize size) const {
+	return (_images[PhotoSizeIndex(size)].loader != nullptr);
+}
+
+bool PhotoData::failed(PhotoSize size) const {
+	return (_images[PhotoSizeIndex(size)].flags & ImageFlag::Failed);
+}
+
+const ImageLocation &PhotoData::location(PhotoSize size) const {
+	return _images[PhotoSizeIndex(size)].location;
+}
+
+int PhotoData::imageByteSize(PhotoSize size) const {
+	return _images[PhotoSizeIndex(size)].byteSize;
 }
 
 bool PhotoData::displayLoading() const {
-	return _large->loading()
-		? _large->displayLoading()
+	const auto index = PhotoSizeIndex(PhotoSize::Large);
+	return _images[index].loader
+		? (!_images[index].loader->loadingLocal()
+			|| !_images[index].loader->autoLoading())
 		: (uploading() && !waitingForAlbum());
 }
 
 void PhotoData::cancel() {
-	_large->cancel();
-	_owner->notifyPhotoLayoutChanged(this);
+	if (!loading()) {
+		return;
+	}
+
+	const auto index = PhotoSizeIndex(PhotoSize::Large);
+	_images[index].flags |= ImageFlag::Cancelled;
+	destroyLoader(PhotoSize::Large);
+	_owner->photoLoadDone(this);
 }
 
 float64 PhotoData::progress() const {
 	if (uploading()) {
 		if (uploadingData->size > 0) {
-			return float64(uploadingData->offset) / uploadingData->size;
+			const auto result = float64(uploadingData->offset)
+				/ uploadingData->size;
+			return snap(result, 0., 1.);
 		}
-		return 0;
+		return 0.;
 	}
-	return _large->progress();
+	const auto index = PhotoSizeIndex(PhotoSize::Large);
+	return loading() ? _images[index].loader->currentProgress() : 0.;
+}
+
+bool PhotoData::cancelled() const {
+	const auto index = PhotoSizeIndex(PhotoSize::Large);
+	return (_images[index].flags & ImageFlag::Cancelled);
 }
 
 void PhotoData::setWaitingForAlbum() {
@@ -95,7 +132,8 @@ bool PhotoData::waitingForAlbum() const {
 }
 
 int32 PhotoData::loadOffset() const {
-	return _large->loadOffset();
+	const auto index = PhotoSizeIndex(PhotoSize::Large);
+	return loading() ? _images[index].loader->currentOffset() : 0;
 }
 
 bool PhotoData::uploading() const {
@@ -103,11 +141,6 @@ bool PhotoData::uploading() const {
 }
 
 void PhotoData::unload() {
-	// Forget thumbnail only when image cache limit exceeds.
-	//_thumbnailInline->unload();
-	_thumbnailSmall->unload();
-	_thumbnail->unload();
-	_large->unload();
 	_replyPreview = nullptr;
 }
 
@@ -142,9 +175,9 @@ QByteArray PhotoData::fileReference() const {
 
 void PhotoData::refreshFileReference(const QByteArray &value) {
 	_fileReference = value;
-	_thumbnailSmall->refreshFileReference(value);
-	_thumbnail->refreshFileReference(value);
-	_large->refreshFileReference(value);
+	for (auto &image : _images) {
+		image.location.refreshFileReference(value);
+	}
 }
 
 void PhotoData::collectLocalData(not_null<PhotoData*> local) {
@@ -152,83 +185,185 @@ void PhotoData::collectLocalData(not_null<PhotoData*> local) {
 		return;
 	}
 
-	const auto copyImage = [&](const ImagePtr &src, const ImagePtr &dst) {
-		if (const auto from = src->cacheKey()) {
-			if (const auto to = dst->cacheKey()) {
+	for (auto i = 0; i != kPhotoSizeCount; ++i) {
+		if (const auto from = local->_images[i].location.file().cacheKey()) {
+			if (const auto to = _images[i].location.file().cacheKey()) {
 				_owner->cache().copyIfEmpty(from, to);
 			}
 		}
-	};
-	copyImage(local->_thumbnailSmall, _thumbnailSmall);
-	copyImage(local->_thumbnail, _thumbnail);
-	copyImage(local->_large, _large);
+	}
+	if (const auto localMedia = local->activeMediaView()) {
+		const auto media = createMediaView();
+		media->collectLocalData(localMedia.get());
+
+		// Keep DocumentMedia alive for some more time.
+		// NB! This allows DocumentMedia to outlive Main::Session!
+		// In case this is a problem this code should be rewritten.
+		crl::on_main(&session(), [media] {});
+	}
 }
 
 bool PhotoData::isNull() const {
-	return _large->isNull();
+	return !_images[PhotoSizeIndex(PhotoSize::Large)].location.valid();
 }
 
-void PhotoData::loadThumbnail(Data::FileOrigin origin) {
-	_thumbnail->load(origin);
+void PhotoData::load(
+		PhotoSize size,
+		Data::FileOrigin origin,
+		LoadFromCloudSetting fromCloud,
+		bool autoLoading) {
+	const auto index = PhotoSizeIndex(size);
+	auto &image = _images[index];
+	if (image.loader) {
+		if (fromCloud == LoadFromCloudOrLocal) {
+			image.loader->permitLoadFromCloud();
+		}
+		return;
+	} else if ((image.flags & ImageFlag::Failed)
+		|| !image.location.valid()) {
+		return;
+	} else if (const auto active = activeMediaView()) {
+		if (active->image(size)) {
+			return;
+		}
+	}
+	image.flags &= ~ImageFlag::Cancelled;
+	image.loader = CreateFileLoader(
+		image.location.file(),
+		origin,
+		QString(),
+		image.byteSize,
+		UnknownFileLocation,
+		LoadToCacheAsWell,
+		fromCloud,
+		autoLoading,
+		Data::kImageCacheTag);
+
+	image.loader->updates(
+	) | rpl::start_with_next_error_done([=] {
+		if (size == PhotoSize::Large) {
+			_owner->photoLoadProgress(this);
+		}
+	}, [=, &image](bool started) {
+		finishLoad(size);
+		image.flags |= ImageFlag::Failed;
+		if (size == PhotoSize::Large) {
+			_owner->photoLoadFail(this, started);
+		}
+	}, [=, &image] {
+		finishLoad(size);
+		if (size == PhotoSize::Large) {
+			_owner->photoLoadDone(this);
+		}
+	}, image.loader->lifetime());
+
+	image.loader->start();
+
+	if (size == PhotoSize::Large) {
+		_owner->notifyPhotoLayoutChanged(this);
+	}
 }
 
-void PhotoData::loadThumbnailSmall(Data::FileOrigin origin) {
-	_thumbnailSmall->load(origin);
+void PhotoData::finishLoad(PhotoSize size) {
+	const auto index = PhotoSizeIndex(size);
+	auto &image = _images[index];
+
+	// NB! image.loader may be in ~FileLoader() already.
+	const auto guard = gsl::finally([&] {
+		destroyLoader(size);
+	});
+	if (!image.loader || image.loader->cancelled()) {
+		image.flags |= ImageFlag::Cancelled;
+		return;
+	} else if (auto read = image.loader->imageData(); read.isNull()) {
+		image.flags |= ImageFlag::Failed;
+	} else if (const auto active = activeMediaView()) {
+		active->set(size, std::move(read));
+	}
 }
 
-Image *PhotoData::thumbnailInline() const {
-	return _thumbnailInline ? _thumbnailInline.get() : nullptr;
+void PhotoData::destroyLoader(PhotoSize size) {
+	const auto index = PhotoSizeIndex(size);
+	auto &image = _images[index];
+
+	// NB! image.loader may be in ~FileLoader() already.
+	if (!image.loader) {
+		return;
+	}
+	const auto loader = base::take(image.loader);
+	if (image.flags & ImageFlag::Cancelled) {
+		loader->cancel();
+	}
 }
 
-not_null<Image*> PhotoData::thumbnailSmall() const {
-	return _thumbnailSmall.get();
+std::shared_ptr<PhotoMedia> PhotoData::createMediaView() {
+	if (auto result = activeMediaView()) {
+		return result;
+	}
+	auto result = std::make_shared<PhotoMedia>(this);
+	_media = result;
+	return result;
 }
 
-not_null<Image*> PhotoData::thumbnail() const {
-	return _thumbnail.get();
-}
-
-void PhotoData::load(Data::FileOrigin origin) {
-	_large->load(origin);
-}
-
-not_null<Image*> PhotoData::large() const {
-	return _large.get();
+std::shared_ptr<PhotoMedia> PhotoData::activeMediaView() const {
+	return _media.lock();
 }
 
 void PhotoData::updateImages(
-		ImagePtr thumbnailInline,
-		ImagePtr thumbnailSmall,
-		ImagePtr thumbnail,
-		ImagePtr large) {
-	if (!thumbnailSmall || !thumbnail || !large) {
-		return;
+		const QByteArray &inlineThumbnailBytes,
+		const ImageWithLocation &small,
+		const ImageWithLocation &thumbnail,
+		const ImageWithLocation &large) {
+	if (!inlineThumbnailBytes.isEmpty()
+		&& _inlineThumbnailBytes.isEmpty()) {
+		_inlineThumbnailBytes = inlineThumbnailBytes;
 	}
-	if (thumbnailInline && !_thumbnailInline) {
-		_thumbnailInline = thumbnailInline;
-	}
-	const auto update = [](ImagePtr &was, ImagePtr now) {
-		if (!was) {
-			was = now;
-		} else if (was->isDelayedStorageImage()) {
-			if (const auto location = now->location(); location.valid()) {
-				was->setDelayedStorageLocation(
-					Data::FileOrigin(),
-					location);
+	const auto update = [&](PhotoSize size, const ImageWithLocation &data) {
+		auto &image = _images[PhotoSizeIndex(size)];
+		if (data.location.valid()
+			&& (!image.location.valid()
+				|| image.location.width() != data.location.width()
+				|| image.location.height() != data.location.height())) {
+			image.location = data.location;
+			image.byteSize = data.bytesCount;
+			if (!data.preloaded.isNull()) {
+				image.loader = nullptr;
+				if (const auto media = activeMediaView()) {
+					media->set(size, data.preloaded);
+				}
+			} else if (image.loader) {
+				const auto origin = base::take(image.loader)->fileOrigin();
+				load(size, origin);
+			}
+			if (!data.bytes.isEmpty()) {
+				if (const auto cacheKey = image.location.file().cacheKey()) {
+					owner().cache().putIfEmpty(
+						cacheKey,
+						Storage::Cache::Database::TaggedValue(
+							base::duplicate(data.bytes),
+							Data::kImageCacheTag));
+				}
 			}
 		}
+		//if (was->isDelayedStorageImage()) { // #TODO optimize
+		//	if (const auto location = now->location(); location.valid()) {
+		//		was->setDelayedStorageLocation(
+		//			Data::FileOrigin(),
+		//			location);
+		//	}
+		//}
 	};
-	update(_thumbnailSmall, thumbnailSmall);
-	update(_thumbnail, thumbnail);
-	update(_large, large);
+	update(PhotoSize::Small, small);
+	update(PhotoSize::Thumbnail, thumbnail);
+	update(PhotoSize::Large, large);
 }
 
 int PhotoData::width() const {
-	return _large->width();
+	return _images[PhotoSizeIndex(PhotoSize::Large)].location.width();
 }
 
 int PhotoData::height() const {
-	return _large->height();
+	return _images[PhotoSizeIndex(PhotoSize::Large)].location.height();
 }
 
 PhotoClickHandler::PhotoClickHandler(
@@ -255,7 +390,7 @@ void PhotoSaveClickHandler::onClickImpl() const {
 	if (!data->date) {
 		return;
 	} else {
-		data->download(context());
+		data->load(context());
 	}
 }
 
diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h
index 772d1ecb4..f08c8fe57 100644
--- a/Telegram/SourceFiles/data/data_photo.h
+++ b/Telegram/SourceFiles/data/data_photo.h
@@ -14,8 +14,26 @@ class Session;
 } // namespace Main
 
 namespace Data {
+
 class Session;
 class ReplyPreview;
+class PhotoMedia;
+
+inline constexpr auto kPhotoSizeCount = 3;
+
+enum class PhotoSize : uchar {
+	Small,
+	Thumbnail,
+	Large,
+};
+
+[[nodiscard]] inline int PhotoSizeIndex(PhotoSize size) {
+	Expects(static_cast<int>(size) >= 0
+		&& static_cast<int>(size) < kPhotoSizeCount);
+
+	return static_cast<int>(size);
+}
+
 } // namespace Data
 
 class PhotoData final {
@@ -25,20 +43,17 @@ public:
 
 	[[nodiscard]] Data::Session &owner() const;
 	[[nodiscard]] Main::Session &session() const;
+	[[nodiscard]] bool isNull() const;
 
-	void automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item);
 	void automaticLoadSettingsChanged();
 
-	void download(Data::FileOrigin origin);
-	[[nodiscard]] bool loaded() const;
 	[[nodiscard]] bool loading() const;
 	[[nodiscard]] bool displayLoading() const;
 	void cancel();
 	[[nodiscard]] float64 progress() const;
 	[[nodiscard]] int32 loadOffset() const;
 	[[nodiscard]] bool uploading() const;
+	[[nodiscard]] bool cancelled() const;
 
 	void setWaitingForAlbum();
 	[[nodiscard]] bool waitingForAlbum() const;
@@ -60,27 +75,39 @@ public:
 	// to (this) received from the server "same" photo.
 	void collectLocalData(not_null<PhotoData*> local);
 
-	bool isNull() const;
+	[[nodiscard]] std::shared_ptr<Data::PhotoMedia> createMediaView();
+	[[nodiscard]] auto activeMediaView() const
+		-> std::shared_ptr<Data::PhotoMedia>;
 
-	void loadThumbnail(Data::FileOrigin origin);
-	void loadThumbnailSmall(Data::FileOrigin origin);
-	Image *thumbnailInline() const;
-	not_null<Image*> thumbnailSmall() const;
-	not_null<Image*> thumbnail() const;
+	void updateImages(
+		const QByteArray &inlineThumbnailBytes,
+		const ImageWithLocation &small,
+		const ImageWithLocation &thumbnail,
+		const ImageWithLocation &large);
 
-	void load(Data::FileOrigin origin);
-	not_null<Image*> large() const;
+	[[nodiscard]] QByteArray inlineThumbnailBytes() const {
+		return _inlineThumbnailBytes;
+	}
+
+	void load(
+		Data::FileOrigin origin,
+		LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal,
+		bool autoLoading = false);
+
+	[[nodiscard]] bool loading(Data::PhotoSize size) const;
+	[[nodiscard]] bool failed(Data::PhotoSize size) const;
+	void load(
+		Data::PhotoSize size,
+		Data::FileOrigin origin,
+		LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal,
+		bool autoLoading = false);
+	[[nodiscard]] const ImageLocation &location(Data::PhotoSize size) const;
+	[[nodiscard]] int imageByteSize(Data::PhotoSize size) const;
 
 	// For now they return size of the 'large' image.
 	int width() const;
 	int height() const;
 
-	void updateImages(
-		ImagePtr thumbnailInline,
-		ImagePtr thumbnailSmall,
-		ImagePtr thumbnail,
-		ImagePtr large);
-
 	PhotoId id = 0;
 	TimeId date = 0;
 	bool hasSticker = false;
@@ -91,15 +118,30 @@ public:
 	std::unique_ptr<Data::UploadState> uploadingData;
 
 private:
-	ImagePtr _thumbnailInline;
-	ImagePtr _thumbnailSmall;
-	ImagePtr _thumbnail;
-	ImagePtr _large;
+	enum class ImageFlag : uchar {
+		Cancelled = 0x01,
+		Failed = 0x02,
+	};
+	friend inline constexpr bool is_flag_type(ImageFlag) { return true; };
+
+	struct Image final {
+		ImageLocation location;
+		std::unique_ptr<FileLoader> loader;
+		int byteSize = 0;
+		base::flags<ImageFlag> flags;
+	};
+
+	void finishLoad(Data::PhotoSize size);
+	void destroyLoader(Data::PhotoSize size);
+
+	QByteArray _inlineThumbnailBytes;
+	std::array<Image, Data::kPhotoSizeCount> _images;
 
 	int32 _dc = 0;
 	uint64 _access = 0;
 	QByteArray _fileReference;
 	std::unique_ptr<Data::ReplyPreview> _replyPreview;
+	std::weak_ptr<Data::PhotoMedia> _media;
 
 	not_null<Data::Session*> _owner;
 
diff --git a/Telegram/SourceFiles/data/data_photo_media.cpp b/Telegram/SourceFiles/data/data_photo_media.cpp
new file mode 100644
index 000000000..7f82a124b
--- /dev/null
+++ b/Telegram/SourceFiles/data/data_photo_media.cpp
@@ -0,0 +1,119 @@
+/*
+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 "data/data_photo_media.h"
+
+#include "data/data_photo.h"
+#include "data/data_session.h"
+#include "data/data_file_origin.h"
+#include "data/data_auto_download.h"
+#include "main/main_session.h"
+#include "history/history_item.h"
+#include "history/history.h"
+#include "storage/file_download.h"
+#include "ui/image/image.h"
+//#include "facades.h"
+//#include "app.h"
+
+namespace Data {
+
+PhotoMedia::PhotoMedia(not_null<PhotoData*> owner)
+: _owner(owner) {
+}
+
+// NB! Right now DocumentMedia can outlive Main::Session!
+// In DocumentData::collectLocalData a shared_ptr is sent on_main.
+// In case this is a problem the ~Gif code should be rewritten.
+PhotoMedia::~PhotoMedia() = default;
+
+not_null<PhotoData*> PhotoMedia::owner() const {
+	return _owner;
+}
+
+Image *PhotoMedia::thumbnailInline() const {
+	if (!_inlineThumbnail) {
+		auto image = Images::FromInlineBytes(_owner->inlineThumbnailBytes());
+		if (!image.isNull()) {
+			_inlineThumbnail = std::make_unique<Image>(
+				std::make_unique<Images::ImageSource>(
+					std::move(image),
+					"PNG"));
+		}
+	}
+	return _inlineThumbnail.get();
+}
+
+Image *PhotoMedia::image(PhotoSize size) const {
+	return _images[PhotoSizeIndex(size)].get();
+}
+
+void PhotoMedia::wanted(PhotoSize size, Data::FileOrigin origin) {
+	const auto index = PhotoSizeIndex(size);
+	if (!_images[index]) {
+		_owner->load(size, origin);
+	}
+}
+
+QSize PhotoMedia::size(PhotoSize size) const {
+	const auto index = PhotoSizeIndex(size);
+	if (const auto image = _images[index].get()) {
+		return image->size();
+	}
+	const auto &location = _owner->location(size);
+	return { location.width(), location.height() };
+}
+
+void PhotoMedia::set(PhotoSize size, QImage image) {
+	_images[PhotoSizeIndex(size)] = std::make_unique<Image>(
+		std::make_unique<Images::ImageSource>(std::move(image), "PNG"));
+	_owner->session().downloaderTaskFinished().notify();
+}
+
+bool PhotoMedia::loaded() const {
+	const auto index = PhotoSizeIndex(PhotoSize::Large);
+	return (_images[index] != nullptr);
+}
+
+float64 PhotoMedia::progress() const {
+	return (_owner->uploading() || _owner->loading())
+		? _owner->progress()
+		: (loaded() ? 1. : 0.);
+}
+
+void PhotoMedia::automaticLoad(
+		Data::FileOrigin origin,
+		const HistoryItem *item) {
+	const auto index = PhotoSizeIndex(PhotoSize::Large);
+	if (!item || loaded() || _owner->cancelled()) {
+		return;
+	}
+	const auto loadFromCloud = Data::AutoDownload::Should(
+		_owner->session().settings().autoDownload(),
+		item->history()->peer,
+		_owner);
+	_owner->load(
+		origin,
+		loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly,
+		true);
+}
+
+void PhotoMedia::collectLocalData(not_null<PhotoMedia*> local) {
+	if (const auto image = local->_inlineThumbnail.get()) {
+		_inlineThumbnail = std::make_unique<Image>(
+			std::make_unique<Images::ImageSource>(image->original(), "PNG"));
+	}
+	for (auto i = 0; i != kPhotoSizeCount; ++i) {
+		if (const auto image = local->_images[i].get()) {
+			_images[i] = std::make_unique<Image>(
+				std::make_unique<Images::ImageSource>(
+					image->original(),
+					"PNG"));
+		}
+	}
+}
+
+} // namespace Data
diff --git a/Telegram/SourceFiles/data/data_photo_media.h b/Telegram/SourceFiles/data/data_photo_media.h
new file mode 100644
index 000000000..09d2f9c2e
--- /dev/null
+++ b/Telegram/SourceFiles/data/data_photo_media.h
@@ -0,0 +1,48 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "base/flags.h"
+#include "data/data_photo.h"
+
+class FileLoader;
+
+namespace Data {
+
+class PhotoMedia final {
+public:
+	explicit PhotoMedia(not_null<PhotoData*> owner);
+	~PhotoMedia();
+
+	[[nodiscard]] not_null<PhotoData*> owner() const;
+
+	[[nodiscard]] Image *thumbnailInline() const;
+
+	[[nodiscard]] Image *image(PhotoSize size) const;
+	[[nodiscard]] QSize size(PhotoSize size) const;
+	void wanted(PhotoSize size, Data::FileOrigin origin);
+	void set(PhotoSize size, QImage image);
+
+	[[nodiscard]] bool loaded() const;
+	[[nodiscard]] float64 progress() const;
+
+	void automaticLoad(Data::FileOrigin origin, const HistoryItem *item);
+
+	void collectLocalData(not_null<PhotoMedia*> local);
+
+private:
+	// NB! Right now DocumentMedia can outlive Main::Session!
+	// In DocumentData::collectLocalData a shared_ptr is sent on_main.
+	// In case this is a problem the ~Gif code should be rewritten.
+	const not_null<PhotoData*> _owner;
+	mutable std::unique_ptr<Image> _inlineThumbnail;
+	std::array<std::unique_ptr<Image>, kPhotoSizeCount>  _images;
+
+};
+
+} // namespace Data
diff --git a/Telegram/SourceFiles/data/data_reply_preview.cpp b/Telegram/SourceFiles/data/data_reply_preview.cpp
index a46e10bea..ba2d84d0b 100644
--- a/Telegram/SourceFiles/data/data_reply_preview.cpp
+++ b/Telegram/SourceFiles/data/data_reply_preview.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_document.h"
 #include "data/data_document_media.h"
 #include "data/data_photo.h"
+#include "data/data_photo_media.h"
 #include "ui/image/image.h"
 #include "ui/image/image_source.h"
 
@@ -62,18 +63,18 @@ Image *ReplyPreview::image(Data::FileOrigin origin) {
 		return _image.get();
 	}
 	if (_document) {
-		if (!_documentMedia) {
-			_documentMedia = _document->createMediaView();
-		}
-		const auto thumbnail = _documentMedia->thumbnail();
-		if (!_image || (!_good && thumbnail)) {
+		if (!_image || (!_good && _document->hasThumbnail())) {
+			if (!_documentMedia) {
+				_documentMedia = _document->createMediaView();
+				_documentMedia->thumbnailWanted(origin);
+			}
+			const auto thumbnail = _documentMedia->thumbnail();
 			const auto option = _document->isVideoMessage()
 				? Images::Option::Circled
 				: Images::Option::None;
 			if (thumbnail) {
 				prepare(thumbnail, option);
 			} else {
-				_documentMedia->thumbnailWanted(origin);
 				if (const auto image = _documentMedia->thumbnailInline()) {
 					prepare(image, option | Images::Option::Blurred);
 				}
@@ -85,22 +86,33 @@ Image *ReplyPreview::image(Data::FileOrigin origin) {
 		}
 	} else {
 		Assert(_photo != nullptr);
-		const auto small = _photo->thumbnailSmall();
-		const auto large = _photo->large();
-		if (!_image || (!_good && (small->loaded() || large->loaded()))) {
-			if (small->isDelayedStorageImage()
-				&& !large->isNull()
-				&& !large->isDelayedStorageImage()
-				&& large->loaded()) {
-				prepare(large, Images::Option(0));
-			} else if (small->loaded()) {
-				prepare(small, Images::Option(0));
-			} else {
-				small->load(origin);
-				if (const auto blurred = _photo->thumbnailInline()) {
-					prepare(blurred, Images::Option::Blurred);
-				}
+		if (!_image || !_good) {
+			if (!_photoMedia) {
+				_photoMedia = _photo->createMediaView();
+				_photoMedia->wanted(PhotoSize::Small, origin);
 			}
+			if (const auto small = _photoMedia->image(PhotoSize::Small)) {
+				prepare(small, Images::Option(0));
+			} else if (const auto large = _photoMedia->image(
+					PhotoSize::Large)) {
+				prepare(large, Images::Option(0));
+			} else if (const auto blurred = _photoMedia->thumbnailInline()) {
+				prepare(blurred, Images::Option::Blurred);
+			}
+
+			//if (small->isDelayedStorageImage() // #TODO optimize
+			//	&& !large->isNull()
+			//	&& !large->isDelayedStorageImage()
+			//	&& large->loaded()) {
+			//	prepare(large, Images::Option(0));
+			//} else if (small->loaded()) {
+			//	prepare(small, Images::Option(0));
+			//} else {
+			//	small->load(origin);
+			//	if (const auto blurred = _photo->thumbnailInline()) {
+			//		prepare(blurred, Images::Option::Blurred);
+			//	}
+			//}
 		}
 	}
 	return _image.get();
diff --git a/Telegram/SourceFiles/data/data_reply_preview.h b/Telegram/SourceFiles/data/data_reply_preview.h
index b607c584d..4e9f0cd74 100644
--- a/Telegram/SourceFiles/data/data_reply_preview.h
+++ b/Telegram/SourceFiles/data/data_reply_preview.h
@@ -12,6 +12,7 @@ class PhotoData;
 
 namespace Data {
 
+class PhotoMedia;
 class DocumentMedia;
 struct FileOrigin;
 
@@ -26,11 +27,12 @@ private:
 	void prepare(not_null<Image*> image, Images::Options options);
 
 	std::unique_ptr<Image> _image;
+	PhotoData *_photo = nullptr;
+	DocumentData *_document = nullptr;
+	std::shared_ptr<PhotoMedia> _photoMedia;
+	std::shared_ptr<DocumentMedia> _documentMedia;
 	bool _good = false;
 	bool _checked = false;
-	DocumentData *_document = nullptr;
-	PhotoData *_photo = nullptr;
-	std::shared_ptr<DocumentMedia> _documentMedia;
 
 };
 
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index d29fe9b5c..5fbb81a1f 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -75,10 +75,10 @@ using ViewElement = HistoryView::Element;
 // b: crop 320x320
 // c: crop 640x640
 // d: crop 1280x1280
-const auto InlineLevels = QByteArray::fromRawData("i", 1);
-const auto SmallLevels = QByteArray::fromRawData("sambcxydwi", 10);
-const auto ThumbnailLevels = QByteArray::fromRawData("mbcxasydwi", 10);
-const auto LargeLevels = QByteArray::fromRawData("yxwmsdcbai", 10);
+const auto InlineLevels = "i"_q;
+const auto SmallLevels = "sambcxydwi"_q;
+const auto ThumbnailLevels = "mbcxasydwi"_q;
+const auto LargeLevels = "yxwmsdcbai"_q;
 
 void CheckForSwitchInlineButton(not_null<HistoryItem*> item) {
 	if (item->out() || !item->hasSwitchInlineButton()) {
@@ -127,23 +127,22 @@ std::vector<UnavailableReason> ExtractUnavailableReasons(
 	}) | ranges::to_vector;
 }
 
-QByteArray FindDocumentInlineThumbnail(const MTPDdocument &data) {
-	const auto thumbs = data.vthumbs();
-	if (!thumbs) {
-		return QByteArray();
-	}
-	const auto &list = thumbs->v;
+[[nodiscard]] QByteArray FindInlineThumbnail(
+		const QVector<MTPPhotoSize> &sizes) {
 	const auto i = ranges::find(
-		list,
+		sizes,
 		mtpc_photoStrippedSize,
 		&MTPPhotoSize::type);
-	if (i == list.end()) {
-		return QByteArray();
-	}
-	return i->c_photoStrippedSize().vbytes().v;
+	return (i != sizes.end())
+		? i->c_photoStrippedSize().vbytes().v
+		: QByteArray();
 }
 
-MTPPhotoSize FindDocumentThumbnail(const MTPDdocument &data) {
+[[nodiscard]] QByteArray FindDocumentInlineThumbnail(const MTPDdocument &data) {
+	return FindInlineThumbnail(data.vthumbs().value_or_empty());
+}
+
+[[nodiscard]] MTPPhotoSize FindDocumentThumbnail(const MTPDdocument &data) {
 	const auto area = [](const MTPPhotoSize &size) {
 		static constexpr auto kInvalid = 0;
 		return size.match([](const MTPDphotoSizeEmpty &) {
@@ -165,7 +164,7 @@ MTPPhotoSize FindDocumentThumbnail(const MTPDdocument &data) {
 		: MTPPhotoSize(MTP_photoSizeEmpty(MTP_string()));
 }
 
-std::optional<MTPVideoSize> FindDocumentVideoThumbnail(
+[[nodiscard]] std::optional<MTPVideoSize> FindDocumentVideoThumbnail(
 		const MTPDdocument &data) {
 	const auto area = [](const MTPVideoSize &size) {
 		static constexpr auto kInvalid = 0;
@@ -184,7 +183,11 @@ std::optional<MTPVideoSize> FindDocumentVideoThumbnail(
 		: std::nullopt;
 }
 
-rpl::producer<int> PinnedDialogsCountMaxValue(
+[[nodiscard]] QByteArray FindPhotoInlineThumbnail(const MTPDphoto &data) {
+	return FindInlineThumbnail(data.vsizes().v);
+}
+
+[[nodiscard]] rpl::producer<int> PinnedDialogsCountMaxValue(
 		not_null<Main::Session*> session) {
 	return rpl::single(
 		rpl::empty_value()
@@ -1099,6 +1102,15 @@ void Session::notifyPhotoLayoutChanged(not_null<const PhotoData*> photo) {
 	}
 }
 
+void Session::requestPhotoViewRepaint(not_null<const PhotoData*> photo) {
+	const auto i = _photoItems.find(photo);
+	if (i != end(_photoItems)) {
+		for (const auto item : i->second) {
+			requestItemRepaint(item);
+		}
+	}
+}
+
 void Session::notifyDocumentLayoutChanged(
 		not_null<const DocumentData*> document) {
 	const auto i = _documentItems.find(document);
@@ -1153,6 +1165,20 @@ void Session::documentLoadFail(
 	notifyDocumentLayoutChanged(document);
 }
 
+void Session::photoLoadProgress(not_null<PhotoData*> photo) {
+	requestPhotoViewRepaint(photo);
+}
+
+void Session::photoLoadDone(not_null<PhotoData*> photo) {
+	notifyPhotoLayoutChanged(photo);
+}
+
+void Session::photoLoadFail(
+		not_null<PhotoData*> photo,
+		bool started) {
+	notifyPhotoLayoutChanged(photo);
+}
+
 void Session::markMediaRead(not_null<const DocumentData*> document) {
 	const auto i = _documentItems.find(document);
 	if (i != end(_documentItems)) {
@@ -2174,11 +2200,10 @@ not_null<PhotoData*> Session::processPhoto(
 	const auto image = [&](const QByteArray &levels) {
 		const auto i = find(levels);
 		return (i == thumbs.end())
-			? ImagePtr()
-			: Images::Create(base::duplicate(i->second), "JPG");
+			? ImageWithLocation()
+			: Images::FromImageInMemory(i->second, "JPG");
 	};
-	const auto thumbnailInline = image(InlineLevels);
-	const auto thumbnailSmall = image(SmallLevels);
+	const auto small = image(SmallLevels);
 	const auto thumbnail = image(ThumbnailLevels);
 	const auto large = image(LargeLevels);
 	return data.match([&](const MTPDphoto &data) {
@@ -2189,8 +2214,8 @@ not_null<PhotoData*> Session::processPhoto(
 			data.vdate().v,
 			data.vdc_id().v,
 			data.is_has_stickers(),
-			thumbnailInline,
-			thumbnailSmall,
+			QByteArray(),
+			small,
 			thumbnail,
 			large);
 	}, [&](const MTPDphotoEmpty &data) {
@@ -2205,10 +2230,10 @@ not_null<PhotoData*> Session::photo(
 		TimeId date,
 		int32 dc,
 		bool hasSticker,
-		const ImagePtr &thumbnailInline,
-		const ImagePtr &thumbnailSmall,
-		const ImagePtr &thumbnail,
-		const ImagePtr &large) {
+		const QByteArray &inlineThumbnailBytes,
+		const ImageWithLocation &small,
+		const ImageWithLocation &thumbnail,
+		const ImageWithLocation &large) {
 	const auto result = photo(id);
 	photoApplyFields(
 		result,
@@ -2217,8 +2242,8 @@ not_null<PhotoData*> Session::photo(
 		date,
 		dc,
 		hasSticker,
-		thumbnailInline,
-		thumbnailSmall,
+		inlineThumbnailBytes,
+		small,
 		thumbnail,
 		large);
 	return result;
@@ -2252,26 +2277,28 @@ void Session::photoConvert(
 
 PhotoData *Session::photoFromWeb(
 		const MTPWebDocument &data,
-		ImagePtr thumbnail,
+		const ImageLocation &thumbnailLocation,
 		bool willBecomeNormal) {
-	const auto large = Images::Create(data);
+	const auto large = Images::FromWebDocument(data);
 	const auto thumbnailInline = ImagePtr();
-	if (large->isNull()) {
+	if (!large.valid()) {
 		return nullptr;
 	}
-	auto thumbnailSmall = large;
+	auto small = large;
+	auto thumbnail = thumbnailLocation;
 	if (willBecomeNormal) {
-		const auto width = large->width();
-		const auto height = large->height();
+		const auto width = large.width();
+		const auto height = large.height();
 
-		auto thumbsize = shrinkToKeepAspect(width, height, 100, 100);
-		thumbnailSmall = Images::Create(thumbsize.width(), thumbsize.height());
+		// #TODO optimize
+		//auto thumbsize = shrinkToKeepAspect(width, height, 100, 100);
+		//small = Images::Create(thumbsize.width(), thumbsize.height());
 
-		if (thumbnail->isNull()) {
-			auto mediumsize = shrinkToKeepAspect(width, height, 320, 320);
-			thumbnail = Images::Create(mediumsize.width(), mediumsize.height());
-		}
-	} else if (thumbnail->isNull()) {
+		//if (!thumbnail.valid()) {
+		//	auto mediumsize = shrinkToKeepAspect(width, height, 320, 320);
+		//	thumbnail = Images::Create(mediumsize.width(), mediumsize.height());
+		//}
+	} else if (!thumbnail.valid()) {
 		thumbnail = large;
 	}
 
@@ -2282,10 +2309,10 @@ PhotoData *Session::photoFromWeb(
 		base::unixtime::now(),
 		0,
 		false,
-		thumbnailInline,
-		thumbnailSmall,
-		thumbnail,
-		large);
+		QByteArray(),
+		ImageWithLocation{ .location = small },
+		ImageWithLocation{ .location = thumbnail },
+		ImageWithLocation{ .location = large });
 }
 
 void Session::photoApplyFields(
@@ -2319,13 +2346,12 @@ void Session::photoApplyFields(
 	};
 	const auto image = [&](const QByteArray &levels) {
 		const auto i = find(levels);
-		return (i == sizes.end()) ? ImagePtr() : Images::Create(data, *i);
+		return (i == sizes.end())
+			? ImageWithLocation()
+			: Images::FromPhotoSize(_session, data, *i);
 	};
-	const auto thumbnailInline = image(InlineLevels);
-	const auto thumbnailSmall = image(SmallLevels);
-	const auto thumbnail = image(ThumbnailLevels);
 	const auto large = image(LargeLevels);
-	if (thumbnailSmall && thumbnail && large) {
+	if (large.location.valid()) {
 		photoApplyFields(
 			photo,
 			data.vaccess_hash().v,
@@ -2333,9 +2359,9 @@ void Session::photoApplyFields(
 			data.vdate().v,
 			data.vdc_id().v,
 			data.is_has_stickers(),
-			thumbnailInline,
-			thumbnailSmall,
-			thumbnail,
+			FindPhotoInlineThumbnail(data),
+			image(SmallLevels),
+			image(ThumbnailLevels),
 			large);
 	}
 }
@@ -2347,10 +2373,10 @@ void Session::photoApplyFields(
 		TimeId date,
 		int32 dc,
 		bool hasSticker,
-		const ImagePtr &thumbnailInline,
-		const ImagePtr &thumbnailSmall,
-		const ImagePtr &thumbnail,
-		const ImagePtr &large) {
+		const QByteArray &inlineThumbnailBytes,
+		const ImageWithLocation &small,
+		const ImageWithLocation &thumbnail,
+		const ImageWithLocation &large) {
 	if (!date) {
 		return;
 	}
@@ -2358,8 +2384,8 @@ void Session::photoApplyFields(
 	photo->date = date;
 	photo->hasSticker = hasSticker;
 	photo->updateImages(
-		thumbnailInline,
-		thumbnailSmall,
+		inlineThumbnailBytes,
+		small,
 		thumbnail,
 		large);
 }
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index aea8a2f6c..8ebbbe2c6 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -427,12 +427,17 @@ public:
 	void documentLoadSettingsChanged();
 
 	void notifyPhotoLayoutChanged(not_null<const PhotoData*> photo);
+	void requestPhotoViewRepaint(not_null<const PhotoData*> photo);
 	void notifyDocumentLayoutChanged(
 		not_null<const DocumentData*> document);
 	void requestDocumentViewRepaint(not_null<const DocumentData*> document);
 	void markMediaRead(not_null<const DocumentData*> document);
 	void requestPollViewRepaint(not_null<const PollData*> poll);
 
+	void photoLoadProgress(not_null<PhotoData*> photo);
+	void photoLoadDone(not_null<PhotoData*> photo);
+	void photoLoadFail(not_null<PhotoData*> photo, bool started);
+
 	void documentLoadProgress(not_null<DocumentData*> document);
 	void documentLoadDone(not_null<DocumentData*> document);
 	void documentLoadFail(not_null<DocumentData*> document, bool started);
@@ -473,16 +478,16 @@ public:
 		TimeId date,
 		int32 dc,
 		bool hasSticker,
-		const ImagePtr &thumbnailInline,
-		const ImagePtr &thumbnailSmall,
-		const ImagePtr &thumbnail,
-		const ImagePtr &large);
+		const QByteArray &inlineThumbnailBytes,
+		const ImageWithLocation &small,
+		const ImageWithLocation &thumbnail,
+		const ImageWithLocation &large);
 	void photoConvert(
 		not_null<PhotoData*> original,
 		const MTPPhoto &data);
 	[[nodiscard]] PhotoData *photoFromWeb(
 		const MTPWebDocument &data,
-		ImagePtr thumbnail = ImagePtr(),
+		const ImageLocation &thumbnailLocation = ImageLocation(),
 		bool willBecomeNormal = false);
 
 	[[nodiscard]] not_null<DocumentData*> document(DocumentId id);
@@ -736,10 +741,10 @@ private:
 		TimeId date,
 		int32 dc,
 		bool hasSticker,
-		const ImagePtr &thumbnailInline,
-		const ImagePtr &thumbnailSmall,
-		const ImagePtr &thumbnail,
-		const ImagePtr &large);
+		const QByteArray &inlineThumbnailBytes,
+		const ImageWithLocation &small,
+		const ImageWithLocation &thumbnail,
+		const ImageWithLocation &large);
 
 	void documentApplyFields(
 		not_null<DocumentData*> document,
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
index 1aad5b722..fdcbaff9e 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
@@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "boxes/peers/edit_participants_box.h"
 #include "data/data_session.h"
 #include "data/data_photo.h"
+#include "data/data_photo_media.h"
 #include "data/data_document.h"
 #include "data/data_media_types.h"
 #include "data/data_file_origin.h"
@@ -1137,11 +1138,13 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 	}
 }
 
-void InnerWidget::savePhotoToFile(PhotoData *photo) {
-	if (!photo || photo->isNull() || !photo->loaded()) {
+void InnerWidget::savePhotoToFile(not_null<PhotoData*> photo) {
+	const auto media = photo->activeMediaView();
+	if (photo->isNull() || !media || !media->loaded()) {
 		return;
 	}
 
+	const auto image = media->image(Data::PhotoSize::Large)->original();
 	auto filter = qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter();
 	FileDialog::GetWritePath(
 		this,
@@ -1150,22 +1153,26 @@ void InnerWidget::savePhotoToFile(PhotoData *photo) {
 		filedialogDefaultName(qsl("photo"), qsl(".jpg")),
 		crl::guard(this, [=](const QString &result) {
 			if (!result.isEmpty()) {
-				photo->large()->original().save(result, "JPG");
+				image.save(result, "JPG");
 			}
 		}));
 }
 
-void InnerWidget::saveDocumentToFile(DocumentData *document) {
+void InnerWidget::saveDocumentToFile(not_null<DocumentData*> document) {
 	DocumentSaveClickHandler::Save(
 		Data::FileOrigin(),
 		document,
 		DocumentSaveClickHandler::Mode::ToNewFile);
 }
 
-void InnerWidget::copyContextImage(PhotoData *photo) {
-	if (!photo || photo->isNull() || !photo->loaded()) return;
+void InnerWidget::copyContextImage(not_null<PhotoData*> photo) {
+	const auto media = photo->activeMediaView();
+	if (photo->isNull() || !media || !media->loaded()) {
+		return;
+	}
 
-	QGuiApplication::clipboard()->setImage(photo->large()->original());
+	const auto image = media->image(Data::PhotoSize::Large)->original();
+	QGuiApplication::clipboard()->setImage(image);
 }
 
 void InnerWidget::copySelectedText() {
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
index b539ed42f..dd00546d5 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
@@ -162,9 +162,9 @@ private:
 	QPoint mapPointToItem(QPoint point, const Element *view) const;
 
 	void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
-	void savePhotoToFile(PhotoData *photo);
-	void saveDocumentToFile(DocumentData *document);
-	void copyContextImage(PhotoData *photo);
+	void savePhotoToFile(not_null<PhotoData*> photo);
+	void saveDocumentToFile(not_null<DocumentData*> document);
+	void copyContextImage(not_null<PhotoData*> photo);
 	void showStickerPackInfo(not_null<DocumentData*> document);
 	void cancelContextDownload(not_null<DocumentData*> document);
 	void showContextInFolder(not_null<DocumentData*> document);
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index c372d017e..ead566496 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -51,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_document.h"
 #include "data/data_poll.h"
 #include "data/data_photo.h"
+#include "data/data_photo_media.h"
 #include "data/data_user.h"
 #include "data/data_file_origin.h"
 #include "data/data_histories.h"
@@ -1853,8 +1854,12 @@ void HistoryInner::copySelectedText() {
 }
 
 void HistoryInner::savePhotoToFile(not_null<PhotoData*> photo) {
-	if (photo->isNull() || !photo->loaded()) return;
+	const auto media = photo->activeMediaView();
+	if (photo->isNull() || !media || !media->loaded()) {
+		return;
+	}
 
+	const auto image = media->image(Data::PhotoSize::Large)->original();
 	auto filter = qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter();
 	FileDialog::GetWritePath(
 		this,
@@ -1865,15 +1870,19 @@ void HistoryInner::savePhotoToFile(not_null<PhotoData*> photo) {
 			qsl(".jpg")),
 		crl::guard(this, [=](const QString &result) {
 			if (!result.isEmpty()) {
-				photo->large()->original().save(result, "JPG");
+				image.save(result, "JPG");
 			}
 		}));
 }
 
 void HistoryInner::copyContextImage(not_null<PhotoData*> photo) {
-	if (photo->isNull() || !photo->loaded()) return;
+	const auto media = photo->activeMediaView();
+	if (photo->isNull() || !media || !media->loaded()) {
+		return;
+	}
 
-	QGuiApplication::clipboard()->setImage(photo->large()->original());
+	const auto image = media->image(Data::PhotoSize::Large)->original();
+	QGuiApplication::clipboard()->setImage(image);
 }
 
 void HistoryInner::showStickerPackInfo(not_null<DocumentData*> document) {
diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
index 95c549740..1f855f440 100644
--- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "boxes/confirm_box.h"
 #include "boxes/sticker_set_box.h"
 #include "data/data_photo.h"
+#include "data/data_photo_media.h"
 #include "data/data_document.h"
 #include "data/data_media_types.h"
 #include "data/data_session.h"
@@ -72,10 +73,12 @@ MsgId ItemIdAcrossData(not_null<HistoryItem*> item) {
 }
 
 void SavePhotoToFile(not_null<PhotoData*> photo) {
-	if (photo->isNull() || !photo->loaded()) {
+	const auto media = photo->activeMediaView();
+	if (photo->isNull() || !media || !media->loaded()) {
 		return;
 	}
 
+	const auto image = media->image(Data::PhotoSize::Large)->original();
 	FileDialog::GetWritePath(
 		Core::App().getFileDialogParent(),
 		tr::lng_save_photo(tr::now),
@@ -83,17 +86,19 @@ void SavePhotoToFile(not_null<PhotoData*> photo) {
 		filedialogDefaultName(qsl("photo"), qsl(".jpg")),
 		crl::guard(&photo->session(), [=](const QString &result) {
 			if (!result.isEmpty()) {
-				photo->large()->original().save(result, "JPG");
+				image.save(result, "JPG");
 			}
 		}));
 }
 
 void CopyImage(not_null<PhotoData*> photo) {
-	if (photo->isNull() || !photo->loaded()) {
+	const auto media = photo->activeMediaView();
+	if (photo->isNull() || !media || !media->loaded()) {
 		return;
 	}
 
-	QGuiApplication::clipboard()->setImage(photo->large()->original());
+	const auto image = media->image(Data::PhotoSize::Large)->original();
+	QGuiApplication::clipboard()->setImage(image);
 }
 
 void ShowStickerPackInfo(not_null<DocumentData*> document) {
diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
index 2d5cb756e..ad63d357d 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
@@ -18,11 +18,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/grouped_layout.h"
 #include "data/data_session.h"
 #include "data/data_photo.h"
+#include "data/data_photo_media.h"
 #include "data/data_file_origin.h"
 #include "app.h"
 #include "styles/style_history.h"
 
 namespace HistoryView {
+namespace {
+
+using Data::PhotoSize;
+
+} // namespace
 
 Photo::Photo(
 	not_null<Element*> parent,
@@ -46,18 +52,48 @@ Photo::Photo(
 	create(parent->data()->fullId(), chat);
 }
 
+Photo::~Photo() = default;
+
 void Photo::create(FullMsgId contextId, PeerData *chat) {
 	setLinks(
 		std::make_shared<PhotoOpenClickHandler>(_data, contextId, chat),
 		std::make_shared<PhotoSaveClickHandler>(_data, contextId, chat),
 		std::make_shared<PhotoCancelClickHandler>(_data, contextId, chat));
-	if (!_data->thumbnailInline()
-		&& !_data->loaded()
-		&& !_data->thumbnail()->loaded()) {
-		_data->thumbnailSmall()->load(contextId);
+	if ((_dataMedia = _data->activeMediaView())) {
+		dataMediaCreated();
+	} else if (_data->inlineThumbnailBytes().isEmpty()) {
+		_data->load(PhotoSize::Small, contextId);
 	}
 }
 
+void Photo::ensureDataMediaCreated() const {
+	if (_dataMedia) {
+		return;
+	}
+	_dataMedia = _data->createMediaView();
+	dataMediaCreated();
+}
+
+void Photo::dataMediaCreated() const {
+	Expects(_dataMedia != nullptr);
+
+	if (!_dataMedia->image(PhotoSize::Large)
+		&& !_dataMedia->image(PhotoSize::Thumbnail)) {
+		_dataMedia->wanted(PhotoSize::Small, _realParent->fullId());
+	}
+	history()->owner().registerHeavyViewPart(_parent);
+}
+
+void Photo::checkHeavyPart() {
+	if (!_dataMedia) {
+		history()->owner().unregisterHeavyViewPart(_parent);
+	}
+}
+
+void Photo::unloadHeavyPart() {
+	_dataMedia = nullptr;
+}
+
 QSize Photo::countOptimalSize() {
 	if (_parent->media() != this) {
 		_caption = Ui::Text::String();
@@ -145,9 +181,10 @@ QSize Photo::countCurrentSize(int newWidth) {
 void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
 	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
 
-	_data->automaticLoad(_realParent->fullId(), _parent->data());
+	ensureDataMediaCreated();
+	_dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
 	auto selected = (selection == FullSelection);
-	auto loaded = _data->loaded();
+	auto loaded = _dataMedia->loaded();
 	auto displayLoading = _data->displayLoading();
 
 	auto inWebPage = (_parent->media() != this);
@@ -159,7 +196,7 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time
 	if (displayLoading) {
 		ensureAnimation();
 		if (!_animation->radial.animating()) {
-			_animation->radial.start(_data->progress());
+			_animation->radial.start(_dataMedia->progress());
 		}
 	}
 	const auto radial = isRadialAnimation();
@@ -167,13 +204,15 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time
 	auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width());
 	if (_serviceWidth > 0) {
 		const auto pix = [&] {
-			if (loaded) {
-				return _data->large()->pixCircled(_realParent->fullId(), _pixw, _pixh);
-			} else if (_data->thumbnail()->loaded()) {
-				return _data->thumbnail()->pixBlurredCircled(_realParent->fullId(), _pixw, _pixh);
-			} else if (_data->thumbnailSmall()->loaded()) {
-				return _data->thumbnailSmall()->pixBlurredCircled(_realParent->fullId(), _pixw, _pixh);
-			} else if (const auto blurred = _data->thumbnailInline()) {
+			if (const auto large = _dataMedia->image(PhotoSize::Large)) {
+				return large->pixCircled(_realParent->fullId(), _pixw, _pixh);
+			} else if (const auto thumbnail = _dataMedia->image(
+					PhotoSize::Thumbnail)) {
+				return thumbnail->pixBlurredCircled(_realParent->fullId(), _pixw, _pixh);
+			} else if (const auto small = _dataMedia->image(
+					PhotoSize::Small)) {
+				return small->pixBlurredCircled(_realParent->fullId(), _pixw, _pixh);
+			} else if (const auto blurred = _dataMedia->thumbnailInline()) {
 				return blurred->pixBlurredCircled(_realParent->fullId(), _pixw, _pixh);
 			} else {
 				return QPixmap();
@@ -197,13 +236,15 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time
 		auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
 			| ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None));
 		const auto pix = [&] {
-			if (loaded) {
-				return _data->large()->pixSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners);
-			} else if (_data->thumbnail()->loaded()) {
-				return _data->thumbnail()->pixBlurredSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners);
-			} else if (_data->thumbnailSmall()->loaded()) {
-				return _data->thumbnailSmall()->pixBlurredSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners);
-			} else if (const auto blurred = _data->thumbnailInline()) {
+			if (const auto large = _dataMedia->image(PhotoSize::Large)) {
+				return large->pixSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners);
+			} else if (const auto thumbnail = _dataMedia->image(
+					PhotoSize::Thumbnail)) {
+				return thumbnail->pixBlurredSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners);
+			} else if (const auto small = _dataMedia->image(
+					PhotoSize::Small)) {
+				return small->pixBlurredSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners);
+			} else if (const auto blurred = _dataMedia->thumbnailInline()) {
 				return blurred->pixBlurredSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners);
 			} else {
 				return QPixmap();
@@ -241,7 +282,7 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time
 		auto icon = [&]() -> const style::icon* {
 			if (radial || _data->loading()) {
 				if (_data->uploading()
-					|| _data->large()->location().valid()) {
+					|| _data->location(PhotoSize::Large).valid()) {
 					return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
 				}
 				return nullptr;
@@ -304,12 +345,13 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
 		painth -= st::mediaCaptionSkip;
 	}
 	if (QRect(paintx, painty, paintw, painth).contains(point)) {
+		ensureDataMediaCreated();
 		if (_data->uploading()) {
 			result.link = _cancell;
-		} else if (_data->loaded()) {
+		} else if (_dataMedia->loaded()) {
 			result.link = _openl;
 		} else if (_data->loading()) {
-			if (_data->large()->location().valid()) {
+			if (_data->location(PhotoSize::Large).valid()) {
 				result.link = _cancell;
 			}
 		} else {
@@ -349,19 +391,20 @@ void Photo::drawGrouped(
 		RectParts corners,
 		not_null<uint64*> cacheKey,
 		not_null<QPixmap*> cache) const {
-	_data->automaticLoad(_realParent->fullId(), _parent->data());
+	ensureDataMediaCreated();
+	_dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
 
 	validateGroupedCache(geometry, corners, cacheKey, cache);
 
 	const auto selected = (selection == FullSelection);
-	const auto loaded = _data->loaded();
+	const auto loaded = _dataMedia->loaded();
 	const auto displayLoading = _data->displayLoading();
 	const auto bubble = _parent->hasBubble();
 
 	if (displayLoading) {
 		ensureAnimation();
 		if (!_animation->radial.animating()) {
-			_animation->radial.start(_data->progress());
+			_animation->radial.start(_dataMedia->progress());
 		}
 	}
 	const auto radial = isRadialAnimation();
@@ -414,7 +457,7 @@ void Photo::drawGrouped(
 				return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting);
 			} else if (radial || _data->loading()) {
 				if (_data->uploading()
-					|| _data->large()->location().valid()) {
+					|| _data->location(PhotoSize::Large).valid()) {
 					return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
 				}
 				return nullptr;
@@ -455,19 +498,21 @@ TextState Photo::getStateGrouped(
 	if (!geometry.contains(point)) {
 		return {};
 	}
+	ensureDataMediaCreated();
 	return TextState(_parent, _data->uploading()
 		? _cancell
-		: _data->loaded()
+		: _dataMedia->loaded()
 		? _openl
 		: _data->loading()
-		? (_data->large()->location().valid()
+		? (_data->location(PhotoSize::Large).valid()
 			? _cancell
 			: nullptr)
 		: _savel);
 }
 
 float64 Photo::dataProgress() const {
-	return _data->progress();
+	ensureDataMediaCreated();
+	return _dataMedia->progress();
 }
 
 bool Photo::dataFinished() const {
@@ -476,7 +521,8 @@ bool Photo::dataFinished() const {
 }
 
 bool Photo::dataLoaded() const {
-	return _data->loaded();
+	ensureDataMediaCreated();
+	return _dataMedia->loaded();
 }
 
 bool Photo::needInfoDisplay() const {
@@ -491,12 +537,15 @@ void Photo::validateGroupedCache(
 		not_null<uint64*> cacheKey,
 		not_null<QPixmap*> cache) const {
 	using Option = Images::Option;
-	const auto loaded = _data->loaded();
+
+	ensureDataMediaCreated();
+
+	const auto loaded = _dataMedia->loaded();
 	const auto loadLevel = loaded
 		? 2
-		: (_data->thumbnailInline()
-			|| _data->thumbnail()->loaded()
-			|| _data->thumbnailSmall()->loaded())
+		: (_dataMedia->thumbnailInline()
+			|| _dataMedia->image(PhotoSize::Small)
+			|| _dataMedia->image(PhotoSize::Thumbnail))
 		? 1
 		: 0;
 	const auto width = geometry.width();
@@ -523,14 +572,14 @@ void Photo::validateGroupedCache(
 		{ width, height });
 	const auto pixWidth = pixSize.width() * cIntRetinaFactor();
 	const auto pixHeight = pixSize.height() * cIntRetinaFactor();
-	const auto image = loaded
-		? _data->large().get()
-		: _data->thumbnail()->loaded()
-		? _data->thumbnail().get()
-		: _data->thumbnailSmall()->loaded()
-		? _data->thumbnailSmall().get()
-		: _data->thumbnailInline()
-		? _data->thumbnailInline()
+	const auto image = _dataMedia->image(PhotoSize::Large)
+		? _dataMedia->image(PhotoSize::Large)
+		: _dataMedia->image(PhotoSize::Thumbnail)
+		? _dataMedia->image(PhotoSize::Thumbnail)
+		: _dataMedia->image(PhotoSize::Small)
+		? _dataMedia->image(PhotoSize::Small)
+		: _dataMedia->thumbnailInline()
+		? _dataMedia->thumbnailInline()
 		: Image::BlankMedia().get();
 
 	*cacheKey = key;
@@ -556,7 +605,8 @@ bool Photo::needsBubble() const {
 }
 
 bool Photo::isReadyForOpen() const {
-	return _data->loaded();
+	ensureDataMediaCreated();
+	return _dataMedia->loaded();
 }
 
 void Photo::parentTextUpdated() {
diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h
index fd9295b90..4e80d9c4f 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_photo.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "history/view/media/history_view_file.h"
 
+namespace Data {
+class PhotoMedia;
+} // namespace Data
+
 namespace HistoryView {
 
 class Photo : public File {
@@ -22,6 +26,7 @@ public:
 		not_null<PeerData*> chat,
 		not_null<PhotoData*> photo,
 		int width);
+	~Photo();
 
 	void draw(Painter &p, const QRect &clip, TextSelection selection, crl::time ms) const override;
 	TextState textState(QPoint point, StateRequest request) const override;
@@ -75,6 +80,9 @@ public:
 
 	void parentTextUpdated() override;
 
+	void checkHeavyPart() override;
+	void unloadHeavyPart() override;
+
 protected:
 	float64 dataProgress() const override;
 	bool dataFinished() const override;
@@ -83,6 +91,9 @@ protected:
 private:
 	void create(FullMsgId contextId, PeerData *chat = nullptr);
 
+	void ensureDataMediaCreated() const;
+	void dataMediaCreated() const;
+
 	QSize countOptimalSize() override;
 	QSize countCurrentSize(int newWidth) override;
 
@@ -98,6 +109,7 @@ private:
 	int _pixw = 1;
 	int _pixh = 1;
 	Ui::Text::String _caption;
+	mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
 
 };
 
diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp
index e17034226..04c33aee5 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_media_types.h"
 #include "data/data_web_page.h"
 #include "data/data_photo.h"
+#include "data/data_photo_media.h"
 #include "data/data_file_origin.h"
 #include "app.h"
 #include "styles/style_history.h"
@@ -32,15 +33,17 @@ namespace {
 constexpr auto kMaxOriginalEntryLines = 8192;
 
 int articleThumbWidth(not_null<PhotoData*> thumb, int height) {
-	auto w = thumb->thumbnail()->width();
-	auto h = thumb->thumbnail()->height();
-	return qMax(qMin(height * w / h, height), 1);
+	const auto size = thumb->location(Data::PhotoSize::Thumbnail);
+	return size.height()
+		? qMax(qMin(height * size.width() / size.height(), height), 1)
+		: 1;
 }
 
-int articleThumbHeight(not_null<PhotoData*> thumb, int width) {
-	return qMax(
-		thumb->thumbnail()->height() * width / thumb->thumbnail()->width(),
-		1);
+int articleThumbHeight(not_null<Data::PhotoMedia*> thumb, int width) {
+	const auto size = thumb->size(Data::PhotoSize::Thumbnail);
+	return size.width()
+		? std::max(size.height() * width / size.width(), 1)
+		: 1;
 }
 
 std::vector<std::unique_ptr<Data::Media>> PrepareCollageMedia(
@@ -410,6 +413,31 @@ void WebPage::refreshParentId(not_null<HistoryItem*> realParent) {
 	}
 }
 
+void WebPage::ensurePhotoMediaCreated() const {
+	Expects(_data->photo != nullptr);
+
+	if (_photoMedia) {
+		return;
+	}
+	_photoMedia = _data->photo->createMediaView();
+	const auto contextId = _parent->data()->fullId();
+	_photoMedia->wanted(Data::PhotoSize::Thumbnail, contextId);
+	history()->owner().registerHeavyViewPart(_parent);
+}
+
+void WebPage::checkHeavyPart() {
+	if (_attach) {
+		_attach->checkHeavyPart();
+	}
+}
+
+void WebPage::unloadHeavyPart() {
+	if (_attach) {
+		_attach->unloadHeavyPart();
+	}
+	_photoMedia = nullptr;
+}
+
 void WebPage::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
 	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
 	auto paintx = 0, painty = 0, paintw = width(), painth = height();
@@ -440,25 +468,28 @@ void WebPage::draw(Painter &p, const QRect &r, TextSelection selection, crl::tim
 
 	auto lineHeight = unitedLineHeight();
 	if (asArticle()) {
+		ensurePhotoMediaCreated();
+
 		const auto contextId = _parent->data()->fullId();
-		_data->photo->loadThumbnail(contextId);
-		bool full = _data->photo->thumbnail()->loaded();
 		QPixmap pix;
 		auto pw = qMax(_pixw, lineHeight);
 		auto ph = _pixh;
-		auto pixw = _pixw, pixh = articleThumbHeight(_data->photo, _pixw);
-		const auto maxw = style::ConvertScale(_data->photo->thumbnail()->width());
-		const auto maxh = style::ConvertScale(_data->photo->thumbnail()->height());
+		auto pixw = _pixw, pixh = articleThumbHeight(_photoMedia.get(), _pixw);
+		const auto maxsize = _photoMedia->size(Data::PhotoSize::Thumbnail);
+		const auto maxw = style::ConvertScale(maxsize.width());
+		const auto maxh = style::ConvertScale(maxsize.height());
 		if (pixw * ph != pixh * pw) {
 			float64 coef = (pixw * ph > pixh * pw) ? qMin(ph / float64(pixh), maxh / float64(pixh)) : qMin(pw / float64(pixw), maxw / float64(pixw));
 			pixh = qRound(pixh * coef);
 			pixw = qRound(pixw * coef);
 		}
-		if (full) {
-			pix = _data->photo->thumbnail()->pixSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small);
-		} else if (_data->photo->thumbnailSmall()->loaded()) {
-			pix = _data->photo->thumbnailSmall()->pixBlurredSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small);
-		} else if (const auto blurred = _data->photo->thumbnailInline()) {
+		if (const auto thumbnail = _photoMedia->image(
+				Data::PhotoSize::Thumbnail)) {
+			pix = thumbnail->pixSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small);
+		} else if (const auto small = _photoMedia->image(
+				Data::PhotoSize::Small)) {
+			pix = small->pixBlurredSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small);
+		} else if (const auto blurred = _photoMedia->thumbnailInline()) {
 			pix = blurred->pixBlurredSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small);
 		}
 		p.drawPixmapLeft(padding.left() + paintw - pw, tshift, width(), pix);
diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.h b/Telegram/SourceFiles/history/view/media/history_view_web_page.h
index 95afb0cbb..c3d9ab429 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 namespace Data {
 class Media;
+class PhotoMedia;
 } // namespace Data
 
 namespace HistoryView {
@@ -85,16 +86,8 @@ public:
 		return _attach.get();
 	}
 
-	void checkHeavyPart() override {
-		if (_attach) {
-			_attach->checkHeavyPart();
-		}
-	}
-	void unloadHeavyPart() override {
-		if (_attach) {
-			_attach->unloadHeavyPart();
-		}
-	}
+	void checkHeavyPart() override;
+	void unloadHeavyPart() override;
 
 	~WebPage();
 
@@ -103,6 +96,8 @@ private:
 	QSize countOptimalSize() override;
 	QSize countCurrentSize(int newWidth) override;
 
+	void ensurePhotoMediaCreated() const;
+
 	TextSelection toTitleSelection(TextSelection selection) const;
 	TextSelection fromTitleSelection(TextSelection selection) const;
 	TextSelection toDescriptionSelection(TextSelection selection) const;
@@ -119,6 +114,7 @@ private:
 	std::vector<std::unique_ptr<Data::Media>> _collage;
 	ClickHandlerPtr _openl;
 	std::unique_ptr<Media> _attach;
+	mutable std::shared_ptr<Data::PhotoMedia> _photoMedia;
 
 	bool _asArticle = false;
 	int _dataVersion = -1;
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp
index d74e0f8ea..050876814 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_document.h"
 #include "data/data_session.h"
 #include "data/data_file_origin.h"
+#include "data/data_photo_media.h"
 #include "data/data_document_media.h"
 #include "styles/style_overview.h"
 #include "styles/style_history.h"
@@ -47,7 +48,9 @@ FileBase::FileBase(not_null<Context*> context, not_null<Result*> result)
 : ItemBase(context, result) {
 }
 
-FileBase::FileBase(not_null<Context*> context, DocumentData *document)
+FileBase::FileBase(
+	not_null<Context*> context,
+	not_null<DocumentData*> document)
 : ItemBase(context, document) {
 }
 
@@ -87,10 +90,16 @@ int FileBase::content_duration() const {
 	return getResultDuration();
 }
 
-Gif::Gif(not_null<Context*> context, Result *result) : FileBase(context, result) {
+Gif::Gif(not_null<Context*> context, not_null<Result*> result)
+: FileBase(context, result) {
+	Expects(getResultDocument() != nullptr);
 }
 
-Gif::Gif(not_null<Context*> context, DocumentData *document, bool hasDeleteButton) : FileBase(context, document) {
+Gif::Gif(
+	not_null<Context*> context,
+	not_null<DocumentData*> document,
+	bool hasDeleteButton)
+: FileBase(context, document) {
 	if (hasDeleteButton) {
 		_delete = std::make_shared<DeleteSavedGifClickHandler>(document);
 	}
@@ -428,7 +437,7 @@ void Gif::clipCallback(Media::Clip::Notification notification) {
 	}
 }
 
-Sticker::Sticker(not_null<Context*> context, Result *result)
+Sticker::Sticker(not_null<Context*> context, not_null<Result*> result)
 : FileBase(context, result) {
 }
 
@@ -455,6 +464,12 @@ void Sticker::ensureDataMediaCreated(not_null<DocumentData*> document) const {
 	_dataMedia = document->createMediaView();
 }
 
+void Sticker::unloadHeavyPart() {
+	_dataMedia = nullptr;
+	_lifetime.destroy();
+	_lottie = nullptr;
+}
+
 void Sticker::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
 	ensureDataMediaCreated(getShownDocument());
 	bool loaded = _dataMedia->loaded();
@@ -572,8 +587,9 @@ void Sticker::prepareThumbnail() const {
 	}
 }
 
-Photo::Photo(not_null<Context*> context, Result *result)
+Photo::Photo(not_null<Context*> context, not_null<Result*> result)
 : ItemBase(context, result) {
+	Expects(getShownPhoto() != nullptr);
 }
 
 void Photo::initDimensions() {
@@ -611,15 +627,20 @@ TextState Photo::getState(
 	return {};
 }
 
+void Photo::unloadHeavyPart() {
+	getShownPhoto()->unload();
+	_photoMedia = nullptr;
+}
+
 PhotoData *Photo::getShownPhoto() const {
-	if (PhotoData *result = getPhoto()) {
+	if (const auto result = getPhoto()) {
 		return result;
 	}
 	return getResultPhoto();
 }
 
 QSize Photo::countFrameSize() const {
-	PhotoData *photo = getShownPhoto();
+	const auto photo = getShownPhoto();
 	int32 framew = photo->width(), frameh = photo->height(), height = st::inlineMediaHeight;
 	if (framew * height > frameh * _width) {
 		if (framew < st::maxStickerSize || frameh > height) {
@@ -672,15 +693,20 @@ void Photo::validateThumbnail(
 
 void Photo::prepareThumbnail(QSize size, QSize frame) const {
 	if (const auto photo = getShownPhoto()) {
-		validateThumbnail(photo->thumbnail(), size, frame, true);
-		validateThumbnail(photo->thumbnailSmall(), size, frame, false);
-		validateThumbnail(photo->thumbnailInline(), size, frame, false);
+		using PhotoSize = Data::PhotoSize;
+		if (!_photoMedia) {
+			_photoMedia = photo->createMediaView();
+			_photoMedia->wanted(PhotoSize::Thumbnail, fileOrigin());
+		}
+		validateThumbnail(_photoMedia->image(PhotoSize::Thumbnail), size, frame, true);
+		validateThumbnail(_photoMedia->image(PhotoSize::Small), size, frame, false);
+		validateThumbnail(_photoMedia->thumbnailInline(), size, frame, false);
 	} else if (const auto thumbnail = getResultThumb()) {
 		validateThumbnail(thumbnail, size, frame, true);
 	}
 }
 
-Video::Video(not_null<Context*> context, Result *result)
+Video::Video(not_null<Context*> context, not_null<Result*> result)
 : FileBase(context, result)
 , _link(getResultPreviewHandler())
 , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip)
@@ -765,6 +791,10 @@ void Video::paint(Painter &p, const QRect &clip, const PaintContext *context) co
 	}
 }
 
+void Video::unloadHeavyPart() {
+	_documentMedia = nullptr;
+}
+
 TextState Video::getState(
 		QPoint point,
 		StateRequest request) const {
@@ -951,6 +981,10 @@ void File::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
 	}
 }
 
+void File::unloadHeavyPart() {
+	_documentMedia = nullptr;
+}
+
 File::~File() {
 	unregDocumentItem(_document, this);
 }
@@ -1056,7 +1090,8 @@ void File::setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 r
 	}
 }
 
-Contact::Contact(not_null<Context*> context, Result *result) : ItemBase(context, result)
+Contact::Contact(not_null<Context*> context, not_null<Result*> result)
+: ItemBase(context, result)
 , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip)
 , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) {
 }
@@ -1111,7 +1146,7 @@ TextState Contact::getState(
 }
 
 void Contact::prepareThumbnail(int width, int height) const {
-	const auto thumb = getResultThumb();
+	const auto thumb = getResultThumb(); // #TODO optimize
 	if (!thumb) {
 		if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) {
 			_thumb = getResultContactAvatar(width, height);
@@ -1142,7 +1177,11 @@ void Contact::prepareThumbnail(int width, int height) const {
 	}
 }
 
-Article::Article(not_null<Context*> context, Result *result, bool withThumb) : ItemBase(context, result)
+Article::Article(
+	not_null<Context*> context,
+	not_null<Result*> result,
+	bool withThumb)
+: ItemBase(context, result)
 , _url(getResultUrlHandler())
 , _link(getResultPreviewHandler())
 , _withThumb(withThumb)
@@ -1196,7 +1235,7 @@ void Article::paint(Painter &p, const QRect &clip, const PaintContext *context)
 		prepareThumbnail(st::inlineThumbSize, st::inlineThumbSize);
 		QRect rthumb(style::rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width));
 		if (_thumb.isNull()) {
-			const auto thumb = getResultThumb();
+			const auto thumb = getResultThumb(); // #TODO optimize
 			if (!thumb && !_thumbLetter.isEmpty()) {
 				int32 index = (_thumbLetter.at(0).unicode() % 4);
 				style::color colors[] = {
@@ -1261,7 +1300,7 @@ TextState Article::getState(
 }
 
 void Article::prepareThumbnail(int width, int height) const {
-	const auto thumb = getResultThumb();
+	const auto thumb = getResultThumb(); // #TODO optimize
 	if (!thumb) {
 		if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) {
 			_thumb = getResultContactAvatar(width, height);
@@ -1292,7 +1331,8 @@ void Article::prepareThumbnail(int width, int height) const {
 	}
 }
 
-Game::Game(not_null<Context*> context, Result *result) : ItemBase(context, result)
+Game::Game(not_null<Context*> context, not_null<Result*> result)
+: ItemBase(context, result)
 , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip)
 , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) {
 	countFrameSize();
@@ -1359,18 +1399,21 @@ void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) con
 
 	// Gif thumb
 	auto thumbDisplayed = false, radial = false;
+	const auto photo = getResultPhoto();
 	const auto document = getResultDocument();
 	if (document) {
 		ensureDataMediaCreated(document);
+	} else if (photo) {
+		ensureDataMediaCreated(photo);
 	}
 	auto animatedThumb = document && document->isAnimation();
 	if (animatedThumb) {
-		_dataMedia->automaticLoad(fileOrigin(), nullptr);
+		_documentMedia->automaticLoad(fileOrigin(), nullptr);
 
-		bool loaded = _dataMedia->loaded(), loading = document->loading(), displayLoading = document->displayLoading();
+		bool loaded = _documentMedia->loaded(), loading = document->loading(), displayLoading = document->displayLoading();
 		if (loaded && !_gif && !_gif.isBad()) {
 			auto that = const_cast<Game*>(this);
-			that->_gif = Media::Clip::MakeReader(_dataMedia.get(), FullMsgId(), [that](Media::Clip::Notification notification) {
+			that->_gif = Media::Clip::MakeReader(_documentMedia.get(), FullMsgId(), [that](Media::Clip::Notification notification) {
 				that->clipCallback(notification);
 			});
 		}
@@ -1383,7 +1426,7 @@ void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) con
 				});
 			}
 			if (!_radial->animating()) {
-				_radial->start(_dataMedia->progress());
+				_radial->start(_documentMedia->progress());
 			}
 		}
 		radial = isRadialAnimation();
@@ -1445,22 +1488,33 @@ TextState Game::getState(
 }
 
 void Game::prepareThumbnail(QSize size) const {
-	if (const auto photo = getResultPhoto()) {
-		validateThumbnail(photo->thumbnail(), size, true);
-		validateThumbnail(photo->thumbnailInline(), size, false);
-	} else if (const auto document = getResultDocument()) {
-		Assert(_dataMedia != nullptr);
-		validateThumbnail(_dataMedia->thumbnail(), size, true);
-		validateThumbnail(_dataMedia->thumbnailInline(), size, false);
+	if (const auto document = getResultDocument()) {
+		Assert(_documentMedia != nullptr);
+		validateThumbnail(_documentMedia->thumbnail(), size, true);
+		validateThumbnail(_documentMedia->thumbnailInline(), size, false);
+	} else if (const auto photo = getResultPhoto()) {
+		using Data::PhotoSize;
+		Assert(_photoMedia != nullptr);
+		validateThumbnail(_photoMedia->image(PhotoSize::Thumbnail), size, true);
+		validateThumbnail(_photoMedia->image(PhotoSize::Small), size, false);
+		validateThumbnail(_photoMedia->thumbnailInline(), size, false);
 	}
 }
 
 void Game::ensureDataMediaCreated(not_null<DocumentData*> document) const {
-	if (_dataMedia) {
+	if (_documentMedia) {
 		return;
 	}
-	_dataMedia = document->createMediaView();
-	_dataMedia->thumbnailWanted(fileOrigin());
+	_documentMedia = document->createMediaView();
+	_documentMedia->thumbnailWanted(fileOrigin());
+}
+
+void Game::ensureDataMediaCreated(not_null<PhotoData*> photo) const {
+	if (_photoMedia) {
+		return;
+	}
+	_photoMedia = photo->createMediaView();
+	_photoMedia->wanted(Data::PhotoSize::Thumbnail, fileOrigin());
 }
 
 void Game::validateThumbnail(Image *image, QSize size, bool good) const {
@@ -1507,7 +1561,7 @@ bool Game::isRadialAnimation() const {
 			return true;
 		} else {
 			ensureDataMediaCreated(getResultDocument());
-			if (_dataMedia->loaded()) {
+			if (_documentMedia->loaded()) {
 				_radial = nullptr;
 			}
 		}
@@ -1520,22 +1574,28 @@ void Game::radialAnimationCallback(crl::time now) const {
 	ensureDataMediaCreated(document);
 	const auto updated = [&] {
 		return _radial->update(
-			_dataMedia->progress(),
-			!document->loading() || _dataMedia->loaded(),
+			_documentMedia->progress(),
+			!document->loading() || _documentMedia->loaded(),
 			now);
 	}();
 	if (!anim::Disabled() || updated) {
 		update();
 	}
-	if (!_radial->animating() && _dataMedia->loaded()) {
+	if (!_radial->animating() && _documentMedia->loaded()) {
 		_radial = nullptr;
 	}
 }
 
 void Game::unloadHeavyPart() {
 	_gif.reset();
-	getResultDocument()->unload();
-	_dataMedia = nullptr;
+	if (const auto document = getResultDocument()) {
+		document->unload();
+		_documentMedia = nullptr;
+	}
+	if (const auto photo = getResultPhoto()) {
+		photo->unload();
+		_photoMedia = nullptr;
+	}
 }
 
 void Game::clipCallback(Media::Clip::Notification notification) {
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h
index dcc9cb6a3..781eede49 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h
@@ -19,6 +19,7 @@ class SinglePlayer;
 } // namespace Lottie
 
 namespace Data {
+class PhotoMedia;
 class DocumentMedia;
 } // namespace Data
 
@@ -29,8 +30,9 @@ namespace internal {
 class FileBase : public ItemBase {
 public:
 	FileBase(not_null<Context*> context, not_null<Result*> result);
-	// for saved gif layouts
-	FileBase(not_null<Context*> context, DocumentData *doc);
+
+	// For saved gif layouts.
+	FileBase(not_null<Context*> context, not_null<DocumentData*> document);
 
 protected:
 	DocumentData *getShownDocument() const;
@@ -54,10 +56,13 @@ private:
 
 };
 
-class Gif : public FileBase {
+class Gif final : public FileBase {
 public:
-	Gif(not_null<Context*> context, Result *result);
-	Gif(not_null<Context*> context, DocumentData *doc, bool hasDeleteButton);
+	Gif(not_null<Context*> context, not_null<Result*> result);
+	Gif(
+		not_null<Context*> context,
+		not_null<DocumentData*> document,
+		bool hasDeleteButton);
 
 	void setPosition(int32 position) override;
 	void initDimensions() override;
@@ -131,9 +136,9 @@ private:
 
 class Photo : public ItemBase {
 public:
-	Photo(not_null<Context*> context, Result *result);
+	Photo(not_null<Context*> context, not_null<Result*> result);
 	// Not used anywhere currently.
-	//Photo(not_null<Context*> context, PhotoData *photo);
+	//Photo(not_null<Context*> context, not_null<PhotoData*> photo);
 
 	void initDimensions() override;
 
@@ -149,6 +154,8 @@ public:
 		QPoint point,
 		StateRequest request) const override;
 
+	void unloadHeavyPart() override;
+
 private:
 	PhotoData *getShownPhoto() const;
 
@@ -163,14 +170,16 @@ private:
 		QSize frame,
 		bool good) const;
 
+	mutable std::shared_ptr<Data::PhotoMedia> _photoMedia;
+
 };
 
 class Sticker : public FileBase {
 public:
-	Sticker(not_null<Context*> context, Result *result);
+	Sticker(not_null<Context*> context, not_null<Result*> result);
 	~Sticker();
 	// Not used anywhere currently.
-	//Sticker(not_null<Context*> context, DocumentData *document);
+	//Sticker(not_null<Context*> context, not_null<DocumentData*> document);
 
 	void initDimensions() override;
 
@@ -190,6 +199,8 @@ public:
 	// ClickHandlerHost interface
 	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
 
+	void unloadHeavyPart() override;
+
 private:
 	void ensureDataMediaCreated(not_null<DocumentData*> document) const;
 	void setupLottie() const;
@@ -210,7 +221,7 @@ private:
 
 class Video : public FileBase {
 public:
-	Video(not_null<Context*> context, Result *result);
+	Video(not_null<Context*> context, not_null<Result*> result);
 
 	void initDimensions() override;
 
@@ -219,6 +230,8 @@ public:
 		QPoint point,
 		StateRequest request) const override;
 
+	void unloadHeavyPart() override;
+
 private:
 	ClickHandlerPtr _link;
 
@@ -262,6 +275,7 @@ private:
 class File : public FileBase {
 public:
 	File(not_null<Context*> context, not_null<Result*> result);
+	~File();
 
 	void initDimensions() override;
 
@@ -273,7 +287,7 @@ public:
 	// ClickHandlerHost interface
 	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
 
-	~File();
+	void unloadHeavyPart() override;
 
 private:
 	void thumbAnimationCallback();
@@ -334,7 +348,7 @@ private:
 
 class Contact : public ItemBase {
 public:
-	Contact(not_null<Context*> context, Result *result);
+	Contact(not_null<Context*> context, not_null<Result*> result);
 
 	void initDimensions() override;
 
@@ -353,7 +367,7 @@ private:
 
 class Article : public ItemBase {
 public:
-	Article(not_null<Context*> context, Result *result, bool withThumb);
+	Article(not_null<Context*> context, not_null<Result*> result, bool withThumb);
 
 	void initDimensions() override;
 	int resizeGetHeight(int width) override;
@@ -378,7 +392,7 @@ private:
 
 class Game : public ItemBase {
 public:
-	Game(not_null<Context*> context, Result *result);
+	Game(not_null<Context*> context, not_null<Result*> result);
 
 	void setPosition(int32 position) override;
 	void initDimensions() override;
@@ -391,6 +405,7 @@ public:
 	void unloadHeavyPart() override;
 
 private:
+	void ensureDataMediaCreated(not_null<PhotoData*> photo) const;
 	void ensureDataMediaCreated(not_null<DocumentData*> document) const;
 	void countFrameSize();
 
@@ -403,7 +418,8 @@ private:
 	void clipCallback(Media::Clip::Notification notification);
 
 	Media::Clip::ReaderPointer _gif;
-	mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
+	mutable std::shared_ptr<Data::PhotoMedia> _photoMedia;
+	mutable std::shared_ptr<Data::DocumentMedia> _documentMedia;
 	mutable QPixmap _thumb;
 	mutable bool _thumbGood = false;
 	mutable std::unique_ptr<Ui::RadialAnimation> _radial;
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp
index 6fbd3c0d5..0173e3b38 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp
@@ -41,7 +41,7 @@ Result *ItemBase::getResult() const {
 }
 
 DocumentData *ItemBase::getDocument() const {
-	return _doc;
+	return _document;
 }
 
 PhotoData *ItemBase::getPhoto() const {
@@ -49,8 +49,8 @@ PhotoData *ItemBase::getPhoto() const {
 }
 
 DocumentData *ItemBase::getPreviewDocument() const {
-	if (_doc) {
-		return _doc;
+	if (_document) {
+		return _document;
 	} else if (_result) {
 		return _result->_document;
 	}
@@ -60,8 +60,7 @@ DocumentData *ItemBase::getPreviewDocument() const {
 PhotoData *ItemBase::getPreviewPhoto() const {
 	if (_photo) {
 		return _photo;
-	}
-	if (_result) {
+	} else if (_result) {
 		return _result->_photo;
 	}
 	return nullptr;
@@ -71,16 +70,16 @@ void ItemBase::preload() const {
 	const auto origin = fileOrigin();
 	if (_result) {
 		if (_result->_photo) {
-			_result->_photo->loadThumbnail(origin);
+			_result->_photo->load(Data::PhotoSize::Thumbnail, origin);
 		} else if (_result->_document) {
 			_result->_document->loadThumbnail(origin);
 		} else if (!_result->_thumb->isNull()) {
 			_result->_thumb->load(origin);
 		}
-	} else if (_doc) {
-		_doc->loadThumbnail(origin);
+	} else if (_document) {
+		_document->loadThumbnail(origin);
 	} else if (_photo) {
-		_photo->loadThumbnail(origin);
+		_photo->load(Data::PhotoSize::Thumbnail, origin);
 	}
 }
 
@@ -96,7 +95,10 @@ void ItemBase::layoutChanged() {
 	}
 }
 
-std::unique_ptr<ItemBase> ItemBase::createLayout(not_null<Context*> context, Result *result, bool forceThumb) {
+std::unique_ptr<ItemBase> ItemBase::createLayout(
+		not_null<Context*> context,
+		not_null<Result*> result,
+		bool forceThumb) {
 	using Type = Result::Type;
 
 	switch (result->_type) {
@@ -126,7 +128,9 @@ std::unique_ptr<ItemBase> ItemBase::createLayout(not_null<Context*> context, Res
 	return nullptr;
 }
 
-std::unique_ptr<ItemBase> ItemBase::createLayoutGif(not_null<Context*> context, DocumentData *document) {
+std::unique_ptr<ItemBase> ItemBase::createLayoutGif(
+		not_null<Context*> context,
+		not_null<DocumentData*> document) {
 	return std::make_unique<internal::Gif>(context, document, true);
 }
 
@@ -140,9 +144,7 @@ PhotoData *ItemBase::getResultPhoto() const {
 
 Image *ItemBase::getResultThumb() const {
 	if (_result) {
-		if (_result->_photo) {
-			return _result->_photo->thumbnail();
-		} else if (!_result->_thumb->isNull()) {
+		if (!_result->_thumb->isNull()) {
 			return _result->_thumb.get();
 		} else if (!_result->_locationThumb->isNull()) {
 			return _result->_locationThumb.get();
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h
index 86e6e0b5c..a630e8308 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h
@@ -50,8 +50,8 @@ public:
 	: _result(result)
 	, _context(context) {
 	}
-	ItemBase(not_null<Context*> context, DocumentData *doc)
-	: _doc(doc)
+	ItemBase(not_null<Context*> context, not_null<DocumentData*> document)
+	: _document(document)
 	, _context(context) {
 	}
 	// Not used anywhere currently.
@@ -94,8 +94,13 @@ public:
 		update();
 	}
 
-	static std::unique_ptr<ItemBase> createLayout(not_null<Context*> context, Result *result, bool forceThumb);
-	static std::unique_ptr<ItemBase> createLayoutGif(not_null<Context*> context, DocumentData *document);
+	static std::unique_ptr<ItemBase> createLayout(
+		not_null<Context*> context,
+		not_null<Result*> result,
+		bool forceThumb);
+	static std::unique_ptr<ItemBase> createLayoutGif(
+		not_null<Context*> context,
+		not_null<DocumentData*> document);
 
 protected:
 	DocumentData *getResultDocument() const;
@@ -114,7 +119,7 @@ protected:
 	Data::FileOrigin fileOrigin() const;
 
 	Result *_result = nullptr;
-	DocumentData *_doc = nullptr;
+	DocumentData *_document = nullptr;
 	PhotoData *_photo = nullptr;
 
 	ClickHandlerPtr _send = ClickHandlerPtr{ new SendClickHandler() };
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp
index 040831799..781e2caff 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_document.h"
 #include "data/data_session.h"
 #include "data/data_file_origin.h"
+#include "data/data_photo_media.h"
 #include "data/data_document_media.h"
 #include "inline_bots/inline_bot_layout_item.h"
 #include "inline_bots/inline_bot_send_data.h"
@@ -105,7 +106,9 @@ std::unique_ptr<Result> Result::create(
 			if (result->_type == Type::Photo) {
 				result->_photo = Auth().data().photoFromWeb(
 					*content,
-					result->_thumb,
+					(imageThumb
+						? Images::FromWebDocument(*r.vthumb())
+						: ImageLocation()),
 					true);
 			} else {
 				result->_document = Auth().data().documentFromWeb(
@@ -276,10 +279,13 @@ std::unique_ptr<Result> Result::create(
 
 bool Result::onChoose(Layout::ItemBase *layout) {
 	if (_photo && _type == Type::Photo) {
-		if (_photo->thumbnail()->loaded()) {
+		const auto media = _photo->activeMediaView();
+		if (!media || media->image(Data::PhotoSize::Thumbnail)) {
 			return true;
-		} else if (!_photo->thumbnail()->loading()) {
-			_photo->thumbnail()->loadEvenCancelled(Data::FileOrigin());
+		} else if (!_photo->loading(Data::PhotoSize::Thumbnail)) {
+			_photo->load(
+				Data::PhotoSize::Thumbnail,
+				Data::FileOrigin());
 		}
 		return false;
 	}
diff --git a/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp b/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp
index c320a3492..f32008236 100644
--- a/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_shared_media.h"
 #include "data/data_user_photos.h"
 #include "data/data_photo.h"
+#include "data/data_photo_media.h"
 #include "data/data_document.h"
 #include "data/data_document_media.h"
 #include "data/data_media_types.h"
@@ -129,9 +130,10 @@ public:
 		Dying,
 	};
 
+	Thumb(Key key, Fn<void()> handler);
 	Thumb(
 		Key key,
-		Image *image,
+		not_null<PhotoData*> photo,
 		Data::FileOrigin origin,
 		Fn<void()> handler);
 	Thumb(
@@ -165,6 +167,7 @@ private:
 	ClickHandlerPtr _link;
 	const Key _key;
 	std::shared_ptr<Data::DocumentMedia> _documentMedia;
+	std::shared_ptr<Data::PhotoMedia> _photoMedia;
 	Image *_image = nullptr;
 	Data::FileOrigin _origin;
 	State _state = State::Alive;
@@ -178,18 +181,28 @@ private:
 
 };
 
+GroupThumbs::Thumb::Thumb(Key key, Fn<void()> handler)
+: _key(key) {
+	_link = std::make_shared<LambdaClickHandler>(std::move(handler));
+	_fullWidth = std::min(
+		wantedPixSize().width(),
+		st::mediaviewGroupWidthMax);
+	validateImage();
+}
+
 GroupThumbs::Thumb::Thumb(
 	Key key,
-	Image *image,
+	not_null<PhotoData*> photo,
 	Data::FileOrigin origin,
 	Fn<void()> handler)
 : _key(key)
-, _image(image)
+, _photoMedia(photo->createMediaView())
 , _origin(origin) {
 	_link = std::make_shared<LambdaClickHandler>(std::move(handler));
 	_fullWidth = std::min(
 		wantedPixSize().width(),
 		st::mediaviewGroupWidthMax);
+	_photoMedia->wanted(Data::PhotoSize::Thumbnail, origin);
 	validateImage();
 }
 
@@ -218,8 +231,12 @@ QSize GroupThumbs::Thumb::wantedPixSize() const {
 }
 
 void GroupThumbs::Thumb::validateImage() {
-	if (!_image && _documentMedia) {
-		_image = _documentMedia->thumbnail();
+	if (!_image) {
+		if (_photoMedia) {
+			_image = _photoMedia->image(Data::PhotoSize::Thumbnail);
+		} else if (_documentMedia) {
+			_image = _documentMedia->thumbnail();
+		}
 	}
 	if (!_full.isNull() || !_image) {
 		return;
@@ -543,12 +560,12 @@ auto GroupThumbs::createThumb(Key key)
 -> std::unique_ptr<Thumb> {
 	if (const auto photoId = base::get_if<PhotoId>(&key)) {
 		const auto photo = Auth().data().photo(*photoId);
-		return createThumb(key, photo->thumbnail());
+		return createThumb(key, photo);
 	} else if (const auto msgId = base::get_if<FullMsgId>(&key)) {
 		if (const auto item = Auth().data().message(*msgId)) {
 			if (const auto media = item->media()) {
 				if (const auto photo = media->photo()) {
-					return createThumb(key, photo->thumbnail());
+					return createThumb(key, photo);
 				} else if (const auto document = media->document()) {
 					return createThumb(key, document);
 				}
@@ -583,18 +600,29 @@ auto GroupThumbs::createThumb(
 	}
 	const auto &item = collage.items[index];
 	if (const auto photo = base::get_if<PhotoData*>(&item)) {
-		return createThumb(key, (*photo)->thumbnail());
+		return createThumb(key, (*photo));
 	} else if (const auto document = base::get_if<DocumentData*>(&item)) {
 		return createThumb(key, (*document));
 	}
 	return createThumb(key, nullptr);
 }
 
-auto GroupThumbs::createThumb(Key key, Image *image)
+auto GroupThumbs::createThumb(Key key, std::nullptr_t)
 -> std::unique_ptr<Thumb> {
 	const auto weak = base::make_weak(this);
 	const auto origin = ComputeFileOrigin(key, _context);
-	return std::make_unique<Thumb>(key, image, origin, [=] {
+	return std::make_unique<Thumb>(key, [=] {
+		if (const auto strong = weak.get()) {
+			strong->_activateStream.fire_copy(key);
+		}
+	});
+}
+
+auto GroupThumbs::createThumb(Key key, not_null<PhotoData*> photo)
+-> std::unique_ptr<Thumb> {
+	const auto weak = base::make_weak(this);
+	const auto origin = ComputeFileOrigin(key, _context);
+	return std::make_unique<Thumb>(key, photo, origin, [=] {
 		if (const auto strong = weak.get()) {
 			strong->_activateStream.fire_copy(key);
 		}
diff --git a/Telegram/SourceFiles/media/view/media_view_group_thumbs.h b/Telegram/SourceFiles/media/view/media_view_group_thumbs.h
index 3126d1ba3..94937bfc9 100644
--- a/Telegram/SourceFiles/media/view/media_view_group_thumbs.h
+++ b/Telegram/SourceFiles/media/view/media_view_group_thumbs.h
@@ -100,10 +100,11 @@ private:
 		Key key,
 		const WebPageCollage &collage,
 		int index);
-	std::unique_ptr<Thumb> createThumb(Key key, Image *image);
+	std::unique_ptr<Thumb> createThumb(Key key, not_null<PhotoData*> photo);
 	std::unique_ptr<Thumb> createThumb(
 		Key key,
 		not_null<DocumentData*> document);
+	std::unique_ptr<Thumb> createThumb(Key key, std::nullptr_t);
 
 	void update();
 	void countUpdatedRect();
diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
index 9e68a0913..23979583a 100644
--- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
@@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_user.h"
 #include "data/data_file_origin.h"
 #include "data/data_media_rotation.h"
+#include "data/data_photo_media.h"
 #include "data/data_document_media.h"
 #include "window/themes/window_theme_preview.h"
 #include "window/window_peer_menu.h"
@@ -449,14 +450,14 @@ QSize OverlayWidget::videoSize() const {
 }
 
 bool OverlayWidget::videoIsGifv() const {
-	return _streamed && _doc->isAnimation() && !_doc->isVideoMessage();
+	return _streamed && _document->isAnimation() && !_document->isVideoMessage();
 }
 
 QImage OverlayWidget::videoFrame() const {
 	Expects(videoShown());
 
 	auto request = Streaming::FrameRequest();
-	//request.radius = (_doc && _doc->isVideoMessage())
+	//request.radius = (_document && _document->isVideoMessage())
 	//	? ImageRoundRadius::Ellipse
 	//	: ImageRoundRadius::None;
 	return _streamed->instance.player().ready()
@@ -504,21 +505,21 @@ QImage OverlayWidget::videoFrameForDirectPaint() const {
 }
 
 bool OverlayWidget::documentContentShown() const {
-	return _doc && (!_staticContent.isNull() || videoShown());
+	return _document && (!_staticContent.isNull() || videoShown());
 }
 
 bool OverlayWidget::documentBubbleShown() const {
-	return (!_photo && !_doc)
-		|| (_doc
+	return (!_photo && !_document)
+		|| (_document
 			&& !_themePreviewShown
 			&& !_streamed
 			&& _staticContent.isNull());
 }
 
 void OverlayWidget::clearStreaming(bool savePosition) {
-	if (_streamed && _doc && savePosition) {
+	if (_streamed && _document && savePosition) {
 		Media::Player::SaveLastPlaybackPosition(
-			_doc,
+			_document,
 			_streamed->instance.player().prepareLegacyState());
 	}
 	_fullScreenVideo = false;
@@ -526,21 +527,21 @@ void OverlayWidget::clearStreaming(bool savePosition) {
 }
 
 void OverlayWidget::documentUpdated(DocumentData *doc) {
-	if (_doc && _doc == doc) {
+	if (_document && _document == doc) {
 		if (documentBubbleShown()) {
-			if ((_doc->loading() && _docCancel->isHidden()) || (!_doc->loading() && !_docCancel->isHidden())) {
+			if ((_document->loading() && _docCancel->isHidden()) || (!_document->loading() && !_docCancel->isHidden())) {
 				updateControls();
-			} else if (_doc->loading()) {
+			} else if (_document->loading()) {
 				updateDocSize();
 				update(_docRect);
 			}
 		} else if (_streamed) {
-			const auto ready = _docMedia->loaded()
-				? _doc->size
-				: _doc->loading()
-				? std::clamp(_doc->loadOffset(), 0, _doc->size)
+			const auto ready = _documentMedia->loaded()
+				? _document->size
+				: _document->loading()
+				? std::clamp(_document->loadOffset(), 0, _document->size)
 				: 0;
-			_streamed->controls.setLoadingProgress(ready, _doc->size);
+			_streamed->controls.setLoadingProgress(ready, _document->size);
 		}
 	}
 }
@@ -553,10 +554,10 @@ void OverlayWidget::changingMsgId(not_null<HistoryItem*> row, MsgId newId) {
 }
 
 void OverlayWidget::updateDocSize() {
-	if (!_doc || !documentBubbleShown()) return;
+	if (!_document || !documentBubbleShown()) return;
 
-	if (_doc->loading()) {
-		quint64 ready = _doc->loadOffset(), total = _doc->size;
+	if (_document->loading()) {
+		quint64 ready = _document->loadOffset(), total = _document->size;
 		QString readyStr, totalStr, mb;
 		if (total >= 1024 * 1024) { // more than 1 mb
 			qint64 readyTenthMb = (ready * 10 / (1024 * 1024)), totalTenthMb = (total * 10 / (1024 * 1024));
@@ -575,7 +576,7 @@ void OverlayWidget::updateDocSize() {
 		}
 		_docSize = tr::lng_media_save_progress(tr::now, lt_ready, readyStr, lt_total, totalStr, lt_mb, mb);
 	} else {
-		_docSize = formatSizeText(_doc->size);
+		_docSize = formatSizeText(_document->size);
 	}
 	_docSizeWidth = st::mediaviewFont->width(_docSize);
 	int32 maxw = st::mediaviewFileSize.width() - st::mediaviewFileIconSize - st::mediaviewFilePadding * 3;
@@ -602,14 +603,14 @@ void OverlayWidget::refreshNavVisibility() {
 }
 
 void OverlayWidget::updateControls() {
-	if (_doc && documentBubbleShown()) {
-		if (_doc->loading()) {
+	if (_document && documentBubbleShown()) {
+		if (_document->loading()) {
 			_docDownload->hide();
 			_docSaveAs->hide();
 			_docCancel->moveToLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop);
 			_docCancel->show();
 		} else {
-			if (_docMedia->loaded(true)) {
+			if (_documentMedia->loaded(true)) {
 				_docDownload->hide();
 				_docSaveAs->moveToLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop);
 				_docSaveAs->show();
@@ -632,10 +633,10 @@ void OverlayWidget::updateControls() {
 
 	updateThemePreviewGeometry();
 
-	_saveVisible = (_photo && _photo->loaded())
-		|| (_doc
-			&& _doc->filepath(true).isEmpty()
-			&& !_doc->loading());
+	_saveVisible = (_photo && _photoMedia->loaded())
+		|| (_document
+			&& _document->filepath(true).isEmpty()
+			&& !_document->loading());
 	_saveNav = myrtlrect(width() - st::mediaviewIconSize.width() * 3, height() - st::mediaviewIconSize.height(), st::mediaviewIconSize.width(), st::mediaviewIconSize.height());
 	_saveNavIcon = style::centerrect(_saveNav, st::mediaviewSave);
 	_rotateNav = myrtlrect(width() - st::mediaviewIconSize.width() * 2, height() - st::mediaviewIconSize.height(), st::mediaviewIconSize.width(), st::mediaviewIconSize.height());
@@ -649,8 +650,8 @@ void OverlayWidget::updateControls() {
 			return ItemDateTime(item);
 		} else if (_photo) {
 			return base::unixtime::parse(_photo->date);
-		} else if (_doc) {
-			return base::unixtime::parse(_doc->date);
+		} else if (_document) {
+			return base::unixtime::parse(_document->date);
 		}
 		return dNow;
 	}();
@@ -733,16 +734,16 @@ void OverlayWidget::refreshCaptionGeometry() {
 void OverlayWidget::updateActions() {
 	_actions.clear();
 
-	if (_doc && _doc->loading()) {
+	if (_document && _document->loading()) {
 		_actions.push_back({ tr::lng_cancel(tr::now), SLOT(onSaveCancel()) });
 	}
 	if (IsServerMsgId(_msgid.msg)) {
 		_actions.push_back({ tr::lng_context_to_msg(tr::now), SLOT(onToMessage()) });
 	}
-	if (_doc && !_doc->filepath(true).isEmpty()) {
+	if (_document && !_document->filepath(true).isEmpty()) {
 		_actions.push_back({ Platform::IsMac() ? tr::lng_context_show_in_finder(tr::now) : tr::lng_context_show_in_folder(tr::now), SLOT(onShowInFolder()) });
 	}
-	if ((_doc && documentContentShown()) || (_photo && _photo->loaded())) {
+	if ((_document && documentContentShown()) || (_photo && _photoMedia->loaded())) {
 		_actions.push_back({ tr::lng_mediaview_copy(tr::now), SLOT(onCopy()) });
 	}
 	if (_photo && _photo->hasSticker) {
@@ -771,7 +772,7 @@ void OverlayWidget::updateActions() {
 	_actions.push_back({ tr::lng_mediaview_save_as(tr::now), SLOT(onSaveAs()) });
 
 	if (const auto overviewType = computeOverviewType()) {
-		_actions.push_back({ _doc ? tr::lng_mediaview_files_all(tr::now) : tr::lng_mediaview_photos_all(tr::now), SLOT(onOverview()) });
+		_actions.push_back({ _document ? tr::lng_mediaview_files_all(tr::now) : tr::lng_mediaview_photos_all(tr::now), SLOT(onOverview()) });
 	}
 }
 
@@ -783,7 +784,7 @@ auto OverlayWidget::computeOverviewType() const
 		} else if (mediaType == SharedMediaType::PhotoVideo) {
 			if (_photo) {
 				return SharedMediaOverviewType(SharedMediaType::Photo);
-			} else if (_doc) {
+			} else if (_document) {
 				return SharedMediaOverviewType(SharedMediaType::Video);
 			}
 		}
@@ -926,19 +927,19 @@ void OverlayWidget::resizeContentByScreenSize() {
 }
 
 float64 OverlayWidget::radialProgress() const {
-	if (_doc) {
-		return _docMedia->progress();
+	if (_document) {
+		return _documentMedia->progress();
 	} else if (_photo) {
-		return _photo->large()->progress();
+		return _photoMedia->progress();
 	}
 	return 1.;
 }
 
 bool OverlayWidget::radialLoading() const {
-	if (_doc) {
-		return _doc->loading() && !_streamed;
+	if (_document) {
+		return _document->loading() && !_streamed;
 	} else if (_photo) {
-		return _photo->large()->loading();
+		return _photo->loading();
 	}
 	return false;
 }
@@ -946,7 +947,7 @@ bool OverlayWidget::radialLoading() const {
 QRect OverlayWidget::radialRect() const {
 	if (_photo) {
 		return _photoRadialRect;
-	} else if (_doc) {
+	} else if (_document) {
 		return QRect(
 			QPoint(
 				_docIconRect.x() + ((_docIconRect.width() - st::radialSize.width()) / 2),
@@ -970,7 +971,7 @@ crl::time OverlayWidget::radialTimeShift() const {
 }
 
 bool OverlayWidget::radialAnimationCallback(crl::time now) {
-	if ((!_doc && !_photo) || _streamed) {
+	if ((!_document && !_photo) || _streamed) {
 		return false;
 	}
 	const auto wasAnimating = _radial.animating();
@@ -982,17 +983,17 @@ bool OverlayWidget::radialAnimationCallback(crl::time now) {
 		&& (!anim::Disabled() || updated)) {
 		update(radialRect());
 	}
-	const auto ready = _doc && _docMedia->loaded();
-	const auto streamVideo = ready && _docMedia->canBePlayed();
-	const auto tryOpenImage = ready && (_doc->size < App::kImageSizeLimit);
+	const auto ready = _document && _documentMedia->loaded();
+	const auto streamVideo = ready && _documentMedia->canBePlayed();
+	const auto tryOpenImage = ready && (_document->size < App::kImageSizeLimit);
 	if (ready && ((tryOpenImage && !_radial.animating()) || streamVideo)) {
 		_streamingStartPaused = false;
 		if (streamVideo) {
 			redisplayContent();
 		} else {
-			auto &location = _doc->location(true);
+			auto &location = _document->location(true);
 			if (location.accessEnable()) {
-				if (_doc->isTheme()
+				if (_document->isTheme()
 					|| QImageReader(location.name()).canRead()) {
 					redisplayContent();
 				}
@@ -1091,9 +1092,7 @@ void OverlayWidget::clearData() {
 	setContext(std::nullopt);
 	_from = nullptr;
 	_fromName = QString();
-	_photo = nullptr;
-	_doc = nullptr;
-	_docMedia = nullptr;
+	assignMediaPointer(nullptr);
 	_pip = nullptr;
 	_fullScreenVideo = false;
 	_caption.clear();
@@ -1103,6 +1102,31 @@ OverlayWidget::~OverlayWidget() {
 	delete base::take(_menu);
 }
 
+void OverlayWidget::assignMediaPointer(DocumentData *document) {
+	_photo = nullptr;
+	_photoMedia = nullptr;
+	if (_document != document) {
+		if ((_document = document)) {
+			_documentMedia = _document->createMediaView();
+			_documentMedia->goodThumbnailWanted();
+			_documentMedia->thumbnailWanted(fileOrigin());
+		} else {
+			_documentMedia = nullptr;
+		}
+	}
+}
+
+void OverlayWidget::assignMediaPointer(not_null<PhotoData*> photo) {
+	_document = nullptr;
+	_documentMedia = nullptr;
+	if (_photo != photo) {
+		_photo = photo;
+		_photoMedia = _photo->createMediaView();
+		_photoMedia->wanted(Data::PhotoSize::Large, fileOrigin());
+		_photoMedia->wanted(Data::PhotoSize::Small, fileOrigin());
+	}
+}
+
 void OverlayWidget::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
 	setCursor((active || ClickHandler::getPressed()) ? style::cur_pointer : style::cur_default);
 	update(QRegion(_saveMsg) + _captionRect);
@@ -1219,14 +1243,14 @@ void OverlayWidget::notifyFileDialogShown(bool shown) {
 
 void OverlayWidget::onSaveAs() {
 	QString file;
-	if (_doc) {
-		const auto &location = _doc->location(true);
-		const auto bytes = _docMedia->bytes();
+	if (_document) {
+		const auto &location = _document->location(true);
+		const auto bytes = _documentMedia->bytes();
 		if (!bytes.isEmpty() || location.accessEnable()) {
 			QFileInfo alreadyInfo(location.name());
 			QDir alreadyDir(alreadyInfo.dir());
 			QString name = alreadyInfo.fileName(), filter;
-			const auto mimeType = Core::MimeTypeForName(_doc->mimeString());
+			const auto mimeType = Core::MimeTypeForName(_document->mimeString());
 			QStringList p = mimeType.globPatterns();
 			QString pattern = p.isEmpty() ? QString() : p.front();
 			if (name.isEmpty()) {
@@ -1257,14 +1281,15 @@ void OverlayWidget::onSaveAs() {
 		} else {
 			DocumentSaveClickHandler::Save(
 				fileOrigin(),
-				_doc,
+				_document,
 				DocumentSaveClickHandler::Mode::ToNewFile);
 			updateControls();
 			updateOver(_lastMouseMovePos);
 		}
 	} else {
-		if (!_photo || !_photo->loaded()) return;
+		if (!_photo || !_photoMedia->loaded()) return;
 
+		const auto image = _photoMedia->image(Data::PhotoSize::Large)->original();
 		auto filter = qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter();
 		FileDialog::GetWritePath(
 			this,
@@ -1277,8 +1302,8 @@ void OverlayWidget::onSaveAs() {
 				false,
 				_photo->date),
 			crl::guard(this, [=, photo = _photo](const QString &result) {
-				if (!result.isEmpty() && _photo == photo && photo->loaded()) {
-					photo->large()->original().save(result, "JPG");
+				if (!result.isEmpty() && _photo == photo) {
+					image.save(result, "JPG");
 				}
 			}));
 	}
@@ -1288,15 +1313,15 @@ void OverlayWidget::onSaveAs() {
 }
 
 void OverlayWidget::onDocClick() {
-	if (_doc->loading()) {
+	if (_document->loading()) {
 		onSaveCancel();
 	} else {
 		DocumentOpenClickHandler::Open(
 			fileOrigin(),
-			_doc,
+			_document,
 			Auth().data().message(_msgid));
-		if (_doc->loading() && !_radial.animating()) {
-			_radial.start(_docMedia->progress());
+		if (_document->loading() && !_radial.animating()) {
+			_radial.start(_documentMedia->progress());
 		}
 	}
 }
@@ -1319,12 +1344,12 @@ void OverlayWidget::onDownload() {
 		path = Global::DownloadPath();
 	}
 	QString toName;
-	if (_doc) {
-		const auto &location = _doc->location(true);
+	if (_document) {
+		const auto &location = _document->location(true);
 		if (location.accessEnable()) {
 			if (!QDir().exists(path)) QDir().mkpath(path);
 			toName = filedialogNextFilename(
-				_doc->filename(),
+				_document->filename(),
 				location.name(),
 				path);
 			if (!toName.isEmpty() && toName != location.name()) {
@@ -1335,11 +1360,11 @@ void OverlayWidget::onDownload() {
 			}
 			location.accessDisable();
 		} else {
-			if (_doc->filepath(true).isEmpty()
-				&& !_doc->loading()) {
+			if (_document->filepath(true).isEmpty()
+				&& !_document->loading()) {
 				DocumentSaveClickHandler::Save(
 					fileOrigin(),
-					_doc,
+					_document,
 					DocumentSaveClickHandler::Mode::ToFile);
 				updateControls();
 			} else {
@@ -1349,13 +1374,18 @@ void OverlayWidget::onDownload() {
 			updateOver(_lastMouseMovePos);
 		}
 	} else {
-		if (!_photo || !_photo->loaded()) {
+		if (!_photo || !_photoMedia->loaded()) {
 			_saveVisible = false;
 			update(_saveNav);
 		} else {
-			if (!QDir().exists(path)) QDir().mkpath(path);
+			const auto image = _photoMedia->image(
+				Data::PhotoSize::Large)->original();
+
+			if (!QDir().exists(path)) {
+				QDir().mkpath(path);
+			}
 			toName = filedialogDefaultName(qsl("photo"), qsl(".jpg"), path);
-			if (!_photo->large()->original().save(toName, "JPG")) {
+			if (!image.save(toName, "JPG")) {
 				toName = QString();
 			}
 		}
@@ -1369,18 +1399,18 @@ void OverlayWidget::onDownload() {
 }
 
 void OverlayWidget::onSaveCancel() {
-	if (_doc && _doc->loading()) {
-		_doc->cancel();
-		if (_docMedia->canBePlayed()) {
+	if (_document && _document->loading()) {
+		_document->cancel();
+		if (_documentMedia->canBePlayed()) {
 			redisplayContent();
 		}
 	}
 }
 
 void OverlayWidget::onShowInFolder() {
-	if (!_doc) return;
+	if (!_document) return;
 
-	auto filepath = _doc->filepath(true);
+	auto filepath = _document->filepath(true);
 	if (!filepath.isEmpty()) {
 		File::ShowInFolder(filepath);
 		close();
@@ -1432,12 +1462,14 @@ void OverlayWidget::onOverview() {
 
 void OverlayWidget::onCopy() {
 	_dropdown->hideAnimated(Ui::DropdownMenu::HideOption::IgnoreShow);
-	if (_doc) {
+	if (_document) {
 		QGuiApplication::clipboard()->setImage(videoShown()
 			? transformVideoFrame(videoFrame())
 			: transformStaticContent(_staticContent));
-	} else if (_photo && _photo->loaded()) {
-		QGuiApplication::clipboard()->setPixmap(_photo->large()->pix(fileOrigin()));
+	} else if (_photo && _photoMedia->loaded()) {
+		const auto image = _photoMedia->image(
+			Data::PhotoSize::Large)->original();
+		QGuiApplication::clipboard()->setImage(image);
 	}
 }
 
@@ -1459,10 +1491,10 @@ std::optional<OverlayWidget::SharedMediaType> OverlayWidget::sharedMediaType() c
 				return Type::PhotoVideo;
 			}
 			return Type::ChatPhoto;
-		} else if (_doc) {
-			if (_doc->isGifv()) {
+		} else if (_document) {
+			if (_document->isGifv()) {
 				return Type::GIF;
-			} else if (_doc->isVideoFile()) {
+			} else if (_document->isVideoFile()) {
 				return Type::PhotoVideo;
 			}
 			return Type::File;
@@ -1563,7 +1595,7 @@ void OverlayWidget::validateSharedMedia() {
 }
 
 void OverlayWidget::handleSharedMediaUpdate(SharedMediaWithLastSlice &&update) {
-	if ((!_photo && !_doc) || !_sharedMedia) {
+	if ((!_photo && !_document) || !_sharedMedia) {
 		_sharedMediaData = std::nullopt;
 		_sharedMediaDataKey = std::nullopt;
 	} else {
@@ -1640,7 +1672,7 @@ std::optional<OverlayWidget::CollageKey> OverlayWidget::collageKey() const {
 		if (const auto media = item->media()) {
 			if (const auto page = media->webpage()) {
 				for (const auto &item : page->collage.items) {
-					if (item == _photo || item == _doc) {
+					if (item == _photo || item == _document) {
 						return item;
 					}
 				}
@@ -1749,10 +1781,10 @@ void OverlayWidget::refreshCaption(HistoryItem *item) {
 	using namespace HistoryView;
 	_caption = Ui::Text::String(st::msgMinWidth);
 	const auto duration = (_streamed && !videoIsGifv())
-		? _doc->getDuration()
+		? _document->getDuration()
 		: 0;
 	const auto base = duration
-		? DocumentTimestampLinkBase(_doc, item->fullId())
+		? DocumentTimestampLinkBase(_document, item->fullId())
 		: QString();
 	_caption.setMarkedText(
 		st::mediaviewCaptionStyle,
@@ -1850,7 +1882,7 @@ void OverlayWidget::showPhoto(not_null<PhotoData*> photo, HistoryItem *context)
 
 	clearControlsState();
 	_firstOpenedPeerPhoto = false;
-	_photo = photo;
+	assignMediaPointer(photo);
 
 	refreshMediaViewer();
 
@@ -1864,7 +1896,7 @@ void OverlayWidget::showPhoto(not_null<PhotoData*> photo, not_null<PeerData*> co
 
 	clearControlsState();
 	_firstOpenedPeerPhoto = true;
-	_photo = photo;
+	assignMediaPointer(photo);
 
 	refreshMediaViewer();
 
@@ -1897,7 +1929,6 @@ void OverlayWidget::showDocument(
 	}
 
 	clearControlsState();
-	_photo = nullptr;
 
 	_streamingStartPaused = false;
 	displayDocument(document, context, cloud, continueStreaming);
@@ -1916,10 +1947,9 @@ void OverlayWidget::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item)
 
 	clearStreaming();
 	destroyThemePreview();
-	_doc = nullptr;
-	_docMedia = nullptr;
+
 	_fullScreenVideo = false;
-	_photo = photo;
+	assignMediaPointer(photo);
 	_rotation = _photo->owner().mediaRotation().get(_photo);
 	_radial.stop();
 
@@ -1938,7 +1968,6 @@ void OverlayWidget::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item)
 	_h = size.height();
 	contentSizeChanged();
 	refreshFromLabel(item);
-	_photo->download(fileOrigin());
 	displayFinished();
 }
 
@@ -1959,7 +1988,7 @@ void OverlayWidget::redisplayContent() {
 	if (_photo) {
 		displayPhoto(_photo, item);
 	} else {
-		displayDocument(_doc, item);
+		displayDocument(_document, item);
 	}
 }
 
@@ -1974,42 +2003,37 @@ void OverlayWidget::displayDocument(
 	}
 	_fullScreenVideo = false;
 	_staticContent = QPixmap();
-	clearStreaming(_doc != doc);
+	clearStreaming(_document != doc);
 	destroyThemePreview();
-	_doc = doc;
-	if (_doc) {
-		_docMedia = _doc->createMediaView();
-		_docMedia->goodThumbnailWanted();
-		_docMedia->thumbnailWanted(fileOrigin());
-	}
-	_rotation = _doc ? _doc->owner().mediaRotation().get(_doc) : 0;
+	assignMediaPointer(doc);
+
+	_rotation = _document ? _document->owner().mediaRotation().get(_document) : 0;
 	_themeCloudData = cloud;
-	_photo = nullptr;
 	_radial.stop();
 
 	refreshMediaViewer();
-	if (_doc) {
-		if (_doc->sticker()) {
-			if (const auto image = _docMedia->getStickerLarge()) {
+	if (_document) {
+		if (_document->sticker()) {
+			if (const auto image = _documentMedia->getStickerLarge()) {
 				_staticContent = image->pix(fileOrigin());
-			} else if (const auto thumbnail = _docMedia->thumbnail()) {
+			} else if (const auto thumbnail = _documentMedia->thumbnail()) {
 				_staticContent = thumbnail->pixBlurred(
 					fileOrigin(),
-					_doc->dimensions.width(),
-					_doc->dimensions.height());
+					_document->dimensions.width(),
+					_document->dimensions.height());
 			}
 		} else {
-			if (_docMedia->canBePlayed()
+			if (_documentMedia->canBePlayed()
 				&& initStreaming(continueStreaming)) {
-			} else if (_doc->isVideoFile()) {
-				_docMedia->automaticLoad(fileOrigin(), item);
+			} else if (_document->isVideoFile()) {
+				_documentMedia->automaticLoad(fileOrigin(), item);
 				initStreamingThumbnail();
-			} else if (_doc->isTheme()) {
-				_docMedia->automaticLoad(fileOrigin(), item);
+			} else if (_document->isTheme()) {
+				_documentMedia->automaticLoad(fileOrigin(), item);
 				initThemePreview();
 			} else {
-				_docMedia->automaticLoad(fileOrigin(), item);
-				auto &location = _doc->location(true);
+				_documentMedia->automaticLoad(fileOrigin(), item);
+				auto &location = _document->location(true);
 				if (location.accessEnable()) {
 					const auto &path = location.name();
 					if (QImageReader(path).canRead()) {
@@ -2024,8 +2048,8 @@ void OverlayWidget::displayDocument(
 
 	_docIconRect = QRect((width() - st::mediaviewFileIconSize) / 2, (height() - st::mediaviewFileIconSize) / 2, st::mediaviewFileIconSize, st::mediaviewFileIconSize);
 	if (documentBubbleShown()) {
-		if (!_doc || !_doc->hasThumbnail()) {
-			int32 colorIndex = documentColorIndex(_doc, _docExt);
+		if (!_document || !_document->hasThumbnail()) {
+			int32 colorIndex = documentColorIndex(_document, _docExt);
 			_docIconColor = documentColor(colorIndex);
 			const style::icon *(thumbs[]) = { &st::mediaviewFileBlue, &st::mediaviewFileGreen, &st::mediaviewFileRed, &st::mediaviewFileYellow };
 			_docIcon = thumbs[colorIndex];
@@ -2037,9 +2061,9 @@ void OverlayWidget::displayDocument(
 				_docExtWidth = st::mediaviewFileExtFont->width(_docExt);
 			}
 		} else {
-			_doc->loadThumbnail(fileOrigin());
-			const auto tw = _docMedia->thumbnailSize().width();
-			const auto th = _docMedia->thumbnailSize().height();
+			_document->loadThumbnail(fileOrigin());
+			const auto tw = _documentMedia->thumbnailSize().width();
+			const auto th = _documentMedia->thumbnailSize().height();
 			if (!tw || !th) {
 				_docThumbx = _docThumby = _docThumbw = 0;
 			} else if (tw > th) {
@@ -2055,14 +2079,14 @@ void OverlayWidget::displayDocument(
 
 		int32 maxw = st::mediaviewFileSize.width() - st::mediaviewFileIconSize - st::mediaviewFilePadding * 3;
 
-		if (_doc) {
-			_docName = (_doc->type == StickerDocument)
+		if (_document) {
+			_docName = (_document->type == StickerDocument)
 				? tr::lng_in_dlg_sticker(tr::now)
-				: (_doc->type == AnimatedDocument
+				: (_document->type == AnimatedDocument
 					? qsl("GIF")
-					: (_doc->filename().isEmpty()
+					: (_document->filename().isEmpty()
 						? tr::lng_mediaview_doc_image(tr::now)
-						: _doc->filename()));
+						: _document->filename()));
 		} else {
 			_docName = tr::lng_message_empty(tr::now);
 		}
@@ -2135,15 +2159,15 @@ void OverlayWidget::displayFinished() {
 }
 
 bool OverlayWidget::initStreaming(bool continueStreaming) {
-	Expects(_doc != nullptr);
-	Expects(_docMedia->canBePlayed());
+	Expects(_document != nullptr);
+	Expects(_documentMedia->canBePlayed());
 
 	if (_streamed) {
 		return true;
 	}
 	initStreamingThumbnail();
 	if (!createStreamingObjects()) {
-		_doc->setInappPlaybackFailed();
+		_document->setInappPlaybackFailed();
 		return false;
 	}
 
@@ -2181,26 +2205,26 @@ void OverlayWidget::startStreamingPlayer() {
 		return;
 	}
 
-	const auto position = _doc
-		? _doc->session().settings().mediaLastPlaybackPosition(_doc->id)
+	const auto position = _document
+		? _document->session().settings().mediaLastPlaybackPosition(_document->id)
 		: 0;
 	restartAtSeekPosition(position);
 }
 
 void OverlayWidget::initStreamingThumbnail() {
-	Expects(_doc != nullptr);
+	Expects(_document != nullptr);
 
-	const auto good = _docMedia->goodThumbnail();
+	const auto good = _documentMedia->goodThumbnail();
 	const auto useGood = (good && good->loaded());
-	const auto thumbnail = _docMedia->thumbnail();
+	const auto thumbnail = _documentMedia->thumbnail();
 	const auto useThumb = (thumbnail != nullptr);
-	const auto blurred = _docMedia->thumbnailInline();
+	const auto blurred = _documentMedia->thumbnailInline();
 	if (good && !useGood) {
 		good->load({});
 	} else if (thumbnail) {
 		thumbnail->load(fileOrigin());
 	}
-	const auto size = useGood ? good->size() : _doc->dimensions;
+	const auto size = useGood ? good->size() : _document->dimensions;
 	if (!useGood && !thumbnail && !blurred) {
 		return;
 	} else if (size.isEmpty()) {
@@ -2208,7 +2232,7 @@ void OverlayWidget::initStreamingThumbnail() {
 	}
 	const auto w = size.width();
 	const auto h = size.height();
-	const auto options = VideoThumbOptions(_doc);
+	const auto options = VideoThumbOptions(_document);
 	const auto goodOptions = (options & ~Images::Option::Blurred);
 	_staticContent = (useGood
 		? good
@@ -2245,7 +2269,7 @@ void OverlayWidget::applyVideoSize() {
 
 bool OverlayWidget::createStreamingObjects() {
 	_streamed = std::make_unique<Streamed>(
-		_doc,
+		_document,
 		fileOrigin(),
 		this,
 		static_cast<PlaybackControls::Delegate*>(this),
@@ -2256,10 +2280,10 @@ bool OverlayWidget::createStreamingObjects() {
 	}
 	_streamed->instance.setPriority(kOverlayLoaderPriority);
 	_streamed->instance.lockPlayer();
-	_streamed->withSound = _doc->isAudioFile()
-		|| _doc->isVideoFile()
-		|| _doc->isVoiceMessage()
-		|| _doc->isVideoMessage();
+	_streamed->withSound = _document->isAudioFile()
+		|| _document->isVideoFile()
+		|| _document->isVoiceMessage()
+		|| _document->isVideoMessage();
 
 	if (videoIsGifv()) {
 		_streamed->controls.hide();
@@ -2316,14 +2340,14 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) {
 }
 
 void OverlayWidget::handleStreamingError(Streaming::Error &&error) {
-	Expects(_doc != nullptr);
+	Expects(_document != nullptr);
 
 	if (error == Streaming::Error::NotStreamable) {
-		_doc->setNotSupportsStreaming();
+		_document->setNotSupportsStreaming();
 	} else if (error == Streaming::Error::OpenFailed) {
-		_doc->setInappPlaybackFailed();
+		_document->setInappPlaybackFailed();
 	}
-	if (!_docMedia->canBePlayed()) {
+	if (!_documentMedia->canBePlayed()) {
 		redisplayContent();
 	} else {
 		updatePlaybackState();
@@ -2333,10 +2357,10 @@ void OverlayWidget::handleStreamingError(Streaming::Error &&error) {
 void OverlayWidget::initThemePreview() {
 	using namespace Window::Theme;
 
-	Assert(_doc && _doc->isTheme());
+	Assert(_document && _document->isTheme());
 
-	const auto bytes = _docMedia->bytes();
-	auto &location = _doc->location();
+	const auto bytes = _documentMedia->bytes();
+	auto &location = _document->location();
 	if (bytes.isEmpty()
 		&& (location.isEmpty() || !location.accessEnable())) {
 		return;
@@ -2348,22 +2372,22 @@ void OverlayWidget::initThemePreview() {
 	current.backgroundImage = Background()->createCurrentImage();
 	current.backgroundTiled = Background()->tile();
 
-	const auto &cloudList = _doc->session().data().cloudThemes().list();
+	const auto &cloudList = _document->session().data().cloudThemes().list();
 	const auto i = ranges::find(
 		cloudList,
-		_doc->id,
+		_document->id,
 		&Data::CloudTheme::documentId);
 	const auto cloud = (i != end(cloudList)) ? *i : Data::CloudTheme();
 	const auto isTrusted = (cloud.documentId != 0);
 	const auto fields = [&] {
 		auto result = _themeCloudData.id ? _themeCloudData : cloud;
 		if (!result.documentId) {
-			result.documentId = _doc->id;
+			result.documentId = _document->id;
 		}
 		return result;
 	}();
 
-	const auto path = _doc->location().name();
+	const auto path = _document->location().name();
 	const auto id = _themePreviewId = rand_value<uint64>();
 	const auto weak = Ui::MakeWeak(this);
 	crl::async([=, data = std::move(current)]() mutable {
@@ -2473,10 +2497,10 @@ void OverlayWidget::playbackControlsRotate() {
 		storage.set(_photo, storage.get(_photo) - 90);
 		_rotation = storage.get(_photo);
 		redisplayContent();
-	} else if (_doc) {
-		auto &storage = _doc->owner().mediaRotation();
-		storage.set(_doc, storage.get(_doc) - 90);
-		_rotation = storage.get(_doc);
+	} else if (_document) {
+		auto &storage = _document->owner().mediaRotation();
+		storage.set(_document, storage.get(_document) - 90);
+		_rotation = storage.get(_document);
 		if (videoShown()) {
 			applyVideoSize();
 			update(contentRect());
@@ -2492,7 +2516,7 @@ void OverlayWidget::playbackPauseResume() {
 	_streamed->resumeOnCallEnd = false;
 	if (_streamed->instance.player().failed()) {
 		clearStreaming();
-		if (!_docMedia->canBePlayed() || !initStreaming()) {
+		if (!_documentMedia->canBePlayed() || !initStreaming()) {
 			redisplayContent();
 		}
 	} else if (_streamed->instance.player().finished()
@@ -2511,7 +2535,7 @@ void OverlayWidget::playbackPauseResume() {
 
 void OverlayWidget::restartAtSeekPosition(crl::time position) {
 	Expects(_streamed != nullptr);
-	Expects(_doc != nullptr);
+	Expects(_document != nullptr);
 
 	if (videoShown()) {
 		_streamed->instance.saveFrameToCover();
@@ -2522,12 +2546,12 @@ void OverlayWidget::restartAtSeekPosition(crl::time position) {
 	}
 	auto options = Streaming::PlaybackOptions();
 	options.position = position;
-	options.audioId = AudioMsgId(_doc, _msgid);
+	options.audioId = AudioMsgId(_document, _msgid);
 	if (!_streamed->withSound) {
 		options.mode = Streaming::Mode::Video;
 		options.loop = true;
 	} else {
-		options.speed = _doc->session().settings().videoPlaybackSpeed();
+		options.speed = _document->session().settings().videoPlaybackSpeed();
 		if (_pip) {
 			_pip = nullptr;
 		}
@@ -2565,8 +2589,8 @@ void OverlayWidget::playbackControlsVolumeChanged(float64 volume) {
 	Global::SetVideoVolume(volume);
 	updateMixerVideoVolume();
 	Global::RefVideoVolumeChanged().notify();
-	if (_doc) {
-		_doc->session().saveSettingsDelayed();
+	if (_document) {
+		_document->session().saveSettingsDelayed();
 	}
 }
 
@@ -2588,10 +2612,10 @@ void OverlayWidget::playbackControlsVolumeChangeFinished() {
 
 void OverlayWidget::playbackControlsSpeedChanged(float64 speed) {
 	DEBUG_LOG(("Media playback speed: change to %1.").arg(speed));
-	if (_doc) {
+	if (_document) {
 		DEBUG_LOG(("Media playback speed: %1 to settings.").arg(speed));
-		_doc->session().settings().setVideoPlaybackSpeed(speed);
-		_doc->session().saveSettingsDelayed();
+		_document->session().settings().setVideoPlaybackSpeed(speed);
+		_document->session().saveSettingsDelayed();
 	}
 	if (_streamed && !videoIsGifv()) {
 		DEBUG_LOG(("Media playback speed: %1 to _streamed.").arg(speed));
@@ -2600,20 +2624,20 @@ void OverlayWidget::playbackControlsSpeedChanged(float64 speed) {
 }
 
 float64 OverlayWidget::playbackControlsCurrentSpeed() {
-	const auto result = _doc
-		? _doc->session().settings().videoPlaybackSpeed()
+	const auto result = _document
+		? _document->session().settings().videoPlaybackSpeed()
 		: 1.;
 	DEBUG_LOG(("Media playback speed: now %1 (doc %2)."
 		).arg(result
-		).arg(Logs::b(_doc != nullptr)));
+		).arg(Logs::b(_document != nullptr)));
 	return result;
 }
 
 void OverlayWidget::switchToPip() {
 	Expects(_streamed != nullptr);
-	Expects(_doc != nullptr);
+	Expects(_document != nullptr);
 
-	const auto document = _doc;
+	const auto document = _document;
 	const auto msgId = _msgid;
 	const auto closeAndContinue = [=] {
 		showDocument(document, document->owner().message(msgId), {}, true);
@@ -2698,9 +2722,6 @@ void OverlayWidget::updatePlaybackState() {
 
 void OverlayWidget::validatePhotoImage(Image *image, bool blurred) {
 	if (!image || !image->loaded()) {
-		if (!blurred) {
-			image->load(fileOrigin());
-		}
 		return;
 	} else if (!_staticContent.isNull() && (blurred || !_blurred)) {
 		return;
@@ -2718,10 +2739,10 @@ void OverlayWidget::validatePhotoImage(Image *image, bool blurred) {
 }
 
 void OverlayWidget::validatePhotoCurrentImage() {
-	validatePhotoImage(_photo->large(), false);
-	validatePhotoImage(_photo->thumbnail(), true);
-	validatePhotoImage(_photo->thumbnailSmall(), true);
-	validatePhotoImage(_photo->thumbnailInline(), true);
+	validatePhotoImage(_photoMedia->image(Data::PhotoSize::Large), false);
+	validatePhotoImage(_photoMedia->image(Data::PhotoSize::Thumbnail), true);
+	validatePhotoImage(_photoMedia->image(Data::PhotoSize::Small), true);
+	validatePhotoImage(_photoMedia->thumbnailInline(), true);
 	if (_staticContent.isNull()
 		&& _peer
 		&& !_msgid
@@ -2732,7 +2753,7 @@ void OverlayWidget::validatePhotoCurrentImage() {
 			true);
 	}
 	if (_staticContent.isNull()) {
-		_photo->loadThumbnailSmall(fileOrigin());
+		_photoMedia->wanted(Data::PhotoSize::Small, fileOrigin());
 	}
 }
 
@@ -2816,9 +2837,9 @@ void OverlayWidget::paintEvent(QPaintEvent *e) {
 			if (_docIconRect.intersects(r)) {
 				const auto radial = _radial.animating();
 				const auto radialOpacity = radial ? _radial.opacity() : 0.;
-				if (!_doc || !_doc->hasThumbnail()) {
+				if (!_document || !_document->hasThumbnail()) {
 					p.fillRect(_docIconRect, _docIconColor);
-					if ((!_doc || _docMedia->loaded()) && (!radial || radialOpacity < 1) && _docIcon) {
+					if ((!_document || _documentMedia->loaded()) && (!radial || radialOpacity < 1) && _docIcon) {
 						_docIcon->paint(p, _docIconRect.x() + (_docIconRect.width() - _docIcon->width()), _docIconRect.y(), width());
 						p.setPen(st::mediaviewFileExtFg);
 						p.setFont(st::mediaviewFileExtFont);
@@ -2826,7 +2847,7 @@ void OverlayWidget::paintEvent(QPaintEvent *e) {
 							p.drawText(_docIconRect.x() + (_docIconRect.width() - _docExtWidth) / 2, _docIconRect.y() + st::mediaviewFileExtTop + st::mediaviewFileExtFont->ascent, _docExt);
 						}
 					}
-				} else if (const auto thumbnail = _docMedia->thumbnail()) {
+				} else if (const auto thumbnail = _documentMedia->thumbnail()) {
 					int32 rf(cIntRetinaFactor());
 					p.drawPixmap(_docIconRect.topLeft(), thumbnail->pix(fileOrigin(), _docThumbw), QRect(_docThumbx * rf, _docThumby * rf, st::mediaviewFileIconSize * rf, st::mediaviewFileIconSize * rf));
 				}
@@ -3029,7 +3050,7 @@ void OverlayWidget::paintTransformedStaticContent(Painter &p) {
 	const auto rect = contentRect();
 
 	PainterHighQualityEnabler hq(p);
-	if ((!_doc || !_docMedia->getStickerLarge())
+	if ((!_document || !_documentMedia->getStickerLarge())
 		&& (_staticContent.isNull()
 			|| _staticContent.hasAlpha())) {
 		p.fillRect(rect, _transparentBrush);
@@ -3060,7 +3081,7 @@ void OverlayWidget::paintRadialLoading(
 		if (!_streamed->instance.waitingShown()) {
 			return;
 		}
-	} else if (!radial && (!_doc || _docMedia->loaded())) {
+	} else if (!radial && (!_document || _documentMedia->loaded())) {
 		return;
 	}
 
@@ -3127,11 +3148,11 @@ void OverlayWidget::paintRadialLoadingContent(
 	} else {
 		const auto o = overLevel(OverIcon);
 		paintBg(
-			_docMedia->loaded() ? radialOpacity : 1.,
+			_documentMedia->loaded() ? radialOpacity : 1.,
 			anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, o));
 
 		const auto icon = [&]() -> const style::icon * {
-			if (radial || _doc->loading()) {
+			if (radial || _document->loading()) {
 				return &st::historyFileThumbCancel;
 			}
 			return &st::historyFileThumbDownload;
@@ -3227,7 +3248,7 @@ void OverlayWidget::keyPressEvent(QKeyEvent *e) {
 		}
 	}
 	if (!_menu && e->key() == Qt::Key_Escape) {
-		if (_doc && _doc->loading() && !_streamed) {
+		if (_document && _document->loading() && !_streamed) {
 			onDocClick();
 		} else {
 			close();
@@ -3239,7 +3260,7 @@ void OverlayWidget::keyPressEvent(QKeyEvent *e) {
 	} else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) {
 		if (_streamed) {
 			playbackPauseResume();
-		} else if (_doc && !_doc->loading() && (documentBubbleShown() || !_docMedia->loaded())) {
+		} else if (_document && !_document->loading() && (documentBubbleShown() || !_documentMedia->loaded())) {
 			onDocClick();
 		}
 	} else if (e->key() == Qt::Key_Left) {
@@ -3485,22 +3506,26 @@ void OverlayWidget::preloadData(int delta) {
 		}
 	}
 
-	auto medias = base::flat_set<std::shared_ptr<Data::DocumentMedia>>();
+	auto photos = base::flat_set<std::shared_ptr<Data::PhotoMedia>>();
+	auto documents = base::flat_set<std::shared_ptr<Data::DocumentMedia>>();
 	for (auto index = from; index != till; ++index) {
 		auto entity = entityByIndex(index);
 		if (auto photo = base::get_if<not_null<PhotoData*>>(&entity.data)) {
-			(*photo)->download(fileOrigin());
+			const auto [i, ok] = photos.emplace((*photo)->createMediaView());
+			(*i)->wanted(Data::PhotoSize::Small, fileOrigin());
+			(*i)->wanted(Data::PhotoSize::Large, fileOrigin());
 		} else if (auto document = base::get_if<not_null<DocumentData*>>(
 				&entity.data)) {
-			const auto [i, ok] = medias.emplace(
+			const auto [i, ok] = documents.emplace(
 				(*document)->createMediaView());
-			(*document)->loadThumbnail(fileOrigin());
+			(*i)->thumbnailWanted(fileOrigin());
 			if (!(*i)->canBePlayed()) {
 				(*i)->automaticLoad(fileOrigin(), entity.item);
 			}
 		}
 	}
-	_preloadMedias = std::move(medias);
+	_preloadPhotos = std::move(photos);
+	_preloadDocuments = std::move(documents);
 }
 
 void OverlayWidget::mousePressEvent(QMouseEvent *e) {
@@ -3694,16 +3719,16 @@ void OverlayWidget::updateOver(QPoint pos) {
 		updateOverState(OverSave);
 	} else if (_rotateNav.contains(pos)) {
 		updateOverState(OverRotate);
-	} else if (_doc && documentBubbleShown() && _docIconRect.contains(pos)) {
+	} else if (_document && documentBubbleShown() && _docIconRect.contains(pos)) {
 		updateOverState(OverIcon);
 	} else if (_moreNav.contains(pos)) {
 		updateOverState(OverMore);
 	} else if (_closeNav.contains(pos)) {
 		updateOverState(OverClose);
 	} else if (documentContentShown() && contentRect().contains(pos)) {
-		if ((_doc->isVideoFile() || _doc->isVideoMessage()) && _streamed) {
+		if ((_document->isVideoFile() || _document->isVideoMessage()) && _streamed) {
 			updateOverState(OverVideo);
-		} else if (!_streamed && !_docMedia->loaded()) {
+		} else if (!_streamed && !_documentMedia->loaded()) {
 			updateOverState(OverIcon);
 		} else if (_over != OverNone) {
 			updateOverState(OverNone);
@@ -3763,7 +3788,7 @@ void OverlayWidget::mouseReleaseEvent(QMouseEvent *e) {
 				if (!_themePreviewRect.contains(e->pos())) {
 					close();
 				}
-			} else if (!_doc
+			} else if (!_document
 				|| documentContentShown()
 				|| !documentBubbleShown()
 				|| !_docRect.contains(e->pos())) {
@@ -4017,7 +4042,7 @@ void OverlayWidget::findCurrent() {
 			: std::nullopt;
 		_fullCount = _userPhotosData->fullCount();
 	} else if (_collageData) {
-		const auto item = _photo ? WebPageCollage::Item(_photo) : _doc;
+		const auto item = _photo ? WebPageCollage::Item(_photo) : _document;
 		const auto &items = _collageData->items;
 		const auto i = ranges::find(items, item);
 		_index = (i != end(items))
@@ -4034,13 +4059,13 @@ void OverlayWidget::updateHeader() {
 	auto index = _fullIndex ? *_fullIndex : -1;
 	auto count = _fullCount ? *_fullCount : -1;
 	if (index >= 0 && index < count && count > 1) {
-		if (_doc) {
+		if (_document) {
 			_headerText = tr::lng_mediaview_file_n_of_amount(
 				tr::now,
 				lt_file,
-				(_doc->filename().isEmpty()
+				(_document->filename().isEmpty()
 					? tr::lng_mediaview_doc_image(tr::now)
-					: _doc->filename()),
+					: _document->filename()),
 				lt_n,
 				QString::number(index + 1),
 				lt_amount,
@@ -4054,8 +4079,8 @@ void OverlayWidget::updateHeader() {
 				QString::number(count));
 		}
 	} else {
-		if (_doc) {
-			_headerText = _doc->filename().isEmpty() ? tr::lng_mediaview_doc_image(tr::now) : _doc->filename();
+		if (_document) {
+			_headerText = _document->filename().isEmpty() ? tr::lng_mediaview_doc_image(tr::now) : _document->filename();
 		} else if (_msgid) {
 			_headerText = tr::lng_mediaview_single_photo(tr::now);
 		} else if (_user) {
diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h
index 7308e047b..5ccabe462 100644
--- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h
+++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h
@@ -17,6 +17,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_cloud_themes.h" // Data::CloudTheme.
 #include "media/view/media_view_playback_controls.h"
 
+namespace Data {
+class PhotoMedia;
+class DocumentMedia;
+} // namespace Data
+
 namespace Ui {
 class PopupMenu;
 class LinkButton;
@@ -189,6 +194,9 @@ private:
 	void playbackPauseMusic();
 	void switchToPip();
 
+	void assignMediaPointer(DocumentData *document);
+	void assignMediaPointer(not_null<PhotoData*> photo);
+
 	void updateOver(QPoint mpos);
 	void moveToScreen(bool force = false);
 	bool moveToNext(int delta);
@@ -347,9 +355,11 @@ private:
 	QBrush _transparentBrush;
 
 	PhotoData *_photo = nullptr;
-	DocumentData *_doc = nullptr;
-	std::shared_ptr<Data::DocumentMedia> _docMedia;
-	base::flat_set<std::shared_ptr<Data::DocumentMedia>> _preloadMedias;
+	DocumentData *_document = nullptr;
+	std::shared_ptr<Data::PhotoMedia> _photoMedia;
+	std::shared_ptr<Data::DocumentMedia> _documentMedia;
+	base::flat_set<std::shared_ptr<Data::PhotoMedia>> _preloadPhotos;
+	base::flat_set<std::shared_ptr<Data::DocumentMedia>> _preloadDocuments;
 	int _rotation = 0;
 	std::unique_ptr<SharedMedia> _sharedMedia;
 	std::optional<SharedMediaWithLastSlice> _sharedMediaData;
diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp
index 433e26815..439aea9a3 100644
--- a/Telegram/SourceFiles/overview/overview_layout.cpp
+++ b/Telegram/SourceFiles/overview/overview_layout.cpp
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_media_types.h"
 #include "data/data_peer.h"
 #include "data/data_file_origin.h"
+#include "data/data_photo_media.h"
 #include "data/data_document_media.h"
 #include "styles/style_overview.h"
 #include "styles/style_history.h"
@@ -297,8 +298,8 @@ Photo::Photo(
 : ItemBase(delegate, parent)
 , _data(photo)
 , _link(std::make_shared<PhotoOpenClickHandler>(photo, parent->fullId())) {
-	if (!_data->thumbnailInline()) {
-		_data->loadThumbnailSmall(parent->fullId());
+	if (_data->inlineThumbnailBytes().isEmpty()) {
+		_data->load(Data::PhotoSize::Small, parent->fullId());
 	}
 }
 
@@ -317,27 +318,25 @@ int32 Photo::resizeGetHeight(int32 width) {
 }
 
 void Photo::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) {
-	bool good = _data->loaded(), selected = (selection == FullSelection);
-	if (!good) {
-		_data->thumbnail()->automaticLoad(parent()->fullId(), parent());
-		good = _data->thumbnail()->loaded();
-	}
-	if ((good && !_goodLoaded) || _pix.width() != _width * cIntRetinaFactor()) {
-		_goodLoaded = good;
-		_pix = QPixmap();
-		if (_goodLoaded) {
-			setPixFrom(_data->loaded()
-				? _data->large()
-				: _data->thumbnail());
-		} else if (_data->thumbnailSmall()->loaded()) {
-			setPixFrom(_data->thumbnailSmall());
-		} else if (const auto blurred = _data->thumbnailInline()) {
-			blurred->load({});
-			if (blurred->loaded()) {
+	const auto selected = (selection == FullSelection);
+	const auto widthChanged = _pix.width() != _width * cIntRetinaFactor();
+	if (!_goodLoaded || widthChanged) {
+		ensureDataMediaCreated();
+		const auto good = _dataMedia->loaded()
+			|| (_dataMedia->image(Data::PhotoSize::Thumbnail) != nullptr);
+		if ((good && !_goodLoaded) || widthChanged) {
+			_goodLoaded = good;
+			_pix = QPixmap();
+			if (_goodLoaded) {
+				setPixFrom(_dataMedia->image(Data::PhotoSize::Large)
+					? _dataMedia->image(Data::PhotoSize::Large)
+					: _dataMedia->image(Data::PhotoSize::Thumbnail));
+			} else if (const auto small = _dataMedia->image(
+					Data::PhotoSize::Small)) {
+				setPixFrom(small);
+			} else if (const auto blurred = _dataMedia->thumbnailInline()) {
 				setPixFrom(blurred);
 			}
-		} else {
-			_data->loadThumbnailSmall(parent()->fullId());
 		}
 	}
 
@@ -377,13 +376,30 @@ void Photo::setPixFrom(not_null<Image*> image) {
 
 	// In case we have inline thumbnail we can unload all images and we still
 	// won't get a blank image in the media viewer when the photo is opened.
-	if (_data->thumbnailInline() != nullptr) {
-		_data->unload();
+	if (!_data->inlineThumbnailBytes().isEmpty()) {
+		_dataMedia = nullptr;
+		delegate()->unregisterHeavyItem(this);
 	}
 
 	_pix = App::pixmapFromImageInPlace(std::move(img));
 }
 
+void Photo::ensureDataMediaCreated() const {
+	if (_dataMedia) {
+		return;
+	}
+	_dataMedia = _data->createMediaView();
+	if (_data->inlineThumbnailBytes().isEmpty()) {
+		_dataMedia->wanted(Data::PhotoSize::Small, parent()->fullId());
+	}
+	_dataMedia->wanted(Data::PhotoSize::Thumbnail, parent()->fullId());
+	delegate()->registerHeavyItem(this);
+}
+
+void Photo::clearHeavyPart() {
+	_dataMedia = nullptr;
+}
+
 TextState Photo::getState(
 		QPoint point,
 		StateRequest request) const {
@@ -1460,12 +1476,7 @@ Link::Link(
 	}
 	int32 tw = 0, th = 0;
 	if (_page && _page->photo) {
-		if (!_page->photo->loaded()
-			&& !_page->photo->thumbnail()->loaded()
-			&& !_page->photo->thumbnailSmall()->loaded()) {
-			_page->photo->loadThumbnailSmall(parent->fullId());
-		}
-
+		_page->photo->load(Data::PhotoSize::Small, parent->fullId());
 		tw = style::ConvertScale(_page->photo->width());
 		th = style::ConvertScale(_page->photo->height());
 	} else if (_page && _page->document && _page->document->hasThumbnail()) {
@@ -1609,17 +1620,21 @@ void Link::validateThumbnail() {
 		return;
 	}
 	if (_page && _page->photo) {
-		if (_page->photo->thumbnail()->loaded()) {
-			_thumbnail = _page->photo->thumbnail()->pixSingle(parent()->fullId(), _pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
-		} else if (_page->photo->loaded()) {
-			_thumbnail = _page->photo->large()->pixSingle(parent()->fullId(), _pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
-		} else if (_page->photo->thumbnailSmall()->loaded()) {
-			_thumbnail = _page->photo->thumbnailSmall()->pixSingle(parent()->fullId(), _pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
-		} else if (const auto blurred = _page->photo->thumbnailInline()) {
+		using Data::PhotoSize;
+		ensurePhotoMediaCreated();
+		if (const auto thumbnail = _photoMedia->image(PhotoSize::Thumbnail)) {
+			_thumbnail = thumbnail->pixSingle(parent()->fullId(), _pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
+		} else if (const auto large = _photoMedia->image(PhotoSize::Large)) {
+			_thumbnail = large->pixSingle(parent()->fullId(), _pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
+		} else if (const auto small = _photoMedia->image(PhotoSize::Small)) {
+			_thumbnail = small->pixSingle(parent()->fullId(), _pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
+		} else if (const auto blurred = _photoMedia->thumbnailInline()) {
 			_thumbnail = blurred->pixBlurredSingle(parent()->fullId(), _pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
 		} else {
 			return;
 		}
+		_photoMedia = nullptr;
+		delegate()->unregisterHeavyItem(this);
 	} else if (_page && _page->document && _page->document->hasThumbnail()) {
 		ensureDocumentMediaCreated();
 		if (const auto thumbnail = _documentMedia->thumbnail()) {
@@ -1664,6 +1679,15 @@ void Link::validateThumbnail() {
 	}
 }
 
+void Link::ensurePhotoMediaCreated() {
+	if (_photoMedia) {
+		return;
+	}
+	_photoMedia = _page->photo->createMediaView();
+	_photoMedia->wanted(Data::PhotoSize::Small, parent()->fullId());
+	delegate()->registerHeavyItem(this);
+}
+
 void Link::ensureDocumentMediaCreated() {
 	if (_documentMedia) {
 		return;
@@ -1674,6 +1698,7 @@ void Link::ensureDocumentMediaCreated() {
 }
 
 void Link::clearHeavyPart() {
+	_photoMedia = nullptr;
 	_documentMedia = nullptr;
 }
 
diff --git a/Telegram/SourceFiles/overview/overview_layout.h b/Telegram/SourceFiles/overview/overview_layout.h
index 24bbfb42a..a81d40a18 100644
--- a/Telegram/SourceFiles/overview/overview_layout.h
+++ b/Telegram/SourceFiles/overview/overview_layout.h
@@ -19,6 +19,7 @@ struct RoundCheckbox;
 
 namespace Data {
 class Media;
+class PhotoMedia;
 class DocumentMedia;
 } // namespace Data
 
@@ -184,10 +185,14 @@ public:
 		QPoint point,
 		StateRequest request) const override;
 
+	void clearHeavyPart() override;
+
 private:
+	void ensureDataMediaCreated() const;
 	void setPixFrom(not_null<Image*> image);
 
-	not_null<PhotoData*> _data;
+	const not_null<PhotoData*> _data;
+	mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
 	ClickHandlerPtr _link;
 
 	QPixmap _pix;
@@ -222,7 +227,7 @@ private:
 	void ensureDataMediaCreated() const;
 	void updateStatusText();
 
-	not_null<DocumentData*> _data;
+	const not_null<DocumentData*> _data;
 	mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
 	StatusText _status;
 
@@ -346,6 +351,7 @@ protected:
 	const style::RoundCheckbox &checkboxStyle() const override;
 
 private:
+	void ensurePhotoMediaCreated();
 	void ensureDocumentMediaCreated();
 	void validateThumbnail();
 
@@ -354,6 +360,7 @@ private:
 	QString _title, _letter;
 	int _titlew = 0;
 	WebPageData *_page = nullptr;
+	std::shared_ptr<Data::PhotoMedia> _photoMedia;
 	std::shared_ptr<Data::DocumentMedia> _documentMedia;
 	int _pixw = 0;
 	int _pixh = 0;
diff --git a/Telegram/SourceFiles/storage/file_download.h b/Telegram/SourceFiles/storage/file_download.h
index 5eb7f0163..0d7fc1c5d 100644
--- a/Telegram/SourceFiles/storage/file_download.h
+++ b/Telegram/SourceFiles/storage/file_download.h
@@ -66,29 +66,30 @@ public:
 
 	[[nodiscard]] Main::Session &session() const;
 
-	bool finished() const {
+	[[nodiscard]] bool finished() const {
 		return _finished;
 	}
 	void finishWithBytes(const QByteArray &data);
-	bool cancelled() const {
+	[[nodiscard]] bool cancelled() const {
 		return _cancelled;
 	}
-	const QByteArray &bytes() const {
+	[[nodiscard]] const QByteArray &bytes() const {
 		return _data;
 	}
-	virtual uint64 objId() const {
+	[[nodiscard]] virtual uint64 objId() const {
 		return 0;
 	}
-	QByteArray imageFormat(const QSize &shrinkBox = QSize()) const;
-	QImage imageData(const QSize &shrinkBox = QSize()) const;
-	QString fileName() const {
+	[[nodiscard]] QByteArray imageFormat(
+		const QSize &shrinkBox = QSize()) const;
+	[[nodiscard]] QImage imageData(const QSize &shrinkBox = QSize()) const;
+	[[nodiscard]] QString fileName() const {
 		return _filename;
 	}
 	// Used in MainWidget::documentLoadFailed.
 	[[nodiscard]] virtual Data::FileOrigin fileOrigin() const;
-	float64 currentProgress() const;
-	virtual int currentOffset() const;
-	int fullSize() const;
+	[[nodiscard]] float64 currentProgress() const;
+	[[nodiscard]] virtual int currentOffset() const;
+	[[nodiscard]] int fullSize() const;
 
 	bool setFileName(const QString &filename); // set filename for loaders to cache
 	void permitLoadFromCloud();
@@ -96,10 +97,10 @@ public:
 	void start();
 	void cancel();
 
-	bool loadingLocal() const {
+	[[nodiscard]] bool loadingLocal() const {
 		return (_localStatus == LocalStatus::Loading);
 	}
-	bool autoLoading() const {
+	[[nodiscard]] bool autoLoading() const {
 		return _autoLoading;
 	}
 
@@ -112,7 +113,7 @@ public:
 		return _updates.events();
 	}
 
-	rpl::lifetime &lifetime() {
+	[[nodiscard]] rpl::lifetime &lifetime() {
 		return _lifetime;
 	}
 
diff --git a/Telegram/SourceFiles/ui/image/image.cpp b/Telegram/SourceFiles/ui/image/image.cpp
index 8e5ed92e6..81c73ffeb 100644
--- a/Telegram/SourceFiles/ui/image/image.cpp
+++ b/Telegram/SourceFiles/ui/image/image.cpp
@@ -946,14 +946,6 @@ QImage Image::original() const {
 	return _data;
 }
 
-void Image::automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item) {
-	if (!loaded()) {
-		_source->automaticLoad(origin, item);
-	}
-}
-
 void Image::load(Data::FileOrigin origin) {
 	if (!loaded()) {
 		_source->load(origin);
diff --git a/Telegram/SourceFiles/ui/image/image.h b/Telegram/SourceFiles/ui/image/image.h
index a19fcb0a6..a72a6cced 100644
--- a/Telegram/SourceFiles/ui/image/image.h
+++ b/Telegram/SourceFiles/ui/image/image.h
@@ -67,11 +67,6 @@ public:
 	virtual QImage takeLoaded() = 0;
 	virtual void unload() = 0;
 
-	virtual void automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item) = 0;
-	virtual void automaticLoadSettingsChanged() = 0;
-
 	virtual bool loading() = 0;
 	virtual bool displayLoading() = 0;
 	virtual void cancel() = 0;
@@ -178,10 +173,6 @@ public:
 		int32 w,
 		int32 h = 0) const;
 
-	void automaticLoad(Data::FileOrigin origin, const HistoryItem *item);
-	void automaticLoadSettingsChanged() {
-		_source->automaticLoadSettingsChanged();
-	}
 	bool loading() const {
 		return _source->loading();
 	}
diff --git a/Telegram/SourceFiles/ui/image/image_location_factory.cpp b/Telegram/SourceFiles/ui/image/image_location_factory.cpp
index 9e741691f..64ff8569a 100644
--- a/Telegram/SourceFiles/ui/image/image_location_factory.cpp
+++ b/Telegram/SourceFiles/ui/image/image_location_factory.cpp
@@ -14,6 +14,65 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 namespace Images {
 
+ImageWithLocation FromPhotoSize(
+		not_null<Main::Session*> session,
+		const MTPDphoto &photo,
+		const MTPPhotoSize &size) {
+	return size.match([&](const MTPDphotoSize &data) {
+		return ImageWithLocation{
+			.location = ImageLocation(
+				DownloadLocation{ StorageFileLocation(
+					photo.vdc_id().v,
+					session->userId(),
+					MTP_inputPhotoFileLocation(
+						photo.vid(),
+						photo.vaccess_hash(),
+						photo.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(
+					photo.vdc_id().v,
+					session->userId(),
+					MTP_inputPhotoFileLocation(
+						photo.vid(),
+						photo.vaccess_hash(),
+						photo.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(
+		//			photo.vdc_id().v,
+		//			session->userId(),
+		//			MTP_inputPhotoFileLocation(
+		//				photo.vid(),
+		//				photo.vaccess_hash(),
+		//				photo.vfile_reference(),
+		//				data.vtype())) },
+		//		width, // ???
+		//		height), // ???
+		//	.bytesCount = bytes.size(),
+		//	.bytes = bytes
+		//};
+	}, [&](const MTPDphotoSizeEmpty &) {
+		return ImageWithLocation();
+	});
+}
+
 ImageWithLocation FromPhotoSize(
 		not_null<Main::Session*> session,
 		const MTPDdocument &document,
diff --git a/Telegram/SourceFiles/ui/image/image_location_factory.h b/Telegram/SourceFiles/ui/image/image_location_factory.h
index 666b05660..a3dc8459f 100644
--- a/Telegram/SourceFiles/ui/image/image_location_factory.h
+++ b/Telegram/SourceFiles/ui/image/image_location_factory.h
@@ -15,6 +15,10 @@ class Session;
 
 namespace Images {
 
+[[nodiscard]] ImageWithLocation FromPhotoSize(
+	not_null<Main::Session*> session,
+	const MTPDphoto &photo,
+	const MTPPhotoSize &size);
 [[nodiscard]] ImageWithLocation FromPhotoSize(
 	not_null<Main::Session*> session,
 	const MTPDdocument &document,
diff --git a/Telegram/SourceFiles/ui/image/image_source.cpp b/Telegram/SourceFiles/ui/image/image_source.cpp
index 1200f8170..bb73d1aa2 100644
--- a/Telegram/SourceFiles/ui/image/image_source.cpp
+++ b/Telegram/SourceFiles/ui/image/image_source.cpp
@@ -57,14 +57,6 @@ void ImageSource::unload() {
 	_data = QImage();
 }
 
-void ImageSource::automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item) {
-}
-
-void ImageSource::automaticLoadSettingsChanged() {
-}
-
 bool ImageSource::loading() {
 	return false;
 }
@@ -186,14 +178,6 @@ void LocalFileSource::unload() {
 	_data = QImage();
 }
 
-void LocalFileSource::automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item) {
-}
-
-void LocalFileSource::automaticLoadSettingsChanged() {
-}
-
 bool LocalFileSource::loading() {
 	return false;
 }
@@ -344,36 +328,6 @@ bool RemoteSource::loading() {
 	return (_loader != nullptr);
 }
 
-void RemoteSource::automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item) {
-	if (!item || cancelled()) {
-		return;
-	}
-	const auto loadFromCloud = Data::AutoDownload::Should(
-		Auth().settings().autoDownload(),
-		item->history()->peer,
-		this);
-
-	if (_loader) {
-		if (loadFromCloud) {
-			_loader->permitLoadFromCloud();
-		}
-	} else {
-		_loader = createLoader(
-			origin,
-			loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly,
-			true);
-	}
-	if (_loader) {
-		_loader->start();
-	}
-}
-
-void RemoteSource::automaticLoadSettingsChanged() {
-	_cancelled = false;
-}
-
 void RemoteSource::load(Data::FileOrigin origin) {
 	if (!_loader) {
 		_loader = createLoader(origin, LoadFromCloudOrLocal, false);
@@ -645,33 +599,33 @@ void DelayedStorageSource::performDelayedLoad(Data::FileOrigin origin) {
 		loadLocal();
 	}
 }
-
-void DelayedStorageSource::automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item) {
-	if (_location.valid()) {
-		StorageSource::automaticLoad(origin, item);
-		return;
-	} else if (_loadCancelled || !item) {
-		return;
-	}
-	const auto loadFromCloud = Data::AutoDownload::Should(
-		Auth().settings().autoDownload(),
-		item->history()->peer,
-		this);
-
-	if (_loadRequested) {
-		if (loadFromCloud) _loadFromCloud = loadFromCloud;
-	} else {
-		_loadFromCloud = loadFromCloud;
-		_loadRequested = true;
-	}
-}
-
-void DelayedStorageSource::automaticLoadSettingsChanged() {
-	if (_loadCancelled) _loadCancelled = false;
-	StorageSource::automaticLoadSettingsChanged();
-}
+//
+//void DelayedStorageSource::automaticLoad(
+//		Data::FileOrigin origin,
+//		const HistoryItem *item) {
+//	if (_location.valid()) {
+//		StorageSource::automaticLoad(origin, item);
+//		return;
+//	} else if (_loadCancelled || !item) {
+//		return;
+//	}
+//	const auto loadFromCloud = Data::AutoDownload::Should(
+//		Auth().settings().autoDownload(),
+//		item->history()->peer,
+//		this);
+//
+//	if (_loadRequested) {
+//		if (loadFromCloud) _loadFromCloud = loadFromCloud;
+//	} else {
+//		_loadFromCloud = loadFromCloud;
+//		_loadRequested = true;
+//	}
+//}
+//
+//void DelayedStorageSource::automaticLoadSettingsChanged() {
+//	if (_loadCancelled) _loadCancelled = false;
+//	StorageSource::automaticLoadSettingsChanged();
+//}
 
 void DelayedStorageSource::load(Data::FileOrigin origin) {
 	if (_location.valid()) {
diff --git a/Telegram/SourceFiles/ui/image/image_source.h b/Telegram/SourceFiles/ui/image/image_source.h
index 44845c7d9..5de31969e 100644
--- a/Telegram/SourceFiles/ui/image/image_source.h
+++ b/Telegram/SourceFiles/ui/image/image_source.h
@@ -20,11 +20,6 @@ public:
 	QImage takeLoaded() override;
 	void unload() override;
 
-	void automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item) override;
-	void automaticLoadSettingsChanged() override;
-
 	bool loading() override;
 	bool displayLoading() override;
 	void cancel() override;
@@ -69,11 +64,6 @@ public:
 	QImage takeLoaded() override;
 	void unload() override;
 
-	void automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item) override;
-	void automaticLoadSettingsChanged() override;
-
 	bool loading() override;
 	bool displayLoading() override;
 	void cancel() override;
@@ -115,11 +105,6 @@ public:
 	QImage takeLoaded() override;
 	void unload() override;
 
-	void automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item) override;
-	void automaticLoadSettingsChanged() override;
-
 	bool loading() override;
 	bool displayLoading() override;
 	void cancel() override;
@@ -256,11 +241,6 @@ public:
 	bool isDelayedStorageImage() const override;
 	void performDelayedLoad(Data::FileOrigin origin) override;
 
-	void automaticLoad(
-		Data::FileOrigin origin,
-		const HistoryItem *item) override; // auto load photo
-	void automaticLoadSettingsChanged() override;
-
 	bool loading() override {
 		return _location.valid()
 			? StorageSource::loading()
diff --git a/Telegram/SourceFiles/window/window_media_preview.cpp b/Telegram/SourceFiles/window/window_media_preview.cpp
index f0693edef..3e2197558 100644
--- a/Telegram/SourceFiles/window/window_media_preview.cpp
+++ b/Telegram/SourceFiles/window/window_media_preview.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "window/window_media_preview.h"
 
 #include "data/data_photo.h"
+#include "data/data_photo_media.h"
 #include "data/data_document.h"
 #include "data/data_document_media.h"
 #include "ui/image/image.h"
@@ -114,6 +115,7 @@ void MediaPreviewWidget::showPreview(
 	startShow();
 	_origin = origin;
 	_photo = nullptr;
+	_photoMedia = nullptr;
 	_document = document;
 	_documentMedia = _document->createMediaView();
 	_documentMedia->thumbnailWanted(_origin);
@@ -128,9 +130,10 @@ void MediaPreviewWidget::showPreview(
 		not_null<PhotoData*> photo) {
 	startShow();
 	_origin = origin;
-	_photo = photo;
 	_document = nullptr;
 	_documentMedia = nullptr;
+	_photo = photo;
+	_photoMedia = _photo->createMediaView();
 	fillEmojiString();
 	resetGifAndCache();
 }
@@ -159,6 +162,7 @@ void MediaPreviewWidget::hidePreview() {
 	_hiding = true;
 	_a_shown.start([=] { update(); }, 1., 0., st::stickerPreviewDuration);
 	_photo = nullptr;
+	_photoMedia = nullptr;
 	_document = nullptr;
 	_documentMedia = nullptr;
 	resetGifAndCache();
@@ -296,30 +300,31 @@ QPixmap MediaPreviewWidget::currentImage() const {
 		}
 	} else if (_photo) {
 		if (_cacheStatus != CacheLoaded) {
-			if (_photo->loaded()) {
+			if (_photoMedia->loaded()) {
 				QSize s = currentDimensions();
-				_cache = _photo->large()->pix(_origin, s.width(), s.height());
+				_cache = _photoMedia->image(Data::PhotoSize::Large)->pix(_origin, s.width(), s.height());
 				_cacheStatus = CacheLoaded;
 			} else {
 				_photo->load(_origin);
 				if (_cacheStatus != CacheThumbLoaded) {
 					QSize s = currentDimensions();
-					if (_photo->thumbnail()->loaded()) {
-						_cache = _photo->thumbnail()->pixBlurred(_origin, s.width(), s.height());
+					if (const auto thumbnail = _photoMedia->image(
+							Data::PhotoSize::Thumbnail)) {
+						_cache = thumbnail->pixBlurred(_origin, s.width(), s.height());
 						_cacheStatus = CacheThumbLoaded;
-					} else if (_photo->thumbnailSmall()->loaded()) {
-						_cache = _photo->thumbnailSmall()->pixBlurred(_origin, s.width(), s.height());
+					} else if (const auto small = _photoMedia->image(
+							Data::PhotoSize::Small)) {
+						_cache = small->pixBlurred(_origin, s.width(), s.height());
 						_cacheStatus = CacheThumbLoaded;
-					} else if (const auto blurred = _photo->thumbnailInline()) {
+					} else if (const auto blurred = _photoMedia->thumbnailInline()) {
 						_cache = blurred->pixBlurred(_origin, s.width(), s.height());
 						_cacheStatus = CacheThumbLoaded;
 					} else {
-						_photo->thumbnailSmall()->load(_origin);
+						_photoMedia->wanted(Data::PhotoSize::Small, _origin);
 					}
 				}
 			}
 		}
-
 	}
 	return _cache;
 }
diff --git a/Telegram/SourceFiles/window/window_media_preview.h b/Telegram/SourceFiles/window/window_media_preview.h
index ce334a505..a7d1283fd 100644
--- a/Telegram/SourceFiles/window/window_media_preview.h
+++ b/Telegram/SourceFiles/window/window_media_preview.h
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/rp_widget.h"
 
 namespace Data {
+class PhotoMedia;
 class DocumentMedia;
 } // namespace Data
 
@@ -60,9 +61,10 @@ private:
 	Ui::Animations::Simple _a_shown;
 	bool _hiding = false;
 	Data::FileOrigin _origin;
-	DocumentData *_document = nullptr;
-	std::shared_ptr<Data::DocumentMedia> _documentMedia;
 	PhotoData *_photo = nullptr;
+	DocumentData *_document = nullptr;
+	std::shared_ptr<Data::PhotoMedia> _photoMedia;
+	std::shared_ptr<Data::DocumentMedia> _documentMedia;
 	Media::Clip::ReaderPointer _gif, _gifThumbnail;
 	crl::time _gifLastPosition = 0;
 	std::unique_ptr<Lottie::SinglePlayer> _lottie;