Sfoglia il codice sorgente

LibGfx/JPEG: Introduce a struct to hold sampling factors

Lucas CHOLLET 1 anno fa
parent
commit
52ce887b80
1 ha cambiato i file con 60 aggiunte e 56 eliminazioni
  1. 60 56
      Userland/Libraries/LibGfx/ImageFormats/JPEGLoader.cpp

+ 60 - 56
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<Optional<Array<u16, 64>>, 4> quantization_tables {};
 
     StartOfFrame frame;
-    u8 hsample_factor { 0 };
-    u8 vsample_factor { 0 };
+    SamplingFactors sampling_factors { 0 };
 
     Optional<Scan> current_scan {};
 
@@ -713,12 +718,12 @@ template<JPEGDecodingMode DecodingMode>
 static ErrorOr<void> build_macroblocks(JPEGLoadingContext& context, Vector<Macroblock>& 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<void> decode_huffman_stream(JPEGLoadingContext& context, Vector<Macroblock>& 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<void> 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<void> 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<void> skip_segment(JPEGStream& stream)
 
 static ErrorOr<void> dequantize(JPEGLoadingContext& context, Vector<Macroblock>& 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<void> dequantize(JPEGLoadingContext& context, Vector<Macroblock>&
 
                 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<Macroblock>& m
     static float const s6 = AK::cos(6.0f / 16.0f * AK::Pi<float>) / 2.0f;
     static float const s7 = AK::cos(7.0f / 16.0f * AK::Pi<float>) / 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<Macroblock>& 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<Macroblock>&
     // 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<Macroblock>&
                     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<Macroblock>& 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<Macroblock>& 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) {