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:
John Preston 2022-03-09 17:37:48 +04:00
parent e84ebc2a5c
commit 6805259f74
4 changed files with 77 additions and 23 deletions

View File

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "boxes/send_files_box.h"
#include "ui/boxes/confirm_box.h"
#include "ui/image/image_prepare.h"
#include "lang/lang_keys.h"
#include "storage/file_download.h"
#include "storage/storage_media_prepare.h"
@ -42,6 +43,7 @@ namespace {
constexpr auto kThumbnailQuality = 87;
constexpr auto kThumbnailSize = 320;
constexpr auto kPhotoUploadPartSize = 32 * 1024;
constexpr auto kRecompressAfterBpp = 4;
using Ui::ValidateThumbDimensions;
@ -53,7 +55,7 @@ struct PreparedFileThumbnail {
MTPPhotoSize mtpSize = MTP_photoSizeEmpty(MTP_string());
};
PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
[[nodiscard]] PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
const auto width = original.width();
const auto height = original.height();
if (!ValidateThumbDimensions(width, height)) {
@ -87,7 +89,9 @@ PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
return result;
}
bool FileThumbnailUploadRequired(const QString &filemime, int32 filesize) {
[[nodiscard]] bool FileThumbnailUploadRequired(
const QString &filemime,
int32 filesize) {
constexpr auto kThumbnailUploadBySize = 5 * 1024 * 1024;
const auto kThumbnailKnownMimes = {
"image/jpeg",
@ -101,7 +105,7 @@ bool FileThumbnailUploadRequired(const QString &filemime, int32 filesize) {
== end(kThumbnailKnownMimes));
}
PreparedFileThumbnail FinalizeFileThumbnail(
[[nodiscard]] PreparedFileThumbnail FinalizeFileThumbnail(
PreparedFileThumbnail &&prepared,
const QString &filemime,
int32 filesize,
@ -115,7 +119,7 @@ PreparedFileThumbnail FinalizeFileThumbnail(
return std::move(prepared);
}
auto FindAlbumItem(
[[nodiscard]] auto FindAlbumItem(
std::vector<SendingAlbum::Item> &items,
not_null<HistoryItem*> item) {
const auto result = ranges::find(
@ -127,7 +131,7 @@ auto FindAlbumItem(
return result;
}
MTPInputSingleMedia PrepareAlbumItemMedia(
[[nodiscard]] MTPInputSingleMedia PrepareAlbumItemMedia(
not_null<HistoryItem*> item,
const MTPInputMedia &media,
uint64 randomId) {
@ -149,7 +153,7 @@ MTPInputSingleMedia PrepareAlbumItemMedia(
sentEntities);
}
std::vector<not_null<DocumentData*>> ExtractStickersFromScene(
[[nodiscard]] std::vector<not_null<DocumentData*>> ExtractStickersFromScene(
not_null<const Ui::PreparedFileInformation::Image*> info) {
const auto allItems = info->modifications.paint->items();
@ -162,6 +166,33 @@ std::vector<not_null<DocumentData*>> ExtractStickersFromScene(
}) | 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
SendMediaPrepare::SendMediaPrepare(
@ -663,15 +694,23 @@ bool FileLoadTask::CheckForImage(
return Images::Read({
.path = filepath,
.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(
QImage &&image,
bool animated,
std::unique_ptr<Ui::PreparedFileInformation> &result) {
std::unique_ptr<Ui::PreparedFileInformation> &result,
QByteArray content,
QByteArray format) {
Expects(result != nullptr);
if (image.isNull()) {
@ -679,6 +718,8 @@ bool FileLoadTask::FillImageInformation(
}
auto media = Ui::PreparedFileInformation::Image();
media.data = std::move(image);
media.bytes = std::move(content);
media.format = std::move(format);
media.animated = animated;
result->media = media;
return true;
@ -703,6 +744,8 @@ void FileLoadTask::process(Args &&args) {
auto isSticker = false;
auto fullimage = QImage();
auto fullimagebytes = QByteArray();
auto fullimageformat = QByteArray();
auto info = _filepath.isEmpty() ? QFileInfo() : QFileInfo(_filepath);
if (info.exists()) {
if (info.isDir()) {
@ -724,8 +767,12 @@ void FileLoadTask::process(Args &&args) {
if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
&_information->media)) {
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));
fullimagebytes = fullimageformat = QByteArray();
}
isAnimation = image->animated;
}
@ -739,12 +786,16 @@ void FileLoadTask::process(Args &&args) {
if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
&_information->media)) {
fullimage = base::take(image->data);
fullimagebytes = base::take(image->bytes);
fullimageformat = base::take(image->format);
}
}
const auto mimeType = Core::MimeTypeForData(_content);
filemime = mimeType.name();
if (!Core::IsMimeSticker(filemime)) {
if (!Core::IsMimeSticker(filemime)
&& fullimageformat != u"jpeg"_q) {
fullimage = Images::Opaque(std::move(fullimage));
fullimagebytes = fullimageformat = QByteArray();
}
if (filemime == "image/jpeg") {
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>(
&_information->media)) {
fullimage = base::take(image->data);
fullimagebytes = base::take(image->bytes);
fullimageformat = base::take(image->format);
}
}
if (!fullimage.isNull() && fullimage.width() > 0) {
@ -786,6 +839,7 @@ void FileLoadTask::process(Args &&args) {
filesize = _content.size();
}
fullimage = Images::Opaque(std::move(fullimage));
fullimagebytes = fullimageformat = QByteArray();
}
}
_result->filesize = (int32)qMin(filesize, qint64(INT_MAX));
@ -878,18 +932,14 @@ void FileLoadTask::process(Args &&args) {
} else if (filemime.startsWith(u"image/"_q)
&& _type != SendMediaType::File) {
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;
{
// 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());
QBuffer buffer(&filedata);
QImageWriter writer(&buffer, "JPEG");
writer.setQuality(87);
writer.setProgressiveScanWrite(true);
writer.write(full);
const auto downscaled = (w > 1280 || h > 1280);
auto full = downscaled ? fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
if (downscaled) {
fullimagebytes = fullimageformat = QByteArray();
}
filedata = ComputePhotoJpegBytes(full, fullimagebytes, fullimageformat);
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)));

View File

@ -263,7 +263,9 @@ public:
static bool FillImageInformation(
QImage &&image,
bool animated,
std::unique_ptr<Ui::PreparedFileInformation> &result);
std::unique_ptr<Ui::PreparedFileInformation> &result,
QByteArray content = {},
QByteArray format = {});
FileLoadTask(
not_null<Main::Session*> session,

View File

@ -20,6 +20,8 @@ class SendFilesWay;
struct PreparedFileInformation {
struct Image {
QImage data;
QByteArray bytes;
QByteArray format;
bool animated = false;
Editor::PhotoModifications modifications;
};

@ -1 +1 @@
Subproject commit dd3cc5000e4f136ee198c5ace014640593e0aa2c
Subproject commit 81e216f1cede22d30573233c36039716cc438b33