Skip to content

Instantly share code, notes, and snippets.

@mwgamera
Last active April 4, 2018 06:53
Show Gist options
  • Save mwgamera/d71e63493a9351c3ecb4b6518cb7d31c to your computer and use it in GitHub Desktop.
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)
"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