mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 10:28:10 +00:00
LibGfx: Make JPGLoader hand out embedded ICC bytes
This commit is contained in:
parent
307712b398
commit
f4bc10c84a
1 changed files with 96 additions and 4 deletions
|
@ -8,6 +8,7 @@
|
|||
#include <AK/Error.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/Math.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Try.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCore/MemoryStream.h>
|
||||
|
@ -170,6 +171,7 @@ struct JPGLoadingContext {
|
|||
NotDecoded = 0,
|
||||
Error,
|
||||
FrameDecoded,
|
||||
HeaderDecoded,
|
||||
BitmapDecoded
|
||||
};
|
||||
|
||||
|
@ -190,6 +192,8 @@ struct JPGLoadingContext {
|
|||
HuffmanStreamState huffman_stream;
|
||||
i32 previous_dc_values[3] = { 0 };
|
||||
MacroblockMeta mblock_meta;
|
||||
OwnPtr<Core::Stream::FixedMemoryStream> stream;
|
||||
Optional<ByteBuffer> icc_data;
|
||||
};
|
||||
|
||||
static void generate_huffman_codes(HuffmanTableSpec& table)
|
||||
|
@ -588,6 +592,59 @@ static ErrorOr<void> read_huffman_table(Core::Stream::SeekableStream& stream, JP
|
|||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<void> read_icc_profile(Core::Stream::SeekableStream& stream, JPGLoadingContext& context, int bytes_to_read)
|
||||
{
|
||||
if (bytes_to_read <= 2)
|
||||
return Error::from_string_literal("icc marker too small");
|
||||
|
||||
auto chunk_sequence_number = TRY(stream.read_value<u8>()); // 1-based
|
||||
auto number_of_chunks = TRY(stream.read_value<u8>());
|
||||
bytes_to_read -= 2;
|
||||
|
||||
// FIXME: Support ICC profiles larger than a single jpeg marker. (They are very rare in practice.)
|
||||
if (number_of_chunks != 1) {
|
||||
dbgln("jpg: Ignoring ICC profile spanning several chunks ({}/{})", chunk_sequence_number, number_of_chunks);
|
||||
return stream.discard(bytes_to_read);
|
||||
}
|
||||
|
||||
ByteBuffer icc_bytes = TRY(ByteBuffer::create_zeroed(bytes_to_read));
|
||||
TRY(stream.read_entire_buffer(icc_bytes));
|
||||
context.icc_data = move(icc_bytes);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<void> read_app_marker(Core::Stream::SeekableStream& stream, JPGLoadingContext& context, int app_marker_number)
|
||||
{
|
||||
i32 bytes_to_read = TRY(stream.read_value<BigEndian<u16>>());
|
||||
TRY(ensure_bounds_okay(TRY(stream.tell()), bytes_to_read, context.data_size));
|
||||
|
||||
if (bytes_to_read <= 2)
|
||||
return Error::from_string_literal("app marker size too small");
|
||||
bytes_to_read -= 2;
|
||||
|
||||
StringBuilder builder;
|
||||
for (;;) {
|
||||
if (bytes_to_read == 0)
|
||||
return Error::from_string_literal("app marker size too small for identifier");
|
||||
|
||||
auto c = TRY(stream.read_value<char>());
|
||||
bytes_to_read--;
|
||||
|
||||
if (c == '\0')
|
||||
break;
|
||||
|
||||
TRY(builder.try_append(c));
|
||||
}
|
||||
|
||||
auto app_id = TRY(builder.to_string());
|
||||
|
||||
if (app_marker_number == 2 && app_id == "ICC_PROFILE"sv)
|
||||
return read_icc_profile(stream, context, bytes_to_read);
|
||||
|
||||
return stream.discard(bytes_to_read);
|
||||
}
|
||||
|
||||
static inline bool validate_luma_and_modify_context(ComponentSpec const& luma, JPGLoadingContext& context)
|
||||
{
|
||||
if ((luma.hsample_factor == 1 || luma.hsample_factor == 2) && (luma.vsample_factor == 1 || luma.vsample_factor == 2)) {
|
||||
|
@ -1017,6 +1074,24 @@ static ErrorOr<void> parse_header(Core::Stream::SeekableStream& stream, JPGLoadi
|
|||
case JPG_EOI:
|
||||
dbgln_if(JPG_DEBUG, "{}: Unexpected marker {:x}!", TRY(stream.tell()), marker);
|
||||
return Error::from_string_literal("Unexpected marker");
|
||||
case JPG_APPN0:
|
||||
case JPG_APPN1:
|
||||
case JPG_APPN2:
|
||||
case JPG_APPN3:
|
||||
case JPG_APPN4:
|
||||
case JPG_APPN5:
|
||||
case JPG_APPN6:
|
||||
case JPG_APPN7:
|
||||
case JPG_APPN8:
|
||||
case JPG_APPN9:
|
||||
case JPG_APPNA:
|
||||
case JPG_APPNB:
|
||||
case JPG_APPNC:
|
||||
case JPG_APPND:
|
||||
case JPG_APPNE:
|
||||
case JPG_APPNF:
|
||||
TRY(read_app_marker(stream, context, marker - JPG_APPN0));
|
||||
break;
|
||||
case JPG_SOF0:
|
||||
TRY(read_start_of_frame(stream, context));
|
||||
context.state = JPGLoadingContext::FrameDecoded;
|
||||
|
@ -1079,17 +1154,30 @@ static ErrorOr<void> scan_huffman_stream(Core::Stream::SeekableStream& stream, J
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static ErrorOr<void> decode_header(JPGLoadingContext& context)
|
||||
{
|
||||
if (context.state < JPGLoadingContext::State::HeaderDecoded) {
|
||||
context.stream = TRY(Core::Stream::FixedMemoryStream::construct({ context.data, context.data_size }));
|
||||
|
||||
if (auto result = parse_header(*context.stream, context); result.is_error()) {
|
||||
context.state = JPGLoadingContext::State::Error;
|
||||
return result.release_error();
|
||||
}
|
||||
context.state = JPGLoadingContext::State::HeaderDecoded;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<void> decode_jpg(JPGLoadingContext& context)
|
||||
{
|
||||
auto stream = TRY(Core::Stream::FixedMemoryStream::construct({ context.data, context.data_size }));
|
||||
|
||||
TRY(parse_header(*stream, context));
|
||||
TRY(scan_huffman_stream(*stream, context));
|
||||
TRY(decode_header(context));
|
||||
TRY(scan_huffman_stream(*context.stream, context));
|
||||
auto macroblocks = TRY(decode_huffman_stream(context));
|
||||
dequantize(context, macroblocks);
|
||||
inverse_dct(context, macroblocks);
|
||||
ycbcr_to_rgb(context, macroblocks);
|
||||
TRY(compose_bitmap(context, macroblocks));
|
||||
context.stream.clear();
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -1180,6 +1268,10 @@ ErrorOr<ImageFrameDescriptor> JPGImageDecoderPlugin::frame(size_t index)
|
|||
|
||||
ErrorOr<Optional<ReadonlyBytes>> JPGImageDecoderPlugin::icc_data()
|
||||
{
|
||||
TRY(decode_header(*m_context));
|
||||
|
||||
if (m_context->icc_data.has_value())
|
||||
return *m_context->icc_data;
|
||||
return OptionalNone {};
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue