1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 11:07:45 +00:00

LibGfx/JPEG: Add support for SOF1 images

More precisely, it allows the decoder to try `SOF1` images. There are
still some sub-kind of this kind of JPEG that we don't support. In a
nutshell `SOF1` images allow more Huffman and quantization tables, 12
bits precision and arithmetic encoding. This patch only brings support
for the "more tables" part.

Please note that `SOF2` images are also allowed to have more tables, so
we gave the decoder the ability to handle these in the same time.
This commit is contained in:
Lucas CHOLLET 2023-05-07 14:33:04 -04:00 committed by Andreas Kling
parent fb6b52b3fb
commit 9b5050a11c

View file

@ -73,6 +73,7 @@
#define JPEG_EOI 0xFFD9 #define JPEG_EOI 0xFFD9
#define JPEG_DRI 0XFFDD #define JPEG_DRI 0XFFDD
#define JPEG_SOF0 0XFFC0 #define JPEG_SOF0 0XFFC0
#define JPEG_SOF1 0XFFC1
#define JPEG_SOF2 0xFFC2 #define JPEG_SOF2 0xFFC2
#define JPEG_SOF15 0xFFCF #define JPEG_SOF15 0xFFCF
#define JPEG_SOI 0XFFD8 #define JPEG_SOI 0XFFD8
@ -783,6 +784,7 @@ static inline bool is_supported_marker(Marker const marker)
case JPEG_DRI: case JPEG_DRI:
case JPEG_EOI: case JPEG_EOI:
case JPEG_SOF0: case JPEG_SOF0:
case JPEG_SOF1:
case JPEG_SOF2: case JPEG_SOF2:
case JPEG_SOI: case JPEG_SOI:
case JPEG_SOS: case JPEG_SOS:
@ -906,18 +908,22 @@ static ErrorOr<void> read_restart_interval(Stream& stream, JPEGLoadingContext& c
static ErrorOr<void> read_huffman_table(Stream& stream, JPEGLoadingContext& context) static ErrorOr<void> read_huffman_table(Stream& stream, JPEGLoadingContext& context)
{ {
// B.2.4.2 - Huffman table-specification syntax
u16 bytes_to_read = TRY(read_effective_chunk_size(stream)); u16 bytes_to_read = TRY(read_effective_chunk_size(stream));
while (bytes_to_read > 0) { while (bytes_to_read > 0) {
HuffmanTable table; HuffmanTable table;
u8 table_info = TRY(stream.read_value<u8>()); u8 const table_info = TRY(stream.read_value<u8>());
u8 table_type = table_info >> 4; u8 const table_type = table_info >> 4;
u8 table_destination_id = table_info & 0x0F; u8 const table_destination_id = table_info & 0x0F;
if (table_type > 1) { if (table_type > 1) {
dbgln_if(JPEG_DEBUG, "Unrecognized huffman table: {}!", table_type); dbgln_if(JPEG_DEBUG, "Unrecognized huffman table: {}!", table_type);
return Error::from_string_literal("Unrecognized huffman table"); return Error::from_string_literal("Unrecognized huffman table");
} }
if (table_destination_id > 1) {
if ((context.frame.type == StartOfFrame::FrameType::Baseline_DCT && table_destination_id > 1)
|| (context.frame.type != StartOfFrame::FrameType::Baseline_DCT && table_destination_id > 3)) {
dbgln_if(JPEG_DEBUG, "Invalid huffman table destination id: {}!", table_destination_id); dbgln_if(JPEG_DEBUG, "Invalid huffman table destination id: {}!", table_destination_id);
return Error::from_string_literal("Invalid huffman table destination id"); return Error::from_string_literal("Invalid huffman table destination id");
} }
@ -948,7 +954,6 @@ static ErrorOr<void> read_huffman_table(Stream& stream, JPEGLoadingContext& cont
auto& huffman_table = table.type == 0 ? context.dc_tables : context.ac_tables; auto& huffman_table = table.type == 0 ? context.dc_tables : context.ac_tables;
huffman_table.set(table.destination_id, table); huffman_table.set(table.destination_id, table);
VERIFY(huffman_table.size() <= 2);
bytes_to_read -= 1 + 16 + total_codes; bytes_to_read -= 1 + 16 + total_codes;
} }
@ -1124,6 +1129,18 @@ static inline void set_macroblock_metadata(JPEGLoadingContext& context)
context.mblock_meta.total = context.mblock_meta.hcount * context.mblock_meta.vcount; context.mblock_meta.total = context.mblock_meta.hcount * context.mblock_meta.vcount;
} }
static ErrorOr<void> ensure_standard_precision(StartOfFrame const& frame)
{
// B.2.2 - Frame header syntax
// Table B.2 - Frame header parameter sizes and values
if (frame.precision == 8)
return {};
dbgln_if(JPEG_DEBUG, "Unsupported precision: {}, for SOF type: {}!", frame.precision, static_cast<int>(frame.type));
return Error::from_string_literal("Unsupported SOF precision.");
}
static ErrorOr<void> read_start_of_frame(Stream& stream, JPEGLoadingContext& context) static ErrorOr<void> read_start_of_frame(Stream& stream, JPEGLoadingContext& context)
{ {
if (context.state == JPEGLoadingContext::FrameDecoded) { if (context.state == JPEGLoadingContext::FrameDecoded) {
@ -1134,10 +1151,8 @@ static ErrorOr<void> read_start_of_frame(Stream& stream, JPEGLoadingContext& con
[[maybe_unused]] u16 const bytes_to_read = TRY(read_effective_chunk_size(stream)); [[maybe_unused]] u16 const bytes_to_read = TRY(read_effective_chunk_size(stream));
context.frame.precision = TRY(stream.read_value<u8>()); context.frame.precision = TRY(stream.read_value<u8>());
if (context.frame.precision != 8) {
dbgln_if(JPEG_DEBUG, "SOF precision != 8!"); TRY(ensure_standard_precision(context.frame));
return Error::from_string_literal("SOF precision != 8");
}
context.frame.height = TRY(stream.read_value<BigEndian<u16>>()); context.frame.height = TRY(stream.read_value<BigEndian<u16>>());
context.frame.width = TRY(stream.read_value<BigEndian<u16>>()); context.frame.width = TRY(stream.read_value<BigEndian<u16>>());
@ -1741,6 +1756,7 @@ static ErrorOr<void> parse_header(Stream& stream, JPEGLoadingContext& context)
dbgln_if(JPEG_DEBUG, "Unexpected marker {:x}!", marker); dbgln_if(JPEG_DEBUG, "Unexpected marker {:x}!", marker);
return Error::from_string_literal("Unexpected marker"); return Error::from_string_literal("Unexpected marker");
case JPEG_SOF0: case JPEG_SOF0:
case JPEG_SOF1:
case JPEG_SOF2: case JPEG_SOF2:
TRY(read_start_of_frame(stream, context)); TRY(read_start_of_frame(stream, context));
context.state = JPEGLoadingContext::FrameDecoded; context.state = JPEGLoadingContext::FrameDecoded;