Last active
August 18, 2019 11:58
-
-
Save nolanlawson/3a7b078f6934b61bba1d327f7aed3cba to your computer and use it in GitHub Desktop.
OffscreenCanvas demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Blurhash = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ | |
"use strict"; | |
Object.defineProperty(exports, "__esModule", { value: true }); | |
var digitCharacters = [ | |
"0", | |
"1", | |
"2", | |
"3", | |
"4", | |
"5", | |
"6", | |
"7", | |
"8", | |
"9", | |
"A", | |
"B", | |
"C", | |
"D", | |
"E", | |
"F", | |
"G", | |
"H", | |
"I", | |
"J", | |
"K", | |
"L", | |
"M", | |
"N", | |
"O", | |
"P", | |
"Q", | |
"R", | |
"S", | |
"T", | |
"U", | |
"V", | |
"W", | |
"X", | |
"Y", | |
"Z", | |
"a", | |
"b", | |
"c", | |
"d", | |
"e", | |
"f", | |
"g", | |
"h", | |
"i", | |
"j", | |
"k", | |
"l", | |
"m", | |
"n", | |
"o", | |
"p", | |
"q", | |
"r", | |
"s", | |
"t", | |
"u", | |
"v", | |
"w", | |
"x", | |
"y", | |
"z", | |
"#", | |
"$", | |
"%", | |
"*", | |
"+", | |
",", | |
"-", | |
".", | |
":", | |
";", | |
"=", | |
"?", | |
"@", | |
"[", | |
"]", | |
"^", | |
"_", | |
"{", | |
"|", | |
"}", | |
"~" | |
]; | |
exports.decode83 = function (str) { | |
var value = 0; | |
for (var i = 0; i < str.length; i++) { | |
var c = str[i]; | |
var digit = digitCharacters.indexOf(c); | |
value = value * 83 + digit; | |
} | |
return value; | |
}; | |
exports.encode83 = function (n, length) { | |
var result = ""; | |
for (var i = 1; i <= length; i++) { | |
var digit = (Math.floor(n) / Math.pow(83, length - i)) % 83; | |
result += digitCharacters[Math.floor(digit)]; | |
} | |
return result; | |
}; | |
},{}],2:[function(require,module,exports){ | |
"use strict"; | |
Object.defineProperty(exports, "__esModule", { value: true }); | |
var base83_1 = require("./base83"); | |
var utils_1 = require("./utils"); | |
var error_1 = require("./error"); | |
/** | |
* Returns an error message if invalid or undefined if valid | |
* @param blurhash | |
*/ | |
var validateBlurhash = function (blurhash) { | |
if (!blurhash || blurhash.length < 6) { | |
throw new error_1.ValidationError("The blurhash string must be at least 6 characters"); | |
} | |
var sizeFlag = base83_1.decode83(blurhash[0]); | |
var numY = Math.floor(sizeFlag / 9) + 1; | |
var numX = (sizeFlag % 9) + 1; | |
if (blurhash.length !== 4 + 2 * numX * numY) { | |
throw new error_1.ValidationError("blurhash length mismatch: length is " + blurhash.length + " but it should be " + (4 + 2 * numX * numY)); | |
} | |
}; | |
exports.isBlurhashValid = function (blurhash) { | |
try { | |
validateBlurhash(blurhash); | |
} | |
catch (error) { | |
return { result: false, errorReason: error.message }; | |
} | |
return { result: true }; | |
}; | |
var decodeDC = function (value) { | |
var intR = value >> 16; | |
var intG = (value >> 8) & 255; | |
var intB = value & 255; | |
return [utils_1.sRGBToLinear(intR), utils_1.sRGBToLinear(intG), utils_1.sRGBToLinear(intB)]; | |
}; | |
var decodeAC = function (value, maximumValue) { | |
var quantR = Math.floor(value / (19 * 19)); | |
var quantG = Math.floor(value / 19) % 19; | |
var quantB = value % 19; | |
var rgb = [ | |
utils_1.signPow((quantR - 9) / 9, 2.0) * maximumValue, | |
utils_1.signPow((quantG - 9) / 9, 2.0) * maximumValue, | |
utils_1.signPow((quantB - 9) / 9, 2.0) * maximumValue | |
]; | |
return rgb; | |
}; | |
var decode = function (blurhash, width, height, punch) { | |
validateBlurhash(blurhash); | |
punch = punch | 1; | |
var sizeFlag = base83_1.decode83(blurhash[0]); | |
var numY = Math.floor(sizeFlag / 9) + 1; | |
var numX = (sizeFlag % 9) + 1; | |
var quantisedMaximumValue = base83_1.decode83(blurhash[1]); | |
var maximumValue = (quantisedMaximumValue + 1) / 166; | |
var colors = new Array(numX * numY); | |
for (var i = 0; i < colors.length; i++) { | |
if (i === 0) { | |
var value = base83_1.decode83(blurhash.substring(2, 6)); | |
colors[i] = decodeDC(value); | |
} | |
else { | |
var value = base83_1.decode83(blurhash.substring(4 + i * 2, 6 + i * 2)); | |
colors[i] = decodeAC(value, maximumValue * punch); | |
} | |
} | |
var bytesPerRow = width * 4; | |
var pixels = new Uint8ClampedArray(bytesPerRow * height); | |
for (var y = 0; y < height; y++) { | |
for (var x = 0; x < width; x++) { | |
var r = 0; | |
var g = 0; | |
var b = 0; | |
for (var j = 0; j < numY; j++) { | |
for (var i = 0; i < numX; i++) { | |
var basis = Math.cos((Math.PI * x * i) / width) * | |
Math.cos((Math.PI * y * j) / height); | |
var color = colors[i + j * numX]; | |
r += color[0] * basis; | |
g += color[1] * basis; | |
b += color[2] * basis; | |
} | |
} | |
var intR = utils_1.linearTosRGB(r); | |
var intG = utils_1.linearTosRGB(g); | |
var intB = utils_1.linearTosRGB(b); | |
pixels[4 * x + 0 + y * bytesPerRow] = intR; | |
pixels[4 * x + 1 + y * bytesPerRow] = intG; | |
pixels[4 * x + 2 + y * bytesPerRow] = intB; | |
pixels[4 * x + 3 + y * bytesPerRow] = 255; // alpha | |
} | |
} | |
return pixels; | |
}; | |
exports.default = decode; | |
},{"./base83":1,"./error":4,"./utils":5}],3:[function(require,module,exports){ | |
"use strict"; | |
Object.defineProperty(exports, "__esModule", { value: true }); | |
var base83_1 = require("./base83"); | |
var utils_1 = require("./utils"); | |
var error_1 = require("./error"); | |
var bytesPerPixel = 4; | |
var multiplyBasisFunction = function (pixels, width, height, basisFunction) { | |
var r = 0; | |
var g = 0; | |
var b = 0; | |
var bytesPerRow = width * bytesPerPixel; | |
for (var x = 0; x < width; x++) { | |
for (var y = 0; y < height; y++) { | |
var basis = basisFunction(x, y); | |
r += | |
basis * utils_1.sRGBToLinear(pixels[bytesPerPixel * x + 0 + y * bytesPerRow]); | |
g += | |
basis * utils_1.sRGBToLinear(pixels[bytesPerPixel * x + 1 + y * bytesPerRow]); | |
b += | |
basis * utils_1.sRGBToLinear(pixels[bytesPerPixel * x + 2 + y * bytesPerRow]); | |
} | |
} | |
var scale = 1 / (width * height); | |
return [r * scale, g * scale, b * scale]; | |
}; | |
var encodeDC = function (value) { | |
var roundedR = utils_1.linearTosRGB(value[0]); | |
var roundedG = utils_1.linearTosRGB(value[1]); | |
var roundedB = utils_1.linearTosRGB(value[2]); | |
return (roundedR << 16) + (roundedG << 8) + roundedB; | |
}; | |
var encodeAC = function (value, maximumValue) { | |
var quantR = Math.floor(Math.max(0, Math.min(18, Math.floor(utils_1.signPow(value[0] / maximumValue, 0.5) * 9 + 9.5)))); | |
var quantG = Math.floor(Math.max(0, Math.min(18, Math.floor(utils_1.signPow(value[1] / maximumValue, 0.5) * 9 + 9.5)))); | |
var quantB = Math.floor(Math.max(0, Math.min(18, Math.floor(utils_1.signPow(value[2] / maximumValue, 0.5) * 9 + 9.5)))); | |
return quantR * 19 * 19 + quantG * 19 + quantB; | |
}; | |
var encode = function (pixels, width, height, componentX, componentY) { | |
if (componentX < 1 || componentX > 9 || componentY < 1 || componentY > 9) { | |
throw new error_1.ValidationError("BlurHash must have between 1 and 9 components"); | |
} | |
if (width * height * 4 !== pixels.length) { | |
throw new error_1.ValidationError("Width and height must match the pixels array"); | |
} | |
var factors = []; | |
var _loop_1 = function (y) { | |
var _loop_2 = function (x) { | |
var normalisation = x == 0 && y == 0 ? 1 : 2; | |
var factor = multiplyBasisFunction(pixels, width, height, function (i, j) { | |
return normalisation * | |
Math.cos((Math.PI * x * i) / width) * | |
Math.cos((Math.PI * y * j) / height); | |
}); | |
factors.push(factor); | |
}; | |
for (var x = 0; x < componentX; x++) { | |
_loop_2(x); | |
} | |
}; | |
for (var y = 0; y < componentY; y++) { | |
_loop_1(y); | |
} | |
var dc = factors[0]; | |
var ac = factors.slice(1); | |
var hash = ""; | |
var sizeFlag = componentX - 1 + (componentY - 1) * 9; | |
hash += base83_1.encode83(sizeFlag, 1); | |
var maximumValue; | |
if (ac.length > 0) { | |
var actualMaximumValue = Math.max.apply(Math, ac.map(function (val) { return Math.max.apply(Math, val); })); | |
var quantisedMaximumValue = Math.floor(Math.max(0, Math.min(82, Math.floor(actualMaximumValue * 166 - 0.5)))); | |
maximumValue = (quantisedMaximumValue + 1) / 166; | |
hash += base83_1.encode83(quantisedMaximumValue, 1); | |
} | |
else { | |
maximumValue = 1; | |
hash += base83_1.encode83(0, 1); | |
} | |
hash += base83_1.encode83(encodeDC(dc), 4); | |
ac.forEach(function (factor) { | |
hash += base83_1.encode83(encodeAC(factor, maximumValue), 2); | |
}); | |
return hash; | |
}; | |
exports.default = encode; | |
},{"./base83":1,"./error":4,"./utils":5}],4:[function(require,module,exports){ | |
"use strict"; | |
var __extends = (this && this.__extends) || (function () { | |
var extendStatics = function (d, b) { | |
extendStatics = Object.setPrototypeOf || | |
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | |
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | |
return extendStatics(d, b); | |
}; | |
return function (d, b) { | |
extendStatics(d, b); | |
function __() { this.constructor = d; } | |
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | |
}; | |
})(); | |
Object.defineProperty(exports, "__esModule", { value: true }); | |
var ValidationError = /** @class */ (function (_super) { | |
__extends(ValidationError, _super); | |
function ValidationError(message) { | |
var _this = _super.call(this, message) || this; | |
_this.name = "ValidationError"; | |
_this.message = message; | |
return _this; | |
} | |
return ValidationError; | |
}(Error)); | |
exports.ValidationError = ValidationError; | |
},{}],5:[function(require,module,exports){ | |
"use strict"; | |
Object.defineProperty(exports, "__esModule", { value: true }); | |
exports.sRGBToLinear = function (value) { | |
var v = value / 255; | |
if (v <= 0.04045) { | |
return v / 12.92; | |
} | |
else { | |
return Math.pow((v + 0.055) / 1.055, 2.4); | |
} | |
}; | |
exports.linearTosRGB = function (value) { | |
var v = Math.max(0, Math.min(1, value)); | |
if (v <= 0.0031308) { | |
return Math.round(v * 12.92 * 255 + 0.5); | |
} | |
else { | |
return Math.round((1.055 * Math.pow(v, 1 / 2.4) - 0.055) * 255 + 0.5); | |
} | |
}; | |
exports.sign = function (n) { return (n < 0 ? -1 : 1); }; | |
exports.signPow = function (val, exp) { | |
return exports.sign(val) * Math.pow(Math.abs(val), exp); | |
}; | |
},{}],"blurhash":[function(require,module,exports){ | |
"use strict"; | |
function __export(m) { | |
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; | |
} | |
Object.defineProperty(exports, "__esModule", { value: true }); | |
var decode_1 = require("./decode"); | |
exports.decode = decode_1.default; | |
exports.isBlurhashValid = decode_1.isBlurhashValid; | |
var encode_1 = require("./encode"); | |
exports.encode = encode_1.default; | |
__export(require("./error")); | |
},{"./decode":2,"./encode":3,"./error":4}]},{},[])("blurhash") | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Offscreen canvas demo</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
</head> | |
<body> | |
<h1>Offscreen canvas demo</h1> | |
<button id="canvas-btn">Decode using worker+<canvas></button> | |
<button id="offscreen-canvas-btn">Decode using OffscreenCanvas</button> | |
<pre id="display"></pre> | |
<script src="script.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(() => { | |
const RESOLUTION = 32 | |
const $ = document.querySelector.bind(document) | |
const $display = $('#display') | |
const $canvasBtn = $('#canvas-btn') | |
const $offscreenBtn = $('#offscreen-canvas-btn') | |
const canvas = document.createElement('canvas') | |
canvas.height = RESOLUTION | |
canvas.width = RESOLUTION | |
const canvasContext2D = canvas.getContext('2d') | |
const worker = new Worker('worker.js') | |
const encoded = 'UJDJS1-.rUox0JxspIocwZxro}WB%~oyVqa}' | |
function postMessageToWorker (msg) { | |
return new Promise((resolve, reject) => { | |
function cleanup () { | |
worker.removeEventListener('message', onMessage) | |
worker.removeEventListener('error', onError) | |
} | |
function onMessage (e) { | |
cleanup() | |
resolve(e.data) | |
} | |
function onError (e) { | |
cleanup() | |
reject(e) | |
} | |
worker.addEventListener('message', onMessage) | |
worker.addEventListener('error', onError) | |
worker.postMessage(msg) | |
}) | |
} | |
async function decodeUsingCanvas () { | |
const { imageData, error } = await postMessageToWorker({ encoded, type: 'canvas' }) | |
if (error) { | |
throw new Error(error) | |
} | |
canvasContext2D.putImageData(imageData, 0, 0) | |
const blob = await new Promise(resolve => canvas.toBlob(resolve)) | |
return URL.createObjectURL(blob) | |
} | |
async function decodeUsingOffscreenCanvas () { | |
const { decoded, error } = await postMessageToWorker({ encoded, type: 'offscreen' }) | |
if (error) { | |
throw new Error(error) | |
} | |
return decoded | |
} | |
$canvasBtn.addEventListener('click', async () => { | |
performance.mark('canvas-start') | |
let error | |
try { | |
const url = await decodeUsingCanvas() | |
console.log('url', url) | |
} catch (e) { | |
error = true | |
} finally { | |
performance.measure('canvas', 'canvas-start') | |
$display.innerHTML += `<canvas> ${error ? 'errored after' : 'took'} ` + | |
`${performance.getEntriesByName('canvas').slice(-1)[0].duration}ms\n` | |
} | |
}) | |
$offscreenBtn.addEventListener('click', async () => { | |
performance.mark('offscreen-start') | |
let error | |
try { | |
const url = await decodeUsingOffscreenCanvas() | |
console.log('url', url) | |
} catch (e) { | |
error = true | |
} finally { | |
performance.measure('offscreen', 'offscreen-start') | |
$display.innerHTML += `OffscreenCanvas ${error ? 'errored after' : 'took'} ` + | |
`${performance.getEntriesByName('offscreen').slice(-1)[0].duration}ms\n` | |
} | |
}) | |
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
importScripts('blurhash.js') | |
const RESOLUTION = 32 | |
const OFFSCREEN_CANVAS = typeof OffscreenCanvas === 'function' | |
? new OffscreenCanvas(RESOLUTION, RESOLUTION) : null | |
const OFFSCREEN_CANVAS_CONTEXT_2D = OFFSCREEN_CANVAS | |
? OFFSCREEN_CANVAS.getContext('2d') : null | |
self.onmessage = async (e) => { | |
performance.mark('onmessage') | |
try { | |
const { encoded, type } = e.data | |
const pixels = Blurhash.decode(encoded, RESOLUTION, RESOLUTION) | |
const imageData = new ImageData(pixels, RESOLUTION, RESOLUTION) | |
if (type === 'offscreen') { | |
OFFSCREEN_CANVAS_CONTEXT_2D.putImageData(imageData, 0, 0) | |
performance.mark('convertToBlob') | |
const blob = await OFFSCREEN_CANVAS.convertToBlob() | |
performance.measure('convertToBlob', 'convertToBlob') | |
const decoded = URL.createObjectURL(blob) | |
postMessage({ decoded, imageData: null }) | |
} else { | |
postMessage({ imageData, decoded: null }) | |
} | |
} catch (error) { | |
postMessage({ error: error.message, imageData: null, decoded: null }) | |
} | |
performance.measure('onmessage', 'onmessage') | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment