From dd88f8fa41a06bdf3128276d8084cfa4f087dee7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 11 Aug 2021 19:54:59 +0300 Subject: [PATCH] Add UnpackGzip and reading gzip-ed SVGs. --- CMakeLists.txt | 2 + ui/image/image_prepare.cpp | 127 +++++++++++++++++++++++++++++++------ ui/image/image_prepare.h | 2 + 3 files changed, 111 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f463fed..235e37c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,4 +266,6 @@ PUBLIC target_link_libraries(lib_ui PUBLIC desktop-app::lib_base +PRIVATE + desktop-app::external_zlib ) diff --git a/ui/image/image_prepare.cpp b/ui/image/image_prepare.cpp index 1b25717..660a20f 100644 --- a/ui/image/image_prepare.cpp +++ b/ui/image/image_prepare.cpp @@ -10,16 +10,22 @@ #include "ui/style/style_core.h" #include "ui/painter.h" #include "base/flat_map.h" +#include "base/debug_log.h" #include "styles/palette.h" #include "styles/style_basic.h" +#include "zlib.h" #include #include #include +#include namespace Images { namespace { +// They should be smaller. +constexpr auto kMaxGzipFileSize = 5 * 1024 * 1024; + TG_FORCE_INLINE uint64 blurGetColors(const uchar *p) { return (uint64)p[0] + ((uint64)p[1] << 16) + ((uint64)p[2] << 32) + ((uint64)p[3] << 48); } @@ -115,6 +121,96 @@ std::array PrepareCorners( return result; } +[[nodiscard]] QByteArray UnpackGzip(const QByteArray &bytes) { + z_stream stream; + stream.zalloc = nullptr; + stream.zfree = nullptr; + stream.opaque = nullptr; + stream.avail_in = 0; + stream.next_in = nullptr; + int res = inflateInit2(&stream, 16 + MAX_WBITS); + if (res != Z_OK) { + return bytes; + } + const auto guard = gsl::finally([&] { inflateEnd(&stream); }); + + auto result = QByteArray(kMaxGzipFileSize + 1, char(0)); + stream.avail_in = bytes.size(); + stream.next_in = reinterpret_cast(const_cast(bytes.data())); + stream.avail_out = 0; + while (!stream.avail_out) { + stream.avail_out = result.size(); + stream.next_out = reinterpret_cast(result.data()); + int res = inflate(&stream, Z_NO_FLUSH); + if (res != Z_OK && res != Z_STREAM_END) { + return bytes; + } else if (!stream.avail_out) { + return bytes; + } + } + result.resize(result.size() - stream.avail_out); + return result; +} + +[[nodiscard]] ReadResult ReadGzipSvg(const ReadArgs &args) { + const auto bytes = UnpackGzip(args.content); + if (bytes.isEmpty()) { + LOG(("Svg Error: Couldn't unpack gzip-ed content.")); + return {}; + } + auto renderer = QSvgRenderer(bytes); + if (!renderer.isValid()) { + LOG(("Svg Error: Invalid data.")); + return {}; + } + auto size = renderer.defaultSize(); + if (!args.maxSize.isEmpty() + && (size.width() > args.maxSize.width() + || size.height() > args.maxSize.height())) { + size = size.scaled(args.maxSize, Qt::KeepAspectRatio); + } + if (size.isEmpty()) { + LOG(("Svg Error: Bad size %1x%2." + ).arg(renderer.defaultSize().width() + ).arg(renderer.defaultSize().height())); + return {}; + } + auto result = ReadResult(); + result.image = QImage(size, QImage::Format_ARGB32_Premultiplied); + result.image.fill(Qt::transparent); + { + QPainter p(&result.image); + renderer.render(&p, QRect(QPoint(), size)); + } + result.format = "svg"; + return result; +} + +[[nodiscard]] ReadResult ReadOther(const ReadArgs &args) { + auto bytes = args.content; + if (bytes.isEmpty()) { + return {}; + } + auto buffer = QBuffer(&bytes); + auto reader = QImageReader(&buffer); + reader.setAutoTransform(true); + if (!reader.canRead()) { + return {}; + } + const auto size = reader.size(); + if (size.width() * size.height() > kReadMaxArea) { + return {}; + } + auto result = ReadResult(); + if (!reader.read(&result.image) || result.image.isNull()) { + return {}; + } + result.animated = reader.supportsAnimation() + && (reader.imageCount() > 1); + result.format = reader.format().toLower(); + return result; +} + ReadResult Read(ReadArgs &&args) { if (args.content.isEmpty()) { auto file = QFile(args.path); @@ -124,33 +220,24 @@ ReadResult Read(ReadArgs &&args) { } args.content = file.readAll(); } - if (args.content.isEmpty()) { + auto result = args.gzipSvg ? ReadGzipSvg(args) : ReadOther(args); + if (result.image.isNull()) { + args = ReadArgs(); return {}; } - auto result = ReadResult(); - { - auto buffer = QBuffer(&args.content); - auto reader = QImageReader(&buffer); - reader.setAutoTransform(true); - if (!reader.canRead()) { - return {}; - } - const auto size = reader.size(); - if (size.width() * size.height() > kReadMaxArea) { - return {}; - } - if (!reader.read(&result.image) || result.image.isNull()) { - return {}; - } - result.animated = reader.supportsAnimation() - && (reader.imageCount() > 1); - result.format = reader.format().toLower(); - } if (args.returnContent) { result.content = args.content; } else { args.content = QByteArray(); } + if (!args.maxSize.isEmpty() + && (result.image.width() > args.maxSize.width() + || result.image.height() > args.maxSize.height())) { + result.image = result.image.scaled( + args.maxSize, + Qt::KeepAspectRatio, + Qt::SmoothTransformation); + } if (args.forceOpaque && result.format != qstr("jpg") && result.format != qstr("jpeg")) { diff --git a/ui/image/image_prepare.h b/ui/image/image_prepare.h index 5683027..512e3a3 100644 --- a/ui/image/image_prepare.h +++ b/ui/image/image_prepare.h @@ -39,6 +39,7 @@ namespace Images { int radius, const style::color &color); +[[nodiscard]] QByteArray UnpackGzip(const QByteArray &bytes); // Try to read images up to 64MB. inline constexpr auto kReadBytesLimit = 64 * 1024 * 1024; @@ -48,6 +49,7 @@ struct ReadArgs { QString path; QByteArray content; QSize maxSize; + bool gzipSvg = false; bool forceOpaque = false; bool returnContent = false; };