Realistic JPEG Artifacting

A shader designed to provide realistic JPEG-style artifacting using a DCT (Discrete cosine transform).

 

The quality is inversed from typical JPEG quality and defaults to 8 (1 = most quality, 100 = worst quality).

 

Recommended to be used in a ColorRect covering the whole viewport, inside of a seperate BackBufferCopy node.

Shader code
shader_type canvas_item;

uniform sampler2D SCREEN_TEXTURE : hint_screen_texture;
uniform int quality : hint_range(1, 100) = 8;
uniform int block_size : hint_range(2, 64, 2) = 8;

// colorspace conversions
vec3 rgb_to_ycbcr(vec3 c) {
    float y =  0.299000 * c.r + 0.587000 * c.g + 0.114000 * c.b;
    float cb = -0.168736 * c.r - 0.331264 * c.g + 0.500000 * c.b + 0.5;
    float cr =  0.500000 * c.r - 0.418688 * c.g - 0.081312 * c.b + 0.5;
    return vec3(y, cb, cr);
}

vec3 ycbcr_to_rgb(vec3 c) {
    float y  = c.x;
    float cb = c.y - 0.5;
    float cr = c.z - 0.5;
    float r = y + 1.402000 * cr;
    float g = y - 0.344136 * cb - 0.714136 * cr;
    float b = y + 1.772000 * cb;
    return vec3(r, g, b);
}

void fragment() {
    const int N = 8; // dct size
    // orthogonal dct basis, constants from https://patents.google.com/patent/CN100409693C/en
    const float dct[64] = float[64](
        0.353553,0.353553,0.353553,0.353553,0.353553,0.353553,0.353553,0.353553,
        0.490393,0.415735,0.277785,0.0975452,-0.0975452,-0.277785,-0.415735,-0.490393,
        0.46194,0.191342,-0.191342,-0.46194,-0.46194,-0.191342,0.191342,0.46194,
        0.415735,-0.0975452,-0.490393,-0.277785,0.277785,0.490393,0.0975452,-0.415735,
        0.353553,-0.353553,-0.353553,0.353553,0.353553,-0.353553,-0.353553,0.353553,
        0.277785,-0.490393,0.0975452,0.415735,-0.415735,-0.0975452,0.490393,-0.277785,
        0.191342,-0.46194,0.46194,-0.191342,-0.191342,0.46194,-0.46194,0.191342,
        0.0975452,-0.277785,0.415735,-0.490393,0.490393,-0.415735,0.277785,-0.0975452
    );

    // flattened 8x8 buffers
    vec3 block[64];
    vec3 horiz[64];
    vec3 vert[64];
    vec3 inv_horiz[64];
    vec3 inv_block[64];

    // get viewport size from SCREEN_PIXEL_SIZE
    vec2 viewport_size = 1.0 / SCREEN_PIXEL_SIZE;

    // pixel coords
    vec2 px_f = SCREEN_UV * viewport_size;
    int px = int(floor(px_f.x));
    int py = int(floor(px_f.y));

    int bs = clamp(block_size, 1, 64);
    int block_origin_x = (px / bs) * bs;
    int block_origin_y = (py / bs) * bs;
    int lid_x = px - block_origin_x;
    int lid_y = py - block_origin_y;

    for (int y = 0; y < N; y++) {
        for (int x = 0; x < N; x++) {
            float fx = (float(x) + 0.5) / float(N);
            float fy = (float(y) + 0.5) / float(N);
            int sample_x = block_origin_x + int(floor(fx * float(bs)));
            int sample_y = block_origin_y + int(floor(fy * float(bs)));
            sample_x = clamp(sample_x, 0, int(viewport_size.x) - 1);
            sample_y = clamp(sample_y, 0, int(viewport_size.y) - 1);
			vec2 uv = (vec2(float(sample_x), float(sample_y)) + vec2(0.5)) / viewport_size;
            vec3 texc = texture(SCREEN_TEXTURE, uv).rgb;
            block[y * N + x] = rgb_to_ycbcr(texc);
        }
    }

    for (int y = 0; y < N; y++) {
        for (int u = 0; u < N; u++) {
            vec3 sum = vec3(0.0);
            for (int x = 0; x < N; x++) {
                sum += block[y * N + x] * dct[u * N + x];
            }
            horiz[y * N + u] = sum;
        }
    }

    float qscale = max(1.0, float(quality));
    for (int v = 0; v < N; v++) {
        for (int u = 0; u < N; u++) {
            vec3 sum = vec3(0.0);
            for (int y = 0; y < N; y++) {
                sum += horiz[y * N + u] * dct[v * N + y];
            }
            float q = qscale * float(u + v + 1);
            vert[v * N + u] = round(sum * 128.0 / q) * (q / 128.0);
        }
    }

    for (int v = 0; v < N; v++) {
        for (int x = 0; x < N; x++) {
            vec3 sum = vec3(0.0);
            for (int u = 0; u < N; u++) {
                sum += vert[v * N + u] * dct[u * N + x];
            }
            inv_horiz[v * N + x] = sum;
        }
    }

    for (int y = 0; y < N; y++) {
        for (int x = 0; x < N; x++) {
            vec3 sum = vec3(0.0);
            for (int v = 0; v < N; v++) {
                sum += inv_horiz[v * N + x] * dct[v * N + y];
            }
            inv_block[y * N + x] = sum;
        }
    }

    int pick_x = int(float(lid_x) * float(N) / float(bs));
    int pick_y = int(float(lid_y) * float(N) / float(bs));
    pick_x = clamp(pick_x, 0, N - 1);
    pick_y = clamp(pick_y, 0, N - 1);

    vec3 out_ycbcr = inv_block[pick_y * N + pick_x];
    vec3 out_rgb = ycbcr_to_rgb(out_ycbcr);
    COLOR = vec4(clamp(out_rgb, 0.0, 1.0), 1.0);
}
Live Preview
Tags
artifacting, compression, jpeg
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
prolley
prolley
11 days ago

awesome shader!!!!!!

Last edited 11 days ago by prolley