This implements conversion from profile connection space to the
device-dependent color for matrix-based profiles.
It only does the inverse color transform but does not yet do the
inverse tone reproduction curve transform -- i.e. it doesn't
implement many cases (LUT transforms), and it does the one thing
it does implement incorrectly. But to vindicate the commit a bit,
it also does the incorrect thing very inefficiently.
This can be used to convert a profile-dependent color to the L*a*b*
color space.
(I'd like to use this to implement the DeltaE (CIE 2000) algorithm,
which is a metric for how similar two colors are perceived.
(And I'd like to use that to evaluate color conversion roundtrip
quality, once I've implemented full conversions.)
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.
I drew the two webp files in Photoshop and saved them using the
"Save a Copy..." dialog, with ICC profile and all other boxes checked.
(I also tried saving with all the boxes unchecked, but it still wrote an
extended webp instead of a basic file.)
The lossless file exposed a bug: I didn't handle chunk padding
correctly before this patch.
The test verifies that loading an icc file and serializing it
again produces exactly the same output as the input. That's not
always the case, but often. It requires the input file either
not having any padding or using null bytes as padding, it
requires the input file putting tag data in the order the
tag data is referenced in in the tag table, and it requires the
input file only using known tag types (which at the moment
means it only works for v4 profiles, but that part will change
in the future).
The new file p3-v4.icc was extracted from a jpeg taken by an
iPhone Mini.
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
icc-v4.jpg is Meta/Websites/serenityos.org/happy/3rd/bgianf.jpg.
There are a whole bunch of jpgs with v4 color profiles and I just picked
one fairly arbitrarily. It looks like a fairly standard v4 matrix
profile that in this form is also present in many jpgs taken by mobile
phone cameras. It uses parametric curves.
icc-v2.png is based on ./Documentation/WebServer_localhost.jpg since
that is the only image in the repo with a v2 color profile. It also has
all kinds of interesting and somewhat exotic tags, such as an 'dscm' (an
Apple extension to have a description of type 'mluc', since normal
'desc' is required ot have type 'desc' in v2 files -- in v4, 'desc' has
type 'mluc') tag of type 'mluc' that actually contains data in several
languages and that exercises the non-BMP UTF-16BE decoder. It's however
still also a fairly standard v2 matrix profile, which uses 'curv'
instead of 'para' for its curves ('para' is v4-only).
I converted that jpeg file to png, and cropped most of the image
data to save on file size by running:
sips -s format png --cropToHeightWidth 21 42 in.jpg --out out.png
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.)
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.
- 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.