Some apps seem to generate malformed images that are accepted
by most readers. We now only throw if malformed data would lead to
a write outside the chunky buffer.
When using the BMP encoding, ICO images are expected to contain a 1-bit
mask for transparency. Regardless an alpha channel is already included
in the image, the mask is always required. As stated here[1], the
mask is used to provide shadow around the image.
Unfortunately, it seems that some encoder do not include that second
transparency mask. So let's read that mask only if some data is still
remaining after decoding the image.
The test case has been generated by truncating the 64 last bytes
(originally dedicated to the mask) from the `serenity.ico` file and
changing the declared size of the image in the ICO header. The size
value is stored at the offset 0x0E in the file and I changed the value
from 0x0468 to 0x0428.
[1]: https://devblogs.microsoft.com/oldnewthing/20101021-00/?p=12483
This fixes an issue where GIF images without a global color table would
have the first segment incorrectly interpreted as color table data.
Makes many more screenshots appear on https://virtuallyfun.com/ :^)
TIFF files are made in a way that make them easily extendable and over
the years people have made sure to exploit that. In other words, it's
easy to find images with non-standard tags. Instead of returning an
error for that, let's skip them.
Note that we need to make sure to realign the reading head in the file.
The test case was originally a 10x10 checkerboard image with required
tags, and also the `DocumentName` tag. Then, I modified this tag in a
hexadecimal editor and replaced its id with 30 000 (0x3075 as a LE u16)
and the type with the same value as well. This is AFAIK, never used as
a custom TIFF tag, so this should remain an invalid tag id and type.
We currently assume that the K (black) channel uses the same sampling
as the Y channel already, so this already works as long as we don't
error out on it.
TIFF images with the PhotometricInterpretation tag set to RGBPalette are
based on indexed colors instead of explicitly describing the color for
each pixel. Let's add support for them.
The test case was generated with GIMP using the Indexed image mode after
adding an alpha layer. Not all decoders are able to open this image, but
GIMP can.
UnassociatedAlpha is the one used by GIMP when generating TIFF images
with transparency. Support is added for Grayscale and RGB images as it's
the two that we support right now but managing transparency should be
really straightforward for other types as well.
This compression (tag Compression=2) is not very popular on its own, but
a base to implement CCITT3 2D and CCITT4 compressions.
As the format has no real benefits, it is quite hard to find an app that
accepts tho encode that for you. So I used the following program that
calls `libtiff` directly:
```cpp
#include <vector>
#include <cstdlib>
#include <iostream>
#include <tiffio.h>
// An array containing 0 and 1 of length width * height.
extern std::vector<uint8_t> array;
int main() {
// From: https://stackoverflow.com/a/34257789
TIFF *image = TIFFOpen("input.tif", "w");
int const width = 400;
int const height = 300;
TIFFSetField(image, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField(image, TIFFTAG_IMAGELENGTH, height);
TIFFSetField(image, TIFFTAG_PHOTOMETRIC, 0);
TIFFSetField(image, TIFFTAG_COMPRESSION, COMPRESSION_CCITTRLE);
TIFFSetField(image, TIFFTAG_BITSPERSAMPLE, 1);
TIFFSetField(image, TIFFTAG_SAMPLESPERPIXEL, 1);
TIFFSetField(image, TIFFTAG_ROWSPERSTRIP, 1);
std::vector<uint8_t> scan_line(width / 8 + 8, 0);
int count = 0;
for (int i = 0; i < height; i++) {
std::fill(scan_line.begin(), scan_line.end(), 0);
for (int x = 0; x < width; ++x) {
uint8_t eight_pixels = scan_line.at(x / 8);
eight_pixels = eight_pixels << 1;
eight_pixels |= !array.at(i * width + x);
scan_line.at(x / 8) = eight_pixels;
}
int bytes = int(width / 8.0 + 0.5);
if (TIFFWriteScanline(image, scan_line.data(), i, bytes) != 1)
std::cerr << "Something went wrong\n";
}
TIFFClose(image);
}
```
This commit un-deprecates DeprecatedString, and repurposes it as a byte
string.
As the null state has already been removed, there are no other
particularly hairy blockers in repurposing this type as a byte string
(what it _really_ is).
This commit is auto-generated:
$ xs=$(ack -l \bDeprecatedString\b\|deprecated_string AK Userland \
Meta Ports Ladybird Tests Kernel)
$ perl -pie 's/\bDeprecatedString\b/ByteString/g;
s/deprecated_string/byte_string/g' $xs
$ clang-format --style=file -i \
$(git diff --name-only | grep \.cpp\|\.h)
$ gn format $(git ls-files '*.gn' '*.gni')
This change limits the amount of memory that is initially allocated for
the color table. This prevents an OOM condition if the file contains an
incorrect color table size.
Previously, the regression tests for OSS-Fuzz issues 62033 and 63296
used test case files directly from OSS-Fuzz. These files are invalid
in multiple ways because they have been generated by a fuzzer. This
commit replaces these files with ones that only expose the issue being
tested.
The first implementation of this property was just plain wrong. Looks
like this property isn't used a lot as I found the issue by reviewing
the code and not because of a specific image.
The test image is a 32x32 mosaic of alternating black and yellow pixels,
it was generated using this code:
Bitdepth 8
RCT 1
Width 32
Height 32
if W-WW-NW+NWW > -300
- Set -1000
- Set 900
This image is exactly the same as the previous one, excepted the RCT
transformation. It has been generated with:
Width 64
Height 64
RCT 29
Upsample 2
Bitdepth 10
if N > 300
- NE -6
- W 6
This image uses the modular encoding with a very simple prediction tree.
It also makes use of two features: upsampling (x2 factor) and a
non-standard bit depth (10 bits). The file has been generated on
https://jxl-art.surma.technology/ , with the following input:
Width 64
Height 64
Upsample 2
Bitdepth 10
if N > 300
- NE -6
- W 6
Reordering these calls allow us to ensure that all encoders are able to
return the size of the image before they are requested to decode the
whole bitmap.
This tests that we can successfully parse the "everything" TVG files,
which make use of every feature in TinyVG.
Test files taken from https://github.com/TinyVG/examples (MIT).
This just works at the moment after e19892a099, but if we ever do
the FIXME in ColorIndexingTransform::transform(), this test will
remind us to think of this case there too.
catdog-alert-13-alpha-used-false.webp is identical to
catdog-alert-13.web but with the byte at offset 0x2a changed from
0x10 to 0x00 -- that is, the bit in the VP8L header that stores
`is_alpha_used` is cleared.
See the commit message of e19892a099 for more information.
simple-vp8l-alpha-used-false.webp is a copy of simple-vp8l.webp,
with the byte at offset 0x18 changed from 0x10 to 0x00 -- that
is, the bit in the VP8L header that stores `is_alpha_used` is cleared.
We would already allocated a BGRx8888 instead of a BGRA8888 bitmap,
but keep actual alpha data in the `x` channel.
That lead to at least `image` still writing a PNG with an alpha channel.
So explicitly set the alpha channel to 0xff when is_alpha_used is false,
to make sure all consumers of decoded lossless webp data have behavior
consistent with other webp readers.
In practice, webp encoders usually don't write files that have
`is_alpha_used` set to false and then write actual alpha data to their
output. So this is rarely observable. However, for example for
lossy+ALPH webp files, the lossless webp used to store the ALPH channel
has `is_alpha_used` set to false and all channels but green are 0
(since the lossless green channel stores the alpha channel of a
lossy+ALPH webp). So if we dump such a bitmap to a standalone webp
file (e.g. with the temporary debugging code in fc3249a1ca),
then without this commit here, `image` would convert that webp to
a fully transparent webp, while other webp software would correctly
display the green image with opaque alpha.
Each secondary partition has an independent BooleanDecoder.
Their bitstreams interleave per macroblock row, that is the first
macroblock row is read from the first decoder, the second from the
second, ..., until it wraps around again.
All partitions share a single prediction state though: The second
macroblock row (which reads coefficients off the second decoder) is
predicted using the result of decoding the frist macroblock row (which
reads coefficients off the first decoder).
So if I understand things right, in theory the coefficient reading could
be parallelized, but prediction can't be. (IDCT can also be
parallelized, but that's true with just a single partition too.)
I created the test image by running
examples/cwebp -low_memory -partitions 3 -o foo.webp \
~/src/serenity/Tests/LibGfx/test-inputs/4.webp
using a cwebp hacked up as described in #19149. Since creating
multi-partition lossy webps requires hacking up `cwebp`, they're likely
very rare in practice. (But maybe other programs using the libwebp API
create them.)
Fixes#19149.
With this, webp lossy support is complete (*) :^)
And with that, webp support is complete: Lossless, lossy, lossy with
alpha, animated lossless, animated lossy, animated lossy with alpha all
work.
(*: Loop filtering isn't implemented yet, which has a minor visual
effect on the output. But it's only visible when carefully comparing
a webp decoded without loop filtering to the same decoded with it.
But it's technically a part of the spec that's still missing.
The upsampling of UV in the YUV->RGB code is also low-quality. This
produces somewhat visible banding in practice in some images (e.g.
in the fire breather's face in 5.webp), so we should probably improve
that at some point. Our JPG decoder has the same issue.)
I somehow added the wrong image here. 4.webp is the one described
by the comment in the test. Now test actually uses the image it
claims to use.
No behavior change.
The alpha channel of a lossy webp is always stored separately from
the (lossy) RGB data. Alpha is either compressed in a lossless webp
that stores just the alpha data, or it's stored completely
uncompressed. (But again, even if it's compressed, it's losslessly
compressed.)
This adds a test for uncompressed alpha, which I hadn't tested before.
It seems to work correctly, though :^)
I generated the test image by running:
~/Downloads/libwebp-1.3.0-mac-arm64/bin/cwebp \
-alpha_method 0 \
Tests/LibGfx/test-inputs/extended-lossless.webp \
-o Tests/LibGfx/test-inputs/extended-lossy-uncompressed-alpha.webp
This image covers two things that aren't covered by the existing
tests, and I found it useful for testing locally. The image's license
allows redistributing it, so add it as a test case.
WebP lossless files that use a color indexing transform with <= 16
colors use pixel bundling to pack 2, 4, or 8 pixels into a single pixel.
If the image's width doesn't happen to be an exact multiple of the
bundling factor, we need to:
1. Use ceil_div() instead of just dividing the width by the bundling
factor
2. Remember the original width and use it instead of computing
reduced width times bundling factor
This does these changes, and adds a simple test for it -- it at least
checks that the decoded images have the right size.
(I created these images myself in Photoshop, and used the same
technique as for Tests/LibGfx/test-inputs/catdog-alert-*.webp
to create images with a certain number of colors.)
For the test files, I opened Base/res/icons/catdog/alert.png in Adobe
Photoshop 2023, used Image->Mode->Index Color...->
Palette: Local (Perceptive) to reduce the number of colors to 13, 8, and
3 with transparency, and 2 without transparency, then converted it back
to Image->Mode->RGB Color (else it can't be saved as webp), then
File->Save a Copy... to save a WebP (mode lossless) for every palette
size.
The image is https://quakewiki.org/wiki/File:Qpalette.png in lossless
webp format with a color indexing transform.
I've created Qpalette.webp by running
examples/cwebp -z 0 ~/src/serenity/tmp.ppm -o Qpalette.webp
built at libwebp webmproject/libwebp@0825faa4c1 (without
png support, so I first ran
Build/lagom/image ~/Downloads/Qpalette.png -o tmp.ppm
to convert it from png to a format my cwebp binary could read).
This file also happens to explicitly set max_symbol, so it serves
as a test for that code path as well.
Introduced in 2c98eff, support for non-interleaved scans was not working
for frames with a number of MCU per line or column that is odd. Indeed,
the decoder assumed that they have scans that include a fabricated MCU
like scans with multiple components.
This patch makes the decoder handle images with a number of MCU per line
or column that is odd. To do so, as in the current decoder state we do
not know if components are interleaved at allocation time, we skip over
falsely-created macroblocks when filling them. As stated in 2c98eff,
this is probably not a good solution and a whole refactor will be
welcome.
It also comes with a test that open a square image with a side of 600px,
meaning 75 MCUs.
You can generate one by using `cjpeg` with the -scan argument.
This image has been generated with the following scan file:
0 1 2: 0 0 0 0;
0: 1 9 0 0;
2: 1 63 0 0 ;
1: 1 63 0 0 ;
0: 10 63 0 0;
This type of image isn't common, and you can probably only find one by
generating it yourself. It can be done using `cjpeg` with the -scan
argument.
This image has been generated with the following scan file:
0: 0 63 0 0;
1: 0 63 0 0;
2: 0 63 0 0;
Nobody made use of the ErrorOr return value and it just added more
chance of confusion, since it was not clear if failing to sniff an
image should return an error or false. The answer was false, if you
returned Error you'd crash the ImageDecoder.
Turns out extended-lossless-animated.webp did have a loop count of 0.
So I opened it in Hex Fiend and changed the byte at position 42
(which is the first byte of the little-endian u16 storing the loop
count) to 0x2A, so that the test can compare the loop count to something
not 0.
The patch also contains modifications on several classes, functions or
files that are related to the `JPGLoader`.
Renaming include:
- JPGLoader{.h, .cpp}
- JPGImageDecoderPlugin
- JPGLoadingContext
- JPG_DEBUG
- decode_jpg
- FuzzJPGLoader.cpp
- Few string literals or texts
Rather than reading files out of /res, put them in a subfolder of
Tests/LibGfx/ and pick the path based on AK_OS_SERENITY.
That way, the tests can also pass when run under lagom.
(I just `cp`d all the files that the test previously read from
random places into Tests/LibGfx/test-inputs.)
- Use MUST() instead of checking plugin_decoder_or_error.is_error()
- Use MappedFile::bytes()
- Don't use EXPECT_EQ when comparing to fixed bools
No intended behavior change.
We should expect the sniffing method and the initialize method to fail
because this test case is testing that the ICO image decoder should not
decode random data within it.
When trying to figure out the correct implementation, we now have a very
strong distinction on plugins that are well suited for sniffing, and
plugins that need a MIME type to be chosen.
Instead of having multiple calls to non-static virtual sniff methods for
each Image decoding plugin, we have 2 static methods for each
implementation:
1. The sniff method, which in contrast to the old method, gets a
ReadonlyBytes parameter and ensures we can figure out the result
with zero heap allocations for most implementations.
2. The create method, which just creates a new instance so we don't
expose the constructor to everyone anymore.
In addition to that, we have a new virtual method called initialize,
which has a per-implementation initialization pattern to actually ensure
each implementation can construct a decoder object, and then have a
correct context being applied to it for the actual decoding.
These instances were detected by searching for files that include
stdlib.h, but don't match the regex:
\\b(_abort|abort|abs|aligned_alloc|arc4random|arc4random_buf|arc4random_
uniform|atexit|atof|atoi|atol|atoll|bsearch|calloc|clearenv|div|div_t|ex
it|_Exit|EXIT_FAILURE|EXIT_SUCCESS|free|getenv|getprogname|grantpt|labs|
ldiv|ldiv_t|llabs|lldiv|lldiv_t|malloc|malloc_good_size|malloc_size|mble
n|mbstowcs|mbtowc|mkdtemp|mkstemp|mkstemps|mktemp|posix_memalign|posix_o
penpt|ptsname|ptsname_r|putenv|qsort|qsort_r|rand|RAND_MAX|random|reallo
c|realpath|secure_getenv|serenity_dump_malloc_stats|serenity_setenv|sete
nv|setprogname|srand|srandom|strtod|strtof|strtol|strtold|strtoll|strtou
l|strtoull|system|unlockpt|unsetenv|wcstombs|wctomb)\\b
(Without the linebreaks.)
This regex is pessimistic, so there might be more files that don't
actually use anything from the stdlib.
In theory, one might use LibCPP to detect things like this
automatically, but let's do this one step after another.
Adapt BMPImageDecoderPlugin to support BMP images included in ICOns.
ICOImageDecoderPlugin now uses BMPImageDecoderPlugin to decode all
BMP images instead of it's own ad-hoc decoder which only supported
32 bpp BMPs.
We have a new, improved string type coming up in AK (OOM aware, no null
state), and while it's going to use UTF-8, the name UTF8String is a
mouthful - so let's free up the String name by renaming the existing
class.
Making the old one have an annoying name will hopefully also help with
quick adoption :^)
Each of these strings would previously rely on StringView's char const*
constructor overload, which would call __builtin_strlen on the string.
Since we now have operator ""sv, we can replace these with much simpler
versions. This opens the door to being able to remove
StringView(char const*).
No functional changes.
We were passing raw Gfx::Bitmap objects into the various image decoders
instead of encoded image data. This made all of them fail, but the test
expectations were set up in a way that aligned with this outcome.
With this patch, we now test the codecs for real. Except ICO, since we
don't have an ICO file handy. That's a FIXME.
With the goal of centralizing all tests in the system, this is a
first step to establish a Tests sub-tree. It will contain all of
the unit tests and test harnesses for the various components in the
system.