iDither/Sources/iDither/Resources/Shaders.metal
2026-01-14 10:04:16 +01:00

170 lines
6.9 KiB
Metal

#include <metal_stdlib>
using namespace metal;
struct RenderParameters {
float brightness;
float contrast;
float pixelScale;
float colorDepth; // New parameter: 1.0 to 32.0 (Levels)
int algorithm; // 0: None, 1: Bayer 2x2, 2: Bayer 4x4, 3: Bayer 8x8, 4: Cluster 4x4, 5: Cluster 8x8, 6: Blue Noise
int isGrayscale;
};
// Bayer 2x2 Matrix
constant float bayer2x2[2][2] = {
{0.0/4.0, 2.0/4.0},
{3.0/4.0, 1.0/4.0}
};
// Bayer 4x4 Matrix
constant float bayer4x4[4][4] = {
{ 0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0 },
{12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0 },
{ 3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0 },
{15.0/16.0, 7.0/16.0, 13.0/16.0, 5.0/16.0 }
};
// Bayer 8x8 Matrix
constant float bayer8x8[8][8] = {
{ 0.0/64.0, 32.0/64.0, 8.0/64.0, 40.0/64.0, 2.0/64.0, 34.0/64.0, 10.0/64.0, 42.0/64.0 },
{48.0/64.0, 16.0/64.0, 56.0/64.0, 24.0/64.0, 50.0/64.0, 18.0/64.0, 58.0/64.0, 26.0/64.0 },
{12.0/64.0, 44.0/64.0, 4.0/64.0, 36.0/64.0, 14.0/64.0, 46.0/64.0, 6.0/64.0, 38.0/64.0 },
{60.0/64.0, 28.0/64.0, 52.0/64.0, 20.0/64.0, 62.0/64.0, 30.0/64.0, 54.0/64.0, 22.0/64.0 },
{ 3.0/64.0, 35.0/64.0, 11.0/64.0, 43.0/64.0, 1.0/64.0, 33.0/64.0, 9.0/64.0, 41.0/64.0 },
{51.0/64.0, 19.0/64.0, 59.0/64.0, 27.0/64.0, 49.0/64.0, 17.0/64.0, 57.0/64.0, 25.0/64.0 },
{15.0/64.0, 47.0/64.0, 7.0/64.0, 39.0/64.0, 13.0/64.0, 45.0/64.0, 5.0/64.0, 37.0/64.0 },
{63.0/64.0, 31.0/64.0, 55.0/64.0, 23.0/64.0, 61.0/64.0, 29.0/64.0, 53.0/64.0, 21.0/64.0 }
};
// Cluster 4x4 Matrix
constant float cluster4x4[4][4] = {
{12.0/16.0, 5.0/16.0, 6.0/16.0, 13.0/16.0},
{ 4.0/16.0, 0.0/16.0, 1.0/16.0, 7.0/16.0},
{11.0/16.0, 3.0/16.0, 2.0/16.0, 8.0/16.0},
{15.0/16.0, 10.0/16.0, 9.0/16.0, 14.0/16.0}
};
// Cluster 8x8 Matrix
constant float cluster8x8[8][8] = {
{24.0/64.0, 10.0/64.0, 12.0/64.0, 26.0/64.0, 35.0/64.0, 47.0/64.0, 49.0/64.0, 37.0/64.0},
{ 8.0/64.0, 0.0/64.0, 2.0/64.0, 14.0/64.0, 45.0/64.0, 59.0/64.0, 61.0/64.0, 51.0/64.0},
{22.0/64.0, 6.0/64.0, 4.0/64.0, 20.0/64.0, 43.0/64.0, 57.0/64.0, 63.0/64.0, 53.0/64.0},
{30.0/64.0, 18.0/64.0, 16.0/64.0, 28.0/64.0, 33.0/64.0, 41.0/64.0, 55.0/64.0, 39.0/64.0},
{34.0/64.0, 46.0/64.0, 48.0/64.0, 36.0/64.0, 25.0/64.0, 11.0/64.0, 13.0/64.0, 27.0/64.0},
{44.0/64.0, 58.0/64.0, 60.0/64.0, 50.0/64.0, 9.0/64.0, 1.0/64.0, 3.0/64.0, 15.0/64.0},
{42.0/64.0, 56.0/64.0, 62.0/64.0, 52.0/64.0, 23.0/64.0, 7.0/64.0, 5.0/64.0, 21.0/64.0},
{32.0/64.0, 40.0/64.0, 54.0/64.0, 38.0/64.0, 31.0/64.0, 19.0/64.0, 17.0/64.0, 29.0/64.0}
};
// Blue Noise 8x8 (Approx)
constant float blueNoise8x8[8][8] = {
{52.0/64.0, 21.0/64.0, 58.0/64.0, 10.0/64.0, 45.0/64.0, 33.0/64.0, 56.0/64.0, 17.0/64.0},
{ 4.0/64.0, 38.0/64.0, 28.0/64.0, 51.0/64.0, 5.0/64.0, 22.0/64.0, 40.0/64.0, 62.0/64.0},
{61.0/64.0, 12.0/64.0, 48.0/64.0, 14.0/64.0, 55.0/64.0, 36.0/64.0, 7.0/64.0, 31.0/64.0},
{32.0/64.0, 43.0/64.0, 2.0/64.0, 46.0/64.0, 25.0/64.0, 63.0/64.0, 19.0/64.0, 50.0/64.0},
{16.0/64.0, 53.0/64.0, 23.0/64.0, 60.0/64.0, 9.0/64.0, 47.0/64.0, 29.0/64.0, 6.0/64.0},
{44.0/64.0, 27.0/64.0, 39.0/64.0, 34.0/64.0, 54.0/64.0, 13.0/64.0, 59.0/64.0, 26.0/64.0},
{ 8.0/64.0, 57.0/64.0, 18.0/64.0, 1.0/64.0, 42.0/64.0, 30.0/64.0, 3.0/64.0, 49.0/64.0},
{35.0/64.0, 24.0/64.0, 0.0/64.0, 41.0/64.0, 15.0/64.0, 52.0/64.0, 20.0/64.0, 37.0/64.0}
};
float ditherChannel(float value, float threshold, float limit) {
// Quantization Formula
// value: 0.0 to 1.0
// threshold: 0.0 to 1.0 (from matrix)
// limit: colorDepth (e.g. 4.0)
float ditheredValue = value + (threshold - 0.5) * (1.0 / (limit - 1.0));
return floor(ditheredValue * (limit - 1.0) + 0.5) / (limit - 1.0);
}
kernel void ditherShader(texture2d<float, access::read> inputTexture [[texture(0)]],
texture2d<float, access::write> outputTexture [[texture(1)]],
constant RenderParameters &params [[buffer(0)]],
uint2 gid [[thread_position_in_grid]]) {
if (gid.x >= outputTexture.get_width() || gid.y >= outputTexture.get_height()) {
return;
}
// 1. Pixelation
float scale = max(1.0, params.pixelScale);
uint2 sourceCoord = uint2(floor(float(gid.x) / scale) * scale, floor(float(gid.y) / scale) * scale);
sourceCoord.x = min(sourceCoord.x, inputTexture.get_width() - 1);
sourceCoord.y = min(sourceCoord.y, inputTexture.get_height() - 1);
float4 color = inputTexture.read(sourceCoord);
// 2. Color Adjustment
float3 rgb = color.rgb;
rgb = rgb + params.brightness;
rgb = (rgb - 0.5) * params.contrast + 0.5;
// Grayscale
float luma = dot(rgb, float3(0.299, 0.587, 0.114));
if (params.isGrayscale > 0) {
rgb = float3(luma);
}
// 3. Dithering
float threshold = 0.5;
bool shouldDither = (params.algorithm > 0);
if (shouldDither) {
uint x, y;
// Fetch threshold from matrix
switch (params.algorithm) {
case 1: // Bayer 2x2
x = uint(sourceCoord.x / scale) % 2;
y = uint(sourceCoord.y / scale) % 2;
threshold = bayer2x2[y][x];
break;
case 2: // Bayer 4x4
x = uint(sourceCoord.x / scale) % 4;
y = uint(sourceCoord.y / scale) % 4;
threshold = bayer4x4[y][x];
break;
case 3: // Bayer 8x8
x = uint(sourceCoord.x / scale) % 8;
y = uint(sourceCoord.y / scale) % 8;
threshold = bayer8x8[y][x];
break;
case 4: // Cluster 4x4
x = uint(sourceCoord.x / scale) % 4;
y = uint(sourceCoord.y / scale) % 4;
threshold = cluster4x4[y][x];
break;
case 5: // Cluster 8x8
x = uint(sourceCoord.x / scale) % 8;
y = uint(sourceCoord.y / scale) % 8;
threshold = cluster8x8[y][x];
break;
case 6: // Blue Noise 8x8
x = uint(sourceCoord.x / scale) % 8;
y = uint(sourceCoord.y / scale) % 8;
threshold = blueNoise8x8[y][x];
break;
default:
break;
}
// Apply Quantized Dithering
if (params.isGrayscale > 0) {
// Apply only to luma (which is already in rgb)
rgb.r = ditherChannel(rgb.r, threshold, params.colorDepth);
rgb.g = rgb.r;
rgb.b = rgb.r;
} else {
// Apply to each channel
rgb.r = ditherChannel(rgb.r, threshold, params.colorDepth);
rgb.g = ditherChannel(rgb.g, threshold, params.colorDepth);
rgb.b = ditherChannel(rgb.b, threshold, params.colorDepth);
}
}
outputTexture.write(float4(rgb, color.a), gid);
}