Skip to content

Conversation

@nico
Copy link
Contributor

@nico nico commented Dec 4, 2025

mft1 BToA0 tags are often present in CMYK profiles, so this is a step towards supporting converting images to CMYK.

Matrix profiles can only be used with PCSXYZ, but CMYK profiles use PCSLAB.

CMYK is 4D, which makes writing roundtrip tests for those profiles iffy. Adding a LAB profile that also uses PCSLAB as PCS space makes testing this easier.

Also add a roundtrip test for sRGB; for some reason I hadn't done that yet as far as I can tell.

@github-actions github-actions bot added the 👀 pr-needs-review PR needs review from a maintainer or community member label Dec 4, 2025
nico added 8 commits December 3, 2025 22:09
This makes it clearer what they do. No behavior change.
Clarify two spec references, fix a copy-pasto.
This will be used to implement conversion from PCS to LUT-based profiles
(Lut16TagType, Lut8TagType, LutBToATagData with a CLUT).

This is our third implementation of ND interpolation:

* This file already as a ND->3D version
* This new version, which does 3D->ND
* SampledFunction::evaluate() in LibPDF's Function.cpp does ND->ND,
  at the expense of allocations (but since it's in an object, it can
  reuse a vector across calls)

I think eventually we instead want a fast 4D->4D version that is always
enough in practice, and a generinc ND->ND version, and use those two in
both LibPDF and here. But let's get something working first.
...and make it slightly more general.

No behavior change.
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.)
Makes this work:

    % echo 'pcslab(100, -128, 127)' | \
          Build/lagom/bin/icc -n LAB --stdin-u8-from-pcs
    255, 0, 255

Also makes it possible to write a roundtrip test for the
IdentityLAB() profile, so do so.
@nico nico force-pushed the icc-mft1-from-pcs branch from 242e0a6 to 5d443c8 Compare December 4, 2025 03:09
Copy link
Member

@LucasChollet LucasChollet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!


static void test_roundtrip(Gfx::ICC::Profile const& profile)
{
// Ideally this would be 1, but that makes tests take a few minutes on fast machine.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use GEN to test a different subset on every run. Which means that we should have complete coverage over time.
I'm just thinking out-loud.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a big fan of deterministic behavior 😅

@nico
Copy link
Contributor Author

nico commented Dec 5, 2025

Thanks!

@nico nico merged commit 3e6b368 into SerenityOS:master Dec 5, 2025
15 checks passed
@nico nico deleted the icc-mft1-from-pcs branch December 5, 2025 02:00
@github-actions github-actions bot removed the 👀 pr-needs-review PR needs review from a maintainer or community member label Dec 5, 2025
nico added a commit to nico/serenity that referenced this pull request Dec 6, 2025
This used to fail:

    % Build/lagom/bin/image \
        --assign-color-profile serenity-sRGB.icc \
        --convert-to-color-profile \
            ./Build/lagom/Root/res/icc/Adobe/CMYK/USWebCoatedSWOP.icc \
        -o buggie-cmyk.jpg \
        Base/res/graphics/buggie.png
    Runtime error: Can only convert to RGB at the moment,
        but destination color space is not RGB

Now it works.

It only works for CMYK profiles that use an mft1 tag for the BToA tags,
because SerenityOS#26452 added support for converting from PCS to mft1.

Most CMYK profiles use mft1, but not all of them. Support for `mft2` and
`mBA ` tag types will be in a (straightforward) follow-up.

Implementation-wise, Profile grows two more methods for converting RGB
and CMYK bitmaps to CMYK. We now have 2x2 implementations for
{RGB,CMYK} -> {RGB,CMYK}. For N different channel types, we need O(N^2)
of these methods.

Instead, we could have conversion methods between RGB bitmap and PCS
bitmap and between CMYK and PCS bitmap. With this approach, we'd only
need O(N) (2*N) of these methods. Concretely, that'd be 2x2 methods too
though, and since there are two PCS types, depending on how it's
implemented it's actually 4*N (i.e. 8 for RGB and CMYK).

So the current 2x2 approach seems not terrible. It *is* a bit
repetitive.

We then call the right of these 4 methods in `image`.

See the PR that added this commit for rough quantitative quality
evaluation of the implementation.
nico added a commit that referenced this pull request Dec 6, 2025
This used to fail:

    % Build/lagom/bin/image \
        --assign-color-profile serenity-sRGB.icc \
        --convert-to-color-profile \
            ./Build/lagom/Root/res/icc/Adobe/CMYK/USWebCoatedSWOP.icc \
        -o buggie-cmyk.jpg \
        Base/res/graphics/buggie.png
    Runtime error: Can only convert to RGB at the moment,
        but destination color space is not RGB

Now it works.

It only works for CMYK profiles that use an mft1 tag for the BToA tags,
because #26452 added support for converting from PCS to mft1.

Most CMYK profiles use mft1, but not all of them. Support for `mft2` and
`mBA ` tag types will be in a (straightforward) follow-up.

Implementation-wise, Profile grows two more methods for converting RGB
and CMYK bitmaps to CMYK. We now have 2x2 implementations for
{RGB,CMYK} -> {RGB,CMYK}. For N different channel types, we need O(N^2)
of these methods.

Instead, we could have conversion methods between RGB bitmap and PCS
bitmap and between CMYK and PCS bitmap. With this approach, we'd only
need O(N) (2*N) of these methods. Concretely, that'd be 2x2 methods too
though, and since there are two PCS types, depending on how it's
implemented it's actually 4*N (i.e. 8 for RGB and CMYK).

So the current 2x2 approach seems not terrible. It *is* a bit
repetitive.

We then call the right of these 4 methods in `image`.

See the PR that added this commit for rough quantitative quality
evaluation of the implementation.
nico added a commit to nico/serenity that referenced this pull request Dec 10, 2025
Similar to what SerenityOS#26452 did for Lut8TagData and SerenityOS#26462 for Lut16TagData.

With this, we can convert from PCS to all tag types :^)

Which means our ICC implementation is now fairly complete! We don't
support DeviceLink or Abstract profile types (both don't appear in
images; both are arguably useful in some not-embedded-in-images
scenarios), nor the optiona BToD / DToB tags, and are missing
some other minor things (like grayscale profiles -- but not much
is missing here). But overall, this is now a fairly complete basic
ICC implementation :^)
nico added a commit to nico/serenity that referenced this pull request Dec 10, 2025
Similar to what SerenityOS#26452 did for Lut8TagData and SerenityOS#26462 for Lut16TagData.

With this, we can convert from PCS to all tag types :^)

Which means our ICC implementation is now fairly complete! We don't
support DeviceLink or Abstract profile types (both don't appear in
images; both are arguably useful in some not-embedded-in-images
scenarios), nor the optiona BToD / DToB tags, and are missing
some other minor things (like grayscale profiles -- but not much
is missing here). But overall, this is now a fairly complete (if slow)
basic ICC implementation :^)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants