瀏覽代碼

LibPDF: Move error for /ImageMask out of load_image()

...and tweak load_image() to support loading mask images
(which don't have a color space and are always 1 bit per pixel).
Nico Weber 1 年之前
父節點
當前提交
a3507ef65b
共有 2 個文件被更改,包括 35 次插入13 次删除
  1. 28 12
      Userland/Libraries/LibPDF/Renderer.cpp
  2. 7 1
      Userland/Libraries/LibPDF/Renderer.h

+ 28 - 12
Userland/Libraries/LibPDF/Renderer.cpp

@@ -1030,7 +1030,7 @@ static Vector<u8> upsample_to_8_bit(ReadonlyBytes content, int samples_per_line,
     return upsampled_storage;
 }
 
-PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> Renderer::load_image(NonnullRefPtr<StreamObject> image)
+PDFErrorOr<Renderer::LoadedImage> Renderer::load_image(NonnullRefPtr<StreamObject> image)
 {
     auto image_dict = image->dict();
     auto width = TRY(m_document->resolve_to<int>(image_dict->get_value(CommonNames::Width)));
@@ -1051,17 +1051,19 @@ PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> Renderer::load_image(NonnullRefPtr<Stream
     if (TRY(is_filter(CommonNames::JPXDecode))) {
         return Error(Error::Type::RenderingUnsupported, "JPXDecode filter");
     }
+
+    bool is_image_mask = false;
     if (image_dict->contains(CommonNames::ImageMask)) {
-        auto is_mask = TRY(m_document->resolve_to<bool>(image_dict->get_value(CommonNames::ImageMask)));
-        if (is_mask) {
-            return Error(Error::Type::RenderingUnsupported, "Image masks");
-        }
+        is_image_mask = TRY(m_document->resolve_to<bool>(image_dict->get_value(CommonNames::ImageMask)));
     }
 
     // "(Required for images, except those that use the JPXDecode filter; not allowed for image masks) [...]
     //  it can be any type of color space except Pattern."
-    auto color_space_object = MUST(image_dict->get_object(m_document, CommonNames::ColorSpace));
-    auto color_space = TRY(get_color_space_from_document(color_space_object));
+    NonnullRefPtr<ColorSpace> color_space = DeviceGrayColorSpace::the();
+    if (!is_image_mask) {
+        auto color_space_object = MUST(image_dict->get_object(m_document, CommonNames::ColorSpace));
+        color_space = TRY(get_color_space_from_document(color_space_object));
+    }
 
     auto color_rendering_intent = state().color_rendering_intent;
     if (image_dict->contains(CommonNames::Intent))
@@ -1069,7 +1071,11 @@ PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> Renderer::load_image(NonnullRefPtr<Stream
     // FIXME: Do something with color_rendering_intent.
 
     // "Valid values are 1, 2, 4, 8, and (in PDF 1.5) 16."
-    auto bits_per_component = TRY(m_document->resolve_to<int>(image_dict->get_value(CommonNames::BitsPerComponent)));
+    // Per spec, this is required even for /Mask images, but it's required to be 1 there.
+    // In practice, it's sometimes missing for /Mask images.
+    auto bits_per_component = 1;
+    if (!is_image_mask)
+        bits_per_component = TRY(m_document->resolve_to<int>(image_dict->get_value(CommonNames::BitsPerComponent)));
     switch (bits_per_component) {
     case 1:
     case 2:
@@ -1091,6 +1097,13 @@ PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> Renderer::load_image(NonnullRefPtr<Stream
         upsampled_storage = upsample_to_8_bit(content, width * n_components, bits_per_component, mode);
         content = upsampled_storage;
         bits_per_component = 8;
+
+        if (is_image_mask) {
+            // "a sample value of 0 marks the page with the current color, and a 1 leaves the previous contents unchanged."
+            // That's opposite of the normal alpha convention, and we're upsampling masks to 8 bit and use that as normal alpha.
+            for (u8& byte : upsampled_storage)
+                byte = ~byte;
+        }
     }
 
     if (bits_per_component == 16) {
@@ -1113,7 +1126,7 @@ PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> Renderer::load_image(NonnullRefPtr<Stream
 
     if (TRY(is_filter(CommonNames::DCTDecode))) {
         // TODO: stream objects could store Variant<bytes/Bitmap> to avoid serialisation/deserialisation here
-        return TRY(Gfx::Bitmap::create_from_serialized_bytes(image->bytes()));
+        return LoadedImage { TRY(Gfx::Bitmap::create_from_serialized_bytes(image->bytes())), is_image_mask };
     }
 
     auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height }));
@@ -1146,7 +1159,7 @@ PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> Renderer::load_image(NonnullRefPtr<Stream
             ++y;
         }
     }
-    return bitmap;
+    return LoadedImage { bitmap, is_image_mask };
 }
 
 Gfx::AffineTransform Renderer::calculate_image_space_transformation(int width, int height)
@@ -1199,9 +1212,12 @@ PDFErrorOr<void> Renderer::show_image(NonnullRefPtr<StreamObject> image)
         return {};
     }
     auto image_bitmap = TRY(load_image(image));
+    if (image_bitmap.is_image_mask)
+        return Error(Error::Type::RenderingUnsupported, "Image masks");
+
     if (image_dict->contains(CommonNames::SMask)) {
         auto smask_bitmap = TRY(load_image(TRY(image_dict->get_stream(m_document, CommonNames::SMask))));
-        TRY(apply_alpha_channel(image_bitmap, smask_bitmap));
+        TRY(apply_alpha_channel(image_bitmap.bitmap, smask_bitmap.bitmap));
     } else if (image_dict->contains(CommonNames::Mask)) {
         auto mask_object = TRY(image_dict->get_object(m_document, CommonNames::Mask));
         if (mask_object->is<StreamObject>()) {
@@ -1213,7 +1229,7 @@ PDFErrorOr<void> Renderer::show_image(NonnullRefPtr<StreamObject> image)
 
     auto image_space = calculate_image_space_transformation(width, height);
     auto image_rect = Gfx::FloatRect { 0, 0, width, height };
-    m_painter.draw_scaled_bitmap_with_transform(image_bitmap->rect(), image_bitmap, image_rect, image_space);
+    m_painter.draw_scaled_bitmap_with_transform(image_bitmap.bitmap->rect(), image_bitmap.bitmap, image_rect, image_space);
     return {};
 }
 

+ 7 - 1
Userland/Libraries/LibPDF/Renderer.h

@@ -131,7 +131,13 @@ private:
     void end_path_paint();
     PDFErrorOr<void> set_graphics_state_from_dict(NonnullRefPtr<DictObject>);
     PDFErrorOr<void> show_text(ByteString const&);
-    PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> load_image(NonnullRefPtr<StreamObject>);
+
+    struct LoadedImage {
+        NonnullRefPtr<Gfx::Bitmap> bitmap;
+        bool is_image_mask = false;
+    };
+    PDFErrorOr<LoadedImage> load_image(NonnullRefPtr<StreamObject>);
+
     PDFErrorOr<void> show_image(NonnullRefPtr<StreamObject>);
     void show_empty_image(int width, int height);
     PDFErrorOr<NonnullRefPtr<ColorSpace>> get_color_space_from_resources(Value const&, NonnullRefPtr<DictObject>);