blender/intern/opencolorio/ocio_impl.cc
Lukas Stockner 6967255906 Color management: Support white balance as part of the display transform
This implements a von-Kries-style chromatic adaption using the Bradford matrix.
The adaption is performed in scene linear space in the OCIO GLSL shader, with
the matrix being computed on the host.

The parameters specify the white point of the input, which is to be mapped to
the white point of the scene linear space. The main parameter is temperature,
specified in Kelvin, which defines the blackbody spectrum that is used as the
input white point. Additionally, a tint parameter can be used to shift the
white point away from pure blackbody spectra (e.g. to match a D illuminant).

The defaults are set to match D65 so there is no immediate color shift when
enabling the option. Tint = 10 is needed since the D-series illuminants aren't
perfect blackbody emitters.

As an alternative to manually specifying the values, there's also a color
picker. When a color is selected, temperature and tint are set such that this
color ends up being balanced to white.
This only works if the color is close enough to a blackbody emitter -
specifically, for tint values within +-150. Beyond this, there can be ambiguity
in the representation.
Currently, in this case, the input is just ignored and temperature/tint aren't
changed. Ideally, we'd eventually give UI feedback for this.

Presets are supported, and all the CIE standard illuminants are included.

One part that I'm not quite happy with is that the tint parameter starts to
give weird results at moderate values when the temperature is low.
The reason for this can be seen here:
https://commons.wikimedia.org/wiki/File:Planckian-locus.png
Tint is moving along the isotherm lines (with the plot corresponding to +-150),
but below 4000K some of that range is outside of the gamut. Not much can
be done there, other than possibly clipping those values...

Adding support for this to the compositor should be quite easy and is planned
as a next step.

Pull Request: https://projects.blender.org/blender/blender/pulls/123278
2024-06-27 23:27:58 +02:00

827 lines
22 KiB
C++

