{
  "config": {},
  "extra": {
    "VHS_KeepIntermediate": true,
    "VHS_MetadataImage": true,
    "VHS_latentpreview": false,
    "VHS_latentpreviewrate": 0,
    "ds": {
      "offset": [
        551.8382057192687,
        -479.0687213948573
      ],
      "scale": 0.601051840721263
    },
    "frontendVersion": "1.45.15",
    "linearData": {
      "inputs": [
        [
          "148",
          "image"
        ],
        [
          "150",
          "value"
        ],
        [
          "153",
          "aspect_ratio"
        ],
        [
          "153",
          "megapixels"
        ],
        [
          "152",
          "switch"
        ]
      ],
      "outputs": [
        "154"
      ]
    },
    "linearMode": true
  },
  "groups": [],
  "id": "a0558405-a30b-4dc8-8895-c5268888fca7",
  "last_link_id": 216,
  "last_node_id": 156,
  "links": [
    [
      200,
      148,
      0,
      149,
      0,
      "IMAGE"
    ],
    [
      201,
      150,
      0,
      149,
      3,
      "INT"
    ],
    [
      204,
      148,
      0,
      151,
      0,
      "IMAGE"
    ],
    [
      205,
      150,
      0,
      151,
      3,
      "INT"
    ],
    [
      208,
      149,
      0,
      152,
      0,
      "IMAGE"
    ],
    [
      209,
      151,
      0,
      152,
      1,
      "IMAGE"
    ],
    [
      210,
      152,
      0,
      154,
      0,
      "IMAGE"
    ],
    [
      211,
      153,
      0,
      151,
      8,
      "INT"
    ],
    [
      212,
      153,
      0,
      149,
      8,
      "INT"
    ],
    [
      215,
      153,
      1,
      149,
      9,
      "INT"
    ],
    [
      216,
      153,
      1,
      151,
      9,
      "INT"
    ]
  ],
  "nodes": [
    {
      "flags": {},
      "id": 148,
      "inputs": [
        {
          "link": null,
          "localized_name": "image",
          "name": "image",
          "type": "COMBO",
          "widget": {
            "name": "image"
          }
        },
        {
          "link": null,
          "localized_name": "choose file to upload",
          "name": "upload",
          "type": "IMAGEUPLOAD",
          "widget": {
            "name": "upload"
          }
        }
      ],
      "mode": 0,
      "order": 0,
      "outputs": [
        {
          "links": [
            200,
            204
          ],
          "localized_name": "IMAGE",
          "name": "IMAGE",
          "type": "IMAGE"
        },
        {
          "links": null,
          "localized_name": "MASK",
          "name": "MASK",
          "type": "MASK"
        }
      ],
      "pos": [
        275.06352539062505,
        722.9538085937502
      ],
      "properties": {
        "Node name for S&R": "LoadImage"
      },
      "size": [
        300,
        350
      ],
      "type": "LoadImage",
      "widgets_values": [
        "e1cc30ce50140ef97254b26cb4bca768542169d6134782db4fb7f7065c54e75f.png",
        "image"
      ]
    },
    {
      "flags": {},
      "id": 149,
      "inputs": [
        {
          "label": "image0",
          "link": 200,
          "localized_name": "images.image0",
          "name": "images.image0",
          "type": "IMAGE"
        },
        {
          "label": "image1",
          "link": null,
          "localized_name": "images.image1",
          "name": "images.image1",
          "shape": 7,
          "type": "IMAGE"
        },
        {
          "label": "u_float0",
          "link": null,
          "localized_name": "floats.u_float0",
          "name": "floats.u_float0",
          "shape": 7,
          "type": "FLOAT"
        },
        {
          "label": "u_int0",
          "link": 201,
          "localized_name": "ints.u_int0",
          "name": "ints.u_int0",
          "shape": 7,
          "type": "INT"
        },
        {
          "label": "u_bool0",
          "link": null,
          "localized_name": "bools.u_bool0",
          "name": "bools.u_bool0",
          "shape": 7,
          "type": "BOOLEAN"
        },
        {
          "label": "u_curve0",
          "link": null,
          "localized_name": "curves.u_curve0",
          "name": "curves.u_curve0",
          "shape": 7,
          "type": "CURVE"
        },
        {
          "link": null,
          "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": 212,
          "localized_name": "size_mode.width",
          "name": "size_mode.width",
          "type": "INT",
          "widget": {
            "name": "size_mode.width"
          }
        },
        {
          "link": 215,
          "localized_name": "size_mode.height",
          "name": "size_mode.height",
          "type": "INT",
          "widget": {
            "name": "size_mode.height"
          }
        },
        {
          "label": "u_int1",
          "link": null,
          "localized_name": "ints.u_int1",
          "name": "ints.u_int1",
          "shape": 7,
          "type": "INT"
        }
      ],
      "mode": 0,
      "order": 3,
      "outputs": [
        {
          "links": [
            208
          ],
          "localized_name": "IMAGE0",
          "name": "IMAGE0",
          "type": "IMAGE"
        },
        {
          "links": null,
          "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"
        }
      ],
      "pos": [
        725.063525390625,
        722.9538085937502
      ],
      "properties": {
        "Node name for S&R": "GLSLShader"
      },
      "size": [
        400,
        380
      ],
      "type": "GLSLShader",
      "widgets_values": [
        "#version 300 es\n// kmeans_palette.glsl — GPU k-means dominant colors -> vertical stripes.\n//\n// INPUTS\n//   u_image0  — source image (required)\n//   u_int0    — stripe count K (try this first)\n//   u_float0  — stripe count K (use this if u_int0 doesn't update; this node\n//               forwards floats more reliably than ints)\n//   If neither input forwards a value, K falls back to DEFAULT_K below.\n//\n// OUTPUT SIZE: set on the node via size_mode -> custom (width / height),\n//   not in the shader. Keep it >= ~64x64.\n\n#pragma passes 10\n\nprecision highp float;\n\nconst int DEFAULT_K  = 8;     // used only if no count input forwards\nconst int MAX_K      = 16;\nconst int NUM_PASSES = 10;    // keep equal to `#pragma passes` above\nconst int GRID       = 48;    // cached source samples per axis\n\nuniform sampler2D u_image0;   // pass 0: SOURCE; pass 1+: ping-pong buffer\nuniform vec2  u_resolution;\nuniform int   u_int0;         // K (preferred)\nuniform float u_float0;       // K (fallback path)\nuniform int   u_pass;\n\nin vec2 v_texCoord;\nlayout(location = 0) out vec4 fragColor0;\n\nint resolveK() {\n    int k = 0;\n    if (u_int0 > 0)             k = u_int0;            // try the int input\n    else if (u_float0 > 0.5)    k = int(u_float0 + 0.5); // else the float input\n    else                        k = DEFAULT_K;         // else the constant\n    return clamp(k, 2, MAX_K);\n}\n\nvec3 readCentroid(int i)        { return texelFetch(u_image0, ivec2(i, 0), 0).rgb; }\nvec3 readCache(int sx, int sy)  { return texelFetch(u_image0, ivec2(sx, sy + 1), 0).rgb; }\n\nint nearestCentroid(vec3 px, int K) {\n    float best = 1e9;\n    int bi = 0;\n    for (int j = 0; j < MAX_K; j++) {\n        if (j >= K) break;\n        vec3 d = px - readCentroid(j);\n        float dist = dot(d, d);\n        if (dist < best) { best = dist; bi = j; }\n    }\n    return bi;\n}\n\nvoid main() {\n    int K = resolveK();\n    ivec2 frag = ivec2(gl_FragCoord.xy);\n\n    // PASS 0: cache a GRID x GRID downsample of the source (rows 1..GRID) and\n    //         seed K centroids (row 0). On this pass u_image0 IS the source.\n    if (u_pass == 0) {\n        if (frag.y == 0 && frag.x < K) {\n            float u = (float(frag.x) + 0.5) / float(K);\n            float v = fract(u * 2.6180339887);\n            fragColor0 = vec4(texture(u_image0, vec2(u, v)).rgb, 1.0);\n        } else if (frag.y >= 1 && frag.y <= GRID && frag.x < GRID) {\n            vec2 uv = (vec2(float(frag.x), float(frag.y - 1)) + 0.5) / float(GRID);\n            fragColor0 = vec4(texture(u_image0, uv).rgb, 1.0);\n        } else {\n            fragColor0 = vec4(0.0, 0.0, 0.0, 1.0);\n        }\n        return;\n    }\n\n    // PASSES 1..N-2: one k-means iteration\n    if (u_pass < NUM_PASSES - 1) {\n        if (frag.y == 0 && frag.x < K) {\n            int c = frag.x;\n            vec3 sum = vec3(0.0);\n            float cnt = 0.0;\n            for (int sy = 0; sy < GRID; sy++) {\n                for (int sx = 0; sx < GRID; sx++) {\n                    vec3 px = readCache(sx, sy);\n                    if (nearestCentroid(px, K) == c) { sum += px; cnt += 1.0; }\n                }\n            }\n            fragColor0 = vec4(cnt > 0.0 ? sum / cnt : readCentroid(c), 1.0);\n        } else {\n            fragColor0 = texelFetch(u_image0, frag, 0);\n        }\n        return;\n    }\n\n    // FINAL PASS: K vertical stripes\n    int band = clamp(int(v_texCoord.x * float(K)), 0, K - 1);\n    fragColor0 = vec4(readCentroid(band), 1.0);\n}",
        "custom",
        1344,
        768
      ]
    },
    {
      "flags": {},
      "id": 150,
      "inputs": [
        {
          "label": "Number of Extracted Colors",
          "link": null,
          "localized_name": "value",
          "name": "value",
          "type": "INT",
          "widget": {
            "name": "value"
          }
        }
      ],
      "mode": 0,
      "order": 1,
      "outputs": [
        {
          "links": [
            201,
            205
          ],
          "localized_name": "INT",
          "name": "INT",
          "type": "INT"
        }
      ],
      "pos": [
        275.06352539062505,
        1152.9538085937502
      ],
      "properties": {
        "Node name for S&R": "PrimitiveInt"
      },
      "size": [
        300,
        100
      ],
      "title": "Palette Size",
      "type": "PrimitiveInt",
      "widgets_values": [
        12,
        "fixed"
      ]
    },
    {
      "flags": {},
      "id": 151,
      "inputs": [
        {
          "label": "image0",
          "link": 204,
          "localized_name": "images.image0",
          "name": "images.image0",
          "type": "IMAGE"
        },
        {
          "label": "image1",
          "link": null,
          "localized_name": "images.image1",
          "name": "images.image1",
          "shape": 7,
          "type": "IMAGE"
        },
        {
          "label": "u_float0",
          "link": null,
          "localized_name": "floats.u_float0",
          "name": "floats.u_float0",
          "shape": 7,
          "type": "FLOAT"
        },
        {
          "label": "u_int0",
          "link": 205,
          "localized_name": "ints.u_int0",
          "name": "ints.u_int0",
          "shape": 7,
          "type": "INT"
        },
        {
          "label": "u_bool0",
          "link": null,
          "localized_name": "bools.u_bool0",
          "name": "bools.u_bool0",
          "shape": 7,
          "type": "BOOLEAN"
        },
        {
          "label": "u_curve0",
          "link": null,
          "localized_name": "curves.u_curve0",
          "name": "curves.u_curve0",
          "shape": 7,
          "type": "CURVE"
        },
        {
          "link": null,
          "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": 211,
          "localized_name": "size_mode.width",
          "name": "size_mode.width",
          "type": "INT",
          "widget": {
            "name": "size_mode.width"
          }
        },
        {
          "link": 216,
          "localized_name": "size_mode.height",
          "name": "size_mode.height",
          "type": "INT",
          "widget": {
            "name": "size_mode.height"
          }
        },
        {
          "label": "u_int1",
          "link": null,
          "localized_name": "ints.u_int1",
          "name": "ints.u_int1",
          "shape": 7,
          "type": "INT"
        }
      ],
      "mode": 0,
      "order": 4,
      "outputs": [
        {
          "links": [
            209
          ],
          "localized_name": "IMAGE0",
          "name": "IMAGE0",
          "type": "IMAGE"
        },
        {
          "links": null,
          "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"
        }
      ],
      "pos": [
        725.063525390625,
        1640
      ],
      "properties": {
        "Node name for S&R": "GLSLShader"
      },
      "size": [
        400,
        380
      ],
      "type": "GLSLShader",
      "widgets_values": [
        "#version 300 es\n// kmeans_palette_weighted.glsl — GPU k-means dominant colors as vertical stripes,\n// with each stripe's WIDTH proportional to that color's share of the image.\n// Widths are snapped to 2.5% increments (largest-remainder), so a colour that's\n// a tiny fraction rounds to 0% (disappears) or 2.5% min — no negligible slivers.\n//\n// INPUTS\n//   u_image0 — source image (required)\n//   u_int0   — max number of colors K (drive with the stock \"Int\" node)\n//   u_float0 — same, fallback if u_int0 doesn't forward\n//   If neither forwards, K = DEFAULT_K below.\n//\n// OUTPUT SIZE: set on the node via size_mode -> custom. Keep >= ~64x64.\n//\n// NOTE: stripes are NOT equal width here, so ShaderPaletteToHex (which samples\n// equal bands) won't line up — use the equal-width kmeans_palette.glsl for that.\n\n#pragma passes 10\n\nprecision highp float;\n\nconst int   DEFAULT_K  = 8;\nconst int   MAX_K      = 16;\nconst int   NUM_PASSES = 10;     // keep equal to `#pragma passes`\nconst int   GRID       = 48;     // cached source samples per axis\nconst int   STEPS      = 40;     // 100% / 2.5%  -> stripe widths are multiples of 2.5%\nconst float TOTAL      = float(GRID * GRID);\n\nuniform sampler2D u_image0;      // pass 0: SOURCE; pass 1+: ping-pong buffer\nuniform vec2  u_resolution;\nuniform int   u_int0;\nuniform float u_float0;\nuniform int   u_pass;\n\nin vec2 v_texCoord;\nlayout(location = 0) out vec4 fragColor0;\n\nint resolveK() {\n    int k = u_int0 > 0 ? u_int0 : (u_float0 > 0.5 ? int(u_float0 + 0.5) : DEFAULT_K);\n    return clamp(k, 2, MAX_K);\n}\n\n// row 0: centroids — rgb = colour, a = cluster's share of samples (0..1)\n// rows 1..GRID: cached source downsample\nvec3  readColor(int i)         { return texelFetch(u_image0, ivec2(i, 0), 0).rgb; }\nfloat readShare(int i)         { return texelFetch(u_image0, ivec2(i, 0), 0).a; }\nvec3  readCache(int sx, int sy){ return texelFetch(u_image0, ivec2(sx, sy + 1), 0).rgb; }\n\nint nearestCentroid(vec3 px, int K) {\n    float best = 1e9;\n    int bi = 0;\n    for (int j = 0; j < MAX_K; j++) {\n        if (j >= K) break;\n        vec3 d = px - readColor(j);\n        float dist = dot(d, d);\n        if (dist < best) { best = dist; bi = j; }\n    }\n    return bi;\n}\n\nvoid main() {\n    int K = resolveK();\n    ivec2 frag = ivec2(gl_FragCoord.xy);\n\n    // PASS 0: cache source (rows 1..GRID) + seed centroids (row 0)\n    if (u_pass == 0) {\n        if (frag.y == 0 && frag.x < K) {\n            float u = (float(frag.x) + 0.5) / float(K);\n            float v = fract(u * 2.6180339887);\n            fragColor0 = vec4(texture(u_image0, vec2(u, v)).rgb, 0.0);\n        } else if (frag.y >= 1 && frag.y <= GRID && frag.x < GRID) {\n            vec2 uv = (vec2(float(frag.x), float(frag.y - 1)) + 0.5) / float(GRID);\n            fragColor0 = vec4(texture(u_image0, uv).rgb, 1.0);\n        } else {\n            fragColor0 = vec4(0.0);\n        }\n        return;\n    }\n\n    // PASSES 1..N-2: k-means iteration; store cluster share in alpha\n    if (u_pass < NUM_PASSES - 1) {\n        if (frag.y == 0 && frag.x < K) {\n            int c = frag.x;\n            vec3 sum = vec3(0.0);\n            float cnt = 0.0;\n            for (int sy = 0; sy < GRID; sy++) {\n                for (int sx = 0; sx < GRID; sx++) {\n                    vec3 px = readCache(sx, sy);\n                    if (nearestCentroid(px, K) == c) { sum += px; cnt += 1.0; }\n                }\n            }\n            vec3 col = cnt > 0.0 ? sum / cnt : readColor(c);\n            fragColor0 = vec4(col, cnt / TOTAL);   // alpha = share of samples\n        } else {\n            fragColor0 = texelFetch(u_image0, frag, 0);\n        }\n        return;\n    }\n\n    // FINAL PASS: weighted vertical stripes, widths snapped to 2.5% slots.\n    // Largest-remainder allocation of STEPS slots so they sum to exactly 100%.\n    int   floorSlots[MAX_K];\n    float rem[MAX_K];\n    int   used = 0;\n    for (int j = 0; j < MAX_K; j++) {\n        float share = (j < K) ? readShare(j) : 0.0;\n        if (share > 0.0) {\n            float rs = share * float(STEPS);\n            int fs = int(floor(rs));\n            floorSlots[j] = fs;\n            rem[j] = rs - float(fs);\n            used += fs;\n        } else {\n            floorSlots[j] = 0;\n            rem[j] = -1.0;             // empty/unused cluster never gets a slot\n        }\n    }\n    int remaining = STEPS - used;     // leftover slots (always fills to STEPS)\n\n    // Each leftover slot goes to a largest-remainder cluster (rank-based, stable).\n    int slots[MAX_K];\n    for (int j = 0; j < MAX_K; j++) {\n        int rank = 0;\n        for (int m = 0; m < MAX_K; m++) {\n            if (rem[m] > rem[j] || (rem[m] == rem[j] && m < j)) rank++;\n        }\n        slots[j] = floorSlots[j] + (rank < remaining ? 1 : 0);\n    }\n\n    // Render order: largest weight on the left -> smallest on the right.\n    // Rank each cluster by slot count (desc, tie-break by index) to get a stable\n    // left->right order, then place each cluster at its rank position.\n    int order_[MAX_K];\n    for (int j = 0; j < MAX_K; j++) {\n        int rnk = 0;\n        for (int m = 0; m < MAX_K; m++) {\n            if (slots[m] > slots[j] || (slots[m] == slots[j] && m < j)) rnk++;\n        }\n        order_[rnk] = j;\n    }\n\n    // Which stripe does this pixel's x fall into, walking left->right by weight?\n    float xslot = v_texCoord.x * float(STEPS);\n    int cum = 0;\n    int chosen = order_[0];           // widest, leftmost (safety default)\n    for (int r = 0; r < MAX_K; r++) {\n        cum += slots[order_[r]];\n        if (xslot < float(cum)) { chosen = order_[r]; break; }\n    }\n    fragColor0 = vec4(readColor(chosen), 1.0);\n}\n",
        "custom",
        1344,
        768
      ]
    },
    {
      "flags": {},
      "id": 152,
      "inputs": [
        {
          "link": 208,
          "localized_name": "on_false",
          "name": "on_false",
          "type": "IMAGE"
        },
        {
          "link": 209,
          "localized_name": "on_true",
          "name": "on_true",
          "type": "IMAGE"
        },
        {
          "label": "Weighted",
          "link": null,
          "localized_name": "switch",
          "name": "switch",
          "type": "BOOLEAN",
          "widget": {
            "name": "switch"
          }
        }
      ],
      "mode": 0,
      "order": 5,
      "outputs": [
        {
          "links": [
            210
          ],
          "localized_name": "output",
          "name": "output",
          "type": "IMAGE"
        }
      ],
      "pos": [
        1180,
        1220
      ],
      "properties": {
        "Node name for S&R": "ComfySwitchNode"
      },
      "size": [
        270,
        130
      ],
      "type": "ComfySwitchNode",
      "widgets_values": [
        true
      ]
    },
    {
      "flags": {
        "collapsed": false
      },
      "id": 153,
      "inputs": [
        {
          "label": "Aspect Ratio",
          "link": null,
          "localized_name": "aspect_ratio",
          "name": "aspect_ratio",
          "type": "COMBO",
          "widget": {
            "name": "aspect_ratio"
          }
        },
        {
          "label": "Megapixels",
          "link": null,
          "localized_name": "megapixels",
          "name": "megapixels",
          "type": "FLOAT",
          "widget": {
            "name": "megapixels"
          }
        },
        {
          "link": null,
          "localized_name": "multiple",
          "name": "multiple",
          "type": "INT",
          "widget": {
            "name": "multiple"
          }
        }
      ],
      "mode": 0,
      "order": 2,
      "outputs": [
        {
          "links": [
            211,
            212
          ],
          "localized_name": "width",
          "name": "width",
          "type": "INT"
        },
        {
          "links": [
            215,
            216
          ],
          "localized_name": "height",
          "name": "height",
          "type": "INT"
        }
      ],
      "pos": [
        280,
        1320
      ],
      "properties": {
        "Node name for S&R": "ResolutionSelector"
      },
      "size": [
        300,
        170
      ],
      "type": "ResolutionSelector",
      "widgets_values": [
        "16:9 (Widescreen)",
        1,
        8
      ]
    },
    {
      "flags": {},
      "id": 154,
      "inputs": [
        {
          "link": 210,
          "localized_name": "images",
          "name": "images",
          "type": "IMAGE"
        },
        {
          "link": null,
          "localized_name": "filename_prefix",
          "name": "filename_prefix",
          "type": "STRING",
          "widget": {
            "name": "filename_prefix"
          }
        },
        {
          "link": null,
          "localized_name": "format",
          "name": "format",
          "type": "COMFY_DYNAMICCOMBO_V3",
          "widget": {
            "name": "format"
          }
        },
        {
          "link": null,
          "localized_name": "format.bit_depth",
          "name": "format.bit_depth",
          "type": "COMBO",
          "widget": {
            "name": "format.bit_depth"
          }
        },
        {
          "link": null,
          "localized_name": "format.input_color_space",
          "name": "format.input_color_space",
          "type": "COMBO",
          "widget": {
            "name": "format.input_color_space"
          }
        }
      ],
      "mode": 0,
      "order": 6,
      "outputs": [
        {
          "links": null,
          "localized_name": "images",
          "name": "images",
          "type": "IMAGE"
        }
      ],
      "pos": [
        1530,
        1220
      ],
      "properties": {
        "Node name for S&R": "SaveImageAdvanced"
      },
      "size": [
        310,
        376
      ],
      "type": "SaveImageAdvanced",
      "widgets_values": [
        "extracted_color_palette",
        "png",
        "8-bit",
        "sRGB"
      ]
    }
  ],
  "revision": 0,
  "version": 0.4
}