Kaido Kert | b108943 | 2024-03-18 19:46:49 -0700 | [diff] [blame^] | 1 | <!DOCTYPE html> |
| 2 | <title>Custom Image Upscaling</title> |
| 3 | <meta charset="utf-8" /> |
| 4 | <meta http-equiv="X-UA-Compatible" content="IE=edge"> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | <script type="text/javascript" src="https://unpkg.com/canvaskit-wasm@0.25.0/bin/full/canvaskit.js"></script> |
| 7 | |
| 8 | <style> |
| 9 | figcaption { |
| 10 | max-width: 800px; |
| 11 | } |
| 12 | </style> |
| 13 | |
| 14 | <body> |
| 15 | <h1>Custom Image Upscaling</h1> |
| 16 | |
| 17 | <div class="slidecontainer"> |
| 18 | <input type="range" min="0" max="1" value="0" step="0.01" class="slider" id="sharpen" |
| 19 | title="sharpen coefficient: 0 means nearest neighbor."> |
| 20 | <input type="range" min="0" max="1" value="0.3" step="0.01" class="slider" id="cubic_B" |
| 21 | title="cubic B"> |
| 22 | <input type="range" min="0" max="1" value="0.3" step="0.01" class="slider" id="cubic_C" |
| 23 | title="cubic C"> |
| 24 | </div> |
| 25 | |
| 26 | <figure> |
| 27 | <canvas id=draw width=820 height=820></canvas> |
| 28 | <figcaption> |
| 29 | This demo shows off a custom image upscaling algorithm written in SkSL. The algorithm |
| 30 | can be between nearest neighbor and linear interpolation, depending if the value of the |
| 31 | sharpen (i.e. the first) slider is 0 or 1, respectively. The upper left quadrant shows |
| 32 | the results of a 100x zoom in on a 4 pixel by 4 pixel image of random colors with this |
| 33 | algorithm. The lower left is the same algorithm with a smoothing curve applied. |
| 34 | <br> |
| 35 | For comparison, the upper right shows a stock linear interpolation and the lower right |
| 36 | shows a cubic interpolation with the B and C values controlled by the two remaining |
| 37 | sliders. |
| 38 | </figcaption> |
| 39 | </figure> |
| 40 | |
| 41 | </body> |
| 42 | |
| 43 | <script type="text/javascript" charset="utf-8"> |
| 44 | const ckLoaded = CanvasKitInit({ locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@0.25.0/bin/full/' + file }); |
| 45 | |
| 46 | ckLoaded.then((CanvasKit) => { |
| 47 | if (!CanvasKit.RuntimeEffect) { |
| 48 | throw 'Need RuntimeEffect'; |
| 49 | } |
| 50 | const surface = CanvasKit.MakeCanvasSurface('draw'); |
| 51 | if (!surface) { |
| 52 | throw 'Could not make surface'; |
| 53 | } |
| 54 | |
| 55 | const prog = ` |
| 56 | uniform shader image; |
| 57 | uniform float sharp; // 1/m 0 --> NN, 1 --> Linear |
| 58 | uniform float do_smooth; // bool |
| 59 | |
| 60 | float2 smooth(float2 t) { |
| 61 | return t * t * (3.0 - 2.0 * t); |
| 62 | } |
| 63 | |
| 64 | float2 sharpen(float2 w) { |
| 65 | return saturate(sharp * (w - 0.5) + 0.5); |
| 66 | } |
| 67 | |
| 68 | half4 main(float2 p) { |
| 69 | half4 pa = image.eval(float2(p.x-0.5, p.y-0.5)); |
| 70 | half4 pb = image.eval(float2(p.x+0.5, p.y-0.5)); |
| 71 | half4 pc = image.eval(float2(p.x-0.5, p.y+0.5)); |
| 72 | half4 pd = image.eval(float2(p.x+0.5, p.y+0.5)); |
| 73 | float2 w = sharpen(fract(p + 0.5)); |
| 74 | if (do_smooth > 0) { |
| 75 | w = smooth(w); |
| 76 | } |
| 77 | return mix(mix(pa, pb, w.x), mix(pc, pd, w.x), w.y); |
| 78 | } |
| 79 | `; |
| 80 | const effect = CanvasKit.RuntimeEffect.Make(prog); |
| 81 | |
| 82 | const paint = new CanvasKit.Paint(); |
| 83 | // image is a 4x4 image of 16 random colors. This very small image will be upscaled |
| 84 | // through various techniques. |
| 85 | const image = function() { |
| 86 | const surf = CanvasKit.MakeSurface(4, 4); |
| 87 | const c = surf.getCanvas(); |
| 88 | for (let y = 0; y < 4; y++) { |
| 89 | for (let x = 0; x < 4; x++) { |
| 90 | paint.setColor([Math.random(), Math.random(), Math.random(), 1]); |
| 91 | c.drawRect(CanvasKit.LTRBRect(x, y, x+1, y+1), paint); |
| 92 | } |
| 93 | } |
| 94 | return surf.makeImageSnapshot(); |
| 95 | }(); |
| 96 | |
| 97 | const imageShader = image.makeShaderOptions(CanvasKit.TileMode.Clamp, |
| 98 | CanvasKit.TileMode.Clamp, |
| 99 | CanvasKit.FilterMode.Nearest, |
| 100 | CanvasKit.MipmapMode.None); |
| 101 | |
| 102 | sharpen.oninput = () => { surface.requestAnimationFrame(drawFrame); }; |
| 103 | cubic_B.oninput = () => { surface.requestAnimationFrame(drawFrame); }; |
| 104 | cubic_C.oninput = () => { surface.requestAnimationFrame(drawFrame); }; |
| 105 | |
| 106 | const drawFrame = function(canvas) { |
| 107 | const v = sharpen.valueAsNumber; |
| 108 | const m = 1/Math.max(v, 0.00001); |
| 109 | const B = cubic_B.valueAsNumber; |
| 110 | const C = cubic_C.valueAsNumber; |
| 111 | |
| 112 | canvas.save(); |
| 113 | // Upscale all drawing by 100x; This is big enough to make the differences in technique |
| 114 | // more obvious. |
| 115 | const scale = 100; |
| 116 | canvas.scale(scale, scale); |
| 117 | |
| 118 | // Upper left, draw image using an algorithm (written in SkSL) between nearest neighbor and |
| 119 | // linear interpolation with no smoothing. |
| 120 | paint.setShader(effect.makeShaderWithChildren([m, 0], [imageShader], null)); |
| 121 | canvas.drawRect(CanvasKit.LTRBRect(0, 0, 4, 4), paint); |
| 122 | |
| 123 | // Lower left, draw image using an algorithm (written in SkSL) between nearest neighbor and |
| 124 | // linear interpolation with smoothing enabled. |
| 125 | canvas.save(); |
| 126 | canvas.translate(0, 4.1); |
| 127 | paint.setShader(effect.makeShaderWithChildren([m, 1], [imageShader], null)); |
| 128 | canvas.drawRect(CanvasKit.LTRBRect(0, 0, 4, 4), paint); |
| 129 | canvas.restore(); |
| 130 | |
| 131 | // Upper right, draw image with built-in linear interpolation. |
| 132 | canvas.drawImageOptions(image, 4.1, 0, CanvasKit.FilterMode.Linear, CanvasKit.MipmapMode.None, null); |
| 133 | |
| 134 | // Lower right, draw image with configurable cubic interpolation. |
| 135 | canvas.drawImageCubic(image, 4.1, 4.1, B, C, null); |
| 136 | |
| 137 | canvas.restore(); |
| 138 | }; |
| 139 | |
| 140 | surface.requestAnimationFrame(drawFrame); |
| 141 | }); |
| 142 | |
| 143 | </script> |