/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cassert>
#include <iostream>
#include <math.h>
#include <sstream>
#include <string.h>
#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable : 4251 4275)
#endif
#include <OpenColorIO/OpenColorIO.h>
#ifdef _MSC_VER
# pragma warning(pop)
#endif
using namespace OCIO_NAMESPACE;
#include "MEM_guardedalloc.h"
#include "BLI_math_color.h"
#include "BLI_math_color.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "ocio_impl.h"
#if !defined(WITH_ASSERT_ABORT)
# define OCIO_abort()
#else
# include <stdlib.h>
# define OCIO_abort() abort()
#endif
#if defined(_MSC_VER)
# define __func__ __FUNCTION__
#endif
using blender::double4x4;
using blender::float3;
using blender::float3x3;
static void OCIO_reportError(const char *err)
{
std::cerr << "OpenColorIO Error: " << err << std::endl;
OCIO_abort();
}
static void OCIO_reportException(Exception &exception)
{
OCIO_reportError(exception.what());
}
OCIO_ConstConfigRcPtr *OCIOImpl::getCurrentConfig(void)
{
ConstConfigRcPtr *config = MEM_new<ConstConfigRcPtr>(__func__);
try {
*config = GetCurrentConfig();
if (*config) {
return (OCIO_ConstConfigRcPtr *)config;
}
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
MEM_delete(config);
return NULL;
}
void OCIOImpl::setCurrentConfig(const OCIO_ConstConfigRcPtr *config)
{
try {
SetCurrentConfig(*(ConstConfigRcPtr *)config);
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
}
OCIO_ConstConfigRcPtr *OCIOImpl::configCreateFromEnv(void)
{
ConstConfigRcPtr *config = MEM_new<ConstConfigRcPtr>(__func__);
try {
*config = Config::CreateFromEnv();
if (*config) {
return (OCIO_ConstConfigRcPtr *)config;
}
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
MEM_delete(config);
return NULL;
}
OCIO_ConstConfigRcPtr *OCIOImpl::configCreateFromFile(const char *filename)
{
ConstConfigRcPtr *config = MEM_new<ConstConfigRcPtr>(__func__);
try {
*config = Config::CreateFromFile(filename);
if (*config) {
return (OCIO_ConstConfigRcPtr *)config;
}
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
MEM_delete(config);
return NULL;
}
void OCIOImpl::configRelease(OCIO_ConstConfigRcPtr *config)
{
MEM_delete((ConstConfigRcPtr *)config);
}
int OCIOImpl::configGetNumColorSpaces(OCIO_ConstConfigRcPtr *config)
{
try {
return (*(ConstConfigRcPtr *)config)->getNumColorSpaces();
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return 0;
}
const char *OCIOImpl::configGetColorSpaceNameByIndex(OCIO_ConstConfigRcPtr *config, int index)
{
try {
return (*(ConstConfigRcPtr *)config)->getColorSpaceNameByIndex(index);
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return NULL;
}
OCIO_ConstColorSpaceRcPtr *OCIOImpl::configGetColorSpace(OCIO_ConstConfigRcPtr *config,
const char *name)
{
ConstColorSpaceRcPtr *cs = MEM_new<ConstColorSpaceRcPtr>(__func__);
try {
*cs = (*(ConstConfigRcPtr *)config)->getColorSpace(name);
if (*cs) {
return (OCIO_ConstColorSpaceRcPtr *)cs;
}
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
MEM_delete(cs);
return NULL;
}
int OCIOImpl::configGetIndexForColorSpace(OCIO_ConstConfigRcPtr *config, const char *name)
{
try {
return (*(ConstConfigRcPtr *)config)->getIndexForColorSpace(name);
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return -1;
}
const char *OCIOImpl::configGetDefaultDisplay(OCIO_ConstConfigRcPtr *config)
{
try {
return (*(ConstConfigRcPtr *)config)->getDefaultDisplay();
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return NULL;
}
int OCIOImpl::configGetNumDisplays(OCIO_ConstConfigRcPtr *config)
{
try {
return (*(ConstConfigRcPtr *)config)->getNumDisplays();
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return 0;
}
const char *OCIOImpl::configGetDisplay(OCIO_ConstConfigRcPtr *config, int index)
{
try {
return (*(ConstConfigRcPtr *)config)->getDisplay(index);
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return NULL;
}
const char *OCIOImpl::configGetDefaultView(OCIO_ConstConfigRcPtr *config, const char *display)
{
try {
return (*(ConstConfigRcPtr *)config)->getDefaultView(display);
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return NULL;
}
int OCIOImpl::configGetNumViews(OCIO_ConstConfigRcPtr *config, const char *display)
{
try {
return (*(ConstConfigRcPtr *)config)->getNumViews(display);
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return 0;
}
const char *OCIOImpl::configGetView(OCIO_ConstConfigRcPtr *config, const char *display, int index)
{
try {
return (*(ConstConfigRcPtr *)config)->getView(display, index);
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return NULL;
}
const char *OCIOImpl::configGetDisplayColorSpaceName(OCIO_ConstConfigRcPtr *config,
const char *display,
const char *view)
{
try {
const char *name = (*(ConstConfigRcPtr *)config)->getDisplayViewColorSpaceName(display, view);
/* OpenColorIO does not resolve this token for us, so do it ourselves. */
if (strcasecmp(name, "<USE_DISPLAY_NAME>") == 0) {
return display;
}
return name;
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return NULL;
}
void OCIOImpl::configGetDefaultLumaCoefs(OCIO_ConstConfigRcPtr *config, float *rgb)
{
try {
double rgb_double[3];
(*(ConstConfigRcPtr *)config)->getDefaultLumaCoefs(rgb_double);
rgb[0] = rgb_double[0];
rgb[1] = rgb_double[1];
rgb[2] = rgb_double[2];
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
}
static bool to_scene_linear_matrix(ConstConfigRcPtr &config,
const char *colorspace,
float to_scene_linear[3][3])
{
ConstProcessorRcPtr processor;
try {
processor = config->getProcessor(colorspace, ROLE_SCENE_LINEAR);
}
catch (Exception &exception) {
OCIO_reportException(exception);
return false;
}
if (!processor) {
return false;
}
ConstCPUProcessorRcPtr cpu_processor = processor->getDefaultCPUProcessor();
if (!cpu_processor) {
return false;
}
unit_m3(to_scene_linear);
cpu_processor->applyRGB(to_scene_linear[0]);
cpu_processor->applyRGB(to_scene_linear[1]);
cpu_processor->applyRGB(to_scene_linear[2]);
return true;
}
void OCIOImpl::configGetXYZtoSceneLinear(OCIO_ConstConfigRcPtr *config_,
float xyz_to_scene_linear[3][3])
{
ConstConfigRcPtr config = (*(ConstConfigRcPtr *)config_);
/* Default to ITU-BT.709 in case no appropriate transform found.
* Note XYZ is defined here as having a D65 white point. */
memcpy(xyz_to_scene_linear, OCIO_XYZ_TO_REC709, sizeof(OCIO_XYZ_TO_REC709));
/* Get from OpenColorO config if it has the required roles. */
if (!config->hasRole(ROLE_SCENE_LINEAR)) {
return;
}
if (config->hasRole("aces_interchange")) {
/* Standard OpenColorIO role, defined as ACES AP0 (ACES2065-1). */
float aces_to_scene_linear[3][3];
if (to_scene_linear_matrix(config, "aces_interchange", aces_to_scene_linear)) {
float xyz_to_aces[3][3];
invert_m3_m3(xyz_to_aces, OCIO_ACES_TO_XYZ);
mul_m3_m3m3(xyz_to_scene_linear, aces_to_scene_linear, xyz_to_aces);
}
}
else if (config->hasRole("XYZ")) {
/* Custom role used before the standard existed. */
to_scene_linear_matrix(config, "XYZ", xyz_to_scene_linear);
}
}
int OCIOImpl::configGetNumLooks(OCIO_ConstConfigRcPtr *config)
{
try {
return (*(ConstConfigRcPtr *)config)->getNumLooks();
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return 0;
}
const char *OCIOImpl::configGetLookNameByIndex(OCIO_ConstConfigRcPtr *config, int index)
{
try {
return (*(ConstConfigRcPtr *)config)->getLookNameByIndex(index);
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return NULL;
}
OCIO_ConstLookRcPtr *OCIOImpl::configGetLook(OCIO_ConstConfigRcPtr *config, const char *name)
{
ConstLookRcPtr *look = MEM_new<ConstLookRcPtr>(__func__);
try {
*look = (*(ConstConfigRcPtr *)config)->getLook(name);
if (*look) {
return (OCIO_ConstLookRcPtr *)look;
}
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
MEM_delete(look);
return NULL;
}
const char *OCIOImpl::lookGetProcessSpace(OCIO_ConstLookRcPtr *look)
{
return (*(ConstLookRcPtr *)look)->getProcessSpace();
}
void OCIOImpl::lookRelease(OCIO_ConstLookRcPtr *look)
{
MEM_delete((ConstLookRcPtr *)look);
}
int OCIOImpl::colorSpaceIsInvertible(OCIO_ConstColorSpaceRcPtr *cs_)
{
ConstColorSpaceRcPtr *cs = (ConstColorSpaceRcPtr *)cs_;
const char *family = (*cs)->getFamily();
if (!strcmp(family, "rrt") || !strcmp(family, "display")) {
/* assume display and rrt transformations are not invertible in fact some of them could be,
* but it doesn't make much sense to allow use them as invertible. */
return false;
}
if ((*cs)->isData()) {
/* data color spaces don't have transformation at all */
return true;
}
if ((*cs)->getTransform(COLORSPACE_DIR_TO_REFERENCE)) {
/* if there's defined transform to reference space,
* color space could be converted to scene linear. */
return true;
}
return true;
}
int OCIOImpl::colorSpaceIsData(OCIO_ConstColorSpaceRcPtr *cs)
{
return (*(ConstColorSpaceRcPtr *)cs)->isData();
}
static float compare_floats(float a, float b, float abs_diff, int ulp_diff)
{
/* Returns true if the absolute difference is smaller than abs_diff (for numbers near zero)
* or their relative difference is less than ulp_diff ULPs. Based on:
* https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ */
if (fabsf(a - b) < abs_diff) {
return true;
}
if ((a < 0.0f) != (b < 0.0f)) {
return false;
}
return (abs((*(int *)&a) - (*(int *)&b)) < ulp_diff);
}
void OCIOImpl::colorSpaceIsBuiltin(OCIO_ConstConfigRcPtr *config_,
OCIO_ConstColorSpaceRcPtr *cs_,
bool &is_scene_linear,
bool &is_srgb)
{
ConstConfigRcPtr *config = (ConstConfigRcPtr *)config_;
ConstColorSpaceRcPtr *cs = (ConstColorSpaceRcPtr *)cs_;
ConstProcessorRcPtr processor;
try {
processor = (*config)->getProcessor((*cs)->getName(), "scene_linear");
}
catch (Exception &) {
/* Silently ignore if no conversion possible, then it's not scene linear or sRGB. */
is_scene_linear = false;
is_srgb = false;
return;
}
ConstCPUProcessorRcPtr cpu_processor = processor->getDefaultCPUProcessor();
is_scene_linear = true;
is_srgb = true;
for (int i = 0; i < 256; i++) {
float v = i / 255.0f;
float cR[3] = {v, 0, 0};
float cG[3] = {0, v, 0};
float cB[3] = {0, 0, v};
float cW[3] = {v, v, v};
cpu_processor->applyRGB(cR);
cpu_processor->applyRGB(cG);
cpu_processor->applyRGB(cB);
cpu_processor->applyRGB(cW);
/* Make sure that there is no channel crosstalk. */
if (fabsf(cR[1]) > 1e-5f || fabsf(cR[2]) > 1e-5f || fabsf(cG[0]) > 1e-5f ||
fabsf(cG[2]) > 1e-5f || fabsf(cB[0]) > 1e-5f || fabsf(cB[1]) > 1e-5f)
{
is_scene_linear = false;
is_srgb = false;
break;
}
/* Make sure that the three primaries combine linearly. */
if (!compare_floats(cR[0], cW[0], 1e-6f, 64) || !compare_floats(cG[1], cW[1], 1e-6f, 64) ||
!compare_floats(cB[2], cW[2], 1e-6f, 64))
{
is_scene_linear = false;
is_srgb = false;
break;
}
/* Make sure that the three channels behave identically. */
if (!compare_floats(cW[0], cW[1], 1e-6f, 64) || !compare_floats(cW[1], cW[2], 1e-6f, 64)) {
is_scene_linear = false;
is_srgb = false;
break;
}
float out_v = (cW[0] + cW[1] + cW[2]) * (1.0f / 3.0f);
if (!compare_floats(v, out_v, 1e-6f, 64)) {
is_scene_linear = false;
}
if (!compare_floats(srgb_to_linearrgb(v), out_v, 1e-4f, 64)) {
is_srgb = false;
}
}
}
void OCIOImpl::colorSpaceRelease(OCIO_ConstColorSpaceRcPtr *cs)
{
MEM_delete((ConstColorSpaceRcPtr *)cs);
}
OCIO_ConstProcessorRcPtr *OCIOImpl::configGetProcessorWithNames(OCIO_ConstConfigRcPtr *config,
const char *srcName,
const char *dstName)
{
ConstProcessorRcPtr *processor = MEM_new<ConstProcessorRcPtr>(__func__);
try {
*processor = (*(ConstConfigRcPtr *)config)->getProcessor(srcName, dstName);
if (*processor) {
return (OCIO_ConstProcessorRcPtr *)processor;
}
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
MEM_delete(processor);
return 0;
}
void OCIOImpl::processorRelease(OCIO_ConstProcessorRcPtr *processor)
{
MEM_delete(processor);
}
OCIO_ConstCPUProcessorRcPtr *OCIOImpl::processorGetCPUProcessor(
OCIO_ConstProcessorRcPtr *processor)
{
ConstCPUProcessorRcPtr *cpu_processor = MEM_new<ConstCPUProcessorRcPtr>(__func__);
*cpu_processor = (*(ConstProcessorRcPtr *)processor)->getDefaultCPUProcessor();
return (OCIO_ConstCPUProcessorRcPtr *)cpu_processor;
}
void OCIOImpl::cpuProcessorApply(OCIO_ConstCPUProcessorRcPtr *cpu_processor,
OCIO_PackedImageDesc *img)
{
try {
(*(ConstCPUProcessorRcPtr *)cpu_processor)->apply(*(PackedImageDesc *)img);
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
}
void OCIOImpl::cpuProcessorApply_predivide(OCIO_ConstCPUProcessorRcPtr *cpu_processor,
OCIO_PackedImageDesc *img_)
{
try {
PackedImageDesc *img = (PackedImageDesc *)img_;
int channels = img->getNumChannels();
if (channels == 4) {
assert(img->isFloat());
float *pixels = (float *)img->getData();
size_t width = img->getWidth();
size_t height = img->getHeight();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float *pixel = pixels + 4 * (y * width + x);
cpuProcessorApplyRGBA_predivide(cpu_processor, pixel);
}
}
}
else {
(*(ConstCPUProcessorRcPtr *)cpu_processor)->apply(*img);
}
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
}
bool OCIOImpl::cpuProcessorIsNoOp(OCIO_ConstCPUProcessorRcPtr *cpu_processor)
{
return (*(ConstCPUProcessorRcPtr *)cpu_processor)->isNoOp();
}
void OCIOImpl::cpuProcessorApplyRGB(OCIO_ConstCPUProcessorRcPtr *cpu_processor, float *pixel)
{
(*(ConstCPUProcessorRcPtr *)cpu_processor)->applyRGB(pixel);
}
void OCIOImpl::cpuProcessorApplyRGBA(OCIO_ConstCPUProcessorRcPtr *cpu_processor, float *pixel)
{
(*(ConstCPUProcessorRcPtr *)cpu_processor)->applyRGBA(pixel);
}
void OCIOImpl::cpuProcessorApplyRGBA_predivide(OCIO_ConstCPUProcessorRcPtr *cpu_processor,
float *pixel)
{
if (pixel[3] == 1.0f || pixel[3] == 0.0f) {
(*(ConstCPUProcessorRcPtr *)cpu_processor)->applyRGBA(pixel);
}
else {
float alpha, inv_alpha;
alpha = pixel[3];
inv_alpha = 1.0f / alpha;
pixel[0] *= inv_alpha;
pixel[1] *= inv_alpha;
pixel[2] *= inv_alpha;
(*(ConstCPUProcessorRcPtr *)cpu_processor)->applyRGBA(pixel);
pixel[0] *= alpha;
pixel[1] *= alpha;
pixel[2] *= alpha;
}
}
void OCIOImpl::cpuProcessorRelease(OCIO_ConstCPUProcessorRcPtr *cpu_processor)
{
MEM_delete(cpu_processor);
}
const char *OCIOImpl::colorSpaceGetName(OCIO_ConstColorSpaceRcPtr *cs)
{
return (*(ConstColorSpaceRcPtr *)cs)->getName();
}
const char *OCIOImpl::colorSpaceGetDescription(OCIO_ConstColorSpaceRcPtr *cs)
{
return (*(ConstColorSpaceRcPtr *)cs)->getDescription();
}
const char *OCIOImpl::colorSpaceGetFamily(OCIO_ConstColorSpaceRcPtr *cs)
{
return (*(ConstColorSpaceRcPtr *)cs)->getFamily();
}
int OCIOImpl::colorSpaceGetNumAliases(OCIO_ConstColorSpaceRcPtr *cs)
{
return (*(ConstColorSpaceRcPtr *)cs)->getNumAliases();
}
const char *OCIOImpl::colorSpaceGetAlias(OCIO_ConstColorSpaceRcPtr *cs, const int index)
{
return (*(ConstColorSpaceRcPtr *)cs)->getAlias(index);
}
OCIO_ConstProcessorRcPtr *OCIOImpl::createDisplayProcessor(OCIO_ConstConfigRcPtr *config_,
const char *input,
const char *view,
const char *display,
const char *look,
const float scale,
const float exponent,
const float temperature,
const float tint,
const bool use_white_balance,
const bool inverse)
{
ConstConfigRcPtr config = *(ConstConfigRcPtr *)config_;
GroupTransformRcPtr group = GroupTransform::Create();
/* Linear transforms. */
if (scale != 1.0f || use_white_balance) {
/* Always apply exposure and/or white balance in scene linear. */
ColorSpaceTransformRcPtr ct = ColorSpaceTransform::Create();
ct->setSrc(input);
ct->setDst(ROLE_SCENE_LINEAR);
group->appendTransform(ct);
/* Make further transforms aware of the color space change. */
input = ROLE_SCENE_LINEAR;
/* Apply scale. */
MatrixTransformRcPtr mt = MatrixTransform::Create();
float3x3 matrix = float3x3::identity() * scale;
/* Apply white balance. */
if (use_white_balance) {
/* Compute white point of the scene space in XYZ.*/
float3x3 xyz_to_scene;
configGetXYZtoSceneLinear(config_, xyz_to_scene.ptr());
float3x3 scene_to_xyz = blender::math::invert(xyz_to_scene);
float3 target = scene_to_xyz * float3(1.0f);
/* Add operations to the matrix.
* Note: Since we're multiplying from the right, the operations here will be performed in
* reverse list order (scene-to-XYZ, then adaption, then XYZ-to-scene, then exposure). */
matrix *= xyz_to_scene;
matrix *= blender::math::chromatic_adaption_matrix(
blender::math::whitepoint_from_temp_tint(temperature, tint), target);
matrix *= scene_to_xyz;
}
mt->setMatrix(double4x4(blender::math::transpose(matrix)).base_ptr());
group->appendTransform(mt);
}
/* Add look transform. */
bool use_look = (look != nullptr && look[0] != 0);
if (use_look) {
const char *look_output = LookTransform::GetLooksResultColorSpace(
config, config->getCurrentContext(), look);
if (look_output != nullptr && look_output[0] != 0) {
LookTransformRcPtr lt = LookTransform::Create();
lt->setSrc(input);
lt->setDst(look_output);
lt->setLooks(look);
group->appendTransform(lt);
/* Make further transforms aware of the color space change. */
input = look_output;
}
else {
/* For empty looks, no output color space is returned. */
use_look = false;
}
}
/* Add view and display transform. */
DisplayViewTransformRcPtr dvt = DisplayViewTransform::Create();
dvt->setSrc(input);
dvt->setLooksBypass(use_look);
dvt->setView(view);
dvt->setDisplay(display);
group->appendTransform(dvt);
/* Gamma. */
if (exponent != 1.0f) {
ExponentTransformRcPtr et = ExponentTransform::Create();
const double value[4] = {exponent, exponent, exponent, 1.0};
et->setValue(value);
group->appendTransform(et);
}
if (inverse) {
group->setDirection(TRANSFORM_DIR_INVERSE);
}
/* Create processor from transform. This is the moment were OCIO validates
* the entire transform, no need to check for the validity of inputs above. */
ConstProcessorRcPtr *p = MEM_new<ConstProcessorRcPtr>(__func__);
try {
*p = config->getProcessor(group);
if (*p) {
return (OCIO_ConstProcessorRcPtr *)p;
}
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
MEM_delete(p);
return NULL;
}
OCIO_PackedImageDesc *OCIOImpl::createOCIO_PackedImageDesc(float *data,
long width,
long height,
long numChannels,
long chanStrideBytes,
long xStrideBytes,
long yStrideBytes)
{
try {
void *mem = MEM_mallocN(sizeof(PackedImageDesc), __func__);
PackedImageDesc *id = new (mem) PackedImageDesc(data,
width,
height,
numChannels,
BIT_DEPTH_F32,
chanStrideBytes,
xStrideBytes,
yStrideBytes);
return (OCIO_PackedImageDesc *)id;
}
catch (Exception &exception) {
OCIO_reportException(exception);
}
return NULL;
}
void OCIOImpl::OCIO_PackedImageDescRelease(OCIO_PackedImageDesc *id)
{
MEM_delete((PackedImageDesc *)id);
}
const char *OCIOImpl::getVersionString(void)
{
return GetVersion();
}
int OCIOImpl::getVersionHex(void)
{
return GetVersionHex();
}