Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Color conversion with ICC profiles #1567

Draft
wants to merge 64 commits into
base: main
Choose a base branch
from

Conversation

JimBobSquarePants
Copy link
Member

@JimBobSquarePants JimBobSquarePants commented Feb 27, 2021

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

Note: This is a replacement for the original PR #273 from @JBildstein that was automatically closed by our Git LFS history rewrite. Individual commits have unfortunately been lost in the process. Help is very much needed to complete the work.

As the title says, this adds methods for converting colors with an ICC profile.

Architecturally, the idea is that the profile is checked once for available and appropriate conversion methods and a then a delegate is stored that only takes the color values to convert and returns the calculated values. The possible performance penalty for using a delegate is far smaller than searching through the profile for every conversion. I'm open for other suggestions though.

There are classes to convert from the profile connection space (=PCS, can be XYZ or Lab) to the data space (RGB, CMYK, etc.) and vice versa. There are also classes to convert from PCS to PCS and Data to Data but they are only used for special profiles and are not important for us now but I still added them for completeness sake.

A challenge here is writing tests for this because of the complexity of the calculations and the big amount of different possible conversion paths. This is a rough list of the paths that exist:

  • "A to B" and "B to A" tags
    • IccLut8TagDataEntry
      • Input IccLut[], Clut, Output IccLut[]
      • Matrix(3x3), Input IccLut[], IccClut, Output IccLut[]
    • IccLut16TagDataEntry
      • Input IccLut[], IccClut, Output IccLut[]
      • Matrix(3x3), Input IccLut[], IccClut, Output IccLut[]
    • IccLutAToBTagDataEntry/IccLutBToATagDataEntry (Curve types can either be IccCurveTagDataEntry or IccParametricCurveTagDataEntry (which has several curve subtypes))
      • CurveA[], Clut, CurveM[], Matrix(3x1), Matrix(3x3), CurveB[]
      • CurveA[], Clut, CurveB[]
      • CurveM[], Matrix(3x1), Matrix(3x3), CurveB[]
      • CurveB[]
  • "D to B" tags
    • IccMultiProcessElementsTagDataEntry that contains an array of any of those types in any order:
      • IccCurveSetProcessElement
        • IccOneDimensionalCurve[] where each curve can have several curve subtypes
      • IccMatrixProcessElement
        • Matrix(Nr. of input Channels by Nr. of output Channels), Matrix(Nr. of output channels by 1)
      • IccClutProcessElement
        • IccClut
  • Color Trc
    • Matrix(3x3), one curve for R, G and B each (Curve types can either be IccCurveTagDataEntry or IccParametricCurveTagDataEntry (which has several curve subtypes))
  • Gray Trc
    • Curve (Curve type can either be IccCurveTagDataEntry or IccParametricCurveTagDataEntry (which has several curve subtypes))

The three main approaches in that list are

  • A to B/B to A: using a combination of lookup tables, matrices and curves
  • D to B: using a chain of multi process elements (curves, matrices or lookup)
  • Trc: using curves (and matrices for color but not for gray)

The most used approaches are Color Trc for RGB profiles and LutAToB/LutBToA for CMYK profiles.

Todo list:

  • Integrate with the rest of the project
  • Write tests that cover all conversion paths
  • Review architecture
  • Improve speed and accuracy of the calculations

Help and suggestions are very welcome.

@brianpopow
Copy link
Collaborator

I wonder why the test MatrixCalculator_WithMatrix_ReturnsResult only fails with netcoreapp2.1 and not with the other frameworks.

@JimBobSquarePants
Copy link
Member Author

@brianpopow It'll be an accuracy issue most likely. (I hope it's not a JIT issue). It should be possible to inspect the result and see.

@codecov
Copy link

codecov bot commented Jul 13, 2021

Codecov Report

❗ No coverage uploaded for pull request base (main@ca20c92). Click here to learn what that means.
The diff coverage is n/a.

❗ Current head 5834c39 differs from pull request most recent head f60d4b8. Consider uploading reports for the commit f60d4b8 to get more accurate results

@@          Coverage Diff           @@
##             main   #1567   +/-   ##
======================================
  Coverage        ?     87%           
======================================
  Files           ?    1023           
  Lines           ?   55212           
  Branches        ?    7052           
======================================
  Hits            ?   48227           
  Misses          ?    5768           
  Partials        ?    1217           
Flag Coverage Δ
unittests 87% <0%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@brianpopow
Copy link
Collaborator

@brianpopow It'll be an accuracy issue most likely. (I hope it's not a JIT issue). It should be possible to inspect the result and see.

The issue only happens with a Release build. I think i found the reason, but it seems very weird. Vector3.Zero does not have the expected value (0, 0, 0).

This can be seen with the testoutput:

