Understanding RawTherapee color output

Thanks for the help @JackH and @gaaned92.

I was trying for the latter: give RT values in camera raw space, and since ColorMatrix1=identity, they’d be interpreted as XYZ.D65 values (since the DNG spec defines ColorMatrix1 as translating between XYZ and camera space).

Anyway, since I couldn’t get the DNG fields to work as I expected, I created a custom ICC profile containing my color matrix, and fortunately that works.

In case it helps someone else, I’ve outlined my steps below, which allow you to:

  • control the color matrix that RawTherapee uses for interpreting raw CFA data, and
  • independently verify RawTherapee’s color math.

Controlling the color matrix that RawTherapee uses to interpret color filter array data

  • Create a custom ICC profile

    • Compile the iccFromXml tool from the RefIccMAX project:
      https://github.com/InternationalColorConsortium/RefIccMAX

    • Create the ICC profile in XML. Here’s a template:

      <?xml version="1.0" encoding="UTF-8"?>
      <IccProfile>
          <Header>
              <PreferredCMMType></PreferredCMMType>
              <CreationDateTime>2020-01-01T00:00:00</CreationDateTime>
              <ProfileVersion>4.3.0.0</ProfileVersion>
              <ProfileDeviceClass>scnr</ProfileDeviceClass>
              <DataColourSpace>RGB </DataColourSpace>
              <PCS>XYZ </PCS>
              <DeviceAttributes ReflectiveOrTransparency="reflective" GlossyOrMatte="glossy" MediaPolarity="positive" MediaColour="colour"/>
              <ProfileFlags EmbeddedInFile="false" UseWithEmbeddedDataOnly="false"/>
              <RenderingIntent>Perceptual</RenderingIntent>
              <PCSIlluminant> <XYZNumber X="0.96422" Y="1" Z="0.8249"/> </PCSIlluminant>
          </Header>
      
          <Tags>
              <profileDescriptionTag> <multiLocalizedUnicodeType>
                <LocalizedText LanguageCountry="enUS">Description</LocalizedText>
              </multiLocalizedUnicodeType> </profileDescriptionTag>
      
              <copyrightTag> <multiLocalizedUnicodeType>
                <LocalizedText LanguageCountry="enUS">Copyright</LocalizedText>
              </multiLocalizedUnicodeType> </copyrightTag>
      
              <mediaWhitePointTag> <XYZArrayType> <XYZNumber X="1" Y="1" Z="1"/> </XYZArrayType> </mediaWhitePointTag>
      
              <redColorantTag> <XYZArrayType> <XYZNumber X="1" Y="0" Z="0"/> </XYZArrayType> </redColorantTag>
              <greenColorantTag> <XYZArrayType> <XYZNumber X="0" Y="1" Z="0"/> </XYZArrayType> </greenColorantTag>
              <blueColorantTag> <XYZArrayType> <XYZNumber X="0" Y="0" Z="1"/> </XYZArrayType> </blueColorantTag>
      
              <redTRCTag> <curveType> <Curve> </Curve> </curveType> </redTRCTag>
              <greenTRCTag> <curveType> <Curve> </Curve> </curveType> </greenTRCTag>
              <blueTRCTag> <curveType> <Curve> </Curve> </curveType> </blueTRCTag>
          </Tags>
      </IccProfile>
      
    • Insert the color matrix into the redColorantTag / greenColorantTag / blueColorantTag fields. (Each field is a column of the matrix.)

    • Convert the XML ICC profile into its binary represenation:

      iccFromXml profile.xml profile.icc
      
  • Convert CFA data to a DNG image

    • Save the CFA data as a monochrome TIFF, but name it img.dng

    • Strip metadata from the image:

      exiftool -overwrite_original -all= img.dng

    • Add DNG fields:

      exiftool                                            \
      -DNGVersion="1.4.0.0"                               \
      -DNGBackwardVersion="1.4.0.0"                       \
      -IFD0:BlackLevel=0                                  \
      -IFD0:WhiteLevel=65535                              \
      -PhotometricInterpretation="Color Filter Array"     \
      -SamplesPerPixel=1                                  \
      -IFD0:CFARepeatPatternDim="2 2"                     \
      -IFD0:CFAPattern2="1 0 2 1"                         \
      -overwrite_original                                 \
      img.dng
      
  • Load img.dng in RawTherapee

    • Disable all image processing (except demosaicing)
    • Input Profile: select the custom ICC profile
    • Output Profile: RTv4_sRGB

Verifying RawTherapee’s color output

Given a 16-bit image filled with [0x7FFF 0x7FFF 0x7FFF]/65535 = [.5 .5 .5], RawTherapee should output sRGB = [0.744 0.726 0.812] = [190 185 207], assuming the ICC profile contains the identity matrix. Other color values (using the identity matrix) can be verified using this color calculator:

The generalized formula to determine the sRGB output color for any rawColor and colorMatrix is:

srgb = SRGBFromLSRGB(LSRGB_From_XYZ * XYZ_D65_From_XYZ_D50_Bradford * colorMatrix * rawColor)

where:

// From brucelindbloom.com
LSRGB_From_XYZ = [ 3.2404542 -1.5371385 -0.4985314 ; -0.9692660 1.8760108 0.0415560 ; 0.0556434 -0.2040259 1.0572252 ]
XYZ_D65_From_XYZ_D50_Bradford = [ 0.9555766 -0.0230393 0.0631636 ; -0.0282895 1.0099416 0.0210077 ; 0.0122982 -0.0204830 1.3299098 ]

float SRGBFromLSRGB(float x) {
    if (x <= 0.0031308) return 12.92*x;
    return 1.055*pow(x, 1/2.4) - .055;
}

Note that SRGBFromLSRGB() is applied to each component separately.


Anyway hopefully that helps someone in the future.

David