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