1cb93a386Sopenharmony_ci<!doctype HTML> 2cb93a386Sopenharmony_ci 3cb93a386Sopenharmony_ci<!DOCTYPE html> 4cb93a386Sopenharmony_ci<title>Custom Image Upscaling</title> 5cb93a386Sopenharmony_ci<meta charset="utf-8" /> 6cb93a386Sopenharmony_ci<meta http-equiv="X-UA-Compatible" content="IE=edge"> 7cb93a386Sopenharmony_ci<meta name="viewport" content="width=device-width, initial-scale=1.0"> 8cb93a386Sopenharmony_ci<script type="text/javascript" src="https://unpkg.com/canvaskit-wasm@0.25.0/bin/full/canvaskit.js"></script> 9cb93a386Sopenharmony_ci 10cb93a386Sopenharmony_ci<style> 11cb93a386Sopenharmony_cicanvas { 12cb93a386Sopenharmony_ci border: 1px dashed grey; 13cb93a386Sopenharmony_ci} 14cb93a386Sopenharmony_ci</style> 15cb93a386Sopenharmony_ci 16cb93a386Sopenharmony_ci<body> 17cb93a386Sopenharmony_ci <h1>Custom Image Upscaling</h1> 18cb93a386Sopenharmony_ci 19cb93a386Sopenharmony_ci <div id=scale_text></div> 20cb93a386Sopenharmony_ci <div class="slidecontainer"> 21cb93a386Sopenharmony_ci <input type="range" min="100" max="500" value="100" class="slider" id="scale_slider"> 22cb93a386Sopenharmony_ci </div> 23cb93a386Sopenharmony_ci 24cb93a386Sopenharmony_ci <canvas id=draw width=1000 height=400></canvas> 25cb93a386Sopenharmony_ci</body> 26cb93a386Sopenharmony_ci 27cb93a386Sopenharmony_ci<script type="text/javascript" charset="utf-8"> 28cb93a386Sopenharmony_cilet CanvasKit; 29cb93a386Sopenharmony_cionload = async () => { 30cb93a386Sopenharmony_ci CanvasKit = await CanvasKitInit({ locateFile: (file) => "https://unpkg.com/canvaskit-wasm@0.25.0/bin/full/" + file }); 31cb93a386Sopenharmony_ci init(); 32cb93a386Sopenharmony_ci}; 33cb93a386Sopenharmony_ci 34cb93a386Sopenharmony_cifunction init() { 35cb93a386Sopenharmony_ci if (!CanvasKit.RuntimeEffect) { 36cb93a386Sopenharmony_ci console.log(CanvasKit.RuntimeEffect); 37cb93a386Sopenharmony_ci throw "Need RuntimeEffect"; 38cb93a386Sopenharmony_ci } 39cb93a386Sopenharmony_ci const surface = CanvasKit.MakeCanvasSurface('draw'); 40cb93a386Sopenharmony_ci if (!surface) { 41cb93a386Sopenharmony_ci throw 'Could not make surface'; 42cb93a386Sopenharmony_ci } 43cb93a386Sopenharmony_ci 44cb93a386Sopenharmony_ci const prog = ` 45cb93a386Sopenharmony_ci uniform shader image; 46cb93a386Sopenharmony_ci uniform float sharp; // slope of the lerp section of the kernel (steeper == sharper) 47cb93a386Sopenharmony_ci 48cb93a386Sopenharmony_ci float2 sharpen(float2 w) { 49cb93a386Sopenharmony_ci // we think of sharp as a slope on a shifted line 50cb93a386Sopenharmony_ci // y = sharp * (w - 0.5) + 0.5 51cb93a386Sopenharmony_ci // Rewrite with mix needed for some GPUs to be correct 52cb93a386Sopenharmony_ci return saturate(mix(float2(0.5), w, sharp)); 53cb93a386Sopenharmony_ci } 54cb93a386Sopenharmony_ci 55cb93a386Sopenharmony_ci bool nearly_center(float2 p) { 56cb93a386Sopenharmony_ci float tolerance = 1/255.0; 57cb93a386Sopenharmony_ci p = abs(fract(p) - 0.5); 58cb93a386Sopenharmony_ci return p.x < tolerance && p.y < tolerance; 59cb93a386Sopenharmony_ci } 60cb93a386Sopenharmony_ci 61cb93a386Sopenharmony_ci half4 main(float2 p) { 62cb93a386Sopenharmony_ci // p+1/2, p-1/2 can be numerically unstable when near the center, so we 63cb93a386Sopenharmony_ci // detect that case, and just sample at our center. 64cb93a386Sopenharmony_ci float h = nearly_center(p) ? 0.0 : 0.5; 65cb93a386Sopenharmony_ci 66cb93a386Sopenharmony_ci // Manual bilerp logic 67cb93a386Sopenharmony_ci half4 pa = image.eval(float2(p.x-h, p.y-h)); 68cb93a386Sopenharmony_ci half4 pb = image.eval(float2(p.x+h, p.y-h)); 69cb93a386Sopenharmony_ci half4 pc = image.eval(float2(p.x-h, p.y+h)); 70cb93a386Sopenharmony_ci half4 pd = image.eval(float2(p.x+h, p.y+h)); 71cb93a386Sopenharmony_ci 72cb93a386Sopenharmony_ci // Now 'sharpen' the weighting. This is the magic sauce where we different 73cb93a386Sopenharmony_ci // from a normal bilerp 74cb93a386Sopenharmony_ci float2 w = sharpen(fract(p + 0.5)); 75cb93a386Sopenharmony_ci return mix(mix(pa, pb, w.x), 76cb93a386Sopenharmony_ci mix(pc, pd, w.x), w.y); 77cb93a386Sopenharmony_ci } 78cb93a386Sopenharmony_ci `; 79cb93a386Sopenharmony_ci const effect = CanvasKit.RuntimeEffect.Make(prog); 80cb93a386Sopenharmony_ci 81cb93a386Sopenharmony_ci const size = 100; 82cb93a386Sopenharmony_ci const shader_paint = new CanvasKit.Paint(); 83cb93a386Sopenharmony_ci const color_paint = new CanvasKit.Paint(); 84cb93a386Sopenharmony_ci 85cb93a386Sopenharmony_ci const image = function() { 86cb93a386Sopenharmony_ci let surf = CanvasKit.MakeSurface(size, size); 87cb93a386Sopenharmony_ci let c = surf.getCanvas(); 88cb93a386Sopenharmony_ci 89cb93a386Sopenharmony_ci color_paint.setColor([1, 1, 1, 1]); 90cb93a386Sopenharmony_ci c.drawRect([0, 0, size, size], color_paint); 91cb93a386Sopenharmony_ci 92cb93a386Sopenharmony_ci color_paint.setColor([0, 0, 0, 1]); 93cb93a386Sopenharmony_ci for (let x = 0; x < size; x += 2) { 94cb93a386Sopenharmony_ci c.drawRect([x, 0, x+1, size], color_paint); 95cb93a386Sopenharmony_ci } 96cb93a386Sopenharmony_ci return surf.makeImageSnapshot(); 97cb93a386Sopenharmony_ci }(); 98cb93a386Sopenharmony_ci 99cb93a386Sopenharmony_ci const imageShader = image.makeShaderOptions(CanvasKit.TileMode.Clamp, 100cb93a386Sopenharmony_ci CanvasKit.TileMode.Clamp, 101cb93a386Sopenharmony_ci CanvasKit.FilterMode.Nearest, 102cb93a386Sopenharmony_ci CanvasKit.MipmapMode.None); 103cb93a386Sopenharmony_ci 104cb93a386Sopenharmony_ci scale_slider.oninput = () => { surface.requestAnimationFrame(drawFrame); } 105cb93a386Sopenharmony_ci 106cb93a386Sopenharmony_ci const fract = function(value) { 107cb93a386Sopenharmony_ci return value - Math.floor(value); 108cb93a386Sopenharmony_ci } 109cb93a386Sopenharmony_ci 110cb93a386Sopenharmony_ci // Uses custom sampling (4 sample points per-pixel) 111cb93a386Sopenharmony_ci draw_one_pass = function(canvas, y, scale) { 112cb93a386Sopenharmony_ci canvas.save(); 113cb93a386Sopenharmony_ci canvas.scale(scale, 1.0); 114cb93a386Sopenharmony_ci shader_paint.setShader(effect.makeShaderWithChildren([Math.round(scale)], true, [imageShader], null)); 115cb93a386Sopenharmony_ci canvas.drawRect([0, 0, size, y], shader_paint); 116cb93a386Sopenharmony_ci canvas.restore(); 117cb93a386Sopenharmony_ci } 118cb93a386Sopenharmony_ci 119cb93a386Sopenharmony_ci // First creates an upscaled image, and then bilerps it 120cb93a386Sopenharmony_ci draw_two_pass = function(canvas, y, scale) { 121cb93a386Sopenharmony_ci let intScale = Math.max(1, Math.floor(scale + 0.5)); 122cb93a386Sopenharmony_ci let intImage = imageAtScale(intScale); 123cb93a386Sopenharmony_ci 124cb93a386Sopenharmony_ci canvas.save(); 125cb93a386Sopenharmony_ci canvas.scale(scale / intScale, 1); 126cb93a386Sopenharmony_ci canvas.drawImageOptions(intImage, 0, y, CanvasKit.FilterMode.Linear, CanvasKit.MipmapMode.None, null); 127cb93a386Sopenharmony_ci canvas.restore(); 128cb93a386Sopenharmony_ci } 129cb93a386Sopenharmony_ci 130cb93a386Sopenharmony_ci drawFrame = function(canvas) { 131cb93a386Sopenharmony_ci const scale = scale_slider.value / 100.0; 132cb93a386Sopenharmony_ci scale_text.innerText = scale 133cb93a386Sopenharmony_ci 134cb93a386Sopenharmony_ci canvas.clear(); 135cb93a386Sopenharmony_ci 136cb93a386Sopenharmony_ci draw_one_pass(canvas, 100, scale); 137cb93a386Sopenharmony_ci drawMagnified(canvas, 0, 100); 138cb93a386Sopenharmony_ci 139cb93a386Sopenharmony_ci draw_two_pass(canvas, 200, scale); 140cb93a386Sopenharmony_ci drawMagnified(canvas, 200, 300); 141cb93a386Sopenharmony_ci } 142cb93a386Sopenharmony_ci 143cb93a386Sopenharmony_ci function drawMagnified(canvas, sampleY, dstY) { 144cb93a386Sopenharmony_ci let pixels = canvas.readPixels( 145cb93a386Sopenharmony_ci 0, sampleY, 146cb93a386Sopenharmony_ci { width: 50, 147cb93a386Sopenharmony_ci height: 1, 148cb93a386Sopenharmony_ci colorType: CanvasKit.ColorType.RGBA_8888, 149cb93a386Sopenharmony_ci alphaType: CanvasKit.AlphaType.Premul, 150cb93a386Sopenharmony_ci colorSpace: CanvasKit.ColorSpace.DISPLAY_P3 151cb93a386Sopenharmony_ci } 152cb93a386Sopenharmony_ci ); 153cb93a386Sopenharmony_ci 154cb93a386Sopenharmony_ci for (let i = 0; i < 50; i++) { 155cb93a386Sopenharmony_ci let color = 156cb93a386Sopenharmony_ci [ pixels[i*4 + 0] / 255.0, 157cb93a386Sopenharmony_ci pixels[i*4 + 1] / 255.0, 158cb93a386Sopenharmony_ci pixels[i*4 + 2] / 255.0, 159cb93a386Sopenharmony_ci pixels[i*4 + 3] / 255.0 ]; 160cb93a386Sopenharmony_ci color_paint.setColor(color); 161cb93a386Sopenharmony_ci canvas.drawRect([i*20, dstY, (i+1)*20, dstY + 100], color_paint); 162cb93a386Sopenharmony_ci } 163cb93a386Sopenharmony_ci } 164cb93a386Sopenharmony_ci 165cb93a386Sopenharmony_ci function imageAtScale(s) { 166cb93a386Sopenharmony_ci let surf = CanvasKit.MakeSurface(s * size, size); 167cb93a386Sopenharmony_ci let c = surf.getCanvas(); 168cb93a386Sopenharmony_ci 169cb93a386Sopenharmony_ci color_paint.setColor([1, 1, 1, 1]); 170cb93a386Sopenharmony_ci c.drawRect([0, 0, s * size, size], color_paint); 171cb93a386Sopenharmony_ci 172cb93a386Sopenharmony_ci color_paint.setColor([0, 0, 0, 1]); 173cb93a386Sopenharmony_ci for (let x = 0; x < size; x += 2) { 174cb93a386Sopenharmony_ci c.drawRect([x * s, 0, (x+1) * s, size], color_paint); 175cb93a386Sopenharmony_ci } 176cb93a386Sopenharmony_ci return surf.makeImageSnapshot(); 177cb93a386Sopenharmony_ci } 178cb93a386Sopenharmony_ci 179cb93a386Sopenharmony_ci surface.requestAnimationFrame(drawFrame); 180cb93a386Sopenharmony_ci} 181cb93a386Sopenharmony_ci 182cb93a386Sopenharmony_ci</script> 183cb93a386Sopenharmony_ci 184