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