Oyranos/CMM API

From ColourWiki

Table of contents


As an excerpt from the current development, I place here a header fragment showing the basic structures to pluging in lcms into Oyranos' module system.

My question to interessted developers is,
Will the below outlined API be enough to work flawless?

Proposal A

/** @brief Oyranos name structure
 *  @since Oyranos: version 0.1.8
 *  @date  october 2007 (API 0.1.8)
typedef struct {
 oyOBJECT_TYPE_e      type;           /*!< internal struct type oyOBJECT_TYPE_NAME_S */
 char               * nick;    /*!< few letters for mass representation (A1) */
 char               * name;           /*!< normal visible name (A1-MySys) */
 char               * description;    /*!< full user description (A1-MySys from Oyranos) */
} oyName_s;
/** @brief icon data
*  Since: 0.1.8
typedef struct {
 oyOBJECT_TYPE_e  type;               /*!< struct type oyOBJECT_TYPE_ICON_S */ 
 int              width;
 int              height;
 float          * data;               /*!< should be sRGB matched */
 oyChar         * file_list;          /*!< colon ':' delimited list of icon file names, SVG, PNG */
} oyIcon_s;
typedef struct oyCMMapi_s oyCMMapi_s;

CMM Registration

/** @brief the CMM API resources struct to implement and set by a CMM
*  Given an example CMM with name "little cms", and it wants to use the 
*  four-char ID 'lcms', the CMM can register itself to Oyranos as follows:
*  The CMM module file must be named
*  something_lcms_cmm_module_something.something .
*  On Linux this could be "liboyranos_lcms_cmm_module.so.0.1.8".
*  The four-chars 'lcms' must be prepended with OY_MODULE_NAME alias
*  "_cmm_module".
*  Oyranos will scan the $(libdir)/color/cmms/ path, opens the available 
*  CMM's from this directory and extracts the four-chars before OY_MODULE_NAME
*  from the library names. Module paths can be added through the
*  OY_MODULE_PATHS environment variable.
*  Oyranos looks for a symbol to a oyCMMInfo_s struct of the four-byte ID plus
*  OY_MODULE_NAME which results in our example in the name "lcms_cmm_module".
*  On Posix system this should be loadable by dlsym.
*  The lcms_cmm_module should be of type oyCMMInfo_s with the type field
*  and all other fields set appropriately.
*  The api field is a placeholder to get a real api struct assigned. If the CMM
*  wants to provide more than one API, they can be chained. The apis_n member
*  is to be set to the number of APIs.
*  @since Oyranos: version 0.1.8
*  @date  5 december 2007 (API 0.1.8)
typedef struct {
 oyOBJECT_TYPE_e  type;               /*!< struct type oyOBJECT_TYPE_CMM_INFO_S */
 icSignature      cmmId;              /*!< ICC signature 'lcms' */
 oyChar         * backend_version;    /*!< non localisable 'v1.17' */
 oyName_s         name;               /*!< localisable 'lcms' 'little cms' '...'*/
 oyName_s         manufacturer;       /*!< localisable 'Marti' 'Marti Maria' 'support email: @; internet: www.littlecms.com; sources: ...' */
 oyName_s         copyright;          /*!< localisable 'MIT','MIT License'.. */
 int              oy_compatibility;   /*!< last supported Oyranos CMM API : OYRANOS_VERSION */

 oyCMMapi_s     * api;                /**< must be casted to a according API */
 int              apis_n;             /**< count of implemented apis */

 oyIcon_s         icon;               /*!< zero terminated list of a icon pyramid */
} oyCMMInfo_s;
typedef void* oyPointer;
typedef oyPointer (*oyStructCopyF_t) ( oyPointer, oyPointer );
typedef int       (*oyStructReleaseF_t) ( oyPointer* );
/** @brief CMM pointer
*  The oyCMMptr_s is used internally and for CMM's.
*  Memory management is done by Oyranos' oyAllocateFunc_ and oyDeallocateFunc_.
*  @since Oyranos: version 0.1.8
*  @date  november 2007 (API 0.1.8)
typedef struct {
 oyOBJECT_TYPE_e      type;           /*!< internal struct type oyOBJECT_TYPE_CMM_POINTER_S */
 char                 cmm[5];         /*!< the 4 char CMM ID 'lcms' */
 char                 func_name[32];  /*!< optional the CMM's function name */
 oyPointer            ptr;            /*!< a CMM's data pointer */
 char                 resource[5];    /**< the resource type 'oyPR' */
 oyStructReleaseF_t   ptrRelease;     /*!< CMM's deallocation function */
 int                  ref;            /**< Oyranos reference counter */
} oyCMMptr_s;

Pixel Layout

