This also slightly refactors things to share more implementation with
the SVG linear gradients, and improve accuracy (which fixes some banding
issues).
LutAToBTagData::from_bytes() and LutBToATagData::from_bytes() already
reject curves for which this isn't true with an error.
Ensure potential future callers of the constructors get it right too.
This allows converting to a color space that uses a non-parametric
curve, for example:
Build/lagom/image -o foo.png \
--convert-to-color-profile .../profiles/sRGB-v2-micro.icc \
input.jpg
...where profiles/sRGB-v2-micro.icc is from
https://github.com/saucecontrol/Compact-ICC-Profiles/
(Parametric curves are new in ICC v4, which means all v2 profiles
use point curves.)
For now, only for color spaces that are supported by Profile::to_pcs()
and Profile::from_pcs(), which currently means that all matrix profiles
(but not LUT profiles) in the source color space work, and that
matrix profiles with parametric curves in the destination color
space work.
This adds Profile::convert_image(Bitmap, source_profile), and
adds a `--convert-to-color-profile file.icc` flag to `image`.
It only takes a file path, so to use it with the built-in
sRGB profile, you have to write it to a file first:
% Build/lagom/icc -n sRGB --reencode-to serenity-sRGB.icc
`image` by default writes the source image's color profile
to the output image, and most image viewers display images
looking at the profile.
For example, take `Seven_Coloured_Pencils_(rg-switch_sRGB).jpg`
from https://commons.wikimedia.org/wiki/User:Colin/BrowserTest.
It looks normal in image viewers because they apply the unusual
profile embedded in the profile. But if you run
% Build/lagom/image -o huh.png --strip-color-profile \
'Seven_Coloured_Pencils_(rg-switch_sRGB).jpeg'
and then look at huh.png, you can see how the image's colors
look like when interpreted as sRGB (which is the color space
PNG data is in if the PNG doesn't store an embedded profile).
If you now run
% Build/lagom/image -o wow.png \
--convert-to-color-profile serenity-sRGB.icc --strip-color-profile \
'Seven_Coloured_Pencils_(rg-switch_sRGB).jpeg'
this will convert that image to sRGB, but then not write
the profile to the output image (verify with `Build/lagom/icc wow.png`).
It will look correct in image viewers, since they display PNGs without
an embedded color profile as sRGB.
(This works because 'Seven_Coloured_Pencils_(rg-switch_sRGB).jpeg'
contains a matrix profile, and Serenity's built-in sRGB profile
uses a matrix profile with a parametric curve.)
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.
Previously, Frames could set both these properties along with a
thickness to confusing effect: Most shapes of the same shadowing only
differentiated at a thickness >= 2, and some not at all. This led
to a lot of creative but ultimately superfluous choices in the code.
Instead let's streamline our options, automate thickness, and get
the right look without so much guesswork.
Plain shadowing has been consolidated into a single Plain style,
and 0 thickness can be had by setting style to NoFrame.
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.)
Only implemented for matrix profiles so far.
This API won't be fast enough to color manage images, but let's
get something working before getting something fast.
No real behavior change. (The two globals are now both initialized
at first use instead of before main(), but that should have no
observable effect. The motivation is readability.)
Negative accumulation on the right-hand side of the accumulation bitmap
would wrap around to the left, causing a negative start for certain
lines which resulted in horizontal streaks of lower alpha values.
Prevent this by not writing out of bounds :^)
Fixes#18394
An oval approximated by quadratic bezier curves ended up with 2048 line
segments when rasterized to a bitmap of around 35 by 35 pixels, which
seems a bit much. :^)
By increasing the tolerance by an order of magnitude, that same oval is
now split up into 512 line segments, which is still more than enough for
a high quality render.
read_webp_first_chunk() sensibly assumes that if decode_webp_header()
succeeds, there are at least sizeof(WebPFileHeader) bytes available.
But if the file size in the header was less than the size of the header,
decode_webp_header() would truncate the data to less than that and
happily report success. Now it no longer does that.
Found by clusterfuzz:
https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=57843&sort=-opened&can=1&q=proj%3Aserenity
No behavior change.
(Well, technically mix() uses the other simple implementation of lerp
and the two have slightly different behavior if the arguments are of
very different magnitude and in various corner cases. But in practice,
for ICC profiles, it shouldn't matter. For a few profiles I tested, it
didn't have a measurable effect.)