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