obs-filters: Add Cube LUT file support

Tested Cube LUT examples from Photoshop, Adobe spec, and some homebrew.

I don't know how to use the domain fields, so they are being ignored.
This commit is contained in:
jpark37 2020-01-03 22:51:01 -08:00
parent 4d6cc442b2
commit 4ea7424ebb

View File

@ -1,6 +1,8 @@
#include <obs-module.h>
#include <graphics/half.h>
#include <graphics/image-file.h>
#include <util/dstr.h>
#include <util/platform.h>
/* clang-format off */
@ -18,8 +20,12 @@ struct lut_filter_data {
obs_source_t *context;
gs_effect_t *effect;
gs_texture_t *target;
gs_image_file_t image;
uint32_t cube_width;
void *cube_data;
char *file;
float clut_amount;
float clut_scale;
@ -32,10 +38,10 @@ static const char *color_grade_filter_get_name(void *unused)
return obs_module_text("ColorGradeFilter");
}
static gs_texture_t *make_clut_texture(const enum gs_color_format format,
const uint32_t image_width,
const uint32_t image_height,
const uint8_t *data)
static gs_texture_t *make_clut_texture_png(const enum gs_color_format format,
const uint32_t image_width,
const uint32_t image_height,
const uint8_t *data)
{
if (image_width % LUT_WIDTH != 0)
return NULL;
@ -80,12 +86,179 @@ static gs_texture_t *make_clut_texture(const enum gs_color_format format,
return texture;
}
static bool get_cube_entry(FILE *const file, float *const red,
float *const green, float *const blue)
{
bool data_found = false;
char line[256];
while (fgets(line, sizeof(line), file)) {
if (sscanf(line, "%f %f %f", red, green, blue) == 3) {
data_found = true;
break;
}
}
return data_found;
}
static void *load_1d_lut(FILE *const file, const uint32_t width, float red,
float green, float blue)
{
const uint32_t data_size =
4 * width * width * width * sizeof(struct half);
struct half *values = bmalloc(data_size);
bool data_found = true;
for (uint32_t index = 0; index < width; ++index) {
if (!data_found) {
bfree(values);
values = NULL;
break;
}
for (uint32_t z = 0; z < width; ++z) {
const uint32_t z_offset = z * width * width;
for (uint32_t y = 0; y < width; ++y) {
const uint32_t y_offset = y * width;
const uint32_t offset =
4 * (index + y_offset + z_offset);
values[offset] = half_from_float(red);
values[offset + 3] =
half_from_bits(0x3C00); // 1.0
}
}
for (uint32_t z = 0; z < width; ++z) {
const uint32_t z_offset = z * width * width;
for (uint32_t x = 0; x < width; ++x) {
const uint32_t offset =
4 * (x + (index * width) + z_offset) +
1;
values[offset] = half_from_float(green);
}
}
for (uint32_t y = 0; y < width; ++y) {
const uint32_t y_offset = y * width;
for (uint32_t x = 0; x < width; ++x) {
const uint32_t offset =
4 * (x + y_offset +
(index * width * width)) +
2;
values[offset] = half_from_float(blue);
}
}
data_found = get_cube_entry(file, &red, &green, &blue);
}
return values;
}
static void *load_3d_lut(FILE *const file, const uint32_t width, float red,
float green, float blue)
{
const uint32_t data_size =
4 * width * width * width * sizeof(struct half);
struct half *values = bmalloc(data_size);
size_t offset = 0;
bool data_found = true;
for (uint32_t z = 0; z < width; ++z) {
for (uint32_t y = 0; y < width; ++y) {
for (uint32_t x = 0; x < width; ++x) {
if (!data_found) {
bfree(values);
values = NULL;
break;
}
values[offset++] = half_from_float(red);
values[offset++] = half_from_float(green);
values[offset++] = half_from_float(blue);
values[offset++] =
half_from_bits(0x3c00); // 1.0
data_found = get_cube_entry(file, &red, &green,
&blue);
}
}
}
return values;
}
static void *load_cube_file(const char *const path, uint32_t *const width)
{
void *data = NULL;
FILE *const file = os_fopen(path, "rb");
if (file) {
float min_value[] = {0.0f, 0.0f, 0.0f};
float max_value[] = {1.0f, 1.0f, 1.0f};
float red, green, blue;
unsigned width_1d = 0;
unsigned width_3d = 0;
bool data_found = false;
char line[256];
unsigned u;
float f[3];
while (fgets(line, sizeof(line), file)) {
if (sscanf(line, "%f %f %f", &red, &green, &blue) ==
3) {
/* no more metadata */
data_found = true;
break;
} else if (sscanf(line, "DOMAIN_MIN %f %f %f", &f[0],
&f[1], &f[2]) == 3) {
min_value[0] = f[0];
min_value[1] = f[1];
min_value[2] = f[2];
} else if (sscanf(line, "DOMAIN_MAX %f %f %f", &f[0],
&f[1], &f[2]) == 3) {
max_value[0] = f[0];
max_value[1] = f[1];
max_value[2] = f[2];
} else if (sscanf(line, "LUT_1D_SIZE %u", &u) == 1) {
width_1d = u;
} else if (sscanf(line, "LUT_3D_SIZE %u", &u) == 1) {
width_3d = u;
}
}
if (data_found) {
if (width_1d > 0) {
data = load_1d_lut(file, width_1d, red, green,
blue);
if (data)
*width = width_1d;
} else if (width_3d > 0) {
data = load_3d_lut(file, width_3d, red, green,
blue);
if (data)
*width = width_3d;
}
}
fclose(file);
}
return data;
}
static void color_grade_filter_update(void *data, obs_data_t *settings)
{
struct lut_filter_data *filter = data;
const char *path = obs_data_get_string(settings, SETTING_IMAGE_PATH);
double clut_amount = obs_data_get_double(settings, SETTING_CLUT_AMOUNT);
if (path && (*path == '\0'))
path = NULL;
const double clut_amount =
obs_data_get_double(settings, SETTING_CLUT_AMOUNT);
bfree(filter->file);
if (path)
@ -93,25 +266,46 @@ static void color_grade_filter_update(void *data, obs_data_t *settings)
else
filter->file = NULL;
bfree(filter->cube_data);
filter->cube_data = NULL;
obs_enter_graphics();
gs_image_file_free(&filter->image);
gs_voltexture_destroy(filter->target);
filter->target = NULL;
obs_leave_graphics();
gs_image_file_init(&filter->image, path);
if (path) {
const char *const ext = os_get_path_extension(path);
if (ext && astrcmpi(ext, ".cube") == 0) {
filter->cube_data =
load_cube_file(path, &filter->cube_width);
} else {
gs_image_file_init(&filter->image, path);
}
}
obs_enter_graphics();
gs_voltexture_destroy(filter->target);
if (filter->image.loaded) {
filter->target = make_clut_texture(filter->image.format,
filter->image.cx,
filter->image.cy,
filter->image.texture_data);
if (path) {
if (filter->image.loaded) {
filter->target = make_clut_texture_png(
filter->image.format, filter->image.cx,
filter->image.cy, filter->image.texture_data);
filter->clut_scale =
(float)(LUT_WIDTH - 1) / (float)LUT_WIDTH;
filter->clut_offset = 0.5f / (float)LUT_WIDTH;
} else if (filter->cube_data) {
const uint32_t width = filter->cube_width;
filter->target = gs_voltexture_create(
width, width, width, GS_RGBA16F, 1,
(uint8_t **)&filter->cube_data, 0);
filter->clut_scale = (float)(width - 1) / (float)width;
filter->clut_offset = 0.5f / (float)width;
}
}
filter->clut_amount = (float)clut_amount;
filter->clut_scale = (float)(LUT_WIDTH - 1) / (float)LUT_WIDTH;
filter->clut_offset = 0.5f / (float)LUT_WIDTH;
char *effect_path = obs_module_file("color_grade_filter.effect");
gs_effect_destroy(filter->effect);
@ -135,7 +329,7 @@ static obs_properties_t *color_grade_filter_properties(void *data)
obs_properties_t *props = obs_properties_create();
struct dstr filter_str = {0};
dstr_cat(&filter_str, "(*.png)");
dstr_cat(&filter_str, "(*.cube;*.png)");
if (s && s->file && *s->file) {
dstr_copy(&path, s->file);
@ -184,6 +378,7 @@ static void color_grade_filter_destroy(void *data)
gs_image_file_free(&filter->image);
obs_leave_graphics();
bfree(filter->cube_data);
bfree(filter->file);
bfree(filter);
}