/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2012 Blender Foundation. All rights reserved. */ #include #include #include #include #include #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable : 4251 4275) #endif #include #ifdef _MSC_VER # pragma warning(pop) #endif using namespace OCIO_NAMESPACE; #include "MEM_guardedalloc.h" #include "BLI_math.h" #include "BLI_math_color.h" #include "BLI_math_matrix.h" #include "ocio_impl.h" #if !defined(WITH_ASSERT_ABORT) # define OCIO_abort() #else # include # define OCIO_abort() abort() #endif #if defined(_MSC_VER) # define __func__ __FUNCTION__ #endif 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(__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(__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(__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(__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, "") == 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(__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-6f, 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(__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(__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); } } 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 bool inverse) { ConstConfigRcPtr config = *(ConstConfigRcPtr *)config_; GroupTransformRcPtr group = GroupTransform::Create(); /* Exposure. */ if (scale != 1.0f) { /* Always apply exposure 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(); const double matrix[16] = { scale, 0.0, 0.0, 0.0, 0.0, scale, 0.0, 0.0, 0.0, 0.0, scale, 0.0, 0.0, 0.0, 0.0, 1.0}; mt->setMatrix(matrix); 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(__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(); }