tdesktop/Telegram/SourceFiles/storage/storage_media_prepare.cpp
John Preston 90f6642d33 Use always the same sizes for group layout.
For the floating point precision to matter less in the album layout
decisions use always full image sizes for layout
when sending an album and when displaying it.

Fixes #5049.
2018-08-04 16:48:15 +03:00

317 lines
8.5 KiB
C++

/*
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 "storage/storage_media_prepare.h"
#include "platform/platform_file_utilities.h"
#include "storage/localimageloader.h"
#include "core/mime_type.h"
namespace Storage {
namespace {
constexpr auto kMaxAlbumCount = 10;
bool HasExtensionFrom(const QString &file, const QStringList &extensions) {
for (const auto &extension : extensions) {
const auto ext = file.right(extension.size());
if (ext.compare(extension, Qt::CaseInsensitive) == 0) {
return true;
}
}
return false;
}
bool ValidPhotoForAlbum(const FileMediaInformation::Image &image) {
if (image.animated) {
return false;
}
const auto width = image.data.width();
const auto height = image.data.height();
return ValidateThumbDimensions(width, height);
}
bool ValidVideoForAlbum(const FileMediaInformation::Video &video) {
const auto width = video.thumbnail.width();
const auto height = video.thumbnail.height();
return ValidateThumbDimensions(width, height);
}
QSize PrepareShownDimensions(const QImage &preview) {
constexpr auto kMaxWidth = 1280;
constexpr auto kMaxHeight = 1280;
const auto result = preview.size();
return (result.width() > kMaxWidth || result.height() > kMaxHeight)
? result.scaled(kMaxWidth, kMaxHeight, Qt::KeepAspectRatio)
: result;
}
bool PrepareAlbumMediaIsWaiting(
QSemaphore &semaphore,
PreparedFile &file,
int previewWidth) {
// TODO: Use some special thread queue, like a separate QThreadPool.
crl::async([=, &semaphore, &file] {
const auto guard = gsl::finally([&] { semaphore.release(); });
if (!file.path.isEmpty()) {
file.mime = Core::MimeTypeForFile(QFileInfo(file.path)).name();
file.information = FileLoadTask::ReadMediaInformation(
file.path,
QByteArray(),
file.mime);
} else if (!file.content.isEmpty()) {
file.mime = Core::MimeTypeForData(file.content).name();
file.information = FileLoadTask::ReadMediaInformation(
QString(),
file.content,
file.mime);
} else {
Assert(file.information != nullptr);
}
using Image = FileMediaInformation::Image;
using Video = FileMediaInformation::Video;
if (const auto image = base::get_if<Image>(
&file.information->media)) {
if (ValidPhotoForAlbum(*image)) {
file.shownDimensions = PrepareShownDimensions(image->data);
file.preview = Images::prepareOpaque(image->data.scaledToWidth(
std::min(previewWidth, convertScale(image->data.width()))
* cIntRetinaFactor(),
Qt::SmoothTransformation));
file.preview.setDevicePixelRatio(cRetinaFactor());
file.type = PreparedFile::AlbumType::Photo;
}
} else if (const auto video = base::get_if<Video>(
&file.information->media)) {
if (ValidVideoForAlbum(*video)) {
auto blurred = Images::prepareBlur(Images::prepareOpaque(video->thumbnail));
file.shownDimensions = PrepareShownDimensions(video->thumbnail);
file.preview = std::move(blurred).scaledToWidth(
previewWidth * cIntRetinaFactor(),
Qt::SmoothTransformation);
file.preview.setDevicePixelRatio(cRetinaFactor());
file.type = PreparedFile::AlbumType::Video;
}
}
});
return true;
}
void PrepareAlbum(PreparedList &result, int previewWidth) {
const auto count = int(result.files.size());
if (count > kMaxAlbumCount) {
return;
}
result.albumIsPossible = (count > 1);
auto waiting = 0;
QSemaphore semaphore;
for (auto &file : result.files) {
if (PrepareAlbumMediaIsWaiting(semaphore, file, previewWidth)) {
++waiting;
}
}
if (waiting > 0) {
semaphore.acquire(waiting);
if (result.albumIsPossible) {
const auto badIt = ranges::find(
result.files,
PreparedFile::AlbumType::None,
[](const PreparedFile &file) { return file.type; });
result.albumIsPossible = (badIt == result.files.end());
}
}
}
} // namespace
bool ValidateThumbDimensions(int width, int height) {
return (width > 0)
&& (height > 0)
&& (width < 20 * height)
&& (height < 20 * width);
}
PreparedFile::PreparedFile(const QString &path) : path(path) {
}
PreparedFile::PreparedFile(PreparedFile &&other) = default;
PreparedFile &PreparedFile::operator=(PreparedFile &&other) = default;
PreparedFile::~PreparedFile() = default;
MimeDataState ComputeMimeDataState(const QMimeData *data) {
if (!data || data->hasFormat(qsl("application/x-td-forward"))) {
return MimeDataState::None;
}
if (data->hasImage()) {
return MimeDataState::Image;
}
const auto uriListFormat = qsl("text/uri-list");
if (!data->hasFormat(uriListFormat)) {
return MimeDataState::None;
}
const auto &urls = data->urls();
if (urls.isEmpty()) {
return MimeDataState::None;
}
const auto imageExtensions = cImgExtensions();
auto files = QStringList();
auto allAreSmallImages = true;
for (const auto &url : urls) {
if (!url.isLocalFile()) {
return MimeDataState::None;
}
const auto file = Platform::File::UrlToLocal(url);
const auto info = QFileInfo(file);
if (info.isDir()) {
return MimeDataState::None;
}
const auto filesize = info.size();
if (filesize > App::kFileSizeLimit) {
return MimeDataState::None;
} else if (allAreSmallImages) {
if (filesize > App::kImageSizeLimit) {
allAreSmallImages = false;
} else if (!HasExtensionFrom(file, imageExtensions)) {
allAreSmallImages = false;
}
}
}
return allAreSmallImages
? MimeDataState::PhotoFiles
: MimeDataState::Files;
}
PreparedList PrepareMediaList(const QList<QUrl> &files, int previewWidth) {
auto locals = QStringList();
locals.reserve(files.size());
for (const auto &url : files) {
if (!url.isLocalFile()) {
return {
PreparedList::Error::NonLocalUrl,
url.toDisplayString()
};
}
locals.push_back(Platform::File::UrlToLocal(url));
}
return PrepareMediaList(locals, previewWidth);
}
PreparedList PrepareMediaList(const QStringList &files, int previewWidth) {
auto result = PreparedList();
result.files.reserve(files.size());
const auto extensionsToCompress = cExtensionsForCompress();
for (const auto &file : files) {
const auto fileinfo = QFileInfo(file);
const auto filesize = fileinfo.size();
if (fileinfo.isDir()) {
return {
PreparedList::Error::Directory,
file
};
} else if (filesize <= 0) {
return {
PreparedList::Error::EmptyFile,
file
};
} else if (filesize > App::kFileSizeLimit) {
return {
PreparedList::Error::TooLargeFile,
file
};
}
const auto toCompress = HasExtensionFrom(file, extensionsToCompress);
if (filesize > App::kImageSizeLimit || !toCompress) {
result.allFilesForCompress = false;
}
result.files.emplace_back(file);
}
PrepareAlbum(result, previewWidth);
return result;
}
PreparedList PrepareMediaFromImage(
QImage &&image,
QByteArray &&content,
int previewWidth) {
auto result = Storage::PreparedList();
result.allFilesForCompress = ValidateThumbDimensions(
image.width(),
image.height());
auto file = PreparedFile(QString());
file.content = content;
if (file.content.isEmpty()) {
file.information = std::make_unique<FileMediaInformation>();
const auto animated = false;
FileLoadTask::FillImageInformation(
std::move(image),
animated,
file.information);
}
result.files.push_back(std::move(file));
PrepareAlbum(result, previewWidth);
return result;
}
PreparedList PreparedList::Reordered(
PreparedList &&list,
std::vector<int> order) {
Expects(list.error == PreparedList::Error::None);
Expects(list.files.size() == order.size());
auto result = PreparedList(list.error, list.errorData);
result.albumIsPossible = list.albumIsPossible;
result.allFilesForCompress = list.allFilesForCompress;
result.files.reserve(list.files.size());
for (auto index : order) {
result.files.push_back(std::move(list.files[index]));
}
return result;
}
void PreparedList::mergeToEnd(PreparedList &&other) {
if (error != Error::None) {
return;
}
if (other.error != Error::None) {
error = other.error;
errorData = other.errorData;
return;
}
allFilesForCompress = allFilesForCompress && other.allFilesForCompress;
files.reserve(files.size() + other.files.size());
for (auto &file : other.files) {
files.push_back(std::move(file));
}
if (files.size() > 1 && files.size() <= kMaxAlbumCount) {
const auto badIt = ranges::find(
files,
PreparedFile::AlbumType::None,
[](const PreparedFile &file) { return file.type; });
albumIsPossible = (badIt == files.end());
} else {
albumIsPossible = false;
}
}
int MaxAlbumItems() {
return kMaxAlbumCount;
}
} // namespace Storage