Browse Source

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.
jpark37 6 years ago
parent
commit
4ea7424ebb
1 changed files with 210 additions and 15 deletions
  1. 210 15
      plugins/obs-filters/color-grade-filter.c

+ 210 - 15
plugins/obs-filters/color-grade-filter.c

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