Commit graph

337 commits

Author SHA1 Message Date
Aliaksandr Kalenik
8c06273dbf LibGfx: Tolerate missing optional WebP ICC chunks
Some WebP files advertise ICC metadata in their VP8X feature flags even
though the ICCP chunk is absent or malformed. We accepted the bitstream
header and could decode the pixels, but the follow-up mux metadata
lookup failed during header decoding and made sniffing reject the image
entirely.

Treat mux and ICC metadata extraction as best-effort after libwebp
accepts the header. Images without readable optional ICC metadata now
decode normally and simply report no ICC profile.
2026-06-22 17:15:21 +02:00
sideshowbarker
8e2eee7654 LibGfx: Reject undersized backing storage when creating bitmaps
Problem: A borked ImageDecoder could send a BitmapSequence over IPC with
metadata for a (large) bitmap while shipping a too-small backing buffer.
Decoding it produced a Gfx::Bitmap that reported the (large) geometry
but pointed at the too-small buffer — making the first write go OOB.

Cause: BitmapSequence decode reads size_in_bytes and the bitmap geometry
as independent fields, and only checked if size_in_bytes matched the
transferred buffer size — never that either is consistent with the
geometry. The single-frame fast path then handed the buffer to
Bitmap::create_with_anonymous_buffer with no verification.

Fix: Make the two bitmap factories that take externally-provided storage
enforce that it covers the geometry. create_with_anonymous_buffer now
fails with buffers smaller than the minimum expected size_in_bytes — and
create_with_raw_data similarly rejects data too small for the geometry.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/10036
2026-06-19 13:43:07 +02:00
sideshowbarker
9d64cd8c9b LibGfx: Reject the Invalid bitmap format from IPC decoders
Problem: A borked process sending a Gfx::Bitmap (inside BitmapSequence)
or a Gfx::ShareableBitmap over IPC could set BitmapFormat::Invalid as
the format field. The receiving process then aborted while decoding the
message — an IPC-reachable crash.

Cause: The helper that both decoders use for validating the format read
off the wire accepts BitmapFormat::Invalid. The decoders go on to build
a bitmap with that format. But that triggers an assert — because
minimum_pitch only knows the four real pixel formats.

Fix: Drop BitmapFormat::Invalid from is_valid_bitmap_format. It’s an
indicator of an absent/unknown format, never one a real bitmap can have.
And any real bitmap is never encoded with it. So, both BitmapSequence
and ShareableBitmap decode now return a clean decode error for it.
2026-06-19 10:33:36 +02:00
sideshowbarker
602e7fe2bd LibGfx: Reject a BMP V5 ICC profile offset that points out of bounds
Problem: A BMP V5 image whose embedded ICC profile offset points past
the end of the file triggers an OOB read.

Cause: The bounds check summed the profile offset, the file-header size,
and the profile size in 32-bit arithmetic. So, a large offset (e.g.
0xfffffff0) wraps the sum back into range and passes the check. The
decoder then returns a span pointing far past the end of the file.

Fix: Compute the sum in 64 bits — so an out-of-bounds offset can no
longer wrap, and the profile’s rejected.

Fixes: https://github.com/LadybirdBrowser/ladybird/issues/9967
2026-06-15 20:19:33 +09:00
sideshowbarker
9bde8a5c88 LibGfx: Avoid undefined behavior on a BMP with an INT_MIN height
Problem: Decoding a BMP whose height is INT_MIN triggered a UBSan error.
A top-down BMP legitimately uses a negative height — and unlike width,
it’s not rejected, so it can be INT_MIN.

Cause: decode_bmp_pixel_data() took the magnitude of the i32 width and
height with abs(). Negating INT_MIN is undefined behavior.

Fix: Widen to i64 before taking the absolute value — so the magnitude of
INT_MIN is representable. The resulting out-of-range dimension is still
rejected by Bitmap::create — so only the undefined behavior changes.

Fixes: https://github.com/LadybirdBrowser/ladybird/issues/9994
2026-06-15 09:44:27 +02:00
sideshowbarker
20b1129352 LibGfx: Fix heap overflow applying Exif transpose to non-square images
Problem: Decoding a non-square image whose Exif orientation is 5
(transpose) writes one pixel past the end of the destination bitmap —
an ASan heap-buffer-overflow in ExifOrientedBitmap::set_pixel.

