mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Don't recompress some JPEGs when sending as photos.
If JPEG is saved in progressive mode and has bpp <= 4 and max(width, height) <= 1280 then we send original bytes.
This commit is contained in:
parent
e84ebc2a5c
commit
6805259f74
4 changed files with 77 additions and 23 deletions
|
@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "boxes/send_files_box.h"
|
#include "boxes/send_files_box.h"
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
|
#include "ui/image/image_prepare.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "storage/file_download.h"
|
#include "storage/file_download.h"
|
||||||
#include "storage/storage_media_prepare.h"
|
#include "storage/storage_media_prepare.h"
|
||||||
|
@ -42,6 +43,7 @@ namespace {
|
||||||
constexpr auto kThumbnailQuality = 87;
|
constexpr auto kThumbnailQuality = 87;
|
||||||
constexpr auto kThumbnailSize = 320;
|
constexpr auto kThumbnailSize = 320;
|
||||||
constexpr auto kPhotoUploadPartSize = 32 * 1024;
|
constexpr auto kPhotoUploadPartSize = 32 * 1024;
|
||||||
|
constexpr auto kRecompressAfterBpp = 4;
|
||||||
|
|
||||||
using Ui::ValidateThumbDimensions;
|
using Ui::ValidateThumbDimensions;
|
||||||
|
|
||||||
|
@ -53,7 +55,7 @@ struct PreparedFileThumbnail {
|
||||||
MTPPhotoSize mtpSize = MTP_photoSizeEmpty(MTP_string());
|
MTPPhotoSize mtpSize = MTP_photoSizeEmpty(MTP_string());
|
||||||
};
|
};
|
||||||
|
|
||||||
PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
|
[[nodiscard]] PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
|
||||||
const auto width = original.width();
|
const auto width = original.width();
|
||||||
const auto height = original.height();
|
const auto height = original.height();
|
||||||
if (!ValidateThumbDimensions(width, height)) {
|
if (!ValidateThumbDimensions(width, height)) {
|
||||||
|
@ -87,7 +89,9 @@ PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileThumbnailUploadRequired(const QString &filemime, int32 filesize) {
|
[[nodiscard]] bool FileThumbnailUploadRequired(
|
||||||
|
const QString &filemime,
|
||||||
|
int32 filesize) {
|
||||||
constexpr auto kThumbnailUploadBySize = 5 * 1024 * 1024;
|
constexpr auto kThumbnailUploadBySize = 5 * 1024 * 1024;
|
||||||
const auto kThumbnailKnownMimes = {
|
const auto kThumbnailKnownMimes = {
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
|
@ -101,7 +105,7 @@ bool FileThumbnailUploadRequired(const QString &filemime, int32 filesize) {
|
||||||
== end(kThumbnailKnownMimes));
|
== end(kThumbnailKnownMimes));
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparedFileThumbnail FinalizeFileThumbnail(
|
[[nodiscard]] PreparedFileThumbnail FinalizeFileThumbnail(
|
||||||
PreparedFileThumbnail &&prepared,
|
PreparedFileThumbnail &&prepared,
|
||||||
const QString &filemime,
|
const QString &filemime,
|
||||||
int32 filesize,
|
int32 filesize,
|
||||||
|
@ -115,7 +119,7 @@ PreparedFileThumbnail FinalizeFileThumbnail(
|
||||||
return std::move(prepared);
|
return std::move(prepared);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto FindAlbumItem(
|
[[nodiscard]] auto FindAlbumItem(
|
||||||
std::vector<SendingAlbum::Item> &items,
|
std::vector<SendingAlbum::Item> &items,
|
||||||
not_null<HistoryItem*> item) {
|
not_null<HistoryItem*> item) {
|
||||||
const auto result = ranges::find(
|
const auto result = ranges::find(
|
||||||
|
@ -127,7 +131,7 @@ auto FindAlbumItem(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
MTPInputSingleMedia PrepareAlbumItemMedia(
|
[[nodiscard]] MTPInputSingleMedia PrepareAlbumItemMedia(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
const MTPInputMedia &media,
|
const MTPInputMedia &media,
|
||||||
uint64 randomId) {
|
uint64 randomId) {
|
||||||
|
@ -149,7 +153,7 @@ MTPInputSingleMedia PrepareAlbumItemMedia(
|
||||||
sentEntities);
|
sentEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<not_null<DocumentData*>> ExtractStickersFromScene(
|
[[nodiscard]] std::vector<not_null<DocumentData*>> ExtractStickersFromScene(
|
||||||
not_null<const Ui::PreparedFileInformation::Image*> info) {
|
not_null<const Ui::PreparedFileInformation::Image*> info) {
|
||||||
const auto allItems = info->modifications.paint->items();
|
const auto allItems = info->modifications.paint->items();
|
||||||
|
|
||||||
|
@ -162,6 +166,33 @@ std::vector<not_null<DocumentData*>> ExtractStickersFromScene(
|
||||||
}) | ranges::to_vector;
|
}) | ranges::to_vector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QByteArray ComputePhotoJpegBytes(
|
||||||
|
QImage &full,
|
||||||
|
const QByteArray &bytes,
|
||||||
|
const QByteArray &format) {
|
||||||
|
if (!bytes.isEmpty()
|
||||||
|
&& (bytes.size()
|
||||||
|
<= full.width() * full.height() * kRecompressAfterBpp / 8)
|
||||||
|
&& (format == u"jpeg"_q)
|
||||||
|
&& Images::IsProgressiveJpeg(bytes)) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have an example of dark .png image that when being sent without
|
||||||
|
// removing its color space is displayed fine on tdesktop, but with
|
||||||
|
// a light gray background on mobile apps.
|
||||||
|
full.setColorSpace(QColorSpace());
|
||||||
|
auto result = QByteArray();
|
||||||
|
QBuffer buffer(&result);
|
||||||
|
QImageWriter writer(&buffer, "JPEG");
|
||||||
|
writer.setQuality(87);
|
||||||
|
writer.setProgressiveScanWrite(true);
|
||||||
|
writer.write(full);
|
||||||
|
buffer.close();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
SendMediaPrepare::SendMediaPrepare(
|
SendMediaPrepare::SendMediaPrepare(
|
||||||
|
@ -663,15 +694,23 @@ bool FileLoadTask::CheckForImage(
|
||||||
return Images::Read({
|
return Images::Read({
|
||||||
.path = filepath,
|
.path = filepath,
|
||||||
.content = content,
|
.content = content,
|
||||||
|
.returnContent = true,
|
||||||
});
|
});
|
||||||
}();
|
}();
|
||||||
return FillImageInformation(std::move(read.image), read.animated, result);
|
return FillImageInformation(
|
||||||
|
std::move(read.image),
|
||||||
|
read.animated,
|
||||||
|
result,
|
||||||
|
std::move(read.content),
|
||||||
|
std::move(read.format));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileLoadTask::FillImageInformation(
|
bool FileLoadTask::FillImageInformation(
|
||||||
QImage &&image,
|
QImage &&image,
|
||||||
bool animated,
|
bool animated,
|
||||||
std::unique_ptr<Ui::PreparedFileInformation> &result) {
|
std::unique_ptr<Ui::PreparedFileInformation> &result,
|
||||||
|
QByteArray content,
|
||||||
|
QByteArray format) {
|
||||||
Expects(result != nullptr);
|
Expects(result != nullptr);
|
||||||
|
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
|
@ -679,6 +718,8 @@ bool FileLoadTask::FillImageInformation(
|
||||||
}
|
}
|
||||||
auto media = Ui::PreparedFileInformation::Image();
|
auto media = Ui::PreparedFileInformation::Image();
|
||||||
media.data = std::move(image);
|
media.data = std::move(image);
|
||||||
|
media.bytes = std::move(content);
|
||||||
|
media.format = std::move(format);
|
||||||
media.animated = animated;
|
media.animated = animated;
|
||||||
result->media = media;
|
result->media = media;
|
||||||
return true;
|
return true;
|
||||||
|
@ -703,6 +744,8 @@ void FileLoadTask::process(Args &&args) {
|
||||||
auto isSticker = false;
|
auto isSticker = false;
|
||||||
|
|
||||||
auto fullimage = QImage();
|
auto fullimage = QImage();
|
||||||
|
auto fullimagebytes = QByteArray();
|
||||||
|
auto fullimageformat = QByteArray();
|
||||||
auto info = _filepath.isEmpty() ? QFileInfo() : QFileInfo(_filepath);
|
auto info = _filepath.isEmpty() ? QFileInfo() : QFileInfo(_filepath);
|
||||||
if (info.exists()) {
|
if (info.exists()) {
|
||||||
if (info.isDir()) {
|
if (info.isDir()) {
|
||||||
|
@ -724,8 +767,12 @@ void FileLoadTask::process(Args &&args) {
|
||||||
if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
|
if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
|
||||||
&_information->media)) {
|
&_information->media)) {
|
||||||
fullimage = base::take(image->data);
|
fullimage = base::take(image->data);
|
||||||
if (!Core::IsMimeSticker(filemime)) {
|
fullimagebytes = base::take(image->bytes);
|
||||||
|
fullimageformat = base::take(image->format);
|
||||||
|
if (!Core::IsMimeSticker(filemime)
|
||||||
|
&& fullimageformat != u"jpeg"_q) {
|
||||||
fullimage = Images::Opaque(std::move(fullimage));
|
fullimage = Images::Opaque(std::move(fullimage));
|
||||||
|
fullimagebytes = fullimageformat = QByteArray();
|
||||||
}
|
}
|
||||||
isAnimation = image->animated;
|
isAnimation = image->animated;
|
||||||
}
|
}
|
||||||
|
@ -739,12 +786,16 @@ void FileLoadTask::process(Args &&args) {
|
||||||
if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
|
if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
|
||||||
&_information->media)) {
|
&_information->media)) {
|
||||||
fullimage = base::take(image->data);
|
fullimage = base::take(image->data);
|
||||||
|
fullimagebytes = base::take(image->bytes);
|
||||||
|
fullimageformat = base::take(image->format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto mimeType = Core::MimeTypeForData(_content);
|
const auto mimeType = Core::MimeTypeForData(_content);
|
||||||
filemime = mimeType.name();
|
filemime = mimeType.name();
|
||||||
if (!Core::IsMimeSticker(filemime)) {
|
if (!Core::IsMimeSticker(filemime)
|
||||||
|
&& fullimageformat != u"jpeg"_q) {
|
||||||
fullimage = Images::Opaque(std::move(fullimage));
|
fullimage = Images::Opaque(std::move(fullimage));
|
||||||
|
fullimagebytes = fullimageformat = QByteArray();
|
||||||
}
|
}
|
||||||
if (filemime == "image/jpeg") {
|
if (filemime == "image/jpeg") {
|
||||||
filename = filedialogDefaultName(qsl("photo"), qsl(".jpg"), QString(), true);
|
filename = filedialogDefaultName(qsl("photo"), qsl(".jpg"), QString(), true);
|
||||||
|
@ -764,6 +815,8 @@ void FileLoadTask::process(Args &&args) {
|
||||||
if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
|
if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
|
||||||
&_information->media)) {
|
&_information->media)) {
|
||||||
fullimage = base::take(image->data);
|
fullimage = base::take(image->data);
|
||||||
|
fullimagebytes = base::take(image->bytes);
|
||||||
|
fullimageformat = base::take(image->format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!fullimage.isNull() && fullimage.width() > 0) {
|
if (!fullimage.isNull() && fullimage.width() > 0) {
|
||||||
|
@ -786,6 +839,7 @@ void FileLoadTask::process(Args &&args) {
|
||||||
filesize = _content.size();
|
filesize = _content.size();
|
||||||
}
|
}
|
||||||
fullimage = Images::Opaque(std::move(fullimage));
|
fullimage = Images::Opaque(std::move(fullimage));
|
||||||
|
fullimagebytes = fullimageformat = QByteArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_result->filesize = (int32)qMin(filesize, qint64(INT_MAX));
|
_result->filesize = (int32)qMin(filesize, qint64(INT_MAX));
|
||||||
|
@ -878,18 +932,14 @@ void FileLoadTask::process(Args &&args) {
|
||||||
} else if (filemime.startsWith(u"image/"_q)
|
} else if (filemime.startsWith(u"image/"_q)
|
||||||
&& _type != SendMediaType::File) {
|
&& _type != SendMediaType::File) {
|
||||||
auto medium = (w > 320 || h > 320) ? fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
|
auto medium = (w > 320 || h > 320) ? fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
|
||||||
auto full = (w > 1280 || h > 1280) ? fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
|
|
||||||
{
|
const auto downscaled = (w > 1280 || h > 1280);
|
||||||
// We have an example of dark .png image that when being sent without
|
auto full = downscaled ? fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
|
||||||
// removing its color space is displayed fine on tdesktop, but with
|
if (downscaled) {
|
||||||
// a light gray background on mobile apps.
|
fullimagebytes = fullimageformat = QByteArray();
|
||||||
full.setColorSpace(QColorSpace());
|
|
||||||
QBuffer buffer(&filedata);
|
|
||||||
QImageWriter writer(&buffer, "JPEG");
|
|
||||||
writer.setQuality(87);
|
|
||||||
writer.setProgressiveScanWrite(true);
|
|
||||||
writer.write(full);
|
|
||||||
}
|
}
|
||||||
|
filedata = ComputePhotoJpegBytes(full, fullimagebytes, fullimageformat);
|
||||||
|
|
||||||
photoThumbs.emplace('m', PreparedPhotoThumb{ .image = medium });
|
photoThumbs.emplace('m', PreparedPhotoThumb{ .image = medium });
|
||||||
photoSizes.push_back(MTP_photoSize(MTP_string("m"), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0)));
|
photoSizes.push_back(MTP_photoSize(MTP_string("m"), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0)));
|
||||||
|
|
||||||
|
|
|
@ -263,7 +263,9 @@ public:
|
||||||
static bool FillImageInformation(
|
static bool FillImageInformation(
|
||||||
QImage &&image,
|
QImage &&image,
|
||||||
bool animated,
|
bool animated,
|
||||||
std::unique_ptr<Ui::PreparedFileInformation> &result);
|
std::unique_ptr<Ui::PreparedFileInformation> &result,
|
||||||
|
QByteArray content = {},
|
||||||
|
QByteArray format = {});
|
||||||
|
|
||||||
FileLoadTask(
|
FileLoadTask(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
|
|
|
@ -20,6 +20,8 @@ class SendFilesWay;
|
||||||
struct PreparedFileInformation {
|
struct PreparedFileInformation {
|
||||||
struct Image {
|
struct Image {
|
||||||
QImage data;
|
QImage data;
|
||||||
|
QByteArray bytes;
|
||||||
|
QByteArray format;
|
||||||
bool animated = false;
|
bool animated = false;
|
||||||
Editor::PhotoModifications modifications;
|
Editor::PhotoModifications modifications;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit dd3cc5000e4f136ee198c5ace014640593e0aa2c
|
Subproject commit 81e216f1cede22d30573233c36039716cc438b33
|
Loading…
Add table
Reference in a new issue