typedef enum {
 oyUINT8,     /*!<  8-bit integer */
 oyUINT16,    /*!< 16-bit integer */
 oyUINT32,    /*!< 32-bit integer */
 oyHALF,      /*!< 16-bit floating point number */
 oyFLOAT,     /*!< IEEE floating point number */
 oyDOUBLE     /*!< IEEE double precission floating point number */

The below pixel description is slightly different from lcms layout as it relies on the addtional profile information, to avoid concurrent definitions for the colour channels.

/** @type oyPixel_t parametric uint32_t integer as shorthand for the channel layout in bitmaps
   should fit into a 32bit type, usual unsigned int or uint32_t

   C  channels count per pixel (3 for RGB); max 255
   O  colour channel offset (0 for RGB, 1 for ARGB)
   P  Planar bool: 0 - interwoven, 1 - one channel after the other   
   S  Swap colour channels bool (BGR)
   T  Type oyDATATYPE_e
   X  non host byte order
   F  Revert bool: 0 - MinIsBlack(Chocolate) 1 - MinIsWhite(Vanilla);
      exchange min and max : (1-x)
/** @brief CMM capability query enum
*  Since: 0.1.8
typedef enum {
 oyQUERY_OYRANOS_COMPATIBILITY,       /*!< provides the Oyranos version and expects the CMM compiled or compatibility Oyranos version back */
 oyQUERY_PIXELLAYOUT_DATATYPE,        /*!< takes a oyDATATYPE_e arg as value */
 oyQUERY_HDR,                         /*!< value a oyDATATYPE_e (oyHALF...) */
 oyQUERY_PROFILE_FORMAT = 20          /*!< value 1 == ICC */

typedef int      (*oyCMMCanHandle_t) ( oyCMMQUERY_e        type,
                                      int                 value );
typedef int      (*oyCMMInit_t)      ( void );
typedef int      (*oyCMMProfile_Open_t)( oyPointer         block,
                                      size_t              size,
                                      oyCMMptr_s        * oy );
#define oyCMM_PROFILE "oyPR"
typedef void     (*oyCMMProgress_t)  ( int                 ID,
                                      double              progress );

typedef icSignature (*oyCMMProfile_GetSignature_t) (
                                      oyCMMptr_s        * cmm_ptr,
                                      int                 pcs);

/*oyPointer          oyCMMallocateFunc ( size_t              size );
void               oyCMMdeallocateFunc(oyPointer           mem );*/
typedef int      (*oyCMMMessageFuncSet_t)( oyMessageFunc_t message_func );

/** @brief the generic part if a API to implement and set by a CMM
*  @since Oyranos: version 0.1.8
*  @date  12 december 2007 (API 0.1.8)
struct oyCMMapi_s {
 oyOBJECT_TYPE_e  type;               /**< struct type oyOBJECT_TYPE_CMM_API1_S */
 oyCMMapi_s     * next;
 oyCMMInit_t      oyCMMInit;
 oyCMMMessageFuncSet_t oyCMMMessageFuncSet;

As the following functions are too inflexible I will propose a different design below.

typedef int      (*oyCMMColourConversion_Create_t) (
                                      oyCMMptr_s       ** cmm_profile_array,
                                      int                 profiles_n,
                                      int                 pixel_layout_in,
                                      int                 pixel_layout_out,
                                      int                 intent,
                                      int                 proofing_intent,
                                      int                 flags,
                                      oyCMMptr_s        * oy );
typedef int      (*oyCMMColourConversion_Run_t)(
                                      oyCMMptr_s        * cmm_transform,
                                      oyPointer           in_data,
                                      oyPointer           out_data,
                                      int                 count,
                                      oyCMMProgress_t     progress );

/** @brief the API 1 to implement and set by a CMM
*  @since Oyranos: version 0.1.8
*  @date  5 december 2007 (API 0.1.8)
typedef struct {
 oyOBJECT_TYPE_e  type;               /**< struct type oyOBJECT_TYPE_CMM_API1_S */
 oyCMMapi_s     * next;
 oyCMMInit_t      oyCMMInit;
 oyCMMMessageFuncSet_t oyCMMMessageFuncSet;
 oyCMMCanHandle_t oyCMMCanHandle;     
 oyCMMProfile_Open_t oyCMMProfile_Open;
 oyCMMProfile_GetText_t oyCMMProfile_GetText;
 oyCMMProfile_GetSignature_t oyCMMProfile_GetSignature;
 //oyCMMColourConversion_Create_t oyCMMColourConversion_Create;
 //oyCMMColourConversion_Run_t oyCMMColourConversion_Run;
} oyCMMapi1_s;


The oyCMMColourConversion_Create_t function seems the most complex one. It covers the profile set, proofing, rendering intent, precission, gamut checking and so on. The provided oyCMMptr_s "oy" struct can be used to store computational intentsive data from the CMM. It has to be filled in with the appropriate values, a CMM specific pointer and release function and can contain additional hints to allow some runtime typechecking in both the CMM itself and Oyranos.

