cbfa34a58c
Also update RELEASE_NOTES to describe new syntax. Change-Id: I2666551b98f80b61ae3a48c92a9e306cdc7242b0 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/444735 Commit-Queue: Brian Osman <brianosman@google.com> Reviewed-by: John Stiles <johnstiles@google.com>
184 lines
5.4 KiB
HTML
184 lines
5.4 KiB
HTML
<!doctype HTML>
|
|
|
|
<!DOCTYPE html>
|
|
<title>Custom Image Upscaling</title>
|
|
<meta charset="utf-8" />
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<script type="text/javascript" src="https://unpkg.com/canvaskit-wasm@0.25.0/bin/full/canvaskit.js"></script>
|
|
|
|
<style>
|
|
canvas {
|
|
border: 1px dashed grey;
|
|
}
|
|
</style>
|
|
|
|
<body>
|
|
<h1>Custom Image Upscaling</h1>
|
|
|
|
<div id=scale_text></div>
|
|
<div class="slidecontainer">
|
|
<input type="range" min="100" max="500" value="100" class="slider" id="scale_slider">
|
|
</div>
|
|
|
|
<canvas id=draw width=1000 height=400></canvas>
|
|
</body>
|
|
|
|
<script type="text/javascript" charset="utf-8">
|
|
let CanvasKit;
|
|
onload = async () => {
|
|
CanvasKit = await CanvasKitInit({ locateFile: (file) => "https://unpkg.com/canvaskit-wasm@0.25.0/bin/full/" + file });
|
|
init();
|
|
};
|
|
|
|
function init() {
|
|
if (!CanvasKit.RuntimeEffect) {
|
|
console.log(CanvasKit.RuntimeEffect);
|
|
throw "Need RuntimeEffect";
|
|
}
|
|
const surface = CanvasKit.MakeCanvasSurface('draw');
|
|
if (!surface) {
|
|
throw 'Could not make surface';
|
|
}
|
|
|
|
const prog = `
|
|
uniform shader image;
|
|
uniform float sharp; // slope of the lerp section of the kernel (steeper == sharper)
|
|
|
|
float2 sharpen(float2 w) {
|
|
// we think of sharp as a slope on a shifted line
|
|
// y = sharp * (w - 0.5) + 0.5
|
|
// Rewrite with mix needed for some GPUs to be correct
|
|
return saturate(mix(float2(0.5), w, sharp));
|
|
}
|
|
|
|
bool nearly_center(float2 p) {
|
|
float tolerance = 1/255.0;
|
|
p = abs(fract(p) - 0.5);
|
|
return p.x < tolerance && p.y < tolerance;
|
|
}
|
|
|
|
half4 main(float2 p) {
|
|
// p+1/2, p-1/2 can be numerically unstable when near the center, so we
|
|
// detect that case, and just sample at our center.
|
|
float h = nearly_center(p) ? 0.0 : 0.5;
|
|
|
|
// Manual bilerp logic
|
|
half4 pa = image.eval(float2(p.x-h, p.y-h));
|
|
half4 pb = image.eval(float2(p.x+h, p.y-h));
|
|
half4 pc = image.eval(float2(p.x-h, p.y+h));
|
|
half4 pd = image.eval(float2(p.x+h, p.y+h));
|
|
|
|
// Now 'sharpen' the weighting. This is the magic sauce where we different
|
|
// from a normal bilerp
|
|
float2 w = sharpen(fract(p + 0.5));
|
|
return mix(mix(pa, pb, w.x),
|
|
mix(pc, pd, w.x), w.y);
|
|
}
|
|
`;
|
|
const effect = CanvasKit.RuntimeEffect.Make(prog);
|
|
|
|
const size = 100;
|
|
const shader_paint = new CanvasKit.Paint();
|
|
const color_paint = new CanvasKit.Paint();
|
|
|
|
const image = function() {
|
|
let surf = CanvasKit.MakeSurface(size, size);
|
|
let c = surf.getCanvas();
|
|
|
|
color_paint.setColor([1, 1, 1, 1]);
|
|
c.drawRect([0, 0, size, size], color_paint);
|
|
|
|
color_paint.setColor([0, 0, 0, 1]);
|
|
for (let x = 0; x < size; x += 2) {
|
|
c.drawRect([x, 0, x+1, size], color_paint);
|
|
}
|
|
return surf.makeImageSnapshot();
|
|
}();
|
|
|
|
const imageShader = image.makeShaderOptions(CanvasKit.TileMode.Clamp,
|
|
CanvasKit.TileMode.Clamp,
|
|
CanvasKit.FilterMode.Nearest,
|
|
CanvasKit.MipmapMode.None);
|
|
|
|
scale_slider.oninput = () => { surface.requestAnimationFrame(drawFrame); }
|
|
|
|
const fract = function(value) {
|
|
return value - Math.floor(value);
|
|
}
|
|
|
|
// Uses custom sampling (4 sample points per-pixel)
|
|
draw_one_pass = function(canvas, y, scale) {
|
|
canvas.save();
|
|
canvas.scale(scale, 1.0);
|
|
shader_paint.setShader(effect.makeShaderWithChildren([Math.round(scale)], true, [imageShader], null));
|
|
canvas.drawRect([0, 0, size, y], shader_paint);
|
|
canvas.restore();
|
|
}
|
|
|
|
// First creates an upscaled image, and then bilerps it
|
|
draw_two_pass = function(canvas, y, scale) {
|
|
let intScale = Math.max(1, Math.floor(scale + 0.5));
|
|
let intImage = imageAtScale(intScale);
|
|
|
|
canvas.save();
|
|
canvas.scale(scale / intScale, 1);
|
|
canvas.drawImageOptions(intImage, 0, y, CanvasKit.FilterMode.Linear, CanvasKit.MipmapMode.None, null);
|
|
canvas.restore();
|
|
}
|
|
|
|
drawFrame = function(canvas) {
|
|
const scale = scale_slider.value / 100.0;
|
|
scale_text.innerText = scale
|
|
|
|
canvas.clear();
|
|
|
|
draw_one_pass(canvas, 100, scale);
|
|
drawMagnified(canvas, 0, 100);
|
|
|
|
draw_two_pass(canvas, 200, scale);
|
|
drawMagnified(canvas, 200, 300);
|
|
}
|
|
|
|
function drawMagnified(canvas, sampleY, dstY) {
|
|
let pixels = canvas.readPixels(
|
|
0, sampleY,
|
|
{ width: 50,
|
|
height: 1,
|
|
colorType: CanvasKit.ColorType.RGBA_8888,
|
|
alphaType: CanvasKit.AlphaType.Premul,
|
|
colorSpace: CanvasKit.ColorSpace.DISPLAY_P3
|
|
}
|
|
);
|
|
|
|
for (let i = 0; i < 50; i++) {
|
|
let color =
|
|
[ pixels[i*4 + 0] / 255.0,
|
|
pixels[i*4 + 1] / 255.0,
|
|
pixels[i*4 + 2] / 255.0,
|
|
pixels[i*4 + 3] / 255.0 ];
|
|
color_paint.setColor(color);
|
|
canvas.drawRect([i*20, dstY, (i+1)*20, dstY + 100], color_paint);
|
|
}
|
|
}
|
|
|
|
function imageAtScale(s) {
|
|
let surf = CanvasKit.MakeSurface(s * size, size);
|
|
let c = surf.getCanvas();
|
|
|
|
color_paint.setColor([1, 1, 1, 1]);
|
|
c.drawRect([0, 0, s * size, size], color_paint);
|
|
|
|
color_paint.setColor([0, 0, 0, 1]);
|
|
for (let x = 0; x < size; x += 2) {
|
|
c.drawRect([x * s, 0, (x+1) * s, size], color_paint);
|
|
}
|
|
return surf.makeImageSnapshot();
|
|
}
|
|
|
|
surface.requestAnimationFrame(drawFrame);
|
|
}
|
|
|
|
</script>
|
|
|