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).
This commit is contained in:
Nico Weber 2023-12-22 18:56:42 -05:00 committed by Andreas Kling
parent 3ad9782e25
commit a3507ef65b
Notes: sideshowbarker 2024-07-17 03:05:16 +09:00
2 changed files with 35 additions and 13 deletions

View File

@ -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 {};
}

View File

@ -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>);