2
0
mirror of https://github.com/kotatogram/kotatogram-desktop synced 2025-08-31 06:35:14 +00:00

Move photo data to Data::PhotoMedia.

This commit is contained in:
John Preston
2020-05-25 18:16:04 +04:00
parent 24fed8105c
commit e27d2bc2d5
51 changed files with 1628 additions and 922 deletions

View File

@@ -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(

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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();

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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,