From 7ed31f72aa4fcc56f7b9616d12fcd9fde462ebf6 Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Fri, 3 Apr 2026 18:11:32 -0400 Subject: [PATCH] feat: add proxyWidgetSelector to subgraph blueprints --- blueprints/Color Balance.json | 2 +- blueprints/Color Curves.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blueprints/Color Balance.json b/blueprints/Color Balance.json index fe272d5dc..8dee6bcd2 100644 --- a/blueprints/Color Balance.json +++ b/blueprints/Color Balance.json @@ -1 +1 @@ -{"revision": 0, "last_node_id": 20, "last_link_id": 0, "nodes": [{"id": 20, "type": "243b9e93-7303-4500-8c70-58acb712f5bc", "pos": [3610, -2630], "size": [270, 420], "flags": {}, "order": 0, "mode": 0, "inputs": [{"label": "image", "localized_name": "images.image0", "name": "images.image0", "type": "IMAGE", "link": null}], "outputs": [{"label": "IMAGE", "localized_name": "IMAGE0", "name": "IMAGE0", "type": "IMAGE", "links": []}], "properties": {"proxyWidgets": [["4", "value"], ["5", "value"], ["6", "value"], ["7", "value"], ["8", "value"], ["9", "value"], ["10", "value"], ["11", "value"], ["12", "value"], ["13", "value"]]}, "widgets_values": [], "title": "Color Balance"}], "links": [], "version": 0.4, "definitions": {"subgraphs": [{"id": "243b9e93-7303-4500-8c70-58acb712f5bc", "version": 1, "state": {"lastGroupId": 0, "lastNodeId": 15, "lastLinkId": 39, "lastRerouteId": 0}, "revision": 0, "config": {}, "name": "Color Balance", "inputNode": {"id": -10, "bounding": [2660, -4500, 120, 60]}, "outputNode": {"id": -20, "bounding": [4270, -4500, 120, 60]}, "inputs": [{"id": "d24c0b6d-00bd-4e95-be80-8114e8376ec0", "name": "images.image0", "type": "IMAGE", "linkIds": [29], "localized_name": "images.image0", "label": "image", "pos": [2760, -4480]}], "outputs": [{"id": "92723f62-996e-496d-ad4f-81a38be4ad64", "name": "IMAGE0", "type": "IMAGE", "linkIds": [28], "localized_name": "IMAGE0", "label": "IMAGE", "pos": [4290, -4480]}], "widgets": [], "nodes": [{"id": 4, "type": "PrimitiveFloat", "pos": [3060, -4500], "size": [270, 58], "flags": {}, "order": 0, "mode": 0, "inputs": [{"label": "shadows red", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": {"name": "value"}, "link": null}], "outputs": [{"localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [30]}], "title": "Shadows Red", "properties": {"Node name for S&R": "PrimitiveFloat", "max": 100, "min": -100, "step": 1, "display": "gradientslider", "gradient_stops": [{"offset": 0, "color": [0, 255, 255]}, {"offset": 0.5, "color": [128, 128, 128]}, {"offset": 1, "color": [255, 0, 0]}]}, "widgets_values": [0]}, {"id": 5, "type": "PrimitiveFloat", "pos": [3060, -4390], "size": [270, 58], "flags": {}, "order": 1, "mode": 0, "inputs": [{"label": "shadows green", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": {"name": "value"}, "link": null}], "outputs": [{"localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [31]}], "title": "Shadows Green", "properties": {"Node name for S&R": "PrimitiveFloat", "max": 100, "min": -100, "step": 1, "display": "gradientslider", "gradient_stops": [{"offset": 0, "color": [255, 0, 255]}, {"offset": 0.5, "color": [128, 128, 128]}, {"offset": 1, "color": [0, 255, 0]}]}, "widgets_values": [0]}, {"id": 6, "type": "PrimitiveFloat", "pos": [3060, -4280], "size": [270, 58], "flags": {}, "order": 2, "mode": 0, "inputs": [{"label": "shadows blue", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": {"name": "value"}, "link": null}], "outputs": [{"localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [32]}], "title": "Shadows Blue", "properties": {"Node name for S&R": "PrimitiveFloat", "max": 100, "min": -100, "step": 1, "display": "gradientslider", "gradient_stops": [{"offset": 0, "color": [255, 255, 0]}, {"offset": 0.5, "color": [128, 128, 128]}, {"offset": 1, "color": [0, 0, 255]}]}, "widgets_values": [0]}, {"id": 7, "type": "PrimitiveFloat", "pos": [3060, -4170], "size": [270, 58], "flags": {}, "order": 3, "mode": 0, "inputs": [{"label": "midtones red", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": {"name": "value"}, "link": null}], "outputs": [{"localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [33]}], "title": "Midtones Red", "properties": {"Node name for S&R": "PrimitiveFloat", "max": 100, "min": -100, "step": 1, "display": "gradientslider", "gradient_stops": [{"offset": 0, "color": [0, 255, 255]}, {"offset": 0.5, "color": [128, 128, 128]}, {"offset": 1, "color": [255, 0, 0]}]}, "widgets_values": [0]}, {"id": 8, "type": "PrimitiveFloat", "pos": [3060, -4060], "size": [270, 58], "flags": {}, "order": 4, "mode": 0, "inputs": [{"label": "midtones green", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": {"name": "value"}, "link": null}], "outputs": [{"localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [34]}], "title": "Midtones Green", "properties": {"Node name for S&R": "PrimitiveFloat", "max": 100, "min": -100, "step": 1, "display": "gradientslider", "gradient_stops": [{"offset": 0, "color": [255, 0, 255]}, {"offset": 0.5, "color": [128, 128, 128]}, {"offset": 1, "color": [0, 255, 0]}]}, "widgets_values": [0]}, {"id": 9, "type": "PrimitiveFloat", "pos": [3060, -3950], "size": [270, 58], "flags": {}, "order": 5, "mode": 0, "inputs": [{"label": "midtones blue", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": {"name": "value"}, "link": null}], "outputs": [{"localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [35]}], "title": "Midtones Blue", "properties": {"Node name for S&R": "PrimitiveFloat", "max": 100, "min": -100, "step": 1, "display": "gradientslider", "gradient_stops": [{"offset": 0, "color": [255, 255, 0]}, {"offset": 0.5, "color": [128, 128, 128]}, {"offset": 1, "color": [0, 0, 255]}]}, "widgets_values": [0]}, {"id": 10, "type": "PrimitiveFloat", "pos": [3060, -3840], "size": [270, 58], "flags": {}, "order": 6, "mode": 0, "inputs": [{"label": "highlights red", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": {"name": "value"}, "link": null}], "outputs": [{"localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [36]}], "title": "Highlights Red", "properties": {"Node name for S&R": "PrimitiveFloat", "max": 100, "min": -100, "step": 1, "display": "gradientslider", "gradient_stops": [{"offset": 0, "color": [0, 255, 255]}, {"offset": 0.5, "color": [128, 128, 128]}, {"offset": 1, "color": [255, 0, 0]}]}, "widgets_values": [0]}, {"id": 11, "type": "PrimitiveFloat", "pos": [3060, -3730], "size": [270, 58], "flags": {}, "order": 7, "mode": 0, "inputs": [{"label": "highlights green", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": {"name": "value"}, "link": null}], "outputs": [{"localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [37]}], "title": "Highlights Green", "properties": {"Node name for S&R": "PrimitiveFloat", "max": 100, "min": -100, "step": 1, "display": "gradientslider", "gradient_stops": [{"offset": 0, "color": [255, 0, 255]}, {"offset": 0.5, "color": [128, 128, 128]}, {"offset": 1, "color": [0, 255, 0]}]}, "widgets_values": [0]}, {"id": 12, "type": "PrimitiveFloat", "pos": [3060, -3620], "size": [270, 58], "flags": {}, "order": 8, "mode": 0, "inputs": [{"label": "highlights blue", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": {"name": "value"}, "link": null}], "outputs": [{"localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [38]}], "title": "Highlights Blue", "properties": {"Node name for S&R": "PrimitiveFloat", "max": 100, "min": -100, "step": 1, "display": "gradientslider", "gradient_stops": [{"offset": 0, "color": [255, 255, 0]}, {"offset": 0.5, "color": [128, 128, 128]}, {"offset": 1, "color": [0, 0, 255]}]}, "widgets_values": [0]}, {"id": 13, "type": "PrimitiveBoolean", "pos": [3060, -3510], "size": [270, 58], "flags": {}, "order": 9, "mode": 0, "inputs": [{"label": "preserve luminosity", "localized_name": "value", "name": "value", "type": "BOOLEAN", "widget": {"name": "value"}, "link": null}], "outputs": [{"localized_name": "BOOLEAN", "name": "BOOLEAN", "type": "BOOLEAN", "links": [39]}], "title": "Preserve Luminosity", "properties": {"Node name for S&R": "PrimitiveBoolean"}, "widgets_values": [true]}, {"id": 15, "type": "GLSLShader", "pos": [3590, -4500], "size": [420, 500], "flags": {}, "order": 10, "mode": 0, "inputs": [{"label": "image0", "localized_name": "images.image0", "name": "images.image0", "type": "IMAGE", "link": 29}, {"label": "image1", "localized_name": "images.image1", "name": "images.image1", "shape": 7, "type": "IMAGE", "link": null}, {"label": "u_float0", "localized_name": "floats.u_float0", "name": "floats.u_float0", "shape": 7, "type": "FLOAT", "link": 30}, {"label": "u_float1", "localized_name": "floats.u_float1", "name": "floats.u_float1", "shape": 7, "type": "FLOAT", "link": 31}, {"label": "u_float2", "localized_name": "floats.u_float2", "name": "floats.u_float2", "shape": 7, "type": "FLOAT", "link": 32}, {"label": "u_float3", "localized_name": "floats.u_float3", "name": "floats.u_float3", "shape": 7, "type": "FLOAT", "link": 33}, {"label": "u_float4", "localized_name": "floats.u_float4", "name": "floats.u_float4", "shape": 7, "type": "FLOAT", "link": 34}, {"label": "u_float5", "localized_name": "floats.u_float5", "name": "floats.u_float5", "shape": 7, "type": "FLOAT", "link": 35}, {"label": "u_float6", "localized_name": "floats.u_float6", "name": "floats.u_float6", "shape": 7, "type": "FLOAT", "link": 36}, {"label": "u_float7", "localized_name": "floats.u_float7", "name": "floats.u_float7", "shape": 7, "type": "FLOAT", "link": 37}, {"label": "u_float8", "localized_name": "floats.u_float8", "name": "floats.u_float8", "shape": 7, "type": "FLOAT", "link": 38}, {"label": "u_bool0", "localized_name": "bools.u_bool0", "name": "bools.u_bool0", "shape": 7, "type": "BOOLEAN", "link": 39}, {"localized_name": "fragment_shader", "name": "fragment_shader", "type": "STRING", "widget": {"name": "fragment_shader"}, "link": null}, {"localized_name": "size_mode", "name": "size_mode", "type": "COMFY_DYNAMICCOMBO_V3", "widget": {"name": "size_mode"}, "link": null}], "outputs": [{"localized_name": "IMAGE0", "name": "IMAGE0", "type": "IMAGE", "links": [28]}, {"localized_name": "IMAGE1", "name": "IMAGE1", "type": "IMAGE", "links": null}, {"localized_name": "IMAGE2", "name": "IMAGE2", "type": "IMAGE", "links": null}, {"localized_name": "IMAGE3", "name": "IMAGE3", "type": "IMAGE", "links": null}], "properties": {"Node name for S&R": "GLSLShader"}, "widgets_values": ["#version 300 es\nprecision highp float;\n\nuniform sampler2D u_image0;\nuniform float u_float0; // shadows red (-100 to 100)\nuniform float u_float1; // shadows green (-100 to 100)\nuniform float u_float2; // shadows blue (-100 to 100)\nuniform float u_float3; // midtones red (-100 to 100)\nuniform float u_float4; // midtones green (-100 to 100)\nuniform float u_float5; // midtones blue (-100 to 100)\nuniform float u_float6; // highlights red (-100 to 100)\nuniform float u_float7; // highlights green (-100 to 100)\nuniform float u_float8; // highlights blue (-100 to 100)\nuniform bool u_bool0; // preserve luminosity\n\nin vec2 v_texCoord;\nout vec4 fragColor;\n\nvec3 rgb2hsl(vec3 c) {\n float maxC = max(c.r, max(c.g, c.b));\n float minC = min(c.r, min(c.g, c.b));\n float l = (maxC + minC) * 0.5;\n if (maxC == minC) return vec3(0.0, 0.0, l);\n float d = maxC - minC;\n float s = l > 0.5 ? d / (2.0 - maxC - minC) : d / (maxC + minC);\n float h;\n if (maxC == c.r) {\n h = (c.g - c.b) / d + (c.g < c.b ? 6.0 : 0.0);\n } else if (maxC == c.g) {\n h = (c.b - c.r) / d + 2.0;\n } else {\n h = (c.r - c.g) / d + 4.0;\n }\n h /= 6.0;\n return vec3(h, s, l);\n}\n\nfloat hue2rgb(float p, float q, float t) {\n if (t < 0.0) t += 1.0;\n if (t > 1.0) t -= 1.0;\n if (t < 1.0 / 6.0) return p + (q - p) * 6.0 * t;\n if (t < 1.0 / 2.0) return q;\n if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6.0;\n return p;\n}\n\nvec3 hsl2rgb(vec3 hsl) {\n float h = hsl.x, s = hsl.y, l = hsl.z;\n if (s == 0.0) return vec3(l);\n float q = l < 0.5 ? l * (1.0 + s) : l + s - l * s;\n float p = 2.0 * l - q;\n return vec3(\n hue2rgb(p, q, h + 1.0 / 3.0),\n hue2rgb(p, q, h),\n hue2rgb(p, q, h - 1.0 / 3.0)\n );\n}\n\nvoid main() {\n vec4 tex = texture(u_image0, v_texCoord);\n vec3 color = tex.rgb;\n\n // Build shadows/midtones/highlights vectors (scale -100..100 to -1..1)\n vec3 shadows = vec3(u_float0, u_float1, u_float2) * 0.01;\n vec3 midtones = vec3(u_float3, u_float4, u_float5) * 0.01;\n vec3 highlights = vec3(u_float6, u_float7, u_float8) * 0.01;\n\n // GIMP: HSL lightness for weight calculation\n float maxC = max(color.r, max(color.g, color.b));\n float minC = min(color.r, min(color.g, color.b));\n float lightness = (maxC + minC) * 0.5;\n\n // GIMP weight curves: linear ramps with constants a=0.25, b=0.333, scale=0.7\n const float a = 0.25;\n const float b = 0.333;\n const float scale = 0.7;\n\n float sw = clamp((lightness - b) / -a + 0.5, 0.0, 1.0) * scale;\n float mw = clamp((lightness - b) / a + 0.5, 0.0, 1.0) *\n clamp((lightness + b - 1.0) / -a + 0.5, 0.0, 1.0) * scale;\n float hw = clamp((lightness + b - 1.0) / a + 0.5, 0.0, 1.0) * scale;\n\n color += sw * shadows + mw * midtones + hw * highlights;\n\n if (u_bool0) {\n vec3 hsl = rgb2hsl(clamp(color, 0.0, 1.0));\n hsl.z = lightness;\n color = hsl2rgb(hsl);\n }\n\n fragColor = vec4(clamp(color, 0.0, 1.0), tex.a);\n}", "from_input"]}], "groups": [], "links": [{"id": 29, "origin_id": -10, "origin_slot": 0, "target_id": 15, "target_slot": 0, "type": "IMAGE"}, {"id": 28, "origin_id": 15, "origin_slot": 0, "target_id": -20, "target_slot": 0, "type": "IMAGE"}, {"id": 30, "origin_id": 4, "origin_slot": 0, "target_id": 15, "target_slot": 2, "type": "FLOAT"}, {"id": 31, "origin_id": 5, "origin_slot": 0, "target_id": 15, "target_slot": 3, "type": "FLOAT"}, {"id": 32, "origin_id": 6, "origin_slot": 0, "target_id": 15, "target_slot": 4, "type": "FLOAT"}, {"id": 33, "origin_id": 7, "origin_slot": 0, "target_id": 15, "target_slot": 5, "type": "FLOAT"}, {"id": 34, "origin_id": 8, "origin_slot": 0, "target_id": 15, "target_slot": 6, "type": "FLOAT"}, {"id": 35, "origin_id": 9, "origin_slot": 0, "target_id": 15, "target_slot": 7, "type": "FLOAT"}, {"id": 36, "origin_id": 10, "origin_slot": 0, "target_id": 15, "target_slot": 8, "type": "FLOAT"}, {"id": 37, "origin_id": 11, "origin_slot": 0, "target_id": 15, "target_slot": 9, "type": "FLOAT"}, {"id": 38, "origin_id": 12, "origin_slot": 0, "target_id": 15, "target_slot": 10, "type": "FLOAT"}, {"id": 39, "origin_id": 13, "origin_slot": 0, "target_id": 15, "target_slot": 11, "type": "BOOLEAN"}], "extra": {"workflowRendererVersion": "LG"}, "category": "Image Tools/Color adjust"}]}} \ No newline at end of file +{"revision": 0,"last_node_id": 20,"last_link_id": 0,"nodes": [{"id": 20,"type": "243b9e93-7303-4500-8c70-58acb712f5bc","pos": [3610,-2630],"size": [270,420],"flags": {},"order": 0,"mode": 0,"inputs": [{"label": "image","localized_name": "images.image0","name": "images.image0","type": "IMAGE","link": null}],"outputs": [{"label": "IMAGE","localized_name": "IMAGE0","name": "IMAGE0","type": "IMAGE","links": []}],"properties": {"proxyWidgets": [["4","value"],["5","value"],["6","value"],["7","value"],["8","value"],["9","value"],["10","value"],["11","value"],["12","value"],["13","value"]],"proxyWidgetSelector": {"name": "Tone","selected": "Shadows","options": [{"label": "Shadows","widgets": [["4","value"],["5","value"],["6","value"]]},{"label": "Midtones","widgets": [["7","value"],["8","value"],["9","value"]]},{"label": "Highlights","widgets": [["10","value"],["11","value"],["12","value"]]}]}},"widgets_values": [],"title": "Color Balance"}],"links": [],"version": 0.4,"definitions": {"subgraphs": [{"id": "243b9e93-7303-4500-8c70-58acb712f5bc","version": 1,"state": {"lastGroupId": 0,"lastNodeId": 15,"lastLinkId": 39,"lastRerouteId": 0},"revision": 0,"config": {},"name": "Color Balance","inputNode": {"id": -10,"bounding": [2660,-4500,120,60]},"outputNode": {"id": -20,"bounding": [4270,-4500,120,60]},"inputs": [{"id": "d24c0b6d-00bd-4e95-be80-8114e8376ec0","name": "images.image0","type": "IMAGE","linkIds": [29],"localized_name": "images.image0","label": "image","pos": [2760,-4480]}],"outputs": [{"id": "92723f62-996e-496d-ad4f-81a38be4ad64","name": "IMAGE0","type": "IMAGE","linkIds": [28],"localized_name": "IMAGE0","label": "IMAGE","pos": [4290,-4480]}],"widgets": [],"nodes": [{"id": 4,"type": "PrimitiveFloat","pos": [3060,-4500],"size": [270,58],"flags": {},"order": 0,"mode": 0,"inputs": [{"label": "shadows red","localized_name": "value","name": "value","type": "FLOAT","widget": {"name": "value"},"link": null}],"outputs": [{"localized_name": "FLOAT","name": "FLOAT","type": "FLOAT","links": [30]}],"title": "Shadows Red","properties": {"Node name for S&R": "PrimitiveFloat","max": 100,"min": -100,"step": 1,"display": "gradientslider","gradient_stops": [{"offset": 0,"color": [0,255,255]},{"offset": 0.5,"color": [128,128,128]},{"offset": 1,"color": [255,0,0]}]},"widgets_values": [0]},{"id": 5,"type": "PrimitiveFloat","pos": [3060,-4390],"size": [270,58],"flags": {},"order": 1,"mode": 0,"inputs": [{"label": "shadows green","localized_name": "value","name": "value","type": "FLOAT","widget": {"name": "value"},"link": null}],"outputs": [{"localized_name": "FLOAT","name": "FLOAT","type": "FLOAT","links": [31]}],"title": "Shadows Green","properties": {"Node name for S&R": "PrimitiveFloat","max": 100,"min": -100,"step": 1,"display": "gradientslider","gradient_stops": [{"offset": 0,"color": [255,0,255]},{"offset": 0.5,"color": [128,128,128]},{"offset": 1,"color": [0,255,0]}]},"widgets_values": [0]},{"id": 6,"type": "PrimitiveFloat","pos": [3060,-4280],"size": [270,58],"flags": {},"order": 2,"mode": 0,"inputs": [{"label": "shadows blue","localized_name": "value","name": "value","type": "FLOAT","widget": {"name": "value"},"link": null}],"outputs": [{"localized_name": "FLOAT","name": "FLOAT","type": "FLOAT","links": [32]}],"title": "Shadows Blue","properties": {"Node name for S&R": "PrimitiveFloat","max": 100,"min": -100,"step": 1,"display": "gradientslider","gradient_stops": [{"offset": 0,"color": [255,255,0]},{"offset": 0.5,"color": [128,128,128]},{"offset": 1,"color": [0,0,255]}]},"widgets_values": [0]},{"id": 7,"type": "PrimitiveFloat","pos": [3060,-4170],"size": [270,58],"flags": {},"order": 3,"mode": 0,"inputs": [{"label": "midtones red","localized_name": "value","name": "value","type": "FLOAT","widget": {"name": "value"},"link": null}],"outputs": [{"localized_name": "FLOAT","name": "FLOAT","type": "FLOAT","links": [33]}],"title": "Midtones Red","properties": {"Node name for S&R": "PrimitiveFloat","max": 100,"min": -100,"step": 1,"display": "gradientslider","gradient_stops": [{"offset": 0,"color": [0,255,255]},{"offset": 0.5,"color": [128,128,128]},{"offset": 1,"color": [255,0,0]}]},"widgets_values": [0]},{"id": 8,"type": "PrimitiveFloat","pos": [3060,-4060],"size": [270,58],"flags": {},"order": 4,"mode": 0,"inputs": [{"label": "midtones green","localized_name": "value","name": "value","type": "FLOAT","widget": {"name": "value"},"link": null}],"outputs": [{"localized_name": "FLOAT","name": "FLOAT","type": "FLOAT","links": [34]}],"title": "Midtones Green","properties": {"Node name for S&R": "PrimitiveFloat","max": 100,"min": -100,"step": 1,"display": "gradientslider","gradient_stops": [{"offset": 0,"color": [255,0,255]},{"offset": 0.5,"color": [128,128,128]},{"offset": 1,"color": [0,255,0]}]},"widgets_values": [0]},{"id": 9,"type": "PrimitiveFloat","pos": [3060,-3950],"size": [270,58],"flags": {},"order": 5,"mode": 0,"inputs": [{"label": "midtones blue","localized_name": "value","name": "value","type": "FLOAT","widget": {"name": "value"},"link": null}],"outputs": [{"localized_name": "FLOAT","name": "FLOAT","type": "FLOAT","links": [35]}],"title": "Midtones Blue","properties": {"Node name for S&R": "PrimitiveFloat","max": 100,"min": -100,"step": 1,"display": "gradientslider","gradient_stops": [{"offset": 0,"color": [255,255,0]},{"offset": 0.5,"color": [128,128,128]},{"offset": 1,"color": [0,0,255]}]},"widgets_values": [0]},{"id": 10,"type": "PrimitiveFloat","pos": [3060,-3840],"size": [270,58],"flags": {},"order": 6,"mode": 0,"inputs": [{"label": "highlights red","localized_name": "value","name": "value","type": "FLOAT","widget": {"name": "value"},"link": null}],"outputs": [{"localized_name": "FLOAT","name": "FLOAT","type": "FLOAT","links": [36]}],"title": "Highlights Red","properties": {"Node name for S&R": "PrimitiveFloat","max": 100,"min": -100,"step": 1,"display": "gradientslider","gradient_stops": [{"offset": 0,"color": [0,255,255]},{"offset": 0.5,"color": [128,128,128]},{"offset": 1,"color": [255,0,0]}]},"widgets_values": [0]},{"id": 11,"type": "PrimitiveFloat","pos": [3060,-3730],"size": [270,58],"flags": {},"order": 7,"mode": 0,"inputs": [{"label": "highlights green","localized_name": "value","name": "value","type": "FLOAT","widget": {"name": "value"},"link": null}],"outputs": [{"localized_name": "FLOAT","name": "FLOAT","type": "FLOAT","links": [37]}],"title": "Highlights Green","properties": {"Node name for S&R": "PrimitiveFloat","max": 100,"min": -100,"step": 1,"display": "gradientslider","gradient_stops": [{"offset": 0,"color": [255,0,255]},{"offset": 0.5,"color": [128,128,128]},{"offset": 1,"color": [0,255,0]}]},"widgets_values": [0]},{"id": 12,"type": "PrimitiveFloat","pos": [3060,-3620],"size": [270,58],"flags": {},"order": 8,"mode": 0,"inputs": [{"label": "highlights blue","localized_name": "value","name": "value","type": "FLOAT","widget": {"name": "value"},"link": null}],"outputs": [{"localized_name": "FLOAT","name": "FLOAT","type": "FLOAT","links": [38]}],"title": "Highlights Blue","properties": {"Node name for S&R": "PrimitiveFloat","max": 100,"min": -100,"step": 1,"display": "gradientslider","gradient_stops": [{"offset": 0,"color": [255,255,0]},{"offset": 0.5,"color": [128,128,128]},{"offset": 1,"color": [0,0,255]}]},"widgets_values": [0]},{"id": 13,"type": "PrimitiveBoolean","pos": [3060,-3510],"size": [270,58],"flags": {},"order": 9,"mode": 0,"inputs": [{"label": "preserve luminosity","localized_name": "value","name": "value","type": "BOOLEAN","widget": {"name": "value"},"link": null}],"outputs": [{"localized_name": "BOOLEAN","name": "BOOLEAN","type": "BOOLEAN","links": [39]}],"title": "Preserve Luminosity","properties": {"Node name for S&R": "PrimitiveBoolean"},"widgets_values": [true]},{"id": 15,"type": "GLSLShader","pos": [3590,-4500],"size": [420,500],"flags": {},"order": 10,"mode": 0,"inputs": [{"label": "image0","localized_name": "images.image0","name": "images.image0","type": "IMAGE","link": 29},{"label": "image1","localized_name": "images.image1","name": "images.image1","shape": 7,"type": "IMAGE","link": null},{"label": "u_float0","localized_name": "floats.u_float0","name": "floats.u_float0","shape": 7,"type": "FLOAT","link": 30},{"label": "u_float1","localized_name": "floats.u_float1","name": "floats.u_float1","shape": 7,"type": "FLOAT","link": 31},{"label": "u_float2","localized_name": "floats.u_float2","name": "floats.u_float2","shape": 7,"type": "FLOAT","link": 32},{"label": "u_float3","localized_name": "floats.u_float3","name": "floats.u_float3","shape": 7,"type": "FLOAT","link": 33},{"label": "u_float4","localized_name": "floats.u_float4","name": "floats.u_float4","shape": 7,"type": "FLOAT","link": 34},{"label": "u_float5","localized_name": "floats.u_float5","name": "floats.u_float5","shape": 7,"type": "FLOAT","link": 35},{"label": "u_float6","localized_name": "floats.u_float6","name": "floats.u_float6","shape": 7,"type": "FLOAT","link": 36},{"label": "u_float7","localized_name": "floats.u_float7","name": "floats.u_float7","shape": 7,"type": "FLOAT","link": 37},{"label": "u_float8","localized_name": "floats.u_float8","name": "floats.u_float8","shape": 7,"type": "FLOAT","link": 38},{"label": "u_bool0","localized_name": "bools.u_bool0","name": "bools.u_bool0","shape": 7,"type": "BOOLEAN","link": 39},{"localized_name": "fragment_shader","name": "fragment_shader","type": "STRING","widget": {"name": "fragment_shader"},"link": null},{"localized_name": "size_mode","name": "size_mode","type": "COMFY_DYNAMICCOMBO_V3","widget": {"name": "size_mode"},"link": null}],"outputs": [{"localized_name": "IMAGE0","name": "IMAGE0","type": "IMAGE","links": [28]},{"localized_name": "IMAGE1","name": "IMAGE1","type": "IMAGE","links": null},{"localized_name": "IMAGE2","name": "IMAGE2","type": "IMAGE","links": null},{"localized_name": "IMAGE3","name": "IMAGE3","type": "IMAGE","links": null}],"properties": {"Node name for S&R": "GLSLShader"},"widgets_values": ["#version 300 es\nprecision highp float;\n\nuniform sampler2D u_image0;\nuniform float u_float0; // shadows red (-100 to 100)\nuniform float u_float1; // shadows green (-100 to 100)\nuniform float u_float2; // shadows blue (-100 to 100)\nuniform float u_float3; // midtones red (-100 to 100)\nuniform float u_float4; // midtones green (-100 to 100)\nuniform float u_float5; // midtones blue (-100 to 100)\nuniform float u_float6; // highlights red (-100 to 100)\nuniform float u_float7; // highlights green (-100 to 100)\nuniform float u_float8; // highlights blue (-100 to 100)\nuniform bool u_bool0; // preserve luminosity\n\nin vec2 v_texCoord;\nout vec4 fragColor;\n\nvec3 rgb2hsl(vec3 c) {\n float maxC = max(c.r, max(c.g, c.b));\n float minC = min(c.r, min(c.g, c.b));\n float l = (maxC + minC) * 0.5;\n if (maxC == minC) return vec3(0.0, 0.0, l);\n float d = maxC - minC;\n float s = l > 0.5 ? d / (2.0 - maxC - minC) : d / (maxC + minC);\n float h;\n if (maxC == c.r) {\n h = (c.g - c.b) / d + (c.g < c.b ? 6.0 : 0.0);\n } else if (maxC == c.g) {\n h = (c.b - c.r) / d + 2.0;\n } else {\n h = (c.r - c.g) / d + 4.0;\n }\n h /= 6.0;\n return vec3(h, s, l);\n}\n\nfloat hue2rgb(float p, float q, float t) {\n if (t < 0.0) t += 1.0;\n if (t > 1.0) t -= 1.0;\n if (t < 1.0 / 6.0) return p + (q - p) * 6.0 * t;\n if (t < 1.0 / 2.0) return q;\n if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6.0;\n return p;\n}\n\nvec3 hsl2rgb(vec3 hsl) {\n float h = hsl.x, s = hsl.y, l = hsl.z;\n if (s == 0.0) return vec3(l);\n float q = l < 0.5 ? l * (1.0 + s) : l + s - l * s;\n float p = 2.0 * l - q;\n return vec3(\n hue2rgb(p, q, h + 1.0 / 3.0),\n hue2rgb(p, q, h),\n hue2rgb(p, q, h - 1.0 / 3.0)\n );\n}\n\nvoid main() {\n vec4 tex = texture(u_image0, v_texCoord);\n vec3 color = tex.rgb;\n\n // Build shadows/midtones/highlights vectors (scale -100..100 to -1..1)\n vec3 shadows = vec3(u_float0, u_float1, u_float2) * 0.01;\n vec3 midtones = vec3(u_float3, u_float4, u_float5) * 0.01;\n vec3 highlights = vec3(u_float6, u_float7, u_float8) * 0.01;\n\n // GIMP: HSL lightness for weight calculation\n float maxC = max(color.r, max(color.g, color.b));\n float minC = min(color.r, min(color.g, color.b));\n float lightness = (maxC + minC) * 0.5;\n\n // GIMP weight curves: linear ramps with constants a=0.25, b=0.333, scale=0.7\n const float a = 0.25;\n const float b = 0.333;\n const float scale = 0.7;\n\n float sw = clamp((lightness - b) / -a + 0.5, 0.0, 1.0) * scale;\n float mw = clamp((lightness - b) / a + 0.5, 0.0, 1.0) *\n clamp((lightness + b - 1.0) / -a + 0.5, 0.0, 1.0) * scale;\n float hw = clamp((lightness + b - 1.0) / a + 0.5, 0.0, 1.0) * scale;\n\n color += sw * shadows + mw * midtones + hw * highlights;\n\n if (u_bool0) {\n vec3 hsl = rgb2hsl(clamp(color, 0.0, 1.0));\n hsl.z = lightness;\n color = hsl2rgb(hsl);\n }\n\n fragColor = vec4(clamp(color, 0.0, 1.0), tex.a);\n}","from_input"]}],"groups": [],"links": [{"id": 29,"origin_id": -10,"origin_slot": 0,"target_id": 15,"target_slot": 0,"type": "IMAGE"},{"id": 28,"origin_id": 15,"origin_slot": 0,"target_id": -20,"target_slot": 0,"type": "IMAGE"},{"id": 30,"origin_id": 4,"origin_slot": 0,"target_id": 15,"target_slot": 2,"type": "FLOAT"},{"id": 31,"origin_id": 5,"origin_slot": 0,"target_id": 15,"target_slot": 3,"type": "FLOAT"},{"id": 32,"origin_id": 6,"origin_slot": 0,"target_id": 15,"target_slot": 4,"type": "FLOAT"},{"id": 33,"origin_id": 7,"origin_slot": 0,"target_id": 15,"target_slot": 5,"type": "FLOAT"},{"id": 34,"origin_id": 8,"origin_slot": 0,"target_id": 15,"target_slot": 6,"type": "FLOAT"},{"id": 35,"origin_id": 9,"origin_slot": 0,"target_id": 15,"target_slot": 7,"type": "FLOAT"},{"id": 36,"origin_id": 10,"origin_slot": 0,"target_id": 15,"target_slot": 8,"type": "FLOAT"},{"id": 37,"origin_id": 11,"origin_slot": 0,"target_id": 15,"target_slot": 9,"type": "FLOAT"},{"id": 38,"origin_id": 12,"origin_slot": 0,"target_id": 15,"target_slot": 10,"type": "FLOAT"},{"id": 39,"origin_id": 13,"origin_slot": 0,"target_id": 15,"target_slot": 11,"type": "BOOLEAN"}],"extra": {"workflowRendererVersion": "LG"},"category": "Image Tools/Color adjust"}]}} \ No newline at end of file diff --git a/blueprints/Color Curves.json b/blueprints/Color Curves.json index 933c53978..683605e44 100644 --- a/blueprints/Color Curves.json +++ b/blueprints/Color Curves.json @@ -1 +1 @@ -{"revision": 0,"last_node_id": 10,"last_link_id": 0,"nodes": [{"id": 10,"type": "d5c462c8-1372-4af8-84f2-547c83470d04","pos": [3610,-2630],"size": [270,420],"flags": {},"order": 0,"mode": 0,"inputs": [{"label": "image","localized_name": "images.image0","name": "images.image0","type": "IMAGE","link": null}],"outputs": [{"label": "IMAGE","localized_name": "IMAGE0","name": "IMAGE0","type": "IMAGE","links": []}],"properties": {"proxyWidgets": [["4","curve"],["5","curve"],["6","curve"],["7","curve"]]},"widgets_values": [],"title": "Color Curves"}],"links": [],"version": 0.4,"definitions": {"subgraphs": [{"id": "d5c462c8-1372-4af8-84f2-547c83470d04","version": 1,"state": {"lastGroupId": 0,"lastNodeId": 8,"lastLinkId": 33,"lastRerouteId": 0},"revision": 0,"config": {},"name": "Color Curves","inputNode": {"id": -10,"bounding": [2660,-4500,120,60]},"outputNode": {"id": -20,"bounding": [4270,-4500,120,60]},"inputs": [{"id": "abc345b7-f55e-4f32-a11d-3aa4c2b0936b","name": "images.image0","type": "IMAGE","linkIds": [29],"localized_name": "images.image0","label": "image","pos": [2760,-4480]}],"outputs": [{"id": "eb0ec079-46da-4408-8263-9ef85569d33d","name": "IMAGE0","type": "IMAGE","linkIds": [28],"localized_name": "IMAGE0","label": "IMAGE","pos": [4290,-4480]}],"widgets": [],"nodes": [{"id": 4,"type": "CurveEditor","pos": [3060,-4500],"size": [270,200],"flags": {},"order": 0,"mode": 0,"inputs": [{"label": "curve","localized_name": "curve","name": "curve","type": "CURVE","widget": {"name": "curve"},"link": null},{"label": "histogram","localized_name": "histogram","name": "histogram","type": "HISTOGRAM","shape": 7,"link": null}],"outputs": [{"localized_name": "CURVE","name": "CURVE","type": "CURVE","links": [30]}],"title": "RGB Master","properties": {"Node name for S&R": "CurveEditor"},"widgets_values": []},{"id": 5,"type": "CurveEditor","pos": [3060,-4250],"size": [270,200],"flags": {},"order": 1,"mode": 0,"inputs": [{"label": "curve","localized_name": "curve","name": "curve","type": "CURVE","widget": {"name": "curve"},"link": null},{"label": "histogram","localized_name": "histogram","name": "histogram","type": "HISTOGRAM","shape": 7,"link": null}],"outputs": [{"localized_name": "CURVE","name": "CURVE","type": "CURVE","links": [31]}],"title": "Red","properties": {"Node name for S&R": "CurveEditor"},"widgets_values": []},{"id": 6,"type": "CurveEditor","pos": [3060,-4000],"size": [270,200],"flags": {},"order": 2,"mode": 0,"inputs": [{"label": "curve","localized_name": "curve","name": "curve","type": "CURVE","widget": {"name": "curve"},"link": null},{"label": "histogram","localized_name": "histogram","name": "histogram","type": "HISTOGRAM","shape": 7,"link": null}],"outputs": [{"localized_name": "CURVE","name": "CURVE","type": "CURVE","links": [32]}],"title": "Green","properties": {"Node name for S&R": "CurveEditor"},"widgets_values": []},{"id": 7,"type": "CurveEditor","pos": [3060,-3750],"size": [270,200],"flags": {},"order": 3,"mode": 0,"inputs": [{"label": "curve","localized_name": "curve","name": "curve","type": "CURVE","widget": {"name": "curve"},"link": null},{"label": "histogram","localized_name": "histogram","name": "histogram","type": "HISTOGRAM","shape": 7,"link": null}],"outputs": [{"localized_name": "CURVE","name": "CURVE","type": "CURVE","links": [33]}],"title": "Blue","properties": {"Node name for S&R": "CurveEditor"},"widgets_values": []},{"id": 8,"type": "GLSLShader","pos": [3590,-4500],"size": [420,500],"flags": {},"order": 4,"mode": 0,"inputs": [{"label": "image0","localized_name": "images.image0","name": "images.image0","type": "IMAGE","link": 29},{"label": "image1","localized_name": "images.image1","name": "images.image1","shape": 7,"type": "IMAGE","link": null},{"label": "u_curve0","localized_name": "curves.u_curve0","name": "curves.u_curve0","shape": 7,"type": "CURVE","link": 30},{"label": "u_curve1","localized_name": "curves.u_curve1","name": "curves.u_curve1","shape": 7,"type": "CURVE","link": 31},{"label": "u_curve2","localized_name": "curves.u_curve2","name": "curves.u_curve2","shape": 7,"type": "CURVE","link": 32},{"label": "u_curve3","localized_name": "curves.u_curve3","name": "curves.u_curve3","shape": 7,"type": "CURVE","link": 33},{"localized_name": "fragment_shader","name": "fragment_shader","type": "STRING","widget": {"name": "fragment_shader"},"link": null},{"localized_name": "size_mode","name": "size_mode","type": "COMFY_DYNAMICCOMBO_V3","widget": {"name": "size_mode"},"link": null}],"outputs": [{"localized_name": "IMAGE0","name": "IMAGE0","type": "IMAGE","links": [28]},{"localized_name": "IMAGE1","name": "IMAGE1","type": "IMAGE","links": null},{"localized_name": "IMAGE2","name": "IMAGE2","type": "IMAGE","links": null},{"localized_name": "IMAGE3","name": "IMAGE3","type": "IMAGE","links": null}],"properties": {"Node name for S&R": "GLSLShader"},"widgets_values": ["#version 300 es\nprecision highp float;\n\nuniform sampler2D u_image0;\nuniform sampler2D u_curve0; // RGB master curve (256x1 LUT)\nuniform sampler2D u_curve1; // Red channel curve\nuniform sampler2D u_curve2; // Green channel curve\nuniform sampler2D u_curve3; // Blue channel curve\n\nin vec2 v_texCoord;\nlayout(location = 0) out vec4 fragColor0;\n\n// GIMP-compatible curve lookup with manual linear interpolation.\n// Matches gimp_curve_map_value_inline() from gimpcurve-map.c:\n// index = value * (n_samples - 1)\n// f = fract(index)\n// result = (1-f) * samples[floor] + f * samples[ceil]\n//\n// Uses texelFetch (NEAREST) to avoid GPU half-texel offset issues\n// that occur with texture() + GL_LINEAR on small 256x1 LUTs.\nfloat applyCurve(sampler2D curve, float value) {\n value = clamp(value, 0.0, 1.0);\n\n float pos = value * 255.0;\n int lo = int(floor(pos));\n int hi = min(lo + 1, 255);\n float f = pos - float(lo);\n\n float a = texelFetch(curve, ivec2(lo, 0), 0).r;\n float b = texelFetch(curve, ivec2(hi, 0), 0).r;\n\n return a + f * (b - a);\n}\n\nvoid main() {\n vec4 color = texture(u_image0, v_texCoord);\n\n // GIMP order: per-channel curves first, then RGB master curve.\n // See gimp_curve_map_pixels() default case in gimpcurve-map.c:\n // dest = colors_curve( channel_curve( src ) )\n float tmp_r = applyCurve(u_curve1, color.r);\n float tmp_g = applyCurve(u_curve2, color.g);\n float tmp_b = applyCurve(u_curve3, color.b);\n color.r = applyCurve(u_curve0, tmp_r);\n color.g = applyCurve(u_curve0, tmp_g);\n color.b = applyCurve(u_curve0, tmp_b);\n\n fragColor0 = vec4(color.rgb, color.a);\n}\n","from_input"]}],"groups": [],"links": [{"id": 29,"origin_id": -10,"origin_slot": 0,"target_id": 8,"target_slot": 0,"type": "IMAGE"},{"id": 28,"origin_id": 8,"origin_slot": 0,"target_id": -20,"target_slot": 0,"type": "IMAGE"},{"id": 30,"origin_id": 4,"origin_slot": 0,"target_id": 8,"target_slot": 2,"type": "CURVE"},{"id": 31,"origin_id": 5,"origin_slot": 0,"target_id": 8,"target_slot": 3,"type": "CURVE"},{"id": 32,"origin_id": 6,"origin_slot": 0,"target_id": 8,"target_slot": 4,"type": "CURVE"},{"id": 33,"origin_id": 7,"origin_slot": 0,"target_id": 8,"target_slot": 5,"type": "CURVE"}],"extra": {"workflowRendererVersion": "LG"},"category": "Image Tools/Color adjust"}]}} \ No newline at end of file +{"revision": 0,"last_node_id": 10,"last_link_id": 0,"nodes": [{"id": 10,"type": "d5c462c8-1372-4af8-84f2-547c83470d04","pos": [3610,-2630],"size": [270,420],"flags": {},"order": 0,"mode": 0,"inputs": [{"label": "image","localized_name": "images.image0","name": "images.image0","type": "IMAGE","link": null}],"outputs": [{"label": "IMAGE","localized_name": "IMAGE0","name": "IMAGE0","type": "IMAGE","links": []}],"properties": {"proxyWidgets": [["4","curve"],["5","curve"],["6","curve"],["7","curve"]],"proxyWidgetSelector": {"name": "Channel","selected": "RGB Master","options": [{"label": "RGB Master","widgets": [["4","curve"]]},{"label": "Red","widgets": [["5","curve"]]},{"label": "Green","widgets": [["6","curve"]]},{"label": "Blue","widgets": [["7","curve"]]}]}},"widgets_values": [],"title": "Color Curves"}],"links": [],"version": 0.4,"definitions": {"subgraphs": [{"id": "d5c462c8-1372-4af8-84f2-547c83470d04","version": 1,"state": {"lastGroupId": 0,"lastNodeId": 8,"lastLinkId": 33,"lastRerouteId": 0},"revision": 0,"config": {},"name": "Color Curves","inputNode": {"id": -10,"bounding": [2660,-4500,120,60]},"outputNode": {"id": -20,"bounding": [4270,-4500,120,60]},"inputs": [{"id": "abc345b7-f55e-4f32-a11d-3aa4c2b0936b","name": "images.image0","type": "IMAGE","linkIds": [29],"localized_name": "images.image0","label": "image","pos": [2760,-4480]}],"outputs": [{"id": "eb0ec079-46da-4408-8263-9ef85569d33d","name": "IMAGE0","type": "IMAGE","linkIds": [28],"localized_name": "IMAGE0","label": "IMAGE","pos": [4290,-4480]}],"widgets": [],"nodes": [{"id": 4,"type": "CurveEditor","pos": [3060,-4500],"size": [270,200],"flags": {},"order": 0,"mode": 0,"inputs": [{"label": "curve","localized_name": "curve","name": "curve","type": "CURVE","widget": {"name": "curve"},"link": null},{"label": "histogram","localized_name": "histogram","name": "histogram","type": "HISTOGRAM","shape": 7,"link": null}],"outputs": [{"localized_name": "CURVE","name": "CURVE","type": "CURVE","links": [30]}],"title": "RGB Master","properties": {"Node name for S&R": "CurveEditor"},"widgets_values": []},{"id": 5,"type": "CurveEditor","pos": [3060,-4250],"size": [270,200],"flags": {},"order": 1,"mode": 0,"inputs": [{"label": "curve","localized_name": "curve","name": "curve","type": "CURVE","widget": {"name": "curve"},"link": null},{"label": "histogram","localized_name": "histogram","name": "histogram","type": "HISTOGRAM","shape": 7,"link": null}],"outputs": [{"localized_name": "CURVE","name": "CURVE","type": "CURVE","links": [31]}],"title": "Red","properties": {"Node name for S&R": "CurveEditor"},"widgets_values": []},{"id": 6,"type": "CurveEditor","pos": [3060,-4000],"size": [270,200],"flags": {},"order": 2,"mode": 0,"inputs": [{"label": "curve","localized_name": "curve","name": "curve","type": "CURVE","widget": {"name": "curve"},"link": null},{"label": "histogram","localized_name": "histogram","name": "histogram","type": "HISTOGRAM","shape": 7,"link": null}],"outputs": [{"localized_name": "CURVE","name": "CURVE","type": "CURVE","links": [32]}],"title": "Green","properties": {"Node name for S&R": "CurveEditor"},"widgets_values": []},{"id": 7,"type": "CurveEditor","pos": [3060,-3750],"size": [270,200],"flags": {},"order": 3,"mode": 0,"inputs": [{"label": "curve","localized_name": "curve","name": "curve","type": "CURVE","widget": {"name": "curve"},"link": null},{"label": "histogram","localized_name": "histogram","name": "histogram","type": "HISTOGRAM","shape": 7,"link": null}],"outputs": [{"localized_name": "CURVE","name": "CURVE","type": "CURVE","links": [33]}],"title": "Blue","properties": {"Node name for S&R": "CurveEditor"},"widgets_values": []},{"id": 8,"type": "GLSLShader","pos": [3590,-4500],"size": [420,500],"flags": {},"order": 4,"mode": 0,"inputs": [{"label": "image0","localized_name": "images.image0","name": "images.image0","type": "IMAGE","link": 29},{"label": "image1","localized_name": "images.image1","name": "images.image1","shape": 7,"type": "IMAGE","link": null},{"label": "u_curve0","localized_name": "curves.u_curve0","name": "curves.u_curve0","shape": 7,"type": "CURVE","link": 30},{"label": "u_curve1","localized_name": "curves.u_curve1","name": "curves.u_curve1","shape": 7,"type": "CURVE","link": 31},{"label": "u_curve2","localized_name": "curves.u_curve2","name": "curves.u_curve2","shape": 7,"type": "CURVE","link": 32},{"label": "u_curve3","localized_name": "curves.u_curve3","name": "curves.u_curve3","shape": 7,"type": "CURVE","link": 33},{"localized_name": "fragment_shader","name": "fragment_shader","type": "STRING","widget": {"name": "fragment_shader"},"link": null},{"localized_name": "size_mode","name": "size_mode","type": "COMFY_DYNAMICCOMBO_V3","widget": {"name": "size_mode"},"link": null}],"outputs": [{"localized_name": "IMAGE0","name": "IMAGE0","type": "IMAGE","links": [28]},{"localized_name": "IMAGE1","name": "IMAGE1","type": "IMAGE","links": null},{"localized_name": "IMAGE2","name": "IMAGE2","type": "IMAGE","links": null},{"localized_name": "IMAGE3","name": "IMAGE3","type": "IMAGE","links": null}],"properties": {"Node name for S&R": "GLSLShader"},"widgets_values": ["#version 300 es\nprecision highp float;\n\nuniform sampler2D u_image0;\nuniform sampler2D u_curve0; // RGB master curve (256x1 LUT)\nuniform sampler2D u_curve1; // Red channel curve\nuniform sampler2D u_curve2; // Green channel curve\nuniform sampler2D u_curve3; // Blue channel curve\n\nin vec2 v_texCoord;\nlayout(location = 0) out vec4 fragColor0;\n\n// GIMP-compatible curve lookup with manual linear interpolation.\n// Matches gimp_curve_map_value_inline() from gimpcurve-map.c:\n// index = value * (n_samples - 1)\n// f = fract(index)\n// result = (1-f) * samples[floor] + f * samples[ceil]\n//\n// Uses texelFetch (NEAREST) to avoid GPU half-texel offset issues\n// that occur with texture() + GL_LINEAR on small 256x1 LUTs.\nfloat applyCurve(sampler2D curve, float value) {\n value = clamp(value, 0.0, 1.0);\n\n float pos = value * 255.0;\n int lo = int(floor(pos));\n int hi = min(lo + 1, 255);\n float f = pos - float(lo);\n\n float a = texelFetch(curve, ivec2(lo, 0), 0).r;\n float b = texelFetch(curve, ivec2(hi, 0), 0).r;\n\n return a + f * (b - a);\n}\n\nvoid main() {\n vec4 color = texture(u_image0, v_texCoord);\n\n // GIMP order: per-channel curves first, then RGB master curve.\n // See gimp_curve_map_pixels() default case in gimpcurve-map.c:\n // dest = colors_curve( channel_curve( src ) )\n float tmp_r = applyCurve(u_curve1, color.r);\n float tmp_g = applyCurve(u_curve2, color.g);\n float tmp_b = applyCurve(u_curve3, color.b);\n color.r = applyCurve(u_curve0, tmp_r);\n color.g = applyCurve(u_curve0, tmp_g);\n color.b = applyCurve(u_curve0, tmp_b);\n\n fragColor0 = vec4(color.rgb, color.a);\n}\n","from_input"]}],"groups": [],"links": [{"id": 29,"origin_id": -10,"origin_slot": 0,"target_id": 8,"target_slot": 0,"type": "IMAGE"},{"id": 28,"origin_id": 8,"origin_slot": 0,"target_id": -20,"target_slot": 0,"type": "IMAGE"},{"id": 30,"origin_id": 4,"origin_slot": 0,"target_id": 8,"target_slot": 2,"type": "CURVE"},{"id": 31,"origin_id": 5,"origin_slot": 0,"target_id": 8,"target_slot": 3,"type": "CURVE"},{"id": 32,"origin_id": 6,"origin_slot": 0,"target_id": 8,"target_slot": 4,"type": "CURVE"},{"id": 33,"origin_id": 7,"origin_slot": 0,"target_id": 8,"target_slot": 5,"type": "CURVE"}],"extra": {"workflowRendererVersion": "LG"},"category": "Image Tools/Color adjust"}]}} \ No newline at end of file