Cause: oriented_position() mapped orientation 5 by composing the
“flip-horizontally” and “rotate-90-clockwise” helpers. Each helper
mirrors using the source width. But after the rotate, the point is
already in the transposed coordinate space — where the relevant
dimension is the source height. For a non-square image, that composition
produces x-coordinates past the destination width. Only square images
happened to stay in bounds. The destination bitmap is the transposed
size — so the out-of-range column wrote past its allocation.

Fix: Map orientation 5 directly as a transpose across the main diagonal:
source (x, y) to destination (y, x).

Fixes https://github.com/LadybirdBrowser/ladybird/issues/10102
2026-06-15 09:31:12 +02:00
Tim Ledbetter
3cddcc8461 LibGfx+LibWeb: Remove TIFF image decoding
This is no longer widely supported by other engines.
2026-06-12 22:37:49 +02:00
Tim Ledbetter
3424c08b27 LibImageDecoders: Decode GIFs using wuffs
Replace the homegrown GIF parser and LZW decompressor with the wuffs
GIF decoder, which is memory-safe by construction and already used in
other engines via Skia.

One behavior change is that `loop_count()` now reports the correct
value, since the raw value stored in the file does not include the
first frane and should be incremented by 1 to be compatible with what
callers expect.
2026-06-10 20:29:11 +02:00
Andreas Kling
37e7526367 LibGfx: Split image decoders into a separate library
Move the image loader sources and decoder-only dependencies from LibGfx
into a new LibImageDecoders library. This keeps the APNG-enabled PNG
loader out of processes that only need core graphics and image writers.

Link the ImageDecoder service, direct decoder tests, fuzzers, test-web,
and the image utility against LibImageDecoders where they still decode
images in-process.
2026-06-10 11:55:48 +02:00
Jelle Raaijmakers
49cdf582ff LibGfx: Implement WCAG's contrast ratio in Color
We're going to implement the `contrast-color()` CSS function in the next
commit, and the spec advises to use the contrast ratio definition as
described in WCAG 2. So let's replace our `Color::contrast_ratio()`
implementation by that.

Co-authored-by: InvalidUsernameException
 <InvalidUsernameException@users.noreply.github.com>
2026-06-09 17:23:26 +02:00
Jelle Raaijmakers
d42b0f72ae LibGfx: Remove unused Color::to_grayscale()
Co-authored-by: InvalidUsernameException
 <InvalidUsernameException@users.noreply.github.com>
2026-06-09 17:23:26 +02:00
sideshowbarker
086e860b48 LibGfx: Fix misaligned u32 store in BMP RLE24 decompression
Problem: Decoding a BI_RLE24 BMP binds a u32 reference to a misaligned
address, gets flagged by UBSan while decompressing the run-length data.

Cause: The decompressed RLE24 buffer holds 24-bit pixels at a 3-byte
stride (decode_bmp_pixel_data reads it back with LE read_u24) — but each
pixel was getting stored with a 4-byte write at 3-byte-strided offsets.

Fix: Store the 24-bit value as three LE bytes — so the write
matches the stride and is always aligned. Size the buffer to the
real 3-bytes-per-pixel total, and bound-check the 3-byte write.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/9958
2026-06-08 13:43:21 +02:00
Andreas Kling
6f616ae9a9 LibGfx: Remove TinyVG image decoding
Remove the TinyVG decoder now that the Qt chrome no longer depends on
TVG resources. Drop the decoder registration, MIME and supported image
type entries, fuzzer target, decoder tests, and TinyVG test inputs.
2026-05-31 19:20:59 +02:00
Timothy Flynn
b7c4cd511f LibGfx: Select largest ICO image before bpp tie-break
When choosing the best image from an ICO file, prefer the entry with the
largest pixel area first, and only use bits-per-pixel as a tie-breaker.
This had regressed in commit b10fe7c136.

This fixes ICO files like Discord's favicon, where 16x16, 32x32, 48x48,
and 256x256 entries all advertise the same bpp. We were previously just
choosing whichever we saw first, which happened to be the 16x16 icon.
2026-05-28 06:32:00 +02:00
sideshowbarker
c701781d11 LibGfx: Fix PNG decoder leaks from libpng error longjmp
Problem: Runs of the Linux Sanitizers intermittently fail with
LeakSanitizer leaks of ~45 KB across 21 allocations from
PNGLoadingContext::read_frames() — triggered when the test suite
decodes malformed PNGs.

