4434 lines
146 KiB
C++
4434 lines
146 KiB
C++
/* SPDX-FileCopyrightText: 2012 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup imbuf
|
|
*/
|
|
|
|
#include "IMB_colormanagement.h"
|
|
#include "IMB_colormanagement_intern.h"
|
|
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
#include "DNA_color_types.h"
|
|
#include "DNA_image_types.h"
|
|
#include "DNA_movieclip_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_space_types.h"
|
|
|
|
#include "IMB_filetype.h"
|
|
#include "IMB_filter.h"
|
|
#include "IMB_imbuf.h"
|
|
#include "IMB_imbuf_types.h"
|
|
#include "IMB_metadata.h"
|
|
#include "IMB_moviecache.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_math_color.h"
|
|
#include "BLI_rect.h"
|
|
#include "BLI_string.h"
|
|
#include "BLI_task.h"
|
|
#include "BLI_threads.h"
|
|
|
|
#include "BKE_appdir.h"
|
|
#include "BKE_colortools.h"
|
|
#include "BKE_context.hh"
|
|
#include "BKE_image.h"
|
|
#include "BKE_image_format.h"
|
|
#include "BKE_main.h"
|
|
|
|
#include "GPU_capabilities.h"
|
|
|
|
#include "RNA_define.hh"
|
|
|
|
#include "SEQ_iterator.hh"
|
|
|
|
#include <ocio_capi.h>
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Global declarations
|
|
* \{ */
|
|
|
|
#define DISPLAY_BUFFER_CHANNELS 4
|
|
|
|
/* ** list of all supported color spaces, displays and views */
|
|
static char global_role_data[MAX_COLORSPACE_NAME];
|
|
static char global_role_scene_linear[MAX_COLORSPACE_NAME];
|
|
static char global_role_color_picking[MAX_COLORSPACE_NAME];
|
|
static char global_role_texture_painting[MAX_COLORSPACE_NAME];
|
|
static char global_role_default_byte[MAX_COLORSPACE_NAME];
|
|
static char global_role_default_float[MAX_COLORSPACE_NAME];
|
|
static char global_role_default_sequencer[MAX_COLORSPACE_NAME];
|
|
|
|
static ListBase global_colorspaces = {nullptr, nullptr};
|
|
static ListBase global_displays = {nullptr, nullptr};
|
|
static ListBase global_views = {nullptr, nullptr};
|
|
static ListBase global_looks = {nullptr, nullptr};
|
|
|
|
static int global_tot_colorspace = 0;
|
|
static int global_tot_display = 0;
|
|
static int global_tot_view = 0;
|
|
static int global_tot_looks = 0;
|
|
|
|
/* Luma coefficients and XYZ to RGB to be initialized by OCIO. */
|
|
|
|
float imbuf_luma_coefficients[3] = {0.0f};
|
|
float imbuf_scene_linear_to_xyz[3][3] = {{0.0f}};
|
|
float imbuf_xyz_to_scene_linear[3][3] = {{0.0f}};
|
|
float imbuf_scene_linear_to_rec709[3][3] = {{0.0f}};
|
|
float imbuf_rec709_to_scene_linear[3][3] = {{0.0f}};
|
|
float imbuf_scene_linear_to_aces[3][3] = {{0.0f}};
|
|
float imbuf_aces_to_scene_linear[3][3] = {{0.0f}};
|
|
|
|
/* lock used by pre-cached processors getters, so processor wouldn't
|
|
* be created several times
|
|
* LOCK_COLORMANAGE can not be used since this mutex could be needed to
|
|
* be locked before pre-cached processor are creating
|
|
*/
|
|
static pthread_mutex_t processor_lock = BLI_MUTEX_INITIALIZER;
|
|
|
|
struct ColormanageProcessor {
|
|
OCIO_ConstCPUProcessorRcPtr *cpu_processor;
|
|
CurveMapping *curve_mapping;
|
|
bool is_data_result;
|
|
};
|
|
|
|
static struct global_gpu_state {
|
|
/* GPU shader currently bound. */
|
|
bool gpu_shader_bound;
|
|
|
|
/* Curve mapping. */
|
|
CurveMapping *curve_mapping, *orig_curve_mapping;
|
|
bool use_curve_mapping;
|
|
int curve_mapping_timestamp;
|
|
OCIO_CurveMappingSettings curve_mapping_settings;
|
|
} global_gpu_state = {false};
|
|
|
|
static struct global_color_picking_state {
|
|
/* Cached processor for color picking conversion. */
|
|
OCIO_ConstCPUProcessorRcPtr *cpu_processor_to;
|
|
OCIO_ConstCPUProcessorRcPtr *cpu_processor_from;
|
|
bool failed;
|
|
} global_color_picking_state = {nullptr};
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Color Managed Cache
|
|
* \{ */
|
|
|
|
/**
|
|
* Cache Implementation Notes
|
|
* ==========================
|
|
*
|
|
* All color management cache stuff is stored in two properties of
|
|
* image buffers:
|
|
*
|
|
* 1. display_buffer_flags
|
|
*
|
|
* This is a bit field which used to mark calculated transformations
|
|
* for particular image buffer. Index inside of this array means index
|
|
* of a color managed display. Element with given index matches view
|
|
* transformations applied for a given display. So if bit B of array
|
|
* element B is set to 1, this means display buffer with display index
|
|
* of A and view transform of B was ever calculated for this imbuf.
|
|
*
|
|
* In contrast with indices in global lists of displays and views this
|
|
* indices are 0-based, not 1-based. This is needed to save some bytes
|
|
* of memory.
|
|
*
|
|
* 2. colormanage_cache
|
|
*
|
|
* This is a pointer to a structure which holds all data which is
|
|
* needed for color management cache to work.
|
|
*
|
|
* It contains two parts:
|
|
* - data
|
|
* - moviecache
|
|
*
|
|
* Data field is used to store additional information about cached
|
|
* buffers which affects on whether cached buffer could be used.
|
|
* This data can't go to cache key because changes in this data
|
|
* shouldn't lead extra buffers adding to cache, it shall
|
|
* invalidate cached images.
|
|
*
|
|
* Currently such a data contains only exposure and gamma, but
|
|
* would likely extended further.
|
|
*
|
|
* data field is not null only for elements of cache, not used for
|
|
* original image buffers.
|
|
*
|
|
* Color management cache is using generic MovieCache implementation
|
|
* to make it easier to deal with memory limitation.
|
|
*
|
|
* Currently color management is using the same memory limitation
|
|
* pool as sequencer and clip editor are using which means color
|
|
* managed buffers would be removed from the cache as soon as new
|
|
* frames are loading for the movie clip and there's no space in
|
|
* cache.
|
|
*
|
|
* Every image buffer has got own movie cache instance, which
|
|
* means keys for color managed buffers could be really simple
|
|
* and look up in this cache would be fast and independent from
|
|
* overall amount of color managed images.
|
|
*/
|
|
|
|
/* NOTE: ColormanageCacheViewSettings and ColormanageCacheDisplaySettings are
|
|
* quite the same as ColorManagedViewSettings and ColorManageDisplaySettings
|
|
* but they holds indexes of all transformations and color spaces, not
|
|
* their names.
|
|
*
|
|
* This helps avoid extra colorspace / display / view lookup without
|
|
* requiring to pass all variables which affects on display buffer
|
|
* to color management cache system and keeps calls small and nice.
|
|
*/
|
|
struct ColormanageCacheViewSettings {
|
|
int flag;
|
|
int look;
|
|
int view;
|
|
float exposure;
|
|
float gamma;
|
|
float dither;
|
|
CurveMapping *curve_mapping;
|
|
};
|
|
|
|
struct ColormanageCacheDisplaySettings {
|
|
int display;
|
|
};
|
|
|
|
struct ColormanageCacheKey {
|
|
int view; /* view transformation used for display buffer */
|
|
int display; /* display device name */
|
|
};
|
|
|
|
struct ColormanageCacheData {
|
|
int flag; /* view flags of cached buffer */
|
|
int look; /* Additional artistic transform. */
|
|
float exposure; /* exposure value cached buffer is calculated with */
|
|
float gamma; /* gamma value cached buffer is calculated with */
|
|
float dither; /* dither value cached buffer is calculated with */
|
|
CurveMapping *curve_mapping; /* curve mapping used for cached buffer */
|
|
int curve_mapping_timestamp; /* time stamp of curve mapping used for cached buffer */
|
|
};
|
|
|
|
struct ColormanageCache {
|
|
MovieCache *moviecache;
|
|
|
|
ColormanageCacheData *data;
|
|
};
|
|
|
|
static MovieCache *colormanage_moviecache_get(const ImBuf *ibuf)
|
|
{
|
|
if (!ibuf->colormanage_cache) {
|
|
return nullptr;
|
|
}
|
|
|
|
return ibuf->colormanage_cache->moviecache;
|
|
}
|
|
|
|
static ColormanageCacheData *colormanage_cachedata_get(const ImBuf *ibuf)
|
|
{
|
|
if (!ibuf->colormanage_cache) {
|
|
return nullptr;
|
|
}
|
|
|
|
return ibuf->colormanage_cache->data;
|
|
}
|
|
|
|
static uint colormanage_hashhash(const void *key_v)
|
|
{
|
|
const ColormanageCacheKey *key = static_cast<const ColormanageCacheKey *>(key_v);
|
|
|
|
uint rval = (key->display << 16) | (key->view % 0xffff);
|
|
|
|
return rval;
|
|
}
|
|
|
|
static bool colormanage_hashcmp(const void *av, const void *bv)
|
|
{
|
|
const ColormanageCacheKey *a = static_cast<const ColormanageCacheKey *>(av);
|
|
const ColormanageCacheKey *b = static_cast<const ColormanageCacheKey *>(bv);
|
|
|
|
return ((a->view != b->view) || (a->display != b->display));
|
|
}
|
|
|
|
static MovieCache *colormanage_moviecache_ensure(ImBuf *ibuf)
|
|
{
|
|
if (!ibuf->colormanage_cache) {
|
|
ibuf->colormanage_cache = MEM_cnew<ColormanageCache>("imbuf colormanage cache");
|
|
}
|
|
|
|
if (!ibuf->colormanage_cache->moviecache) {
|
|
MovieCache *moviecache;
|
|
|
|
moviecache = IMB_moviecache_create("colormanage cache",
|
|
sizeof(ColormanageCacheKey),
|
|
colormanage_hashhash,
|
|
colormanage_hashcmp);
|
|
|
|
ibuf->colormanage_cache->moviecache = moviecache;
|
|
}
|
|
|
|
return ibuf->colormanage_cache->moviecache;
|
|
}
|
|
|
|
static void colormanage_cachedata_set(ImBuf *ibuf, ColormanageCacheData *data)
|
|
{
|
|
if (!ibuf->colormanage_cache) {
|
|
ibuf->colormanage_cache = MEM_cnew<ColormanageCache>("imbuf colormanage cache");
|
|
}
|
|
|
|
ibuf->colormanage_cache->data = data;
|
|
}
|
|
|
|
static void colormanage_view_settings_to_cache(ImBuf *ibuf,
|
|
ColormanageCacheViewSettings *cache_view_settings,
|
|
const ColorManagedViewSettings *view_settings)
|
|
{
|
|
int look = IMB_colormanagement_look_get_named_index(view_settings->look);
|
|
int view = IMB_colormanagement_view_get_named_index(view_settings->view_transform);
|
|
|
|
cache_view_settings->look = look;
|
|
cache_view_settings->view = view;
|
|
cache_view_settings->exposure = view_settings->exposure;
|
|
cache_view_settings->gamma = view_settings->gamma;
|
|
cache_view_settings->dither = ibuf->dither;
|
|
cache_view_settings->flag = view_settings->flag;
|
|
cache_view_settings->curve_mapping = view_settings->curve_mapping;
|
|
}
|
|
|
|
static void colormanage_display_settings_to_cache(
|
|
ColormanageCacheDisplaySettings *cache_display_settings,
|
|
const ColorManagedDisplaySettings *display_settings)
|
|
{
|
|
int display = IMB_colormanagement_display_get_named_index(display_settings->display_device);
|
|
|
|
cache_display_settings->display = display;
|
|
}
|
|
|
|
static void colormanage_settings_to_key(ColormanageCacheKey *key,
|
|
const ColormanageCacheViewSettings *view_settings,
|
|
const ColormanageCacheDisplaySettings *display_settings)
|
|
{
|
|
key->view = view_settings->view;
|
|
key->display = display_settings->display;
|
|
}
|
|
|
|
static ImBuf *colormanage_cache_get_ibuf(ImBuf *ibuf,
|
|
ColormanageCacheKey *key,
|
|
void **cache_handle)
|
|
{
|
|
ImBuf *cache_ibuf;
|
|
MovieCache *moviecache = colormanage_moviecache_get(ibuf);
|
|
|
|
if (!moviecache) {
|
|
/* If there's no moviecache it means no color management was applied
|
|
* on given image buffer before. */
|
|
return nullptr;
|
|
}
|
|
|
|
*cache_handle = nullptr;
|
|
|
|
cache_ibuf = IMB_moviecache_get(moviecache, key, nullptr);
|
|
|
|
*cache_handle = cache_ibuf;
|
|
|
|
return cache_ibuf;
|
|
}
|
|
|
|
static uchar *colormanage_cache_get(ImBuf *ibuf,
|
|
const ColormanageCacheViewSettings *view_settings,
|
|
const ColormanageCacheDisplaySettings *display_settings,
|
|
void **cache_handle)
|
|
{
|
|
ColormanageCacheKey key;
|
|
ImBuf *cache_ibuf;
|
|
int view_flag = 1 << (view_settings->view - 1);
|
|
CurveMapping *curve_mapping = view_settings->curve_mapping;
|
|
int curve_mapping_timestamp = curve_mapping ? curve_mapping->changed_timestamp : 0;
|
|
|
|
colormanage_settings_to_key(&key, view_settings, display_settings);
|
|
|
|
/* check whether image was marked as dirty for requested transform */
|
|
if ((ibuf->display_buffer_flags[display_settings->display - 1] & view_flag) == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
cache_ibuf = colormanage_cache_get_ibuf(ibuf, &key, cache_handle);
|
|
|
|
if (cache_ibuf) {
|
|
ColormanageCacheData *cache_data;
|
|
|
|
BLI_assert(cache_ibuf->x == ibuf->x && cache_ibuf->y == ibuf->y);
|
|
|
|
/* only buffers with different color space conversions are being stored
|
|
* in cache separately. buffer which were used only different exposure/gamma
|
|
* are re-suing the same cached buffer
|
|
*
|
|
* check here which exposure/gamma/curve was used for cached buffer and if they're
|
|
* different from requested buffer should be re-generated
|
|
*/
|
|
cache_data = colormanage_cachedata_get(cache_ibuf);
|
|
|
|
if (cache_data->look != view_settings->look ||
|
|
cache_data->exposure != view_settings->exposure ||
|
|
cache_data->gamma != view_settings->gamma || cache_data->dither != view_settings->dither ||
|
|
cache_data->flag != view_settings->flag || cache_data->curve_mapping != curve_mapping ||
|
|
cache_data->curve_mapping_timestamp != curve_mapping_timestamp)
|
|
{
|
|
*cache_handle = nullptr;
|
|
|
|
IMB_freeImBuf(cache_ibuf);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
return (uchar *)cache_ibuf->byte_buffer.data;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static void colormanage_cache_put(ImBuf *ibuf,
|
|
const ColormanageCacheViewSettings *view_settings,
|
|
const ColormanageCacheDisplaySettings *display_settings,
|
|
uchar *display_buffer,
|
|
void **cache_handle)
|
|
{
|
|
ColormanageCacheKey key;
|
|
ImBuf *cache_ibuf;
|
|
ColormanageCacheData *cache_data;
|
|
int view_flag = 1 << (view_settings->view - 1);
|
|
MovieCache *moviecache = colormanage_moviecache_ensure(ibuf);
|
|
CurveMapping *curve_mapping = view_settings->curve_mapping;
|
|
int curve_mapping_timestamp = curve_mapping ? curve_mapping->changed_timestamp : 0;
|
|
|
|
colormanage_settings_to_key(&key, view_settings, display_settings);
|
|
|
|
/* mark display buffer as valid */
|
|
ibuf->display_buffer_flags[display_settings->display - 1] |= view_flag;
|
|
|
|
/* buffer itself */
|
|
cache_ibuf = IMB_allocImBuf(ibuf->x, ibuf->y, ibuf->planes, 0);
|
|
IMB_assign_byte_buffer(cache_ibuf, display_buffer, IB_TAKE_OWNERSHIP);
|
|
|
|
/* Store data which is needed to check whether cached buffer
|
|
* could be used for color managed display settings. */
|
|
cache_data = MEM_cnew<ColormanageCacheData>("color manage cache imbuf data");
|
|
cache_data->look = view_settings->look;
|
|
cache_data->exposure = view_settings->exposure;
|
|
cache_data->gamma = view_settings->gamma;
|
|
cache_data->dither = view_settings->dither;
|
|
cache_data->flag = view_settings->flag;
|
|
cache_data->curve_mapping = curve_mapping;
|
|
cache_data->curve_mapping_timestamp = curve_mapping_timestamp;
|
|
|
|
colormanage_cachedata_set(cache_ibuf, cache_data);
|
|
|
|
*cache_handle = cache_ibuf;
|
|
|
|
IMB_moviecache_put(moviecache, &key, cache_ibuf);
|
|
}
|
|
|
|
static void colormanage_cache_handle_release(void *cache_handle)
|
|
{
|
|
ImBuf *cache_ibuf = static_cast<ImBuf *>(cache_handle);
|
|
|
|
IMB_freeImBuf(cache_ibuf);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Initialization / De-initialization
|
|
* \{ */
|
|
|
|
static void colormanage_role_color_space_name_get(OCIO_ConstConfigRcPtr *config,
|
|
char *colorspace_name,
|
|
const char *role,
|
|
const char *backup_role)
|
|
{
|
|
OCIO_ConstColorSpaceRcPtr *ociocs;
|
|
|
|
ociocs = OCIO_configGetColorSpace(config, role);
|
|
|
|
if (!ociocs && backup_role) {
|
|
ociocs = OCIO_configGetColorSpace(config, backup_role);
|
|
}
|
|
|
|
if (ociocs) {
|
|
const char *name = OCIO_colorSpaceGetName(ociocs);
|
|
|
|
/* assume function was called with buffer properly allocated to MAX_COLORSPACE_NAME chars */
|
|
BLI_strncpy(colorspace_name, name, MAX_COLORSPACE_NAME);
|
|
OCIO_colorSpaceRelease(ociocs);
|
|
}
|
|
else {
|
|
printf("Color management: Error could not find role %s role.\n", role);
|
|
}
|
|
}
|
|
|
|
static void colormanage_load_config(OCIO_ConstConfigRcPtr *config)
|
|
{
|
|
int tot_colorspace, tot_display, tot_display_view, tot_looks;
|
|
int index, viewindex, viewindex2;
|
|
const char *name;
|
|
|
|
/* get roles */
|
|
colormanage_role_color_space_name_get(config, global_role_data, OCIO_ROLE_DATA, nullptr);
|
|
colormanage_role_color_space_name_get(
|
|
config, global_role_scene_linear, OCIO_ROLE_SCENE_LINEAR, nullptr);
|
|
colormanage_role_color_space_name_get(
|
|
config, global_role_color_picking, OCIO_ROLE_COLOR_PICKING, nullptr);
|
|
colormanage_role_color_space_name_get(
|
|
config, global_role_texture_painting, OCIO_ROLE_TEXTURE_PAINT, nullptr);
|
|
colormanage_role_color_space_name_get(
|
|
config, global_role_default_sequencer, OCIO_ROLE_DEFAULT_SEQUENCER, OCIO_ROLE_SCENE_LINEAR);
|
|
colormanage_role_color_space_name_get(
|
|
config, global_role_default_byte, OCIO_ROLE_DEFAULT_BYTE, OCIO_ROLE_TEXTURE_PAINT);
|
|
colormanage_role_color_space_name_get(
|
|
config, global_role_default_float, OCIO_ROLE_DEFAULT_FLOAT, OCIO_ROLE_SCENE_LINEAR);
|
|
|
|
/* load colorspaces */
|
|
tot_colorspace = OCIO_configGetNumColorSpaces(config);
|
|
for (index = 0; index < tot_colorspace; index++) {
|
|
OCIO_ConstColorSpaceRcPtr *ocio_colorspace;
|
|
const char *description;
|
|
bool is_invertible, is_data;
|
|
|
|
name = OCIO_configGetColorSpaceNameByIndex(config, index);
|
|
|
|
ocio_colorspace = OCIO_configGetColorSpace(config, name);
|
|
description = OCIO_colorSpaceGetDescription(ocio_colorspace);
|
|
is_invertible = OCIO_colorSpaceIsInvertible(ocio_colorspace);
|
|
is_data = OCIO_colorSpaceIsData(ocio_colorspace);
|
|
|
|
ColorSpace *colorspace = colormanage_colorspace_add(name, description, is_invertible, is_data);
|
|
|
|
colorspace->num_aliases = OCIO_colorSpaceGetNumAliases(ocio_colorspace);
|
|
if (colorspace->num_aliases > 0) {
|
|
colorspace->aliases = static_cast<char(*)[MAX_COLORSPACE_NAME]>(MEM_callocN(
|
|
sizeof(*colorspace->aliases) * colorspace->num_aliases, "ColorSpace aliases"));
|
|
for (int i = 0; i < colorspace->num_aliases; i++) {
|
|
BLI_strncpy(colorspace->aliases[i],
|
|
OCIO_colorSpaceGetAlias(ocio_colorspace, i),
|
|
MAX_COLORSPACE_NAME);
|
|
}
|
|
}
|
|
|
|
OCIO_colorSpaceRelease(ocio_colorspace);
|
|
}
|
|
|
|
/* load displays */
|
|
viewindex2 = 0;
|
|
tot_display = OCIO_configGetNumDisplays(config);
|
|
|
|
for (index = 0; index < tot_display; index++) {
|
|
const char *displayname;
|
|
ColorManagedDisplay *display;
|
|
|
|
displayname = OCIO_configGetDisplay(config, index);
|
|
|
|
display = colormanage_display_add(displayname);
|
|
|
|
/* load views */
|
|
tot_display_view = OCIO_configGetNumViews(config, displayname);
|
|
for (viewindex = 0; viewindex < tot_display_view; viewindex++, viewindex2++) {
|
|
const char *viewname;
|
|
ColorManagedView *view;
|
|
LinkData *display_view;
|
|
|
|
viewname = OCIO_configGetView(config, displayname, viewindex);
|
|
|
|
/* first check if view transform with given name was already loaded */
|
|
view = colormanage_view_get_named(viewname);
|
|
|
|
if (!view) {
|
|
view = colormanage_view_add(viewname);
|
|
}
|
|
|
|
display_view = BLI_genericNodeN(view);
|
|
|
|
BLI_addtail(&display->views, display_view);
|
|
}
|
|
}
|
|
|
|
global_tot_display = tot_display;
|
|
|
|
/* load looks */
|
|
tot_looks = OCIO_configGetNumLooks(config);
|
|
colormanage_look_add("None", "", true);
|
|
for (index = 0; index < tot_looks; index++) {
|
|
OCIO_ConstLookRcPtr *ocio_look;
|
|
const char *process_space;
|
|
|
|
name = OCIO_configGetLookNameByIndex(config, index);
|
|
ocio_look = OCIO_configGetLook(config, name);
|
|
process_space = OCIO_lookGetProcessSpace(ocio_look);
|
|
OCIO_lookRelease(ocio_look);
|
|
|
|
colormanage_look_add(name, process_space, false);
|
|
}
|
|
|
|
/* Load luminance coefficients. */
|
|
OCIO_configGetDefaultLumaCoefs(config, imbuf_luma_coefficients);
|
|
|
|
/* Load standard color spaces. */
|
|
OCIO_configGetXYZtoSceneLinear(config, imbuf_xyz_to_scene_linear);
|
|
invert_m3_m3(imbuf_scene_linear_to_xyz, imbuf_xyz_to_scene_linear);
|
|
|
|
mul_m3_m3m3(imbuf_scene_linear_to_rec709, OCIO_XYZ_TO_REC709, imbuf_scene_linear_to_xyz);
|
|
invert_m3_m3(imbuf_rec709_to_scene_linear, imbuf_scene_linear_to_rec709);
|
|
|
|
mul_m3_m3m3(imbuf_aces_to_scene_linear, imbuf_xyz_to_scene_linear, OCIO_ACES_TO_XYZ);
|
|
invert_m3_m3(imbuf_scene_linear_to_aces, imbuf_aces_to_scene_linear);
|
|
}
|
|
|
|
static void colormanage_free_config()
|
|
{
|
|
ColorSpace *colorspace;
|
|
ColorManagedDisplay *display;
|
|
|
|
/* free color spaces */
|
|
colorspace = static_cast<ColorSpace *>(global_colorspaces.first);
|
|
while (colorspace) {
|
|
ColorSpace *colorspace_next = colorspace->next;
|
|
|
|
/* Free precomputed processors. */
|
|
if (colorspace->to_scene_linear) {
|
|
OCIO_cpuProcessorRelease((OCIO_ConstCPUProcessorRcPtr *)colorspace->to_scene_linear);
|
|
}
|
|
if (colorspace->from_scene_linear) {
|
|
OCIO_cpuProcessorRelease((OCIO_ConstCPUProcessorRcPtr *)colorspace->from_scene_linear);
|
|
}
|
|
|
|
/* free color space itself */
|
|
MEM_SAFE_FREE(colorspace->aliases);
|
|
MEM_freeN(colorspace);
|
|
|
|
colorspace = colorspace_next;
|
|
}
|
|
BLI_listbase_clear(&global_colorspaces);
|
|
global_tot_colorspace = 0;
|
|
|
|
/* free displays */
|
|
display = static_cast<ColorManagedDisplay *>(global_displays.first);
|
|
while (display) {
|
|
ColorManagedDisplay *display_next = display->next;
|
|
|
|
/* free precomputer processors */
|
|
if (display->to_scene_linear) {
|
|
OCIO_cpuProcessorRelease((OCIO_ConstCPUProcessorRcPtr *)display->to_scene_linear);
|
|
}
|
|
if (display->from_scene_linear) {
|
|
OCIO_cpuProcessorRelease((OCIO_ConstCPUProcessorRcPtr *)display->from_scene_linear);
|
|
}
|
|
|
|
/* free list of views */
|
|
BLI_freelistN(&display->views);
|
|
|
|
MEM_freeN(display);
|
|
display = display_next;
|
|
}
|
|
BLI_listbase_clear(&global_displays);
|
|
global_tot_display = 0;
|
|
|
|
/* free views */
|
|
BLI_freelistN(&global_views);
|
|
global_tot_view = 0;
|
|
|
|
/* free looks */
|
|
BLI_freelistN(&global_looks);
|
|
global_tot_looks = 0;
|
|
|
|
OCIO_exit();
|
|
}
|
|
|
|
void colormanagement_init()
|
|
{
|
|
const char *ocio_env;
|
|
const char *configdir;
|
|
char configfile[FILE_MAX];
|
|
OCIO_ConstConfigRcPtr *config = nullptr;
|
|
|
|
OCIO_init();
|
|
|
|
ocio_env = BLI_getenv("OCIO");
|
|
|
|
if (ocio_env && ocio_env[0] != '\0') {
|
|
config = OCIO_configCreateFromEnv();
|
|
if (config != nullptr) {
|
|
printf("Color management: Using %s as a configuration file\n", ocio_env);
|
|
}
|
|
}
|
|
|
|
if (config == nullptr) {
|
|
configdir = BKE_appdir_folder_id(BLENDER_DATAFILES, "colormanagement");
|
|
|
|
if (configdir) {
|
|
BLI_path_join(configfile, sizeof(configfile), configdir, BCM_CONFIG_FILE);
|
|
|
|
config = OCIO_configCreateFromFile(configfile);
|
|
}
|
|
}
|
|
|
|
if (config == nullptr) {
|
|
printf("Color management: using fallback mode for management\n");
|
|
|
|
config = OCIO_configCreateFallback();
|
|
}
|
|
|
|
if (config) {
|
|
OCIO_setCurrentConfig(config);
|
|
|
|
colormanage_load_config(config);
|
|
|
|
OCIO_configRelease(config);
|
|
}
|
|
|
|
/* If there are no valid display/views, use fallback mode. */
|
|
if (global_tot_display == 0 || global_tot_view == 0) {
|
|
printf("Color management: no displays/views in the config, using fallback mode instead\n");
|
|
|
|
/* Free old config. */
|
|
colormanage_free_config();
|
|
|
|
/* Initialize fallback config. */
|
|
config = OCIO_configCreateFallback();
|
|
colormanage_load_config(config);
|
|
}
|
|
|
|
BLI_init_srgb_conversion();
|
|
}
|
|
|
|
void colormanagement_exit()
|
|
{
|
|
OCIO_gpuCacheFree();
|
|
|
|
if (global_gpu_state.curve_mapping) {
|
|
BKE_curvemapping_free(global_gpu_state.curve_mapping);
|
|
}
|
|
|
|
if (global_gpu_state.curve_mapping_settings.lut) {
|
|
MEM_freeN(global_gpu_state.curve_mapping_settings.lut);
|
|
}
|
|
|
|
if (global_color_picking_state.cpu_processor_to) {
|
|
OCIO_cpuProcessorRelease(global_color_picking_state.cpu_processor_to);
|
|
}
|
|
|
|
if (global_color_picking_state.cpu_processor_from) {
|
|
OCIO_cpuProcessorRelease(global_color_picking_state.cpu_processor_from);
|
|
}
|
|
|
|
memset(&global_gpu_state, 0, sizeof(global_gpu_state));
|
|
memset(&global_color_picking_state, 0, sizeof(global_color_picking_state));
|
|
|
|
colormanage_free_config();
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Internal functions
|
|
* \{ */
|
|
|
|
static bool has_explicit_look_for_view(const char *view_name)
|
|
{
|
|
if (!view_name) {
|
|
return false;
|
|
}
|
|
|
|
LISTBASE_FOREACH (ColorManagedLook *, look, &global_looks) {
|
|
if (STREQ(look->view, view_name)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool colormanage_compatible_look(const ColorManagedLook *look,
|
|
const char *view_name,
|
|
const bool has_explicit_look)
|
|
{
|
|
if (look->is_noop) {
|
|
return true;
|
|
}
|
|
|
|
/* Skip looks only relevant to specific view transforms.
|
|
* If the view transform has view-specific look ignore non-specific looks. */
|
|
|
|
if (view_name && STREQ(look->view, view_name)) {
|
|
return true;
|
|
}
|
|
|
|
if (has_explicit_look) {
|
|
return false;
|
|
}
|
|
|
|
return look->view[0] == '\0';
|
|
}
|
|
|
|
static bool colormanage_compatible_look(const ColorManagedLook *look, const char *view_name)
|
|
{
|
|
const bool has_explicit_look = has_explicit_look_for_view(view_name);
|
|
return colormanage_compatible_look(look, view_name, has_explicit_look);
|
|
}
|
|
|
|
static bool colormanage_use_look(const char *look, const char *view_name)
|
|
{
|
|
ColorManagedLook *look_descr = colormanage_look_get_named(look);
|
|
return (look_descr->is_noop == false && colormanage_compatible_look(look_descr, view_name));
|
|
}
|
|
|
|
void colormanage_cache_free(ImBuf *ibuf)
|
|
{
|
|
MEM_SAFE_FREE(ibuf->display_buffer_flags);
|
|
|
|
if (ibuf->colormanage_cache) {
|
|
ColormanageCacheData *cache_data = colormanage_cachedata_get(ibuf);
|
|
MovieCache *moviecache = colormanage_moviecache_get(ibuf);
|
|
|
|
if (cache_data) {
|
|
MEM_freeN(cache_data);
|
|
}
|
|
|
|
if (moviecache) {
|
|
IMB_moviecache_free(moviecache);
|
|
}
|
|
|
|
MEM_freeN(ibuf->colormanage_cache);
|
|
|
|
ibuf->colormanage_cache = nullptr;
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_display_settings_from_ctx(
|
|
const bContext *C,
|
|
ColorManagedViewSettings **r_view_settings,
|
|
ColorManagedDisplaySettings **r_display_settings)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
SpaceImage *sima = CTX_wm_space_image(C);
|
|
|
|
*r_view_settings = &scene->view_settings;
|
|
*r_display_settings = &scene->display_settings;
|
|
|
|
if (sima && sima->image) {
|
|
if ((sima->image->flag & IMA_VIEW_AS_RENDER) == 0) {
|
|
*r_view_settings = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *get_display_colorspace_name(const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings)
|
|
{
|
|
OCIO_ConstConfigRcPtr *config = OCIO_getCurrentConfig();
|
|
|
|
const char *display = display_settings->display_device;
|
|
const char *view = view_settings->view_transform;
|
|
const char *colorspace_name;
|
|
|
|
colorspace_name = OCIO_configGetDisplayColorSpaceName(config, display, view);
|
|
|
|
OCIO_configRelease(config);
|
|
|
|
return colorspace_name;
|
|
}
|
|
|
|
static ColorSpace *display_transform_get_colorspace(
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings)
|
|
{
|
|
const char *colorspace_name = get_display_colorspace_name(view_settings, display_settings);
|
|
|
|
if (colorspace_name) {
|
|
return colormanage_colorspace_get_named(colorspace_name);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static OCIO_ConstCPUProcessorRcPtr *create_display_buffer_processor(const char *look,
|
|
const char *view_transform,
|
|
const char *display,
|
|
float exposure,
|
|
float gamma,
|
|
const char *from_colorspace)
|
|
{
|
|
OCIO_ConstConfigRcPtr *config = OCIO_getCurrentConfig();
|
|
const bool use_look = colormanage_use_look(look, view_transform);
|
|
const float scale = (exposure == 0.0f) ? 1.0f : powf(2.0f, exposure);
|
|
const float exponent = (gamma == 1.0f) ? 1.0f : 1.0f / max_ff(FLT_EPSILON, gamma);
|
|
|
|
OCIO_ConstProcessorRcPtr *processor = OCIO_createDisplayProcessor(config,
|
|
from_colorspace,
|
|
view_transform,
|
|
display,
|
|
(use_look) ? look : "",
|
|
scale,
|
|
exponent,
|
|
false);
|
|
|
|
OCIO_configRelease(config);
|
|
|
|
if (processor == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
OCIO_ConstCPUProcessorRcPtr *cpu_processor = OCIO_processorGetCPUProcessor(processor);
|
|
OCIO_processorRelease(processor);
|
|
|
|
return cpu_processor;
|
|
}
|
|
|
|
static OCIO_ConstProcessorRcPtr *create_colorspace_transform_processor(const char *from_colorspace,
|
|
const char *to_colorspace)
|
|
{
|
|
OCIO_ConstConfigRcPtr *config = OCIO_getCurrentConfig();
|
|
OCIO_ConstProcessorRcPtr *processor;
|
|
|
|
processor = OCIO_configGetProcessorWithNames(config, from_colorspace, to_colorspace);
|
|
|
|
OCIO_configRelease(config);
|
|
|
|
return processor;
|
|
}
|
|
|
|
static OCIO_ConstCPUProcessorRcPtr *colorspace_to_scene_linear_cpu_processor(
|
|
ColorSpace *colorspace)
|
|
{
|
|
if (colorspace->to_scene_linear == nullptr) {
|
|
BLI_mutex_lock(&processor_lock);
|
|
|
|
if (colorspace->to_scene_linear == nullptr) {
|
|
OCIO_ConstProcessorRcPtr *processor = create_colorspace_transform_processor(
|
|
colorspace->name, global_role_scene_linear);
|
|
|
|
if (processor != nullptr) {
|
|
colorspace->to_scene_linear = (OCIO_ConstCPUProcessorRcPtr *)OCIO_processorGetCPUProcessor(
|
|
processor);
|
|
OCIO_processorRelease(processor);
|
|
}
|
|
}
|
|
|
|
BLI_mutex_unlock(&processor_lock);
|
|
}
|
|
|
|
return (OCIO_ConstCPUProcessorRcPtr *)colorspace->to_scene_linear;
|
|
}
|
|
|
|
static OCIO_ConstCPUProcessorRcPtr *colorspace_from_scene_linear_cpu_processor(
|
|
ColorSpace *colorspace)
|
|
{
|
|
if (colorspace->from_scene_linear == nullptr) {
|
|
BLI_mutex_lock(&processor_lock);
|
|
|
|
if (colorspace->from_scene_linear == nullptr) {
|
|
OCIO_ConstProcessorRcPtr *processor = create_colorspace_transform_processor(
|
|
global_role_scene_linear, colorspace->name);
|
|
|
|
if (processor != nullptr) {
|
|
colorspace->from_scene_linear = (OCIO_ConstCPUProcessorRcPtr *)
|
|
OCIO_processorGetCPUProcessor(processor);
|
|
OCIO_processorRelease(processor);
|
|
}
|
|
}
|
|
|
|
BLI_mutex_unlock(&processor_lock);
|
|
}
|
|
|
|
return (OCIO_ConstCPUProcessorRcPtr *)colorspace->from_scene_linear;
|
|
}
|
|
|
|
static OCIO_ConstCPUProcessorRcPtr *display_from_scene_linear_processor(
|
|
ColorManagedDisplay *display)
|
|
{
|
|
if (display->from_scene_linear == nullptr) {
|
|
BLI_mutex_lock(&processor_lock);
|
|
|
|
if (display->from_scene_linear == nullptr) {
|
|
const char *view_name = colormanage_view_get_default_name(display);
|
|
OCIO_ConstConfigRcPtr *config = OCIO_getCurrentConfig();
|
|
OCIO_ConstProcessorRcPtr *processor = nullptr;
|
|
|
|
if (view_name && config) {
|
|
processor = OCIO_createDisplayProcessor(config,
|
|
global_role_scene_linear,
|
|
view_name,
|
|
display->name,
|
|
nullptr,
|
|
1.0f,
|
|
1.0f,
|
|
false);
|
|
|
|
OCIO_configRelease(config);
|
|
}
|
|
|
|
if (processor != nullptr) {
|
|
display->from_scene_linear = (OCIO_ConstCPUProcessorRcPtr *)OCIO_processorGetCPUProcessor(
|
|
processor);
|
|
OCIO_processorRelease(processor);
|
|
}
|
|
}
|
|
|
|
BLI_mutex_unlock(&processor_lock);
|
|
}
|
|
|
|
return (OCIO_ConstCPUProcessorRcPtr *)display->from_scene_linear;
|
|
}
|
|
|
|
static OCIO_ConstCPUProcessorRcPtr *display_to_scene_linear_processor(ColorManagedDisplay *display)
|
|
{
|
|
if (display->to_scene_linear == nullptr) {
|
|
BLI_mutex_lock(&processor_lock);
|
|
|
|
if (display->to_scene_linear == nullptr) {
|
|
const char *view_name = colormanage_view_get_default_name(display);
|
|
OCIO_ConstConfigRcPtr *config = OCIO_getCurrentConfig();
|
|
OCIO_ConstProcessorRcPtr *processor = nullptr;
|
|
|
|
if (view_name && config) {
|
|
processor = OCIO_createDisplayProcessor(
|
|
config, global_role_scene_linear, view_name, display->name, nullptr, 1.0f, 1.0f, true);
|
|
|
|
OCIO_configRelease(config);
|
|
}
|
|
|
|
if (processor != nullptr) {
|
|
display->to_scene_linear = (OCIO_ConstCPUProcessorRcPtr *)OCIO_processorGetCPUProcessor(
|
|
processor);
|
|
OCIO_processorRelease(processor);
|
|
}
|
|
}
|
|
|
|
BLI_mutex_unlock(&processor_lock);
|
|
}
|
|
|
|
return (OCIO_ConstCPUProcessorRcPtr *)display->to_scene_linear;
|
|
}
|
|
|
|
void IMB_colormanagement_init_default_view_settings(
|
|
ColorManagedViewSettings *view_settings, const ColorManagedDisplaySettings *display_settings)
|
|
{
|
|
/* First, try use "Standard" view transform of the requested device. */
|
|
ColorManagedView *default_view = colormanage_view_get_named_for_display(
|
|
display_settings->display_device, "Standard");
|
|
/* If that fails, we fall back to the default view transform of the display
|
|
* as per OCIO configuration. */
|
|
if (default_view == nullptr) {
|
|
ColorManagedDisplay *display = colormanage_display_get_named(display_settings->display_device);
|
|
if (display != nullptr) {
|
|
default_view = colormanage_view_get_default(display);
|
|
}
|
|
}
|
|
if (default_view != nullptr) {
|
|
STRNCPY(view_settings->view_transform, default_view->name);
|
|
}
|
|
else {
|
|
view_settings->view_transform[0] = '\0';
|
|
}
|
|
/* TODO(sergey): Find a way to safely/reliable un-hardcode this. */
|
|
STRNCPY(view_settings->look, "None");
|
|
/* Initialize rest of the settings. */
|
|
view_settings->flag = 0;
|
|
view_settings->gamma = 1.0f;
|
|
view_settings->exposure = 0.0f;
|
|
view_settings->curve_mapping = nullptr;
|
|
}
|
|
|
|
static void curve_mapping_apply_pixel(CurveMapping *curve_mapping, float *pixel, int channels)
|
|
{
|
|
if (channels == 1) {
|
|
pixel[0] = BKE_curvemap_evaluateF(curve_mapping, curve_mapping->cm, pixel[0]);
|
|
}
|
|
else if (channels == 2) {
|
|
pixel[0] = BKE_curvemap_evaluateF(curve_mapping, curve_mapping->cm, pixel[0]);
|
|
pixel[1] = BKE_curvemap_evaluateF(curve_mapping, curve_mapping->cm, pixel[1]);
|
|
}
|
|
else {
|
|
BKE_curvemapping_evaluate_premulRGBF(curve_mapping, pixel, pixel);
|
|
}
|
|
}
|
|
|
|
void colorspace_set_default_role(char *colorspace, int size, int role)
|
|
{
|
|
if (colorspace && colorspace[0] == '\0') {
|
|
const char *role_colorspace;
|
|
|
|
role_colorspace = IMB_colormanagement_role_colorspace_name_get(role);
|
|
|
|
BLI_strncpy(colorspace, role_colorspace, size);
|
|
}
|
|
}
|
|
|
|
void colormanage_imbuf_set_default_spaces(ImBuf *ibuf)
|
|
{
|
|
ibuf->byte_buffer.colorspace = colormanage_colorspace_get_named(global_role_default_byte);
|
|
}
|
|
|
|
void colormanage_imbuf_make_linear(ImBuf *ibuf, const char *from_colorspace)
|
|
{
|
|
ColorSpace *colorspace = colormanage_colorspace_get_named(from_colorspace);
|
|
|
|
if (colorspace && colorspace->is_data) {
|
|
ibuf->colormanage_flag |= IMB_COLORMANAGE_IS_DATA;
|
|
return;
|
|
}
|
|
|
|
if (ibuf->float_buffer.data) {
|
|
const char *to_colorspace = global_role_scene_linear;
|
|
const bool predivide = IMB_alpha_affects_rgb(ibuf);
|
|
|
|
if (ibuf->byte_buffer.data) {
|
|
imb_freerectImBuf(ibuf);
|
|
}
|
|
|
|
IMB_colormanagement_transform(ibuf->float_buffer.data,
|
|
ibuf->x,
|
|
ibuf->y,
|
|
ibuf->channels,
|
|
from_colorspace,
|
|
to_colorspace,
|
|
predivide);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Generic Functions
|
|
* \{ */
|
|
|
|
static void colormanage_check_display_settings(ColorManagedDisplaySettings *display_settings,
|
|
const char *what,
|
|
const ColorManagedDisplay *default_display)
|
|
{
|
|
if (display_settings->display_device[0] == '\0') {
|
|
STRNCPY(display_settings->display_device, default_display->name);
|
|
}
|
|
else {
|
|
ColorManagedDisplay *display = colormanage_display_get_named(display_settings->display_device);
|
|
|
|
if (!display) {
|
|
printf(
|
|
"Color management: display \"%s\" used by %s not found, setting to default (\"%s\").\n",
|
|
display_settings->display_device,
|
|
what,
|
|
default_display->name);
|
|
|
|
STRNCPY(display_settings->display_device, default_display->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void colormanage_check_view_settings(ColorManagedDisplaySettings *display_settings,
|
|
ColorManagedViewSettings *view_settings,
|
|
const char *what)
|
|
{
|
|
ColorManagedDisplay *display;
|
|
ColorManagedView *default_view = nullptr;
|
|
const char *default_look_name = IMB_colormanagement_look_get_default_name();
|
|
|
|
if (view_settings->view_transform[0] == '\0') {
|
|
display = colormanage_display_get_named(display_settings->display_device);
|
|
|
|
if (display) {
|
|
default_view = colormanage_view_get_default(display);
|
|
}
|
|
|
|
if (default_view) {
|
|
STRNCPY(view_settings->view_transform, default_view->name);
|
|
}
|
|
}
|
|
else {
|
|
ColorManagedView *view = colormanage_view_get_named(view_settings->view_transform);
|
|
|
|
if (!view) {
|
|
display = colormanage_display_get_named(display_settings->display_device);
|
|
|
|
if (display) {
|
|
default_view = colormanage_view_get_default(display);
|
|
}
|
|
|
|
if (default_view) {
|
|
printf("Color management: %s view \"%s\" not found, setting default \"%s\".\n",
|
|
what,
|
|
view_settings->view_transform,
|
|
default_view->name);
|
|
|
|
STRNCPY(view_settings->view_transform, default_view->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (view_settings->look[0] == '\0') {
|
|
STRNCPY(view_settings->look, default_look_name);
|
|
}
|
|
else {
|
|
ColorManagedLook *look = colormanage_look_get_named(view_settings->look);
|
|
if (look == nullptr) {
|
|
printf("Color management: %s look \"%s\" not found, setting default \"%s\".\n",
|
|
what,
|
|
view_settings->look,
|
|
default_look_name);
|
|
|
|
STRNCPY(view_settings->look, default_look_name);
|
|
}
|
|
else if (!colormanage_compatible_look(look, view_settings->view_transform)) {
|
|
printf(
|
|
"Color management: %s look \"%s\" is not compatible with view \"%s\", setting default "
|
|
"\"%s\".\n",
|
|
what,
|
|
view_settings->look,
|
|
view_settings->view_transform,
|
|
default_look_name);
|
|
|
|
STRNCPY(view_settings->look, default_look_name);
|
|
}
|
|
}
|
|
|
|
/* OCIO_TODO: move to do_versions() */
|
|
if (view_settings->exposure == 0.0f && view_settings->gamma == 0.0f) {
|
|
view_settings->exposure = 0.0f;
|
|
view_settings->gamma = 1.0f;
|
|
}
|
|
}
|
|
|
|
static void colormanage_check_colorspace_settings(
|
|
ColorManagedColorspaceSettings *colorspace_settings, const char *what)
|
|
{
|
|
if (colorspace_settings->name[0] == '\0') {
|
|
/* pass */
|
|
}
|
|
else {
|
|
ColorSpace *colorspace = colormanage_colorspace_get_named(colorspace_settings->name);
|
|
|
|
if (!colorspace) {
|
|
printf("Color management: %s colorspace \"%s\" not found, will use default instead.\n",
|
|
what,
|
|
colorspace_settings->name);
|
|
|
|
STRNCPY(colorspace_settings->name, "");
|
|
}
|
|
}
|
|
|
|
(void)what;
|
|
}
|
|
|
|
static bool seq_callback(Sequence *seq, void * /*user_data*/)
|
|
{
|
|
if (seq->strip) {
|
|
colormanage_check_colorspace_settings(&seq->strip->colorspace_settings, "sequencer strip");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void IMB_colormanagement_check_file_config(Main *bmain)
|
|
{
|
|
ColorManagedDisplay *default_display = colormanage_display_get_default();
|
|
if (!default_display) {
|
|
/* happens when OCIO configuration is incorrect */
|
|
return;
|
|
}
|
|
|
|
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
|
|
ColorManagedColorspaceSettings *sequencer_colorspace_settings;
|
|
|
|
/* check scene color management settings */
|
|
colormanage_check_display_settings(&scene->display_settings, "scene", default_display);
|
|
colormanage_check_view_settings(&scene->display_settings, &scene->view_settings, "scene");
|
|
|
|
sequencer_colorspace_settings = &scene->sequencer_colorspace_settings;
|
|
|
|
colormanage_check_colorspace_settings(sequencer_colorspace_settings, "sequencer");
|
|
|
|
if (sequencer_colorspace_settings->name[0] == '\0') {
|
|
STRNCPY(sequencer_colorspace_settings->name, global_role_default_sequencer);
|
|
}
|
|
|
|
/* check sequencer strip input color space settings */
|
|
if (scene->ed != nullptr) {
|
|
SEQ_for_each_callback(&scene->ed->seqbase, seq_callback, nullptr);
|
|
}
|
|
}
|
|
|
|
/* ** check input color space settings ** */
|
|
|
|
LISTBASE_FOREACH (Image *, image, &bmain->images) {
|
|
colormanage_check_colorspace_settings(&image->colorspace_settings, "image");
|
|
}
|
|
|
|
LISTBASE_FOREACH (MovieClip *, clip, &bmain->movieclips) {
|
|
colormanage_check_colorspace_settings(&clip->colorspace_settings, "clip");
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_validate_settings(const ColorManagedDisplaySettings *display_settings,
|
|
ColorManagedViewSettings *view_settings)
|
|
{
|
|
ColorManagedDisplay *display = colormanage_display_get_named(display_settings->display_device);
|
|
ColorManagedView *default_view = colormanage_view_get_default(display);
|
|
|
|
bool found = false;
|
|
LISTBASE_FOREACH (LinkData *, view_link, &display->views) {
|
|
ColorManagedView *view = static_cast<ColorManagedView *>(view_link->data);
|
|
|
|
if (STREQ(view->name, view_settings->view_transform)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found && default_view) {
|
|
STRNCPY(view_settings->view_transform, default_view->name);
|
|
}
|
|
}
|
|
|
|
const char *IMB_colormanagement_role_colorspace_name_get(int role)
|
|
{
|
|
switch (role) {
|
|
case COLOR_ROLE_DATA:
|
|
return global_role_data;
|
|
case COLOR_ROLE_SCENE_LINEAR:
|
|
return global_role_scene_linear;
|
|
case COLOR_ROLE_COLOR_PICKING:
|
|
return global_role_color_picking;
|
|
case COLOR_ROLE_TEXTURE_PAINTING:
|
|
return global_role_texture_painting;
|
|
case COLOR_ROLE_DEFAULT_SEQUENCER:
|
|
return global_role_default_sequencer;
|
|
case COLOR_ROLE_DEFAULT_FLOAT:
|
|
return global_role_default_float;
|
|
case COLOR_ROLE_DEFAULT_BYTE:
|
|
return global_role_default_byte;
|
|
default:
|
|
printf("Unknown role was passed to %s\n", __func__);
|
|
BLI_assert(0);
|
|
break;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void IMB_colormanagement_check_is_data(ImBuf *ibuf, const char *name)
|
|
{
|
|
ColorSpace *colorspace = colormanage_colorspace_get_named(name);
|
|
|
|
if (colorspace && colorspace->is_data) {
|
|
ibuf->colormanage_flag |= IMB_COLORMANAGE_IS_DATA;
|
|
}
|
|
else {
|
|
ibuf->colormanage_flag &= ~IMB_COLORMANAGE_IS_DATA;
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagegent_copy_settings(ImBuf *ibuf_src, ImBuf *ibuf_dst)
|
|
{
|
|
IMB_colormanagement_assign_byte_colorspace(ibuf_dst,
|
|
IMB_colormanagement_get_rect_colorspace(ibuf_src));
|
|
IMB_colormanagement_assign_float_colorspace(ibuf_dst,
|
|
IMB_colormanagement_get_float_colorspace(ibuf_src));
|
|
if (ibuf_src->flags & IB_alphamode_premul) {
|
|
ibuf_dst->flags |= IB_alphamode_premul;
|
|
}
|
|
else if (ibuf_src->flags & IB_alphamode_channel_packed) {
|
|
ibuf_dst->flags |= IB_alphamode_channel_packed;
|
|
}
|
|
else if (ibuf_src->flags & IB_alphamode_ignore) {
|
|
ibuf_dst->flags |= IB_alphamode_ignore;
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_assign_float_colorspace(ImBuf *ibuf, const char *name)
|
|
{
|
|
ColorSpace *colorspace = colormanage_colorspace_get_named(name);
|
|
|
|
ibuf->float_buffer.colorspace = colorspace;
|
|
|
|
if (colorspace && colorspace->is_data) {
|
|
ibuf->colormanage_flag |= IMB_COLORMANAGE_IS_DATA;
|
|
}
|
|
else {
|
|
ibuf->colormanage_flag &= ~IMB_COLORMANAGE_IS_DATA;
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_assign_byte_colorspace(ImBuf *ibuf, const char *name)
|
|
{
|
|
ColorSpace *colorspace = colormanage_colorspace_get_named(name);
|
|
|
|
ibuf->byte_buffer.colorspace = colorspace;
|
|
|
|
if (colorspace && colorspace->is_data) {
|
|
ibuf->colormanage_flag |= IMB_COLORMANAGE_IS_DATA;
|
|
}
|
|
else {
|
|
ibuf->colormanage_flag &= ~IMB_COLORMANAGE_IS_DATA;
|
|
}
|
|
}
|
|
|
|
const char *IMB_colormanagement_get_float_colorspace(ImBuf *ibuf)
|
|
{
|
|
if (ibuf->float_buffer.colorspace) {
|
|
return ibuf->float_buffer.colorspace->name;
|
|
}
|
|
|
|
return IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_SCENE_LINEAR);
|
|
}
|
|
|
|
const char *IMB_colormanagement_get_rect_colorspace(ImBuf *ibuf)
|
|
{
|
|
if (ibuf->byte_buffer.colorspace) {
|
|
return ibuf->byte_buffer.colorspace->name;
|
|
}
|
|
|
|
return IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE);
|
|
}
|
|
|
|
bool IMB_colormanagement_space_is_data(ColorSpace *colorspace)
|
|
{
|
|
return (colorspace && colorspace->is_data);
|
|
}
|
|
|
|
static void colormanage_ensure_srgb_scene_linear_info(ColorSpace *colorspace)
|
|
{
|
|
if (colorspace && !colorspace->info.cached) {
|
|
OCIO_ConstConfigRcPtr *config = OCIO_getCurrentConfig();
|
|
OCIO_ConstColorSpaceRcPtr *ocio_colorspace = OCIO_configGetColorSpace(config,
|
|
colorspace->name);
|
|
|
|
bool is_scene_linear, is_srgb;
|
|
OCIO_colorSpaceIsBuiltin(config, ocio_colorspace, &is_scene_linear, &is_srgb);
|
|
|
|
OCIO_colorSpaceRelease(ocio_colorspace);
|
|
OCIO_configRelease(config);
|
|
|
|
colorspace->info.is_scene_linear = is_scene_linear;
|
|
colorspace->info.is_srgb = is_srgb;
|
|
colorspace->info.cached = true;
|
|
}
|
|
}
|
|
|
|
bool IMB_colormanagement_space_is_scene_linear(ColorSpace *colorspace)
|
|
{
|
|
colormanage_ensure_srgb_scene_linear_info(colorspace);
|
|
return (colorspace && colorspace->info.is_scene_linear);
|
|
}
|
|
|
|
bool IMB_colormanagement_space_is_srgb(ColorSpace *colorspace)
|
|
{
|
|
colormanage_ensure_srgb_scene_linear_info(colorspace);
|
|
return (colorspace && colorspace->info.is_srgb);
|
|
}
|
|
|
|
bool IMB_colormanagement_space_name_is_data(const char *name)
|
|
{
|
|
ColorSpace *colorspace = colormanage_colorspace_get_named(name);
|
|
return (colorspace && colorspace->is_data);
|
|
}
|
|
|
|
bool IMB_colormanagement_space_name_is_scene_linear(const char *name)
|
|
{
|
|
ColorSpace *colorspace = colormanage_colorspace_get_named(name);
|
|
return (colorspace && IMB_colormanagement_space_is_scene_linear(colorspace));
|
|
}
|
|
|
|
bool IMB_colormanagement_space_name_is_srgb(const char *name)
|
|
{
|
|
ColorSpace *colorspace = colormanage_colorspace_get_named(name);
|
|
return (colorspace && IMB_colormanagement_space_is_srgb(colorspace));
|
|
}
|
|
|
|
const float *IMB_colormanagement_get_xyz_to_scene_linear()
|
|
{
|
|
return &imbuf_xyz_to_scene_linear[0][0];
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Threaded Display Buffer Transform Routines
|
|
* \{ */
|
|
|
|
struct DisplayBufferThread {
|
|
ColormanageProcessor *cm_processor;
|
|
|
|
const float *buffer;
|
|
uchar *byte_buffer;
|
|
|
|
float *display_buffer;
|
|
uchar *display_buffer_byte;
|
|
|
|
int width;
|
|
int start_line;
|
|
int tot_line;
|
|
|
|
int channels;
|
|
float dither;
|
|
bool is_data;
|
|
bool predivide;
|
|
|
|
const char *byte_colorspace;
|
|
const char *float_colorspace;
|
|
};
|
|
|
|
struct DisplayBufferInitData {
|
|
ImBuf *ibuf;
|
|
ColormanageProcessor *cm_processor;
|
|
const float *buffer;
|
|
uchar *byte_buffer;
|
|
|
|
float *display_buffer;
|
|
uchar *display_buffer_byte;
|
|
|
|
int width;
|
|
|
|
const char *byte_colorspace;
|
|
const char *float_colorspace;
|
|
};
|
|
|
|
static void display_buffer_init_handle(void *handle_v,
|
|
int start_line,
|
|
int tot_line,
|
|
void *init_data_v)
|
|
{
|
|
DisplayBufferThread *handle = (DisplayBufferThread *)handle_v;
|
|
DisplayBufferInitData *init_data = (DisplayBufferInitData *)init_data_v;
|
|
ImBuf *ibuf = init_data->ibuf;
|
|
|
|
int channels = ibuf->channels;
|
|
float dither = ibuf->dither;
|
|
bool is_data = (ibuf->colormanage_flag & IMB_COLORMANAGE_IS_DATA) != 0;
|
|
|
|
size_t offset = size_t(channels) * start_line * ibuf->x;
|
|
size_t display_buffer_byte_offset = size_t(DISPLAY_BUFFER_CHANNELS) * start_line * ibuf->x;
|
|
|
|
memset(handle, 0, sizeof(DisplayBufferThread));
|
|
|
|
handle->cm_processor = init_data->cm_processor;
|
|
|
|
if (init_data->buffer) {
|
|
handle->buffer = init_data->buffer + offset;
|
|
}
|
|
|
|
if (init_data->byte_buffer) {
|
|
handle->byte_buffer = init_data->byte_buffer + offset;
|
|
}
|
|
|
|
if (init_data->display_buffer) {
|
|
handle->display_buffer = init_data->display_buffer + offset;
|
|
}
|
|
|
|
if (init_data->display_buffer_byte) {
|
|
handle->display_buffer_byte = init_data->display_buffer_byte + display_buffer_byte_offset;
|
|
}
|
|
|
|
handle->width = ibuf->x;
|
|
|
|
handle->start_line = start_line;
|
|
handle->tot_line = tot_line;
|
|
|
|
handle->channels = channels;
|
|
handle->dither = dither;
|
|
handle->is_data = is_data;
|
|
handle->predivide = IMB_alpha_affects_rgb(ibuf);
|
|
|
|
handle->byte_colorspace = init_data->byte_colorspace;
|
|
handle->float_colorspace = init_data->float_colorspace;
|
|
}
|
|
|
|
static void display_buffer_apply_get_linear_buffer(DisplayBufferThread *handle,
|
|
int height,
|
|
float *linear_buffer,
|
|
bool *is_straight_alpha)
|
|
{
|
|
int channels = handle->channels;
|
|
int width = handle->width;
|
|
|
|
size_t buffer_size = size_t(channels) * width * height;
|
|
|
|
bool is_data = handle->is_data;
|
|
bool is_data_display = handle->cm_processor->is_data_result;
|
|
bool predivide = handle->predivide;
|
|
|
|
if (!handle->buffer) {
|
|
uchar *byte_buffer = handle->byte_buffer;
|
|
|
|
const char *from_colorspace = handle->byte_colorspace;
|
|
const char *to_colorspace = global_role_scene_linear;
|
|
|
|
float *fp;
|
|
uchar *cp;
|
|
const size_t i_last = size_t(width) * height;
|
|
size_t i;
|
|
|
|
/* first convert byte buffer to float, keep in image space */
|
|
for (i = 0, fp = linear_buffer, cp = byte_buffer; i != i_last;
|
|
i++, fp += channels, cp += channels) {
|
|
if (channels == 3) {
|
|
rgb_uchar_to_float(fp, cp);
|
|
}
|
|
else if (channels == 4) {
|
|
rgba_uchar_to_float(fp, cp);
|
|
}
|
|
else {
|
|
BLI_assert_msg(0, "Buffers of 3 or 4 channels are only supported here");
|
|
}
|
|
}
|
|
|
|
if (!is_data && !is_data_display) {
|
|
/* convert float buffer to scene linear space */
|
|
IMB_colormanagement_transform(
|
|
linear_buffer, width, height, channels, from_colorspace, to_colorspace, false);
|
|
}
|
|
|
|
*is_straight_alpha = true;
|
|
}
|
|
else if (handle->float_colorspace) {
|
|
/* currently float is non-linear only in sequencer, which is working
|
|
* in its own color space even to handle float buffers.
|
|
* This color space is the same for byte and float images.
|
|
* Need to convert float buffer to linear space before applying display transform
|
|
*/
|
|
|
|
const char *from_colorspace = handle->float_colorspace;
|
|
const char *to_colorspace = global_role_scene_linear;
|
|
|
|
memcpy(linear_buffer, handle->buffer, buffer_size * sizeof(float));
|
|
|
|
if (!is_data && !is_data_display) {
|
|
IMB_colormanagement_transform(
|
|
linear_buffer, width, height, channels, from_colorspace, to_colorspace, predivide);
|
|
}
|
|
|
|
*is_straight_alpha = false;
|
|
}
|
|
else {
|
|
/* some processors would want to modify float original buffer
|
|
* before converting it into display byte buffer, so we need to
|
|
* make sure original's ImBuf buffers wouldn't be modified by
|
|
* using duplicated buffer here
|
|
*/
|
|
|
|
memcpy(linear_buffer, handle->buffer, buffer_size * sizeof(float));
|
|
|
|
*is_straight_alpha = false;
|
|
}
|
|
}
|
|
|
|
static void *do_display_buffer_apply_thread(void *handle_v)
|
|
{
|
|
DisplayBufferThread *handle = (DisplayBufferThread *)handle_v;
|
|
ColormanageProcessor *cm_processor = handle->cm_processor;
|
|
float *display_buffer = handle->display_buffer;
|
|
uchar *display_buffer_byte = handle->display_buffer_byte;
|
|
int channels = handle->channels;
|
|
int width = handle->width;
|
|
int height = handle->tot_line;
|
|
float dither = handle->dither;
|
|
bool is_data = handle->is_data;
|
|
|
|
if (cm_processor == nullptr) {
|
|
if (display_buffer_byte && display_buffer_byte != handle->byte_buffer) {
|
|
IMB_buffer_byte_from_byte(display_buffer_byte,
|
|
handle->byte_buffer,
|
|
IB_PROFILE_SRGB,
|
|
IB_PROFILE_SRGB,
|
|
false,
|
|
width,
|
|
height,
|
|
width,
|
|
width);
|
|
}
|
|
|
|
if (display_buffer) {
|
|
IMB_buffer_float_from_byte(display_buffer,
|
|
handle->byte_buffer,
|
|
IB_PROFILE_SRGB,
|
|
IB_PROFILE_SRGB,
|
|
false,
|
|
width,
|
|
height,
|
|
width,
|
|
width);
|
|
}
|
|
}
|
|
else {
|
|
bool is_straight_alpha;
|
|
float *linear_buffer = static_cast<float *>(MEM_mallocN(
|
|
size_t(channels) * width * height * sizeof(float), "color conversion linear buffer"));
|
|
|
|
display_buffer_apply_get_linear_buffer(handle, height, linear_buffer, &is_straight_alpha);
|
|
|
|
bool predivide = handle->predivide && (is_straight_alpha == false);
|
|
|
|
if (is_data) {
|
|
/* special case for data buffers - no color space conversions,
|
|
* only generate byte buffers
|
|
*/
|
|
}
|
|
else {
|
|
/* apply processor */
|
|
IMB_colormanagement_processor_apply(
|
|
cm_processor, linear_buffer, width, height, channels, predivide);
|
|
}
|
|
|
|
/* copy result to output buffers */
|
|
if (display_buffer_byte) {
|
|
/* do conversion */
|
|
IMB_buffer_byte_from_float(display_buffer_byte,
|
|
linear_buffer,
|
|
channels,
|
|
dither,
|
|
IB_PROFILE_SRGB,
|
|
IB_PROFILE_SRGB,
|
|
predivide,
|
|
width,
|
|
height,
|
|
width,
|
|
width);
|
|
}
|
|
|
|
if (display_buffer) {
|
|
memcpy(display_buffer, linear_buffer, size_t(width) * height * channels * sizeof(float));
|
|
|
|
if (is_straight_alpha && channels == 4) {
|
|
const size_t i_last = size_t(width) * height;
|
|
size_t i;
|
|
float *fp;
|
|
|
|
for (i = 0, fp = display_buffer; i != i_last; i++, fp += channels) {
|
|
straight_to_premul_v4(fp);
|
|
}
|
|
}
|
|
}
|
|
|
|
MEM_freeN(linear_buffer);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static void display_buffer_apply_threaded(ImBuf *ibuf,
|
|
const float *buffer,
|
|
uchar *byte_buffer,
|
|
float *display_buffer,
|
|
uchar *display_buffer_byte,
|
|
ColormanageProcessor *cm_processor)
|
|
{
|
|
DisplayBufferInitData init_data;
|
|
|
|
init_data.ibuf = ibuf;
|
|
init_data.cm_processor = cm_processor;
|
|
init_data.buffer = buffer;
|
|
init_data.byte_buffer = byte_buffer;
|
|
init_data.display_buffer = display_buffer;
|
|
init_data.display_buffer_byte = display_buffer_byte;
|
|
|
|
if (ibuf->byte_buffer.colorspace != nullptr) {
|
|
init_data.byte_colorspace = ibuf->byte_buffer.colorspace->name;
|
|
}
|
|
else {
|
|
/* happens for viewer images, which are not so simple to determine where to
|
|
* set image buffer's color spaces
|
|
*/
|
|
init_data.byte_colorspace = global_role_default_byte;
|
|
}
|
|
|
|
if (ibuf->float_buffer.colorspace != nullptr) {
|
|
/* sequencer stores float buffers in non-linear space */
|
|
init_data.float_colorspace = ibuf->float_buffer.colorspace->name;
|
|
}
|
|
else {
|
|
init_data.float_colorspace = nullptr;
|
|
}
|
|
|
|
IMB_processor_apply_threaded(ibuf->y,
|
|
sizeof(DisplayBufferThread),
|
|
&init_data,
|
|
display_buffer_init_handle,
|
|
do_display_buffer_apply_thread);
|
|
}
|
|
|
|
static bool is_ibuf_rect_in_display_space(ImBuf *ibuf,
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings)
|
|
{
|
|
if ((view_settings->flag & COLORMANAGE_VIEW_USE_CURVES) == 0 &&
|
|
view_settings->exposure == 0.0f && view_settings->gamma == 1.0f)
|
|
{
|
|
const char *from_colorspace = ibuf->byte_buffer.colorspace->name;
|
|
const char *to_colorspace = get_display_colorspace_name(view_settings, display_settings);
|
|
ColorManagedLook *look_descr = colormanage_look_get_named(view_settings->look);
|
|
if (look_descr != nullptr && !STREQ(look_descr->process_space, "")) {
|
|
return false;
|
|
}
|
|
|
|
if (to_colorspace && STREQ(from_colorspace, to_colorspace)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void colormanage_display_buffer_process_ex(
|
|
ImBuf *ibuf,
|
|
float *display_buffer,
|
|
uchar *display_buffer_byte,
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings)
|
|
{
|
|
ColormanageProcessor *cm_processor = nullptr;
|
|
bool skip_transform = false;
|
|
|
|
/* if we're going to transform byte buffer, check whether transformation would
|
|
* happen to the same color space as byte buffer itself is
|
|
* this would save byte -> float -> byte conversions making display buffer
|
|
* computation noticeable faster
|
|
*/
|
|
if (ibuf->float_buffer.data == nullptr && ibuf->byte_buffer.colorspace) {
|
|
skip_transform = is_ibuf_rect_in_display_space(ibuf, view_settings, display_settings);
|
|
}
|
|
|
|
if (skip_transform == false) {
|
|
cm_processor = IMB_colormanagement_display_processor_new(view_settings, display_settings);
|
|
}
|
|
|
|
display_buffer_apply_threaded(ibuf,
|
|
ibuf->float_buffer.data,
|
|
ibuf->byte_buffer.data,
|
|
display_buffer,
|
|
display_buffer_byte,
|
|
cm_processor);
|
|
|
|
if (cm_processor) {
|
|
IMB_colormanagement_processor_free(cm_processor);
|
|
}
|
|
}
|
|
|
|
static void colormanage_display_buffer_process(ImBuf *ibuf,
|
|
uchar *display_buffer,
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings)
|
|
{
|
|
colormanage_display_buffer_process_ex(
|
|
ibuf, nullptr, display_buffer, view_settings, display_settings);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Threaded Processor Transform Routines
|
|
* \{ */
|
|
|
|
struct ProcessorTransformThread {
|
|
ColormanageProcessor *cm_processor;
|
|
uchar *byte_buffer;
|
|
float *float_buffer;
|
|
int width;
|
|
int start_line;
|
|
int tot_line;
|
|
int channels;
|
|
bool predivide;
|
|
bool float_from_byte;
|
|
};
|
|
|
|
struct ProcessorTransformInitData {
|
|
ColormanageProcessor *cm_processor;
|
|
uchar *byte_buffer;
|
|
float *float_buffer;
|
|
int width;
|
|
int height;
|
|
int channels;
|
|
bool predivide;
|
|
bool float_from_byte;
|
|
};
|
|
|
|
static void processor_transform_init_handle(void *handle_v,
|
|
int start_line,
|
|
int tot_line,
|
|
void *init_data_v)
|
|
{
|
|
ProcessorTransformThread *handle = (ProcessorTransformThread *)handle_v;
|
|
ProcessorTransformInitData *init_data = (ProcessorTransformInitData *)init_data_v;
|
|
|
|
const int channels = init_data->channels;
|
|
const int width = init_data->width;
|
|
const bool predivide = init_data->predivide;
|
|
const bool float_from_byte = init_data->float_from_byte;
|
|
|
|
const size_t offset = size_t(channels) * start_line * width;
|
|
|
|
memset(handle, 0, sizeof(ProcessorTransformThread));
|
|
|
|
handle->cm_processor = init_data->cm_processor;
|
|
|
|
if (init_data->byte_buffer != nullptr) {
|
|
/* TODO(serge): Offset might be different for byte and float buffers. */
|
|
handle->byte_buffer = init_data->byte_buffer + offset;
|
|
}
|
|
if (init_data->float_buffer != nullptr) {
|
|
handle->float_buffer = init_data->float_buffer + offset;
|
|
}
|
|
|
|
handle->width = width;
|
|
|
|
handle->start_line = start_line;
|
|
handle->tot_line = tot_line;
|
|
|
|
handle->channels = channels;
|
|
handle->predivide = predivide;
|
|
handle->float_from_byte = float_from_byte;
|
|
}
|
|
|
|
static void *do_processor_transform_thread(void *handle_v)
|
|
{
|
|
ProcessorTransformThread *handle = (ProcessorTransformThread *)handle_v;
|
|
uchar *byte_buffer = handle->byte_buffer;
|
|
float *float_buffer = handle->float_buffer;
|
|
const int channels = handle->channels;
|
|
const int width = handle->width;
|
|
const int height = handle->tot_line;
|
|
const bool predivide = handle->predivide;
|
|
const bool float_from_byte = handle->float_from_byte;
|
|
|
|
if (float_from_byte) {
|
|
IMB_buffer_float_from_byte(float_buffer,
|
|
byte_buffer,
|
|
IB_PROFILE_SRGB,
|
|
IB_PROFILE_SRGB,
|
|
false,
|
|
width,
|
|
height,
|
|
width,
|
|
width);
|
|
IMB_colormanagement_processor_apply(
|
|
handle->cm_processor, float_buffer, width, height, channels, predivide);
|
|
IMB_premultiply_rect_float(float_buffer, 4, width, height);
|
|
}
|
|
else {
|
|
if (byte_buffer != nullptr) {
|
|
IMB_colormanagement_processor_apply_byte(
|
|
handle->cm_processor, byte_buffer, width, height, channels);
|
|
}
|
|
if (float_buffer != nullptr) {
|
|
IMB_colormanagement_processor_apply(
|
|
handle->cm_processor, float_buffer, width, height, channels, predivide);
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static void processor_transform_apply_threaded(uchar *byte_buffer,
|
|
float *float_buffer,
|
|
const int width,
|
|
const int height,
|
|
const int channels,
|
|
ColormanageProcessor *cm_processor,
|
|
const bool predivide,
|
|
const bool float_from_byte)
|
|
{
|
|
ProcessorTransformInitData init_data;
|
|
|
|
init_data.cm_processor = cm_processor;
|
|
init_data.byte_buffer = byte_buffer;
|
|
init_data.float_buffer = float_buffer;
|
|
init_data.width = width;
|
|
init_data.height = height;
|
|
init_data.channels = channels;
|
|
init_data.predivide = predivide;
|
|
init_data.float_from_byte = float_from_byte;
|
|
|
|
IMB_processor_apply_threaded(height,
|
|
sizeof(ProcessorTransformThread),
|
|
&init_data,
|
|
processor_transform_init_handle,
|
|
do_processor_transform_thread);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Color Space Transformation Functions
|
|
* \{ */
|
|
|
|
/* Convert the whole buffer from specified by name color space to another -
|
|
* internal implementation. */
|
|
static void colormanagement_transform_ex(uchar *byte_buffer,
|
|
float *float_buffer,
|
|
int width,
|
|
int height,
|
|
int channels,
|
|
const char *from_colorspace,
|
|
const char *to_colorspace,
|
|
bool predivide,
|
|
bool do_threaded)
|
|
{
|
|
if (from_colorspace[0] == '\0') {
|
|
return;
|
|
}
|
|
|
|
if (STREQ(from_colorspace, to_colorspace)) {
|
|
/* if source and destination color spaces are identical, skip
|
|
* threading overhead and simply do nothing
|
|
*/
|
|
return;
|
|
}
|
|
|
|
ColormanageProcessor *cm_processor = IMB_colormanagement_colorspace_processor_new(
|
|
from_colorspace, to_colorspace);
|
|
if (IMB_colormanagement_processor_is_noop(cm_processor)) {
|
|
IMB_colormanagement_processor_free(cm_processor);
|
|
return;
|
|
}
|
|
|
|
if (do_threaded) {
|
|
processor_transform_apply_threaded(
|
|
byte_buffer, float_buffer, width, height, channels, cm_processor, predivide, false);
|
|
}
|
|
else {
|
|
if (byte_buffer != nullptr) {
|
|
IMB_colormanagement_processor_apply_byte(cm_processor, byte_buffer, width, height, channels);
|
|
}
|
|
if (float_buffer != nullptr) {
|
|
IMB_colormanagement_processor_apply(
|
|
cm_processor, float_buffer, width, height, channels, predivide);
|
|
}
|
|
}
|
|
|
|
IMB_colormanagement_processor_free(cm_processor);
|
|
}
|
|
|
|
void IMB_colormanagement_transform(float *buffer,
|
|
int width,
|
|
int height,
|
|
int channels,
|
|
const char *from_colorspace,
|
|
const char *to_colorspace,
|
|
bool predivide)
|
|
{
|
|
colormanagement_transform_ex(
|
|
nullptr, buffer, width, height, channels, from_colorspace, to_colorspace, predivide, false);
|
|
}
|
|
|
|
void IMB_colormanagement_transform_threaded(float *buffer,
|
|
int width,
|
|
int height,
|
|
int channels,
|
|
const char *from_colorspace,
|
|
const char *to_colorspace,
|
|
bool predivide)
|
|
{
|
|
colormanagement_transform_ex(
|
|
nullptr, buffer, width, height, channels, from_colorspace, to_colorspace, predivide, true);
|
|
}
|
|
|
|
void IMB_colormanagement_transform_byte(uchar *buffer,
|
|
int width,
|
|
int height,
|
|
int channels,
|
|
const char *from_colorspace,
|
|
const char *to_colorspace)
|
|
{
|
|
colormanagement_transform_ex(
|
|
buffer, nullptr, width, height, channels, from_colorspace, to_colorspace, false, false);
|
|
}
|
|
void IMB_colormanagement_transform_byte_threaded(uchar *buffer,
|
|
int width,
|
|
int height,
|
|
int channels,
|
|
const char *from_colorspace,
|
|
const char *to_colorspace)
|
|
{
|
|
colormanagement_transform_ex(
|
|
buffer, nullptr, width, height, channels, from_colorspace, to_colorspace, false, true);
|
|
}
|
|
|
|
void IMB_colormanagement_transform_from_byte(float *float_buffer,
|
|
uchar *byte_buffer,
|
|
int width,
|
|
int height,
|
|
int channels,
|
|
const char *from_colorspace,
|
|
const char *to_colorspace)
|
|
{
|
|
IMB_buffer_float_from_byte(float_buffer,
|
|
byte_buffer,
|
|
IB_PROFILE_SRGB,
|
|
IB_PROFILE_SRGB,
|
|
true,
|
|
width,
|
|
height,
|
|
width,
|
|
width);
|
|
IMB_colormanagement_transform(
|
|
float_buffer, width, height, channels, from_colorspace, to_colorspace, true);
|
|
}
|
|
void IMB_colormanagement_transform_from_byte_threaded(float *float_buffer,
|
|
uchar *byte_buffer,
|
|
int width,
|
|
int height,
|
|
int channels,
|
|
const char *from_colorspace,
|
|
const char *to_colorspace)
|
|
{
|
|
ColormanageProcessor *cm_processor;
|
|
if (from_colorspace == nullptr || from_colorspace[0] == '\0') {
|
|
return;
|
|
}
|
|
if (STREQ(from_colorspace, to_colorspace)) {
|
|
/* Because this function always takes a byte buffer and returns a float buffer, it must
|
|
* always do byte-to-float conversion of some kind. To avoid threading overhead
|
|
* IMB_buffer_float_from_byte is used when color spaces are identical. See #51002.
|
|
*/
|
|
IMB_buffer_float_from_byte(float_buffer,
|
|
byte_buffer,
|
|
IB_PROFILE_SRGB,
|
|
IB_PROFILE_SRGB,
|
|
false,
|
|
width,
|
|
height,
|
|
width,
|
|
width);
|
|
IMB_premultiply_rect_float(float_buffer, 4, width, height);
|
|
return;
|
|
}
|
|
cm_processor = IMB_colormanagement_colorspace_processor_new(from_colorspace, to_colorspace);
|
|
processor_transform_apply_threaded(
|
|
byte_buffer, float_buffer, width, height, channels, cm_processor, false, true);
|
|
IMB_colormanagement_processor_free(cm_processor);
|
|
}
|
|
|
|
void IMB_colormanagement_transform_v4(float pixel[4],
|
|
const char *from_colorspace,
|
|
const char *to_colorspace)
|
|
{
|
|
ColormanageProcessor *cm_processor;
|
|
|
|
if (from_colorspace[0] == '\0') {
|
|
return;
|
|
}
|
|
|
|
if (STREQ(from_colorspace, to_colorspace)) {
|
|
/* if source and destination color spaces are identical, skip
|
|
* threading overhead and simply do nothing
|
|
*/
|
|
return;
|
|
}
|
|
|
|
cm_processor = IMB_colormanagement_colorspace_processor_new(from_colorspace, to_colorspace);
|
|
|
|
IMB_colormanagement_processor_apply_v4(cm_processor, pixel);
|
|
|
|
IMB_colormanagement_processor_free(cm_processor);
|
|
}
|
|
|
|
void IMB_colormanagement_colorspace_to_scene_linear_v3(float pixel[3], ColorSpace *colorspace)
|
|
{
|
|
OCIO_ConstCPUProcessorRcPtr *processor;
|
|
|
|
if (!colorspace) {
|
|
/* should never happen */
|
|
printf("%s: perform conversion from unknown color space\n", __func__);
|
|
return;
|
|
}
|
|
|
|
processor = colorspace_to_scene_linear_cpu_processor(colorspace);
|
|
|
|
if (processor != nullptr) {
|
|
OCIO_cpuProcessorApplyRGB(processor, pixel);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_scene_linear_to_colorspace_v3(float pixel[3], ColorSpace *colorspace)
|
|
{
|
|
OCIO_ConstCPUProcessorRcPtr *processor;
|
|
|
|
if (!colorspace) {
|
|
/* should never happen */
|
|
printf("%s: perform conversion from unknown color space\n", __func__);
|
|
return;
|
|
}
|
|
|
|
processor = colorspace_from_scene_linear_cpu_processor(colorspace);
|
|
|
|
if (processor != nullptr) {
|
|
OCIO_cpuProcessorApplyRGB(processor, pixel);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_colorspace_to_scene_linear_v4(float pixel[4],
|
|
bool predivide,
|
|
ColorSpace *colorspace)
|
|
{
|
|
OCIO_ConstCPUProcessorRcPtr *processor;
|
|
|
|
if (!colorspace) {
|
|
/* should never happen */
|
|
printf("%s: perform conversion from unknown color space\n", __func__);
|
|
return;
|
|
}
|
|
|
|
processor = colorspace_to_scene_linear_cpu_processor(colorspace);
|
|
|
|
if (processor != nullptr) {
|
|
if (predivide) {
|
|
OCIO_cpuProcessorApplyRGBA_predivide(processor, pixel);
|
|
}
|
|
else {
|
|
OCIO_cpuProcessorApplyRGBA(processor, pixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_colorspace_to_scene_linear(
|
|
float *buffer, int width, int height, int channels, ColorSpace *colorspace, bool predivide)
|
|
{
|
|
OCIO_ConstCPUProcessorRcPtr *processor;
|
|
|
|
if (!colorspace) {
|
|
/* should never happen */
|
|
printf("%s: perform conversion from unknown color space\n", __func__);
|
|
return;
|
|
}
|
|
|
|
processor = colorspace_to_scene_linear_cpu_processor(colorspace);
|
|
|
|
if (processor != nullptr) {
|
|
OCIO_PackedImageDesc *img;
|
|
|
|
img = OCIO_createOCIO_PackedImageDesc(buffer,
|
|
width,
|
|
height,
|
|
channels,
|
|
sizeof(float),
|
|
size_t(channels) * sizeof(float),
|
|
size_t(channels) * sizeof(float) * width);
|
|
|
|
if (predivide) {
|
|
OCIO_cpuProcessorApply_predivide(processor, img);
|
|
}
|
|
else {
|
|
OCIO_cpuProcessorApply(processor, img);
|
|
}
|
|
|
|
OCIO_PackedImageDescRelease(img);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_imbuf_to_byte_texture(uchar *out_buffer,
|
|
const int offset_x,
|
|
const int offset_y,
|
|
const int width,
|
|
const int height,
|
|
const ImBuf *ibuf,
|
|
const bool store_premultiplied)
|
|
{
|
|
/* Byte buffer storage, only for sRGB, scene linear and data texture since other
|
|
* color space conversions can't be done on the GPU. */
|
|
BLI_assert(ibuf->byte_buffer.data);
|
|
BLI_assert(ibuf->float_buffer.data == nullptr);
|
|
BLI_assert(IMB_colormanagement_space_is_srgb(ibuf->byte_buffer.colorspace) ||
|
|
IMB_colormanagement_space_is_scene_linear(ibuf->byte_buffer.colorspace) ||
|
|
IMB_colormanagement_space_is_data(ibuf->byte_buffer.colorspace));
|
|
|
|
const uchar *in_buffer = ibuf->byte_buffer.data;
|
|
const bool use_premultiply = IMB_alpha_affects_rgb(ibuf) && store_premultiplied;
|
|
|
|
for (int y = 0; y < height; y++) {
|
|
const size_t in_offset = (offset_y + y) * ibuf->x + offset_x;
|
|
const size_t out_offset = y * width;
|
|
const uchar *in = in_buffer + in_offset * 4;
|
|
uchar *out = out_buffer + out_offset * 4;
|
|
|
|
if (use_premultiply) {
|
|
/* Premultiply only. */
|
|
for (int x = 0; x < width; x++, in += 4, out += 4) {
|
|
out[0] = (in[0] * in[3]) >> 8;
|
|
out[1] = (in[1] * in[3]) >> 8;
|
|
out[2] = (in[2] * in[3]) >> 8;
|
|
out[3] = in[3];
|
|
}
|
|
}
|
|
else {
|
|
/* Copy only. */
|
|
for (int x = 0; x < width; x++, in += 4, out += 4) {
|
|
out[0] = in[0];
|
|
out[1] = in[1];
|
|
out[2] = in[2];
|
|
out[3] = in[3];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ImbufByteToFloatData {
|
|
OCIO_ConstCPUProcessorRcPtr *processor;
|
|
int width;
|
|
int offset, stride;
|
|
const uchar *in_buffer;
|
|
float *out_buffer;
|
|
bool use_premultiply;
|
|
};
|
|
|
|
static void imbuf_byte_to_float_cb(void *__restrict userdata,
|
|
const int y,
|
|
const TaskParallelTLS *__restrict /*tls*/)
|
|
{
|
|
ImbufByteToFloatData *data = static_cast<ImbufByteToFloatData *>(userdata);
|
|
|
|
const size_t in_offset = data->offset + y * data->stride;
|
|
const size_t out_offset = y * data->width;
|
|
const uchar *in = data->in_buffer + in_offset * 4;
|
|
float *out = data->out_buffer + out_offset * 4;
|
|
|
|
/* Convert to scene linear, to sRGB and premultiply. */
|
|
for (int x = 0; x < data->width; x++, in += 4, out += 4) {
|
|
float pixel[4];
|
|
rgba_uchar_to_float(pixel, in);
|
|
if (data->processor) {
|
|
OCIO_cpuProcessorApplyRGB(data->processor, pixel);
|
|
}
|
|
else {
|
|
srgb_to_linearrgb_v3_v3(pixel, pixel);
|
|
}
|
|
if (data->use_premultiply) {
|
|
mul_v3_fl(pixel, pixel[3]);
|
|
}
|
|
copy_v4_v4(out, pixel);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_imbuf_to_float_texture(float *out_buffer,
|
|
const int offset_x,
|
|
const int offset_y,
|
|
const int width,
|
|
const int height,
|
|
const ImBuf *ibuf,
|
|
const bool store_premultiplied)
|
|
{
|
|
/* Float texture are stored in scene linear color space, with premultiplied
|
|
* alpha depending on the image alpha mode. */
|
|
if (ibuf->float_buffer.data) {
|
|
/* Float source buffer. */
|
|
const float *in_buffer = ibuf->float_buffer.data;
|
|
const int in_channels = ibuf->channels;
|
|
const bool use_unpremultiply = IMB_alpha_affects_rgb(ibuf) && !store_premultiplied;
|
|
|
|
for (int y = 0; y < height; y++) {
|
|
const size_t in_offset = (offset_y + y) * ibuf->x + offset_x;
|
|
const size_t out_offset = y * width;
|
|
const float *in = in_buffer + in_offset * in_channels;
|
|
float *out = out_buffer + out_offset * 4;
|
|
|
|
if (in_channels == 1) {
|
|
/* Copy single channel. */
|
|
for (int x = 0; x < width; x++, in += 1, out += 4) {
|
|
out[0] = in[0];
|
|
out[1] = in[0];
|
|
out[2] = in[0];
|
|
out[3] = in[0];
|
|
}
|
|
}
|
|
else if (in_channels == 3) {
|
|
/* Copy RGB. */
|
|
for (int x = 0; x < width; x++, in += 3, out += 4) {
|
|
out[0] = in[0];
|
|
out[1] = in[1];
|
|
out[2] = in[2];
|
|
out[3] = 1.0f;
|
|
}
|
|
}
|
|
else if (in_channels == 4) {
|
|
/* Copy or convert RGBA. */
|
|
if (use_unpremultiply) {
|
|
for (int x = 0; x < width; x++, in += 4, out += 4) {
|
|
premul_to_straight_v4_v4(out, in);
|
|
}
|
|
}
|
|
else {
|
|
memcpy(out, in, sizeof(float[4]) * width);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* Byte source buffer. */
|
|
const uchar *in_buffer = ibuf->byte_buffer.data;
|
|
const bool use_premultiply = IMB_alpha_affects_rgb(ibuf) && store_premultiplied;
|
|
|
|
OCIO_ConstCPUProcessorRcPtr *processor = (ibuf->byte_buffer.colorspace) ?
|
|
colorspace_to_scene_linear_cpu_processor(
|
|
ibuf->byte_buffer.colorspace) :
|
|
nullptr;
|
|
|
|
ImbufByteToFloatData data = {};
|
|
data.processor = processor;
|
|
data.width = width;
|
|
data.offset = offset_y * ibuf->x + offset_x;
|
|
data.stride = ibuf->x;
|
|
data.in_buffer = in_buffer;
|
|
data.out_buffer = out_buffer;
|
|
data.use_premultiply = use_premultiply;
|
|
|
|
TaskParallelSettings settings;
|
|
BLI_parallel_range_settings_defaults(&settings);
|
|
settings.use_threading = (height > 128);
|
|
BLI_task_parallel_range(0, height, &data, imbuf_byte_to_float_cb, &settings);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_scene_linear_to_color_picking_v3(float color_picking[3],
|
|
const float scene_linear[3])
|
|
{
|
|
if (!global_color_picking_state.cpu_processor_to && !global_color_picking_state.failed) {
|
|
/* Create processor if none exists. */
|
|
BLI_mutex_lock(&processor_lock);
|
|
|
|
if (!global_color_picking_state.cpu_processor_to && !global_color_picking_state.failed) {
|
|
OCIO_ConstProcessorRcPtr *processor = create_colorspace_transform_processor(
|
|
global_role_scene_linear, global_role_color_picking);
|
|
|
|
if (processor != nullptr) {
|
|
global_color_picking_state.cpu_processor_to = OCIO_processorGetCPUProcessor(processor);
|
|
OCIO_processorRelease(processor);
|
|
}
|
|
else {
|
|
global_color_picking_state.failed = true;
|
|
}
|
|
}
|
|
|
|
BLI_mutex_unlock(&processor_lock);
|
|
}
|
|
|
|
copy_v3_v3(color_picking, scene_linear);
|
|
|
|
if (global_color_picking_state.cpu_processor_to) {
|
|
OCIO_cpuProcessorApplyRGB(global_color_picking_state.cpu_processor_to, color_picking);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_color_picking_to_scene_linear_v3(float scene_linear[3],
|
|
const float color_picking[3])
|
|
{
|
|
if (!global_color_picking_state.cpu_processor_from && !global_color_picking_state.failed) {
|
|
/* Create processor if none exists. */
|
|
BLI_mutex_lock(&processor_lock);
|
|
|
|
if (!global_color_picking_state.cpu_processor_from && !global_color_picking_state.failed) {
|
|
OCIO_ConstProcessorRcPtr *processor = create_colorspace_transform_processor(
|
|
global_role_color_picking, global_role_scene_linear);
|
|
|
|
if (processor != nullptr) {
|
|
global_color_picking_state.cpu_processor_from = OCIO_processorGetCPUProcessor(processor);
|
|
OCIO_processorRelease(processor);
|
|
}
|
|
else {
|
|
global_color_picking_state.failed = true;
|
|
}
|
|
}
|
|
|
|
BLI_mutex_unlock(&processor_lock);
|
|
}
|
|
|
|
copy_v3_v3(scene_linear, color_picking);
|
|
|
|
if (global_color_picking_state.cpu_processor_from) {
|
|
OCIO_cpuProcessorApplyRGB(global_color_picking_state.cpu_processor_from, scene_linear);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_scene_linear_to_display_v3(float pixel[3], ColorManagedDisplay *display)
|
|
{
|
|
OCIO_ConstCPUProcessorRcPtr *processor = display_from_scene_linear_processor(display);
|
|
|
|
if (processor != nullptr) {
|
|
OCIO_cpuProcessorApplyRGB(processor, pixel);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_display_to_scene_linear_v3(float pixel[3], ColorManagedDisplay *display)
|
|
{
|
|
OCIO_ConstCPUProcessorRcPtr *processor = display_to_scene_linear_processor(display);
|
|
|
|
if (processor != nullptr) {
|
|
OCIO_cpuProcessorApplyRGB(processor, pixel);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_pixel_to_display_space_v4(
|
|
float result[4],
|
|
const float pixel[4],
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings)
|
|
{
|
|
ColormanageProcessor *cm_processor;
|
|
|
|
copy_v4_v4(result, pixel);
|
|
|
|
cm_processor = IMB_colormanagement_display_processor_new(view_settings, display_settings);
|
|
IMB_colormanagement_processor_apply_v4(cm_processor, result);
|
|
IMB_colormanagement_processor_free(cm_processor);
|
|
}
|
|
|
|
void IMB_colormanagement_pixel_to_display_space_v3(
|
|
float result[3],
|
|
const float pixel[3],
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings)
|
|
{
|
|
ColormanageProcessor *cm_processor;
|
|
|
|
copy_v3_v3(result, pixel);
|
|
|
|
cm_processor = IMB_colormanagement_display_processor_new(view_settings, display_settings);
|
|
IMB_colormanagement_processor_apply_v3(cm_processor, result);
|
|
IMB_colormanagement_processor_free(cm_processor);
|
|
}
|
|
|
|
static void colormanagement_imbuf_make_display_space(
|
|
ImBuf *ibuf,
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings,
|
|
bool make_byte)
|
|
{
|
|
if (!ibuf->byte_buffer.data && make_byte) {
|
|
imb_addrectImBuf(ibuf);
|
|
}
|
|
|
|
colormanage_display_buffer_process_ex(
|
|
ibuf, ibuf->float_buffer.data, ibuf->byte_buffer.data, view_settings, display_settings);
|
|
}
|
|
|
|
void IMB_colormanagement_imbuf_make_display_space(
|
|
ImBuf *ibuf,
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings)
|
|
{
|
|
colormanagement_imbuf_make_display_space(ibuf, view_settings, display_settings, false);
|
|
}
|
|
|
|
static ImBuf *imbuf_ensure_editable(ImBuf *ibuf, ImBuf *colormanaged_ibuf, bool allocate_result)
|
|
{
|
|
if (colormanaged_ibuf != ibuf) {
|
|
/* Is already an editable copy. */
|
|
return colormanaged_ibuf;
|
|
}
|
|
|
|
if (allocate_result) {
|
|
/* Copy full image buffer. */
|
|
colormanaged_ibuf = IMB_dupImBuf(ibuf);
|
|
IMB_metadata_copy(colormanaged_ibuf, ibuf);
|
|
return colormanaged_ibuf;
|
|
}
|
|
|
|
/* Render pipeline is constructing image buffer itself,
|
|
* but it's re-using byte and float buffers from render result make copy of this buffers
|
|
* here sine this buffers would be transformed to other color space here. */
|
|
IMB_make_writable_byte_buffer(ibuf);
|
|
IMB_make_writable_float_buffer(ibuf);
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
ImBuf *IMB_colormanagement_imbuf_for_write(ImBuf *ibuf,
|
|
bool save_as_render,
|
|
bool allocate_result,
|
|
const ImageFormatData *image_format)
|
|
{
|
|
ImBuf *colormanaged_ibuf = ibuf;
|
|
|
|
/* Update byte buffer if exists but invalid. */
|
|
if (ibuf->float_buffer.data && ibuf->byte_buffer.data &&
|
|
(ibuf->userflags & (IB_DISPLAY_BUFFER_INVALID | IB_RECT_INVALID)) != 0)
|
|
{
|
|
IMB_rect_from_float(ibuf);
|
|
ibuf->userflags &= ~(IB_RECT_INVALID | IB_DISPLAY_BUFFER_INVALID);
|
|
}
|
|
|
|
/* Detect if we are writing to a file format that needs a linear float buffer. */
|
|
const bool linear_float_output = BKE_imtype_requires_linear_float(image_format->imtype);
|
|
|
|
/* Detect if we are writing output a byte buffer, which we would need to create
|
|
* with color management conversions applied. This may be for either applying the
|
|
* display transform for renders, or a user specified color space for the file. */
|
|
const bool byte_output = BKE_image_format_is_byte(image_format);
|
|
|
|
BLI_assert(!(byte_output && linear_float_output));
|
|
|
|
/* If we're saving from RGBA to RGB buffer then it's not so much useful to just ignore alpha --
|
|
* it leads to bad artifacts especially when saving byte images.
|
|
*
|
|
* What we do here is we're overlaying our image on top of background color (which is currently
|
|
* black). This is quite much the same as what Gimp does and it seems to be what artists expects
|
|
* from saving.
|
|
*
|
|
* Do a conversion here, so image format writers could happily assume all the alpha tricks were
|
|
* made already. helps keep things locally here, not spreading it to all possible image writers
|
|
* we've got.
|
|
*/
|
|
if (image_format->planes != R_IMF_PLANES_RGBA) {
|
|
float color[3] = {0, 0, 0};
|
|
|
|
colormanaged_ibuf = imbuf_ensure_editable(ibuf, colormanaged_ibuf, allocate_result);
|
|
|
|
if (colormanaged_ibuf->float_buffer.data && colormanaged_ibuf->channels == 4) {
|
|
IMB_alpha_under_color_float(
|
|
colormanaged_ibuf->float_buffer.data, colormanaged_ibuf->x, colormanaged_ibuf->y, color);
|
|
}
|
|
|
|
if (colormanaged_ibuf->byte_buffer.data) {
|
|
IMB_alpha_under_color_byte(
|
|
colormanaged_ibuf->byte_buffer.data, colormanaged_ibuf->x, colormanaged_ibuf->y, color);
|
|
}
|
|
}
|
|
|
|
if (save_as_render && !linear_float_output) {
|
|
/* Render output: perform conversion to display space using view transform. */
|
|
colormanaged_ibuf = imbuf_ensure_editable(ibuf, colormanaged_ibuf, allocate_result);
|
|
|
|
colormanagement_imbuf_make_display_space(colormanaged_ibuf,
|
|
&image_format->view_settings,
|
|
&image_format->display_settings,
|
|
byte_output);
|
|
|
|
if (colormanaged_ibuf->float_buffer.data) {
|
|
/* Float buffer isn't linear anymore,
|
|
* image format write callback should check for this flag and assume
|
|
* no space conversion should happen if ibuf->float_buffer.colorspace != nullptr. */
|
|
colormanaged_ibuf->float_buffer.colorspace = display_transform_get_colorspace(
|
|
&image_format->view_settings, &image_format->display_settings);
|
|
if (byte_output) {
|
|
colormanaged_ibuf->byte_buffer.colorspace = colormanaged_ibuf->float_buffer.colorspace;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* Linear render or regular file output: conversion between two color spaces. */
|
|
|
|
/* Detect which color space we need to convert between. */
|
|
const char *from_colorspace = (ibuf->float_buffer.data &&
|
|
!(byte_output && ibuf->byte_buffer.data)) ?
|
|
/* From float buffer. */
|
|
(ibuf->float_buffer.colorspace) ?
|
|
ibuf->float_buffer.colorspace->name :
|
|
global_role_scene_linear :
|
|
/* From byte buffer. */
|
|
(ibuf->byte_buffer.colorspace) ?
|
|
ibuf->byte_buffer.colorspace->name :
|
|
global_role_default_byte;
|
|
|
|
const char *to_colorspace = image_format->linear_colorspace_settings.name;
|
|
|
|
/* TODO: can we check with OCIO if color spaces are the same but have different names? */
|
|
if (to_colorspace[0] == '\0' || STREQ(from_colorspace, to_colorspace)) {
|
|
/* No conversion needed, but may still need to allocate byte buffer for output. */
|
|
if (byte_output && !ibuf->byte_buffer.data) {
|
|
ibuf->byte_buffer.colorspace = ibuf->float_buffer.colorspace;
|
|
IMB_rect_from_float(ibuf);
|
|
}
|
|
}
|
|
else {
|
|
/* Color space conversion needed. */
|
|
colormanaged_ibuf = imbuf_ensure_editable(ibuf, colormanaged_ibuf, allocate_result);
|
|
|
|
if (byte_output) {
|
|
colormanaged_ibuf->byte_buffer.colorspace = colormanage_colorspace_get_named(
|
|
to_colorspace);
|
|
|
|
if (colormanaged_ibuf->byte_buffer.data) {
|
|
/* Byte to byte. */
|
|
IMB_colormanagement_transform_byte_threaded(colormanaged_ibuf->byte_buffer.data,
|
|
colormanaged_ibuf->x,
|
|
colormanaged_ibuf->y,
|
|
colormanaged_ibuf->channels,
|
|
from_colorspace,
|
|
to_colorspace);
|
|
}
|
|
else {
|
|
/* Float to byte. */
|
|
IMB_rect_from_float(colormanaged_ibuf);
|
|
}
|
|
}
|
|
else {
|
|
if (!colormanaged_ibuf->float_buffer.data) {
|
|
/* Byte to float. */
|
|
IMB_float_from_rect(colormanaged_ibuf);
|
|
imb_freerectImBuf(colormanaged_ibuf);
|
|
|
|
/* This conversion always goes to scene linear. */
|
|
from_colorspace = global_role_scene_linear;
|
|
}
|
|
|
|
if (colormanaged_ibuf->float_buffer.data) {
|
|
/* Float to float. */
|
|
IMB_colormanagement_transform(colormanaged_ibuf->float_buffer.data,
|
|
colormanaged_ibuf->x,
|
|
colormanaged_ibuf->y,
|
|
colormanaged_ibuf->channels,
|
|
from_colorspace,
|
|
to_colorspace,
|
|
false);
|
|
|
|
colormanaged_ibuf->float_buffer.colorspace = colormanage_colorspace_get_named(
|
|
to_colorspace);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return colormanaged_ibuf;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Public Display Buffers Interfaces
|
|
* \{ */
|
|
|
|
uchar *IMB_display_buffer_acquire(ImBuf *ibuf,
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings,
|
|
void **cache_handle)
|
|
{
|
|
uchar *display_buffer;
|
|
size_t buffer_size;
|
|
ColormanageCacheViewSettings cache_view_settings;
|
|
ColormanageCacheDisplaySettings cache_display_settings;
|
|
ColorManagedViewSettings default_view_settings;
|
|
const ColorManagedViewSettings *applied_view_settings;
|
|
|
|
*cache_handle = nullptr;
|
|
|
|
if (!ibuf->x || !ibuf->y) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (view_settings) {
|
|
applied_view_settings = view_settings;
|
|
}
|
|
else {
|
|
/* If no view settings were specified, use default ones, which will
|
|
* attempt not to do any extra color correction. */
|
|
IMB_colormanagement_init_default_view_settings(&default_view_settings, display_settings);
|
|
applied_view_settings = &default_view_settings;
|
|
}
|
|
|
|
/* early out: no float buffer and byte buffer is already in display space,
|
|
* let's just use if
|
|
*/
|
|
if (ibuf->float_buffer.data == nullptr && ibuf->byte_buffer.colorspace && ibuf->channels == 4) {
|
|
if (is_ibuf_rect_in_display_space(ibuf, applied_view_settings, display_settings)) {
|
|
return ibuf->byte_buffer.data;
|
|
}
|
|
}
|
|
|
|
colormanage_view_settings_to_cache(ibuf, &cache_view_settings, applied_view_settings);
|
|
colormanage_display_settings_to_cache(&cache_display_settings, display_settings);
|
|
|
|
if (ibuf->invalid_rect.xmin != ibuf->invalid_rect.xmax) {
|
|
if ((ibuf->userflags & IB_DISPLAY_BUFFER_INVALID) == 0) {
|
|
IMB_partial_display_buffer_update_threaded(ibuf,
|
|
ibuf->float_buffer.data,
|
|
ibuf->byte_buffer.data,
|
|
ibuf->x,
|
|
0,
|
|
0,
|
|
applied_view_settings,
|
|
display_settings,
|
|
ibuf->invalid_rect.xmin,
|
|
ibuf->invalid_rect.ymin,
|
|
ibuf->invalid_rect.xmax,
|
|
ibuf->invalid_rect.ymax);
|
|
}
|
|
|
|
BLI_rcti_init(&ibuf->invalid_rect, 0, 0, 0, 0);
|
|
}
|
|
|
|
BLI_thread_lock(LOCK_COLORMANAGE);
|
|
|
|
/* ensure color management bit fields exists */
|
|
if (!ibuf->display_buffer_flags) {
|
|
ibuf->display_buffer_flags = static_cast<uint *>(
|
|
MEM_callocN(sizeof(uint) * global_tot_display, "imbuf display_buffer_flags"));
|
|
}
|
|
else if (ibuf->userflags & IB_DISPLAY_BUFFER_INVALID) {
|
|
/* all display buffers were marked as invalid from other areas,
|
|
* now propagate this flag to internal color management routines
|
|
*/
|
|
memset(ibuf->display_buffer_flags, 0, global_tot_display * sizeof(uint));
|
|
|
|
ibuf->userflags &= ~IB_DISPLAY_BUFFER_INVALID;
|
|
}
|
|
|
|
display_buffer = colormanage_cache_get(
|
|
ibuf, &cache_view_settings, &cache_display_settings, cache_handle);
|
|
|
|
if (display_buffer) {
|
|
BLI_thread_unlock(LOCK_COLORMANAGE);
|
|
return display_buffer;
|
|
}
|
|
|
|
buffer_size = DISPLAY_BUFFER_CHANNELS * size_t(ibuf->x) * ibuf->y * sizeof(char);
|
|
display_buffer = static_cast<uchar *>(MEM_callocN(buffer_size, "imbuf display buffer"));
|
|
|
|
colormanage_display_buffer_process(
|
|
ibuf, display_buffer, applied_view_settings, display_settings);
|
|
|
|
colormanage_cache_put(
|
|
ibuf, &cache_view_settings, &cache_display_settings, display_buffer, cache_handle);
|
|
|
|
BLI_thread_unlock(LOCK_COLORMANAGE);
|
|
|
|
return display_buffer;
|
|
}
|
|
|
|
uchar *IMB_display_buffer_acquire_ctx(const bContext *C, ImBuf *ibuf, void **cache_handle)
|
|
{
|
|
ColorManagedViewSettings *view_settings;
|
|
ColorManagedDisplaySettings *display_settings;
|
|
|
|
IMB_colormanagement_display_settings_from_ctx(C, &view_settings, &display_settings);
|
|
|
|
return IMB_display_buffer_acquire(ibuf, view_settings, display_settings, cache_handle);
|
|
}
|
|
|
|
void IMB_display_buffer_transform_apply(uchar *display_buffer,
|
|
float *linear_buffer,
|
|
int width,
|
|
int height,
|
|
int channels,
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings,
|
|
bool predivide)
|
|
{
|
|
float *buffer;
|
|
ColormanageProcessor *cm_processor = IMB_colormanagement_display_processor_new(view_settings,
|
|
display_settings);
|
|
|
|
buffer = static_cast<float *>(MEM_mallocN(size_t(channels) * width * height * sizeof(float),
|
|
"display transform temp buffer"));
|
|
memcpy(buffer, linear_buffer, size_t(channels) * width * height * sizeof(float));
|
|
|
|
IMB_colormanagement_processor_apply(cm_processor, buffer, width, height, channels, predivide);
|
|
|
|
IMB_colormanagement_processor_free(cm_processor);
|
|
|
|
IMB_buffer_byte_from_float(display_buffer,
|
|
buffer,
|
|
channels,
|
|
0.0f,
|
|
IB_PROFILE_SRGB,
|
|
IB_PROFILE_SRGB,
|
|
false,
|
|
width,
|
|
height,
|
|
width,
|
|
width);
|
|
|
|
MEM_freeN(buffer);
|
|
}
|
|
|
|
void IMB_display_buffer_transform_apply_float(float *float_display_buffer,
|
|
float *linear_buffer,
|
|
int width,
|
|
int height,
|
|
int channels,
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings,
|
|
bool predivide)
|
|
{
|
|
float *buffer;
|
|
ColormanageProcessor *cm_processor = IMB_colormanagement_display_processor_new(view_settings,
|
|
display_settings);
|
|
|
|
buffer = static_cast<float *>(MEM_mallocN(size_t(channels) * width * height * sizeof(float),
|
|
"display transform temp buffer"));
|
|
memcpy(buffer, linear_buffer, size_t(channels) * width * height * sizeof(float));
|
|
|
|
IMB_colormanagement_processor_apply(cm_processor, buffer, width, height, channels, predivide);
|
|
|
|
IMB_colormanagement_processor_free(cm_processor);
|
|
|
|
memcpy(float_display_buffer, buffer, size_t(channels) * width * height * sizeof(float));
|
|
MEM_freeN(buffer);
|
|
}
|
|
|
|
void IMB_display_buffer_release(void *cache_handle)
|
|
{
|
|
if (cache_handle) {
|
|
BLI_thread_lock(LOCK_COLORMANAGE);
|
|
|
|
colormanage_cache_handle_release(cache_handle);
|
|
|
|
BLI_thread_unlock(LOCK_COLORMANAGE);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Display Functions
|
|
* \{ */
|
|
|
|
const char *colormanage_display_get_default_name()
|
|
{
|
|
OCIO_ConstConfigRcPtr *config = OCIO_getCurrentConfig();
|
|
const char *display_name;
|
|
|
|
display_name = OCIO_configGetDefaultDisplay(config);
|
|
|
|
OCIO_configRelease(config);
|
|
|
|
return display_name;
|
|
}
|
|
|
|
ColorManagedDisplay *colormanage_display_get_default()
|
|
{
|
|
const char *display_name = colormanage_display_get_default_name();
|
|
|
|
if (display_name[0] == '\0') {
|
|
return nullptr;
|
|
}
|
|
|
|
return colormanage_display_get_named(display_name);
|
|
}
|
|
|
|
ColorManagedDisplay *colormanage_display_add(const char *name)
|
|
{
|
|
ColorManagedDisplay *display;
|
|
int index = 0;
|
|
|
|
if (global_displays.last) {
|
|
ColorManagedDisplay *last_display = static_cast<ColorManagedDisplay *>(global_displays.last);
|
|
|
|
index = last_display->index;
|
|
}
|
|
|
|
display = MEM_cnew<ColorManagedDisplay>("ColorManagedDisplay");
|
|
|
|
display->index = index + 1;
|
|
|
|
STRNCPY(display->name, name);
|
|
|
|
BLI_addtail(&global_displays, display);
|
|
|
|
return display;
|
|
}
|
|
|
|
ColorManagedDisplay *colormanage_display_get_named(const char *name)
|
|
{
|
|
LISTBASE_FOREACH (ColorManagedDisplay *, display, &global_displays) {
|
|
if (STREQ(display->name, name)) {
|
|
return display;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ColorManagedDisplay *colormanage_display_get_indexed(int index)
|
|
{
|
|
/* display indices are 1-based */
|
|
return static_cast<ColorManagedDisplay *>(BLI_findlink(&global_displays, index - 1));
|
|
}
|
|
|
|
int IMB_colormanagement_display_get_named_index(const char *name)
|
|
{
|
|
ColorManagedDisplay *display;
|
|
|
|
display = colormanage_display_get_named(name);
|
|
|
|
if (display) {
|
|
return display->index;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const char *IMB_colormanagement_display_get_indexed_name(int index)
|
|
{
|
|
ColorManagedDisplay *display;
|
|
|
|
display = colormanage_display_get_indexed(index);
|
|
|
|
if (display) {
|
|
return display->name;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const char *IMB_colormanagement_display_get_default_name()
|
|
{
|
|
ColorManagedDisplay *display = colormanage_display_get_default();
|
|
|
|
return display->name;
|
|
}
|
|
|
|
ColorManagedDisplay *IMB_colormanagement_display_get_named(const char *name)
|
|
{
|
|
return colormanage_display_get_named(name);
|
|
}
|
|
|
|
const char *IMB_colormanagement_display_get_none_name()
|
|
{
|
|
if (colormanage_display_get_named("None") != nullptr) {
|
|
return "None";
|
|
}
|
|
|
|
return colormanage_display_get_default_name();
|
|
}
|
|
|
|
const char *IMB_colormanagement_display_get_default_view_transform_name(
|
|
ColorManagedDisplay *display)
|
|
{
|
|
return colormanage_view_get_default_name(display);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name View Functions
|
|
* \{ */
|
|
|
|
const char *colormanage_view_get_default_name(const ColorManagedDisplay *display)
|
|
{
|
|
OCIO_ConstConfigRcPtr *config = OCIO_getCurrentConfig();
|
|
const char *name = OCIO_configGetDefaultView(config, display->name);
|
|
|
|
OCIO_configRelease(config);
|
|
|
|
return name;
|
|
}
|
|
|
|
ColorManagedView *colormanage_view_get_default(const ColorManagedDisplay *display)
|
|
{
|
|
const char *name = colormanage_view_get_default_name(display);
|
|
|
|
if (!name || name[0] == '\0') {
|
|
return nullptr;
|
|
}
|
|
|
|
return colormanage_view_get_named(name);
|
|
}
|
|
|
|
ColorManagedView *colormanage_view_add(const char *name)
|
|
{
|
|
ColorManagedView *view;
|
|
int index = global_tot_view;
|
|
|
|
view = MEM_cnew<ColorManagedView>("ColorManagedView");
|
|
view->index = index + 1;
|
|
STRNCPY(view->name, name);
|
|
|
|
BLI_addtail(&global_views, view);
|
|
|
|
global_tot_view++;
|
|
|
|
return view;
|
|
}
|
|
|
|
ColorManagedView *colormanage_view_get_named(const char *name)
|
|
{
|
|
LISTBASE_FOREACH (ColorManagedView *, view, &global_views) {
|
|
if (STREQ(view->name, name)) {
|
|
return view;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ColorManagedView *colormanage_view_get_indexed(int index)
|
|
{
|
|
/* view transform indices are 1-based */
|
|
return static_cast<ColorManagedView *>(BLI_findlink(&global_views, index - 1));
|
|
}
|
|
|
|
ColorManagedView *colormanage_view_get_named_for_display(const char *display_name,
|
|
const char *name)
|
|
{
|
|
ColorManagedDisplay *display = colormanage_display_get_named(display_name);
|
|
if (display == nullptr) {
|
|
return nullptr;
|
|
}
|
|
LISTBASE_FOREACH (LinkData *, view_link, &display->views) {
|
|
ColorManagedView *view = static_cast<ColorManagedView *>(view_link->data);
|
|
if (STRCASEEQ(name, view->name)) {
|
|
return view;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int IMB_colormanagement_view_get_named_index(const char *name)
|
|
{
|
|
ColorManagedView *view = colormanage_view_get_named(name);
|
|
|
|
if (view) {
|
|
return view->index;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const char *IMB_colormanagement_view_get_indexed_name(int index)
|
|
{
|
|
ColorManagedView *view = colormanage_view_get_indexed(index);
|
|
|
|
if (view) {
|
|
return view->name;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const char *IMB_colormanagement_view_get_default_name(const char *display_name)
|
|
{
|
|
ColorManagedDisplay *display = colormanage_display_get_named(display_name);
|
|
ColorManagedView *view = nullptr;
|
|
|
|
if (display) {
|
|
view = colormanage_view_get_default(display);
|
|
}
|
|
|
|
if (view) {
|
|
return view->name;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const char *IMB_colormanagement_view_get_raw_or_default_name(const char *display_name)
|
|
{
|
|
ColorManagedDisplay *display = colormanage_display_get_named(display_name);
|
|
if (!display) {
|
|
return nullptr;
|
|
}
|
|
|
|
ColorManagedView *view = nullptr;
|
|
|
|
if (!view) {
|
|
view = colormanage_view_get_named_for_display(display_name, "Raw");
|
|
}
|
|
|
|
if (!view) {
|
|
view = colormanage_view_get_default(display);
|
|
}
|
|
|
|
if (!view) {
|
|
return nullptr;
|
|
}
|
|
|
|
return view->name;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Color Space Functions
|
|
* \{ */
|
|
|
|
static void colormanage_description_strip(char *description)
|
|
{
|
|
int i, n;
|
|
|
|
for (i = int(strlen(description)) - 1; i >= 0; i--) {
|
|
if (ELEM(description[i], '\r', '\n')) {
|
|
description[i] = '\0';
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = 0, n = strlen(description); i < n; i++) {
|
|
if (ELEM(description[i], '\r', '\n')) {
|
|
description[i] = ' ';
|
|
}
|
|
}
|
|
}
|
|
|
|
ColorSpace *colormanage_colorspace_add(const char *name,
|
|
const char *description,
|
|
bool is_invertible,
|
|
bool is_data)
|
|
{
|
|
ColorSpace *colorspace, *prev_space;
|
|
int counter = 1;
|
|
|
|
colorspace = MEM_cnew<ColorSpace>("ColorSpace");
|
|
|
|
STRNCPY(colorspace->name, name);
|
|
|
|
if (description) {
|
|
STRNCPY(colorspace->description, description);
|
|
|
|
colormanage_description_strip(colorspace->description);
|
|
}
|
|
|
|
colorspace->is_invertible = is_invertible;
|
|
colorspace->is_data = is_data;
|
|
|
|
for (prev_space = static_cast<ColorSpace *>(global_colorspaces.first); prev_space;
|
|
prev_space = prev_space->next)
|
|
{
|
|
if (BLI_strcasecmp(prev_space->name, colorspace->name) > 0) {
|
|
break;
|
|
}
|
|
|
|
prev_space->index = counter++;
|
|
}
|
|
|
|
if (!prev_space) {
|
|
BLI_addtail(&global_colorspaces, colorspace);
|
|
}
|
|
else {
|
|
BLI_insertlinkbefore(&global_colorspaces, prev_space, colorspace);
|
|
}
|
|
|
|
colorspace->index = counter++;
|
|
for (; prev_space; prev_space = prev_space->next) {
|
|
prev_space->index = counter++;
|
|
}
|
|
|
|
global_tot_colorspace++;
|
|
|
|
return colorspace;
|
|
}
|
|
|
|
ColorSpace *colormanage_colorspace_get_named(const char *name)
|
|
{
|
|
LISTBASE_FOREACH (ColorSpace *, colorspace, &global_colorspaces) {
|
|
if (STREQ(colorspace->name, name)) {
|
|
return colorspace;
|
|
}
|
|
|
|
for (int i = 0; i < colorspace->num_aliases; i++) {
|
|
if (STREQ(colorspace->aliases[i], name)) {
|
|
return colorspace;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ColorSpace *colormanage_colorspace_get_roled(int role)
|
|
{
|
|
const char *role_colorspace = IMB_colormanagement_role_colorspace_name_get(role);
|
|
|
|
return colormanage_colorspace_get_named(role_colorspace);
|
|
}
|
|
|
|
ColorSpace *colormanage_colorspace_get_indexed(int index)
|
|
{
|
|
/* color space indices are 1-based */
|
|
return static_cast<ColorSpace *>(BLI_findlink(&global_colorspaces, index - 1));
|
|
}
|
|
|
|
int IMB_colormanagement_colorspace_get_named_index(const char *name)
|
|
{
|
|
ColorSpace *colorspace = colormanage_colorspace_get_named(name);
|
|
|
|
if (colorspace) {
|
|
return colorspace->index;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const char *IMB_colormanagement_colorspace_get_indexed_name(int index)
|
|
{
|
|
ColorSpace *colorspace = colormanage_colorspace_get_indexed(index);
|
|
|
|
if (colorspace) {
|
|
return colorspace->name;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
const char *IMB_colormanagement_colorspace_get_name(const ColorSpace *colorspace)
|
|
{
|
|
return colorspace->name;
|
|
}
|
|
|
|
void IMB_colormanagement_colorspace_from_ibuf_ftype(
|
|
ColorManagedColorspaceSettings *colorspace_settings, ImBuf *ibuf)
|
|
{
|
|
/* Don't modify non-color data space, it does not change with file type. */
|
|
ColorSpace *colorspace = colormanage_colorspace_get_named(colorspace_settings->name);
|
|
|
|
if (colorspace && colorspace->is_data) {
|
|
return;
|
|
}
|
|
|
|
/* Get color space from file type. */
|
|
const ImFileType *type = IMB_file_type_from_ibuf(ibuf);
|
|
if (type != nullptr) {
|
|
if (type->save != nullptr) {
|
|
const char *role_colorspace = IMB_colormanagement_role_colorspace_name_get(
|
|
type->default_save_role);
|
|
STRNCPY(colorspace_settings->name, role_colorspace);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Looks Functions
|
|
* \{ */
|
|
|
|
ColorManagedLook *colormanage_look_add(const char *name, const char *process_space, bool is_noop)
|
|
{
|
|
ColorManagedLook *look;
|
|
int index = global_tot_looks;
|
|
|
|
look = MEM_cnew<ColorManagedLook>("ColorManagedLook");
|
|
look->index = index + 1;
|
|
STRNCPY(look->name, name);
|
|
STRNCPY(look->ui_name, name);
|
|
STRNCPY(look->process_space, process_space);
|
|
look->is_noop = is_noop;
|
|
|
|
/* Detect view specific looks. */
|
|
const char *separator_offset = strstr(look->name, " - ");
|
|
if (separator_offset) {
|
|
BLI_strncpy(look->view, look->name, separator_offset - look->name + 1);
|
|
STRNCPY(look->ui_name, separator_offset + strlen(" - "));
|
|
}
|
|
|
|
BLI_addtail(&global_looks, look);
|
|
|
|
global_tot_looks++;
|
|
|
|
return look;
|
|
}
|
|
|
|
ColorManagedLook *colormanage_look_get_named(const char *name)
|
|
{
|
|
LISTBASE_FOREACH (ColorManagedLook *, look, &global_looks) {
|
|
if (STREQ(look->name, name)) {
|
|
return look;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ColorManagedLook *colormanage_look_get_indexed(int index)
|
|
{
|
|
/* look indices are 1-based */
|
|
return static_cast<ColorManagedLook *>(BLI_findlink(&global_looks, index - 1));
|
|
}
|
|
|
|
int IMB_colormanagement_look_get_named_index(const char *name)
|
|
{
|
|
ColorManagedLook *look = colormanage_look_get_named(name);
|
|
|
|
if (look) {
|
|
return look->index;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const char *IMB_colormanagement_look_get_indexed_name(int index)
|
|
{
|
|
ColorManagedLook *look;
|
|
|
|
look = colormanage_look_get_indexed(index);
|
|
|
|
if (look) {
|
|
return look->name;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const char *IMB_colormanagement_look_get_default_name()
|
|
{
|
|
const ColorManagedLook *default_look = static_cast<const ColorManagedLook *>(global_looks.first);
|
|
if (!default_look) {
|
|
return "";
|
|
}
|
|
|
|
return default_look->name;
|
|
}
|
|
|
|
const char *IMB_colormanagement_look_validate_for_view(const char *view_name,
|
|
const char *look_name)
|
|
{
|
|
ColorManagedLook *look_descr = colormanage_look_get_named(look_name);
|
|
if (!look_descr) {
|
|
return look_name;
|
|
}
|
|
|
|
/* Keep same look if compatible. */
|
|
if (colormanage_compatible_look(look_descr, view_name)) {
|
|
return look_name;
|
|
}
|
|
|
|
/* Try to find another compatible look with the same UI name, in case
|
|
* of looks specialized for view transform, */
|
|
LISTBASE_FOREACH (ColorManagedLook *, other_look, &global_looks) {
|
|
if (STREQ(look_descr->ui_name, other_look->ui_name) &&
|
|
colormanage_compatible_look(other_look, view_name))
|
|
{
|
|
return other_look->name;
|
|
}
|
|
}
|
|
|
|
return IMB_colormanagement_look_get_default_name();
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name RNA Helper Functions
|
|
* \{ */
|
|
|
|
void IMB_colormanagement_display_items_add(EnumPropertyItem **items, int *totitem)
|
|
{
|
|
LISTBASE_FOREACH (ColorManagedDisplay *, display, &global_displays) {
|
|
EnumPropertyItem item;
|
|
|
|
item.value = display->index;
|
|
item.name = display->name;
|
|
item.identifier = display->name;
|
|
item.icon = 0;
|
|
item.description = "";
|
|
|
|
RNA_enum_item_add(items, totitem, &item);
|
|
}
|
|
}
|
|
|
|
static void colormanagement_view_item_add(EnumPropertyItem **items,
|
|
int *totitem,
|
|
ColorManagedView *view)
|
|
{
|
|
EnumPropertyItem item;
|
|
|
|
item.value = view->index;
|
|
item.name = view->name;
|
|
item.identifier = view->name;
|
|
item.icon = 0;
|
|
item.description = "";
|
|
|
|
RNA_enum_item_add(items, totitem, &item);
|
|
}
|
|
|
|
void IMB_colormanagement_view_items_add(EnumPropertyItem **items,
|
|
int *totitem,
|
|
const char *display_name)
|
|
{
|
|
ColorManagedDisplay *display = colormanage_display_get_named(display_name);
|
|
|
|
if (display) {
|
|
LISTBASE_FOREACH (LinkData *, display_view, &display->views) {
|
|
ColorManagedView *view = static_cast<ColorManagedView *>(display_view->data);
|
|
|
|
colormanagement_view_item_add(items, totitem, view);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_look_items_add(EnumPropertyItem **items,
|
|
int *totitem,
|
|
const char *view_name)
|
|
{
|
|
const bool has_explicit_look = has_explicit_look_for_view(view_name);
|
|
|
|
LISTBASE_FOREACH (ColorManagedLook *, look, &global_looks) {
|
|
if (!colormanage_compatible_look(look, view_name, has_explicit_look)) {
|
|
continue;
|
|
}
|
|
|
|
EnumPropertyItem item;
|
|
|
|
item.value = look->index;
|
|
item.name = look->ui_name;
|
|
item.identifier = look->name;
|
|
item.icon = 0;
|
|
item.description = "";
|
|
|
|
RNA_enum_item_add(items, totitem, &item);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_colorspace_items_add(EnumPropertyItem **items, int *totitem)
|
|
{
|
|
LISTBASE_FOREACH (ColorSpace *, colorspace, &global_colorspaces) {
|
|
EnumPropertyItem item;
|
|
|
|
if (!colorspace->is_invertible) {
|
|
continue;
|
|
}
|
|
|
|
item.value = colorspace->index;
|
|
item.name = colorspace->name;
|
|
item.identifier = colorspace->name;
|
|
item.icon = 0;
|
|
item.description = colorspace->description;
|
|
|
|
RNA_enum_item_add(items, totitem, &item);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Partial Display Buffer Update
|
|
* \{ */
|
|
|
|
/*
|
|
* Partial display update is supposed to be used by such areas as
|
|
* compositor and renderer, This areas are calculating tiles of the
|
|
* images and because of performance reasons only this tiles should
|
|
* be color managed.
|
|
* This gives nice visual feedback without slowing things down.
|
|
*
|
|
* Updating happens for active display transformation only, all
|
|
* the rest buffers would be marked as dirty
|
|
*/
|
|
|
|
static void partial_buffer_update_rect(ImBuf *ibuf,
|
|
uchar *display_buffer,
|
|
const float *linear_buffer,
|
|
const uchar *byte_buffer,
|
|
int display_stride,
|
|
int linear_stride,
|
|
int linear_offset_x,
|
|
int linear_offset_y,
|
|
ColormanageProcessor *cm_processor,
|
|
const int xmin,
|
|
const int ymin,
|
|
const int xmax,
|
|
const int ymax)
|
|
{
|
|
int x, y;
|
|
int channels = ibuf->channels;
|
|
float dither = ibuf->dither;
|
|
ColorSpace *rect_colorspace = ibuf->byte_buffer.colorspace;
|
|
float *display_buffer_float = nullptr;
|
|
const int width = xmax - xmin;
|
|
const int height = ymax - ymin;
|
|
bool is_data = (ibuf->colormanage_flag & IMB_COLORMANAGE_IS_DATA) != 0;
|
|
|
|
if (dither != 0.0f) {
|
|
/* cm_processor is nullptr in cases byte_buffer's space matches display
|
|
* buffer's space
|
|
* in this case we could skip extra transform and only apply dither
|
|
* use 4 channels for easier byte->float->byte conversion here so
|
|
* (this is only needed to apply dither, in other cases we'll convert
|
|
* byte buffer to display directly)
|
|
*/
|
|
if (!cm_processor) {
|
|
channels = 4;
|
|
}
|
|
|
|
display_buffer_float = static_cast<float *>(MEM_callocN(
|
|
size_t(channels) * width * height * sizeof(float), "display buffer for dither"));
|
|
}
|
|
|
|
if (cm_processor) {
|
|
for (y = ymin; y < ymax; y++) {
|
|
for (x = xmin; x < xmax; x++) {
|
|
size_t display_index = (size_t(y) * display_stride + x) * 4;
|
|
size_t linear_index = (size_t(y - linear_offset_y) * linear_stride +
|
|
(x - linear_offset_x)) *
|
|
channels;
|
|
float pixel[4];
|
|
|
|
if (linear_buffer) {
|
|
if (channels == 4) {
|
|
copy_v4_v4(pixel, (float *)linear_buffer + linear_index);
|
|
}
|
|
else if (channels == 3) {
|
|
copy_v3_v3(pixel, (float *)linear_buffer + linear_index);
|
|
pixel[3] = 1.0f;
|
|
}
|
|
else if (channels == 1) {
|
|
pixel[0] = linear_buffer[linear_index];
|
|
}
|
|
else {
|
|
BLI_assert_msg(0, "Unsupported number of channels in partial buffer update");
|
|
}
|
|
}
|
|
else if (byte_buffer) {
|
|
rgba_uchar_to_float(pixel, byte_buffer + linear_index);
|
|
IMB_colormanagement_colorspace_to_scene_linear_v3(pixel, rect_colorspace);
|
|
straight_to_premul_v4(pixel);
|
|
}
|
|
|
|
if (!is_data) {
|
|
IMB_colormanagement_processor_apply_pixel(cm_processor, pixel, channels);
|
|
}
|
|
|
|
if (display_buffer_float) {
|
|
size_t index = (size_t(y - ymin) * width + (x - xmin)) * channels;
|
|
|
|
if (channels == 4) {
|
|
copy_v4_v4(display_buffer_float + index, pixel);
|
|
}
|
|
else if (channels == 3) {
|
|
copy_v3_v3(display_buffer_float + index, pixel);
|
|
}
|
|
else /* if (channels == 1) */ {
|
|
display_buffer_float[index] = pixel[0];
|
|
}
|
|
}
|
|
else {
|
|
if (channels == 4) {
|
|
float pixel_straight[4];
|
|
premul_to_straight_v4_v4(pixel_straight, pixel);
|
|
rgba_float_to_uchar(display_buffer + display_index, pixel_straight);
|
|
}
|
|
else if (channels == 3) {
|
|
rgb_float_to_uchar(display_buffer + display_index, pixel);
|
|
display_buffer[display_index + 3] = 255;
|
|
}
|
|
else /* if (channels == 1) */ {
|
|
display_buffer[display_index] = display_buffer[display_index + 1] =
|
|
display_buffer[display_index + 2] = display_buffer[display_index + 3] =
|
|
unit_float_to_uchar_clamp(pixel[0]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (display_buffer_float) {
|
|
/* huh, for dither we need float buffer first, no cheaper way. currently */
|
|
IMB_buffer_float_from_byte(display_buffer_float,
|
|
byte_buffer,
|
|
IB_PROFILE_SRGB,
|
|
IB_PROFILE_SRGB,
|
|
true,
|
|
width,
|
|
height,
|
|
width,
|
|
display_stride);
|
|
}
|
|
else {
|
|
int i;
|
|
|
|
for (i = ymin; i < ymax; i++) {
|
|
size_t byte_offset = (size_t(linear_stride) * i + xmin) * 4;
|
|
size_t display_offset = (size_t(display_stride) * i + xmin) * 4;
|
|
|
|
memcpy(
|
|
display_buffer + display_offset, byte_buffer + byte_offset, sizeof(char[4]) * width);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (display_buffer_float) {
|
|
size_t display_index = (size_t(ymin) * display_stride + xmin) * channels;
|
|
|
|
IMB_buffer_byte_from_float(display_buffer + display_index,
|
|
display_buffer_float,
|
|
channels,
|
|
dither,
|
|
IB_PROFILE_SRGB,
|
|
IB_PROFILE_SRGB,
|
|
true,
|
|
width,
|
|
height,
|
|
display_stride,
|
|
width);
|
|
|
|
MEM_freeN(display_buffer_float);
|
|
}
|
|
}
|
|
|
|
struct PartialThreadData {
|
|
ImBuf *ibuf;
|
|
uchar *display_buffer;
|
|
const float *linear_buffer;
|
|
const uchar *byte_buffer;
|
|
int display_stride;
|
|
int linear_stride;
|
|
int linear_offset_x, linear_offset_y;
|
|
ColormanageProcessor *cm_processor;
|
|
int xmin, ymin, xmax;
|
|
};
|
|
|
|
static void partial_buffer_update_rect_thread_do(void *data_v, int scanline)
|
|
{
|
|
PartialThreadData *data = (PartialThreadData *)data_v;
|
|
int ymin = data->ymin + scanline;
|
|
const int num_scanlines = 1;
|
|
partial_buffer_update_rect(data->ibuf,
|
|
data->display_buffer,
|
|
data->linear_buffer,
|
|
data->byte_buffer,
|
|
data->display_stride,
|
|
data->linear_stride,
|
|
data->linear_offset_x,
|
|
data->linear_offset_y,
|
|
data->cm_processor,
|
|
data->xmin,
|
|
ymin,
|
|
data->xmax,
|
|
ymin + num_scanlines);
|
|
}
|
|
|
|
static void imb_partial_display_buffer_update_ex(
|
|
ImBuf *ibuf,
|
|
const float *linear_buffer,
|
|
const uchar *byte_buffer,
|
|
int stride,
|
|
int offset_x,
|
|
int offset_y,
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings,
|
|
int xmin,
|
|
int ymin,
|
|
int xmax,
|
|
int ymax,
|
|
bool do_threads)
|
|
{
|
|
ColormanageCacheViewSettings cache_view_settings;
|
|
ColormanageCacheDisplaySettings cache_display_settings;
|
|
void *cache_handle = nullptr;
|
|
uchar *display_buffer = nullptr;
|
|
int buffer_width = ibuf->x;
|
|
|
|
if (ibuf->display_buffer_flags) {
|
|
int view_flag, display_index;
|
|
|
|
colormanage_view_settings_to_cache(ibuf, &cache_view_settings, view_settings);
|
|
colormanage_display_settings_to_cache(&cache_display_settings, display_settings);
|
|
|
|
view_flag = 1 << (cache_view_settings.view - 1);
|
|
display_index = cache_display_settings.display - 1;
|
|
|
|
BLI_thread_lock(LOCK_COLORMANAGE);
|
|
|
|
if ((ibuf->userflags & IB_DISPLAY_BUFFER_INVALID) == 0) {
|
|
display_buffer = colormanage_cache_get(
|
|
ibuf, &cache_view_settings, &cache_display_settings, &cache_handle);
|
|
}
|
|
|
|
/* In some rare cases buffer's dimension could be changing directly from
|
|
* different thread
|
|
* this i.e. happens when image editor acquires render result
|
|
*/
|
|
buffer_width = ibuf->x;
|
|
|
|
/* Mark all other buffers as invalid. */
|
|
memset(ibuf->display_buffer_flags, 0, global_tot_display * sizeof(uint));
|
|
ibuf->display_buffer_flags[display_index] |= view_flag;
|
|
|
|
BLI_thread_unlock(LOCK_COLORMANAGE);
|
|
}
|
|
|
|
if (display_buffer) {
|
|
ColormanageProcessor *cm_processor = nullptr;
|
|
bool skip_transform = false;
|
|
|
|
/* Byte buffer is assumed to be in imbuf's rect space, so if byte buffer
|
|
* is known we could skip display->linear->display conversion in case
|
|
* display color space matches imbuf's rect space.
|
|
*
|
|
* But if there's a float buffer it's likely operation was performed on
|
|
* it first and byte buffer is likely to be out of date here.
|
|
*/
|
|
if (linear_buffer == nullptr && byte_buffer != nullptr) {
|
|
skip_transform = is_ibuf_rect_in_display_space(ibuf, view_settings, display_settings);
|
|
}
|
|
|
|
if (!skip_transform) {
|
|
cm_processor = IMB_colormanagement_display_processor_new(view_settings, display_settings);
|
|
}
|
|
|
|
if (do_threads) {
|
|
PartialThreadData data;
|
|
data.ibuf = ibuf;
|
|
data.display_buffer = display_buffer;
|
|
data.linear_buffer = linear_buffer;
|
|
data.byte_buffer = byte_buffer;
|
|
data.display_stride = buffer_width;
|
|
data.linear_stride = stride;
|
|
data.linear_offset_x = offset_x;
|
|
data.linear_offset_y = offset_y;
|
|
data.cm_processor = cm_processor;
|
|
data.xmin = xmin;
|
|
data.ymin = ymin;
|
|
data.xmax = xmax;
|
|
IMB_processor_apply_threaded_scanlines(
|
|
ymax - ymin, partial_buffer_update_rect_thread_do, &data);
|
|
}
|
|
else {
|
|
partial_buffer_update_rect(ibuf,
|
|
display_buffer,
|
|
linear_buffer,
|
|
byte_buffer,
|
|
buffer_width,
|
|
stride,
|
|
offset_x,
|
|
offset_y,
|
|
cm_processor,
|
|
xmin,
|
|
ymin,
|
|
xmax,
|
|
ymax);
|
|
}
|
|
|
|
if (cm_processor) {
|
|
IMB_colormanagement_processor_free(cm_processor);
|
|
}
|
|
|
|
IMB_display_buffer_release(cache_handle);
|
|
}
|
|
}
|
|
|
|
void IMB_partial_display_buffer_update(ImBuf *ibuf,
|
|
const float *linear_buffer,
|
|
const uchar *byte_buffer,
|
|
int stride,
|
|
int offset_x,
|
|
int offset_y,
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings,
|
|
int xmin,
|
|
int ymin,
|
|
int xmax,
|
|
int ymax)
|
|
{
|
|
imb_partial_display_buffer_update_ex(ibuf,
|
|
linear_buffer,
|
|
byte_buffer,
|
|
stride,
|
|
offset_x,
|
|
offset_y,
|
|
view_settings,
|
|
display_settings,
|
|
xmin,
|
|
ymin,
|
|
xmax,
|
|
ymax,
|
|
false);
|
|
}
|
|
|
|
void IMB_partial_display_buffer_update_threaded(
|
|
ImBuf *ibuf,
|
|
const float *linear_buffer,
|
|
const uchar *byte_buffer,
|
|
int stride,
|
|
int offset_x,
|
|
int offset_y,
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings,
|
|
int xmin,
|
|
int ymin,
|
|
int xmax,
|
|
int ymax)
|
|
{
|
|
int width = xmax - xmin;
|
|
int height = ymax - ymin;
|
|
bool do_threads = (size_t(width) * height >= 64 * 64);
|
|
imb_partial_display_buffer_update_ex(ibuf,
|
|
linear_buffer,
|
|
byte_buffer,
|
|
stride,
|
|
offset_x,
|
|
offset_y,
|
|
view_settings,
|
|
display_settings,
|
|
xmin,
|
|
ymin,
|
|
xmax,
|
|
ymax,
|
|
do_threads);
|
|
}
|
|
|
|
void IMB_partial_display_buffer_update_delayed(ImBuf *ibuf, int xmin, int ymin, int xmax, int ymax)
|
|
{
|
|
if (ibuf->invalid_rect.xmin == ibuf->invalid_rect.xmax) {
|
|
BLI_rcti_init(&ibuf->invalid_rect, xmin, xmax, ymin, ymax);
|
|
}
|
|
else {
|
|
rcti rect;
|
|
BLI_rcti_init(&rect, xmin, xmax, ymin, ymax);
|
|
BLI_rcti_union(&ibuf->invalid_rect, &rect);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Pixel Processor Functions
|
|
* \{ */
|
|
|
|
ColormanageProcessor *IMB_colormanagement_display_processor_new(
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings)
|
|
{
|
|
ColormanageProcessor *cm_processor;
|
|
ColorManagedViewSettings default_view_settings;
|
|
const ColorManagedViewSettings *applied_view_settings;
|
|
ColorSpace *display_space;
|
|
|
|
cm_processor = MEM_cnew<ColormanageProcessor>("colormanagement processor");
|
|
|
|
if (view_settings) {
|
|
applied_view_settings = view_settings;
|
|
}
|
|
else {
|
|
IMB_colormanagement_init_default_view_settings(&default_view_settings, display_settings);
|
|
applied_view_settings = &default_view_settings;
|
|
}
|
|
|
|
display_space = display_transform_get_colorspace(applied_view_settings, display_settings);
|
|
if (display_space) {
|
|
cm_processor->is_data_result = display_space->is_data;
|
|
}
|
|
|
|
cm_processor->cpu_processor = create_display_buffer_processor(
|
|
applied_view_settings->look,
|
|
applied_view_settings->view_transform,
|
|
display_settings->display_device,
|
|
applied_view_settings->exposure,
|
|
applied_view_settings->gamma,
|
|
global_role_scene_linear);
|
|
|
|
if (applied_view_settings->flag & COLORMANAGE_VIEW_USE_CURVES) {
|
|
cm_processor->curve_mapping = BKE_curvemapping_copy(applied_view_settings->curve_mapping);
|
|
BKE_curvemapping_premultiply(cm_processor->curve_mapping, false);
|
|
}
|
|
|
|
return cm_processor;
|
|
}
|
|
|
|
ColormanageProcessor *IMB_colormanagement_colorspace_processor_new(const char *from_colorspace,
|
|
const char *to_colorspace)
|
|
{
|
|
ColormanageProcessor *cm_processor;
|
|
ColorSpace *color_space;
|
|
|
|
cm_processor = MEM_cnew<ColormanageProcessor>("colormanagement processor");
|
|
|
|
color_space = colormanage_colorspace_get_named(to_colorspace);
|
|
cm_processor->is_data_result = color_space->is_data;
|
|
|
|
OCIO_ConstProcessorRcPtr *processor = create_colorspace_transform_processor(from_colorspace,
|
|
to_colorspace);
|
|
if (processor != nullptr) {
|
|
cm_processor->cpu_processor = OCIO_processorGetCPUProcessor(processor);
|
|
}
|
|
OCIO_processorRelease(processor);
|
|
|
|
return cm_processor;
|
|
}
|
|
|
|
bool IMB_colormanagement_processor_is_noop(ColormanageProcessor *cm_processor)
|
|
{
|
|
if (cm_processor->curve_mapping) {
|
|
/* Consider processor which has curve mapping as a non no-op.
|
|
* This is mainly for the simplicity of the check, since the current cases where this function
|
|
* is used the curve mapping is never assigned. */
|
|
return false;
|
|
}
|
|
|
|
return OCIO_cpuProcessorIsNoOp(cm_processor->cpu_processor);
|
|
}
|
|
|
|
void IMB_colormanagement_processor_apply_v4(ColormanageProcessor *cm_processor, float pixel[4])
|
|
{
|
|
if (cm_processor->curve_mapping) {
|
|
BKE_curvemapping_evaluate_premulRGBF(cm_processor->curve_mapping, pixel, pixel);
|
|
}
|
|
|
|
if (cm_processor->cpu_processor) {
|
|
OCIO_cpuProcessorApplyRGBA(cm_processor->cpu_processor, pixel);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_processor_apply_v4_predivide(ColormanageProcessor *cm_processor,
|
|
float pixel[4])
|
|
{
|
|
if (cm_processor->curve_mapping) {
|
|
BKE_curvemapping_evaluate_premulRGBF(cm_processor->curve_mapping, pixel, pixel);
|
|
}
|
|
|
|
if (cm_processor->cpu_processor) {
|
|
OCIO_cpuProcessorApplyRGBA_predivide(cm_processor->cpu_processor, pixel);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_processor_apply_v3(ColormanageProcessor *cm_processor, float pixel[3])
|
|
{
|
|
if (cm_processor->curve_mapping) {
|
|
BKE_curvemapping_evaluate_premulRGBF(cm_processor->curve_mapping, pixel, pixel);
|
|
}
|
|
|
|
if (cm_processor->cpu_processor) {
|
|
OCIO_cpuProcessorApplyRGB(cm_processor->cpu_processor, pixel);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_processor_apply_pixel(ColormanageProcessor *cm_processor,
|
|
float *pixel,
|
|
int channels)
|
|
{
|
|
if (channels == 4) {
|
|
IMB_colormanagement_processor_apply_v4_predivide(cm_processor, pixel);
|
|
}
|
|
else if (channels == 3) {
|
|
IMB_colormanagement_processor_apply_v3(cm_processor, pixel);
|
|
}
|
|
else if (channels == 1) {
|
|
if (cm_processor->curve_mapping) {
|
|
curve_mapping_apply_pixel(cm_processor->curve_mapping, pixel, 1);
|
|
}
|
|
}
|
|
else {
|
|
BLI_assert(
|
|
!"Incorrect number of channels passed to IMB_colormanagement_processor_apply_pixel");
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_processor_apply(ColormanageProcessor *cm_processor,
|
|
float *buffer,
|
|
int width,
|
|
int height,
|
|
int channels,
|
|
bool predivide)
|
|
{
|
|
/* apply curve mapping */
|
|
if (cm_processor->curve_mapping) {
|
|
int x, y;
|
|
|
|
for (y = 0; y < height; y++) {
|
|
for (x = 0; x < width; x++) {
|
|
float *pixel = buffer + channels * (size_t(y) * width + x);
|
|
|
|
curve_mapping_apply_pixel(cm_processor->curve_mapping, pixel, channels);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cm_processor->cpu_processor && channels >= 3) {
|
|
OCIO_PackedImageDesc *img;
|
|
|
|
/* apply OCIO processor */
|
|
img = OCIO_createOCIO_PackedImageDesc(buffer,
|
|
width,
|
|
height,
|
|
channels,
|
|
sizeof(float),
|
|
size_t(channels) * sizeof(float),
|
|
size_t(channels) * sizeof(float) * width);
|
|
|
|
if (predivide) {
|
|
OCIO_cpuProcessorApply_predivide(cm_processor->cpu_processor, img);
|
|
}
|
|
else {
|
|
OCIO_cpuProcessorApply(cm_processor->cpu_processor, img);
|
|
}
|
|
|
|
OCIO_PackedImageDescRelease(img);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_processor_apply_byte(
|
|
ColormanageProcessor *cm_processor, uchar *buffer, int width, int height, int channels)
|
|
{
|
|
/* TODO(sergey): Would be nice to support arbitrary channels configurations,
|
|
* but for now it's not so important.
|
|
*/
|
|
BLI_assert(channels == 4);
|
|
float pixel[4];
|
|
for (int y = 0; y < height; y++) {
|
|
for (int x = 0; x < width; x++) {
|
|
size_t offset = channels * (size_t(y) * width + x);
|
|
rgba_uchar_to_float(pixel, buffer + offset);
|
|
IMB_colormanagement_processor_apply_v4(cm_processor, pixel);
|
|
rgba_float_to_uchar(buffer + offset, pixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_processor_free(ColormanageProcessor *cm_processor)
|
|
{
|
|
if (cm_processor->curve_mapping) {
|
|
BKE_curvemapping_free(cm_processor->curve_mapping);
|
|
}
|
|
if (cm_processor->cpu_processor) {
|
|
OCIO_cpuProcessorRelease(cm_processor->cpu_processor);
|
|
}
|
|
|
|
MEM_freeN(cm_processor);
|
|
}
|
|
|
|
/* **** OpenGL drawing routines using GLSL for color space transform ***** */
|
|
|
|
static void curve_mapping_to_ocio_settings(CurveMapping *curve_mapping,
|
|
OCIO_CurveMappingSettings *curve_mapping_settings)
|
|
{
|
|
int i;
|
|
|
|
BKE_curvemapping_init(curve_mapping);
|
|
BKE_curvemapping_premultiply(curve_mapping, false);
|
|
BKE_curvemapping_table_RGBA(
|
|
curve_mapping, &curve_mapping_settings->lut, &curve_mapping_settings->lut_size);
|
|
|
|
curve_mapping_settings->use_extend_extrapolate = (curve_mapping->flag &
|
|
CUMA_EXTEND_EXTRAPOLATE) != 0;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
CurveMap *cuma = curve_mapping->cm + i;
|
|
curve_mapping_settings->range[i] = cuma->range;
|
|
curve_mapping_settings->mintable[i] = cuma->mintable;
|
|
curve_mapping_settings->ext_in_x[i] = cuma->ext_in[0];
|
|
curve_mapping_settings->ext_in_y[i] = cuma->ext_in[1];
|
|
curve_mapping_settings->ext_out_x[i] = cuma->ext_out[0];
|
|
curve_mapping_settings->ext_out_y[i] = cuma->ext_out[1];
|
|
curve_mapping_settings->first_x[i] = cuma->table[0].x;
|
|
curve_mapping_settings->first_y[i] = cuma->table[0].y;
|
|
curve_mapping_settings->last_x[i] = cuma->table[CM_TABLE].x;
|
|
curve_mapping_settings->last_y[i] = cuma->table[CM_TABLE].y;
|
|
}
|
|
|
|
copy_v3_v3(curve_mapping_settings->black, curve_mapping->black);
|
|
copy_v3_v3(curve_mapping_settings->bwmul, curve_mapping->bwmul);
|
|
|
|
curve_mapping_settings->cache_id = size_t(curve_mapping) + curve_mapping->changed_timestamp;
|
|
}
|
|
|
|
static OCIO_CurveMappingSettings *update_glsl_curve_mapping(
|
|
const ColorManagedViewSettings *view_settings)
|
|
{
|
|
/* Using curve mapping? */
|
|
const bool use_curve_mapping = (view_settings->flag & COLORMANAGE_VIEW_USE_CURVES) != 0;
|
|
if (!use_curve_mapping) {
|
|
return nullptr;
|
|
}
|
|
|
|
/* Already up to date? */
|
|
OCIO_CurveMappingSettings *curve_mapping_settings = &global_gpu_state.curve_mapping_settings;
|
|
if (view_settings->curve_mapping->changed_timestamp ==
|
|
global_gpu_state.curve_mapping_timestamp &&
|
|
view_settings->curve_mapping == global_gpu_state.orig_curve_mapping)
|
|
{
|
|
return curve_mapping_settings;
|
|
}
|
|
|
|
/* Need to update. */
|
|
CurveMapping *new_curve_mapping = nullptr;
|
|
|
|
/* We're using curve mapping's address as a cache ID,
|
|
* so we need to make sure re-allocation gives new address here.
|
|
* We do this by allocating new curve mapping before freeing old one. */
|
|
if (use_curve_mapping) {
|
|
new_curve_mapping = BKE_curvemapping_copy(view_settings->curve_mapping);
|
|
}
|
|
|
|
if (global_gpu_state.curve_mapping) {
|
|
BKE_curvemapping_free(global_gpu_state.curve_mapping);
|
|
MEM_freeN(curve_mapping_settings->lut);
|
|
global_gpu_state.curve_mapping = nullptr;
|
|
curve_mapping_settings->lut = nullptr;
|
|
}
|
|
|
|
/* Fill in OCIO's curve mapping settings. */
|
|
if (use_curve_mapping) {
|
|
curve_mapping_to_ocio_settings(new_curve_mapping, &global_gpu_state.curve_mapping_settings);
|
|
|
|
global_gpu_state.curve_mapping = new_curve_mapping;
|
|
global_gpu_state.curve_mapping_timestamp = view_settings->curve_mapping->changed_timestamp;
|
|
global_gpu_state.orig_curve_mapping = view_settings->curve_mapping;
|
|
global_gpu_state.use_curve_mapping = true;
|
|
}
|
|
else {
|
|
global_gpu_state.orig_curve_mapping = nullptr;
|
|
global_gpu_state.use_curve_mapping = false;
|
|
}
|
|
|
|
return curve_mapping_settings;
|
|
}
|
|
|
|
bool IMB_colormanagement_support_glsl_draw(const ColorManagedViewSettings * /*view_settings*/)
|
|
{
|
|
return OCIO_supportGPUShader();
|
|
}
|
|
|
|
bool IMB_colormanagement_setup_glsl_draw_from_space(
|
|
const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings,
|
|
ColorSpace *from_colorspace,
|
|
float dither,
|
|
bool predivide,
|
|
bool do_overlay_merge)
|
|
{
|
|
ColorManagedViewSettings default_view_settings;
|
|
const ColorManagedViewSettings *applied_view_settings;
|
|
|
|
if (view_settings) {
|
|
applied_view_settings = view_settings;
|
|
}
|
|
else {
|
|
/* If no view settings were specified, use default ones, which will
|
|
* attempt not to do any extra color correction. */
|
|
IMB_colormanagement_init_default_view_settings(&default_view_settings, display_settings);
|
|
applied_view_settings = &default_view_settings;
|
|
}
|
|
|
|
/* Ensure curve mapping is up to data. */
|
|
OCIO_CurveMappingSettings *curve_mapping_settings = update_glsl_curve_mapping(
|
|
applied_view_settings);
|
|
|
|
/* GPU shader parameters. */
|
|
const char *input = from_colorspace ? from_colorspace->name : global_role_scene_linear;
|
|
const char *view = applied_view_settings->view_transform;
|
|
const char *display = display_settings->display_device;
|
|
const bool use_look = colormanage_use_look(applied_view_settings->look,
|
|
applied_view_settings->view_transform);
|
|
const char *look = (use_look) ? applied_view_settings->look : "";
|
|
const float exposure = applied_view_settings->exposure;
|
|
const float gamma = applied_view_settings->gamma;
|
|
const float scale = (exposure == 0.0f) ? 1.0f : powf(2.0f, exposure);
|
|
const float exponent = (gamma == 1.0f) ? 1.0f : 1.0f / max_ff(FLT_EPSILON, gamma);
|
|
const bool use_hdr = GPU_hdr_support() &&
|
|
(applied_view_settings->flag & COLORMANAGE_VIEW_USE_HDR) != 0;
|
|
|
|
OCIO_ConstConfigRcPtr *config = OCIO_getCurrentConfig();
|
|
|
|
/* Bind shader. Internally GPU shaders are created and cached on demand. */
|
|
global_gpu_state.gpu_shader_bound = OCIO_gpuDisplayShaderBind(config,
|
|
input,
|
|
view,
|
|
display,
|
|
look,
|
|
curve_mapping_settings,
|
|
scale,
|
|
exponent,
|
|
dither,
|
|
predivide,
|
|
do_overlay_merge,
|
|
use_hdr);
|
|
|
|
OCIO_configRelease(config);
|
|
|
|
return global_gpu_state.gpu_shader_bound;
|
|
}
|
|
|
|
bool IMB_colormanagement_setup_glsl_draw(const ColorManagedViewSettings *view_settings,
|
|
const ColorManagedDisplaySettings *display_settings,
|
|
float dither,
|
|
bool predivide)
|
|
{
|
|
return IMB_colormanagement_setup_glsl_draw_from_space(
|
|
view_settings, display_settings, nullptr, dither, predivide, false);
|
|
}
|
|
|
|
bool IMB_colormanagement_setup_glsl_draw_from_space_ctx(const bContext *C,
|
|
ColorSpace *from_colorspace,
|
|
float dither,
|
|
bool predivide)
|
|
{
|
|
ColorManagedViewSettings *view_settings;
|
|
ColorManagedDisplaySettings *display_settings;
|
|
|
|
IMB_colormanagement_display_settings_from_ctx(C, &view_settings, &display_settings);
|
|
|
|
return IMB_colormanagement_setup_glsl_draw_from_space(
|
|
view_settings, display_settings, from_colorspace, dither, predivide, false);
|
|
}
|
|
|
|
bool IMB_colormanagement_setup_glsl_draw_ctx(const bContext *C, float dither, bool predivide)
|
|
{
|
|
return IMB_colormanagement_setup_glsl_draw_from_space_ctx(C, nullptr, dither, predivide);
|
|
}
|
|
|
|
void IMB_colormanagement_finish_glsl_draw()
|
|
{
|
|
if (global_gpu_state.gpu_shader_bound) {
|
|
OCIO_gpuDisplayShaderUnbind();
|
|
global_gpu_state.gpu_shader_bound = false;
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Rendering Tables
|
|
* \{ */
|
|
|
|
/* Calculate color in range 800..12000 using an approximation
|
|
* a/x+bx+c for R and G and ((at + b)t + c)t + d) for B
|
|
*
|
|
* The result of this can be negative to support gamut wider than
|
|
* than rec.709, just needs to be clamped. */
|
|
|
|
static const float blackbody_table_r[7][3] = {{1.61919106e+03f, -2.05010916e-03f, 5.02995757e+00f},
|
|
{2.48845471e+03f, -1.11330907e-03f, 3.22621544e+00f},
|
|
{3.34143193e+03f, -4.86551192e-04f, 1.76486769e+00f},
|
|
{4.09461742e+03f, -1.27446582e-04f, 7.25731635e-01f},
|
|
{4.67028036e+03f, 2.91258199e-05f, 1.26703442e-01f},
|
|
{4.59509185e+03f, 2.87495649e-05f, 1.50345020e-01f},
|
|
{3.78717450e+03f, 9.35907826e-06f, 3.99075871e-01f}};
|
|
|
|
static const float blackbody_table_g[7][3] = {
|
|
{-4.88999748e+02f, 6.04330754e-04f, -7.55807526e-02f},
|
|
{-7.55994277e+02f, 3.16730098e-04f, 4.78306139e-01f},
|
|
{-1.02363977e+03f, 1.20223470e-04f, 9.36662319e-01f},
|
|
{-1.26571316e+03f, 4.87340896e-06f, 1.27054498e+00f},
|
|
{-1.42529332e+03f, -4.01150431e-05f, 1.43972784e+00f},
|
|
{-1.17554822e+03f, -2.16378048e-05f, 1.30408023e+00f},
|
|
{-5.00799571e+02f, -4.59832026e-06f, 1.09098763e+00f}};
|
|
|
|
static const float blackbody_table_b[7][4] = {
|
|
{5.96945309e-11f, -4.85742887e-08f, -9.70622247e-05f, -4.07936148e-03f},
|
|
{2.40430366e-11f, 5.55021075e-08f, -1.98503712e-04f, 2.89312858e-02f},
|
|
{-1.40949732e-11f, 1.89878968e-07f, -3.56632824e-04f, 9.10767778e-02f},
|
|
{-3.61460868e-11f, 2.84822009e-07f, -4.93211319e-04f, 1.56723440e-01f},
|
|
{-1.97075738e-11f, 1.75359352e-07f, -2.50542825e-04f, -2.22783266e-02f},
|
|
{-1.61997957e-13f, -1.64216008e-08f, 3.86216271e-04f, -7.38077418e-01f},
|
|
{6.72650283e-13f, -2.73078809e-08f, 4.24098264e-04f, -7.52335691e-01f}};
|
|
|
|
static void blackbody_temperature_to_rec709(float rec709[3], float t)
|
|
{
|
|
if (t >= 12000.0f) {
|
|
rec709[0] = 0.8262954810464208f;
|
|
rec709[1] = 0.9945080501520986f;
|
|
rec709[2] = 1.566307710274283f;
|
|
}
|
|
else if (t < 800.0f) {
|
|
rec709[0] = 5.413294490189271f;
|
|
rec709[1] = -0.20319390035873933f;
|
|
rec709[2] = -0.0822535242887164f;
|
|
}
|
|
else {
|
|
int i = (t >= 6365.0f) ? 6 :
|
|
(t >= 3315.0f) ? 5 :
|
|
(t >= 1902.0f) ? 4 :
|
|
(t >= 1449.0f) ? 3 :
|
|
(t >= 1167.0f) ? 2 :
|
|
(t >= 965.0f) ? 1 :
|
|
0;
|
|
|
|
const float *r = blackbody_table_r[i];
|
|
const float *g = blackbody_table_g[i];
|
|
const float *b = blackbody_table_b[i];
|
|
|
|
const float t_inv = 1.0f / t;
|
|
rec709[0] = r[0] * t_inv + r[1] * t + r[2];
|
|
rec709[1] = g[0] * t_inv + g[1] * t + g[2];
|
|
rec709[2] = ((b[0] * t + b[1]) * t + b[2]) * t + b[3];
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_blackbody_temperature_to_rgb_table(float *r_table,
|
|
const int width,
|
|
const float min,
|
|
const float max)
|
|
{
|
|
for (int i = 0; i < width; i++) {
|
|
float temperature = min + (max - min) / float(width) * float(i);
|
|
|
|
float rec709[3];
|
|
blackbody_temperature_to_rec709(rec709, temperature);
|
|
|
|
float rgb[3];
|
|
IMB_colormanagement_rec709_to_scene_linear(rgb, rec709);
|
|
clamp_v3(rgb, 0.0f, FLT_MAX);
|
|
|
|
copy_v3_v3(&r_table[i * 4], rgb);
|
|
r_table[i * 4 + 3] = 0.0f;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* CIE color matching functions `xBar`, `yBar`, and `zBar` for
|
|
* wavelengths from 380 through 780 nanometers, every 5 nanometers.
|
|
*
|
|
* For a wavelength lambda in this range:
|
|
* \code{.txt}
|
|
* cie_color_match[(lambda - 380) / 5][0] = xBar
|
|
* cie_color_match[(lambda - 380) / 5][1] = yBar
|
|
* cie_color_match[(lambda - 380) / 5][2] = zBar
|
|
* \endcode
|
|
*/
|
|
|
|
static float cie_colour_match[81][3] = {
|
|
{0.0014f, 0.0000f, 0.0065f}, {0.0022f, 0.0001f, 0.0105f}, {0.0042f, 0.0001f, 0.0201f},
|
|
{0.0076f, 0.0002f, 0.0362f}, {0.0143f, 0.0004f, 0.0679f}, {0.0232f, 0.0006f, 0.1102f},
|
|
{0.0435f, 0.0012f, 0.2074f}, {0.0776f, 0.0022f, 0.3713f}, {0.1344f, 0.0040f, 0.6456f},
|
|
{0.2148f, 0.0073f, 1.0391f}, {0.2839f, 0.0116f, 1.3856f}, {0.3285f, 0.0168f, 1.6230f},
|
|
{0.3483f, 0.0230f, 1.7471f}, {0.3481f, 0.0298f, 1.7826f}, {0.3362f, 0.0380f, 1.7721f},
|
|
{0.3187f, 0.0480f, 1.7441f}, {0.2908f, 0.0600f, 1.6692f}, {0.2511f, 0.0739f, 1.5281f},
|
|
{0.1954f, 0.0910f, 1.2876f}, {0.1421f, 0.1126f, 1.0419f}, {0.0956f, 0.1390f, 0.8130f},
|
|
{0.0580f, 0.1693f, 0.6162f}, {0.0320f, 0.2080f, 0.4652f}, {0.0147f, 0.2586f, 0.3533f},
|
|
{0.0049f, 0.3230f, 0.2720f}, {0.0024f, 0.4073f, 0.2123f}, {0.0093f, 0.5030f, 0.1582f},
|
|
{0.0291f, 0.6082f, 0.1117f}, {0.0633f, 0.7100f, 0.0782f}, {0.1096f, 0.7932f, 0.0573f},
|
|
{0.1655f, 0.8620f, 0.0422f}, {0.2257f, 0.9149f, 0.0298f}, {0.2904f, 0.9540f, 0.0203f},
|
|
{0.3597f, 0.9803f, 0.0134f}, {0.4334f, 0.9950f, 0.0087f}, {0.5121f, 1.0000f, 0.0057f},
|
|
{0.5945f, 0.9950f, 0.0039f}, {0.6784f, 0.9786f, 0.0027f}, {0.7621f, 0.9520f, 0.0021f},
|
|
{0.8425f, 0.9154f, 0.0018f}, {0.9163f, 0.8700f, 0.0017f}, {0.9786f, 0.8163f, 0.0014f},
|
|
{1.0263f, 0.7570f, 0.0011f}, {1.0567f, 0.6949f, 0.0010f}, {1.0622f, 0.6310f, 0.0008f},
|
|
{1.0456f, 0.5668f, 0.0006f}, {1.0026f, 0.5030f, 0.0003f}, {0.9384f, 0.4412f, 0.0002f},
|
|
{0.8544f, 0.3810f, 0.0002f}, {0.7514f, 0.3210f, 0.0001f}, {0.6424f, 0.2650f, 0.0000f},
|
|
{0.5419f, 0.2170f, 0.0000f}, {0.4479f, 0.1750f, 0.0000f}, {0.3608f, 0.1382f, 0.0000f},
|
|
{0.2835f, 0.1070f, 0.0000f}, {0.2187f, 0.0816f, 0.0000f}, {0.1649f, 0.0610f, 0.0000f},
|
|
{0.1212f, 0.0446f, 0.0000f}, {0.0874f, 0.0320f, 0.0000f}, {0.0636f, 0.0232f, 0.0000f},
|
|
{0.0468f, 0.0170f, 0.0000f}, {0.0329f, 0.0119f, 0.0000f}, {0.0227f, 0.0082f, 0.0000f},
|
|
{0.0158f, 0.0057f, 0.0000f}, {0.0114f, 0.0041f, 0.0000f}, {0.0081f, 0.0029f, 0.0000f},
|
|
{0.0058f, 0.0021f, 0.0000f}, {0.0041f, 0.0015f, 0.0000f}, {0.0029f, 0.0010f, 0.0000f},
|
|
{0.0020f, 0.0007f, 0.0000f}, {0.0014f, 0.0005f, 0.0000f}, {0.0010f, 0.0004f, 0.0000f},
|
|
{0.0007f, 0.0002f, 0.0000f}, {0.0005f, 0.0002f, 0.0000f}, {0.0003f, 0.0001f, 0.0000f},
|
|
{0.0002f, 0.0001f, 0.0000f}, {0.0002f, 0.0001f, 0.0000f}, {0.0001f, 0.0000f, 0.0000f},
|
|
{0.0001f, 0.0000f, 0.0000f}, {0.0001f, 0.0000f, 0.0000f}, {0.0000f, 0.0000f, 0.0000f}};
|
|
|
|
static void wavelength_to_xyz(float xyz[3], float lambda_nm)
|
|
{
|
|
float ii = (lambda_nm - 380.0f) * (1.0f / 5.0f); /* Scaled 0..80. */
|
|
int i = int(ii);
|
|
|
|
if (i < 0 || i >= 80) {
|
|
xyz[0] = 0.0f;
|
|
xyz[1] = 0.0f;
|
|
xyz[2] = 0.0f;
|
|
}
|
|
else {
|
|
ii -= float(i);
|
|
const float *c = cie_colour_match[i];
|
|
xyz[0] = c[0] + ii * (c[3] - c[0]);
|
|
xyz[1] = c[1] + ii * (c[4] - c[1]);
|
|
xyz[2] = c[2] + ii * (c[5] - c[2]);
|
|
}
|
|
}
|
|
|
|
void IMB_colormanagement_wavelength_to_rgb_table(float *r_table, const int width)
|
|
{
|
|
for (int i = 0; i < width; i++) {
|
|
float temperature = 380 + 400 / float(width) * float(i);
|
|
|
|
float xyz[3];
|
|
wavelength_to_xyz(xyz, temperature);
|
|
|
|
float rgb[3];
|
|
IMB_colormanagement_xyz_to_scene_linear(rgb, xyz);
|
|
clamp_v3(rgb, 0.0f, FLT_MAX);
|
|
|
|
copy_v3_v3(&r_table[i * 4], rgb);
|
|
r_table[i * 4 + 3] = 0.0f;
|
|
}
|
|
}
|
|
|
|
/** \} */
|