Color Management Manual

Convert color data between color spaces and color formats provided by the maxon API or loaded from disk.

Overview

In digital content creation it is often necessary to convert colors between color spaces to ensure faithful representations of colors in different in- and output contexts. A color space defines a representable set of colors with a dedicated purpose. Such purposes could for example be to define a set of colors

  • which is perceivable by humans, as for example the CIE 1931 space,
  • which a physical device such as a computer display can reproduce, as for example the sRGB space,
  • or which lends itself well for a specific task, as for example the ACEScg space which is specifically intended for CGI tasks.

The abstract concept of a color space is often implemented in the form of color profiles, most notably in form of International Color Consortium (ICC) color profiles. When colors defined by a color profile must be stored in memory or a file, a color storage format must be chosen, answering questions such as:

  • With how many channels is the color being expressed?
    1. E.g., three channels for RGB
    2. E.g., four channels for CMYK.
  • With which data type and bit depth are color channels expressed?
    1. As integer or floating point values.
    2. 4, 8, 16, or 32 bits per channel or the channels of a pixel using different bit depths.
  • With which memory layout?
    1. An RGB value could be written as RGB or BGR.
    2. In a packed format where the data for one pixel is written consecutively and occupies one location in memory.
    3. In a planar format where the data for one pixel is spread over planes and does not occupy one location in memory.

The act of producing, using, and converting between such color spaces and formats is called color management. Another key concept within color management is gamma correction. Humans perceive intensity values non-linearly (Fig. I), while raw data, as provided by a camera or generated by a render engine, is often stored in a linear format.

Fig. I: One the left - An illustration for the human perception of intensity. Shown are four light sources, each placed in a box. The light sources are measured in a fictitious linearly scaling physical unit of intensity. Most readers will be able to easily distinguish the light sources with 0.1 and 0.2 units of intensity, but at the same time have difficulties to distinguish the two light sources with 0.5 and 0.6 units of intensity, although they are separated by the same distance. This is due to the non-linearly scaling human perception of brightness/intensity which is more sensitive towards lower values. One the right - A linear (γ = 1.0) gamma curve in blue and a non-linear (γ = 2.2) gamma curve in pink. Values on each curve can be mapped to to each other and a gamma of 2.2 is often considered to be a good approximation of human perception. A linear value of 50% is perceived as roughly 22%. This demonstrates the relation of linear and non-linear color profiles for color spaces.

Color profiles capture this phenomenon in the form of gamma values. In practice, color profiles use a more complex approach called tristimulus values which encodes sensitivity over the three cone cell types of the human eye but this is often hidden away by high level APIs; roughly speaking, tri-stimulus values separate sensitivity over different bins of wave-lengths of light which are handled by one of the cone cell types. When color profiles are associated with gamma values, there is then often the notion of linear and non-linear profiles. A color profile is being considered linear when it has a gamma value of exactly 1.0; and it is being considered non-linear when it has a gamma value not equal to one.

When a set of color values is converted to such non-linear profile, the operation is in non-linear transform, the elements of the converted set will no longer be in the same distance relations as the elements in the original set. Taking the example from Fig. I, the list of greyscale values [0.1, 0.2, 0.5, 0.6] can be converted with a gamma value of 2.2 into values that more closely align with the human perception of these linear values. The first two values 0.35 and 0.48 are clearly separated, while the last two values 0.73 and 0.79 are almost identical, the distance between these two pairs is no longer the same, as the operation was non-linear. In the inverse operation, one can choose a in a non-linear space and convert them to a set of values which would be considered by humans visually linear within a linear space (Fig. II).

Fig. II: Converting the list [0.1, 0.2, 0.5, 0.6] from linear to non-linear space will yield a representation of how humans perceive these values, while converting the same list defined in non-linear space to linear space, will yield a set of visually linear values which can be used in environments which require using linear color spaces.

Technical Overview

The image.framework provides two high level color management entry points in the form of namespaces.

  • maxon::ColorSpaces : Provides access to abstract color space implementations as for example RGB or CMYK.
  • maxon::PixelFormats : Provides access to pixel formats for these color spaces as for example RGB, BGR, or CMYK.

A color space is represented by the type ColorSpaceInterface and provides only two methods:

A color profile is represented by the type maxon::ColorProfileInterface and its most important methods are:

  • GetColorSpace: Returns the color space for which the profile is valid.
  • CheckCompatiblePixelFormat: Tests if the profile is compatible with the given pixel format.
  • GetInfo: Retrieves metadata stored in a profile as for example its manufacturer or description string.
  • HasProfile: Returns if the profile is valid or not.
  • OpenProfileFromFile: Instantiate a color profile object from an ICC file.
  • CreateProfile: Instantiate a profile from an OCIO display and view profile.

