Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibGfx+LibPDF: Some prep work to become better at color conversion, and a small LibPDF cleanup #25680

Merged
merged 5 commits into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions Userland/Libraries/LibGfx/ImageFormats/ISOBMFF/JPEG2000Boxes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,25 +103,28 @@ ErrorOr<void> JPEG2000ColorSpecificationBox::read_from_stream(BoxStream& stream)
method = TRY(stream.read_value<u8>());
precedence = TRY(stream.read_value<i8>());
approximation = TRY(stream.read_value<u8>());
if (method == 1)
if (method == Method::Enumerated) {
enumerated_color_space = TRY(stream.read_value<BigEndian<u32>>());
if (method == 2) {
// FIXME: For some enumerated color spaces, there's additional data here
// (e.g. CIELab, see T.801 M.11.7.4.1 EP field format for the CIELab colourspace)
} else if (method == Method::ICC_Restricted) {
ByteBuffer local_icc_data = TRY(ByteBuffer::create_uninitialized(stream.remaining()));
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) {
} else if (method == Method::ICC_Any) {
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)
} else if (method == Method::Vendor) {
return Error::from_string_literal("Method 4 is not yet implemented");
if (method == 5)
} else if (method == Method::Parameterized) {
return Error::from_string_literal("Method 5 is not yet implemented");
} else {
// "Reserved for other ITU-T | ISO uses. [...] ]there may be fields in this box following the APPROX field,
// and a conforming JP2 reader shall ignore the entire Colour Specification box."
dbgln("Unknown method value: {}", method);
TRY(stream.discard(stream.remaining()));
}

return {};
}
Expand Down
36 changes: 34 additions & 2 deletions Userland/Libraries/LibGfx/ImageFormats/ISOBMFF/JPEG2000Boxes.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,40 @@ struct JPEG2000ColorSpecificationBox final : public Box {
u8 method { 0 };
i8 precedence { 0 };
u8 approximation { 0 };
u32 enumerated_color_space { 0 }; // Only set if method == 1
ByteBuffer icc_data; // Only set if method == 2
u32 enumerated_color_space { 0 }; // Only set if method == Method::Enumerated
ByteBuffer icc_data; // Only set if method == Method::ICC_Restricted or Method::ICC_Any

enum Method {
// T.800, Table I.9 – Legal METH values

// "Enumerated Colourspace. This colourspace specification box contains the enumerated value of the colourspace of this image. The
// enumerated value is found in the EnumCS field in this box."
Enumerated = 1,

// "Restricted ICC profile. This Colour Specification box contains an ICC profile in the PROFILE field. This profile shall specify the
// transformation needed to convert the decompressed image data into the PCSXYZ, and shall conform to either the Monochrome Input, the
// Three-Component Matrix-Based Input profile class, the Monochrome Display or the Three-Component Matrix-Based Display class and
// contain all the required tags specified therein"
ICC_Restricted = 2,

// "other values" "Reserved for other ITU-T | ISO uses. If the value of METH is not 1 or 2, there may be fields in this box following the APPROX field,
// and a conforming JP2 reader shall ignore the entire Colour Specification box.""

// T.801, Table M.22 – Legal METH values

// "Any ICC method. This Colour Specification box indicates that the colourspace of the codestream is specified by an
// embedded input ICC profile. Contrary to the Restricted ICC method defined in the JP2 file format, this method allows
// for any input ICC profile"
ICC_Any = 3,

// "Vendor Colour method. This Colour Specification box indicates that the colourspace of the codestream is specified by
// a unique vendor defined code.
Vendor = 4,

// "Parameterized colourspace. This Colour Specification box indicates that the colourspace of the codestream is
// parameterized"
Parameterized = 5,
};

enum EnumCS {
// T.800, Table I.10 – Legal EnumCS values
Expand Down
15 changes: 10 additions & 5 deletions Userland/Libraries/LibGfx/ImageFormats/JPEG2000Loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,8 @@ struct JPEG2000LoadingContext {
State state { State::NotDecoded };
ReadonlyBytes codestream_data;
size_t codestream_cursor { 0 };
Optional<ReadonlyBytes> icc_data;

Optional<ISOBMFF::JPEG2000ColorSpecificationBox const&> color_box; // This is always set for box-based files.

IntSize size;

Expand Down Expand Up @@ -1190,9 +1191,7 @@ static ErrorOr<void> decode_jpeg2000_header(JPEG2000LoadingContext& context, Rea
if (image_header_box.compression_type != ISOBMFF::JPEG2000ImageHeaderBox::CompressionType::Default)
return Error::from_string_literal("JPEG2000ImageDecoderPlugin: Decoding of non-jpeg2000 data embedded in jpeg2000 files is not implemented");

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 || color_header_box.method == 3)
context.icc_data = color_header_box.icc_data.bytes();
context.color_box = static_cast<ISOBMFF::JPEG2000ColorSpecificationBox const&>(*header_box.child_boxes()[color_header_box_index.value()]);

TRY(parse_codestream_main_header(context));

Expand Down Expand Up @@ -2076,7 +2075,13 @@ ErrorOr<ImageFrameDescriptor> JPEG2000ImageDecoderPlugin::frame(size_t index, Op

ErrorOr<Optional<ReadonlyBytes>> JPEG2000ImageDecoderPlugin::icc_data()
{
return m_context->icc_data;
if (m_context->color_box.has_value()
&& (m_context->color_box->method == ISOBMFF::JPEG2000ColorSpecificationBox::Method::ICC_Restricted
|| m_context->color_box->method == ISOBMFF::JPEG2000ColorSpecificationBox::Method::ICC_Any)) {
return m_context->color_box->icc_data.bytes();
}

return OptionalNone {};
}

}
4 changes: 2 additions & 2 deletions Userland/Libraries/LibPDF/Filter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,10 +364,10 @@ PDFErrorOr<ByteBuffer> Filter::decode_jbig2(Document* document, ReadonlyBytes by

PDFErrorOr<ByteBuffer> Filter::decode_dct(ReadonlyBytes bytes)
{
if (!Gfx::JPEGImageDecoderPlugin::sniff({ bytes.data(), bytes.size() }))
if (!Gfx::JPEGImageDecoderPlugin::sniff(bytes))
return AK::Error::from_string_literal("Not a JPEG image!");

auto decoder = TRY(Gfx::JPEGImageDecoderPlugin::create_with_options({ bytes.data(), bytes.size() }, { .cmyk = Gfx::JPEGDecoderOptions::CMYK::PDF }));
auto decoder = TRY(Gfx::JPEGImageDecoderPlugin::create_with_options(bytes, { .cmyk = Gfx::JPEGDecoderOptions::CMYK::PDF }));
auto internal_format = decoder->natural_frame_format();

if (internal_format == Gfx::NaturalFrameFormat::CMYK) {
Expand Down