| <!-- | 
 | 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> |