summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGfx
diff options
context:
space:
mode:
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; });
}
}