Source code
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform sampler2D u_texture;
uniform vec2 u_texture_resolution;
uniform float u_threshold;
uniform float u_stepCount;
uniform float u_offset;
uniform bool u_noise;
uniform float u_noiseAmount;

vec4 desaturate(vec4 color) {
    vec3 luma = vec3(0.2126, 0.7152, 0.0722);
    float gray = dot(color.rgb, luma);
    return vec4(vec3(gray), color.a);
}

vec2 adjustUV(float textureAR, float canvasAR, vec2 uv) {
    if(canvasAR < textureAR) {
        // Canvas is wider than texture - crop sides
        float scale = canvasAR / textureAR;
        return vec2((uv.x - 0.5) * scale + 0.5, uv.y);
    } else {
        // Canvas is taller than texture - crop top/bottom
        float scale = textureAR / canvasAR;
        return vec2(uv.x, (uv.y - 0.5) * scale + 0.5);
    }
}

float threshold(float color) {
    return floor(color * u_stepCount - u_offset) / (u_stepCount - 1.);
}

float rand(vec2 uv) {
    return fract(sin(dot(uv.xy, vec2(12.9898, 78.233))) * 43758.5453);
}

float grainMultiplier = 1.2;

void main() {
    // Get normalized coordinates
    vec2 uv = gl_FragCoord.xy / u_resolution.xy;

    // Calculate aspect ratios
    float textureAR = u_texture_resolution.x / u_texture_resolution.y;
    float canvasAR = u_resolution.x / u_resolution.y;
    vec2 adjustedUV = adjustUV(textureAR, canvasAR, uv);

    vec4 texColor = texture2D(u_texture, adjustedUV);
    vec4 gray = desaturate(texColor);
    float inColor = u_noise ? gray.r + rand(adjustedUV) * u_noiseAmount : gray.r;
    vec3 outRgb = vec3(threshold(inColor));

    gl_FragColor = vec4(outRgb, 1.);
}