Last active
April 4, 2018 06:53
-
-
Save mwgamera/d71e63493a9351c3ecb4b6518cb7d31c to your computer and use it in GitHub Desktop.
Support simple lossy WebPs in Firefox (See https://github.com/antimatter15/weppy for prior art; NIH, Blobs, less moving parts)
This file contains hidden or 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
"use strict"; | |
// klg, Sep 2016 | |
function LoadWebP(url) { | |
// Fetch the WebP | |
return new Promise(function (resolve, reject) { | |
var xhr = new XMLHttpRequest(); | |
xhr.open('GET', url, true); | |
xhr.responseType = 'arraybuffer'; | |
xhr.onerror = function (e) { reject(e); }; | |
xhr.onload = function () { resolve(new DataView(this.response)); }; | |
xhr.send(); | |
}) | |
// Remux into WebM | |
.then(function (data) { | |
if (data.getUint32(0, true) != 0x46464952 | |
|| data.getUint32(8, true) != 0x50424557) | |
return reject('Not a WebP file'); | |
if (data.getUint32(12, true) != 0x20385056) | |
return reject('Not a simple lossy WebP'); | |
if (data.getUint32(22, true) >> 8 != 0x2a019d) | |
return reject('VP8 frame error'); | |
var W = data.getUint16(26, true); | |
var H = data.getUint16(28, true); | |
W = (W & 0x3fff) * 10 / [10,8,6,5][W >> 14] >>> 0; | |
H = (H & 0x3fff) * 10 / [10,8,6,5][H >> 14] >>> 0; | |
data = data.buffer.slice(20); | |
var template = [ // minimal WebM EBML header | |
26,69,223,163,143,66,130,132,119,101,98,109,66,135,129,2,66,133,129,2, | |
24,83,128,103,8,255,255,255,255,21,73,169,102,141,42,215,177,131,15,66, | |
64,77,128,128,87,65,128,22,84,174,107,161,174,159,215,129,1,131,129,1, | |
134,133,86,95,86,80,56,115,197,129,1,224,140,176,132,255,255,255,255, | |
186,132,255,255,255,255,31,67,182,117,8,255,255,255,255,231,129,0,163, | |
8,255,255,255,255,129,0,0,128]; | |
var header = new DataView(new ArrayBuffer(template.length)); | |
new Uint8Array(header.buffer).set(template); | |
header.setUint32(75, W, false); | |
header.setUint32(81, H, false); | |
header.setUint32(99, data.byteLength+4, false); | |
header.setUint32(90, data.byteLength+13, false); | |
header.setUint32(25, data.byteLength+78, false); | |
return new Blob([header, data], {type: 'video/webm'}); | |
}) | |
// Load as video | |
.then(function (blob) { | |
return new Promise(function (resolve, reject) { | |
var url = URL.createObjectURL(blob); | |
var video = document.createElement('video'); | |
video.onerror = function (e) { | |
reject(e); | |
URL.revokeObjectURL(url); | |
}; | |
video.oncanplay = | |
video.oncanplaythrough = function () { | |
resolve(video); | |
URL.revokeObjectURL(url); | |
}; | |
video.src = url; | |
}); | |
}) | |
// Render on canvas | |
.then(function (video) { | |
return new Promise(function (resolve, reject) { | |
var canvas = document.createElement('canvas'); | |
canvas.width = video.videoWidth; | |
canvas.height = video.videoHeight; | |
var ctx = canvas.getContext('2d'); | |
ctx.drawImage(video, 0, 0, canvas.width, canvas.height); | |
canvas.toBlob(function (blob) { | |
resolve(URL.createObjectURL(blob)); | |
}); | |
}); | |
}); | |
} | |
!function () { | |
document.createElement('video').canPlayType('video/webm; codecs="vp8"') | |
&& new Promise(function (resolve, reject) { | |
var img = document.createElement('img'); | |
img.onload = function () { reject(); }; | |
img.onerror = function () { resolve(); }; | |
img.src = 'data:image/webp;base64,UklGRhYAAABXRUJQVlA4IAoAAAAWAACdASoBAAEA'; | |
}) | |
.then(function () { | |
var map = {}; | |
// Handle style sheets | |
Array.prototype.forEach.call(document.styleSheets, function (sheet) { | |
Array.prototype.forEach.call(sheet.cssRules, function (rule, rs) { | |
var pr = [], re = /url\("((?:\\"|[^"]+)*\.webp)"\)/ig; | |
while (rs = re.exec(rule.cssText)) { | |
if (!map[rs[1]]) | |
map[rs[1]] = LoadWebP(rs[1]); | |
pr.push(map[rs[1]]); | |
} | |
if (pr.length) | |
Promise.all(pr).then(function (s) { | |
var i = 0; | |
sheet.insertRule( | |
rule.cssText.replace(re, function () { | |
return 'url("'+s[i++]+'")'; | |
}), sheet.cssRules.length); | |
}); | |
}); | |
}); | |
// Handle regular images | |
Array.prototype.forEach.call( | |
document.querySelectorAll('img[src$="webp"]'), function (img) { | |
if (!map[img.src]) | |
map[img.src] = LoadWebP(img.src); | |
map[img.src].then(function (blob) { img.src = blob; }); | |
}); | |
window.addEventListener('error', function (e) { | |
if (e && e.target && e.target.nodeName == 'IMG' | |
&& String(e.target.src).match(/\.webp$/i)) { | |
var src = e.target.src; | |
if (!map[src]) | |
map[src] = LoadWebP(src); | |
map[src].then(function (blob) { e.target.src = blob; }); | |
} | |
}, true); | |
}, function () {}); | |
} (); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment