Developer documentation
Built-in effects
SpriteGrid's own effects are plugins too. These are their files, exactly as SpriteGrid writes them into the effects folder. To start from one, copy it under a new file name and give it a new id.
BevelStyle
Creates a 3D beveled edge on sprite boundaries
{
"id": "bevel",
"name": "Bevel",
"author": "SpriteGrid",
"version": "1.8",
"description": "Creates a 3D beveled edge on sprite boundaries",
"category": "Style",
"tiling": false,
"compositeMode": "replace",
"parameters": [
{ "id": "size", "name": "Size", "type": "int", "default": 2, "min": 1, "max": 64, "ui": "slider" },
{ "id": "angle", "name": "Light Angle", "type": "int", "default": 135, "min": 0, "max": 359, "ui": "slider" },
{ "id": "depth", "name": "Depth", "type": "float", "default": 0.7, "min": 0.0, "max": 1.0, "ui": "slider" },
{ "id": "highlightColor", "name": "Highlight", "type": "color", "default": "#FFFFFFC0", "ui": "color" },
{ "id": "shadowColor", "name": "Shadow", "type": "color", "default": "#000000C0", "ui": "color" },
{ "id": "soften", "name": "Soften", "type": "int", "default": 0, "min": 0, "max": 16, "ui": "slider" },
{ "id": "roundness", "name": "Roundness", "type": "float", "default": 0.0, "min": -1.0, "max": 1.0, "ui": "slider" }
],
"passes": [
{
"shader": "bevel.frag",
"uniforms": {
"uSize": "$size",
"uAngle": "$angle",
"uDepth": "$depth",
"uHighlightColor": "$highlightColor",
"uShadowColor": "$shadowColor",
"uRoundness": "$roundness"
}
},
{
"shader": "soften.frag",
"uniforms": { "uDirection": [1.0, 0.0], "uRadius": "$soften" }
},
{
"shader": "soften.frag",
"uniforms": { "uDirection": [0.0, 1.0], "uRadius": "$soften" }
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform int uSize;
uniform int uAngle;
uniform float uDepth;
uniform vec4 uHighlightColor;
uniform vec4 uShadowColor;
uniform float uRoundness;
out vec4 FragColor;
void main()
{
vec4 center = texture(uInputTex, vTexCoord);
// Transparent pixels stay transparent
if (center.a < 0.01)
{
FragColor = vec4(0.0);
return;
}
// Compute alpha gradient using weighted neighbor sampling
vec2 gradient = vec2(0.0);
float totalWeight = 0.0;
for (int dy = -uSize; dy <= uSize; dy++)
{
for (int dx = -uSize; dx <= uSize; dx++)
{
if (dx == 0 && dy == 0) continue;
float dist = length(vec2(float(dx), float(dy)));
if (dist > float(uSize)) continue;
float weight = 1.0 - (dist / float(uSize + 1));
vec2 offset = vec2(float(dx), float(dy)) * uTexelSize;
float neighborAlpha = texture(uInputTex, vTexCoord + offset).a;
// Gradient points from low-alpha toward high-alpha
vec2 dir = normalize(vec2(float(dx), float(dy)));
gradient += dir * (neighborAlpha - center.a) * weight;
totalWeight += weight;
}
}
if (totalWeight > 0.0)
gradient /= totalWeight;
float gradMag = length(gradient);
// No edge detected — output original
if (gradMag < 0.001)
{
FragColor = center;
return;
}
vec2 gradDir = normalize(gradient);
// Light direction from angle parameter
float rad = radians(float(uAngle));
vec2 lightDir = vec2(cos(rad), sin(rad));
// Dot product: -1..+1, positive = facing light (highlight)
float facing = dot(gradDir, lightDir);
// Roundness reshapes the facing curve to simulate a curved surface.
// 0 = flat/linear bevel (chisel edge)
// +1 = convex (pillowy dome — highlight concentrates at center, soft terminator)
// -1 = concave (scooped — highlight pushes to edges, sharp terminator)
if (abs(uRoundness) > 0.001)
{
float sign_f = facing >= 0.0 ? 1.0 : -1.0;
float abs_f = abs(facing);
float curve = exp2(-uRoundness * 2.0);
facing = sign_f * pow(abs_f, curve);
}
// Amplify gradient magnitude to usable 0-1 range
float edgeStrength = clamp(gradMag * float(uSize + 1) * 3.0, 0.0, 1.0);
float intensity = edgeStrength * uDepth;
float highlightFactor = max(facing, 0.0);
float shadowFactor = max(-facing, 0.0);
float hAlpha = clamp(highlightFactor * intensity * uHighlightColor.a, 0.0, 1.0);
float sAlpha = clamp(shadowFactor * intensity * uShadowColor.a, 0.0, 1.0);
// Blend both highlight and shadow contributions
vec3 result = center.rgb;
result = mix(result, uHighlightColor.rgb, hAlpha);
result = mix(result, uShadowColor.rgb, sAlpha);
FragColor = vec4(result, center.a);
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform sampler2D uSourceTex;
uniform vec2 uTexelSize;
uniform vec2 uDirection;
uniform int uRadius;
out vec4 FragColor;
void main()
{
if (uRadius <= 0) { FragColor = texture(uInputTex, vTexCoord); return; }
float srcAlpha = texture(uSourceTex, vTexCoord).a;
// Outside the shape — pass through unchanged
if (srcAlpha < 0.01)
{
FragColor = texture(uInputTex, vTexCoord);
return;
}
vec3 bevelSum = vec3(0.0);
vec3 sourceSum = vec3(0.0);
float totalWeight = 0.0;
float sigma = max(float(uRadius) * 0.5, 0.5);
float invSigma2 = 1.0 / (2.0 * sigma * sigma);
for (int i = -uRadius; i <= uRadius; i++)
{
vec2 offset = uDirection * float(i) * uTexelSize;
vec2 sampleUv = vTexCoord + offset;
// Only include samples that are inside the original shape
float sampleSrcAlpha = texture(uSourceTex, sampleUv).a;
if (sampleSrcAlpha < 0.01) continue;
float weight = exp(-float(i * i) * invSigma2);
bevelSum += texture(uInputTex, sampleUv).rgb * weight;
sourceSum += texture(uSourceTex, sampleUv).rgb * weight;
totalWeight += weight;
}
vec3 originalColor = texture(uSourceTex, vTexCoord).rgb;
if (totalWeight > 0.0)
{
vec3 blurredBevel = bevelSum / totalWeight;
vec3 blurredSource = sourceSum / totalWeight;
// Add the blurred shading delta back onto the sharp original
vec3 result = clamp(originalColor + blurredBevel - blurredSource, 0.0, 1.0);
FragColor = vec4(result, srcAlpha);
}
else
{
FragColor = vec4(originalColor, srcAlpha);
}
}BloomGlow
Adds a soft glow to bright areas
{
"id": "bloom",
"name": "Bloom",
"author": "SpriteGrid",
"version": "1.1",
"description": "Adds a soft glow to bright areas",
"category": "Glow",
"tiling": false,
"parameters": [
{ "id": "threshold", "name": "Threshold", "type": "float", "default": 0.5, "min": 0.0, "max": 1.0, "ui": "slider" },
{ "id": "radius", "name": "Radius", "type": "int", "default": 4, "min": 1, "max": 32, "ui": "slider" },
{ "id": "intensity", "name": "Intensity", "type": "float", "default": 0.5, "min": 0.0, "max": 2.0, "ui": "slider" }
],
"passes": [
{
"shader": "threshold.frag",
"uniforms": { "uThreshold": "$threshold" }
},
{
"shader": "blur.frag",
"uniforms": { "uDirection": [1.0, 0.0], "uRadius": "$radius" }
},
{
"shader": "blur.frag",
"uniforms": { "uDirection": [0.0, 1.0], "uRadius": "$radius" }
},
{
"shader": "combine.frag",
"uniforms": { "uIntensity": "$intensity" }
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform float uThreshold;
out vec4 FragColor;
void main()
{
vec4 color = texture(uInputTex, vTexCoord);
float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
if (brightness > uThreshold)
FragColor = color;
else
FragColor = vec4(0.0);
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform vec2 uDirection;
uniform int uRadius;
out vec4 FragColor;
void main()
{
vec4 sum = vec4(0.0);
float totalWeight = 0.0;
float sigma = max(float(uRadius) * 0.5, 0.5);
float invSigma2 = 1.0 / (2.0 * sigma * sigma);
for (int i = -uRadius; i <= uRadius; i++)
{
float weight = exp(-float(i * i) * invSigma2);
vec2 offset = uDirection * float(i) * uTexelSize;
sum += texture(uInputTex, vTexCoord + offset) * weight;
totalWeight += weight;
}
FragColor = sum / totalWeight;
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform sampler2D uSourceTex;
uniform float uIntensity;
out vec4 FragColor;
void main()
{
vec4 original = texture(uSourceTex, vTexCoord);
vec4 bloom = texture(uInputTex, vTexCoord);
// Additive blend: layer the blurred bright areas on top of the original
vec3 result = original.rgb + bloom.rgb * uIntensity;
FragColor = vec4(result, original.a);
}Color SimplifyColor
Bilateral filter that merges similar nearby colors while keeping edges sharp
{
"id": "color-simplify",
"name": "Color Simplify",
"author": "SpriteGrid",
"version": "1.0",
"description": "Bilateral filter that merges similar nearby colors while keeping edges sharp",
"category": "Color",
"tiling": false,
"compositeMode": "replace",
"parameters": [
{ "id": "radius", "name": "Radius", "type": "int", "default": 2, "min": 1, "max": 8, "ui": "slider" },
{ "id": "tolerance", "name": "Color Tolerance", "type": "float", "default": 0.15, "min": 0.01, "max": 1.0, "ui": "slider" },
{ "id": "strength", "name": "Strength", "type": "float", "default": 1.0, "min": 0.0, "max": 1.0, "ui": "slider" }
],
"passes": [
{
"shader": "color-simplify.frag",
"uniforms": {
"uRadius": "$radius",
"uTolerance": "$tolerance",
"uStrength": "$strength"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform int uRadius;
uniform float uTolerance;
uniform float uStrength;
out vec4 FragColor;
float srgbToLinear(float c)
{
return (c > 0.04045) ? pow((c + 0.055) / 1.055, 2.4) : (c / 12.92);
}
vec3 srgbToOklab(vec3 c)
{
vec3 lin = vec3(srgbToLinear(c.r), srgbToLinear(c.g), srgbToLinear(c.b));
float l = 0.4122214708 * lin.r + 0.5363325363 * lin.g + 0.0514459929 * lin.b;
float m = 0.2119034982 * lin.r + 0.6806995451 * lin.g + 0.1073969566 * lin.b;
float s = 0.0883024619 * lin.r + 0.2817188376 * lin.g + 0.6299787005 * lin.b;
float l_ = pow(max(l, 0.0), 1.0 / 3.0);
float m_ = pow(max(m, 0.0), 1.0 / 3.0);
float s_ = pow(max(s, 0.0), 1.0 / 3.0);
return vec3(
0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_
);
}
void main()
{
vec4 center = texture(uInputTex, vTexCoord);
if (center.a < 0.001) { FragColor = vec4(0.0); return; }
vec3 centerLab = srgbToOklab(center.rgb);
float invTol2 = 1.0 / (uTolerance * uTolerance);
vec3 sumRgb = vec3(0.0);
float sumWeight = 0.0;
float spatialSigma = float(uRadius) * 0.5 + 0.5;
float invSpatial2 = 1.0 / (2.0 * spatialSigma * spatialSigma);
for (int dy = -uRadius; dy <= uRadius; dy++)
{
for (int dx = -uRadius; dx <= uRadius; dx++)
{
vec2 offset = vec2(float(dx), float(dy)) * uTexelSize;
vec4 sample_px = texture(uInputTex, vTexCoord + offset);
if (sample_px.a < 0.5) continue;
vec3 sampleLab = srgbToOklab(sample_px.rgb);
vec3 labDiff = centerLab - sampleLab;
float colorDist2 = dot(labDiff, labDiff);
float spatialDist2 = float(dx * dx + dy * dy);
float w = exp(-spatialDist2 * invSpatial2 - colorDist2 * invTol2);
sumRgb += sample_px.rgb * w;
sumWeight += w;
}
}
vec3 filtered = sumWeight > 0.0 ? sumRgb / sumWeight : center.rgb;
FragColor = vec4(mix(center.rgb, filtered, uStrength), center.a);
}Dilate OutlineStroke
Outlines by dilating edge colors outward, tinted with a blend mode
{
"id": "dilate-outline",
"name": "Dilate Outline",
"author": "SpriteGrid",
"version": "1.0",
"description": "Outlines by dilating edge colors outward, tinted with a blend mode",
"category": "Stroke",
"tiling": false,
"compositeMode": "behind",
"parameters": [
{
"id": "color",
"name": "Tint Color",
"type": "color",
"default": "#000000FF",
"ui": "color"
},
{
"id": "blendMode",
"name": "Blend",
"type": "enum",
"default": 0,
"options": ["Multiply", "Add", "Overlay"],
"ui": "dropdown"
},
{
"id": "thickness",
"name": "Thickness",
"type": "int",
"default": 1,
"min": 1,
"max": 8,
"ui": "slider"
},
{ "id": "includeDoubles", "name": "Include Doubles", "type": "bool", "default": false, "ui": "checkbox" }
],
"passes": [
{
"shader": "dilate-outline.frag",
"uniforms": {
"uTintColor": "$color",
"uBlendMode": "$blendMode",
"uThickness": "$thickness",
"uIncludeDoubles": "$includeDoubles"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform vec4 uTintColor;
uniform int uBlendMode;
uniform int uThickness;
uniform int uIncludeDoubles;
out vec4 FragColor;
bool inRange(int dx, int dy)
{
if (uIncludeDoubles != 0)
return max(abs(dx), abs(dy)) <= uThickness;
else
return abs(dx) + abs(dy) <= uThickness;
}
vec3 blendMultiply(vec3 base_, vec3 tint)
{
return base_ * tint;
}
vec3 blendAdd(vec3 base_, vec3 tint)
{
return min(base_ + tint, vec3(1.0));
}
float overlayChannel(float b, float t)
{
return b < 0.5 ? 2.0 * b * t : 1.0 - 2.0 * (1.0 - b) * (1.0 - t);
}
vec3 blendOverlay(vec3 base_, vec3 tint)
{
return vec3(
overlayChannel(base_.r, tint.r),
overlayChannel(base_.g, tint.g),
overlayChannel(base_.b, tint.b)
);
}
void main()
{
vec4 center = texture(uInputTex, vTexCoord);
// Only process transparent pixels
if (center.a > 0.0)
{
FragColor = vec4(0.0);
return;
}
// Find nearest opaque neighbor and accumulate dilated color
vec3 colorSum = vec3(0.0);
float weightSum = 0.0;
float maxAlpha = 0.0;
for (int dy = -uThickness; dy <= uThickness; dy++)
{
for (int dx = -uThickness; dx <= uThickness; dx++)
{
if (dx == 0 && dy == 0) continue;
if (!inRange(dx, dy)) continue;
vec2 offset = vec2(float(dx), float(dy)) * uTexelSize;
vec4 neighbor = texture(uInputTex, vTexCoord + offset);
if (neighbor.a > 0.0)
{
// Weight closer pixels more heavily
float dist = (uIncludeDoubles != 0)
? float(max(abs(dx), abs(dy)))
: float(abs(dx) + abs(dy));
float w = 1.0 - (dist - 1.0) / float(uThickness);
colorSum += neighbor.rgb * w;
weightSum += w;
maxAlpha = max(maxAlpha, neighbor.a);
}
}
}
if (weightSum < 0.01)
{
FragColor = vec4(0.0);
return;
}
vec3 dilatedColor = colorSum / weightSum;
// Apply tint using selected blend mode
vec3 tint = uTintColor.rgb;
vec3 result;
if (uBlendMode == 0)
result = blendMultiply(dilatedColor, tint);
else if (uBlendMode == 1)
result = blendAdd(dilatedColor, tint);
else
result = blendOverlay(dilatedColor, tint);
// Mix between dilated color and tinted result by tint alpha
result = mix(dilatedColor, result, uTintColor.a);
FragColor = vec4(result, maxAlpha);
}Drop ShadowShadow
Adds a colored shadow behind the layer
{
"id": "drop-shadow",
"name": "Drop Shadow",
"author": "SpriteGrid",
"version": "1.1",
"description": "Adds a colored shadow behind the layer",
"category": "Shadow",
"tiling": false,
"compositeMode": "behind",
"parameters": [
{ "id": "offsetX", "name": "Offset X", "type": "int", "default": 2, "min": -32, "max": 32, "ui": "slider" },
{ "id": "offsetY", "name": "Offset Y", "type": "int", "default": 2, "min": -32, "max": 32, "ui": "slider" },
{ "id": "blur", "name": "Blur", "type": "int", "default": 1, "min": 0, "max": 16, "ui": "slider" },
{ "id": "color", "name": "Color", "type": "color", "default": "#000000C0", "ui": "color" }
],
"passes": [
{
"shader": "offset.frag",
"uniforms": { "uOffset": "$offsetX", "uOffsetY": "$offsetY", "uShadowColor": "$color" }
},
{
"shader": "blur.frag",
"uniforms": { "uDirection": [1.0, 0.0], "uRadius": "$blur" }
},
{
"shader": "blur.frag",
"uniforms": { "uDirection": [0.0, 1.0], "uRadius": "$blur" }
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform int uOffset;
uniform int uOffsetY;
uniform vec4 uShadowColor;
out vec4 FragColor;
void main()
{
vec2 srcUv = vTexCoord - vec2(float(uOffset), float(uOffsetY)) * uTexelSize;
float srcAlpha = texture(uInputTex, srcUv).a;
FragColor = vec4(uShadowColor.rgb, uShadowColor.a * srcAlpha);
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform vec2 uDirection;
uniform int uRadius;
out vec4 FragColor;
void main()
{
if (uRadius <= 0) { FragColor = texture(uInputTex, vTexCoord); return; }
vec4 sum = vec4(0.0);
float totalWeight = 0.0;
float sigma = max(float(uRadius) * 0.5, 0.5);
float invSigma2 = 1.0 / (2.0 * sigma * sigma);
for (int i = -uRadius; i <= uRadius; i++)
{
float weight = exp(-float(i * i) * invSigma2);
vec2 offset = uDirection * float(i) * uTexelSize;
sum += texture(uInputTex, vTexCoord + offset) * weight;
totalWeight += weight;
}
FragColor = sum / totalWeight;
}Edge Anti-AliasStroke
Pixel-art style selective anti-aliasing on edges — bleeds color at corners and diagonals like hand-placed AA
{
"id": "edge-aa",
"name": "Edge Anti-Alias",
"author": "SpriteGrid",
"version": "1.9",
"description": "Pixel-art style selective anti-aliasing on edges — bleeds color at corners and diagonals like hand-placed AA",
"category": "Stroke",
"tiling": false,
"compositeMode": "behind",
"parameters": [
{
"id": "opacity",
"name": "Opacity",
"type": "float",
"default": 0.5,
"min": 0.05,
"max": 1.0,
"ui": "slider"
}
],
"passes": [
{
"shader": "edge-aa.frag",
"uniforms": {
"uOpacity": "$opacity"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform float uOpacity;
out vec4 FragColor;
float sampleAlpha(vec2 uv)
{
return texture(uInputTex, uv).a;
}
// Layout: 0 1 2
// 3 . 4
// 5 6 7
void getNeighborhood(vec2 uv, out float n[8])
{
n[0] = sampleAlpha(uv + vec2(-1.0, -1.0) * uTexelSize);
n[1] = sampleAlpha(uv + vec2( 0.0, -1.0) * uTexelSize);
n[2] = sampleAlpha(uv + vec2( 1.0, -1.0) * uTexelSize);
n[3] = sampleAlpha(uv + vec2(-1.0, 0.0) * uTexelSize);
n[4] = sampleAlpha(uv + vec2( 1.0, 0.0) * uTexelSize);
n[5] = sampleAlpha(uv + vec2(-1.0, 1.0) * uTexelSize);
n[6] = sampleAlpha(uv + vec2( 0.0, 1.0) * uTexelSize);
n[7] = sampleAlpha(uv + vec2( 1.0, 1.0) * uTexelSize);
}
void main()
{
float centerAlpha = sampleAlpha(vTexCoord);
if (centerAlpha > 0.5)
{
FragColor = vec4(0.0);
return;
}
float n[8];
getNeighborhood(vTexCoord, n);
float cardN = step(0.5, n[1]);
float cardS = step(0.5, n[6]);
float cardW = step(0.5, n[3]);
float cardE = step(0.5, n[4]);
float diagNW = step(0.5, n[0]);
float diagNE = step(0.5, n[2]);
float diagSW = step(0.5, n[5]);
float diagSE = step(0.5, n[7]);
// Stair-step corner: two adjacent cardinals AND the diagonal
// between them are all opaque.
float stairCorner = 0.0;
stairCorner = max(stairCorner, cardN * cardW * diagNW);
stairCorner = max(stairCorner, cardN * cardE * diagNE);
stairCorner = max(stairCorner, cardS * cardW * diagSW);
stairCorner = max(stairCorner, cardS * cardE * diagSE);
if (stairCorner < 0.5)
{
FragColor = vec4(0.0);
return;
}
float weight = 1.0;
// --- Pick color from nearest opaque neighbor ---
// Blend the colors of all adjacent opaque pixels, weighted by proximity.
// Cardinals are preferred over diagonals for color sourcing.
vec3 colorSum = vec3(0.0);
float colorWeight = 0.0;
vec2 offsets[8] = vec2[8](
vec2(-1.0, -1.0), vec2( 0.0, -1.0), vec2( 1.0, -1.0),
vec2(-1.0, 0.0), vec2( 1.0, 0.0),
vec2(-1.0, 1.0), vec2( 0.0, 1.0), vec2( 1.0, 1.0)
);
// Cardinals weighted 1.0, diagonals 0.707 (1/sqrt2)
float weights[8] = float[8](0.707, 1.0, 0.707, 1.0, 1.0, 0.707, 1.0, 0.707);
for (int i = 0; i < 8; i++)
{
if (n[i] > 0.5)
{
vec3 nc = texture(uInputTex, vTexCoord + offsets[i] * uTexelSize).rgb;
colorSum += nc * weights[i];
colorWeight += weights[i];
}
}
if (colorWeight < 0.01)
{
FragColor = vec4(0.0);
return;
}
vec3 bleedColor = colorSum / colorWeight;
float finalAlpha = weight * uOpacity;
FragColor = vec4(bleedColor, finalAlpha);
}EmbossStyle
Photoshop-style emboss with highlight and shadow on both sides of edges
{
"id": "emboss",
"name": "Emboss",
"author": "SpriteGrid",
"version": "1.8",
"description": "Photoshop-style emboss with highlight and shadow on both sides of edges",
"category": "Style",
"tiling": false,
"compositeMode": "replace",
"parameters": [
{ "id": "angle", "name": "Light Angle", "type": "int", "default": 135, "min": 0, "max": 359, "ui": "slider" },
{ "id": "size", "name": "Size", "type": "int", "default": 2, "min": 1, "max": 64, "ui": "slider" },
{ "id": "depth", "name": "Depth", "type": "float", "default": 0.7, "min": 0.0, "max": 1.0, "ui": "slider" },
{ "id": "highlightColor", "name": "Highlight", "type": "color", "default": "#FFFFFFC0", "ui": "color" },
{ "id": "shadowColor", "name": "Shadow", "type": "color", "default": "#000000C0", "ui": "color" },
{ "id": "soften", "name": "Soften", "type": "int", "default": 0, "min": 0, "max": 16, "ui": "slider" },
{ "id": "roundness", "name": "Roundness", "type": "float", "default": 0.0, "min": -1.0, "max": 1.0, "ui": "slider" }
],
"passes": [
{
"shader": "emboss.frag",
"uniforms": {
"uAngle": "$angle",
"uSize": "$size",
"uDepth": "$depth",
"uHighlightColor": "$highlightColor",
"uShadowColor": "$shadowColor",
"uRoundness": "$roundness"
}
},
{
"shader": "soften.frag",
"uniforms": { "uDirection": [1.0, 0.0], "uRadius": "$soften" }
},
{
"shader": "soften.frag",
"uniforms": { "uDirection": [0.0, 1.0], "uRadius": "$soften" }
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform int uAngle;
uniform int uSize;
uniform float uDepth;
uniform vec4 uHighlightColor;
uniform vec4 uShadowColor;
uniform float uRoundness;
out vec4 FragColor;
void main()
{
vec4 center = texture(uInputTex, vTexCoord);
// Light direction from angle parameter
float rad = radians(float(uAngle));
vec2 lightDir = vec2(cos(rad), sin(rad));
// Compute alpha gradient using weighted neighbor sampling
vec2 gradient = vec2(0.0);
float totalWeight = 0.0;
for (int dy = -uSize; dy <= uSize; dy++)
{
for (int dx = -uSize; dx <= uSize; dx++)
{
if (dx == 0 && dy == 0) continue;
float dist = length(vec2(float(dx), float(dy)));
if (dist > float(uSize)) continue;
float weight = 1.0 - (dist / float(uSize + 1));
vec2 offset = vec2(float(dx), float(dy)) * uTexelSize;
float neighborAlpha = texture(uInputTex, vTexCoord + offset).a;
vec2 dir = normalize(vec2(float(dx), float(dy)));
gradient += dir * (neighborAlpha - center.a) * weight;
totalWeight += weight;
}
}
if (totalWeight > 0.0)
gradient /= totalWeight;
float gradMag = length(gradient);
// No edge detected — pass through original pixel
if (gradMag < 0.001)
{
FragColor = center;
return;
}
vec2 gradDir = normalize(gradient);
float facing = dot(gradDir, lightDir);
// Roundness reshapes the facing curve
if (abs(uRoundness) > 0.001)
{
float sign_f = facing >= 0.0 ? 1.0 : -1.0;
float abs_f = abs(facing);
float curve = exp2(-uRoundness * 2.0);
facing = sign_f * pow(abs_f, curve);
}
float edgeStrength = clamp(gradMag * float(uSize + 1) * 3.0, 0.0, 1.0);
float intensity = edgeStrength * uDepth;
float highlightFactor = max(facing, 0.0);
float shadowFactor = max(-facing, 0.0);
float hAlpha = clamp(highlightFactor * intensity * uHighlightColor.a, 0.0, 1.0);
float sAlpha = clamp(shadowFactor * intensity * uShadowColor.a, 0.0, 1.0);
// Inner emboss: blend onto opaque pixels
// Outer emboss: paint into transparent areas past the border
if (center.a > 0.0)
{
vec3 result = center.rgb;
result = mix(result, uHighlightColor.rgb, hAlpha);
result = mix(result, uShadowColor.rgb, sAlpha);
FragColor = vec4(result, center.a);
}
else
{
float totalAlpha = max(hAlpha, sAlpha);
vec3 color = mix(uShadowColor.rgb, uHighlightColor.rgb, hAlpha / max(hAlpha + sAlpha, 0.001));
FragColor = vec4(color, totalAlpha);
}
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform vec2 uDirection;
uniform int uRadius;
out vec4 FragColor;
void main()
{
if (uRadius <= 0) { FragColor = texture(uInputTex, vTexCoord); return; }
vec4 sum = vec4(0.0);
float totalWeight = 0.0;
float sigma = max(float(uRadius) * 0.5, 0.5);
float invSigma2 = 1.0 / (2.0 * sigma * sigma);
for (int i = -uRadius; i <= uRadius; i++)
{
float weight = exp(-float(i * i) * invSigma2);
vec2 offset = uDirection * float(i) * uTexelSize;
sum += texture(uInputTex, vTexCoord + offset) * weight;
totalWeight += weight;
}
FragColor = sum / totalWeight;
}Gaussian BlurBlur
Smooths pixels using a two-pass Gaussian blur
{
"id": "gaussian-blur",
"name": "Gaussian Blur",
"author": "SpriteGrid",
"version": "1.3",
"description": "Smooths pixels using a two-pass Gaussian blur",
"category": "Blur",
"tiling": false,
"parameters": [
{
"id": "radius",
"name": "Radius",
"type": "int",
"default": 2,
"min": 1,
"max": 32,
"ui": "slider"
}
],
"passes": [
{
"shader": "blur.frag",
"uniforms": {
"uDirection": [1.0, 0.0],
"uRadius": "$radius"
}
},
{
"shader": "blur.frag",
"uniforms": {
"uDirection": [0.0, 1.0],
"uRadius": "$radius"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform vec2 uDirection;
uniform int uRadius;
out vec4 FragColor;
void main()
{
vec4 sum = vec4(0.0);
float totalWeight = 0.0;
float sigma = float(uRadius) * 0.5;
float invSigma2 = 1.0 / (2.0 * sigma * sigma);
for (int i = -uRadius; i <= uRadius; i++)
{
float weight = exp(-float(i * i) * invSigma2);
vec2 offset = uDirection * float(i) * uTexelSize;
vec4 s = texture(uInputTex, vTexCoord + offset);
// Premultiply: weight RGB by alpha so transparent pixels
// contribute (0,0,0) instead of (black * weight)
sum += vec4(s.rgb * s.a, s.a) * weight;
totalWeight += weight;
}
vec4 blurred = sum / totalWeight;
// Unpremultiply to recover straight-alpha RGB
if (blurred.a > 0.001)
FragColor = vec4(blurred.rgb / blurred.a, blurred.a);
else
FragColor = vec4(0.0);
}GlowGlow
Adds a colored glow inside or outside sprite boundaries
{
"id": "glow",
"name": "Glow",
"author": "SpriteGrid",
"version": "1.1",
"description": "Adds a colored glow inside or outside sprite boundaries",
"category": "Glow",
"tiling": false,
"compositeMode": "replace",
"parameters": [
{ "id": "inner", "name": "Inner Glow", "type": "bool", "default": false, "ui": "checkbox" },
{ "id": "color", "name": "Color", "type": "color", "default": "#FFD700FF", "ui": "color" },
{ "id": "size", "name": "Size", "type": "int", "default": 4, "min": 1, "max": 16, "ui": "slider" },
{ "id": "intensity", "name": "Intensity", "type": "float", "default": 0.8, "min": 0.0, "max": 2.0, "ui": "slider" }
],
"passes": [
{
"shader": "glow.frag",
"uniforms": {
"uInner": "$inner",
"uGlowColor": "$color",
"uSize": "$size",
"uIntensity": "$intensity"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform int uInner;
uniform vec4 uGlowColor;
uniform int uSize;
uniform float uIntensity;
out vec4 FragColor;
void main()
{
vec4 center = texture(uInputTex, vTexCoord);
// Outer glow mode
if (uInner == 0)
{
// Opaque pixel — output unchanged
if (center.a > 0.5)
{
FragColor = center;
return;
}
// Transparent pixel — search for nearest opaque neighbor
float minDist = float(uSize) + 1.0;
for (int dy = -uSize; dy <= uSize; dy++)
{
for (int dx = -uSize; dx <= uSize; dx++)
{
float dist = length(vec2(float(dx), float(dy)));
if (dist > float(uSize) || dist >= minDist) continue;
vec2 offset = vec2(float(dx), float(dy)) * uTexelSize;
float neighborAlpha = texture(uInputTex, vTexCoord + offset).a;
if (neighborAlpha > 0.5)
minDist = dist;
}
}
if (minDist > float(uSize))
{
FragColor = vec4(0.0);
return;
}
// Distance-based falloff
float falloff = 1.0 - (minDist / float(uSize));
falloff = smoothstep(0.0, 1.0, falloff);
float glowAlpha = falloff * uIntensity * uGlowColor.a;
glowAlpha = clamp(glowAlpha, 0.0, 1.0);
FragColor = vec4(uGlowColor.rgb, glowAlpha);
}
// Inner glow mode
else
{
// Transparent pixel — output transparent
if (center.a < 0.01)
{
FragColor = vec4(0.0);
return;
}
// Search for nearest transparent neighbor
float minDist = float(uSize) + 1.0;
for (int dy = -uSize; dy <= uSize; dy++)
{
for (int dx = -uSize; dx <= uSize; dx++)
{
float dist = length(vec2(float(dx), float(dy)));
if (dist > float(uSize) || dist >= minDist) continue;
vec2 offset = vec2(float(dx), float(dy)) * uTexelSize;
float neighborAlpha = texture(uInputTex, vTexCoord + offset).a;
if (neighborAlpha < 0.5)
minDist = dist;
}
}
// Interior pixel far from edge — output unchanged
if (minDist > float(uSize))
{
FragColor = center;
return;
}
// Distance-based falloff (closer to edge = stronger glow)
float falloff = 1.0 - (minDist / float(uSize));
falloff = smoothstep(0.0, 1.0, falloff);
float glowStrength = falloff * uIntensity * uGlowColor.a;
glowStrength = clamp(glowStrength, 0.0, 1.0);
// Blend glow color over original
vec3 result = mix(center.rgb, uGlowColor.rgb, glowStrength);
FragColor = vec4(result, center.a);
}
}Gradient MapColor
Maps pixel luminance to a user-defined gradient
{
"id": "gradient-map",
"name": "Gradient Map",
"author": "SpriteGrid",
"version": "1.0",
"description": "Maps pixel luminance to a user-defined gradient",
"category": "Color",
"parameters": [
{
"id": "gradient",
"name": "Gradient",
"type": "gradient"
}
],
"passes": [
{
"shader": "gradient-map.frag",
"uniforms": {
"uGradientLut": "$gradient"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform sampler2D uGradientLut;
out vec4 FragColor;
void main()
{
vec4 src = texture(uInputTex, vTexCoord);
if (src.a < 0.001) { FragColor = vec4(0.0); return; }
float lum = dot(src.rgb, vec3(0.2126, 0.7152, 0.0722));
vec4 gradColor = texture(uGradientLut, vec2(lum, 0.5));
FragColor = vec4(gradColor.rgb, gradColor.a * src.a);
}HSVColor
Adjusts hue rotation, saturation, and value (brightness) of the layer
{
"id": "hsv",
"name": "HSV",
"author": "SpriteGrid",
"version": "1.0",
"description": "Adjusts hue rotation, saturation, and value (brightness) of the layer",
"category": "Color",
"parameters": [
{ "id": "hue", "name": "Hue Shift", "type": "int", "default": 0, "min": -180, "max": 180, "ui": "slider" },
{ "id": "saturation", "name": "Saturation", "type": "float", "default": 1.0, "min": 0.0, "max": 2.0, "ui": "slider" },
{ "id": "value", "name": "Value", "type": "float", "default": 1.0, "min": 0.0, "max": 2.0, "ui": "slider" }
],
"passes": [
{
"shader": "hsv.frag",
"uniforms": {
"uHue": "$hue",
"uSaturation": "$saturation",
"uValue": "$value"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform int uHue;
uniform float uSaturation;
uniform float uValue;
out vec4 FragColor;
vec3 rgbToHsv(vec3 c)
{
float cMax = max(c.r, max(c.g, c.b));
float cMin = min(c.r, min(c.g, c.b));
float delta = cMax - cMin;
float h = 0.0;
if (delta > 0.0)
{
if (cMax == c.r)
h = mod((c.g - c.b) / delta, 6.0);
else if (cMax == c.g)
h = (c.b - c.r) / delta + 2.0;
else
h = (c.r - c.g) / delta + 4.0;
h /= 6.0;
}
float s = (cMax > 0.0) ? (delta / cMax) : 0.0;
float v = cMax;
return vec3(h, s, v);
}
vec3 hsvToRgb(vec3 c)
{
float h = c.x * 6.0;
float s = c.y;
float v = c.z;
float i = floor(h);
float f = h - i;
float p = v * (1.0 - s);
float q = v * (1.0 - s * f);
float t = v * (1.0 - s * (1.0 - f));
int sector = int(mod(i, 6.0));
if (sector == 0) return vec3(v, t, p);
if (sector == 1) return vec3(q, v, p);
if (sector == 2) return vec3(p, v, t);
if (sector == 3) return vec3(p, q, v);
if (sector == 4) return vec3(t, p, v);
return vec3(v, p, q);
}
void main()
{
vec4 src = texture(uInputTex, vTexCoord);
if (src.a < 0.001) { FragColor = vec4(0.0); return; }
vec3 hsv = rgbToHsv(src.rgb);
hsv.x = fract(hsv.x + float(uHue) / 360.0);
hsv.y = clamp(hsv.y * uSaturation, 0.0, 1.0);
hsv.z = clamp(hsv.z * uValue, 0.0, 1.0);
FragColor = vec4(hsvToRgb(hsv), src.a);
}Invert ColorsColor
Inverts all color channels
{
"id": "invert",
"name": "Invert Colors",
"author": "SpriteGrid",
"version": "1.0",
"description": "Inverts all color channels",
"category": "Color",
"parameters": [
{
"id": "strength",
"name": "Strength",
"type": "float",
"default": 1.0,
"min": 0.0,
"max": 1.0,
"ui": "slider"
}
],
"passes": [
{
"shader": "invert.frag",
"uniforms": {
"uStrength": "$strength"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform float uStrength;
out vec4 FragColor;
void main()
{
vec4 color = texture(uInputTex, vTexCoord);
vec3 inverted = vec3(1.0) - color.rgb;
FragColor = vec4(mix(color.rgb, inverted, uStrength), color.a);
}LevelsColor
Remaps input tonal range to output range with gamma control
{
"id": "levels",
"name": "Levels",
"author": "SpriteGrid",
"version": "1.1",
"description": "Remaps input tonal range to output range with gamma control",
"category": "Color",
"parameters": [
{ "id": "inBlack", "name": "Input Black", "type": "float", "default": 0.0, "min": 0.0, "max": 1.0, "ui": "slider" },
{ "id": "inWhite", "name": "Input White", "type": "float", "default": 1.0, "min": 0.0, "max": 1.0, "ui": "slider" },
{ "id": "gamma", "name": "Gamma", "type": "float", "default": 1.0, "min": 0.1, "max": 4.0, "ui": "slider" },
{ "id": "outBlack", "name": "Output Black", "type": "float", "default": 0.0, "min": 0.0, "max": 1.0, "ui": "slider" },
{ "id": "outWhite", "name": "Output White", "type": "float", "default": 1.0, "min": 0.0, "max": 1.0, "ui": "slider" },
{ "id": "channel", "name": "Channel", "type": "enum", "default": 0, "options": ["RGB", "Red", "Green", "Blue", "Alpha"], "ui": "dropdown" }
],
"passes": [
{
"shader": "levels.frag",
"uniforms": {
"uInBlack": "$inBlack",
"uInWhite": "$inWhite",
"uGamma": "$gamma",
"uOutBlack": "$outBlack",
"uOutWhite": "$outWhite",
"uChannel": "$channel"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform float uInBlack;
uniform float uInWhite;
uniform float uGamma;
uniform float uOutBlack;
uniform float uOutWhite;
uniform int uChannel;
out vec4 FragColor;
float applyLevels(float v)
{
float inRange = max(uInWhite - uInBlack, 0.001);
float mapped = clamp((v - uInBlack) / inRange, 0.0, 1.0);
mapped = pow(mapped, 1.0 / uGamma);
return mix(uOutBlack, uOutWhite, mapped);
}
void main()
{
vec4 src = texture(uInputTex, vTexCoord);
if (src.a < 0.001)
{
FragColor = vec4(0.0);
return;
}
vec3 result = src.rgb;
float alpha = src.a;
if (uChannel == 0)
{
result.r = applyLevels(result.r);
result.g = applyLevels(result.g);
result.b = applyLevels(result.b);
}
else if (uChannel == 1) result.r = applyLevels(result.r);
else if (uChannel == 2) result.g = applyLevels(result.g);
else if (uChannel == 3) result.b = applyLevels(result.b);
else alpha = applyLevels(alpha);
FragColor = vec4(result, alpha);
}OutlineStroke
Draws an outline around or inside opaque pixels
{
"id": "outline",
"name": "Outline",
"author": "SpriteGrid",
"version": "1.3",
"description": "Draws an outline around or inside opaque pixels",
"category": "Stroke",
"tiling": false,
"parameters": [
{
"id": "color",
"name": "Color",
"type": "color",
"default": "#000000FF",
"ui": "color"
},
{
"id": "thickness",
"name": "Thickness",
"type": "int",
"default": 1,
"min": 1,
"max": 8,
"ui": "slider"
},
{ "id": "interior", "name": "Interior", "type": "bool", "default": false, "ui": "checkbox" },
{ "id": "includeDoubles", "name": "Include Doubles", "type": "bool", "default": false, "ui": "checkbox" },
{ "id": "alphaThreshold", "name": "Alpha Threshold", "type": "bool", "default": false, "ui": "checkbox" },
{
"id": "thresholdValue",
"name": "Threshold",
"type": "float",
"default": 0.5,
"min": 0.0,
"max": 1.0,
"ui": "slider"
}
],
"passes": [
{
"shader": "outline.frag",
"uniforms": {
"uOutlineColor": "$color",
"uThickness": "$thickness",
"uInterior": "$interior",
"uIncludeDoubles": "$includeDoubles",
"uAlphaThreshold": "$alphaThreshold",
"uThresholdValue": "$thresholdValue"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform vec4 uOutlineColor;
uniform int uThickness;
uniform int uInterior;
uniform int uIncludeDoubles;
uniform int uAlphaThreshold;
uniform float uThresholdValue;
out vec4 FragColor;
float threshAlpha(float a)
{
if (uAlphaThreshold != 0)
return a >= uThresholdValue ? 1.0 : 0.0;
return a;
}
bool inRange(int dx, int dy)
{
if (uIncludeDoubles != 0)
return max(abs(dx), abs(dy)) <= uThickness;
else
return abs(dx) + abs(dy) <= uThickness;
}
void main()
{
vec4 center = texture(uInputTex, vTexCoord);
float centerA = threshAlpha(center.a);
if (uInterior == 0)
{
if (centerA > 0.0)
{
FragColor = vec4(center.rgb, centerA);
return;
}
float maxAlpha = 0.0;
for (int dy = -uThickness; dy <= uThickness; dy++)
{
for (int dx = -uThickness; dx <= uThickness; dx++)
{
if (dx == 0 && dy == 0) continue;
if (!inRange(dx, dy)) continue;
vec2 offset = vec2(float(dx), float(dy)) * uTexelSize;
float neighborAlpha = threshAlpha(texture(uInputTex, vTexCoord + offset).a);
maxAlpha = max(maxAlpha, neighborAlpha);
}
}
if (maxAlpha > 0.0)
FragColor = vec4(uOutlineColor.rgb, uOutlineColor.a * maxAlpha);
else
FragColor = vec4(0.0);
}
else
{
if (centerA <= 0.0)
{
FragColor = vec4(0.0);
return;
}
float minNeighborAlpha = 1.0;
for (int dy = -uThickness; dy <= uThickness; dy++)
{
for (int dx = -uThickness; dx <= uThickness; dx++)
{
if (dx == 0 && dy == 0) continue;
if (!inRange(dx, dy)) continue;
vec2 offset = vec2(float(dx), float(dy)) * uTexelSize;
float neighborAlpha = threshAlpha(texture(uInputTex, vTexCoord + offset).a);
minNeighborAlpha = min(minNeighborAlpha, neighborAlpha);
}
}
if (minNeighborAlpha < 1.0)
FragColor = vec4(uOutlineColor.rgb, uOutlineColor.a * centerA);
else
FragColor = vec4(center.rgb, centerA);
}
}Palette SnapColor
Snaps all colors to their nearest match in a palette
{
"id": "palette-snap",
"name": "Palette Snap",
"author": "SpriteGrid",
"version": "1.7",
"description": "Snaps all colors to their nearest match in a palette",
"category": "Color",
"parameters": [
{ "id": "mode", "name": "Mode", "type": "enum", "default": 0, "options": ["OkLab", "RGB"], "ui": "dropdown" },
{ "id": "lightBias", "name": "Lightness Bias", "type": "float", "default": 1.0, "min": 0.0, "max": 5.0, "ui": "slider" },
{ "id": "chromaBias", "name": "Chroma Bias", "type": "float", "default": 1.0, "min": 0.0, "max": 5.0, "ui": "slider" },
{ "id": "grayscaleFirst", "name": "Grayscale First", "type": "bool", "default": false, "ui": "checkbox" },
{ "id": "palette", "name": "Palette", "type": "palette" }
],
"passes": [
{
"shader": "palette-snap.frag",
"uniforms": {
"uMode": "$mode",
"uLightBias": "$lightBias",
"uChromaBias": "$chromaBias",
"uGrayscaleFirst": "$grayscaleFirst",
"uPaletteLut": "$palette"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform sampler2D uPaletteLut;
uniform int uPaletteLutCount;
uniform int uMode;
uniform float uLightBias;
uniform float uChromaBias;
uniform int uGrayscaleFirst;
out vec4 FragColor;
float srgbToLinear(float c)
{
return (c > 0.04045) ? pow((c + 0.055) / 1.055, 2.4) : (c / 12.92);
}
vec3 srgbToOklab(vec3 c)
{
vec3 lin = vec3(srgbToLinear(c.r), srgbToLinear(c.g), srgbToLinear(c.b));
float l = 0.4122214708 * lin.r + 0.5363325363 * lin.g + 0.0514459929 * lin.b;
float m = 0.2119034982 * lin.r + 0.6806995451 * lin.g + 0.1073969566 * lin.b;
float s = 0.0883024619 * lin.r + 0.2817188376 * lin.g + 0.6299787005 * lin.b;
float l_ = pow(max(l, 0.0), 1.0 / 3.0);
float m_ = pow(max(m, 0.0), 1.0 / 3.0);
float s_ = pow(max(s, 0.0), 1.0 / 3.0);
return vec3(
0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_
);
}
float colorDistance(vec3 srgb1, vec3 srgb2)
{
if (uMode == 1)
{
vec3 d = srgb1 - srgb2;
return dot(d, d);
}
vec3 lab1 = srgbToOklab(srgb1);
vec3 lab2 = srgbToOklab(srgb2);
float dL = lab1.x - lab2.x;
float da = lab1.y - lab2.y;
float db = lab1.z - lab2.z;
return dL * dL * uLightBias + (da * da + db * db) * uChromaBias;
}
void main()
{
vec4 src = texture(uInputTex, vTexCoord);
if (src.a < 0.001)
{
FragColor = vec4(0.0);
return;
}
vec3 color = src.rgb;
if (uGrayscaleFirst != 0)
{
float lum = dot(color, vec3(0.2126, 0.7152, 0.0722));
color = vec3(lum);
}
float bestDist = 1e10;
vec3 bestColor = color;
float palSize = float(uPaletteLutCount);
for (int i = 0; i < uPaletteLutCount; i++)
{
float u = (float(i) + 0.5) / palSize;
vec4 palColor = texture(uPaletteLut, vec2(u, 0.5));
if (palColor.a < 0.5) continue;
float dist = colorDistance(color, palColor.rgb);
if (dist < bestDist)
{
bestDist = dist;
bestColor = palColor.rgb;
}
}
FragColor = vec4(bestColor, src.a);
}PosterizeColor
Reduces color depth by quantizing channels in RGB, HSL, or OkLab
{
"id": "posterize",
"name": "Posterize",
"author": "SpriteGrid",
"version": "1.4",
"description": "Reduces color depth by quantizing channels in RGB, HSL, or OkLab",
"category": "Color",
"parameters": [
{ "id": "levels", "name": "Levels", "type": "int", "default": 4, "min": 2, "max": 64, "ui": "slider" },
{ "id": "mode", "name": "Color Space", "type": "enum", "default": "OkLab", "options": ["RGB", "HSL", "OkLab"], "ui": "dropdown" },
{ "id": "hueLevels", "name": "Hue / Chroma Levels", "type": "int", "default": 0, "min": 0, "max": 64, "ui": "slider" },
{ "id": "dither", "name": "Dither", "type": "float", "default": 0.0, "min": 0.0, "max": 1.0, "ui": "slider" },
{ "id": "strength", "name": "Strength", "type": "float", "default": 1.0, "min": 0.0, "max": 1.0, "ui": "slider" }
],
"passes": [
{
"shader": "posterize.frag",
"uniforms": {
"uLevels": "$levels",
"uMode": "$mode",
"uHueLevels": "$hueLevels",
"uDither": "$dither",
"uStrength": "$strength"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uResolution;
uniform int uLevels;
uniform int uMode; // 0=RGB, 1=HSL, 2=OkLab
uniform int uHueLevels;
uniform float uDither;
uniform float uStrength;
out vec4 FragColor;
float bayerThreshold(vec2 pos)
{
vec2 p = mod(floor(pos), 4.0);
float a = mod(p.x + p.y * 2.0, 4.0);
float b = mod(p.x * 2.0 + p.y, 4.0);
return (a * 4.0 + b + 0.5) / 16.0 - 0.5;
}
float quantize(float val, float steps, float dither)
{
float v = val * steps + dither;
return clamp(floor(v + 0.5) / steps, 0.0, 1.0);
}
float quantizeSigned(float val, float lo, float hi, float steps, float dither)
{
float t = (val - lo) / (hi - lo);
float q = clamp(floor(t * steps + dither + 0.5) / steps, 0.0, 1.0);
return lo + q * (hi - lo);
}
// === HSL ===
vec3 rgbToHsl(vec3 c)
{
float mx = max(c.r, max(c.g, c.b));
float mn = min(c.r, min(c.g, c.b));
float l = (mx + mn) * 0.5;
if (mx == mn) return vec3(0.0, 0.0, l);
float d = mx - mn;
float s = l > 0.5 ? d / (2.0 - mx - mn) : d / (mx + mn);
float h;
if (mx == c.r) h = (c.g - c.b) / d + (c.g < c.b ? 6.0 : 0.0);
else if (mx == c.g) h = (c.b - c.r) / d + 2.0;
else h = (c.r - c.g) / d + 4.0;
h /= 6.0;
return vec3(h, s, l);
}
float hue2rgb(float p, float q, float t)
{
if (t < 0.0) t += 1.0;
if (t > 1.0) t -= 1.0;
if (t < 1.0/6.0) return p + (q - p) * 6.0 * t;
if (t < 1.0/2.0) return q;
if (t < 2.0/3.0) return p + (q - p) * (2.0/3.0 - t) * 6.0;
return p;
}
vec3 hslToRgb(vec3 hsl)
{
if (hsl.y == 0.0) return vec3(hsl.z);
float q = hsl.z < 0.5 ? hsl.z * (1.0 + hsl.y) : hsl.z + hsl.y - hsl.z * hsl.y;
float p = 2.0 * hsl.z - q;
return vec3(
hue2rgb(p, q, hsl.x + 1.0/3.0),
hue2rgb(p, q, hsl.x),
hue2rgb(p, q, hsl.x - 1.0/3.0)
);
}
// === OkLab ===
float srgbToLinear(float c)
{
return (c > 0.04045) ? pow((c + 0.055) / 1.055, 2.4) : (c / 12.92);
}
float linearToSrgb(float c)
{
return (c > 0.0031308) ? 1.055 * pow(c, 1.0 / 2.4) - 0.055 : 12.92 * c;
}
vec3 srgbToOklab(vec3 c)
{
vec3 lin = vec3(srgbToLinear(c.r), srgbToLinear(c.g), srgbToLinear(c.b));
float l = 0.4122214708 * lin.r + 0.5363325363 * lin.g + 0.0514459929 * lin.b;
float m = 0.2119034982 * lin.r + 0.6806995451 * lin.g + 0.1073969566 * lin.b;
float s = 0.0883024619 * lin.r + 0.2817188376 * lin.g + 0.6299787005 * lin.b;
float l_ = pow(max(l, 0.0), 1.0 / 3.0);
float m_ = pow(max(m, 0.0), 1.0 / 3.0);
float s_ = pow(max(s, 0.0), 1.0 / 3.0);
return vec3(
0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_
);
}
vec3 oklabToSrgb(vec3 lab)
{
float l_ = lab.x + 0.3963377774 * lab.y + 0.2158037573 * lab.z;
float m_ = lab.x - 0.1055613458 * lab.y - 0.0638541728 * lab.z;
float s_ = lab.x - 0.0894841775 * lab.y - 1.2914855480 * lab.z;
float l = l_ * l_ * l_;
float m = m_ * m_ * m_;
float s = s_ * s_ * s_;
float r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
float g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
float b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
return vec3(
linearToSrgb(clamp(r, 0.0, 1.0)),
linearToSrgb(clamp(g, 0.0, 1.0)),
linearToSrgb(clamp(b, 0.0, 1.0))
);
}
void main()
{
vec4 src = texture(uInputTex, vTexCoord);
if (src.a < 0.001) { FragColor = vec4(0.0); return; }
float steps = float(uLevels);
float d = bayerThreshold(gl_FragCoord.xy) * uDither;
vec3 result;
if (uMode == 2)
{
// OkLab: quantize lightness, optionally quantize chroma
vec3 lab = srgbToOklab(src.rgb);
lab.x = quantize(lab.x, steps, d);
if (uHueLevels > 0)
{
float cSteps = float(uHueLevels);
lab.y = quantizeSigned(lab.y, -0.4, 0.4, cSteps, d);
lab.z = quantizeSigned(lab.z, -0.4, 0.4, cSteps, d);
}
result = oklabToSrgb(lab);
}
else if (uMode == 1)
{
// HSL: quantize lightness+saturation, optionally quantize hue
vec3 hsl = rgbToHsl(src.rgb);
if (uHueLevels > 0)
hsl.x = quantize(hsl.x, float(uHueLevels), d);
hsl.y = quantize(hsl.y, steps, d);
hsl.z = quantize(hsl.z, steps, d);
result = hslToRgb(hsl);
}
else
{
// RGB quantization
result.r = quantize(src.r, steps, d);
result.g = quantize(src.g, steps, d);
result.b = quantize(src.b, steps, d);
}
FragColor = vec4(mix(src.rgb, result, uStrength), src.a);
}Rim LightStyle
Places a bright highlight rim on the light-facing edge of the sprite, leaving the opposite side untouched
{
"id": "rim-light",
"name": "Rim Light",
"author": "SpriteGrid",
"version": "1.0",
"description": "Places a bright highlight rim on the light-facing edge of the sprite, leaving the opposite side untouched",
"category": "Style",
"tiling": false,
"compositeMode": "replace",
"parameters": [
{ "id": "angle", "name": "Light Angle", "type": "int", "default": 135, "min": 0, "max": 359, "ui": "slider" },
{ "id": "color", "name": "Rim Color", "type": "color", "default": "#FFFFFFD0", "ui": "color" },
{ "id": "intensity", "name": "Intensity", "type": "float", "default": 1.0, "min": 0.0, "max": 2.0, "ui": "slider" }
],
"passes": [
{
"shader": "rim-light.frag",
"uniforms": {
"uAngle": "$angle",
"uRimColor": "$color",
"uIntensity": "$intensity"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform int uAngle;
uniform vec4 uRimColor;
uniform float uIntensity;
out vec4 FragColor;
void main()
{
vec4 center = texture(uInputTex, vTexCoord);
// Only paint onto opaque pixels
if (center.a < 0.01)
{
FragColor = vec4(0.0);
return;
}
// Compute alpha gradient from 8-neighbor kernel.
// The gradient points from low-alpha toward high-alpha (into the sprite from the edge).
vec2 gradient = vec2(0.0);
for (int dy = -1; dy <= 1; dy++)
{
for (int dx = -1; dx <= 1; dx++)
{
if (dx == 0 && dy == 0) continue;
vec2 offset = vec2(float(dx), float(dy)) * uTexelSize;
float neighborAlpha = texture(uInputTex, vTexCoord + offset).a;
gradient += normalize(vec2(float(dx), float(dy))) * (neighborAlpha - center.a);
}
}
float gradMag = length(gradient);
// Interior pixel — no edge detected, pass through unchanged
if (gradMag < 0.05)
{
FragColor = center;
return;
}
vec2 gradDir = normalize(gradient);
// Light direction from angle parameter (same convention as bevel/emboss)
float rad = radians(float(uAngle));
vec2 lightDir = vec2(cos(rad), sin(rad));
// facing > 0 means gradient aligns with light — this is the lit edge
float facing = dot(gradDir, lightDir);
if (facing <= 0.0)
{
FragColor = center;
return;
}
float edgeStrength = clamp(gradMag * 3.0, 0.0, 1.0);
float rimStrength = clamp(edgeStrength * facing * uIntensity * uRimColor.a, 0.0, 1.0);
vec3 result = mix(center.rgb, uRimColor.rgb, rimStrength);
FragColor = vec4(result, center.a);
}Sobel EdgeStyle
Pixel-perfect color edge detection within opaque regions
{
"id": "sobel-edge",
"name": "Sobel Edge",
"author": "SpriteGrid",
"version": "1.6",
"description": "Pixel-perfect color edge detection within opaque regions",
"category": "Style",
"tiling": false,
"compositeMode": "replace",
"parameters": [
{ "id": "color", "name": "Edge Color", "type": "color", "default": "#000000FF", "ui": "color" },
{ "id": "threshold", "name": "Threshold", "type": "float", "default": 0.1, "min": 0.0, "max": 1.0, "ui": "slider" },
{ "id": "blur", "name": "Blur", "type": "int", "default": 0, "min": 0, "max": 16, "ui": "slider" },
{ "id": "silhouette", "name": "Include Silhouette", "type": "bool", "default": false, "ui": "checkbox" },
{ "id": "includeDoubles", "name": "Include Doubles", "type": "bool", "default": false, "ui": "checkbox" },
{ "id": "mode", "name": "Blend Mode", "type": "enum", "default": "Replace", "options": ["Replace", "Multiply", "Overlay"], "ui": "dropdown" },
{ "id": "opacity", "name": "Opacity", "type": "float", "default": 1.0, "min": 0.0, "max": 1.0, "ui": "slider" }
],
"passes": [
{
"shader": "sobel-edge.frag",
"uniforms": {
"uEdgeColor": "$color",
"uThreshold": "$threshold",
"uSilhouette": "$silhouette",
"uIncludeDoubles": "$includeDoubles"
}
},
{
"shader": "sobel-blur.frag",
"uniforms": {
"uDirection": [1.0, 0.0],
"uRadius": "$blur"
}
},
{
"shader": "sobel-blur.frag",
"uniforms": {
"uDirection": [0.0, 1.0],
"uRadius": "$blur"
}
},
{
"shader": "sobel-composite.frag",
"uniforms": {
"uBlendMode": "$mode",
"uOpacity": "$opacity"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform vec4 uEdgeColor;
uniform float uThreshold;
uniform int uSilhouette;
uniform int uIncludeDoubles;
out vec4 FragColor;
void main()
{
vec4 center = texture(uInputTex, vTexCoord);
if (center.a < 0.5)
{
FragColor = vec4(0.0);
return;
}
vec4 sT = texture(uInputTex, vTexCoord + vec2( 0.0, -1.0) * uTexelSize);
vec4 sB = texture(uInputTex, vTexCoord + vec2( 0.0, 1.0) * uTexelSize);
vec4 sL = texture(uInputTex, vTexCoord + vec2(-1.0, 0.0) * uTexelSize);
vec4 sR = texture(uInputTex, vTexCoord + vec2( 1.0, 0.0) * uTexelSize);
vec3 lum = vec3(0.2126, 0.7152, 0.0722);
float cL = dot(center.rgb, lum);
float maxDiff = 0.0;
bool onAlphaBoundary = false;
if (sT.a >= 0.5) maxDiff = max(maxDiff, abs(cL - dot(sT.rgb, lum)));
else onAlphaBoundary = true;
if (sB.a >= 0.5) maxDiff = max(maxDiff, abs(cL - dot(sB.rgb, lum)));
else onAlphaBoundary = true;
if (sL.a >= 0.5) maxDiff = max(maxDiff, abs(cL - dot(sL.rgb, lum)));
else onAlphaBoundary = true;
if (sR.a >= 0.5) maxDiff = max(maxDiff, abs(cL - dot(sR.rgb, lum)));
else onAlphaBoundary = true;
if (uIncludeDoubles != 0)
{
vec4 sTL = texture(uInputTex, vTexCoord + vec2(-1.0, -1.0) * uTexelSize);
vec4 sTR = texture(uInputTex, vTexCoord + vec2( 1.0, -1.0) * uTexelSize);
vec4 sBL = texture(uInputTex, vTexCoord + vec2(-1.0, 1.0) * uTexelSize);
vec4 sBR = texture(uInputTex, vTexCoord + vec2( 1.0, 1.0) * uTexelSize);
if (sTL.a >= 0.5) maxDiff = max(maxDiff, abs(cL - dot(sTL.rgb, lum)));
else onAlphaBoundary = true;
if (sTR.a >= 0.5) maxDiff = max(maxDiff, abs(cL - dot(sTR.rgb, lum)));
else onAlphaBoundary = true;
if (sBL.a >= 0.5) maxDiff = max(maxDiff, abs(cL - dot(sBL.rgb, lum)));
else onAlphaBoundary = true;
if (sBR.a >= 0.5) maxDiff = max(maxDiff, abs(cL - dot(sBR.rgb, lum)));
else onAlphaBoundary = true;
}
bool isEdge = maxDiff > uThreshold;
if (uSilhouette != 0 && onAlphaBoundary)
isEdge = true;
if (!isEdge)
{
FragColor = vec4(0.0);
return;
}
FragColor = uEdgeColor;
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uTexelSize;
uniform vec2 uDirection;
uniform int uRadius;
out vec4 FragColor;
void main()
{
if (uRadius <= 0)
{
FragColor = texture(uInputTex, vTexCoord);
return;
}
vec4 sum = vec4(0.0);
float totalWeight = 0.0;
float sigma = float(uRadius) * 0.5;
float invSigma2 = 1.0 / (2.0 * sigma * sigma);
for (int i = -uRadius; i <= uRadius; i++)
{
float weight = exp(-float(i * i) * invSigma2);
vec2 offset = uDirection * float(i) * uTexelSize;
vec4 s = texture(uInputTex, vTexCoord + offset);
sum += vec4(s.rgb * s.a, s.a) * weight;
totalWeight += weight;
}
vec4 blurred = sum / totalWeight;
if (blurred.a > 0.001)
FragColor = vec4(blurred.rgb / blurred.a, blurred.a);
else
FragColor = vec4(0.0);
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform sampler2D uSourceTex;
uniform int uBlendMode;
uniform float uOpacity;
out vec4 FragColor;
void main()
{
vec4 edge = texture(uInputTex, vTexCoord);
vec4 src = texture(uSourceTex, vTexCoord);
if (edge.a < 0.001)
{
FragColor = src;
return;
}
float mask = edge.a * uOpacity;
vec3 blended;
if (uBlendMode == 1)
{
blended = src.rgb * mix(vec3(1.0), edge.rgb, mask);
}
else if (uBlendMode == 2)
{
vec3 ov;
ov.r = src.r < 0.5 ? 2.0 * src.r * edge.r : 1.0 - 2.0 * (1.0 - src.r) * (1.0 - edge.r);
ov.g = src.g < 0.5 ? 2.0 * src.g * edge.g : 1.0 - 2.0 * (1.0 - src.g) * (1.0 - edge.g);
ov.b = src.b < 0.5 ? 2.0 * src.b * edge.b : 1.0 - 2.0 * (1.0 - src.b) * (1.0 - edge.b);
blended = mix(src.rgb, ov, mask);
}
else
{
blended = mix(src.rgb, edge.rgb, mask);
}
FragColor = vec4(blended, max(src.a, edge.a * uOpacity));
}StepColor
Applies GLSL step function (threshold) to RGB channels, optionally including alpha
{
"id": "step",
"name": "Step",
"author": "SpriteGrid",
"version": "1.1",
"description": "Applies GLSL step function (threshold) to RGB channels, optionally including alpha",
"category": "Color",
"parameters": [
{
"id": "edge",
"name": "Threshold",
"type": "float",
"default": 0.5,
"min": 0.0,
"max": 1.0,
"ui": "slider"
},
{ "id": "includeAlpha", "name": "Include Alpha", "type": "bool", "default": false, "ui": "checkbox" }
],
"passes": [
{
"shader": "step.frag",
"uniforms": {
"uEdge": "$edge",
"uIncludeAlpha": "$includeAlpha"
}
}
]
}#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform float uEdge;
uniform int uIncludeAlpha;
out vec4 FragColor;
void main()
{
vec4 color = texture(uInputTex, vTexCoord);
vec3 stepped = step(vec3(uEdge), color.rgb);
float alpha = (uIncludeAlpha != 0) ? step(uEdge, color.a) : color.a;
FragColor = vec4(stepped, alpha);
}