• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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