[xUnit.net 00:00:07.19]     MatrixCalculator_WithMatrix_ReturnsResult(matrix2D: { {M11:1 M12:0 M13:0 M14:0} {M21:0 M22:1 M23:0 M24:0} {M31:0 M32:0 M33:1 M34:0} {M41:0 M42:0 M43:0 M44:1} }, matrix1D: <-0,0007887525. 4,590794E-41. 1>, input: <0,5. 0,5. 0,5. 0>, expected: <0,5. 0,5. 0,5. 0>) [FAIL]

matrix1D is supposed to be Vector3.Zero

@JimBobSquarePants
Copy link
Member Author

Vector3.Zero does not have the expected value (0, 0, 0).

@brianpopow Woah! That's bonkers!

@brianpopow
Copy link
Collaborator

Vector3.Zero does not have the expected value (0, 0, 0).

@brianpopow Woah! That's bonkers!

I have reported this issue: dotnet/runtime#55623

They confirmed the issue, but they say its unlikely to be fixed because netcore2.1 is out of support in august.
So long story short: be careful with default values or Vector.Zero in testdata.

@brianpopow
Copy link
Collaborator

@JimBobSquarePants It would be really nice, if we could bring this PR forward. This would be a good addition to ImageSharp. I thought, I may ask you, if you know what the PR needs (besides tests) to be finished?

What would be the right way to apply an embedded color profile? My first attempt was:

var converter = new IccPcsToDataConverter(profile);
for (int y = 0; y < image.Height; y++)
{
    for (int x = 0; x < image.Width; x++)
    {
        var inputVec = image[x, y].ToVector4();
        Vector4 converted = converter.Calculate(inputVec);
        image[x, y] = new RgbaVector(converted.X, converted.Y, converted.Z);
    }
}

Here is an example image with adobe rgb color profile:

Momiji-AdobeRGB-yes

This does not seems to work, the colors seem to be wrong. Here are more example images

@JimBobSquarePants
Copy link
Member Author

@brianpopow Honestly.....

I don't know. I was hoping the OP would come back to finish things off. I've just kept things updated over the years and hadn't gotten involved at all in the implementation as yet.

Judging from the old comments in the previous PR I believe the code is based somewhat based on the following

https://github.com/InternationalColorConsortium/DemoIccMAX/tree/master/IccProfLib

As for accuracy. That result looks like it's just spitting out the sRGB values again.

I do agree that it would be an awesome addition to the library and would save a ton of headaches. I hoped we'd get it in V3 but that's a mighty big ask.

@brianpopow
Copy link
Collaborator

I think we definitely need a reference implementation to compare the results against. I tried BABL which gnome is using, but i could not get it to work on windows. I will take a look at DemoIccMAX

@JimBobSquarePants
Copy link
Member Author

I'm in love with these diagrams.🤩

@saucecontrol
Copy link
Contributor

I'm in love with Excalidraw. 😍

I didn't even know if the pictures would be useful, but I had too much fun drawing them!

@JimBobSquarePants JimBobSquarePants modified the milestones: 3.0.0, Future Feb 27, 2023
@JimBobSquarePants
Copy link
Member Author

JimBobSquarePants commented Nov 1, 2023

@brianpopow @saucecontrol I've been trying to debug the port of the Clut 4d interpolator but I'm drawing a blank because I have no idea how to run DemoIccMAX IccProfLib to do a comparison. Any hints?

My money is on the port being sound but I'm doing something stupid porting the pcs to CIELab to convert via the color converter in IccProfileConverter

@brianpopow
Copy link
Collaborator

brianpopow commented Nov 1, 2023

@brianpopow @saucecontrol I've been trying to debug the port of the Clut 4d interpolator but I'm drawing a blank because I have no idea how to run DemoIccMAX IccProfLib to do a comparison. Any hints?

@JimBobSquarePants I struggle myself to understand how DemoIccMAX is supposed to work.

I would suggest trying to understand how Little-CMS does it. There are alot of tests included in Little-CMS, maybe we can try to replicate one of them (maybe Check4Dinterp) and see what is the difference to our implementation.

You can generate visual studio solution files with the meson build system of LittleCms like this:

meson --backend=vs2022 --buildtype=debug buildvs2022

edit: There is no need to generate the visual studio project files, there are already ones in the repo: https://github.com/mm2/Little-CMS/tree/master/Projects/VC2022

@JimBobSquarePants
Copy link
Member Author

Thanks @brianpopow I can't find anything useful there unfortunately.

@JimBobSquarePants
Copy link
Member Author

I added some code and notes to the converter to help debugging. What I'm seeing is the results of the lut transform result in very minor differences to the output value for each pixel 1e05. I'm wondering if somehow, we have a scaling problem.

@cguedel
Copy link

cguedel commented Apr 11, 2024

What is the state of this? Lack of ICC support is a major blocker for us.

@JimBobSquarePants
Copy link
Member Author

The state is as discussed in the various comments above, without contribution this PR will not be completed.

We support ICC profile preservation but not transformation during decode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
ImageSharp
  
To Do
Development

Successfully merging this pull request may close these issues.

None yet

5 participants