The classic API type ColorProfile is a thin wrapper around the maxon API type maxon::ColorProfileInterface and the wrapped object can be get and set with the methods GetInternalProfile and SetInternalProfile. For classic API types such as BaseBitmap and ShaderData, one must still pass or retrieves the classic API type, but for computations one should always use the maxon API type.

Warning
The reference to a maxon::ColorProfileInterface must not be confused with the classic API type ColorProfile as both are named ColorProfile. Functions signatures in the image.framework documentation which return or accept a reference to an maxon::ColorProfileInterface instance, unfortunately often link incorrectly to the classic API type. Meant is here always the interface reference, not the classic API type.

Color data associated with a color profile and a pixel format can be converted to a different color profile and/or pixel format with the type ColorProfileConvertInterface, its most important methods are:

  • Convert: Convert data from a pixel data input buffer to an output buffer.
  • Init: Initializes a converter for CPU color conversions.
  • InitGPU : Initializes a converter for GPU color conversions.

The method ColorProfileConvertInterface::Convert makes use of the maxon::ImageBufferTemplate buffer handler aliases:

This construction allows for operating on raw pixel data stored in collection types as maxon::Block or maxon::BaseArray, as opposed to being tied to a specific bitmap data interface as BaseBitmap or ImageBaseInterface. When initializing buffer handlers, it is often most convenient to use the largest possible channel size, maxon::Pix, even when the channel data of a pixel is smaller, as data will then be simply written with a stride.

Related Topics

Articles OpenColorIO Manual Convert colors with the color profiles provided by the OpenColorIO (OCIO) configuration of a scene.
Images Manual Read and write pixel data with an abstract bitmap interface which can be serialized into multiple file formats.
BaseBitmap Manual

Read and write pixel data with an abstract bitmap interface which can be used to display image data in GUIs.

Important API Entities maxon::ColorSpaces Provides access to abstract color space implementations as for example RGB or CMYK.
maxon::PixelFormats Provides access to pixel formats for color spaces as for example RGB, BGR, or CMYK.
maxon::ColorSpaceInterface Represents an abstract color space over a linear and non-linear color profile for the space.
maxon::ColorProfileInterface Represents a concrete color space to carry out color conversions with.
ColorProfile Wraps the type ColorProfileInterface for classic API interfaces.
maxon::ColorProfileConvertInterface Converts pixel data between two color profiles and/or pixel formats.
maxon::ImageBufferTemplate Wraps raw pixel-array buffers for methods in the image.framework.
maxon::ImageBaseInterface Represents a bitmap which can be modified and read and written from disk.
BaseBitmap

Wraps the type maxon::ImageBaseInterface, adds drawing functionalities and can be drawn directly into GUIs.

SDK Plugins Color Management Plugin

Contains the examples shown in this manual.

Examples Instantiate Builtin Color Profiles Instantiate color profiles from the builtin color spaces and pixel formats.
Instantiate Color Profiles from ICC Files Instantiate color profiles from ICC color profile files and the builtin pixel formats.
Read Color Profile Metadata Read color profile metadata such as description strings and supported pixel formats.
Write Color Profiles Write a color profile object to an ICC color profile file on disk.
Access Pixel Formats Access builtin pixel formats and their associated metadata, such as formatting groups, number of channels, and memory layout per channel.
Color Convert Singular Pixels Convert color data pixel by pixel with color profiles and/or pixel formats.
Color Convert Chunks of Pixel Data Convert color data in chunks of pixels with color profiles and/or pixel formats.
Color Convert Bitmap Data Read color data from a bitmap to a buffer and convert this buffer with color profiles and/or pixel formats.
Use Color Conversion Utilities Convert colors between common color representations such as RGB, HSL, and CMYK.

Examples

The examples shown here are all part of the Color Management Plugin in the SDK. Once the SDK has been compiled and installed, the plugin can be found under the menu entry Extensions\Example.image\C++ SDK: Color Management Examples.

In order to run the Color Management Plugin plugin, one must provide two ICC files named sRGB2014.icc and D65_XYZ.icc in the directory /plugins/example.image/source of the extended SDK (Fig. III ). Any ICC files can be used in principal, but the intended files are the ICC reference profiles sRGB2014.icc and D65_XYZ.icc from www.color.org (last retrieved 08/11/2022). As of Cinema 4D 2023.2, the color profiles are included with the Extended C++ SDK, but not yet with the SDK delivered with a Cinema 4D installation.

Fig. III: Place the two ICC files in the example.image/source directory next to the examples_color_management.cpp file which is using them.

Instantiate Builtin Color Profiles

Instantiate color profiles from the builtin color spaces and pixel formats.

