diff --git a/plugins/obs-filters/color-grade-filter.c b/plugins/obs-filters/color-grade-filter.c index ccb86532b..1f47164f3 100644 --- a/plugins/obs-filters/color-grade-filter.c +++ b/plugins/obs-filters/color-grade-filter.c @@ -1,6 +1,8 @@ #include +#include #include #include +#include /* 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); }