Files
test2/source/blender/blenlib/intern/string_utils.cc
Sergey Sharybin 7ceb4495c5 Refactor: OpenColorIO integration
Briefly about this change:
- OpenColorIO C-API is removed.
- The information about color spaces in ImBuf module is removed.
  It was stored in global ListBase in colormanagement.cc.
- Both OpenColorIO and fallback implementation supports GPU drawing.
- Fallback implementation supports white point, RGB curves, etc.
- Removed check for support of GPU drawing in IMB.

Historically it was implemented in a separate library with C-API, this
is because way back C++ code needed to stay in intern. This causes all
sort of overheads, and even calls that are strictly considered bad
level.

This change moves OpenColorIO integration into a module within imbuf,
next to movie, and next to IMB_colormanagement which is the main user
of it. This allows to avoid copy of color spaces, displays, views etc
in the ImBuf: they were used to help quickly querying information to
be shown on the interface. With this change it can be stored in the
same data structures as what is used by the OpenColorIO integration.
While it might not be fully avoiding duplication it is now less, and
there is no need in the user code to maintain the copies.

In a lot of cases this change also avoids allocations done per access
to the OpenColorIO. For example, it is not needed anymore to allocate
image descriptor in a heap.

The bigger user-visible change is that the fallback implementation now
supports GLSL drawing, with the whole list of supported features, such
as curve mapping and white point. This should help simplifying code
which relies on color space conversion on GPU: there is no need to
figure out fallback solution in such cases. The only case when drawing
will not work is when there is some actual bug, or driver issue, and
shader has failed to compile.

The change avoids having an opaque type for color space, and instead
uses forward declaration. It is a bit verbose on declaration, but helps
avoiding unsafe type-casts. There are ways to solve this in the future,
like having a header for forward declaration, or to flatten the name
space a bit.

There should be no user-level changes under normal operation.
When building without OpenColorIO or the configuration has a typo or
is missing a fuller set of color management tools is applies (such as the
white point correction).

Pull Request: https://projects.blender.org/blender/blender/pulls/138433
2025-05-09 14:01:43 +02:00

655 lines
18 KiB
C++