maxon::Result<void> GetColorProfilesFromColorSpaces(
{
// Cinema 4D provides multiple color spaces which are exposed in the #ColorSpaces namespace, being
// accessed is here the RGB color space.
const maxon::ColorSpace rgbSpace = maxon::ColorSpaces::RGBspace();
// Each color space has a default linear and non-linear color profile associated with it. The
// linear profile will have a gamma of exactly 1.0, while the non-linear will have a gamma not
// equal to 1.0; in many cases it will be a gammma of ~2.2.
const maxon::ColorProfile rgbProfile = rgbSpace.GetDefaultLinearColorProfile();
const maxon::ColorProfile nlRgbProfile = rgbSpace.GetDefaultNonlinearColorProfile();
// Retrieve default linear color profile associated with the #GREYspace color space and insert
// both the default linear profile for the RGB and Grey space into #collection to pass these
// color profiles to the other Color Management examples.
const maxon::ColorSpace greySpace = maxon::ColorSpaces::GREYspace();
const maxon::ColorProfile greyProfile = greySpace.GetDefaultLinearColorProfile();
collection.Insert("rgbProfile"_s, rgbProfile) iferr_return;
collection.Insert("greyProfile"_s, greyProfile) iferr_return;
ApplicationOutput("\tLoaded builtin color profiles: @, @", rgbProfile, greyProfile);
return maxon::OK;
}
Definition: hashmap.h:1119
ResultRef< Entry > Insert(KEY &&key, const V &value, Bool &created=BoolLValue())
Definition: hashmap.h:1611
return OK
Definition: apibase.h:2690
#define MAXON_FUNCTIONNAME
This macro returns the function name of the current member or global function.
Definition: objectbase.h:2860
#define ApplicationOutput(formatString,...)
Definition: debugdiagnostics.h:210
#define iferr_scope
Definition: resultbase.h:1384
#define iferr_return
Definition: resultbase.h:1519

Instantiate Color Profiles from ICC Files

Instantiate color profiles from ICC color profile files and the builtin pixel formats.

maxon::Result<void> GetColorProfilesFromFile(
{
// Color profiles can also be loaded from ICC files, memory, and OCIO configurations. This
// example uses two ICC reference color profiles, the 'sRGB2014' and the 'D65_XYZ' profile.
// Due to copyright restrictions, these files cannot be provided with the SDK.
//
// "sRGB2014.icc": https://www.color.org/srgbprofiles.xalter
// "D65_XYZ.icc": https://www.color.org/XYZprofiles.xalter
//
// To run this example, you must download these files and put them next to this cpp file.
// Construct the URLs for the two ICC profiles next to this cpp file.
const maxon::Url directory = maxon::Url(maxon::String(__FILE__)).GetDirectory();
const maxon::Url srgbIccFile = (directory + "sRGB2014.icc"_s) iferr_return;
const maxon::Url xyzIccFile = (directory + "D65_XYZ.icc"_s) iferr_return;
if (srgbIccFile.IoDetect() == maxon::IODETECT::NONEXISTENT)
return maxon::IoError(MAXON_SOURCE_LOCATION, srgbIccFile, "Could not access sRGB2014.icc profile."_s);
if (xyzIccFile.IoDetect() == maxon::IODETECT::NONEXISTENT)
return maxon::IoError(MAXON_SOURCE_LOCATION, xyzIccFile, "Could not access D65_XYZ.icc profile."_s);
// Instantiate color profiles with these two ICC files and insert the profiles into #collection.
const maxon::ColorProfile srgbIccProfile = maxon::ColorProfileInterface::OpenProfileFromFile(
srgbIccFile) iferr_return;
const maxon::ColorProfile xyzIccProfile = maxon::ColorProfileInterface::OpenProfileFromFile(
xyzIccFile) iferr_return;
collection.Insert("sRGB2014"_s, srgbIccProfile) iferr_return;
collection.Insert("D65_XYZ"_s, xyzIccProfile) iferr_return;
ApplicationOutput("\tLoaded ICC color profiles: sRGB2014.icc, D65_XYZ.icc");
return maxon::OK;
}
static MAXON_METHOD Result< ColorProfile > OpenProfileFromFile(const Url &fn)
Definition: string.h:1235
Definition: url.h:952
#define MAXON_SOURCE_LOCATION
Definition: memoryallocationbase.h:67
@ NONEXISTENT
Url doesn't exist.

Read Color Profile Metadata

Read color profile metadata such as description strings and supported pixel formats.

maxon::Result<void> GetColorProfileMetadata(
{
// A message string used by the example.
const maxon::Char* msg =
"\tkey: '@', profile: '@'\n"
"\tname: '@', description: '@'\n"
"\tmodel: '@', manufacturer: '@'\n"
"\tisvalid: '@', space: '@'\n"
"\thashCode: '@', crc: @\n";
// Iterate over all keys in the passed hashmap.
for (const maxon::String& key : collection.GetKeys())
{
// Get the profile associated with the key.
const maxon::ColorProfile& profile = collection.FindValue(key).GetValue() iferr_return;
// The metadata associated with the profile. The model and manufacturer fields are often not
// populated, and the name and description field often have the same value.
const maxon::String description = profile.GetInfo(maxon::COLORPROFILEINFO::DESCRIPTION);
const maxon::String model = profile.GetInfo(maxon::COLORPROFILEINFO::MODEL);
const maxon::String manufacturer = profile.GetInfo(maxon::COLORPROFILEINFO::MANUFACTURER);
// HasProfile() tests if the profile is not a null-reference, and the hash code and crc identify
// a profile. For builtin profiles provided through maxon::ColorSpaces, the CRC and hash will
// be identical, for profiles loaded from a file, this will not be the case.
const maxon::Bool isvalid = profile.HasProfile();
const maxon::HashInt hashCode = profile.GetHashCode();
const maxon::Int32 crc = profile.GetCrc();
// Get the color space associated with the profile, e.g., maxon::ColorSpaces::RGBspace(), and
// print out the meta data associated with this profile.
const maxon::ColorSpace space = profile.GetColorSpace();
ApplicationOutput(msg, key, profile, name, description, model, manufacturer, isvalid, space,
hashCode, crc);
}
return maxon::OK;
}
PyObject * key
Definition: abstract.h:289
const char const char * name
Definition: abstract.h:195
KeyIterator GetKeys()
Definition: hashmap.h:2424
Opt< V & > FindValue(const KEY &key)
Definition: hashmap.h:1372
char Char
signed 8 bit character
Definition: apibase.h:184
bool Bool
boolean type, possible values are only false/true, 8 bit
Definition: apibase.h:181
UInt HashInt
Definition: apibase.h:337
int32_t Int32
32 bit signed integer datatype.
Definition: apibase.h:176
@ DESCRIPTION
Description of the color profile.
@ MANUFACTURER
Manufacturer of the color profile.
@ MODEL
Model of the color profile.
@ NAME
Name of the color profile.
const char const char * msg
Definition: object.h:438

Write Color Profiles

Write a color profile object to an ICC color profile file on disk.

This can also be done with the builtin profiles or any maxon::ColorProfileInterface reference exposed in the APIs. With Cinema 4D 2023.2, this will make it for example possible to serialize an OCIO Display + View transform to disk.

maxon::Result<void> WriteColorProfileToFile()
{
// Get the non-linear profile for the RGB space.
const maxon::ColorSpace space = maxon::ColorSpaces::RGBspace();
const maxon::ColorProfile profile = space.GetDefaultNonlinearColorProfile();
// Construct a URL for an ICC file next to this cpp file and write it to disk, written is
// here effectively an sRGB-2.2 ICC profile from the builtin color RGB color space.
const maxon::Url directory = maxon::Url(maxon::String(__FILE__)).GetDirectory();
const maxon::Url url = (directory + "myProfile.icc"_s) iferr_return;
profile.WriteProfileToFile(url) iferr_return;
ApplicationOutput("\tWrote @ profile to: @", profile, url);
return maxon::OK;
}

Access Pixel Formats

Access builtin pixel formats and their associated metadata, such as formatting groups, number of channels, and memory layout per channel.

maxon::Result<void> GetPixelFormats()
{
// Color profiles can support multiple pixel formats which primarily express a memory layout for
// pixel data. A color in the RGB space can for example be expressed as a tuple of 16 or 32 bit
// floating point numbers among other formats.
//
// Pixel formats can be accessed via the maxon::PixelFormats namespace to which namespace groups
// for the principal color spaces, e.g., RGB, are attached. Each of these groups then contains
// all the pixel formats associated with that color space.
// The (Float32, Float32, Float32) pixel format for the RGB space.
maxon::PixelFormat pixRgbF32 = maxon::PixelFormats::RGB::F32();
// A pixel format is associated with one of the principal color spaces, RGBspace in this case.
const maxon::ColorSpace colorSpace = pixRgbF32.GetColorSpace();
// A pixel format has a default color profile associated with it, in this case the default
// linear profile of the RGB space.
maxon::ColorProfile rgbDefaultProfile = pixRgbF32.GetDefaultColorProfile();
"\tThe pixel format '@' is in the color space '@' and has the default color profile: '@'.",
pixRgbF32, colorSpace, rgbDefaultProfile);
// And a color profile can be tested for supporting a specific pixel format with the method
// CheckCompatiblePixelFormat().
const maxon::String msg("\tThe profile '@' supports the pixel format '@'.");
if (rgbDefaultProfile.CheckCompatiblePixelFormat(maxon::PixelFormats::RGB::F32()))
ApplicationOutput(msg, rgbDefaultProfile, maxon::PixelFormats::RGB::F32());
if (rgbDefaultProfile.CheckCompatiblePixelFormat(maxon::PixelFormats::RGB::F16()))
ApplicationOutput(msg, rgbDefaultProfile, maxon::PixelFormats::RGB::F16());
// A pixel format is associated with a PixelFormatGroup which binds associated pixel formats
// together. One can iterate over that group with PixelFomatGroupInterface::GetEntries().
const maxon::PixelFormatGroup formatGroup = pixRgbF32.GetPixelFormatGroup();
for (const maxon::PixelFormat& pixelFormat : formatGroup.GetEntries())
ApplicationOutput("\t\t'@' is a member of the pixel format group '@'.", pixelFormat, formatGroup);
// One of the core functionalities of a pixel format is to describe the memory layout of that
// format. This will become relevant when pixels must be converted between formats and/or color
// spaces.
// The number of channels/components of this pixel format, three in this case.
const maxon::Int channelCount = pixRgbF32.GetChannelCount();
// The size of each channel in bits, the block [32, 32, 32] in this case, there exists also an
// alias for the Block<Bits> return type, maxon::ChannelOffsets.
const maxon::Block<const maxon::BITS> channelSizes = pixRgbF32.GetChannelOffsets();
// The total size of a pixel in bits, i.e., the sum of GetChannelOffsets().
const maxon::BITS pixelSize = pixRgbF32.GetBitsPerPixel();
ApplicationOutput("\t CannelCount: @, Offsets: @, BitsPerPixel: @", channelCount, channelSizes, pixelSize);
// A pixel format also provides access to image channels, a more precise description of each
// channel. In most cases accessing these from a pixel format is not required.
for (const maxon::ImageChannel& channel : pixRgbF32.GetChannels())
{
// The size of the pixel channel, i.e., component, in bits.
const maxon::BITS bits = channel.GetChannelBits();
// The channel type which provides among other things access to the associated color space and
// the default value for this channel for a pixel.
const maxon::ImageChannelType channelType = channel.GetChannelType();
// The data type of the channel, e.g., Float32.
const maxon::DataType dataType = channel.GetDataType();
ApplicationOutput("\t\tRGB::F32 channel '@' - Bits: @, ChannelType: @, DataType: @",
channel, bits, channelType, dataType);
}
return maxon::OK;
}
This class represents the number of bits in a pixel format.
Definition: gfx_image_bits.h:15
Definition: block.h:423
Definition: datatypebase.h:772
Int64 Int
signed 32/64 bit int, size depends on the platform
Definition: apibase.h:188

Color Convert Singular Pixels

Convert color data pixel by pixel with color profiles and/or pixel formats.

This pixel-by-pixel approach is not (considerably) less efficient than converting data in blocks and in fact the dominant form of usage within our own APIs. The advantage is that with this approach one can be less verbose in setting up the conversion handlers.

maxon::Result<void> ConvertSinglePixelWithColorProfile(
{
// Get the two ICC profiles loaded in the GetColorProfiles() example.
maxon::ColorProfile srgbProfile = collection.FindValue("sRGB2014"_s).GetValue() iferr_return;
maxon::ColorProfile xyzProfile = collection.FindValue("D65_XYZ"_s).GetValue() iferr_return;
// Select a pixel format for the conversion, the XYZ profile operates internally in RGB format
// and in this case also no memory layout conversion is desired, so the same pixel format can be
// used for the in- and output buffer.
const maxon::PixelFormat rgbFormat = maxon::PixelFormats::RGB::F32();
// Construct a converter that converts from sRGB2014 to D65_XYZ space.
const maxon::ColorProfileConvert converter = maxon::ColorProfileConvertInterface::Init(
rgbFormat, srgbProfile, rgbFormat, xyzProfile,
// Initialize an input buffer for a single pixel in RGB space (pure red) and a nulled output
// buffer for XYZ space.
maxon::Color32 input{ 1, 0, 0 };
maxon::Color32 output{ 0, 0, 0 };
// Now one must wrap both buffers in buffer handlers. ImageConstBuffer wraps read-only input
// buffers, while ImageMutableBuffer wraps read-write output buffers.
// Wrap the input and output buffers. A pointer to the first component of the to be converted
// color is being passed as the buffer arguments. Since here only a conversion for single pixel
// is being carried out, the simple (buffer, pixel format) constructor of ImageBufferTemplate
// can be used. For converting arrays of pixels, a more complex constructor must be used. See
// ConvertPixelArrayWithColorProfile() for details.
(const maxon::Pix*)&input.r, maxon::PixelFormats::RGB::F32());
(maxon::Pix*)&output.r, maxon::PixelFormats::RGB::F32());
// Convert 1 pixel in #bufferIn to #bufferOut and output the result. Trying to convert more than
// one pixel will fail with the ImageBufferTemplate constructors used in this example.
converter.Convert(bufferIn, bufferOut, 1) iferr_return;
ApplicationOutput("\tIn (sRGB2014): @, Out (D65_XYZ): @", input, output);
return maxon::OK;
}
static MAXON_METHOD Result< ColorProfileConvert > Init(const PixelFormat &srcPixelFormat, const ColorProfile &srcProfile, const PixelFormat &dstPixelFormat, const ColorProfile &dstProfile, COLORCONVERSIONINTENT intent, COLORCONVERSIONFLAGS flags)
Py_ssize_t char * output
Definition: unicodeobject.h:985
ImageBufferTemplate< PixelConstBuffer > ImageConstBuffer
Definition: gfx_image_pixelformat.h:185
UChar Pix
unspecified pixel format depth.
Definition: gfx_image_imagechannel.h:17
ImageBufferTemplate< PixelMutableBuffer > ImageMutableBuffer
Definition: gfx_image_pixelformat.h:186
A color consisting of three components R, G and B.
Definition: col.h:16
Several functions use this helper structure to pass the image data to functions.
Definition: gfx_image_pixelformat.h:521

Color Convert Chunks of Pixel Data

Convert color data in chunks of pixels with color profiles and/or pixel formats.

This example also picks up the linear to non-linear space conversions example [0.1, 0.2, 0.5, 0.6] of the Color Management Manual.

maxon::Result<void> ConvertManyPixelWithColorProfile()
{
// Other than in the ConvertSinglePixelWithColorProfile, here the color profiles are being
// constructed with the default profiles provided by the Image API.
// Retrieve GREY color space.
const maxon::ColorSpace greySpace = maxon::ColorSpaces::GREYspace();
// Get both the default linear and non-linear profile for the GREY space and the single
// precision floating point pixel format for the space.
const maxon::ColorProfile linGreyProfile = greySpace.GetDefaultLinearColorProfile();
const maxon::ColorProfile nonlinGreyProfile = greySpace.GetDefaultNonlinearColorProfile();
const maxon::PixelFormat greyFormat = maxon::PixelFormats::GREY::F32();
// Construct a converter that converts from linear GREY to non-linear GREY space.
const maxon::ColorProfileConvert converter = maxon::ColorProfileConvertInterface::Init(
greyFormat, linGreyProfile, greyFormat, nonlinGreyProfile,
// Initialize an input and output buffer for the conversion, #inBufferData holds the values
// [0.1, 0.2, 0.5, 0.6] from the Color Management Manual example, the output buffer is just
// resized to match the length of #inBufferData.
const maxon::Block<const maxon::Float32> inputBuffer { 0.1f, 0.2f, 0.5f, 0.6f };
nonLinOutputBuffer.Resize(4) iferr_return;
// To carry out the conversions, the buffers must be wrapped in buffer handlers. ImageConstBuffer
// wraps read-only input buffers, while ImageMutableBuffer wraps read-write output buffers.
// Since other than in the ConvertPixelWithColorProfile() example not only a single pixel is
// being converted, but an array of them, a more complex constructor for ImageBufferTemplate
// must be used to wrap both buffers. Aside from the first argument for the start of the buffer,
// passed in is also the specific memory layout for the array of pixels. Which in this case is
// taken from the PixelFormatInterface instances themselves. If so desired, the formats could be
// customized, e.g., writing a single channel GREY space value to each fourth component of a
// four channel output buffer.
(const maxon::Pix*)(inputBuffer.GetFirst()),
greyFormat.GetBitsPerPixel(), greyFormat.GetChannelOffsets(), greyFormat);
(maxon::Pix*)(nonLinOutputBuffer.GetFirst()),
greyFormat.GetBitsPerPixel(), greyFormat.GetChannelOffsets(), greyFormat);
// Convert 4 pixels in #inputBuffer to #nonLinOutputBuffer, converting the data from linear
// grey to non-linear grey.
converter.Convert(inHandler, outHandler, 4) iferr_return;
// Do the inverse operation and interpret #inputBuffer as non-linear data, converting it to
// linear data.
// Initialize an non-linear-to-linear GREY space converter, allocate an output buffer, and bind
// it to a buffer handler.
const maxon::ColorProfileConvert invConverter = maxon::ColorProfileConvertInterface::Init(
greyFormat, nonlinGreyProfile, greyFormat, linGreyProfile,
linOutputBuffer.Resize(4) iferr_return;
(maxon::Pix*)(linOutputBuffer.GetFirst()),
greyFormat.GetBitsPerPixel(), greyFormat.GetChannelOffsets(), greyFormat);
// Convert 4 pixels in #inputBuffer to #linOutputBuffer, converting the data from non-linear
// grey to linear grey, and print all results.
invConverter.Convert(inHandler, invOutHandler, 4) iferr_return;
ApplicationOutput("\tlinear -> non-linear: @ -> @", inputBuffer, nonLinOutputBuffer);
ApplicationOutput("\tnon-linear -> linear: @ -> @", inputBuffer, linOutputBuffer);
return maxon::OK;
}
Definition: basearray.h:412
MAXON_ATTRIBUTE_FORCE_INLINE const T * GetFirst() const
Definition: basearray.h:1326
ResultMem Resize(Int newCnt, COLLECTION_RESIZE_FLAGS resizeFlags=COLLECTION_RESIZE_FLAGS::DEFAULT)
Definition: basearray.h:1369

Color Convert Bitmap Data

Read color data from a bitmap to a buffer and convert this buffer with color profiles and/or pixel formats.

maxon::Result<void> ConvertTextureWithColorProfile(
{
// Get the Asset API user preferences repository to get the "HDR004.hdr" texture asset in
// Textures/HDR/Legacy and retrieve its URL.
maxon::AssetRepositoryRef repository = maxon::AssetInterface::GetUserPrefsRepository();
if (MAXON_UNLIKELY(!repository))
return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Could not retrieve user repository."_s);
const maxon::Id assetId("file_9748feafc2c00be8");
const maxon::AssetDescription asset = repository.FindLatestAsset(
// --- Start of Image API related code ----------------------------------------------------------
// Load the image file at #url into an ImageTextureInterface instance.
const maxon::ImageTextureRef texture = maxon::ImageTextureInterface::LoadTexture(
textureUrl, maxon::TimeValue(), maxon::MEDIASESSIONFLAGS::NONE) iferr_return;
// Get the pixel format and color profile associated with the texture. When there is no profile,
// fall back to the default profile of the pixel format.
const maxon::PixelFormat texPixFormat = texture.GetPixelFormat();
const maxon::ColorProfile texProfile = texture.Get(
maxon::IMAGEPROPERTIES::IMAGE::COLORPROFILE, texPixFormat.GetDefaultColorProfile());
// Get the pixel width and height of the image.
const maxon::Int w = texture.GetWidth();
const maxon::Int h = texture.GetHeight();
// Define a new color profile and pixel format for the conversion target, in this case the
// sRGB2014.icc profile loaded by the GetColorProfiles() example.
maxon::ColorProfile srgbProfile = collection.FindValue("sRGB2014"_s).GetValue() iferr_return;
const maxon::PixelFormat rgbFormat = maxon::PixelFormats::RGB::F32();
// Initialize an ImageInterface to write the converted pixel data to. ImageTexture cannot be
// written to directly, because only ImageInterface, ImageLayerInterface, and
// ImagePixelStorageInterface support pixel buffer write access.
const maxon::ImageRef image = maxon::ImageClasses::IMAGE().Create() iferr_return;
image.Init(w, h, maxon::ImagePixelStorageClasses::Normal(), rgbFormat) iferr_return;
// Initialize the output buffer and buffer interface for the to be converted pixel data. An input
// buffer is not required in this case because the image API will handle the texture data. The
// buffer is being sized to the pixel count in the source texture times the channel count in the
// output format.
maxon::BaseArray<maxon::Pix> outBufferData;
outBufferData.Resize(w * h * rgbFormat.GetChannelCount()) iferr_return;
(maxon::Pix*)(outBufferData.GetFirst()),
rgbFormat.GetBitsPerPixel(), rgbFormat.GetChannelOffsets(), rgbFormat);
// Initialize handlers for pixel read and write operations on the input texture and output image.
// A GetPixelHandler wraps a destination pixel format and color profile to read and convert to and
// not the source format and profile of the to be read image. One can also pass the source format
// and profile of the input image, then no conversion will be carried out on read operations.
const maxon::GetPixelHandlerStruct readHandler = texture.GetPixelHandler(
rgbFormat, rgbFormat.GetChannelOffsets(), srgbProfile,
const maxon::SetPixelHandlerStruct writeHandler = image.SetPixelHandler(
rgbFormat, rgbFormat.GetChannelOffsets(), srgbProfile,
// Iterate over the data row-by-row and write the data. maxon::ImagePos can define despite its
// name more than a single pixel location in an image, but is limited to addressing data within
// one row.
for (Int i = 0; i < h; i++)
{
const maxon::ImagePos scope{ 0, i, w };
readHandler.GetPixel(scope, bufferOut, maxon::GETPIXELFLAGS::NONE) iferr_return;
writeHandler.SetPixel(scope, bufferOut, maxon::SETPIXELFLAGS::NONE) iferr_return;
}
// Set the color profile of the image to the ICC profile "sRGB2014".
image.Set(maxon::IMAGEPROPERTIES::IMAGE::COLORPROFILE, srgbProfile) iferr_return;
// Instantiate a PSD file format output handler and define a storage URL next to this cpp file
// with the file name "texture.psd",
const maxon::MediaOutputUrlRef psdFormat = maxon::ImageSaverClasses::Psd().Create() iferr_return;
const maxon::Url url = (maxon::Url(maxon::String(__FILE__)).GetDirectory() + "texture.psd"_s) iferr_return;
// To store the ImageRef #image, it must be inserted below a type instance in the ImageInterface
// hierarchy that supports serialization, e.g., ImageTextureInterface.
const maxon::ImageTextureRef outTexture = maxon::ImageTextureClasses::TEXTURE().Create() iferr_return;
// Insert #image as a child of #outTexture, and also set the color profile of #outTexture to the
// ICC profile "sRGB2014".
outTexture.AddChildren(maxon::IMAGEHIERARCHY::IMAGE, image, maxon::ImageBaseRef()) iferr_return;
outTexture.Set(maxon::IMAGEPROPERTIES::IMAGE::COLORPROFILE, srgbProfile) iferr_return;
// Write #outTexture as a psd file to #url.
outTexture.Save(url, psdFormat, maxon::MEDIASESSIONFLAGS::NONE) iferr_return;
ApplicationOutput("\tWrote color converted texture asset '@' to '@'.", assetId, url);
return maxon::OK;
}
Py_ssize_t i
Definition: abstract.h:645
NONE
The command asset state is not defined.
Definition: asset_command.h:0
Definition: c4d_basebitmap.h:156
static MAXON_METHOD const UpdatableAssetRepositoryRef & GetUserPrefsRepository()
static MAXON_METHOD Result< Url > GetAssetUrl(const AssetDescription &asset, Bool isLatest)
Definition: apibaseid.h:253
for(i=0;i< length;i++)
Definition: unicodeobject.h:61
RGB
8-bit RGB channels.
Definition: ge_prepass.h:7
maxon::Int Int
Definition: ge_sys_math.h:64
static auto Create(ARGS &&... args)
Definition: apibase.h:2773
GETPIXELHANDLERFLAGS
Flags to control the GetPixelHandler functions.
Definition: gfx_image_pixelformat.h:42
SETPIXELHANDLERFLAGS
Flags to control the SetPixelHandler functions.
Definition: gfx_image_pixelformat.h:66
@ NONE
no options set.
@ IMAGE
Adds a subImage to a texture. Images are only allowed under Textures.
@ NONE
No options set.
#define MAXON_UNLIKELY(...)
Definition: compilerdetection.h:404
IMAGE
Filename Image input, one DescID.
Definition: lib_substance.h:9
const Class< R > & Get(const Id &cls)
Definition: objectbase.h:2073
The maxon namespace contains all declarations of the MAXON API.
Definition: autoweight.h:14
MEDIASESSIONFLAGS
Definition: mediasession_session.h:15
@ LATEST
Set this flag to obtain only the latest version of the asset.
Several functions use this helper structure to pass the position within an image and number of pixels...
Definition: gfx_image_pixelhandler.h:18

Use Color Conversion Utilities

Convert colors between common color representations such as RGB, HSL, and CMYK.

maxon::Result<void> ConvertColorWithUtils()
{
// Define a color which should be converted.
maxon::Color color { 1, 0, 0 };
// Convert the color between common color representations.
maxon::Color colorXyz = maxon::RgbToXyz(color); // The inverse is XyzToRgb()
maxon::Color colorCmy = maxon::RgbToCmy(color); // The inverse is CmyToRgb()
maxon::Color colorHsv = maxon::RgbToHsv(color); // The inverse is HsvToRgb()
maxon::Color colorHsl = maxon::RgbToHsl(color); // The inverse is HslToRgb()
// Generate a color from a color temperature.
// Print the results.
const maxon::Char* msg =
"\trgb: '@'\n"
"\txyz: '@'\n"
"\tcmy: '@'\n"
"\thsv: '@'\n"
"\thsl: '@'\n"
"\ttemp (6500K): '@'";
ApplicationOutput(msg, color, colorXyz, colorCmy, colorHsv, colorHsl, colorTemp);
return maxon::OK;
}
MAXON_ATTRIBUTE_FORCE_INLINE Color RgbToCmy(const Color rgb)
Definition: gfx_image_functions_color_conversions.h:36
MAXON_ATTRIBUTE_FORCE_INLINE COLORTYPE RgbToHsl(const COLORTYPE &rgb)
Definition: gfx_image_functions_color_conversions.h:58
MAXON_ATTRIBUTE_FORCE_INLINE COLORTYPE RgbToHsv(const COLORTYPE &rgb)
Definition: gfx_image_functions_color_conversions.h:46
MAXON_ATTRIBUTE_FORCE_INLINE Color RgbToXyz(const Color &rgb)
Definition: gfx_image_functions_color_conversions.h:27
MAXON_ATTRIBUTE_FORCE_INLINE Color ColorTemperatureToRGB(Float temperature)
Definition: gfx_image_functions_color_conversions.h:161