diff --git a/Userland/Libraries/LibPDF/Renderer.cpp b/Userland/Libraries/LibPDF/Renderer.cpp index fd7bfb5384..be27847a8f 100644 --- a/Userland/Libraries/LibPDF/Renderer.cpp +++ b/Userland/Libraries/LibPDF/Renderer.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -1216,6 +1217,49 @@ PDFErrorOr Renderer::load_image(NonnullRefPtr> Renderer::make_mask_bitmap_from_array(NonnullRefPtr array, NonnullRefPtr image) +{ + // PDF 1.7 spec, 4.8.5. Masked Images, Color Key Masking + // "For color key masking, the value of the Mask entry is an array of 2 × n integers, [min_1 max_1 ... min_n max_n], + // where n is the number of color components in the image’s color space. Each integer must be in the range 0 to 2**(BitsPerComponent − 1), + // representing color values _before_ decoding with the Decode array. + // An image sample is masked [...] if min_i ≤ c_i ≤ max_i for all 1 ≤ i ≤ n." + // For indexed images, this means the array masks the index, not the color. + auto image_dict = image->dict(); + auto width = TRY(m_document->resolve_to(image_dict->get_value(CommonNames::Width))); + auto height = TRY(m_document->resolve_to(image_dict->get_value(CommonNames::Height))); + auto bits_per_component = TRY(m_document->resolve_to(image_dict->get_value(CommonNames::BitsPerComponent))); + VERIFY(bits_per_component == 1 || bits_per_component == 2 || bits_per_component == 4 || bits_per_component == 8 || bits_per_component == 16); + + if (array->size() % 2 != 0) + return Error(Error::Type::MalformedPDF, "Mask array must have an even number of elements"); + auto n_components = array->size() / 2; + Vector min, max; + for (size_t i = 0; i < n_components; ++i) { + min.append(array->at(i * 2).to_int()); + max.append(array->at(i * 2 + 1).to_int()); + } + + auto mask_bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height })); + auto bit_stream = make(make(image->bytes())); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + bool is_masked = true; + for (size_t i = 0; i < n_components; ++i) { + u16 sample = TRY(bit_stream->read_bits(bits_per_component)); + if (sample < min[i] || sample > max[i]) { + is_masked = false; + TRY(bit_stream->read_bits((n_components - 1 - i) * bits_per_component)); + break; + } + } + mask_bitmap->set_pixel(x, y, Color::from_argb(is_masked ? 0x00'00'00'00 : 0xff'ff'ff'ff)); + } + bit_stream->align_to_byte_boundary(); + } + return mask_bitmap; +} + Gfx::AffineTransform Renderer::calculate_image_space_transformation(Gfx::IntSize size) { // Image space maps to a 1x1 unit of user space and starts at the top-left @@ -1299,7 +1343,8 @@ PDFErrorOr Renderer::show_image(NonnullRefPtr image) auto mask_bitmap = TRY(load_image(mask_object->cast())); image_bitmap.bitmap = TRY(apply_alpha_channel(image_bitmap.bitmap, mask_bitmap.bitmap)); } else if (mask_object->is()) { - return Error::rendering_unsupported_error("/Mask array objects not yet implemented"); + auto mask_bitmap = TRY(make_mask_bitmap_from_array(mask_object->cast(), image)); + image_bitmap.bitmap = TRY(apply_alpha_channel(image_bitmap.bitmap, mask_bitmap)); } } diff --git a/Userland/Libraries/LibPDF/Renderer.h b/Userland/Libraries/LibPDF/Renderer.h index c2451889dc..bb45d00ae7 100644 --- a/Userland/Libraries/LibPDF/Renderer.h +++ b/Userland/Libraries/LibPDF/Renderer.h @@ -158,7 +158,7 @@ private: bool is_image_mask = false; }; PDFErrorOr load_image(NonnullRefPtr); - + PDFErrorOr> make_mask_bitmap_from_array(NonnullRefPtr, NonnullRefPtr); PDFErrorOr show_image(NonnullRefPtr); void show_empty_image(Gfx::IntSize); PDFErrorOr> get_color_space_from_resources(Value const&, NonnullRefPtr);