blob: 6626630a3695d2b5f7a1f79e2e3a1702c119e7cc [file] [log] [blame]
Kaido Kertb1089432024-03-18 19:46:49 -07001<!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>