mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-05 09:52:54 +00:00
LibGfx/WebP: Change ImageData optional-ness
Instead of ImageData having an Optional<Chunk> for the image data, have it have a Chunk, and give the context an Optional<ImageData>. This allows sharing a single code path for checking that either the main image or an animation frame has a main image data chunk, and that an alpha chunk is only present with a lossy main image data chunk. No behavior change.
This commit is contained in:
parent
119ccfe5da
commit
bdba70b38f
Notes:
sideshowbarker
2024-07-17 20:19:08 +09:00
Author: https://github.com/nico
Commit: bdba70b38f
Pull-request: https://github.com/SerenityOS/serenity/pull/18726
1 changed files with 42 additions and 41 deletions
|
@ -107,8 +107,8 @@ struct ANMFChunk {
|
||||||
// A bitstream subchunk."
|
// A bitstream subchunk."
|
||||||
struct ImageData {
|
struct ImageData {
|
||||||
// "This optional chunk contains encoded alpha data for this frame. A frame containing a 'VP8L' chunk SHOULD NOT contain this chunk."
|
// "This optional chunk contains encoded alpha data for this frame. A frame containing a 'VP8L' chunk SHOULD NOT contain this chunk."
|
||||||
Optional<Chunk> alpha_chunk; // 'ALPH'
|
Optional<Chunk> alpha_chunk; // 'ALPH'
|
||||||
Optional<Chunk> image_data_chunk; // Either 'VP8 ' or 'VP8L'. For 'VP8L', alpha_chunk will not have a value.
|
Chunk image_data_chunk; // Either 'VP8 ' or 'VP8L'. For 'VP8L', alpha_chunk will not have a value.
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,8 @@ struct WebPLoadingContext {
|
||||||
VP8XHeader vp8x_header;
|
VP8XHeader vp8x_header;
|
||||||
|
|
||||||
// If first_chunk is not a VP8X chunk, then only image_data.image_data_chunk is set and all the other Chunks are not set.
|
// If first_chunk is not a VP8X chunk, then only image_data.image_data_chunk is set and all the other Chunks are not set.
|
||||||
ImageData image_data;
|
// Once state is >= ChunksDecoded, for non-animated images, this will have a value, or decoding will have failed.
|
||||||
|
Optional<ImageData> image_data;
|
||||||
|
|
||||||
Optional<Chunk> animation_header_chunk; // 'ANIM'
|
Optional<Chunk> animation_header_chunk; // 'ANIM'
|
||||||
Vector<Chunk> animation_frame_chunks; // 'ANMF'
|
Vector<Chunk> animation_frame_chunks; // 'ANMF'
|
||||||
|
@ -464,11 +465,28 @@ static ErrorOr<ANMFChunk> decode_webp_chunk_ANMF(WebPLoadingContext& context, Ch
|
||||||
return ANMFChunk { frame_x, frame_y, frame_width, frame_height, frame_duration, blending_method, disposal_method, frame_data };
|
return ANMFChunk { frame_x, frame_y, frame_width, frame_height, frame_duration, blending_method, disposal_method, frame_data };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ErrorOr<ImageData> decode_webp_set_image_data(WebPLoadingContext& context, Optional<Chunk> alpha, Optional<Chunk> image_data)
|
||||||
|
{
|
||||||
|
if (!image_data.has_value())
|
||||||
|
return context.error("WebPImageDecoderPlugin: missing image data");
|
||||||
|
|
||||||
|
// https://developers.google.com/speed/webp/docs/riff_container#alpha
|
||||||
|
// "A frame containing a 'VP8L' chunk SHOULD NOT contain this chunk."
|
||||||
|
if (alpha.has_value() && image_data->type == FourCC("VP8L")) {
|
||||||
|
dbgln_if(WEBP_DEBUG, "WebPImageDecoderPlugin: VP8L frames should not have ALPH chunks. Ignoring ALPH chunk.");
|
||||||
|
alpha.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImageData { move(alpha), image_data.value() };
|
||||||
|
}
|
||||||
|
|
||||||
// https://developers.google.com/speed/webp/docs/riff_container#extended_file_format
|
// https://developers.google.com/speed/webp/docs/riff_container#extended_file_format
|
||||||
static ErrorOr<void> decode_webp_extended(WebPLoadingContext& context, ReadonlyBytes chunks)
|
static ErrorOr<void> decode_webp_extended(WebPLoadingContext& context, ReadonlyBytes chunks)
|
||||||
{
|
{
|
||||||
VERIFY(context.first_chunk->type == FourCC("VP8X"));
|
VERIFY(context.first_chunk->type == FourCC("VP8X"));
|
||||||
|
|
||||||
|
Optional<Chunk> alpha, image_data;
|
||||||
|
|
||||||
// FIXME: This isn't quite to spec, which says
|
// FIXME: This isn't quite to spec, which says
|
||||||
// "All chunks SHOULD be placed in the same order as listed above.
|
// "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."
|
// 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."
|
||||||
|
@ -482,7 +500,7 @@ static ErrorOr<void> decode_webp_extended(WebPLoadingContext& context, ReadonlyB
|
||||||
if (chunk.type == FourCC("ICCP"))
|
if (chunk.type == FourCC("ICCP"))
|
||||||
store(context.iccp_chunk, chunk);
|
store(context.iccp_chunk, chunk);
|
||||||
else if (chunk.type == FourCC("ALPH"))
|
else if (chunk.type == FourCC("ALPH"))
|
||||||
store(context.image_data.alpha_chunk, chunk);
|
store(alpha, chunk);
|
||||||
else if (chunk.type == FourCC("ANIM"))
|
else if (chunk.type == FourCC("ANIM"))
|
||||||
store(context.animation_header_chunk, chunk);
|
store(context.animation_header_chunk, chunk);
|
||||||
else if (chunk.type == FourCC("ANMF"))
|
else if (chunk.type == FourCC("ANMF"))
|
||||||
|
@ -492,7 +510,7 @@ static ErrorOr<void> decode_webp_extended(WebPLoadingContext& context, ReadonlyB
|
||||||
else if (chunk.type == FourCC("XMP "))
|
else if (chunk.type == FourCC("XMP "))
|
||||||
store(context.xmp_chunk, chunk);
|
store(context.xmp_chunk, chunk);
|
||||||
else if (chunk.type == FourCC("VP8 ") || chunk.type == FourCC("VP8L"))
|
else if (chunk.type == FourCC("VP8 ") || chunk.type == FourCC("VP8L"))
|
||||||
store(context.image_data.image_data_chunk, chunk);
|
store(image_data, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate chunks.
|
// Validate chunks.
|
||||||
|
@ -512,18 +530,17 @@ static ErrorOr<void> decode_webp_extended(WebPLoadingContext& context, ReadonlyB
|
||||||
context.animation_frame_chunks.clear();
|
context.animation_frame_chunks.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://developers.google.com/speed/webp/docs/riff_container#alpha
|
// Image data is not optional -- but the spec doesn't explicitly say that an animated image must have more than 0 frames.
|
||||||
// "A frame containing a 'VP8L' chunk SHOULD NOT contain this chunk."
|
// The spec also doesn't say that animated images must not contain a regular image data segment.
|
||||||
if (context.image_data.alpha_chunk.has_value() && context.image_data.image_data_chunk.has_value() && context.image_data.image_data_chunk->type == FourCC("VP8L")) {
|
if (!context.vp8x_header.has_animation || image_data.has_value())
|
||||||
dbgln_if(WEBP_DEBUG, "WebPImageDecoderPlugin: VP8L frames should not have ALPH chunks. Ignoring ALPH chunk.");
|
context.image_data = TRY(decode_webp_set_image_data(context, move(alpha), move(image_data)));
|
||||||
context.image_data.alpha_chunk.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developers.google.com/speed/webp/docs/riff_container#color_profile
|
// https://developers.google.com/speed/webp/docs/riff_container#color_profile
|
||||||
// "This chunk MUST appear before the image data."
|
// "This chunk MUST appear before the image data."
|
||||||
if (context.iccp_chunk.has_value()
|
if (context.iccp_chunk.has_value()
|
||||||
&& ((context.image_data.image_data_chunk.has_value() && context.iccp_chunk->data.data() > context.image_data.image_data_chunk->data.data())
|
&& ((context.image_data.has_value()
|
||||||
|| (context.image_data.alpha_chunk.has_value() && context.iccp_chunk->data.data() > context.image_data.alpha_chunk->data.data())
|
&& (context.iccp_chunk->data.data() > context.image_data->image_data_chunk.data.data()
|
||||||
|
|| (context.image_data->alpha_chunk.has_value() && context.iccp_chunk->data.data() > context.image_data->alpha_chunk->data.data())))
|
||||||
|| (!context.animation_frame_chunks.is_empty() && context.iccp_chunk->data.data() > context.animation_frame_chunks[0].data.data()))) {
|
|| (!context.animation_frame_chunks.is_empty() && context.iccp_chunk->data.data() > context.animation_frame_chunks[0].data.data()))) {
|
||||||
return context.error("WebPImageDecoderPlugin: ICCP chunk is after image data");
|
return context.error("WebPImageDecoderPlugin: ICCP chunk is after image data");
|
||||||
}
|
}
|
||||||
|
@ -550,7 +567,7 @@ static ErrorOr<void> read_webp_first_chunk(WebPLoadingContext& context)
|
||||||
context.state = WebPLoadingContext::State::FirstChunkRead;
|
context.state = WebPLoadingContext::State::FirstChunkRead;
|
||||||
|
|
||||||
if (first_chunk.type == FourCC("VP8 ") || first_chunk.type == FourCC("VP8L"))
|
if (first_chunk.type == FourCC("VP8 ") || first_chunk.type == FourCC("VP8L"))
|
||||||
context.image_data.image_data_chunk = first_chunk;
|
context.image_data = TRY(decode_webp_set_image_data(context, OptionalNone {}, first_chunk));
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -617,39 +634,28 @@ static ErrorOr<void> decode_webp_animation_frame_chunks(WebPLoadingContext& cont
|
||||||
static ErrorOr<ImageData> decode_webp_animation_frame_image_data(WebPLoadingContext& context, ANMFChunk const& frame)
|
static ErrorOr<ImageData> decode_webp_animation_frame_image_data(WebPLoadingContext& context, ANMFChunk const& frame)
|
||||||
{
|
{
|
||||||
ReadonlyBytes chunks = frame.frame_data;
|
ReadonlyBytes chunks = frame.frame_data;
|
||||||
|
|
||||||
ImageData image_data;
|
|
||||||
|
|
||||||
auto chunk = TRY(decode_webp_advance_chunk(context, chunks));
|
auto chunk = TRY(decode_webp_advance_chunk(context, chunks));
|
||||||
|
|
||||||
|
Optional<Chunk> alpha, image_data;
|
||||||
if (chunk.type == FourCC("ALPH")) {
|
if (chunk.type == FourCC("ALPH")) {
|
||||||
image_data.alpha_chunk = chunk;
|
alpha = chunk;
|
||||||
chunk = TRY(decode_webp_advance_chunk(context, chunks));
|
chunk = TRY(decode_webp_advance_chunk(context, chunks));
|
||||||
}
|
}
|
||||||
|
if (chunk.type == FourCC("VP8 ") || chunk.type == FourCC("VP8L"))
|
||||||
|
image_data = chunk;
|
||||||
|
|
||||||
if (chunk.type != FourCC("VP8 ") && chunk.type != FourCC("VP8L"))
|
return decode_webp_set_image_data(context, move(alpha), move(image_data));
|
||||||
return context.error("WebPImageDecoderPlugin: no image data found in animation frame");
|
|
||||||
|
|
||||||
image_data.image_data_chunk = chunk;
|
|
||||||
|
|
||||||
// https://developers.google.com/speed/webp/docs/riff_container#alpha
|
|
||||||
// "A frame containing a 'VP8L' chunk SHOULD NOT contain this chunk."
|
|
||||||
if (image_data.alpha_chunk.has_value() && image_data.image_data_chunk.has_value() && image_data.image_data_chunk->type == FourCC("VP8L")) {
|
|
||||||
dbgln_if(WEBP_DEBUG, "WebPImageDecoderPlugin: VP8L frames should not have ALPH chunks. Ignoring ALPH chunk.");
|
|
||||||
image_data.alpha_chunk.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
return image_data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ErrorOr<NonnullRefPtr<Bitmap>> decode_webp_image_data(WebPLoadingContext& context, ImageData const& image_data)
|
static ErrorOr<NonnullRefPtr<Bitmap>> decode_webp_image_data(WebPLoadingContext& context, ImageData const& image_data)
|
||||||
{
|
{
|
||||||
if (image_data.image_data_chunk->type == FourCC("VP8L")) {
|
if (image_data.image_data_chunk.type == FourCC("VP8L")) {
|
||||||
VERIFY(!image_data.alpha_chunk.has_value());
|
VERIFY(!image_data.alpha_chunk.has_value());
|
||||||
return decode_webp_chunk_VP8L(context, image_data.image_data_chunk.value());
|
return decode_webp_chunk_VP8L(context, image_data.image_data_chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
VERIFY(image_data.image_data_chunk->type == FourCC("VP8 "));
|
VERIFY(image_data.image_data_chunk.type == FourCC("VP8 "));
|
||||||
auto bitmap = TRY(decode_webp_chunk_VP8(context, image_data.image_data_chunk.value()));
|
auto bitmap = TRY(decode_webp_chunk_VP8(context, image_data.image_data_chunk));
|
||||||
|
|
||||||
if (image_data.alpha_chunk.has_value())
|
if (image_data.alpha_chunk.has_value())
|
||||||
TRY(decode_webp_chunk_ALPH(context, image_data.alpha_chunk.value(), *bitmap));
|
TRY(decode_webp_chunk_ALPH(context, image_data.alpha_chunk.value(), *bitmap));
|
||||||
|
@ -694,8 +700,6 @@ static ErrorOr<ImageFrameDescriptor> decode_webp_animation_frame(WebPLoadingCont
|
||||||
}
|
}
|
||||||
|
|
||||||
auto frame_image_data = TRY(decode_webp_animation_frame_image_data(context, frame_description));
|
auto frame_image_data = TRY(decode_webp_animation_frame_image_data(context, frame_description));
|
||||||
VERIFY(frame_image_data.image_data_chunk.has_value());
|
|
||||||
|
|
||||||
auto frame_bitmap = TRY(decode_webp_image_data(context, frame_image_data));
|
auto frame_bitmap = TRY(decode_webp_image_data(context, frame_image_data));
|
||||||
if (static_cast<u32>(frame_bitmap->width()) != frame_description.frame_width || static_cast<u32>(frame_bitmap->height()) != frame_description.frame_height)
|
if (static_cast<u32>(frame_bitmap->width()) != frame_description.frame_width || static_cast<u32>(frame_bitmap->height()) != frame_description.frame_height)
|
||||||
return context.error("WebPImageDecoderPlugin: decoded frame bitmap size doesn't match frame description size");
|
return context.error("WebPImageDecoderPlugin: decoded frame bitmap size doesn't match frame description size");
|
||||||
|
@ -824,11 +828,8 @@ ErrorOr<ImageFrameDescriptor> WebPImageDecoderPlugin::frame(size_t index)
|
||||||
return decode_webp_animation_frame(*m_context, index);
|
return decode_webp_animation_frame(*m_context, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_context->image_data.image_data_chunk.has_value())
|
|
||||||
return m_context->error("WebPImageDecoderPlugin: Did not find image data chunk");
|
|
||||||
|
|
||||||
if (m_context->state < WebPLoadingContext::State::BitmapDecoded) {
|
if (m_context->state < WebPLoadingContext::State::BitmapDecoded) {
|
||||||
auto bitmap = TRY(decode_webp_image_data(*m_context, m_context->image_data));
|
auto bitmap = TRY(decode_webp_image_data(*m_context, m_context->image_data.value()));
|
||||||
|
|
||||||
// Check that size in VP8X chunk matches dimensions in VP8 or VP8L chunk if both are present.
|
// Check that size in VP8X chunk matches dimensions in VP8 or VP8L chunk if both are present.
|
||||||
if (m_context->first_chunk->type == FourCC("VP8X")) {
|
if (m_context->first_chunk->type == FourCC("VP8X")) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue