Autor: panda

  • Adding new camera support in darktable / LibRaw / ExifTool

    As I recently bought a new Canon R5 Mark II, I needed to find out, how to add it to several projects like darktable / LibRaw / ExifTool to be able to use the camera on day one and not having to wait for others to implement support for me.

    As this has been quite the journey, diving into file formats and projects I have never used myself before, I decided to write a short blog post so other people don’t need to find all the little parts of the puzzle themselves. I love being independent. This includes, for me, understanding how your tools work. At least so much that you can use them without being „just“ a user. So this has been a nice side quest to get a better understanding of the process.

    The CR3 file format

    The CR3 file format has been introduced with the Canon mirrorless cameras. Many talented people have been working on understanding and reverse engieneering it. A documentation is provided by Laurent Clévy. There are still plenty of tags which are not understood, so if you feel like this is the right place for you to jump in, please do so. The book of Robin Mills, the creator of exiv2, also is a great place to start!

    As darktable was unable to read CR3 files the time I got my R5, Daniel Vogelbacher, now author of dnglab, created a pull request for rawspeed, the submodule darktable uses to load raws, adding support for reading CR3 files. Back in those days, discussions over the patents around the file format delayed broad support of CR3.

    As rawspeed has not integrated Daniel’s pull request until today, LibRaw had been integrated into darktable for supporting CR3 files.

    Adding camera support to LibRaw

    The CR3 file format has been understood for all neccessary functions in darktable. Adding camera support involves telling LibRaw which color matrix for the camera it has to use and where to find black and white level of the image. LibRaw also want to know where white balance presets and white balance color temperature presets are stored in the captured file.

    As LibRaw unfortunately, to my knowledge, doesn’t have any information on how to contribute to the project, I took a look at previous pull requests. This gave me almost all the necessary information:

    For the color matrix you have to wait for Adobe to add the camera support to their DNG converter, which runs fine under linux with wine. When converting raws to dng with it, it adds a color matrix to the dng file, which then can be extracted with tools like Exiftool or exiv2:

    exiftool Downloads/020A1233.dng | grep Color
    
    Photometric Interpretation : Color Filter Array
    CFA Plane Color : Red,Green,Blue
    Color Space : sRGB
    Color Matrix 1 : 1.1261 -0.5701 0.0384 -0.3524 1.0833 0.311 -0.0024 0.0612 0.7273
    Color Matrix 2 : 0.9396 -0.2598 -0.1207 -0.4408 1.2296 0.2369 -0.0505 0.1575 0.6077
    Preview Color Space : sRGB
    

    LibRaw uses the Color Matrix 2 data, but the values need to be multiplied by 10000. This then gets added to colordata.cpp:

    { LIBRAW_CAMERAMAKER_Canon, "EOS R5 Mark II", 0, 0,
    	  { 9396,-2598,-1207,-4408,12296,2369,-505,1575,6077 } },
    

    The remaining steps are basically adding information about the camera itself. Adding the model ID, also found in the EXIF tags, to libraw_cameraids.h as follows:

    #define CanonID_EOS_R5m2          (0x80000000ULL + 0x496ULL)
    

    One can see, Canon internally uses R5m2 as the model name. Next step is to add this model name to canon.cpp in the FF (full frame) part:

               || (id == CanonID_EOS_R5)
               || (id == CanonID_EOS_R5m2)
    
    

    and also adding it to cameralist.cpp:

    	"Canon EOS R5",
    	"Canon EOS R5 Mark II",
    
    

    Finally a kind of translation is added to normalize_model.cpp which translates the model name used by Canon internally to the name, we usually know the camera under:

          { CanonID_EOS_R5,            "EOS R5"},
          { CanonID_EOS_R5m2,          "EOS R5 Mark II"},
    

    The next step needed some deep diving into the CR3 file format. I dimly remembered from the R5 days that Daniel wanted me to create files for each ISO value so that he could extract black and white levels. So there must be a part in the code that extracts those values. Scrolling down in canon.cpp this code can be found. The address in the CR3 file format is 0x4001. Software reads this tag, and depending on the length of the tag, switches cases. For known cameras like the R5 it looks like that when using the following ExifTool command -a -U -H -v4 -b XXX.cr3:

    | | + [TIFF directory]
    | | | + [MakerNotes directory with 4 entries]
    | | | | 0)  ColorData10 (SubDirectory) -->
    | | | |     - Tag 0x4001 (7312 bytes, int16u[3656] read as undef[7312]):
    

    The length of the int16u is then used as the case in LibRaw:

        case 3656: // R5, R6; ColorDataSubVer: 33
          imCanon.ColorDataVer = 10;
          AsShot_Auto_MeasuredWB(0x0055);
          CR3_ColorData(0x0055);
          break;
    

    A valuable tool for me was an online hex calculator, as I am not very fluent in hex 😉

    The AsShot whitebalance can thus be found in tag 0x0055 in the 0x4001 directory:

    | | | | | Canon_ColorData10_0x0053 = 0
    | | | | | - Tag 0x0053 (2 bytes, int16s[1]):
    | | | | |  4793202: 00 00                                           [..]
    | | | | | Canon_ColorData10_0x0054 = 0
    | | | | | - Tag 0x0054 (2 bytes, int16s[1]):
    | | | | |  4793204: 00 00                                           [..]
    | | | | | WB_RGGBLevelsAsShot = 1953 1024 1024 1917
    | | | | | - Tag 0x0055 (8 bytes, int16s[4]):
    

    Which it already is by ExifTool. And also take a look at the zeros in the tags before 0x0055, this will be important later. The CR3_ColorData() function is defined above as:

    CR3_ColorData(offset)                                
      fseek(ifp, save1 + ((offset+0x0041) << 1), SEEK_SET);      
      Canon_WBpresets(2, 12);                                    
      fseek(ifp, save1 + ((offset+0x00c3) << 1), SEEK_SET);      
      Canon_WBCTpresets(0);                                      
      offsetChannelBlackLevel2 = save1 + ((offset+0x0102) << 1); 
      offsetChannelBlackLevel  = save1 + ((offset+0x02d1) << 1); 
      offsetWhiteLevels        = save1 + ((offset+0x02d5) << 1);
    

    where the first part tells LibRaw where to find the white balance presets of the camera, the second part where to find the color temperature presets and finally the black and white levels of the camera. As this changes with the ColorDataSubVer number, it has been externalized into the cases for newer cameras:

    case 3973: // R3; ColorDataSubVer: 34
    case 3778: // R6 Mark II, R7, R8, R10, R50; ColorDataSubVer: 48
          imCanon.ColorDataVer = 11;
          AsShot_Auto_MeasuredWB(0x0069);
    
          fseek(ifp, save1 + ((0x0069+0x0064) << 1), SEEK_SET);
          Canon_WBpresets(2, 12);
          fseek(ifp, save1 + ((0x0069+0x00c3) << 1), SEEK_SET);
          Canon_WBCTpresets(0);
          offsetChannelBlackLevel2 = save1 + ((0x0069+0x0102) << 1);
          offsetChannelBlackLevel  = save1 + ((0x0069+0x0213) << 1);
          offsetWhiteLevels        = save1 + ((0x0069+0x0217) << 1);
          break;
    

    As ColorDataSubVer is 64 for the R5 Mark II and the R1 as well, I needed to find the correct addresses for the black and white levels as they have been unknown in LibRaw until this stage. So this is where the „real detective work“ begins. I downloaded sample RAWs and compared the numbers at the addresses for known cameras and, with a bit of experimenting, I found the correct addresses using ExifTool:

    | | | | | Canon_ColorDataUnknown_0x017f = 512
    | | | | | - Tag 0x017f (2 bytes, int16s[1]):
    | | | | |  2a9848c: 00 02                                           [..]
    | | | | | Canon_ColorDataUnknown_0x0180 = 512
    | | | | | - Tag 0x0180 (2 bytes, int16s[1]):
    | | | | |  2a9848e: 00 02                                           [..]
    | | | | | Canon_ColorDataUnknown_0x0181 = 512
    | | | | | - Tag 0x0181 (2 bytes, int16s[1]):
    | | | | |  2a98490: 00 02                                           [..]
    | | | | | Canon_ColorDataUnknown_0x0182 = 512
    | | | | | - Tag 0x0182 (2 bytes, int16s[1]):
    

    I knew from the R5 files that I was looking for something like:

    | | | | | PerChannelBlackLevel = 510 510 510 510
    | | | | | - Tag 0x0157 (8 bytes, int16u[4]):
    

    But of course not already nicely grouped. So for ColorDataSubVer 64 the black levels are found a bit later in the directory. The procedure was the same for white levels. Searching for numbers in the range of 12000 to 14500:

    | | | | | NormalWhiteLevel = 12735
    | | | | | - Tag 0x032a (2 bytes, int16u[1]):
    | | | | |  47937b0: bf 31                                           [.1]
    | | | | | SpecularWhiteLevel = 14008
    | | | | | - Tag 0x032b (2 bytes, int16u[1]):
    | | | | |  47937b2: b8 36                                           [.6]
    

    Putting it all together

    This lead me to the following code for the R5 Mark II:

    case 4528: // R1 and R5 Mark II; ColorDataSubVer: 64
          imCanon.ColorDataVer = 12;
          AsShot_Auto_MeasuredWB(0x0069);     
    
          //fseek(ifp, save1 + ((0x0069+0x0064) << 1), SEEK_SET);
          //Canon_WBpresets(2, 12);
          //fseek(ifp, save1 + ((0x0069+0x00c3) << 1), SEEK_SET);
          //Canon_WBCTpresets(0);
          offsetChannelBlackLevel2 = save1 + (0x017f << 1); 
          offsetChannelBlackLevel  = save1 + (0x0290 << 1); 
          offsetWhiteLevels        = save1 + (0x0294 << 1); 
          break;
    

    imCanon.ColorDataVer = 12; I randomly guessed by just increasing the variable by 1. AsShot is the first white balance value in the 0x4001 directory consisting of RGGB values. This is at position 0x0069, as it was for previous models:

    | | | | | Canon_ColorDataUnknown_0x0067 = 0
    | | | | | - Tag 0x0067 (2 bytes, int16s[1]):
    | | | | |  2a9825c: 00 00                                           [..]
    | | | | | Canon_ColorDataUnknown_0x0068 = 0
    | | | | | - Tag 0x0068 (2 bytes, int16s[1]):
    | | | | |  2a9825e: 00 00                                           [..]
    | | | | | Canon_ColorDataUnknown_0x0069 = 1531
    | | | | | - Tag 0x0069 (2 bytes, int16s[1]):
    | | | | |  2a98260: fb 05                                           [..]
    | | | | | Canon_ColorDataUnknown_0x006a = 1024
    | | | | | - Tag 0x006a (2 bytes, int16s[1]):
    | | | | |  2a98262: 00 04                                           [..]
    | | | | | Canon_ColorDataUnknown_0x006b = 1024
    | | | | | - Tag 0x006b (2 bytes, int16s[1]):
    | | | | |  2a98264: 00 04                                           [..]
    | | | | | Canon_ColorDataUnknown_0x006c = 1971
    | | | | | - Tag 0x006c (2 bytes, int16s[1]):
    

    As above, zeros before the first real white balance RGGB values. At first, I put the real positions of the white and black values, as I still don’t understand why LibRaw puts the addresses as offsets to the AsShot position.

    This finally made darktable properly read the pictures I found online. So I was set for myself. But for submitting a proper pull request to LibRaw, which in my opinion should state that they don’t want/accept external contributions to avoid frustration, the WBpresets positions and the WBCTpresets (Color Temperature presets I guess) still needed to be found. As I lacked the experience for this, I submitted my findings and samples in the ExifTool forum. Phil Harvey, the author of the software, analyzed the samples and promptly added support for the new data structures to ExifTool. As far as I understood, naming the WB presets correctly involves comparing color temperature values to previous presets and guessing them based on experience.

    With all this, the final code needed for LibRaw is the following:

        case 4528: // R5 Mark II; ColorDataSubVer: 64
          imCanon.ColorDataVer = 12;
          AsShot_Auto_MeasuredWB(0x0069);     
    
          fseek(ifp, save1 + (0x006e << 1), SEEK_SET);
          Canon_WBpresets(2, 12);
          fseek(ifp, save1 + (0x0140 << 1), SEEK_SET);
          Canon_WBCTpresets(0);
          offsetChannelBlackLevel2 = save1 + (0x017f << 1); 
          offsetChannelBlackLevel  = save1 + (0x0290 << 1); 
          offsetWhiteLevels        = save1 + (0x0294 << 1); 
          break;
    

    In darktable the following small changes need to be made: Rawspeed’s cameras.xml needs to blacklist the camera for LibRaw to take over (edit: this might be incorrect as CR3 is whitelisted for LibRaw here):

    	<Camera make="Canon" model="Canon EOS R5m2" supported="no">
    		<ID make="Canon" model="EOS R5 Mark II">Canon EOS R5 Mark II</ID>
    
    

    and in imageio_libraw.c:

      {
        .exif_make = "Canon",
        .exif_model = "Canon EOS R5m2",
        .clean_make = "Canon",
        .clean_model = "EOS R5 Mark II",
        .clean_alias = "EOS R5 Mark II"
      },
    

    Final thoughts

    With all in place, noiseprofiles (I’m using a slightly more userfriendly procedure with a lamp, as mentioned in the comments) and white balance presets can be created.

    All this is now waiting in pull requests at LibRaw (which will most likely never be accepted. Without knowing if external contributions are welcome, this is kinda frustrating, but at least I can use it locally already) and darktable 1, 2, 3.