area_scaling.glsl
1 #version 430 core 2 precision mediump float; 3 layout (local_size_x = 16, local_size_y = 16) in; 4 layout(rgba8, binding = 0, location=0) uniform image2D imgOutput; 5 layout( location=1 ) uniform sampler2D Source; 6 layout( location=2 ) uniform float srcX0; 7 layout( location=3 ) uniform float srcX1; 8 layout( location=4 ) uniform float srcY0; 9 layout( location=5 ) uniform float srcY1; 10 layout( location=6 ) uniform float dstX0; 11 layout( location=7 ) uniform float dstX1; 12 layout( location=8 ) uniform float dstY0; 13 layout( location=9 ) uniform float dstY1; 14 15 /***** Area Sampling *****/ 16 17 // By Sam Belliveau and Filippo Tarpini. Public Domain license. 18 // Effectively a more accurate sharp bilinear filter when upscaling, 19 // that also works as a mathematically perfect downscale filter. 20 // https://entropymine.com/imageworsener/pixelmixing/ 21 // https://github.com/obsproject/obs-studio/pull/1715 22 // https://legacy.imagemagick.org/Usage/filter/ 23 vec4 AreaSampling(vec2 xy) 24 { 25 // Determine the sizes of the source and target images. 26 vec2 source_size = vec2(abs(srcX1 - srcX0), abs(srcY1 - srcY0)); 27 vec2 target_size = vec2(abs(dstX1 - dstX0), abs(dstY1 - dstY0)); 28 vec2 inverted_target_size = vec2(1.0) / target_size; 29 30 // Compute the top-left and bottom-right corners of the target pixel box. 31 vec2 t_beg = floor(xy - vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1)); 32 vec2 t_end = t_beg + vec2(1.0, 1.0); 33 34 // Convert the target pixel box to source pixel box. 35 vec2 beg = t_beg * inverted_target_size * source_size; 36 vec2 end = t_end * inverted_target_size * source_size; 37 38 // Compute the top-left and bottom-right corners of the pixel box. 39 ivec2 f_beg = ivec2(beg); 40 ivec2 f_end = ivec2(end); 41 42 // Compute how much of the start and end pixels are covered horizontally & vertically. 43 float area_w = 1.0 - fract(beg.x); 44 float area_n = 1.0 - fract(beg.y); 45 float area_e = fract(end.x); 46 float area_s = fract(end.y); 47 48 // Compute the areas of the corner pixels in the pixel box. 49 float area_nw = area_n * area_w; 50 float area_ne = area_n * area_e; 51 float area_sw = area_s * area_w; 52 float area_se = area_s * area_e; 53 54 // Initialize the color accumulator. 55 vec4 avg_color = vec4(0.0, 0.0, 0.0, 0.0); 56 57 // Accumulate corner pixels. 58 avg_color += area_nw * texelFetch(Source, ivec2(f_beg.x, f_beg.y), 0); 59 avg_color += area_ne * texelFetch(Source, ivec2(f_end.x, f_beg.y), 0); 60 avg_color += area_sw * texelFetch(Source, ivec2(f_beg.x, f_end.y), 0); 61 avg_color += area_se * texelFetch(Source, ivec2(f_end.x, f_end.y), 0); 62 63 // Determine the size of the pixel box. 64 int x_range = int(f_end.x - f_beg.x - 0.5); 65 int y_range = int(f_end.y - f_beg.y - 0.5); 66 67 // Accumulate top and bottom edge pixels. 68 for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x) 69 { 70 avg_color += area_n * texelFetch(Source, ivec2(x, f_beg.y), 0); 71 avg_color += area_s * texelFetch(Source, ivec2(x, f_end.y), 0); 72 } 73 74 // Accumulate left and right edge pixels and all the pixels in between. 75 for (int y = f_beg.y + 1; y <= f_beg.y + y_range; ++y) 76 { 77 avg_color += area_w * texelFetch(Source, ivec2(f_beg.x, y), 0); 78 avg_color += area_e * texelFetch(Source, ivec2(f_end.x, y), 0); 79 80 for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x) 81 { 82 avg_color += texelFetch(Source, ivec2(x, y), 0); 83 } 84 } 85 86 // Compute the area of the pixel box that was sampled. 87 float area_corners = area_nw + area_ne + area_sw + area_se; 88 float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e); 89 float area_center = float(x_range) * float(y_range); 90 91 // Return the normalized average color. 92 return avg_color / (area_corners + area_edges + area_center); 93 } 94 95 float insideBox(vec2 v, vec2 bLeft, vec2 tRight) { 96 vec2 s = step(bLeft, v) - step(tRight, v); 97 return s.x * s.y; 98 } 99 100 vec2 translateDest(vec2 pos) { 101 vec2 translatedPos = vec2(pos.x, pos.y); 102 translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x; 103 translatedPos.y = dstY0 > dstY1 ? dstY0 + dstY1 - translatedPos.y - 1 : translatedPos.y; 104 return translatedPos; 105 } 106 107 void main() 108 { 109 vec2 bLeft = vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1); 110 vec2 tRight = vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0); 111 ivec2 loc = ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y); 112 if (insideBox(loc, bLeft, tRight) == 0) { 113 imageStore(imgOutput, loc, vec4(0, 0, 0, 1)); 114 return; 115 } 116 117 vec4 outColor = AreaSampling(loc); 118 imageStore(imgOutput, ivec2(translateDest(loc)), vec4(outColor.rgb, 1)); 119 }