summaryrefslogtreecommitdiff
path: root/src/shaders/math/color_c64.wgsl
blob: ef8f07316352e8d4553e2b79f7c0935a8a651a52 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// C64 palette and ordered-dither quantization.
// Provides Dither() to snap any RGBA color to the nearest C64 color pair
// via 8x8 Bayer threshold, suitable for NTSC and retro post-process effects.

const NUM_COLORS : u32 = 16;
const C64Colors = array<vec3f, NUM_COLORS>(
  vec3f(13.0/255.0, 13.0/255.0, 13.0/255.0),       // 0 black
  vec3f(242.0/255.0, 242.0/255.0, 242.0/255.0),    // 1 white
  vec3f(89.0/255.0, 39.0/255.0, 33.0/255.0),       // 2 red
  vec3f(170.0/255.0, 220.0/255.0, 240.0/255.0),    // 3 cyan

  vec3f(101.0/255.0, 27.0/255.0, 109.0/255.0),     // 4 purple
  vec3f(120.0/255.0, 198.0/255.0, 112.0/255.0),    // 5 green
  vec3f(54.0/255.0, 53.0/255.0, 152.0/255.0),      // 6 blue
  vec3f(226.0/255.0, 229.0/255.0, 115.0/255.0),    // 7 yellow

  vec3f(141.0/255.0, 85.0/255.0, 50.0/255.0),      // 8 orange
  vec3f(89.0/255.0, 63.0/255.0, 13.0/255.0),       // 9 brown
  vec3f(189.0/255.0, 110.0/255.0, 115.0/255.0),    // 10 pink
  vec3f(80.0/255.0, 80.0/255.0, 80.0/255.0),       // 11 dark gray

  vec3f(124.0/255.0, 124.0/255.0, 124.0/255.0),    // 12 gray
  vec3f(165.0/255.0, 242.0/255.0, 156.0/255.0),    // 13 bright green
  vec3f(114.0/255.0, 111.0/255.0, 224.0/255.0),    // 14 light blue
  vec3f(128.0/255.0, 128.0/255.0, 128.0/255.0)     // 15 middle gray
);

const BAYER_8X8 = array<u32, 64>(
     0, 32,  8, 40,  2, 34, 10, 42,
    48, 16, 56, 24, 50, 18, 58, 26,
    12, 44,  4, 36, 14, 46,  6, 38,
    60, 28, 52, 20, 62, 30, 54, 22,
     3, 35, 11, 43,  1, 33,  9, 41,
    51, 19, 59, 27, 49, 17, 57, 25,
    15, 47,  7, 39, 13, 45,  5, 37,
    63, 31, 55, 23, 61, 29, 53, 21
);

const DistanceParam = 0.65;
fn colorDistance(color: vec4f, c1: vec4f, c2: vec4f, frac: f32) -> f32 {
  return mix(distance(color, mix(c1, c2, frac)),
             distance(color, c1) + distance(color, c2),
             0.5 * DistanceParam * DistanceParam);
}

// Quantize col to the nearest C64 color pair using 8x8 Bayer dithering.
// xsize/ysize: virtual pixel grid dimensions (e.g. 54.*8., 33.*8.).
fn Dither(col: vec4f, uv: vec2f, xsize: f32, ysize: f32) -> vec4f {
  let ix = u32(floor(uv.x * xsize / 2.)) % 8;
  let iy = u32(floor(uv.y * ysize / 2.)) % 8;
  let thresh = f32(BAYER_8X8[ix + 8 * iy]) / 64.;

  var color = min(col, vec4f(1.));
  color *= color;  // ~gamma

  var c1 = vec4f(0.);
  var c2 = vec4f(0.);
  var frac = 0.0;
  var best = 1.e5;

  for (var i = 0u; i < NUM_COLORS; i += 1u) {
    var p1 = vec4f(C64Colors[i], 1.);
    p1 *= p1;
    for (var j = i + 1u; j < NUM_COLORS; j += 1u) {
      var p2 = vec4f(C64Colors[j], 1.);
      p2 *= p2;

      // min-L2 optim
      let dc = p1 - p2;
      var a = dot(dc, p1 - color) / dot(dc, dc);
      a = clamp(a, 0., 1.);
      let dist = colorDistance(color, p1, p2, a);
      if (dist < best) {
        best = dist;
        c1 = p1;
        c2 = p2;
        frac = a;
      }
    }
  }
  color = mix(c1, c2, f32(frac > thresh));
  return sqrt(color);
}