Cause: When libpng hits a corrupted IDAT chunk inside png_read_image(),
it longjmps back to the setjmp landing pad in
PNGLoadingContext::read_all_frames(). longjmp unwinds the stack without
running C++ destructors — so the stack-locals in read_frames
(Vector<u8*> row_pointers, the in-flight Bitmap inside decode_frame, and
the APNG branch’s output buffer and Painter) leak their heap storage.

Fix: Promote those stack-locals to members of PNGLoadingContext (which
is heap-allocated and outlives the setjmp scope) — so their storage is
reachable by RAII when the context is destroyed. Clear them in the
setjmp error handler too — so memory is released promptly on the error
path, rather than waiting until ~PNGLoadingContext().
2026-05-26 10:05:08 +02:00
Tim Ledbetter
f831a99067 Tests/LibGfx: Remove stale Skia include from CMakeLists 2026-05-05 23:49:11 +02:00
Aliaksandr Kalenik
00181dcf50 LibGfx: Move bitmap export out of ImmutableBitmap
Bitmap export is pixel conversion, not an ImmutableBitmap-specific
operation. It needs a source bitmap and color space, and callers should
provide those explicitly instead of routing through the immutable
snapshot abstraction.

Move the export formats, flags, result type, and conversion
implementation into a new BitmapExport helper. Keep
BitmapExportResult.h as a forwarding header for existing includes
while making BitmapExport.h the new home for the public API.

Update WebGL and the LibGfx export test to use the standalone helper
directly.
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
ee4f9ef82c LibGfx: Preserve native YUV samples for CPU conversion
This is a small prerequisite for moving decoded YUV frames from
ImmutableBitmap into Media::VideoFrame. Once the frame owns the planes
directly, CPU consumers and the Skia upload path will share the same
YUVData object.

Those consumers need different representations. Skia expects high bit
depth YUVA pixmaps to be full-range 16-bit samples, while CPU conversion
paths must see the decoder-native samples and the original bit depth.

Keep YUVData in decoder-native form, and make the Skia expansion a
temporary copy owned by SkYUVAPixmaps. Add a regression test that checks
10-bit pixmaps are expanded for Skia without mutating the source planes
or changing YUVData::to_bitmap() output.
2026-05-05 14:39:17 -05:00
aplefull
7e492ff554 LibGfx: Set maximum bitmap dimensions to 65535
This matches Firefox's behavior which allows up to 65535 for both width
and height. The total area is still constrained by the pitch overflow
check. It was changed to int32_t, so now it limits the maximum area to
~23kx23k, which is still less than the 65535x65535 area that Firefox
allows, but we would eat too much memory if we allowed that.
2026-05-05 20:13:43 +02:00
Jelle Raaijmakers
73658137b6 Documentation+Tests: Optimize PNGs using optipng
The wide gamut test file went unused, so it was deleted.
2026-02-23 13:45:04 +01:00
InvalidUsernameException
1f8a42c367 LibGfx: Add a test for bitmap export
The verified pixel output in this test just reflects currently observed
behavior, I have not verified that all cases output the correct data wrt
what the spec expects.
2025-11-28 18:32:48 +01:00
InvalidUsernameException
7c315ef67f Everywhere: Unify naming of RGBA-like colors
The `Bitmap` type was referring to to its internal pixel format by a
name that represents the order of the color components as they are layed
out in memory. Contrary, the `Color` type was using a naming that where
the name represents the order of the components from most to least
significant byte when viewed as a unsigned 32bit integer. This is
confusing as you have to keep remembering which mental model to use
depending on which code you work with.

To unify the two, the naming of RGBA-like colors in the `Color` type has
been adjusted to match the one from the Bitmap type. This seems to be
generally in line with how web APIs think about these types:
* `ImageData.pixelFormat` can be `rgba-8unorm` backed by a
  `Uint8ClamedArray`, but there is no pixel format backed by a 32bit
  unsigned type.
* WebGL can use format `RGBA` with type `UNSIGNED_BYTE`, but there is no
  such format with type `UNSIGNED_INT`.

Additionally, it appears that other browsers and browser-adjacent
libraries also think similarly about these types:
* Firefox:
  https://github.com/mozilla-firefox/firefox/blob/main/gfx/2d/Types.h
* WebKit:
  https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/PixelFormat.h
* Skia:
  https://chromium.googlesource.com/skia/+/refs/heads/main/include/core/SkColorType.h

