|
|
@@ -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);
|
|
|
}
|