typedef int      (*oyCMMColourConversion_Create_t) (
                                      oyCMMptr_s       ** cmm_profile_array,
                                      int                 profiles_n,
                                      int                 pixel_layout_in,
                                      int                 pixel_layout_out,
                                      int                 intent,
                                      int                 proofing_intent,
                                      int                 flags,
                                      oyCMMptr_s        * oy );

The pixel_layout_xx is described in the "parametric enum" comment. The profiles need to follow a certain order to make sense for proofing. Or it would be more secure to have the proofing profile as a extra parameter? The intent and proofing intent should be clear.

The flags are open to discuss. They need to cover gamut checking and the BPC algorithm. Optionally it could cover the no precalculation flag known from lcms to increase precision, selection of the interpolation (tetrahedral versus trilinear) and other speed flags.

K channel preservation stuff?


What is missed or is unclear, at least to me, are the CIE CAM02 handling, on disk transform caching and profile creation in the API.

Probably it is enough to handle the last points later.

The oyCMMCanHandle_t function will be merged as a static field into the oyCMMapi1_s struct.

Proposal B

As mentioned above the oyColourConversion_s has far too many options and too less flexibility. To provide a easy way for creation and later extention, I want to separate the options and introduce theire layout as a XML representation. This appies for oxColourConversion_s objects.


The above design models completely the ICC CMM approach, with a clean separation of colour space manipulations and no image manipulations. The later might cover neighbour pixels. However with HDR content becoming usual, and other requirements it seems useful to combine pure colour manipulations and image ones. The proposed filter architecture allowes for chaining of colour spaces, as typical for simple colour conversions, proofing and colour effects. With the complete access to image information it is as well possible to apply in the same way tonemapping and other generic filters.

Changes to A

The filter architecture is mostly similiar to the in A outlined, except that oyColourConversion_s, the higher level objects, are exchanged by oyFilter_s and oyConversions_s objects. oyPixel_t, oyImage_s and oyProfile_s remain as in A.

Filter Design

Filter are sets of functions inside a CMM. They are different endities and implement various functions. To make a proper selection, according to their character they contain a filter type information.

typedef enum {
  oyFILTER_TYPE_COLOUR,                /**< colour */
  oyFILTER_TYPE_TONEMAP,               /**< contrast or tone mapping */
  oyFILTER_TYPE_GENERIC                /**< generic */

For traditional usage a oyFILTER_TYPE_COLOUR would be needed. To map HDR to LDR ranges a oyFILTER_TYPE_TONEMAP would be selected. Applying other corrections could be done with oyFILTER_TYPE_GENERIC.

To match the CMM to ICC terminology the COLOUR and TONEMAP oyFilter_s can exist only one per library module or plug-in. The GENERIC filters can occure multiple times as these may provide very different functions. To difference functions Filters contain a category name field to describe the usage. Category strings are similiar to directory names with backslashes. The category prefix is derived from the CMM name.

Filters provide very simple interfaces. They take data oyCMM_ptr's for profiles, oyImage_s objects and XML formatted options. For interactive options the need arrises to provide a XForms UI description and default values. To allow for standard options, which are typical for colour conversions, Oyranos adds in the middle a standards XForms UI description to the possible CMM provided options filled with defaults.

Filters are separate to keep them simple. They dont know about parents or children. Nethertheless they consist themself of filters by deploying the Oyranos frontend oyConversions_s and oyFilter_s API's.

Conversion Design

To build a chain a root filter has to be created from an oyImage_s. As a oyImage_s must have a oyProfile already set, the contruction is very simple. Then more filters can be selected to chain for processing. The big question is how does they fit, given a lot of properties like sample types, colour spaces, pixel layouts and so on. One possibility is to use only fitting filters. This would be of course very limitting. The other way is to provide one adapter and let him decide by the input and output property informations of the filter what representation is required to let the filter successfully work. This might become expensive especially for colour transformations and random pixel access.


Interactive usage of a Oyranos filter:

Image -> select filter by usage type and category ->
  display XForms options obtained from the filter in native toolkit widgets ->
    the toolkit layer returns the GUI results as XForms model->
      Oyranos forward the XForms model to the filter ->
        XForms model validation by the filter -> apply filter to image

A relation of OpenRaster XML to Oyranos could contructed as follows:

  • parse the OpenRaster XML tree
  • identify a filter (openraster:filter:standard:name),
  • Oyranos internally looks up the according filter, e.g. in "liboyranos_oysf_module.so" in lib/color/cmms
  • XSL transform the openraster:filter:xxx to xmlns:xf and
  • pass the resulting xf:model to the filter, and checks the filters own validation result
    • possibly tries an other filter in case the match is not perfect
  • chain the filter with a image and process for the output

Of course Oyranos can not cover the whole tree at once as it is not designed with compositing in mind. But single steps or one layer image files with various filters could be completely processed with Oyranos.