From 52ce887b80ebdb29cbc4c794a59b6f70fc5fe7ff Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Thu, 4 Jan 2024 20:45:27 -0500 Subject: [PATCH] LibGfx/JPEG: Introduce a struct to hold sampling factors --- .../LibGfx/ImageFormats/JPEGLoader.cpp | 116 +++++++++--------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/Userland/Libraries/LibGfx/ImageFormats/JPEGLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/JPEGLoader.cpp index 38bdd34d93..70758969eb 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/JPEGLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/JPEGLoader.cpp @@ -30,16 +30,22 @@ struct MacroblockMeta { u32 vpadded_count { 0 }; }; +struct SamplingFactors { + u8 horizontal {}; + u8 vertical {}; + + bool operator==(SamplingFactors const&) const = default; +}; + // In the JPEG format, components are defined first at the frame level, then // referenced in each scan and aggregated with scan-specific information. The // two following structs mimic this hierarchy. struct Component { // B.2.2 - Frame header syntax - u8 id { 0 }; // Ci, Component identifier - u8 hsample_factor { 1 }; // Hi, Horizontal sampling factor - u8 vsample_factor { 1 }; // Vi, Vertical sampling factor - u8 quantization_table_id { 0 }; // Tqi, Quantization table destination selector + u8 id { 0 }; // Ci, Component identifier + SamplingFactors sampling_factors { 1, 1 }; // Hi, Horizontal sampling factor and Vi, Vertical sampling factor + u8 quantization_table_id { 0 }; // Tqi, Quantization table destination selector // The JPEG specification does not specify which component corresponds to // Y, Cb or Cr. This field (actually the index in the parent Vector) will @@ -441,8 +447,7 @@ struct JPEGLoadingContext { Array>, 4> quantization_tables {}; StartOfFrame frame; - u8 hsample_factor { 0 }; - u8 vsample_factor { 0 }; + SamplingFactors sampling_factors { 0 }; Optional current_scan {}; @@ -713,12 +718,12 @@ template static ErrorOr build_macroblocks(JPEGLoadingContext& context, Vector& macroblocks, u32 hcursor, u32 vcursor) { for (auto const& scan_component : context.current_scan->components) { - for (u8 vfactor_i = 0; vfactor_i < scan_component.component.vsample_factor; vfactor_i++) { - for (u8 hfactor_i = 0; hfactor_i < scan_component.component.hsample_factor; hfactor_i++) { + for (u8 vfactor_i = 0; vfactor_i < scan_component.component.sampling_factors.vertical; vfactor_i++) { + for (u8 hfactor_i = 0; hfactor_i < scan_component.component.sampling_factors.horizontal; hfactor_i++) { // A.2.3 - Interleaved order u32 macroblock_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hfactor_i + hcursor); if (!context.current_scan->are_components_interleaved()) { - macroblock_index = vcursor * context.mblock_meta.hpadded_count + (hfactor_i + (hcursor * scan_component.component.vsample_factor) + (vfactor_i * scan_component.component.hsample_factor)); + macroblock_index = vcursor * context.mblock_meta.hpadded_count + (hfactor_i + (hcursor * scan_component.component.sampling_factors.vertical) + (vfactor_i * scan_component.component.sampling_factors.horizontal)); // A.2.4 Completion of partial MCU // If the component is [and only if!] to be interleaved, the encoding process @@ -784,14 +789,14 @@ static void reset_decoder(JPEGLoadingContext& context) static ErrorOr decode_huffman_stream(JPEGLoadingContext& context, Vector& macroblocks) { - for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) { - for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) { + for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.sampling_factors.vertical) { + for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.sampling_factors.horizontal) { u32 i = vcursor * context.mblock_meta.hpadded_count + hcursor; auto& huffman_stream = context.current_scan->huffman_stream; if (context.dc_restart_interval > 0) { - if (i != 0 && i % (context.dc_restart_interval * context.vsample_factor * context.hsample_factor) == 0) { + if (i != 0 && i % (context.dc_restart_interval * context.sampling_factors.vertical * context.sampling_factors.horizontal) == 0) { reset_decoder(context); // Restart markers are stored in byte boundaries. Advance the huffman stream cursor to @@ -1189,13 +1194,12 @@ static ErrorOr read_app_marker(JPEGStream& stream, JPEGLoadingContext& con static inline bool validate_luma_and_modify_context(Component const& luma, JPEGLoadingContext& context) { - if ((luma.hsample_factor == 1 || luma.hsample_factor == 2) && (luma.vsample_factor == 1 || luma.vsample_factor == 2)) { - context.mblock_meta.hpadded_count += luma.hsample_factor == 1 ? 0 : context.mblock_meta.hcount % 2; - context.mblock_meta.vpadded_count += luma.vsample_factor == 1 ? 0 : context.mblock_meta.vcount % 2; + if ((luma.sampling_factors.horizontal == 1 || luma.sampling_factors.horizontal == 2) && (luma.sampling_factors.vertical == 1 || luma.sampling_factors.vertical == 2)) { + context.mblock_meta.hpadded_count += luma.sampling_factors.horizontal == 1 ? 0 : context.mblock_meta.hcount % 2; + context.mblock_meta.vpadded_count += luma.sampling_factors.vertical == 1 ? 0 : context.mblock_meta.vcount % 2; context.mblock_meta.padded_total = context.mblock_meta.hpadded_count * context.mblock_meta.vpadded_count; // For easy reference to relevant sample factors. - context.hsample_factor = luma.hsample_factor; - context.vsample_factor = luma.vsample_factor; + context.sampling_factors = luma.sampling_factors; return true; } @@ -1265,30 +1269,30 @@ static ErrorOr read_start_of_frame(JPEGStream& stream, JPEGLoadingContext& component.index = i; u8 subsample_factors = TRY(stream.read_u8()); - component.hsample_factor = subsample_factors >> 4; - component.vsample_factor = subsample_factors & 0x0F; + component.sampling_factors.horizontal = subsample_factors >> 4; + component.sampling_factors.vertical = subsample_factors & 0x0F; - dbgln_if(JPEG_DEBUG, "Component subsampling: {}, {}", component.hsample_factor, component.vsample_factor); + dbgln_if(JPEG_DEBUG, "Component subsampling: {}, {}", component.sampling_factors.horizontal, component.sampling_factors.vertical); if (i == 0) { // By convention, downsampling is applied only on chroma components. So we should // hope to see the maximum sampling factor in the luma component. if (!validate_luma_and_modify_context(component, context)) { dbgln_if(JPEG_DEBUG, "Unsupported luma subsampling factors: horizontal: {}, vertical: {}", - component.hsample_factor, - component.vsample_factor); + component.sampling_factors.horizontal, + component.sampling_factors.vertical); return Error::from_string_literal("Unsupported luma subsampling factors"); } } else { // YCCK with just CC subsampled and K matching Y is fine. auto const& y_component = context.components[0]; - bool channel_matches_y_factor = component.hsample_factor == y_component.hsample_factor && component.vsample_factor == y_component.vsample_factor; + bool channel_matches_y_factor = component.sampling_factors == y_component.sampling_factors; bool k_channel_matches_y = context.color_transform == ColorTransform::YCCK && i == 3 && channel_matches_y_factor; - if (((component.hsample_factor != 1 || component.vsample_factor != 1) && !k_channel_matches_y) || (i == 3 && !channel_matches_y_factor)) { + if (((component.sampling_factors != SamplingFactors { 1, 1 }) && !k_channel_matches_y) || (i == 3 && !channel_matches_y_factor)) { dbgln_if(JPEG_DEBUG, "Unsupported chroma subsampling factors: horizontal: {}, vertical: {}", - component.hsample_factor, - component.vsample_factor); + component.sampling_factors.horizontal, + component.sampling_factors.vertical); return Error::from_string_literal("Unsupported chroma subsampling factors"); } } @@ -1354,8 +1358,8 @@ static ErrorOr skip_segment(JPEGStream& stream) static ErrorOr dequantize(JPEGLoadingContext& context, Vector& macroblocks) { - for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) { - for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) { + for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.sampling_factors.vertical) { + for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.sampling_factors.horizontal) { for (u32 i = 0; i < context.components.size(); i++) { auto const& component = context.components[i]; @@ -1366,8 +1370,8 @@ static ErrorOr dequantize(JPEGLoadingContext& context, Vector& auto const& table = context.quantization_tables[component.quantization_table_id].value(); - for (u32 vfactor_i = 0; vfactor_i < component.vsample_factor; vfactor_i++) { - for (u32 hfactor_i = 0; hfactor_i < component.hsample_factor; hfactor_i++) { + for (u32 vfactor_i = 0; vfactor_i < component.sampling_factors.vertical; vfactor_i++) { + for (u32 hfactor_i = 0; hfactor_i < component.sampling_factors.horizontal; hfactor_i++) { u32 macroblock_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hfactor_i + hcursor); Macroblock& block = macroblocks[macroblock_index]; auto* block_component = get_component(block, i); @@ -1399,12 +1403,12 @@ static void inverse_dct(JPEGLoadingContext const& context, Vector& m static float const s6 = AK::cos(6.0f / 16.0f * AK::Pi) / 2.0f; static float const s7 = AK::cos(7.0f / 16.0f * AK::Pi) / 2.0f; - for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) { - for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) { + for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.sampling_factors.vertical) { + for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.sampling_factors.horizontal) { for (u32 component_i = 0; component_i < context.components.size(); component_i++) { auto& component = context.components[component_i]; - for (u8 vfactor_i = 0; vfactor_i < component.vsample_factor; vfactor_i++) { - for (u8 hfactor_i = 0; hfactor_i < component.hsample_factor; hfactor_i++) { + for (u8 vfactor_i = 0; vfactor_i < component.sampling_factors.vertical; vfactor_i++) { + for (u8 hfactor_i = 0; hfactor_i < component.sampling_factors.horizontal; hfactor_i++) { u32 macroblock_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hfactor_i + hcursor); Macroblock& block = macroblocks[macroblock_index]; auto* block_component = get_component(block, component_i); @@ -1551,10 +1555,10 @@ static void inverse_dct(JPEGLoadingContext const& context, Vector& m // F.2.1.5 - Inverse DCT (IDCT) auto const level_shift = 1 << (context.frame.precision - 1); auto const max_value = (1 << context.frame.precision) - 1; - for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) { - for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) { - for (u8 vfactor_i = 0; vfactor_i < context.vsample_factor; ++vfactor_i) { - for (u8 hfactor_i = 0; hfactor_i < context.hsample_factor; ++hfactor_i) { + for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.sampling_factors.vertical) { + for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.sampling_factors.horizontal) { + for (u8 vfactor_i = 0; vfactor_i < context.sampling_factors.vertical; ++vfactor_i) { + for (u8 hfactor_i = 0; hfactor_i < context.sampling_factors.horizontal; ++hfactor_i) { u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hcursor + hfactor_i); for (u8 i = 0; i < 8; ++i) { for (u8 j = 0; j < 8; ++j) { @@ -1584,13 +1588,13 @@ static void ycbcr_to_rgb(JPEGLoadingContext const& context, Vector& // Conversion from YCbCr to RGB isn't specified in the first JPEG specification but in the JFIF extension: // See: https://www.itu.int/rec/dologin_pub.asp?lang=f&id=T-REC-T.871-201105-I!!PDF-E&type=items // 7 - Conversion to and from RGB - for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) { - for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) { + for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.sampling_factors.vertical) { + for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.sampling_factors.horizontal) { u32 const chroma_block_index = vcursor * context.mblock_meta.hpadded_count + hcursor; Macroblock const& chroma = macroblocks[chroma_block_index]; // Overflows are intentional. - for (u8 vfactor_i = context.vsample_factor - 1; vfactor_i < context.vsample_factor; --vfactor_i) { - for (u8 hfactor_i = context.hsample_factor - 1; hfactor_i < context.hsample_factor; --hfactor_i) { + for (u8 vfactor_i = context.sampling_factors.vertical - 1; vfactor_i < context.sampling_factors.vertical; --vfactor_i) { + for (u8 hfactor_i = context.sampling_factors.horizontal - 1; hfactor_i < context.sampling_factors.horizontal; --hfactor_i) { u32 macroblock_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hcursor + hfactor_i); auto* y = macroblocks[macroblock_index].y; auto* cb = macroblocks[macroblock_index].cb; @@ -1598,8 +1602,8 @@ static void ycbcr_to_rgb(JPEGLoadingContext const& context, Vector& for (u8 i = 7; i < 8; --i) { for (u8 j = 7; j < 8; --j) { u8 const pixel = i * 8 + j; - u32 const chroma_pxrow = (i / context.vsample_factor) + 4 * vfactor_i; - u32 const chroma_pxcol = (j / context.hsample_factor) + 4 * hfactor_i; + u32 const chroma_pxrow = (i / context.sampling_factors.vertical) + 4 * vfactor_i; + u32 const chroma_pxcol = (j / context.sampling_factors.horizontal) + 4 * hfactor_i; u32 const chroma_pixel = chroma_pxrow * 8 + chroma_pxcol; int r = y[pixel] + 1.402f * (chroma.cr[chroma_pixel] - 128); int g = y[pixel] - 0.3441f * (chroma.cb[chroma_pixel] - 128) - 0.7141f * (chroma.cr[chroma_pixel] - 128); @@ -1626,10 +1630,10 @@ static void invert_colors_for_adobe_images(JPEGLoadingContext const& context, Ve // files: 0 represents 100% ink coverage, rather than 0% ink as you'd expect. // This is arguably a bug in Photoshop, but if you need to work with Photoshop // CMYK files, you will have to deal with it in your application. - for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) { - for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) { - for (u8 vfactor_i = 0; vfactor_i < context.vsample_factor; ++vfactor_i) { - for (u8 hfactor_i = 0; hfactor_i < context.hsample_factor; ++hfactor_i) { + for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.sampling_factors.vertical) { + for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.sampling_factors.horizontal) { + for (u8 vfactor_i = 0; vfactor_i < context.sampling_factors.vertical; ++vfactor_i) { + for (u8 hfactor_i = 0; hfactor_i < context.sampling_factors.horizontal; ++hfactor_i) { u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hcursor + hfactor_i); for (u8 i = 0; i < 8; ++i) { for (u8 j = 0; j < 8; ++j) { @@ -1650,10 +1654,10 @@ static void cmyk_to_rgb(JPEGLoadingContext const& context, Vector& m if (context.options.cmyk == JPEGDecoderOptions::CMYK::Normal) invert_colors_for_adobe_images(context, macroblocks); - for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) { - for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) { - for (u8 vfactor_i = context.vsample_factor - 1; vfactor_i < context.vsample_factor; --vfactor_i) { - for (u8 hfactor_i = context.hsample_factor - 1; hfactor_i < context.hsample_factor; --hfactor_i) { + for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.sampling_factors.vertical) { + for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.sampling_factors.horizontal) { + for (u8 vfactor_i = context.sampling_factors.vertical - 1; vfactor_i < context.sampling_factors.vertical; --vfactor_i) { + for (u8 hfactor_i = context.sampling_factors.horizontal - 1; hfactor_i < context.sampling_factors.horizontal; --hfactor_i) { u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hcursor + hfactor_i); auto* c = macroblocks[mb_index].y; auto* m = macroblocks[mb_index].cb; @@ -1690,10 +1694,10 @@ static void ycck_to_rgb(JPEGLoadingContext const& context, Vector& m ycbcr_to_rgb(context, macroblocks); // RGB to CMYK, as mentioned in https://www.smcm.iqfr.csic.es/docs/intel/ipp/ipp_manual/IPPI/ippi_ch15/functn_YCCKToCMYK_JPEG.htm#functn_YCCKToCMYK_JPEG - for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) { - for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) { - for (u8 vfactor_i = 0; vfactor_i < context.vsample_factor; ++vfactor_i) { - for (u8 hfactor_i = 0; hfactor_i < context.hsample_factor; ++hfactor_i) { + for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.sampling_factors.vertical) { + for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.sampling_factors.horizontal) { + for (u8 vfactor_i = 0; vfactor_i < context.sampling_factors.vertical; ++vfactor_i) { + for (u8 hfactor_i = 0; hfactor_i < context.sampling_factors.horizontal; ++hfactor_i) { u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hcursor + hfactor_i); for (u8 i = 0; i < 8; ++i) { for (u8 j = 0; j < 8; ++j) {