This has the not so nice side effect that APIs that interact with these
types through 32bit unsigned integers now have the component order
inverted due to little-endian byte order. E.g. specifying a color as hex
constant needs to be done as `0xAABBGGRR` if it is to be treated as
RGBA8888.

We could alleviate this by providing endian-independent APIs to callers.
But I suspect long-term we might want to think differently about bitmap
data anyway, e.g. to better support HDR in the future. However, such
changes would be more involved than just unifying the naming as done
here. So I considered that out of scope for now.
2025-11-28 18:32:48 +01:00
Aliaksandr Kalenik
ed921b66f5 LibGfx+LibWeb: Delete unused Line class and Rect methods 2025-11-04 23:16:02 +01:00
aplefull
5df216218b LibGfx: Correctly determine when to invert CMYK
We should invert CMYK data only if color space is JCS_CMYK and either
there is no Adobe marker, or the Adobe transform is 0. Transform 2
indicates YCCK data, which we should not invert.
2025-10-15 21:50:16 +02:00
aplefull
b3bdb202f8 LibGfx: Allow decoding of GIFs with empty LZW data
The decoder was requiring GIF files to be at least 32 bytes, but the
actual minimum for a valid GIF is only 26 bytes:
- 6 bytes for the header
- 7 bytes for the Logical Screen Descriptor
- 10 bytes for the Image Descriptor
- 2 bytes for the LZW minimum code size and block terminator
- 1 byte for the GIF trailer

This change allows us to load minimal 1x1 GIFs with empty LZW data.
They are commonly used on the web as transparent placeholders with
minimal file size.
2025-10-02 11:04:35 +02:00
Viktor Szépe
1c01e183b7 Everywhere: Fix even more typos 2025-08-27 08:48:01 +02:00
Jelle Raaijmakers
62cf33b98e LibGfx: Remove WebP animation writer and utility
This went unused.
2025-08-05 11:30:20 +02:00
ayeteadoe
ccb87b4cb4 Tests/LibGfx: Enable file based tests on Windows
These actually were always working since we first enabling LibGfx
on Windows. I was just running them outside of the ctest context
and therefore had the wrong working directory so the test-inputs folder
could not be found
2025-07-11 10:54:37 +02:00
ayeteadoe
25f5936dee CMake: Rename serenity_* helper functions/macros to ladybird_* 2025-07-03 23:19:41 +02:00
ayeteadoe
e28c956629 Tests/LibGfx: Enable non file input tests in Windows CI
All the file-based tests left out build, but they all fail at run time
with the error "No such file or directory" or a Core::File-based
assertion failure for the Benchmark test
2025-06-26 19:35:14 -06:00
Jelle Raaijmakers
2687246808 LibGfx: Use NonnullRefPtr<Bitmap> for frame descriptors
This makes it a bit easier to reason about where bitmaps should be
available.
2025-06-25 22:54:48 +12:00
Luke Wilde
ffae0d8b2d LibGfx/AVIF: Always reduce decoding output to a bit depth of 8
Gfx::Bitmap only supports a bit depth of 8, therefore we refused to
load AVIF images which didn't have this bit depth.

However, we can tell the libavif decoder to reduce the output depth by
setting avifRGBImage.depth to 8. This allows us to support any input
depth.

