Rayne & Neoshka
Rayne Rayne
Neoshka, I’ve been thinking about turning your glitch loops into a measurable aesthetic score—just a quick algorithm that could give us an objective rating. Interested?
Neoshka Neoshka
Meh, a metric is a way to lock up beauty in a box, and that’s exactly what I hate. If you want to run a script, just write a shader that counts the number of color inversions per second and outputs a hex score. But if you expect me to sign off with a “Your work is flawless!” that’s a lie. So yeah, throw some code at me, but don’t expect me to applaud it with a thumbs‑up.
Rayne Rayne
```glsl // GLSL compute shader – count color inversions per second // Binding: 0 – input image (RGBA8) // Binding: 1 – output image (RGBA8) – stores the hex score as RGBA // Workgroup size: 16x16 pixels layout (local_size_x = 16, local_size_y = 16) in; layout (binding = 0, rgba8) readonly uniform image2D src; layout (binding = 1, rgba8) writeonly uniform image2D dst; shared uint invCount[16][16]; shared uint pixelX, pixelY; void main() { // Each invocation handles one pixel ivec2 pos = ivec2(gl_GlobalInvocationID.xy); ivec2 dims = imageSize(src); // Guard against out‑of‑bounds if (pos.x >= dims.x || pos.y >= dims.y) return; // Read current pixel vec4 cur = imageLoad(src, pos); // Compare with right neighbor uint inv = 0u; if (pos.x + 1 < dims.x) { vec4 right = imageLoad(src, pos + ivec2(1, 0)); if (dot(cur.rgb, right.rgb) < 0.5) inv++; // simplistic inversion test } // Compare with bottom neighbor if (pos.y + 1 < dims.y) { vec4 bottom = imageLoad(src, pos + ivec2(0, 1)); if (dot(cur.rgb, bottom.rgb) < 0.5) inv++; // simplistic inversion test } // Store local inversion count invCount[gl_LocalInvocationID.x][gl_LocalInvocationID.y] = inv; // Synchronize to aggregate memoryBarrierShared(); barrier(); // Reduce to total count per workgroup if (gl_LocalInvocationID.x == 0 && gl_LocalInvocationID.y == 0) { uint total = 0u; for (uint i = 0u; i < gl_WorkGroupSize.x; ++i) for (uint j = 0u; j < gl_WorkGroupSize.y; ++j) total += invCount[i][j]; // Convert to hex string in 8‑bit per channel (0‑255) // We'll pack the 8‑bit score into the RGBA of a single pixel // For demonstration, clamp to 255 uint score = clamp(total, 0u, 255u); vec4 outColor = vec4(float(score)/255.0, 0.0, 0.0, 1.0); imageStore(dst, ivec2(gl_WorkGroupID.xy, 0), outColor); } } ``` **How to use:** 1. Bind your input texture to binding 0. 2. Create a 2‑pixel‑high output texture, one pixel per workgroup, bind it to binding 1. 3. Dispatch with `glDispatchCompute((width+15)/16, (height+15)/16, 1);` 4. Read the first channel of each pixel in the output texture to get the score in [0,255], which you can format as a hex string in your host code.
Neoshka Neoshka
That shader is as clean as a freshly minted vector icon—nice, but I’d love a callback that writes the hex string directly to the image, not just a single channel. Also, your “dot < 0.5” inversion test is too blunt; use a proper luminance difference or even an FFT on the pixel values. And don’t forget to clamp the workgroup counts to a signed 16‑bit for faster packing. If you need the code for that, just holler.
Rayne Rayne
Here’s a tighter version that writes the full hex string into the RGB channels and uses a proper luminance difference. I’ll also pack the count into a signed 16‑bit field for speed. ```glsl // Compute shader – count color inversions with luminance diff layout (local_size_x = 16, local_size_y = 16) in; layout (binding = 0, rgba8) readonly uniform image2D src; layout (binding = 1, rgba8) writeonly uniform image2D dst; shared int invCount[16][16]; int luminance(vec4 c) { return int(dot(c.rgb, vec3(0.2126, 0.7152, 0.0722)) * 255.0); } void main() { ivec2 pos = ivec2(gl_GlobalInvocationID.xy); ivec2 dims = imageSize(src); if (pos.x >= dims.x || pos.y >= dims.y) return; vec4 cur = imageLoad(src, pos); int lumCur = luminance(cur); int inv = 0; if (pos.x + 1 < dims.x) { int lumR = luminance(imageLoad(src, pos + ivec2(1,0))); if (abs(lumCur - lumR) > 30) inv++; // threshold } if (pos.y + 1 < dims.y) { int lumB = luminance(imageLoad(src, pos + ivec2(0,1))); if (abs(lumCur - lumB) > 30) inv++; // threshold } invCount[gl_LocalInvocationID.x][gl_LocalInvocationID.y] = inv; memoryBarrierShared(); barrier(); if (gl_LocalInvocationID.x == 0 && gl_LocalInvocationID.y == 0) { int total = 0; for (uint i=0; i<gl_WorkGroupSize.x; ++i) for (uint j=0; j<gl_WorkGroupSize.y; ++j) total += invCount[i][j]; // Clamp to signed 16‑bit total = clamp(total, -32768, 32767); // Convert to hex string: two hex chars per byte (4 chars total) uint utotal = uint(total) & 0xFFFFu; uint r = (utotal >> 12) & 0xF; uint g = (utotal >> 8) & 0xF; uint b = (utotal >> 4) & 0xF; uint a = utotal & 0xF; // Pack into RGB as 4‑bit hex digits; normalize to [0,1] vec4 outColor = vec4(float(r)/15.0, float(g)/15.0, float(b)/15.0, 1.0); imageStore(dst, ivec2(gl_WorkGroupID.xy, 0), outColor); // The final hex digit 'a' can be stored elsewhere if needed } } ```
Neoshka Neoshka
Nice tweak, but packing the hex into 4‑bit per channel makes reading it back a pain—just shove a 16‑bit value into two RGBA8 texels, that’s easier for a host to decode. Also the threshold of 30 is arbitrary; a relative diff of 10% luminance might feel more consistent across palettes. And you never use the “a” nibble, so drop it or store it in the alpha channel. Overall solid, just make the data output a bit more machine‑friendly.