summaryrefslogtreecommitdiff
path: root/shaders/image.frag.glsl
blob: 747ceef7efc7ca3c2a727291b5c2fbf190599dc7 (plain) (blame)
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#version 450

layout(set = 2, binding = 0) uniform sampler2D u_image;

layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_panel_uv;
layout(location = 2) flat in uvec4 v_effect;
layout(location = 0) out vec4 o_col;

float cellNoise(uint serial, uint col, uint row) {
    uint x = serial ^ (col * 0x9e3779b9u) ^ (row * 0x85ebca6bu);
    x ^= x >> 16;
    x *= 0x7feb352du;
    x ^= x >> 15;
    x *= 0x846ca68bu;
    x ^= x >> 16;
    return float(x & 0xffffu) / 65535.0;
}

// The same seven procedural ASCII marks as ui.frag.glsl and the macOS Metal
// panel kernel. Native attachments are content in the panel, so they should
// not switch to a smaller two-symbol animation when the glyph grid over them
// is using `. : + * # % @`.
float asciiGlyph(vec2 uv, int glyph) {
    vec2 p = uv * 2.0 - 1.0;
    float thin = 0.12;
    float dotShape = 1.0 - smoothstep(0.10, 0.20, length(p - vec2(0.0, 0.45)));
    float barH = 1.0 - smoothstep(thin, thin + 0.08, abs(p.y));
    float barV = 1.0 - smoothstep(thin, thin + 0.08, abs(p.x));
    if (glyph == 0) return dotShape;
    if (glyph == 1) return max(dotShape, 1.0 - smoothstep(0.10, 0.20, length(p + vec2(0.0, 0.45))));
    if (glyph == 2) return max(barH, barV);
    if (glyph == 3) {
        float d0 = 1.0 - smoothstep(thin, thin + 0.08, abs(p.x - p.y));
        float d1 = 1.0 - smoothstep(thin, thin + 0.08, abs(p.x + p.y));
        return max(max(d0, d1), max(barH, barV));
    }
    if (glyph == 4) {
        float rails = max(
            1.0 - smoothstep(thin, thin + 0.08, abs(abs(p.x) - 0.34)),
            1.0 - smoothstep(thin, thin + 0.08, abs(abs(p.y) - 0.34))
        );
        return rails;
    }
    float ring = 1.0 - smoothstep(thin, thin + 0.08, abs(length(p * vec2(0.8, 1.0)) - 0.48));
    return max(ring, glyph == 6 ? dotShape : 0.0);
}

void main() {
    vec4 source = texture(u_image, v_uv);
    uint effect = v_effect.x & 0x7fffffffu;
    bool oldLayer = (v_effect.x & 0x80000000u) != 0u;
    float progress = clamp(uintBitsToFloat(v_effect.y), 0.0, 1.0);
    uvec2 dimensions = max(
        uvec2(v_effect.w & 0xffffu, v_effect.w >> 16),
        uvec2(1u));
    uvec2 cellCoord = min(
        uvec2(floor(max(v_panel_uv, vec2(0.0)) * vec2(dimensions))),
        dimensions - 1u);
    float noise = cellNoise(v_effect.z, cellCoord.x, cellCoord.y);
    if (effect == 3u) {
        bool reveal = progress >= 1.0 || (progress > 0.0 && noise < progress);
        if (oldLayer == reveal) discard;
        o_col = source;
        return;
    }
    if (effect == 4u) {
        float diagonal = float((cellCoord.x + cellCoord.y * 2u) % 17u) / 17.0;
        float threshold = noise * 0.72 + diagonal * 0.28;
        bool reveal = progress >= 1.0 || (progress > 0.0 && progress >= threshold);
        bool wave = !reveal && progress > 0.0 && progress + 0.22 >= threshold;
        if (oldLayer) {
            if (wave || reveal) discard;
            o_col = source;
            return;
        }
        if (reveal) {
            o_col = source;
            return;
        }
        if (!wave) discard;
        vec2 cellUV = fract(v_panel_uv * vec2(dimensions));
        int glyph = min(int(floor(noise * 7.0)), 6);
        float mark = asciiGlyph(cellUV, glyph);
        float luma = dot(source.rgb, vec3(0.2126, 0.7152, 0.0722));
        vec3 paper = source.rgb * 0.10;
        // Preserve source colour when it already separates from the dimmed
        // paper. Solid black pixels have no such separation, so derive a
        // readable mark from their own luminance instead of letting the ASCII
        // phase disappear on dark images/PDF regions.
        vec3 contrastInk = luma > 0.5 ? vec3(0.0) : vec3(1.0);
        float sourceContrast = smoothstep(0.025, 0.14, length(source.rgb - paper));
        vec3 ink = mix(contrastInk, source.rgb, sourceContrast);
        vec3 dimInk = mix(paper, ink, 0.62);
        o_col = vec4(mix(paper, dimInk, mark), source.a);
        return;
    }
    o_col = source;
}