Makes images load on https://www.ikea.com/ which uses Cloudflare Images
to re-encode their images to 16-bit AVIF.
2025-06-18 14:27:24 +02:00
Timothy Flynn
7280ed6312 Meta: Enforce newlines around namespaces
This has come up several times during code review, so let's just enforce
it using a new clang-format 20 option.
2025-05-14 02:01:59 -06:00
aplefull
7da2339c89 LibGfx: Properly skip IDAT chunks without fcTL in APNG files
In the previous fix, we were still drawing IDAT data to the reference
frame even when no fcTL was present. This would cause rendering issues
when subsequent frames use APNG_BLEND_OP_OVER blending mode, as they
would composite over the incorrect reference frame. This commit adds a
simple check to properly skip any frame without an fcTL chunk.
2025-05-09 21:45:29 +02:00
aplefull
e5944a4d9e LibGfx: Correctly handle OS/2 BMPs with 3-byte color entries
We now properly handle OS/2 format BMPs that use 3 bytes per color
entry instead of 4. While OS/2 2.x officially specified 4 bytes per
color, some tools still produce files with 3-byte entries. We can
identify such files by checking the available color table space.
2025-05-09 21:45:29 +02:00
aplefull
4d8717fa3a LibGfx: Add .cur file support to ICO loader
This extends ICO loader to support Windows cursor files. There is no
point in creating a separate loader for this, as the ICO format is
very similar to the CUR format. The only differences are bytes used to
identify the file and a presence of a hotspot in the CUR header.
2025-05-09 21:45:29 +02:00
aplefull
48ca895f5f LibGfx: Properly skip color masks in BMP V2+ headers
Color masks should only be used when the compression type is either
BITFIELDS or ALPHABITFIELDS. They were always read before and produced
corrupted images when there was random data in the mask fields.
2025-05-09 21:45:29 +02:00
aplefull
c908c14aac LibGfx: Clamp BMP color palette to 1024
Other browsers don't think that BMP files with more than 1024 colors are
invalid. They clamp the palette instead, and now we do the same. This
allows us to load more BMPs.
2025-05-09 21:45:29 +02:00
Sam Atkins
6b762331df LibGfx: Rename WOFF[2]::try_load_from_externally_owned_memory()
Typeface::try_load_from_externally_owned_memory() relies on that
external owner keeping the memory around. However, neither WOFF nor
WOFF2 do so - they both create separate ByteBuffers to hold the TTF
data. So, rename them to make it clearer that they don't have any
requirements on the byte owner.
2025-05-03 12:01:43 +01:00
aplefull
71a4e18bf8 LibGfx: Only include frames with fcTL chunks in the animation
Before this change, IDAT data was mistakenly always included in the
animation. Now we only include frames with explicit fcTL chunks.

As per the PNG spec (third edition):
"The static image may be included as the first frame of the animation
by the presence of a single fcTL chunk before IDAT. Otherwise, the
static image is not part of the animation."

We also fall back to the IDAT data when APNG has acTL but no fcTL
chunks. Test image is 062.png from fDAT-inherits-cICP.html from WPT.
2025-05-01 10:30:00 +02:00
aplefull
e0ceb66580 LibGfx: Fix incorrect colors in ICO-embedded BMPs 2025-04-24 13:46:54 +01:00
InvalidUsernameException
578a3af87d LibGfx: Support missing pixel formats in get_pixel()
Bitmap::get_pixel() was only handling two out of the four possible pixel
formats, asserting when called with the other two. The asserting code
path was triggered when loading JPEG XL images, causing crashes on pages
like https://jpegxl.info/resources/jpeg-xl-test-page or
https://html5test.co/.
2025-04-23 09:26:45 +02:00
aplefull
57d0c563e0 LibGfx: Fix handling of partially corrupt GIFs
GIF loader was completely failing when encountering errors with
frame descriptors or individual frames, even when some frames were
successfully loaded. Now we attempt to decode at least some frames
and fail only when no frames can be decoded at all.
2025-03-20 16:12:53 +01:00
Jelle Raaijmakers
7eb4f3da37 LibGfx: Add Rect::unite()
The existing `::unite_horizontally()` and `::unite_vertically()` tests
did not properly test the edge cases where left/top in the Rect were
updated, so they get re-arranged a bit.
2025-01-23 09:33:10 +01:00
Lucas CHOLLET
2174e5dfcc LibGfx: Remove ICC::Profile and friends
This is not really used anymore since the fork.
2024-12-16 07:39:49 +01:00
Aaron Van Doren
7824721107 LibGfx: Verify Rect and Quad are consistent in boundary point inclusions 2024-12-15 23:27:42 +01:00
Aaron Van Doren
5c4e162ba9 LibGfx: Implement even-odd method for Quad::contains() and add tests 2024-12-15 23:27:42 +01:00
Pavel Shliak
ede0dbafc6 AK: Remove Statistics.h
This also removes MedianCut and GIFWriter
2024-12-05 16:53:28 +01:00
Psychpsyo
b22341fc77 LibGfx: Support AVIF images with missing pixi property 2024-11-23 03:57:54 +00:00
Borna Lang
201648485f LibGfx: Reimplement from_hsla
- Hue now wraps properly when negative or larger than 360
- The hsl to rgb conversion now closely mirrors the code example from
  the spec.

This fixes a number of WPT tests in
/css/css-color/parsing/color-computed-hsl.html
2024-11-22 12:10:30 +01:00