瀏覽代碼

LibGfx/JPEG2000: Support jpx extended 'colr' boxes

The T.800 spec says there should only be one 'colr' box, but the
extended jpx file format spec in T.801 annex M allows having multiple.

Method 2 is a basic ICC profile, while method 3 (jpx-only) allows full
ICC profiles. Support that.

For the test, I opened buggie.png in Photoshop, converted it to
grayscale, and saved it as a JPEG2000, with "JP2 Compatible" checked
and "Include Transparency" unchecked. I also unchecked "Include
Metadata", and "Lossless". I left "Fast Mode" checked and the quality
at the default 50.
Nico Weber 1 年之前
父節點
當前提交
ab7da32d25

+ 14 - 0
Tests/LibGfx/TestImageDecoder.cpp

@@ -584,6 +584,20 @@ TEST_CASE(test_jpeg2000_simple)
     EXPECT_EQ(icc_bytes->size(), 3144u);
 }
 
+TEST_CASE(test_jpeg2000_gray)
+{
+    auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("jpeg2000/buggie-gray.jpf"sv)));
+    EXPECT(Gfx::JPEG2000ImageDecoderPlugin::sniff(file->bytes()));
+    auto plugin_decoder = TRY_OR_FAIL(Gfx::JPEG2000ImageDecoderPlugin::create(file->bytes()));
+
+    EXPECT_EQ(plugin_decoder->size(), Gfx::IntSize(64, 138));
+
+    // The file contains both a simple and a real profile. Make sure we get the bigger one.
+    auto icc_bytes = MUST(plugin_decoder->icc_data());
+    EXPECT(icc_bytes.has_value());
+    EXPECT_EQ(icc_bytes->size(), 912u);
+}
+
 TEST_CASE(test_pam_rgb)
 {
     auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("pnm/2x1.pam"sv)));

二進制
Tests/LibGfx/test-inputs/jpeg2000/buggie-gray.jpf


+ 14 - 1
Userland/Libraries/LibGfx/ImageFormats/ISOBMFF/JPEG2000Boxes.cpp

@@ -76,6 +76,19 @@ ErrorOr<void> JPEG2000ColorSpecificationBox::read_from_stream(BoxStream& stream)
         TRY(stream.read_until_filled(local_icc_data));
         icc_data = move(local_icc_data);
     }
+
+    // T.801 JPX extended file format syntax,
+    // Table M.22 – Legal METH values
+    if (method == 3) {
+        ByteBuffer local_icc_data = TRY(ByteBuffer::create_uninitialized(stream.remaining()));
+        TRY(stream.read_until_filled(local_icc_data));
+        icc_data = move(local_icc_data);
+    }
+    if (method == 4)
+        return Error::from_string_literal("Method 4 is not yet implemented");
+    if (method == 5)
+        return Error::from_string_literal("Method 5 is not yet implemented");
+
     return {};
 }
 
@@ -87,7 +100,7 @@ void JPEG2000ColorSpecificationBox::dump(String const& prepend) const
     outln("{}- approximation = {}", prepend, approximation);
     if (method == 1)
         outln("{}- enumerated_color_space = {}", prepend, enumerated_color_space);
-    if (method == 2)
+    if (method == 2 || method == 3)
         outln("{}- icc_data = {} bytes", prepend, icc_data.size());
 }
 

+ 13 - 4
Userland/Libraries/LibGfx/ImageFormats/JPEG2000Loader.cpp

@@ -108,9 +108,18 @@ static ErrorOr<void> decode_jpeg2000_header(JPEG2000LoadingContext& context, Rea
             image_header_box_index = i;
         }
         if (subbox->box_type() == ISOBMFF::BoxType::JPEG2000ColorSpecificationBox) {
-            if (color_header_box_index.has_value())
-                return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Multiple Color Specification boxes");
-            color_header_box_index = i;
+            // T.800 says there should be just one 'colr' box, but T.801 allows several and says to pick the one with highest precedence.
+            bool use_this_color_box;
+            if (!color_header_box_index.has_value()) {
+                use_this_color_box = true;
+            } else {
+                auto const& new_header_box = static_cast<ISOBMFF::JPEG2000ColorSpecificationBox const&>(*header_box.child_boxes()[i]);
+                auto const& current_color_box = static_cast<ISOBMFF::JPEG2000ColorSpecificationBox const&>(*header_box.child_boxes()[color_header_box_index.value()]);
+                use_this_color_box = new_header_box.precedence > current_color_box.precedence;
+            }
+
+            if (use_this_color_box)
+                color_header_box_index = i;
         }
     }
 
@@ -123,7 +132,7 @@ static ErrorOr<void> decode_jpeg2000_header(JPEG2000LoadingContext& context, Rea
     context.size = { image_header_box.width, image_header_box.height };
 
     auto const& color_header_box = static_cast<ISOBMFF::JPEG2000ColorSpecificationBox const&>(*header_box.child_boxes()[color_header_box_index.value()]);
-    if (color_header_box.method == 2)
+    if (color_header_box.method == 2 || color_header_box.method == 3)
         context.icc_data = color_header_box.icc_data.bytes();
 
     return {};