summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGfx
diff options
context:
space:
mode:
authorNico Weber <thakis@chromium.org>2023-02-25 16:14:05 -0500
committerLinus Groh <mail@linusgroh.de>2023-02-25 22:30:32 +0100
commit19ca8d7fb7ccc02889684e85c9d90d472166bdee (patch)
tree03f75de3d154bb02e76c67257971d6a128b46aa6 /Userland/Libraries/LibGfx
parent5eec76b441d5449feeee29e9f292a60429448ede (diff)
downloadserenity-19ca8d7fb7ccc02889684e85c9d90d472166bdee.zip
LibGfx: Decode WebP canvas size
This reorganizes things so that: * When initially decoding chunks, we only store pointers to their data and don't look at the contents * We allow pausing decoding after decoding the first chunk, since that's where the dimensions are stored, and we don't need to read more than that if we only care about dimensions. (Currently inconsequential, but maybe we want to get dimensions after receiving the first few bytes off the network in the future.) * We then have separate methods to interpret chunk data (only for the first few bytes which store the size, so far.)
Diffstat (limited to 'Userland/Libraries/LibGfx')
-rw-r--r--Userland/Libraries/LibGfx/WebPLoader.cpp171
1 files changed, 132 insertions, 39 deletions
diff --git a/Userland/Libraries/LibGfx/WebPLoader.cpp b/Userland/Libraries/LibGfx/WebPLoader.cpp
index 80d7f710eb..ca9644dea2 100644
--- a/Userland/Libraries/LibGfx/WebPLoader.cpp
+++ b/Userland/Libraries/LibGfx/WebPLoader.cpp
@@ -7,6 +7,7 @@
#include <AK/Debug.h>
#include <AK/Endian.h>
#include <AK/Format.h>
+#include <AK/Vector.h>
#include <LibGfx/WebPLoader.h>
// Overview: https://developers.google.com/speed/webp/docs/compression
@@ -52,6 +53,32 @@ struct Chunk {
ReadonlyBytes data;
};
+struct VP8Header {
+ u8 version;
+ bool show_frame;
+ u32 size_of_first_partition;
+ u32 width;
+ u8 horizontal_scale;
+ u32 height;
+ u8 vertical_scale;
+};
+
+struct VP8LHeader {
+ u16 width;
+ u16 height;
+ bool is_alpha_used;
+};
+
+struct VP8XHeader {
+ bool has_icc;
+ bool has_alpha;
+ bool has_exif;
+ bool has_xmp;
+ bool has_animation;
+ u32 width;
+ u32 height;
+};
+
}
struct WebPLoadingContext {
@@ -59,6 +86,7 @@ struct WebPLoadingContext {
NotDecoded = 0,
Error,
HeaderDecoded,
+ FirstChunkDecoded,
SizeDecoded,
ChunksDecoded,
BitmapDecoded,
@@ -66,9 +94,29 @@ struct WebPLoadingContext {
State state { State::NotDecoded };
ReadonlyBytes data;
+ ReadonlyBytes chunks_cursor;
+
+ Optional<IntSize> size;
+
RefPtr<Gfx::Bitmap> bitmap;
- Optional<ReadonlyBytes> icc_data;
+ // Either 'VP8 ' (simple lossy file), 'VP8L' (simple lossless file), or 'VP8X' (extended file).
+ Optional<Chunk> first_chunk;
+
+ // If first_chunk is not a VP8X chunk, then only image_data_chunk is set and all the other Chunks are not set.
+
+ // "For a still image, the image data consists of a single frame, which is made up of:
+ // An optional alpha subchunk.
+ // A bitstream subchunk."
+ Optional<Chunk> alpha_chunk; // 'ALPH'
+ Optional<Chunk> image_data_chunk; // Either 'VP8 ' or 'VP8L'.
+
+ Optional<Chunk> animation_header_chunk; // 'ANIM'
+ Vector<Chunk> animation_frame_chunks; // 'ANMF'
+
+ Optional<Chunk> iccp_chunk; // 'ICCP'
+ Optional<Chunk> exif_chunk; // 'EXIF'
+ Optional<Chunk> xmp_chunk; // 'XMP '
template<size_t N>
[[nodiscard]] class Error error(char const (&string_literal)[N])
@@ -149,7 +197,7 @@ static ErrorOr<Chunk> decode_webp_advance_chunk(WebPLoadingContext& context, Rea
// https://developers.google.com/speed/webp/docs/riff_container#simple_file_format_lossy
// https://datatracker.ietf.org/doc/html/rfc6386#section-19 "Annex A: Bitstream Syntax"
-static ErrorOr<void> decode_webp_simple_lossy(WebPLoadingContext& context, Chunk const& vp8_chunk)
+static ErrorOr<VP8Header> decode_webp_chunk_VP8_header(WebPLoadingContext& context, Chunk const& vp8_chunk)
{
VERIFY(vp8_chunk.type == FourCC("VP8 "));
@@ -200,12 +248,12 @@ static ErrorOr<void> decode_webp_simple_lossy(WebPLoadingContext& context, Chunk
dbgln_if(WEBP_DEBUG, "version {}, show_frame {}, size_of_first_partition {}, width {}, horizontal_scale {}, height {}, vertical_scale {}",
version, show_frame, size_of_first_partition, width, horizontal_scale, height, vertical_scale);
- return {};
+ return VP8Header { version, show_frame, size_of_first_partition, width, horizontal_scale, height, vertical_scale };
}
// https://developers.google.com/speed/webp/docs/riff_container#simple_file_format_lossless
// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification#7_overall_structure_of_the_format
-static ErrorOr<void> decode_webp_simple_lossless(WebPLoadingContext& context, Chunk const& vp8l_chunk)
+static ErrorOr<VP8LHeader> decode_webp_chunk_VP8L_header(WebPLoadingContext& context, Chunk const& vp8l_chunk)
{
VERIFY(vp8l_chunk.type == FourCC("VP8L"));
@@ -231,10 +279,10 @@ static ErrorOr<void> decode_webp_simple_lossless(WebPLoadingContext& context, Ch
if (version_number != 0)
return context.error("WebPImageDecoderPlugin: VP8L chunk invalid version_number");
- return {};
+ return VP8LHeader { width, height, is_alpha_used };
}
-static ErrorOr<void> decode_webp_chunk_VP8X(WebPLoadingContext& context, Chunk const& vp8x_chunk)
+static ErrorOr<VP8XHeader> decode_webp_chunk_VP8X(WebPLoadingContext& context, Chunk const& vp8x_chunk)
{
VERIFY(vp8x_chunk.type == FourCC("VP8X"));
@@ -260,11 +308,11 @@ static ErrorOr<void> decode_webp_chunk_VP8X(WebPLoadingContext& context, Chunk c
bool has_xmp = flags & 0x4;
bool has_animation = flags & 0x2;
- // 3 byte reserved
- // 3 byte width minus one
+ // 3 bytes reserved
+ // 3 bytes width minus one
u32 width = (data[4] | (data[5] << 8) | (data[6] << 16)) + 1;
- // 3 byte height minus one
+ // 3 bytes height minus one
u32 height = (data[7] | (data[8] << 8) | (data[9] << 16)) + 1;
dbgln_if(WEBP_DEBUG, "flags 0x{:x} --{}{}{}{}{}{}, width {}, height {}",
@@ -277,60 +325,105 @@ static ErrorOr<void> decode_webp_chunk_VP8X(WebPLoadingContext& context, Chunk c
(flags & 0x3e) == 0 ? " none" : "",
width, height);
- return {};
+ return VP8XHeader { has_icc, has_alpha, has_exif, has_xmp, has_animation, width, height };
}
// https://developers.google.com/speed/webp/docs/riff_container#extended_file_format
-static ErrorOr<void> decode_webp_extended(WebPLoadingContext& context, Chunk const& vp8x_chunk, ReadonlyBytes chunks)
+static ErrorOr<void> decode_webp_extended(WebPLoadingContext& context, ReadonlyBytes chunks)
{
- TRY(decode_webp_chunk_VP8X(context, vp8x_chunk));
-
// FIXME: This isn't quite to spec, which says
// "All chunks SHOULD be placed in the same order as listed above.
// If a chunk appears in the wrong place, the file is invalid, but readers MAY parse the file, ignoring the chunks that are out of order."
+ auto store = [](auto& field, Chunk const& chunk) {
+ if (!field.has_value())
+ field = chunk;
+ };
while (!chunks.is_empty()) {
auto chunk = TRY(decode_webp_advance_chunk(context, chunks));
if (chunk.type == FourCC("ICCP"))
- context.icc_data = chunk.data;
-
- // FIXME: Probably want to make this and decode_webp_simple_lossy/lossless call the same function
- // instead of calling the _simple functions from the _extended function.
- if (chunk.type == FourCC("VP8 "))
- TRY(decode_webp_simple_lossy(context, chunk));
- if (chunk.type == FourCC("VP8L"))
- TRY(decode_webp_simple_lossless(context, chunk));
+ store(context.iccp_chunk, chunk);
+ else if (chunk.type == FourCC("ALPH"))
+ store(context.alpha_chunk, chunk);
+ else if (chunk.type == FourCC("ANIM"))
+ store(context.animation_header_chunk, chunk);
+ else if (chunk.type == FourCC("ANMF"))
+ TRY(context.animation_frame_chunks.try_append(chunk));
+ else if (chunk.type == FourCC("EXIF"))
+ store(context.exif_chunk, chunk);
+ else if (chunk.type == FourCC("XMP "))
+ store(context.xmp_chunk, chunk);
+ else if (chunk.type == FourCC("VP8 ") || chunk.type == FourCC("VP8L"))
+ store(context.image_data_chunk, chunk);
}
context.state = WebPLoadingContext::State::ChunksDecoded;
return {};
}
-static ErrorOr<void> decode_webp_chunks(WebPLoadingContext& context)
+static ErrorOr<void> decode_webp_first_chunk(WebPLoadingContext& context)
{
- if (context.state >= WebPLoadingContext::State::ChunksDecoded)
+ if (context.state >= WebPLoadingContext::State::FirstChunkDecoded)
return {};
if (context.state < WebPLoadingContext::HeaderDecoded)
TRY(decode_webp_header(context));
- ReadonlyBytes chunks = context.data.slice(sizeof(WebPFileHeader));
- auto first_chunk = TRY(decode_webp_advance_chunk(context, chunks));
+ context.chunks_cursor = context.data.slice(sizeof(WebPFileHeader));
+ auto first_chunk = TRY(decode_webp_advance_chunk(context, context.chunks_cursor));
- if (first_chunk.type == FourCC("VP8 ")) {
- context.state = WebPLoadingContext::State::ChunksDecoded;
- return decode_webp_simple_lossy(context, first_chunk);
- }
+ if (first_chunk.type != FourCC("VP8 ") && first_chunk.type != FourCC("VP8L") && first_chunk.type != FourCC("VP8X"))
+ return context.error("WebPImageDecoderPlugin: Invalid first chunk type");
+
+ context.first_chunk = first_chunk;
+ context.state = WebPLoadingContext::State::FirstChunkDecoded;
+
+ if (first_chunk.type == FourCC("VP8 ") || first_chunk.type == FourCC("VP8L"))
+ context.image_data_chunk = first_chunk;
- if (first_chunk.type == FourCC("VP8L")) {
- context.state = WebPLoadingContext::State::ChunksDecoded;
- return decode_webp_simple_lossless(context, first_chunk);
+ return {};
+}
+
+static ErrorOr<void> decode_webp_size(WebPLoadingContext& context)
+{
+ if (context.state >= WebPLoadingContext::State::SizeDecoded)
+ return {};
+
+ if (context.state < WebPLoadingContext::FirstChunkDecoded)
+ TRY(decode_webp_first_chunk(context));
+
+ if (context.first_chunk->type == FourCC("VP8 ")) {
+ auto header = TRY(decode_webp_chunk_VP8_header(context, context.first_chunk.value()));
+ context.size = IntSize { header.width, header.height };
+ context.state = WebPLoadingContext::State::SizeDecoded;
+ return {};
}
+ if (context.first_chunk->type == FourCC("VP8L")) {
+ auto header = TRY(decode_webp_chunk_VP8L_header(context, context.first_chunk.value()));
+ context.size = IntSize { header.width, header.height };
+ context.state = WebPLoadingContext::State::SizeDecoded;
+ return {};
+ }
+ VERIFY(context.first_chunk->type == FourCC("VP8X"));
+ auto header = TRY(decode_webp_chunk_VP8X(context, context.first_chunk.value()));
+ context.size = IntSize { header.width, header.height };
+ context.state = WebPLoadingContext::State::SizeDecoded;
+ return {};
+}
+
+static ErrorOr<void> decode_webp_chunks(WebPLoadingContext& context)
+{
+ if (context.state >= WebPLoadingContext::State::ChunksDecoded)
+ return {};
+
+ if (context.state < WebPLoadingContext::SizeDecoded)
+ TRY(decode_webp_size(context));
- if (first_chunk.type == FourCC("VP8X"))
- return decode_webp_extended(context, first_chunk, chunks);
+ if (context.first_chunk->type == FourCC("VP8X"))
+ return decode_webp_extended(context, context.chunks_cursor);
- return context.error("WebPImageDecoderPlugin: Invalid first chunk type");
+ context.state = WebPLoadingContext::State::ChunksDecoded;
+ return {};
}
WebPImageDecoderPlugin::WebPImageDecoderPlugin(ReadonlyBytes data, OwnPtr<WebPLoadingContext> context)
@@ -347,11 +440,11 @@ IntSize WebPImageDecoderPlugin::size()
return {};
if (m_context->state < WebPLoadingContext::State::SizeDecoded) {
- // FIXME
+ if (decode_webp_size(*m_context).is_error())
+ return {};
}
- // FIXME
- return { 0, 0 };
+ return m_context->size.value();
}
void WebPImageDecoderPlugin::set_volatile()
@@ -418,7 +511,7 @@ ErrorOr<Optional<ReadonlyBytes>> WebPImageDecoderPlugin::icc_data()
// FIXME: "If this chunk is not present, sRGB SHOULD be assumed."
- return m_context->icc_data;
+ return m_context->iccp_chunk.map([](auto iccp_chunk) { return iccp_chunk.data; });
}
}