<!--
Copyright 2019 The Chromium Authors
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<!doctype html>
<head>
<title>piex wasm raw image preview / thumbnail test page</title>
<link type="text/css" rel="stylesheet" href="/tests.css">
</head>
<body>
<button onclick=runTest()>Run Test</button>
</body>
<script>
class ImageBuffer {
constructor(buffer) {
this.source = new Uint8Array(buffer);
this.length = buffer.byteLength;
}
process() {
this.memory = Module._malloc(this.length);
if (!this.memory)
throw new Error('image malloc failure');
Module.HEAP8.set(this.source, this.memory);
this.result = Module.image(this.memory, this.length);
return this.result;
}
preview() {
let preview = this.result ? this.result.preview : null;
if (!preview || this.result.error)
return null;
if (preview.format != 0)
throw new Error('preview images should be JPEG format');
const offset = preview.offset;
const length = preview.length;
if (offset > this.length || (this.length - offset) < length)
throw new Error('failed to extract preview');
const view = new Uint8Array(this.source.buffer, offset, length);
preview.data = new Uint8Array(view);
preview.type = 'preview';
return preview;
}
thumbnail() {
let thumbnail = this.result ? this.result.thumbnail : null;
if (!thumbnail || this.result.error)
return null;
const offset = thumbnail.offset;
const length = thumbnail.length;
if (offset > this.length || (this.length - offset) < length)
throw new Error('failed to extract thumbnail');
const view = new Uint8Array(this.source.buffer, offset, length);
thumbnail.data = new Uint8Array(view);
if (thumbnail.format == 1) // RGB
thumbnail.size = thumbnail.width * thumbnail.height * 3;
thumbnail.type = 'thumbnail';
return thumbnail;
}
details() {
const details = this.result ? this.result.details : null;
if (!details || this.result.error)
return null;
let format = {};
for (const [key, value] of Object.entries(details)) {
if (typeof value === 'string') {
format[key] = value.replace(/\0+$/, '').trim();
} else if (typeof value === 'number') {
if (!Number.isInteger(value)) {
format[key] = Number(value.toFixed(3).replace(/0+$/, ''));
} else {
format[key] = value;
}
}
}
return JSON.stringify(format);
}
close() {
Module._free(this.memory);
}
}
function createFileSystem(images) {
return new Promise((resolve, reject) => {
document.title = 'createFileSystem';
function failed(error) {
reject(new Error('Creating file system: ' + error));
}
function createdFileSystem(fileSystem) {
console.log('test: created file system', fileSystem.name);
window.fileSystem = fileSystem;
resolve();
}
const bytes = images * 30 * 1024 * 1024; // 30M per image.
window.webkitRequestFileSystem(
window.TEMPORARY, bytes, createdFileSystem, failed);
});
}
function writeToFileSystem(image) {
return new Promise(async (resolve, reject) => {
document.title = image;
const buffer = await fetch(image).then((response) => {
if (!response.ok)
throw new Error('Failed to fetch image: ' + image);
return response.arrayBuffer();
}).catch(reject);
function failure(error) {
reject(new Error('Writing file system: ' + error));
}
function writeEntry(fileEntry) {
fileEntry.createWriter((writer) => {
writer.onerror = failure;
writer.onwrite = resolve;
writer.write(new Blob([buffer]));
}, failure);
}
window.fileSystem.root.getFile(
image.replace('images/', ''), {create: true}, writeEntry, failure);
});
}
function readFromFileSystem(image) {
return new Promise((resolve, reject) => {
document.title = image;
function failure(error) {
reject(new Error('Reading file system: ' + error));
}
function invalid(size) {
return size <= 0 || size >= Math.pow(2, 30);
}
function readEntry(fileEntry) {
fileEntry.file((file) => {
if (invalid(file.size))
return failure('invalid file size');
const reader = new FileReader();
reader.onerror = failure;
reader.onload = () => resolve(reader.result);
reader.readAsArrayBuffer(file);
}, failure);
}
window.fileSystem.root.getFile(
image.replace('images/', ''), {}, readEntry, failure);
});
}
function hashUint8Array(data, hash = ~0) {
for (let i = 0; i < data.byteLength; ++i)
hash = (hash << 5) - hash + data[i];
return Math.abs(hash).toString(16);
}
function renderJPG(name, image) {
const data = image.data;
let renderer = new Image();
renderer.onerror = renderer.onload = (event) => {
if (renderer.width > (window.screen.availWidth / 2))
renderer.classList.add('zoom');
document.body.appendChild(renderer);
URL.revokeObjectURL(renderer.src);
if (--window.images_ <= 0)
document.title = 'DONE';
};
renderer.src = URL.createObjectURL(new Blob([data]));
++window.images_;
}
function renderRGB(name, image) {
const data = image.data;
let canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
if (image.width > (window.screen.availWidth / 2))
canvas.classList.add('zoom');
// Create imageData from the image RGB data.
let context = canvas.getContext('2d');
let imageData = context.createImageData(image.width, image.height);
for (let i = 0, j = 0; i < image.size; i += 3, j += 4) {
imageData.data[j + 0] = data[i + 0]; // R
imageData.data[j + 1] = data[i + 1]; // G
imageData.data[j + 2] = data[i + 2]; // B
imageData.data[j + 3] = 255; // A
}
// Render the imageData.
context.putImageData(imageData, 0, 0);
document.body.appendChild(canvas);
}
function renderResult(name, image) {
if (!image)
return;
const hash = hashUint8Array(image.data);
const data = image.data;
image.data = undefined;
const result = JSON.stringify(image);
console.log('test:', name, image.type, 'hash', hash, result);
image.data = data;
if (image.format == 0)
return renderJPG(name, image);
if (image.format == 1)
return renderRGB(name, image);
}
function renderDetails(name, details) {
if (!details)
return;
const text = new TextEncoder('UTF-8').encode(details);
const hash = hashUint8Array(text);
console.log('test:', name, 'details hash', hash, details);
}
window.onload = function loadPiexModule() {
let script = document.createElement('script');
document.head.appendChild(script);
script.src = '/piex.js.wasm';
script.onload = () => {
createPiexModule().then(module => {
window.Module = module;
document.title = 'READY';
});
};
};
window.onerror = (error) => {
console.log('test: FAIL', error, '\n');
document.title = 'DONE';
};
async function runTest(image = 'images/SONY_A500_01.ARW') {
// Start the test of image.
document.title = image;
console.log('test:', image);
document.body.innerHTML = `<pre>${image}</pre>`;
// Fetch the image in an array buffer.
let time = window.performance.now();
const buffer = await readFromFileSystem(image).catch(onerror);
if (!buffer)
return;
let imageBuffer = new ImageBuffer(buffer);
const fetched = window.performance.now() - time;
// Extract the preview|thumbnail images, render them.
return new Promise((resolve) => {
resolve(imageBuffer.process());
}).then((result) => {
let preview = imageBuffer.preview();
let thumb = imageBuffer.thumbnail();
let details = imageBuffer.details();
imageBuffer.close();
time = window.performance.now() - time;
window.images_ = 0;
renderDetails(image, details);
renderResult(image, preview);
renderResult(image, thumb);
console.log('test: done',
time.toFixed(3), fetched.toFixed(3), (time - fetched).toFixed(3));
window.testTime += time;
console.log('\n');
if (!window.images_)
document.title = 'DONE';
}).catch((error) => {
imageBuffer.close();
console.log(error);
document.title = 'DONE';
});
}
</script>