/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <array>
#include <stdexcept>
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.hh"
#include "BLI_utildefines.h"
#include "BLI_dynstr.h"
#include "DNA_listBase.h"
#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
/* -------------------------------------------------------------------- */
/** \name String Replace
* \{ */
char *BLI_string_replaceN(const char *__restrict str,
const char *__restrict substr_old,
const char *__restrict substr_new)
{
DynStr *ds = nullptr;
size_t len_old = strlen(substr_old);
const char *match;
BLI_assert(substr_old[0] != '\0');
/* While we can still find a match for the old sub-string that we're searching for,
* keep dicing and replacing. */
while ((match = strstr(str, substr_old))) {
/* the assembly buffer only gets created when we actually need to rebuild the string */
if (ds == nullptr) {
ds = BLI_dynstr_new();
}
/* If the match position does not match the current position in the string,
* copy the text up to this position and advance the current position in the string. */
if (str != match) {
/* Add the segment of the string from `str` to match to the buffer,
* then restore the value at match. */
BLI_dynstr_nappend(ds, str, int(match - str));
/* now our current position should be set on the start of the match */
str = match;
}
/* Add the replacement text to the accumulation buffer. */
BLI_dynstr_append(ds, substr_new);
/* Advance the current position of the string up to the end of the replaced segment. */
str += len_old;
}
/* Finish off and return a new string that has had all occurrences of. */
if (ds) {
char *str_new;
/* Add what's left of the string to the assembly buffer
* - we've been adjusting `str` to point at the end of the replaced segments. */
BLI_dynstr_append(ds, str);
/* Convert to new c-string (MEM_malloc'd), and free the buffer. */
str_new = BLI_dynstr_get_cstring(ds);
BLI_dynstr_free(ds);
return str_new;
}
/* Just create a new copy of the entire string - we avoid going through the assembly buffer
* for what should be a bit more efficiency. */
return BLI_strdup(str);
}
void BLI_string_replace(std::string &haystack,
const blender::StringRef needle,
const blender::StringRef other)
{
size_t i = 0;
size_t index;
while ((index = haystack.find(needle, i)) != std::string::npos) {
haystack.replace(index, size_t(needle.size()), other);
i = index + size_t(other.size());
}
}
void BLI_string_replace_char(char *str, char src, char dst)
{
while (*str) {
if (*str == src) {
*str = dst;
}
str++;
}
}
bool BLI_string_replace_table_exact(char *string,
const size_t string_len,
const char *replace_table[][2],
int replace_table_len)
{
BLI_string_debug_size_after_nil(string, string_len);
for (int i = 0; i < replace_table_len; i++) {
if (STREQ(string, replace_table[i][0])) {
BLI_strncpy(string, replace_table[i][1], string_len);
return true;
}
}
return false;
}
size_t BLI_string_replace_range(
char *string, size_t string_maxncpy, int src_beg, int src_end, const char *dst)
{
int string_len = int(strlen(string));
BLI_assert(src_beg <= src_end);
BLI_assert(src_end <= string_len);
const int src_len = src_end - src_beg;
int dst_len = int(strlen(dst));
if (src_len < dst_len) {
/* Grow, first handle special cases. */
/* Special case, the src_end is entirely clipped. */
if (UNLIKELY(int(string_maxncpy) <= src_beg + dst_len)) {
/* There is only room for the destination. */
dst_len = (int(string_maxncpy) - src_beg) - 1;
string_len = src_end;
string[string_len] = '\0';
}
const int ofs = dst_len - src_len;
/* Clip the string when inserting the destination string exceeds `string_maxncpy`. */
if (string_len + ofs >= int(string_maxncpy)) {
string_len = (int(string_maxncpy) - ofs) - 1;
string[string_len] = '\0';
BLI_assert(src_end <= string_len);
}
/* Grow. */
memmove(string + (src_end + ofs), string + src_end, size_t(string_len - src_end) + 1);
string_len += ofs;
}
else if (src_len > dst_len) {
/* Shrink. */
const int ofs = src_len - dst_len;
memmove(string + (src_end - ofs), string + src_end, size_t(string_len - src_end) + 1);
string_len -= ofs;
}
else { /* Simple case, no resizing. */
BLI_assert(src_len == dst_len);
}
if (dst_len > 0) {
memcpy(string + src_beg, dst, size_t(dst_len));
}
BLI_assert(string[string_len] == '\0');
return size_t(string_len);
}
/** \} */
blender::StringRef BLI_string_split_name_number(const blender::StringRef name_full,
const char delim,
int &r_number)
{
const int64_t delim_index = name_full.rfind(delim);
r_number = 0;
if (delim_index == blender::StringRef::not_found) {
return name_full;
}
blender::StringRef name_base = name_full.substr(0, delim_index);
if (delim_index < name_full.size() - 1) {
const blender::StringRef num_str = name_full.substr(delim_index + 1);
if (!std::all_of(num_str.begin(), num_str.end(), ::isdigit)) {
return name_full;
}
/* Converting numerical suffix to an int, can overflow for large numbers. */
try {
r_number = std::stoi(num_str);
return name_base;
}
catch (std::out_of_range const & /*ex*/) {
r_number = 0;
}
}
return name_full;
}
size_t BLI_string_split_name_number(const char *name,
const char delim,
char *r_name_left,
int *r_number)
{
const std::string name_base = BLI_string_split_name_number(name, delim, *r_number);
BLI_strncpy(r_name_left, name_base.c_str(), name_base.size() + 1);
return name_base.size();
}
bool BLI_string_is_decimal(const char *string)
{
if (*string == '\0') {
return false;
}
/* Keep iterating over the string until a non-digit is found. */
while (isdigit(*string)) {
string++;
}
/* If the non-digit we found is the terminating \0, everything was digits. */
return *string == '\0';
}
static bool is_char_sep(const char c)
{
return ELEM(c, '.', ' ', '-', '_');
}
void BLI_string_split_suffix(const char *string,
const size_t string_maxlen,
char *r_body,
char *r_suf)
{
BLI_string_debug_size(r_body, string_maxlen);
BLI_string_debug_size(r_suf, string_maxlen);
size_t len = BLI_strnlen(string, string_maxlen);
size_t i;
r_body[0] = r_suf[0] = '\0';
for (i = len; i > 0; i--) {
if (is_char_sep(string[i])) {
BLI_strncpy(r_body, string, i + 1);
BLI_strncpy(r_suf, string + i, (len + 1) - i);
return;
}
}
memcpy(r_body, string, len + 1);
}
void BLI_string_split_prefix(const char *string,
const size_t string_maxlen,
char *r_pre,
char *r_body)
{
BLI_string_debug_size(r_pre, string_maxlen);
BLI_string_debug_size(r_body, string_maxlen);
size_t len = BLI_strnlen(string, string_maxlen);
size_t i;
r_body[0] = r_pre[0] = '\0';
for (i = 1; i < len; i++) {
if (is_char_sep(string[i])) {
i++;
BLI_strncpy(r_pre, string, i + 1);
BLI_strncpy(r_body, string + i, (len + 1) - i);
return;
}
}
BLI_strncpy(r_body, string, len);
}
size_t BLI_string_flip_side_name(char *name_dst,
const char *name_src,
const bool strip_number,
const size_t name_dst_maxncpy)
{
BLI_string_debug_size(name_dst, name_dst_maxncpy);
size_t len;
char *prefix = static_cast<char *>(alloca(name_dst_maxncpy)); /* The part before the facing */
char *suffix = static_cast<char *>(alloca(name_dst_maxncpy)); /* The part after the facing */
char *number = static_cast<char *>(alloca(name_dst_maxncpy)); /* The number extension string */
const char *replace = nullptr;
char *index = nullptr;
bool is_set = false;
*prefix = *suffix = *number = '\0';
/* always copy the name, since this can be called with an uninitialized string */
len = BLI_strncpy_rlen(name_dst, name_src, name_dst_maxncpy);
if (len < 3) {
/* We don't support names such as `.R` or `.L`. */
return len;
}
/* We first check the case with a .### extension, let's find the last period */
if (isdigit(name_dst[len - 1])) {
index = strrchr(name_dst, '.'); /* Last occurrence. */
if (index && isdigit(index[1])) { /* Doesn't handle case `bone.1abc2` correct..., whatever! */
if (strip_number == false) {
BLI_strncpy(number, index, name_dst_maxncpy);
}
*index = 0;
len = BLI_strnlen(name_dst, name_dst_maxncpy);
}
}
BLI_strncpy(prefix, name_dst, name_dst_maxncpy);
/* First case; separator (`.` or `_`) with extensions in `r R l L`. */
if ((len > 1) && is_char_sep(name_dst[len - 2])) {
is_set = true;
switch (name_dst[len - 1]) {
case 'l':
prefix[len - 1] = 0;
replace = "r";
break;
case 'r':
prefix[len - 1] = 0;
replace = "l";
break;
case 'L':
prefix[len - 1] = 0;
replace = "R";
break;
case 'R':
prefix[len - 1] = 0;
replace = "L";
break;
default:
is_set = false;
}
}
/* case; beginning with r R l L, with separator after it */
if (!is_set && is_char_sep(name_dst[1])) {
is_set = true;
switch (name_dst[0]) {
case 'l':
replace = "r";
BLI_strncpy(suffix, name_dst + 1, name_dst_maxncpy);
prefix[0] = 0;
break;
case 'r':
replace = "l";
BLI_strncpy(suffix, name_dst + 1, name_dst_maxncpy);
prefix[0] = 0;
break;
case 'L':
replace = "R";
BLI_strncpy(suffix, name_dst + 1, name_dst_maxncpy);
prefix[0] = 0;
break;
case 'R':
replace = "L";
BLI_strncpy(suffix, name_dst + 1, name_dst_maxncpy);
prefix[0] = 0;
break;
default:
is_set = false;
}
}
if (!is_set && len > 5) {
/* Test for a separator to apply the rule: ultimate left or right. */
if (((index = BLI_strcasestr(prefix, "right")) == prefix) || (index == prefix + len - 5)) {
is_set = true;
if (index[0] == 'r') {
replace = "left";
}
else {
replace = (index[1] == 'I' ? "LEFT" : "Left");
}
*index = 0;
BLI_strncpy(suffix, index + 5, name_dst_maxncpy);
}
else if (((index = BLI_strcasestr(prefix, "left")) == prefix) || (index == prefix + len - 4)) {
is_set = true;
if (index[0] == 'l') {
replace = "right";
}
else {
replace = (index[1] == 'E' ? "RIGHT" : "Right");
}
*index = 0;
BLI_strncpy(suffix, index + 4, name_dst_maxncpy);
}
}
return BLI_snprintf_rlen(
name_dst, name_dst_maxncpy, "%s%s%s%s", prefix, replace ? replace : "", suffix, number);
}
/* Unique name utils. */
void BLI_uniquename_cb(blender::FunctionRef<bool(blender::StringRefNull)> unique_check,
const char *defname,
char delim,
char *name,
size_t name_maxncpy)
{
BLI_string_debug_size_after_nil(name, name_maxncpy);
if (name[0] == '\0') {
BLI_strncpy(name, defname, name_maxncpy);
}
if (unique_check(name)) {
char numstr[16];
char *tempname = static_cast<char *>(alloca(name_maxncpy));
char *left = static_cast<char *>(alloca(name_maxncpy));
int number;
size_t len = BLI_string_split_name_number(name, delim, left, &number);
do {
const size_t numlen = SNPRINTF(numstr, "%c%03d", delim, ++number);
/* highly unlikely the string only has enough room for the number
* but support anyway */
if (UNLIKELY((len == 0) || (numlen + 1 >= name_maxncpy))) {
/* number is know not to be utf-8 */
BLI_strncpy(tempname, numstr, name_maxncpy);
}
else {
char *tempname_buf;
tempname_buf = tempname + BLI_strncpy_utf8_rlen(tempname, left, name_maxncpy - numlen);
memcpy(tempname_buf, numstr, numlen + 1);
}
} while (unique_check(tempname));
BLI_strncpy(name, tempname, name_maxncpy);
}
}
std::string BLI_uniquename_cb(blender::FunctionRef<bool(blender::StringRef)> unique_check,
const char delim,
const blender::StringRef name)
{
std::string new_name = name;
if (!unique_check(new_name)) {
return new_name;
}
int number;
blender::Array<char> left_buffer(int64_t(new_name.size()) + 1);
const size_t len = BLI_string_split_name_number(
new_name.c_str(), delim, left_buffer.data(), &number);
const std::string left = left_buffer.data();
do {
std::array<char, 16> num_str;
BLI_snprintf(num_str.data(), num_str.size(), "%c%03d", delim, ++number);
if (len == 0) {
new_name = num_str.data();
}
else {
new_name = left + num_str.data();
}
} while (unique_check(new_name));
return new_name;
}
void BLI_uniquename(const ListBase *list,
void *vlink,
const char *defname,
char delim,
int name_offset,
size_t name_maxncpy)
{
BLI_assert(name_maxncpy > 1);
/* See if we are given an empty string */
if (ELEM(nullptr, vlink)) {
return;
}
BLI_uniquename_cb(
[&](const blender::StringRefNull name) {
LISTBASE_FOREACH (Link *, link, list) {
if (link != vlink) {
const char *link_name = POINTER_OFFSET((const char *)link, name_offset);
if (name == link_name) {
return true;
}
}
}
return false;
},
defname,
delim,
static_cast<char *>(POINTER_OFFSET(vlink, name_offset)),
name_maxncpy);
}
size_t BLI_string_len_array(const char *strings[], uint strings_num)
{
size_t total_len = 0;
for (uint i = 0; i < strings_num; i++) {
total_len += strlen(strings[i]);
}
return total_len;
}
/* ------------------------------------------------------------------------- */
/** \name Join Strings
*
* For non array versions of these functions, use the macros:
* - #BLI_string_join
* - #BLI_string_joinN
* - #BLI_string_join_by_sep_charN
* - #BLI_string_join_by_sep_char_with_tableN
*
* \{ */
size_t BLI_string_join_array(char *result,
size_t result_maxncpy,
const char *strings[],
uint strings_num)
{
BLI_string_debug_size(result, result_maxncpy);
char *c = result;
char *c_end = &result[result_maxncpy - 1];
for (uint i = 0; i < strings_num; i++) {
const char *p = strings[i];
while (*p) {
if (UNLIKELY(!(c < c_end))) {
i = strings_num; /* Break outer loop. */
break;
}
*c++ = *p++;
}
}
*c = '\0';
return size_t(c - result);
}
size_t BLI_string_join_array_by_sep_char(
char *result, size_t result_maxncpy, char sep, const char *strings[], uint strings_num)
{
BLI_string_debug_size(result, result_maxncpy);
char *c = result;
char *c_end = &result[result_maxncpy - 1];
for (uint i = 0; i < strings_num; i++) {
if (i != 0) {
if (UNLIKELY(!(c < c_end))) {
break;
}
*c++ = sep;
}
const char *p = strings[i];
while (*p) {
if (UNLIKELY(!(c < c_end))) {
i = strings_num; /* Break outer loop. */
break;
}
*c++ = *p++;
}
}
*c = '\0';
return size_t(c - result);
}
char *BLI_string_join_arrayN(const char *strings[], uint strings_num)
{
const size_t result_size = BLI_string_len_array(strings, strings_num) + 1;
char *result = MEM_calloc_arrayN<char>(result_size, __func__);
char *c = result;
for (uint i = 0; i < strings_num; i++) {
const size_t string_len = strlen(strings[i]);
memcpy(c, strings[i], string_len);
c += string_len;
}
/* Only needed when `strings_num == 0`. */
*c = '\0';
BLI_assert(result + result_size == c + 1);
return result;
}
char *BLI_string_join_array_by_sep_charN(char sep, const char *strings[], uint strings_num)
{
const size_t result_size = BLI_string_len_array(strings, strings_num) +
(strings_num ? strings_num - 1 : 0) + 1;
char *result = MEM_calloc_arrayN<char>(result_size, __func__);
char *c = result;
if (strings_num != 0) {
for (uint i = 0; i < strings_num; i++) {
const size_t string_len = strlen(strings[i]);
memcpy(c, strings[i], string_len);
c += string_len;
*c = sep;
c++;
}
c--;
}
*c = '\0';
BLI_assert(result + result_size == c + 1);
return result;
}
char *BLI_string_join_array_by_sep_char_with_tableN(char sep,
char *table[],
const char *strings[],
uint strings_num)
{
size_t result_size = 0;
for (uint i = 0; i < strings_num; i++) {
result_size += strlen(strings[i]) + 1;
}
if (result_size == 0) {
result_size = 1;
}
char *result = MEM_calloc_arrayN<char>(result_size, __func__);
char *c = result;
if (strings_num != 0) {
for (uint i = 0; i < strings_num; i++) {
const size_t string_len = strlen(strings[i]);
memcpy(c, strings[i], string_len);
table[i] = c; /* <-- only difference to BLI_string_join_array_by_sep_charN. */
memcpy(c, strings[i], string_len);
c += string_len;
*c = sep;
c++;
}
c--;
}
*c = '\0';
BLI_assert(result + result_size == c + 1);
return result;
}
/** \} */