These are really only useful for testing, so don't mention them
in `icc --help` output for `-n`.
They're supposed to behave identically to the built-in XYZ and
LAB profiles (but not the LAB_mft2 profile, which uses a slightly
different internal encoding, see #26462. The new XYZ profiles added
here behave like the XYZ profile described there, but the new LAB
profiles do not behave like the LAB_mft2 profile described there,
but like the LAB (no _mft2) profile:
% echo 'pcslab(100, 127, -128)' | \
icc -n LAB_mABmBA_u8_clut --stdin-u8-from-pcs | \
icc -n LAB --stdin-u8-to-pcs
pcslab(100, 127, -128)
% echo 'pcslab(100, 127, -128)' | \
icc -n LAB_mABmBA_u16_clut --stdin-u8-from-pcs | \
icc -n LAB --stdin-u8-to-pcs
pcslab(100, 127, -128)
% echo 'pcslab(100, 127, -128)' | \
icc -n LAB_mABmBA_no_clut --stdin-u8-from-pcs | \
icc -n LAB --stdin-u8-to-pcs
pcslab(100, 127, -128)
The profiles without a CLUT already round-trip fine, so add
tests for those.
This is even weirder than the XYZ mft2 profile added in an
earlier commit in this PR:
mft2 uses a "legacy 16-bit PCSLAB encoding" instead of the "normal"
16-bit ICC PCSLAB encoding, and this being an identity mapping leaks
this into the outputs. We again linearly map this internal 16-bit
encoding to 8 bits.
This legacy encoding maps 0 to 0.0 and FF00 to 1.0 for L
(and FFFF to a bit more than 1.0), and 0 to -128.0 and FF00 to
127.0 for a* and b* (and FFFF to a bit more than 127.0).
This means this produces different values than our built-in LAB mft1
profile:
% echo 'pcslab(100, 127, -128)' | \
icc -n LAB_mft2 --stdin-u8-from-pcs | \
icc -n LAB --stdin-u8-to-pcs
pcslab(99.60784, 126, -128)
Staying in LAB for both steps is exact, of course:
% echo 'pcslab(100, 127, -128)' | \
icc -n LAB --stdin-u8-from-pcs | \
icc -n LAB --stdin-u8-to-pcs
pcslab(100, 127, -128)
Staying in LAB_mft2 for both steps is also exact, up to u8
resolution (100 and 127 don't exactly map to an u8 value with
our "16-bit legacy PCSLAB encoding mapped to u8" encoding):
% echo 'pcslab(100, 127, -128)' | \
icc -n LAB_mft2 --stdin-u8-from-pcs | \
icc -n LAB_mft2 --stdin-u8-to-pcs
pcslab(99.99693, 126.99219, -128)
This is just because the same PCSLAB value is encoded slightly
differently in the mft1 and mft2 LAB encodings:
% echo 'pcslab(100, 127, -128)' | \
icc -n LAB --stdin-u8-from-pcs
255, 255, 0
% echo 'pcslab(100, 127, -128)' | \
icc -n LAB_mft2 --stdin-u8-from-pcs
254, 254, 0
(There's no XYZ mft1 encoding, so at least we don't have mft1 and mft2
profiles that have different output.)
Use this profile to add a roundtrip test for Lut16TagData when PCS
is PCSLAB.
This profile identity-maps PCSXYZ to nCIEXYZ.
This allows converting byte-encoded LAB values to XYZ:
% echo '255, 0, 128' | \
icc -n XYZ --stdin-u8-to-pcs
pcsxyz(1.9999695, 0, 1.0039062)
It can also be written to a file, for other tools to use it:
icc -n XYZ --reencode-to serenity-XYZ.icc
(...or you can then run `icc serenity-XYZ.icc` instead of
`icc -n XYZ` if you want to dump it from a file instead of from
memory, for some reason.)
This profile uses mft2 (16-bit lut) because no mft1 (8-bit lut) PCSXYZ
encoding exists. That's probably because XYZ is a linear space, and for
linear spaces 8 bits isn't enough resolution.
This profile is a bit weird!
- *We* map the result to 8-bit values for display in `icc`.
We store PCS values as floats, so when converting to a space with
gamma, getting 8 bits out is fine, since we convert to 8 bits after
all the other conversion math. But here the final data color
space is also XYZ, and now we invented our own 8-bit XYZ encoding
by just linearly mapping the ICC 16-bit XYZ encoding to it.
- Since this is an identity mapping, it leaks the spec's internal PCSXYZ
encoding into the output data:
0x0000 maps to 0.0f per spec, which for us becomes 0x00
0x8000 maps to 1.0f per spec, which for us is not representable
(0x80 corresponds to 0x8080)
0xFFFF maps to 1.0 + (32767.0 / 32768.0) == 1.999969f (for us, 0xFF)
To repeat, 0xFF maps to 1.999969f.
We could use a linear ramp to map 0x00..0xff to 0.0f..1.0f, but
then the table wouldn't be its own inverse, and directly exposing
the actual PCSXYZ encoding is nice for testing.
In other words, ICC profiles are data-encoding-dependent. One could also
make a profile that maps 0x00..0xff to red 0.25..0.5 instead of to
0.0..1.0 (if one only needed red values in that range and wanted more
resolution in that range).
Anyways, this profile is probably mostly useful for testing.
This profile idenity-maps PCSLAB to CIELAB.
This allows converting byte-encoded LAB values to PCS:
% echo '255, 0, 255' | \
icc -n LAB --stdin-u8-to-pcs
pcslab(100, -128, 127)
It can also be written to a file, for other tools to use it:
icc -n LAB --reencode-to serenity-LAB.icc
(...or you can then run `icc serenity-LAB.icc` instead of
`icc -n LAB` if you want to dump it from a file instead of from
memory, for some reason.)
This allows inspecting profile conversion intermediate results,
and running things like:
echo '255, 0, 0' |
icc profile1.icc --stdin-u8-to-pcs |
icc profile2.icc --stdin-u8-from-pcs |
icc profile2.icc --stdin-u8-to-pcs |
icc profile1.icc --stdin-u8-from-pcs
The output of --stdin-u8-to-pcs prints data in the profile
connection space, which is either XYZ or LAB:
% echo '255, 255, 255' | \
Build/lagom/bin/icc -n sRGB --stdin-u8-to-pcs
pcsxyz(0.9642, 0.99999994, 0.8249001)
% echo '255, 255, 255, 128' | \
Build/lagom/bin/icc \
Build/lagom/Root/res/icc/Adobe/CMYK/USWebCoatedSWOP.icc \
--stdin-u8-to-pcs
pcslab(6.606001, 2.4342346, -1.8503876)
Which one of the two is printed is a property of the used .icc file.
--stdin-u8-from-pcs parses these two formats and prints bytes in
the data color space of the passed-in color profile.
This is useful for converting single colors from one profile to
another, and for inspecting internal LibGfx state (the PCS values).
As a comment in the code points out, the PCS->data conversion is
a tiny bit fishy as it also depends on the illuminant of the
_source_ profile. To be 100% correct, I think (but I'm not sure)
that we'd also have to write the illuminant to the output
(`pcsxyz(a, b, c, illuminant=d, e, f)` or something). But the V4
ICC spec mandates that the illuminant is the D50 illuminant, and
for most V2 profiles that's true too. So this takes a shortcut,
at least for now. Since ICC::Profile rejects anything that's not
using a D50 illuminant with a diag, it should be fine in practice.
When presented with this option, `icc` will now convert the given color
from the passed ICC profile to sRGB. It supports both 3 and 4 components
colors and can be invoked like this:
```
$ icc buggie-cmyk.jpg --color-to-sRGB=0,0,0,255
[0, 0, 0, 255] in sRGB is rgb(35, 31, 32)
```
This change is in preparation for implementing audio codecs into the
library and using audio as timing for video playback.
(cherry picked from commit 7c10e1a08d7a109b63c9258578eb98aa9dcc1425)
These changes are compatible with clang-format 16 and will be mandatory
when we eventually bump clang-format version. So, since there are no
real downsides, let's commit them now.
This URL library ends up being a relatively fundamental base library of
the system, as LibCore depends on LibURL.
This change has two main benefits:
* Moving AK back more towards being an agnostic library that can
be used between the kernel and userspace. URL has never really fit
that description - and is not used in the kernel.
* URL _should_ depend on LibUnicode, as it needs punnycode support.
However, it's not really possible to do this inside of AK as it can't
depend on any external library. This change brings us a little closer
to being able to do that, but unfortunately we aren't there quite
yet, as the code generators depend on LibCore.
...from try_create_for_raw_bytes().
If a plugin returns `true` from sniff but then fails when calling
its `create()` method, we now no longer swallow that error.
Allows `image` (and other places in the system) to print a more
actionable error if early image headers are invalid.
(We now no longer try to find another plugin that can also handle
the image.)
Fixes a regression from #20063 / #19893 -- before then, we didn't
do fallible work this early.
The former automatically adapts the prefix to binary and octal
output, and is what we already use in the majority of cases.
Patch generated by:
rg -l '0x\{' | xargs sed -i '' -e 's/0x{:/{:#/'
I ran it 4 times (until it stopped changing things) since each
invocation only converted one instance per line.
No behavior change.
Before, we used to reject profiles where the creation datetime was
invalid per spec. But invalid dates happen in practice (most commonly,
all fields set to 0). They don't affect profile conversion at all,
so be lenient about this, in exchange for slightly more wordy code
in the places that want to show the creation datetime.
Fixes a crash rendering page 2 of
https://fredrikbk.com/publications/copy-and-patch.pdf
With this, we convert a bunch of random colors to a
profile's profile connection space and back, and count
how many u8 colors make it through unchanged.
With this, you can do something like:
for f in ~/Downloads/Adobe\ ICC\ Profiles\ \(end-user\)/*/*.icc; do
echo $f
Build/lagom/bin/icc --debug-roundtrip $f
done
to test conversion through a bunch of profiles. These profiles are
available at:
https://www.adobe.com/support/downloads/iccprofiles/iccprofiles_win.html
...which ultimately leads to:
https://download.adobe.com/pub/adobe/iccprofiles/win/AdobeICCProfilesCS4Win_end-user.zip
(Or use `Build/lagom/bin/icc --debug-roundtrip -n sRGB` to test the
built-in sRGB profile.)
If --measure is passed, icc prints the color pairs with the smallest
and largest perceptual difference between them.
Converting 254 * 254 * 254 * 4 = 65 Million colors from sRGB to LAB
and then computing 254 * 254 * 254 * 3 = 49 Million DeltaEs between
them takes a while. On my laptop, it takes 17s to run. So there's
a small progress display.
...instead of from a file.
For now, `--name=sRGB` is the only valid value, but more will
probably follow in the future.
Just `icc --name=sRGB` prints the built-in sRGB profile.
`icc --name=sRGB --reencode-to=file.icc` writes it to file.icc.
I didn't use it in out_curve(), out_parametric_curve() since it
wouldn't be necessary if I printed the indent with a loop instead
of String::repeated() there.
This adds three flags to icc:
* --reencode-to=<file> re-serializes a profile to a given file,
using Gfx::ICC::encode(). This is mainly useful for testing that
code.
* --dump-to=<file> dumps the raw ICC profile bytes read from the
input to disk. This is useful for extracting ICC profiles from
images, and for comparing the raw profile bytes to the profile
bytes written by --reencode-to=.
If either of these flags is passed, icc by default no longer dumps
the icc data to stdout. To force priting of the data even if either
of these flags is present, this also adds:
* --print, which forces printing ICC contents, even if either or
both of the icc-data writing flags are present.
This isn't terribly useful. But some profiles, for example the ones at
https://vpifg.com/help/icc-profiles/, do contain this tag and it seems
nice to be able to dump it, just for completeness.
I haven't seen any files that contain a phosphor or colorant type
different from "Unknown", even for the Rec2020 profile on that page.
(It has x,y coordinates that match the values required for Rec2020,
but it doesn't set the phosphor or colorant type to that.)
Not terribly useful in practice either and also mostly for
completionism. But with this, we can dump all types present
in Lightroom Classic-exported jpegs :^)
These are among the permitted tag types of ATo0Tag and BToA0Tag,
which are among the required tags of most profiles. They are the
last permitted tag types for those profiles (the other are
lut8Type or lut16Type, which are already implemented).
They are pretty chonky types though, so this only implements
support for the E matrix and the CLUT. Support for the various
curves will be in a future PR.
This is a very new tag used for HDR content. The only files I know that
use it are the jpegs on https://ccameron-chromium.github.io/hdr-jpeg/
But they have an invalid ICC creation date, so `icc` can't process them.
(Commenting out the check for that does allow to print them.)
If the CIPC tag is present, it takes precedence about the actual data
in the profile and from what I understand, the ICC profile is
basically ignored. See https://www.color.org/events/HDR_experts.xalter
for background, in particular
https://www.color.org/hdr/02-Luke_Wallis.pdf (but the other talks
are very interesting too).
(PNG also has a cICP chunk that's supposed to take precedence over
iCCP.)
This isn't used by any mandatory tags, and it's not terribly useful.
But jpegs exported by Lightroom Classic write the 'tech' tag, and
it seems nice to be able to dump its contents.
signatureType stores a single u32 which for different tags with this
type means different things.
In each case, the value is one from a short table of valid values,
suggesting this should be a per-tag enum class instead of a
per-tag DistinctFourCC, per the comment at the top of DistincFourCC.h.
On the other hand, 3 of the 4 tables have an explicit "It is possible
that the ICC will define other signature values in the future" note,
which suggests the FourCC might actually be the way to go.
For now, just punt on that and manually dump the u32 in fourcc style
in icc.cpp and don't add any to_string() methods that return a readable
string based on the contents of these tables.
This is the type of namedColor2Tag, which is a required tag in
NamedColor profiles.
The implementation is pretty basic for now and only exposes the
numbers stored in the file directly (after endian conversion).
s15Fixed16Number and XYZNumber are somewhat awkwardly duplicated
in both Profile.cpp and TagTypes.cpp. Other than that, this is a
pure code move.
No behavior change.
The spec names are still a bit cryptic ("deviceMfgDescTag"
for "device manufacturer description"), but less cryptic than
just the fourcc.
There's a private tag area, so this will only print the spec name
of tags in the current spec. Private tags are in active use, e.g.:
$ icc /Library/ColorSync/Profiles/WebSafeColors.icc
...
Unknown tag ('dscm'): type 'mluc', offset 312, size 1490
(That's a v2 file. In v2, 'desc' has that strange textDescriptionType.
In v4, 'desc' has type 'mluc' -- but in v2, it didn't yet, so Apple
invented the private 'dscm' tag which has the description as an 'mluc'.)
When several tags refer to the same TagData object, we now only print
it the first time, and print "(see 'foob' above)" the following times,
where `foob` is the tag identifier where we printed it the first time.