Source code

Revision control

Copy as Markdown

Other Tools

const TILE_SIZE = 3;
const SCALE = 20;
function createTileCanvas() {
const canvas = document.createElement('canvas');
canvas.width = TILE_SIZE;
canvas.height = TILE_SIZE;
return canvas;
}
function fillUnsupported(canvas) {
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(255, 0, 255)';
ctx.fillRect(0, 0, TILE_SIZE, TILE_SIZE);
}
function fillError() {
const output = document.getElementById('output');
output.width = 40;
output.height = 40;
const ctx = output.getContext('2d');
ctx.fillStyle = 'rgb(255, 0, 0)';
ctx.fillRect(0, 0, output.width, output.height);
}
async function loadImage(src) {
const image = new Image();
await new Promise((resolve, reject) => {
image.onload = resolve;
image.onerror = () => reject(new Error(`image load failed: ${src}`));
image.src = src;
});
return image;
}
async function renderDrawImageTile(image) {
const canvas = createTileCanvas();
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
return canvas;
}
async function renderImageBitmapTile(src) {
const canvas = createTileCanvas();
const ctx = canvas.getContext('2d');
try {
const response = await fetch(src);
const blob = await response.blob();
const bitmap = await createImageBitmap(blob);
ctx.drawImage(bitmap, 0, 0);
bitmap.close();
return canvas;
} catch (error) {
fillUnsupported(canvas);
return canvas;
}
}
function makeImageDataFromWebGLPixels(pixels) {
const flipped = new Uint8ClampedArray(pixels.length);
const rowWidth = TILE_SIZE * 4;
for (let y = 0; y < TILE_SIZE; ++y) {
const sourceOffset = (TILE_SIZE - 1 - y) * rowWidth;
const destinationOffset = y * rowWidth;
flipped.set(pixels.subarray(sourceOffset, sourceOffset + rowWidth),
destinationOffset);
}
return new ImageData(flipped, TILE_SIZE, TILE_SIZE);
}
function renderWebGLTile(image) {
const outputCanvas = createTileCanvas();
const outputContext = outputCanvas.getContext('2d');
const webglCanvas = createTileCanvas();
const gl = webglCanvas.getContext('webgl');
if (!gl) {
fillUnsupported(outputCanvas);
return outputCanvas;
}
const texture = gl.createTexture();
const framebuffer = gl.createFramebuffer();
if (!texture || !framebuffer) {
fillUnsupported(outputCanvas);
return outputCanvas;
}
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
try {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
} catch (error) {
fillUnsupported(outputCanvas);
gl.deleteFramebuffer(framebuffer);
gl.deleteTexture(texture);
return outputCanvas;
}
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
fillUnsupported(outputCanvas);
gl.deleteFramebuffer(framebuffer);
gl.deleteTexture(texture);
return outputCanvas;
}
const pixels = new Uint8Array(TILE_SIZE * TILE_SIZE * 4);
gl.readPixels(0, 0, TILE_SIZE, TILE_SIZE, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
const imageData = makeImageDataFromWebGLPixels(pixels);
outputContext.putImageData(imageData, 0, 0);
gl.deleteFramebuffer(framebuffer);
gl.deleteTexture(texture);
return outputCanvas;
}
function convertFloatChannelToByte(value) {
if (!Number.isFinite(value)) {
return 0;
}
if (value <= 0) {
return 0;
}
if (value >= 1) {
return 255;
}
return Math.round(value * 255);
}
function renderFloat16Tile(image) {
const canvas = createTileCanvas();
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
let floatData;
try {
floatData =
ctx.getImageData(0, 0, TILE_SIZE, TILE_SIZE,
{pixelFormat: 'rgba-float16'}).data;
} catch (error) {
fillUnsupported(canvas);
return canvas;
}
if (!(floatData instanceof Float16Array)) {
fillUnsupported(canvas);
return canvas;
}
const converted = new Uint8ClampedArray(floatData.length);
for (let i = 0; i < floatData.length; ++i) {
converted[i] = convertFloatChannelToByte(floatData[i]);
}
ctx.putImageData(new ImageData(converted, TILE_SIZE, TILE_SIZE), 0, 0);
return canvas;
}
function drawOutputTiles(tiles) {
const output = document.getElementById('output');
output.width = TILE_SIZE * SCALE * tiles.length;
output.height = TILE_SIZE * SCALE;
const ctx = output.getContext('2d');
ctx.imageSmoothingEnabled = false;
const scaledTileSize = TILE_SIZE * SCALE;
for (let i = 0; i < tiles.length; ++i) {
ctx.drawImage(
tiles[i],
0,
0,
TILE_SIZE,
TILE_SIZE,
i * scaledTileSize,
0,
scaledTileSize,
scaledTileSize);
}
}
async function runCanvasDecodePathReftest(source) {
try {
const image = await loadImage(source);
const drawImageTile = await renderDrawImageTile(image);
const imageBitmapTile = await renderImageBitmapTile(source);
const webglTile = renderWebGLTile(image);
const float16Tile = renderFloat16Tile(image);
drawOutputTiles([drawImageTile, imageBitmapTile, webglTile, float16Tile]);
} catch (error) {
fillError();
} finally {
document.documentElement.classList.remove('reftest-wait');
}
}