#define STB_IMAGE_RESIZE_IMPLEMENTATION #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include "stb_image/include/stb_image_resize2.h" #pragma GCC diagnostic pop #include "jpeg_process.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr int MIN_COVER_IMAGE_DIMENSION = 400; constexpr int MAX_COVER_IMAGE_DIMENSION = 8'192; constexpr std::size_t MAX_COVER_IMAGE_PIXELS = 25'000'000; constexpr std::size_t JFIF_COMPATIBILITY_MARKER_INDEX = 0x0D; constexpr int START_QUALITY = 97; constexpr int MIN_SAME_DIMENSION_QUALITY = 75; constexpr int MIN_JPEG_QUALITY = 1; constexpr int MAX_JPEG_QUALITY = 100; constexpr int MAX_RESIZE_ATTEMPTS = 300; constexpr int PROGRESSIVE_JPEG_FLAGS = TJFLAG_PROGRESSIVE; constexpr int DECODE_PIXEL_FORMAT = TJPF_RGB; constexpr int DECODE_BYTES_PER_PIXEL = 3; constexpr stbir_pixel_layout RESIZE_PIXEL_LAYOUT = STBIR_RGB; constexpr auto COMMENT_BLOCK_CLOSE_SIG = std::to_array({ 0x23, 0x3E }); constexpr auto CLEAN_JFIF_SIG = std::to_array({ 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00 }); struct ImageSize { int width = 0; int height = 0; }; struct JpegSegment { Byte marker = 0; std::size_t marker_offset = 0; std::size_t payload_offset = 0; std::size_t payload_size = 0; }; constexpr Byte JPEG_MARKER_SOS = 0xDA; constexpr Byte JPEG_MARKER_DQT = 0xDB; constexpr Byte JPEG_MARKER_APP1 = 0xE1; [[nodiscard]] bool containsCommentBlockClose(std::span jpg) { if (jpg.size() < 2) { return false; } const Byte* cursor = jpg.data(); const Byte* const last_candidate = jpg.data() + (jpg.size() - 1); while (cursor < last_candidate) { const auto remaining = static_cast(last_candidate - cursor); const void* hit = std::memchr(cursor, COMMENT_BLOCK_CLOSE_SIG[0], remaining); if (hit == nullptr) { return false; } const auto* pos = static_cast(hit); if (pos[1] == COMMENT_BLOCK_CLOSE_SIG[1]) { return true; } cursor = pos + 1; } return false; } [[nodiscard]] std::optional readBigEndian16(std::span bytes, std::size_t offset) { if (offset > bytes.size() || bytes.size() - offset < 2) { return std::nullopt; } return static_cast((static_cast(bytes[offset]) << 8) | static_cast(bytes[offset + 1])); } [[nodiscard]] bool markerHasNoPayload(Byte marker) { return marker == 0x01 || marker == 0xD8 || marker == 0xD9 || (marker >= 0xD0 && marker <= 0xD7); } template [[nodiscard]] std::optional findJpegHeaderSegment(std::span jpg, Predicate predicate) { if (jpg.size() < 2 || jpg[0] != 0xFF || jpg[1] != 0xD8) { return std::nullopt; } std::size_t pos = 2; while (pos < jpg.size()) { if (jpg[pos] != 0xFF) { return std::nullopt; } const std::size_t marker_offset = pos; while (pos < jpg.size() && jpg[pos] == 0xFF) { ++pos; } if (pos >= jpg.size()) { return std::nullopt; } const Byte marker = jpg[pos++]; if (marker == 0x00) { return std::nullopt; } if (markerHasNoPayload(marker)) { continue; } const auto segment_length_opt = readBigEndian16(jpg, pos); if (!segment_length_opt) { return std::nullopt; } const std::size_t segment_length = *segment_length_opt; if (segment_length < 2 || pos > jpg.size() - segment_length) { return std::nullopt; } const std::size_t payload_offset = pos + 2; const JpegSegment segment{ .marker = marker, .marker_offset = marker_offset, .payload_offset = payload_offset, .payload_size = segment_length - 2 }; if (predicate(segment)) { return segment; } if (marker == JPEG_MARKER_SOS) { return std::nullopt; } pos += segment_length; } return std::nullopt; } [[nodiscard]] std::size_t checkedPixelCount(int width, int height) { if (width <= 0 || height <= 0) { throw std::runtime_error("Image Error: Invalid image dimensions."); } const auto pixel_count = static_cast(width) * static_cast(height); if (pixel_count > MAX_COVER_IMAGE_PIXELS) { throw std::runtime_error("Image Error: Pixel count exceeds the supported maximum of 25 megapixels."); } return pixel_count; } void validateImageDimensions(int width, int height) { if (width < MIN_COVER_IMAGE_DIMENSION || height < MIN_COVER_IMAGE_DIMENSION) { throw std::runtime_error("Image Error: Dimensions are too small.\nFor platform compatibility, cover image must be at least 400px for both width and height."); } if (width > MAX_COVER_IMAGE_DIMENSION || height > MAX_COVER_IMAGE_DIMENSION) { throw std::runtime_error(std::format("Image Error: Dimensions exceed the supported maximum of {}px.", MAX_COVER_IMAGE_DIMENSION)); } (void)checkedPixelCount(width, height); } [[nodiscard]] std::size_t checkedPixelBufferSize(int width, int height, int bytes_per_pixel) { if (bytes_per_pixel <= 0) { throw std::runtime_error("Image Error: Invalid pixel format."); } const auto pixel_count = checkedPixelCount(width, height); if (pixel_count > std::numeric_limits::max() / static_cast(bytes_per_pixel)) { throw std::runtime_error("Image dimensions too large for pixel buffer allocation."); } return pixel_count * static_cast(bytes_per_pixel); } [[nodiscard]] bool hasCompatibleJfifHeader(std::span jpg) { constexpr auto COMPATIBLE_JFIF_SIG = std::to_array({ 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x19, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00 }); return jpg.size() >= COMPATIBLE_JFIF_SIG.size() && std::ranges::equal(jpg.first(COMPATIBLE_JFIF_SIG.size()), COMPATIBLE_JFIF_SIG); } [[nodiscard]] bool isAlreadyCompatible(std::span jpg) { return hasCompatibleJfifHeader(jpg) && !containsCommentBlockClose(jpg); } struct TiffReader { std::span bytes; bool little_endian = false; [[nodiscard]] std::optional read16(std::size_t offset) const { if (offset > bytes.size() || bytes.size() - offset < 2) { return std::nullopt; } if (little_endian) { return static_cast(static_cast(bytes[offset]) | (static_cast(bytes[offset + 1]) << 8)); } return static_cast((static_cast(bytes[offset]) << 8) | static_cast(bytes[offset + 1])); } [[nodiscard]] std::optional read32(std::size_t offset) const { if (offset > bytes.size() || bytes.size() - offset < 4) { return std::nullopt; } if (little_endian) { return static_cast(bytes[offset]) | (static_cast(bytes[offset + 1]) << 8) | (static_cast(bytes[offset + 2]) << 16) | (static_cast(bytes[offset + 3]) << 24); } return (static_cast(bytes[offset]) << 24) | (static_cast(bytes[offset + 1]) << 16) | (static_cast(bytes[offset + 2]) << 8) | static_cast(bytes[offset + 3]); } }; [[nodiscard]] std::optional tiffIsLittleEndian(std::span tiff_data) { if (tiff_data.size() < 2) { return std::nullopt; } if (tiff_data[0] == 'I' && tiff_data[1] == 'I') { return true; } if (tiff_data[0] == 'M' && tiff_data[1] == 'M') { return false; } return std::nullopt; } [[nodiscard]] std::optional exifOrientation(std::span jpg) { constexpr std::size_t EXIF_HEADER_SIZE = 6; constexpr auto EXIF_SIG = std::to_array({'E', 'x', 'i', 'f', '\0', '\0'}); const auto exif_segment = findJpegHeaderSegment(jpg, [&](const JpegSegment& segment) { return segment.marker == JPEG_MARKER_APP1 && segment.payload_size >= EXIF_HEADER_SIZE && std::ranges::equal(jpg.subspan(segment.payload_offset, EXIF_HEADER_SIZE), EXIF_SIG); }); if (!exif_segment) { return std::nullopt; } std::span payload = jpg.subspan(exif_segment->payload_offset, exif_segment->payload_size); std::span tiff_data = payload.subspan(EXIF_HEADER_SIZE); if (tiff_data.size() < 8) return std::nullopt; const auto little_endian = tiffIsLittleEndian(tiff_data); if (!little_endian) return std::nullopt; const TiffReader tiff{ .bytes = tiff_data, .little_endian = *little_endian }; if (tiff.read16(2) != 0x002A) return std::nullopt; const auto ifd_offset = tiff.read32(4); if (!ifd_offset) return std::nullopt; // Need at least 2 bytes at ifd_offset for the entry count. if (*ifd_offset < 8 || static_cast(*ifd_offset) > tiff_data.size() || tiff_data.size() - static_cast(*ifd_offset) < 2) { return std::nullopt; } const auto entry_count = tiff.read16(*ifd_offset); if (!entry_count) return std::nullopt; const std::size_t entries_offset = static_cast(*ifd_offset) + 2; constexpr uint16_t TAG_ORIENTATION = 0x0112; constexpr uint16_t TIFF_TYPE_SHORT = 3; constexpr std::size_t ENTRY_SIZE = 12; // Bound the loop to the number of entries that actually fit — guards // against a malicious entry_count larger than the payload allows. const std::size_t max_entries = (tiff_data.size() - entries_offset) / ENTRY_SIZE; const std::size_t bounded_count = std::min(*entry_count, max_entries); for (std::size_t i = 0, current_entry = entries_offset; i < bounded_count; ++i, current_entry += ENTRY_SIZE) { const auto tag_id = tiff.read16(current_entry); if (tag_id == TAG_ORIENTATION) { const auto type = tiff.read16(current_entry + 2); const auto count = tiff.read32(current_entry + 4); const auto value = tiff.read16(current_entry + 8); if (!type || !count || !value || *type != TIFF_TYPE_SHORT || *count != 1) { return std::nullopt; } return value; } } return std::nullopt; } [[nodiscard]] int getTransformOp(uint16_t orientation) { switch (orientation) { case 2: return TJXOP_HFLIP; case 3: return TJXOP_ROT180; case 4: return TJXOP_VFLIP; case 5: return TJXOP_TRANSPOSE; case 6: return TJXOP_ROT90; case 7: return TJXOP_TRANSVERSE; case 8: return TJXOP_ROT270; default: return TJXOP_NONE; } } struct TJHandle { tjhandle handle = nullptr; explicit TJHandle(tjhandle raw_handle = nullptr) : handle(raw_handle) {} ~TJHandle() { if (handle) tjDestroy(handle); } TJHandle(const TJHandle&) = delete; TJHandle& operator=(const TJHandle&) = delete; TJHandle(TJHandle&& other) noexcept : handle(std::exchange(other.handle, nullptr)) {} TJHandle& operator=(TJHandle&& other) noexcept { if (this != &other) { if (handle) tjDestroy(handle); handle = std::exchange(other.handle, nullptr); } return *this; } [[nodiscard]] tjhandle get() const { return handle; } [[nodiscard]] explicit operator bool() const { return handle != nullptr; } }; [[nodiscard]] TJHandle makeHandle(tjhandle raw_handle, std::string_view init_name) { if (!raw_handle) { throw std::runtime_error(std::format("{} failed.", init_name)); } return TJHandle(raw_handle); } [[nodiscard]] unsigned long toTurboJpegSize(std::size_t size) { if (size == 0 || size > static_cast(std::numeric_limits::max())) { throw std::runtime_error("Image Error: JPEG buffer size is unsupported by TurboJPEG."); } return static_cast(size); } [[nodiscard]] ImageSize readJpegSize(tjhandle handle, std::span jpg, std::string_view error_prefix) { ImageSize size{}; int jpeg_subsamp = 0; int jpeg_colorspace = 0; if (tjDecompressHeader3( handle, jpg.data(), toTurboJpegSize(jpg.size()), &size.width, &size.height, &jpeg_subsamp, &jpeg_colorspace) != 0) { throw std::runtime_error(std::format("{}: {}", error_prefix, tjGetErrorStr2(handle))); } validateImageDimensions(size.width, size.height); return size; } void validateJpegHeader(std::span jpg) { auto decompressor = makeHandle(tjInitDecompress(), "tjInitDecompress()"); (void)readJpegSize(decompressor.get(), jpg, "Image Error"); } struct TJBuffer { unsigned char* data = nullptr; TJBuffer() = default; ~TJBuffer() { if (data) tjFree(data); } TJBuffer(const TJBuffer&) = delete; TJBuffer& operator=(const TJBuffer&) = delete; }; void assignFromTJBuffer(vBytes& out, const TJBuffer& buffer, unsigned long byte_count) { if (!buffer.data || byte_count == 0) { throw std::runtime_error("Image Error: TurboJPEG produced an empty output buffer."); } if (byte_count > static_cast(std::numeric_limits::max()) || byte_count > static_cast(std::numeric_limits::max())) { throw std::runtime_error("Image Error: TurboJPEG output is too large to store on this platform."); } const auto size = static_cast(byte_count); out.assign(buffer.data, buffer.data + size); } struct DecodedImage { ImageSize size{}; vBytes pixels; }; struct EncodeCandidate { int subsamp = TJSAMP_444; int flags = PROGRESSIVE_JPEG_FLAGS; std::string_view label; }; constexpr auto ENCODE_CANDIDATES = std::to_array({ EncodeCandidate{ .subsamp = TJSAMP_444, .flags = PROGRESSIVE_JPEG_FLAGS, .label = "4:4:4 default" }, EncodeCandidate{ .subsamp = TJSAMP_444, .flags = PROGRESSIVE_JPEG_FLAGS | TJFLAG_ACCURATEDCT, .label = "4:4:4 accurate" }, EncodeCandidate{ .subsamp = TJSAMP_444, .flags = PROGRESSIVE_JPEG_FLAGS | TJFLAG_FASTDCT, .label = "4:4:4 fast" } }); void validateJpegQuality(int quality_val) { if (quality_val < MIN_JPEG_QUALITY || quality_val > MAX_JPEG_QUALITY) { throw std::runtime_error("Image Error: JPEG quality value is outside the supported range."); } } void printEncodeProgress(std::string_view phase, std::string_view detail, int quality_val, int width, int height) { std::print("\r{:<10} {:<14} | Quality: {:>3}% | Width: {:>5} | Height: {:>5}", phase, detail, quality_val, width, height); std::fflush(stdout); } void compressPixelsToJpeg( vBytes& image_file_vec, std::span pixels, ImageSize size, tjhandle compressor, int quality_val, const EncodeCandidate& candidate) { validateJpegQuality(quality_val); if (pixels.empty()) { throw std::runtime_error("Image Error: Empty pixel buffer."); } if ((candidate.flags & TJFLAG_PROGRESSIVE) == 0) { throw std::runtime_error("Internal Error: JPEG compression candidate is not progressive."); } TJBuffer jpegBuf; unsigned long jpegSize = 0; if (tjCompress2( compressor, pixels.data(), size.width, 0, size.height, DECODE_PIXEL_FORMAT, &jpegBuf.data, &jpegSize, candidate.subsamp, quality_val, candidate.flags) != 0) { throw std::runtime_error(std::format("tjCompress2: {}", tjGetErrorStr2(compressor))); } assignFromTJBuffer(image_file_vec, jpegBuf, jpegSize); } [[nodiscard]] DecodedImage decodeJpeg(std::span jpg) { auto decompressor = makeHandle(tjInitDecompress(), "tjInitDecompress()"); const auto image_size = readJpegSize(decompressor.get(), jpg, "tjDecompressHeader3"); vBytes decoded_image_vec(checkedPixelBufferSize(image_size.width, image_size.height, DECODE_BYTES_PER_PIXEL)); if (tjDecompress2( decompressor.get(), jpg.data(), toTurboJpegSize(jpg.size()), decoded_image_vec.data(), image_size.width, 0, image_size.height, DECODE_PIXEL_FORMAT, 0) != 0) { throw std::runtime_error(std::format("tjDecompress2: {}", tjGetErrorStr2(decompressor.get()))); } return DecodedImage{ .size = image_size, .pixels = std::move(decoded_image_vec) }; } void optimizeImage(vBytes& jpg_vec) { if (jpg_vec.empty()) { throw std::runtime_error("JPG image is empty!"); } auto transformer = makeHandle(tjInitTransform(), "tjInitTransform()"); (void)readJpegSize(transformer.get(), jpg_vec, "Image Error"); tjtransform xform{}; xform.op = getTransformOp(exifOrientation(jpg_vec).value_or(1)); xform.options = TJXOPT_COPYNONE | TJXOPT_TRIM | TJXOPT_PROGRESSIVE; TJBuffer dstBuffer; unsigned long dstSize = 0; if (tjTransform( transformer.get(), jpg_vec.data(), toTurboJpegSize(jpg_vec.size()), 1, &dstBuffer.data, &dstSize, &xform, 0) != 0) { throw std::runtime_error(std::format("Image Error: {}", tjGetErrorStr2(transformer.get()))); } assignFromTJBuffer(jpg_vec, dstBuffer, dstSize); } void resizeImage( vBytes& image_file_vec, const DecodedImage& source, vBytes& resize_scratch, tjhandle compressor, int quality_val, int decrease_dims_val, const EncodeCandidate& candidate) { validateJpegQuality(quality_val); if (source.size.width < decrease_dims_val || source.size.height < decrease_dims_val) { throw std::runtime_error(std::format("Image is too small to decrease by {} pixels.", decrease_dims_val)); } const int new_width = source.size.width - decrease_dims_val; const int new_height = source.size.height - decrease_dims_val; if (new_width < MIN_COVER_IMAGE_DIMENSION || new_height < MIN_COVER_IMAGE_DIMENSION) { throw std::runtime_error("Image Compatibility Error: Unable to remove close-comment block sequences without shrinking below the 400px minimum."); } printEncodeProgress("Resize", candidate.label, quality_val, new_width, new_height); resize_scratch.resize(checkedPixelBufferSize(new_width, new_height, DECODE_BYTES_PER_PIXEL)); if (!stbir_resize_uint8_srgb(source.pixels.data(), source.size.width, source.size.height, 0, resize_scratch.data(), new_width, new_height, 0, RESIZE_PIXEL_LAYOUT)) { throw std::runtime_error("stbir_resize_uint8_srgb failed."); } compressPixelsToJpeg( image_file_vec, resize_scratch, ImageSize{ .width = new_width, .height = new_height }, compressor, quality_val, candidate); } [[nodiscard]] std::size_t findRequiredDqtOffset(std::span jpg) { const auto dqt = findJpegHeaderSegment(jpg, [](const JpegSegment& segment) { return segment.marker == JPEG_MARKER_DQT; }); if (!dqt) { throw std::runtime_error("Image File Error: No DQT segment found (corrupt or unsupported JPG)."); } return dqt->marker_offset; } constexpr int AUTO_DOWNSCALE_MAX_DIM = 2048; void downscaleToFit(vBytes& image_file_vec, int max_dim) { const DecodedImage source = decodeJpeg(image_file_vec); const int orig_w = source.size.width; const int orig_h = source.size.height; const int max_current = std::max(orig_w, orig_h); if (max_current <= max_dim) return; const double scale = static_cast(max_dim) / static_cast(max_current); const int new_w = std::max(MIN_COVER_IMAGE_DIMENSION, static_cast(static_cast(orig_w) * scale)); const int new_h = std::max(MIN_COVER_IMAGE_DIMENSION, static_cast(static_cast(orig_h) * scale)); vBytes resized_pixels(checkedPixelBufferSize(new_w, new_h, DECODE_BYTES_PER_PIXEL)); if (!stbir_resize_uint8_srgb( source.pixels.data(), orig_w, orig_h, 0, resized_pixels.data(), new_w, new_h, 0, RESIZE_PIXEL_LAYOUT)) { throw std::runtime_error("Image Error: Failed to downscale image."); } auto compressor = makeHandle(tjInitCompress(), "tjInitCompress()"); compressPixelsToJpeg( image_file_vec, resized_pixels, ImageSize{ .width = new_w, .height = new_h }, compressor.get(), START_QUALITY, ENCODE_CANDIDATES[0]); std::println("Auto-downscaled from {}x{} to {}x{} for processing efficiency.", orig_w, orig_h, new_w, new_h); } void replaceLeadingMetadataWithCleanJfif(vBytes& image_file_vec) { const std::size_t dqt_pos = findRequiredDqtOffset(image_file_vec); vBytes cleaned; cleaned.reserve(CLEAN_JFIF_SIG.size() + image_file_vec.size() - dqt_pos); cleaned.insert(cleaned.end(), CLEAN_JFIF_SIG.begin(), CLEAN_JFIF_SIG.end()); cleaned.insert(cleaned.end(), image_file_vec.begin() + static_cast(dqt_pos), image_file_vec.end()); image_file_vec = std::move(cleaned); validateJpegHeader(image_file_vec); } [[nodiscard]] bool recompressSameDimensionsUntilCommentBlockFree(vBytes& image_file_vec) { const DecodedImage source = decodeJpeg(image_file_vec); auto compressor = makeHandle(tjInitCompress(), "tjInitCompress()"); for (int quality_val = START_QUALITY; quality_val >= MIN_SAME_DIMENSION_QUALITY; --quality_val) { for (const auto& candidate : ENCODE_CANDIDATES) { printEncodeProgress("Recompress", candidate.label, quality_val, source.size.width, source.size.height); compressPixelsToJpeg( image_file_vec, source.pixels, source.size, compressor.get(), quality_val, candidate); if (!containsCommentBlockClose(image_file_vec)) { return true; } } } return false; } [[nodiscard]] bool resizeUntilCommentBlockFree(vBytes& image_file_vec) { const DecodedImage pristine = decodeJpeg(image_file_vec); vBytes resize_scratch; auto compressor = makeHandle(tjInitCompress(), "tjInitCompress()"); const int max_dim = std::max(pristine.size.width, pristine.size.height); const int step = std::max(1, max_dim / 300); for (int attempt = 1; attempt <= MAX_RESIZE_ATTEMPTS; ++attempt) { const int decrease_dims_val = attempt * step; if (pristine.size.width - decrease_dims_val < MIN_COVER_IMAGE_DIMENSION || pristine.size.height - decrease_dims_val < MIN_COVER_IMAGE_DIMENSION) { break; } const int quality_val = std::clamp( START_QUALITY - ((attempt / 15) * 2), MIN_JPEG_QUALITY, MAX_JPEG_QUALITY); const auto& candidate = ENCODE_CANDIDATES[static_cast(attempt - 1) % ENCODE_CANDIDATES.size()]; resizeImage(image_file_vec, pristine, resize_scratch, compressor.get(), quality_val, decrease_dims_val, candidate); if (!containsCommentBlockClose(image_file_vec)) { return true; } } return false; } } bool makeTailRetryCandidate(std::span source_jpg, vBytes& out, int retry_index) { if (retry_index < 0) { return false; } const DecodedImage source = decodeJpeg(source_jpg); auto compressor = makeHandle(tjInitCompress(), "tjInitCompress()"); constexpr int QUALITY_CANDIDATE_COUNT = START_QUALITY - MIN_SAME_DIMENSION_QUALITY + 1; const int same_dimension_candidate_count = QUALITY_CANDIDATE_COUNT * static_cast(ENCODE_CANDIDATES.size()); if (retry_index < same_dimension_candidate_count) { const int quality_val = START_QUALITY - (retry_index / static_cast(ENCODE_CANDIDATES.size())); const auto& candidate = ENCODE_CANDIDATES[static_cast(retry_index) % ENCODE_CANDIDATES.size()]; printEncodeProgress("Tail retry", candidate.label, quality_val, source.size.width, source.size.height); compressPixelsToJpeg(out, source.pixels, source.size, compressor.get(), quality_val, candidate); return !containsCommentBlockClose(out); } retry_index -= same_dimension_candidate_count; if (retry_index >= MAX_RESIZE_ATTEMPTS) { return false; } vBytes resize_scratch; const int decrease_dims_val = retry_index + 1; const int quality_val = std::clamp( START_QUALITY - ((decrease_dims_val / 15) * 2), MIN_JPEG_QUALITY, MAX_JPEG_QUALITY); const auto& candidate = ENCODE_CANDIDATES[static_cast(retry_index) % ENCODE_CANDIDATES.size()]; resizeImage(out, source, resize_scratch, compressor.get(), quality_val, decrease_dims_val, candidate); return !containsCommentBlockClose(out); } bool ensureImageCompatible(vBytes& image_file_vec) { if (image_file_vec.size() <= JFIF_COMPATIBILITY_MARKER_INDEX) { throw std::runtime_error("Image File Error: Image too small to process."); } validateJpegHeader(image_file_vec); if (isAlreadyCompatible(image_file_vec)) { return false; } std::println("\nChecking cover image for comment-block close sequences \"#>\" (0x23, 0x3E).\n"); std::println("Image will be progressively recompressed first; dimensions will only be reduced if needed.\n"); optimizeImage(image_file_vec); downscaleToFit(image_file_vec, AUTO_DOWNSCALE_MAX_DIM); replaceLeadingMetadataWithCleanJfif(image_file_vec); if (!containsCommentBlockClose(image_file_vec)) { return true; } if (recompressSameDimensionsUntilCommentBlockFree(image_file_vec)) { return true; } if (resizeUntilCommentBlockFree(image_file_vec)) { return true; } std::println(std::cerr, "\n\nImage Compatibility Error:\n\nProcedure failed to remove close-comment block sequences from cover image."); throw std::runtime_error("Try another image or use an editor such as GIMP to manually reduce (scale) image dimensions."); }