blob: f01512a4247ea28a60b44cf89aab4f4377849f64 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<title>WebP2 Triangle Preview Demo</title>
<script type="text/javascript">
////////////////////////////////////////////////////////////////////////////////
function CreateShader(gl, type, src) {
var shader = gl.createShader(type);
gl.shaderSource(shader, src);
gl.compileShader(shader);
return shader;
}
var f_shader_src = " \
precision mediump float; \
uniform float amp; \
varying vec4 vColor; \
float rand(vec2 pt) { \
return fract(sin(dot(pt.xy, vec2(2.9, 23.7))) * 198057.4); \
} \
void main(void) { \
vec3 tmp = vColor.rgb; \
float incr = amp * (rand(gl_FragCoord.xy) - 0.25); \
tmp += vec3(incr, incr, incr); \
gl_FragColor = vec4(tmp, vColor.a); \
} \
";
var v_shader_src = " \
attribute vec3 coordinates; \
attribute vec4 color; \
varying vec4 vColor; \
void main(void) { \
gl_Position = vec4(coordinates, 1.0); \
vColor = color; \
} \
";
function initGL(gl) {
// TODO(skal): trap errors
var f_shader = CreateShader(gl, gl.FRAGMENT_SHADER, f_shader_src);
var v_shader = CreateShader(gl, gl.VERTEX_SHADER, v_shader_src);
var shader_program = gl.createProgram();
gl.attachShader(shader_program, f_shader);
gl.attachShader(shader_program, v_shader);
gl.linkProgram(shader_program);
gl.useProgram(shader_program);
return shader_program;
}
////////////////////////////////////////////////////////////////////////////////
// Format constants
var kYCoCgBitDepth = 6;
var kYCoCgMax = (1 << kYCoCgBitDepth) - 1;
var kPreviewMinNumVertices = 0;
var kPreviewMaxNumVertices = 1024;
var kPreviewMinNumColors = 2;
var kPreviewMaxNumColors = 32;
var kPreviewMinGridSize = 2;
var kPreviewMaxGridSize = 256;
var kProbaMax = 1 << 16;
var kPreviewOpaqueProba = 3 * kProbaMax / 4;
var kPreviewNoiseProba = kProbaMax / 2;
var kMaxSum = 256;
////////////////////////////////////////////////////////////////////////////////
// ANS
function ANSBinSymbol(p0, p1) {
this.p0_ = p0;
this.sum_ = p0 + p1;
}
ANSBinSymbol.prototype.Update = function(bit) {
if (this.sum_ < kMaxSum) {
if (!bit) ++this.p0_;
++this.sum_;
}
return bit;
}
ANSBinSymbol.prototype.Proba = function() {
return Math.floor((this.p0_ << 16) / this.sum_);
}
function ValueStats() {
this.zero = new ANSBinSymbol(1, 1);
this.sign = new ANSBinSymbol(1, 1);
this.bits = new Array(kYCoCgBitDepth);
for (var i = 0; i < kYCoCgBitDepth; ++i) {
this.bits[i] = new ANSBinSymbol(1, 1);
}
return this;
}
////////////////////////////////////////////////////////////////////////////////
function ANSDec(data) {
this.data_ = data;
this.pos_ = 0;
this.max_pos_ = data.length;
this.state_ = new Uint32Array(1); // we need *unsigned* 32b
this.state_[0] = 0;
this.NextWord();
this.NextWord();
}
ANSDec.prototype.NextWord = function() {
var pos = this.pos_;
this.pos_ = pos + 2;
var hi = this.data_.charCodeAt(pos + 0);
var lo = this.data_.charCodeAt(pos + 1);
var val = (lo + 256 * hi) || 0;
this.state_[0] = (this.state_[0] << 16) | val;
}
ANSDec.prototype.NextBit = function(p0) {
var q0 = kProbaMax - p0;
if (p0 == 0) return 1;
if (q0 == 0) return 0;
var xfrac = this.state_[0] % kProbaMax;
var bit = (xfrac >= p0);
if (!bit) {
this.state_[0] = p0 * (this.state_[0] >>> 16) + xfrac;
} else {
this.state_[0] = q0 * (this.state_[0] >>> 16) + xfrac - p0;
}
if (this.state_[0] < kProbaMax) this.NextWord();
return bit;
}
ANSDec.prototype.NextAdaptiveBit = function(s) { // adaptive bit
var bit = this.NextBit(s.Proba());
return s.Update(bit);
}
// Reads a value in range [min_range, max_range]
ANSDec.prototype.ReadRange = function(min_range, max_range) {
max_range = max_range + 1 - min_range;
var s = this.state_[0];
var value = s % max_range;
s /= max_range;
if (s < kProbaMax) { // we need to do pseudo-uint64 arithmetic
this.NextWord();
var s_lo = this.state_[0] & 0xffff;
value = (value << 16) + s_lo;
s = (s << 16) + Math.floor(value / max_range);
value = value % max_range;
}
this.state_[0] = s;
return value + min_range;
}
ANSDec.prototype.ReadAValue = function(stats, is_positive, pred) {
if (this.NextAdaptiveBit(stats.zero)) return pred;
var v = 1;
for (var i = 0, j = 1; j <= kYCoCgMax; j <<= 1) {
if (this.NextAdaptiveBit(stats.bits[i++])) v += j;
}
if (!is_positive && this.NextAdaptiveBit(stats.sign)) v = -v;
return pred + v;
}
////////////////////////////////////////////////////////////////////////////////
// Preview parameters
function Vtx(x, y, idx) {
this.x = x;
this.y = y;
this.idx = idx;
}
Vtx.prototype = {
eq: function(p) {
var eps = 0.0001;
var dx = this.x - p.x;
var dy = this.y - p.y;
return (dx < 0 ? -dx : dx) < eps && (dy < 0 ? -dy : dy) < eps;
}
}
function Color(r, g, b, a) {
this.r = r;
this.g = g;
this.b = b;
this.a = (a > 0) ? 255 : 0;
return this;
}
function clip8b(v) { return (v < 0) ? 0 : (v > 255) ? 255 : v; }
function YCoCg_to_RGB(y, co, cg, a) {
y = Math.floor((y * 255) / (64 - 1));
cg = Math.floor((cg * 255) / (64 - 1)) - 128;
co = Math.floor((co * 255) / (64 - 1)) - 128;
var diff = y - cg;
var r = clip8b(diff + co);
var g = clip8b(y + cg);
var b = clip8b(diff - co);
return new Color(r, g, b, a);
}
function Preview() {
this.grid_x = 0;
this.grid_y = 0;
this.num_colors = 0;
this.has_alpha = false;
this.num_pts = 0;
this.use_noise = false;
this.txt = "";
this.qpts = null;
this.counts = null;
return this;
}
Preview.prototype.GetBinText = function(reader) { return this.txt; }
Preview.prototype.GetInfoText = function(reader) {
var info = "[";
info += " grid = " + this.grid_x + " x " + this.grid_y;
info += ", num_colors = " + this.num_colors;
info += " num_pts = " + this.num_pts;
if (this.has_alpha) info += " (with alpha)";
info += "]";
return info;
}
Preview.prototype.GetCMapText = function(reader) {
var cmap_txt = "<h5>Colormap (w/ use counts):<br />";
for (var i = 0; i < this.num_colors; ++i) {
cmap_txt += "<div style='width:30px; height:20px; ";
cmap_txt += "background-color: rgb(" +
this.cmap[i].r + ", " + this.cmap[i].g + ", " + this.cmap[i].b + ");"
cmap_txt += " display: inline-block;'>";
cmap_txt += "</div>(" + this.counts[i] + ")&nbsp;";
}
cmap_txt += "</h5>";
return cmap_txt;
}
Preview.prototype.ReadPalette = function(reader) {
this.num_colors = reader.ReadRange(kPreviewMinNumColors, kPreviewMaxNumColors);
this.has_alpha = reader.NextBit(kPreviewOpaqueProba);
this.txt += this.grid_x + " " + this.grid_y + " ";
this.txt += this.num_colors + " " + (this.has_alpha ? 1 : 0) + "\n";
this.cmap = new Array(this.num_colors);
var alpha = new ANSBinSymbol(2, 2);
var stats_yco = new ValueStats;
var stats_cg = new ValueStats;
var pred = Color(kYCoCgMax >> 1, kYCoCgMax >> 1, 0, 255); // predictor
for (var i = 0; i < this.num_colors; ++i) {
if (this.has_alpha && reader.NextAdaptiveBit(alpha)) pred.a = 1 - pred.a;
var y = pred.r = reader.ReadAValue(stats_yco, false, pred.r);
var co = pred.g = reader.ReadAValue(stats_yco, false, pred.g);
var cg = pred.b = reader.ReadAValue(stats_cg, true, pred.b);
this.cmap[i] = YCoCg_to_RGB(y, co, cg, pred.a); // YCoCg -> RGB
this.txt += y + " " + co + " " + cg + " " + (pred.a > 0 ? 1 : 0) + "\n";
}
}
Preview.prototype.IsCorner = function(x, y) {
return ((x == 0 || x == this.grid_x - 1) &&
(y == 0 || y == this.grid_y - 1));
}
Preview.prototype.DecodeColorIdx = function(reader, stats, idx) {
if (reader.NextAdaptiveBit(stats)) {
var tmp = reader.ReadRange(0, this.num_colors - 2);
idx = (tmp >= idx ? tmp + 1 : tmp);
}
++this.counts[idx];
return idx;
}
Preview.prototype.ReadVertices = function(reader) {
var pts_left = this.grid_x * this.grid_y - 4; // omit corners
var min_num_pts = Math.max(Math.max(this.num_colors - 4, 0),
kPreviewMinNumVertices);
var max_num_pts = Math.min(pts_left, kPreviewMaxNumVertices);
this.num_pts = reader.ReadRange(min_num_pts, max_num_pts);
this.qpts = new Array(this.num_pts + 4, 0); // including corners
this.counts = new Array(this.num_colors).fill(0);
// decode corners
var stats_idx = new ANSBinSymbol(2, 2);
var idx = 0; // color index (used as predictor too)
idx = this.DecodeColorIdx(reader, stats_idx, idx);
this.qpts[0] = new Vtx(0, 0, idx);
idx = this.DecodeColorIdx(reader, stats_idx, idx);
this.qpts[1] = new Vtx(this.grid_x - 1, 0, idx);
idx = this.DecodeColorIdx(reader, stats_idx, idx);
this.qpts[2] = new Vtx(0, this.grid_y - 1, idx);
idx = this.DecodeColorIdx(reader, stats_idx, idx);
this.qpts[3] = new Vtx(this.grid_x - 1, this.grid_y - 1, idx);
// and the remaining vertices
var k = 0;
var den = pts_left;
var num = this.num_pts;
for (var y = 0; y < this.grid_y; ++y) {
for (var x = 0; x < this.grid_x; ++x) {
var letter = '.';
if (this.IsCorner(x, y)) {
var c = this.qpts[(x != 0 ? 1 : 0) + (y != 0 ? 2 : 0)].idx;
letter = String.fromCharCode(c + 97);
} else if (k < this.num_pts) {
var proba = kProbaMax - Math.floor((num << 16) / den);
var bit = reader.NextBit(proba);
if (bit) {
idx = this.DecodeColorIdx(reader, stats_idx, idx);
this.qpts[k + 4] = new Vtx(x, y, idx);
letter = String.fromCharCode(idx + 97); // 'a' + idx
--num;
++k;
}
--den;
}
this.txt += letter;
}
this.txt += "\n";
}
}
////////////////////////////////////////////////////////////////////////////////
// Delaunay triangulation
var Delaunay = (function() {
function Edge(p0, p1) { this.pts = [p0, p1]; }
Edge.prototype = {
eq: function(edge, vtx) {
var na = this.pts, nb = edge.pts;
var na0 = vtx[na[0]], na1 = vtx[na[1]];
var nb0 = vtx[nb[0]], nb1 = vtx[nb[1]];
return na0.eq(nb1) && na1.eq(nb0); // are edges opposite?
}
}
function Triangle(P0, P1, P2, vtx) {
this.vtx = [P0, P1, P2];
this.edges = [new Edge(P0, P1), new Edge(P1, P2), new Edge(P2, P0)];
var circle = this.circle = new Object();
var p0 = vtx[P0], p1 = vtx[P1], p2 = vtx[P2];
var ax = p1.x - p0.x, ay = p1.y - p0.y;
var bx = p2.x - p0.x, by = p2.y - p0.y;
var t = (p1.x * p1.x - p0.x * p0.x + p1.y * p1.y - p0.y * p0.y);
var u = (p2.x * p2.x - p0.x * p0.x + p2.y * p2.y - p0.y * p0.y);
var norm = 0.5 / (ax * by - ay * bx);
var x = circle.x = ((p2.y - p0.y) * t + (p0.y - p1.y) * u) * norm;
var y = circle.y = ((p0.x - p2.x) * t + (p1.x - p0.x) * u) * norm;
var dx = p0.x - x;
var dy = p0.y - y;
circle.r2 = dx * dx + dy * dy; // radius squared, times i_s ^ 2
}
Triangle.prototype = {
InCircle: function(x, y) {
var circle = this.circle;
var dx = x - circle.x;
var dy = y - circle.y;
var r2 = dx * dx + dy * dy;
// note the '<=': this is important for replacing vertex!
return (r2 <= circle.r2 + 0.001);
}
}
function Delaunay(width, height, pts) {
this.width = width;
this.height = height;
this.triangles = null;
this.vtx = new Array();
this.Init(width, height);
if (pts != null) {
for (var i = 0; i < pts.length; ++i) this.Insert(pts[i]);
}
}
Delaunay.prototype = {
NewVtx: function(x, y, idx) {
return this.vtx.push(new Vtx(x, y, idx)) - 1;
},
Init: function(w, h) {
// TODO(skal): remove corner dups
var p0 = this.NewVtx( 0, 0, 0);
var p1 = this.NewVtx(w - 1, 0, 0);
var p2 = this.NewVtx(w - 1, h - 1, 0);
var p3 = this.NewVtx( 0, h - 1, 0);
this.triangles = [
new Triangle(p0, p1, p2, this.vtx),
new Triangle(p0, p2, p3, this.vtx)
];
},
Insert: function(new_pt) {
var triangles = this.triangles;
var tri = [];
var edges = [];
for (var i = 0; i < this.vtx.length; ++i) {
if (this.vtx[i].x == new_pt.x && this.vtx[i].y == new_pt.y) {
this.vtx[i].idx = new_pt.idx;
return;
}
}
for (var i = 0; i < triangles.length; ++i) {
var t = triangles[i];
if (t.InCircle(new_pt.x, new_pt.y)) {
edges.push(t.edges[0], t.edges[1], t.edges[2]);
} else {
tri.push(t);
}
}
var polygon = [];
mainLoop:
for (var i = 0; i < edges.length; ++i) { // remove duplicate edges
var edge = edges[i];
for (var j = 0; j < polygon.length; ++j) {
if (edge.eq(polygon[j], this.vtx)) {
polygon.splice(j, 1);
continue mainLoop;
}
}
polygon.push(edge);
}
for (var i = 0; i < polygon.length; ++i) {
var edge = polygon[i];
var new_vtx = this.NewVtx(new_pt.x, new_pt.y, new_pt.idx);
tri.push(new Triangle(edge.pts[0], edge.pts[1], new_vtx, this.vtx));
}
this.triangles = tri;
},
getTriangles: function() {
return this.triangles.slice();
}
}
return Delaunay;
})();
////////////////////////////////////////////////////////////////////////////////
Preview.prototype.Render= function(w, canvas, show_triangles) {
var h = w * this.grid_y / this.grid_x;
canvas.width = w;
canvas.height = h;
var delaunay = new Delaunay(this.grid_x, this.grid_y, this.qpts);
var triangles = delaunay.getTriangles();
var vtx = delaunay.vtx;
var num_triangles = triangles.length;
var num_vtx = vtx.length;
do {
var gl = canvas.getContext('webgl');
if (!gl) { alert("ERROR!"); break; }
var shader_program = initGL(gl);
var amp = gl.getUniformLocation(shader_program, "amp");
if (!amp) { alert("shader ERROR!"); break; }
var amp_value = document.getElementById('amp').value;
gl.uniform1f(amp, parseFloat(amp_value));
var colors = new Uint8Array(4 * num_vtx);
var vertices = new Float32Array(3 * num_vtx);
var sx = 2. / (this.grid_x - 1.);
var sy = -2. / (this.grid_y - 1.);
for (var i = 0; i < num_vtx; ++i) {
var V = vtx[i];
var col = this.cmap[V.idx];
vertices[i * 3 + 0] = -1 + sx * V.x;
vertices[i * 3 + 1] = 1 + sy * V.y;
vertices[i * 3 + 2] = 1.;
colors[i * 4 + 0] = col.r;
colors[i * 4 + 1] = col.g;
colors[i * 4 + 2] = col.b;
colors[i * 4 + 3] = col.a;
}
var vertex_idx_buffer;
{
var indexes = new Uint16Array(3 * num_triangles);
for (var i = 0; i < num_triangles; ++i) {
var t = triangles[i];
indexes[3 * i + 0] = t.vtx[0];
indexes[3 * i + 1] = t.vtx[1];
indexes[3 * i + 2] = t.vtx[2];
}
vertex_idx_buffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertex_idx_buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexes, gl.STATIC_DRAW);
}
{
var vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
var coord = gl.getAttribLocation(shader_program, "coordinates");
gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coord);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
{
var color_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
var color = gl.getAttribLocation(shader_program, "color");
gl.vertexAttribPointer(color, 4, gl.UNSIGNED_BYTE, true, 0, 0) ;
gl.enableVertexAttribArray(color);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
gl.disable(gl.DEPTH_TEST);
gl.viewport(0, 0, w, h);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertex_idx_buffer);
gl.drawElements(gl.TRIANGLES, 3 * num_triangles, gl.UNSIGNED_SHORT, 0);
if (show_triangles) {
gl.enable(gl.BLEND);
gl.blendFunc(gl.ZERO, gl.ZERO);
for (var i = 0; i < num_triangles; ++i) {
gl.drawElements(gl.LINE_LOOP, 3, gl.UNSIGNED_SHORT, 3 * i * 2);
}
}
} while (false);
}
////////////////////////////////////////////////////////////////////////////////
// URL manipulation
// sets ?preview=xxxx param value in URL
function SetPreviewURL(data64) {
var param = "preview";
var pattern = new RegExp('\\b('+ param +'=).*?(&|#|$)');
var url = window.location.href;
var new_url;
if (url.search(pattern) >= 0) {
new_url = url.replace(pattern,'$1' + data64 + '$2');
} else {
new_url = url.replace(/[?#]$/, '');
new_url = new_url
+ (new_url.indexOf('?') > 0 ? '&' : '?') + param + '=' + data64;
}
window.history.pushState("", "", new_url);
}
// break down URL params in key/value. Example: ZwnQWM?preview=543254634643
function GetParamsFromURL() {
var vars = {};
var url = window.location.href;
url.replace(/[?&]+([^=&]+)=([^&]*)/gi,
function(m, key, value) { vars[key] = value; });
return vars;
}
////////////////////////////////////////////////////////////////////////////////
// Main call.
function decode(data64) {
var canvas = document.createElement('canvas');
var show_tri = document.getElementById('show_tri').checked;
var size = parseInt(document.getElementById('size').value);
if (size < 16) size = 16;
SetPreviewURL(data64);
// Parsing starts.
var reader = new ANSDec(atob(data64));
var p = new Preview();
p.grid_x = reader.ReadRange(kPreviewMinGridSize, kPreviewMaxGridSize);
p.grid_y = reader.ReadRange(kPreviewMinGridSize, kPreviewMaxGridSize);
p.use_noise = reader.NextBit(kPreviewNoiseProba);
// read palette
p.ReadPalette(reader);
// Read vertices
p.ReadVertices(reader);
// Draw the vertices.
p.Render(size, canvas, show_tri);
// Print side infos
var elm = document.getElementById('comp_data');
elm.innerHTML = p.GetBinText();
elm.rows = p.grid_y + p.num_colors + 2;
elm.cols = p.grid_x;
document.getElementById('info').innerHTML = p.GetInfoText();
document.getElementById('text').innerHTML = reader.max_pos_ + " bytes ";
// Canvas to lossless WebP
var image = document.getElementById('output');
image.width = canvas.width;
image.height = canvas.height;
image.src = canvas.toDataURL('image/webp', 1.);
// Draw the small cmap
document.getElementById('cmap').innerHTML = p.GetCMapText();
}
function copy_to_clipboard() {
document.getElementById('comp_data').select();
document.execCommand('copy');
}
////////////////////////////////////////////////////////////////////////////////
function decode_text() {
decode(document.getElementById('data').value);
}
function setPrecalc(idx) {
var precalc = [
// Mona Lisa:
"AzFhqCBwmt34gyNzfH7iVoErk7T3RW6hS6Xo4vEq2AT2TcphQkyZZpyDDyZxHoGflzF7crbOvhvvl26pVjYkNWgdBdprEeqUURSfwEwXiVuHmt2kuSl8lO5ThRSfluaOTDjc/P/EdTHUHFkia/wBTh73YNYS9zFrrbzGtk8tS9gg3dJ5bAjiwmM+gxXvIu5rm42ObIr0YMxMgeAtWjV3mH/ita9nKI9PrRD7qvuQGoDg5BPhAO+kPXhddDDziI8Ocb3MLImE46ILFAEJTDeV4PvG2srfDAWVp+h8Dntgm4FnTr4h+NR23ExOx8CLakoSHI598ZzMOVMW3g==",
// Van Gogh:
"AASbbGnIRlbWayT+bDQVuQYM3k1RSBbFw22jBlneyFudlxZtnGXTcgzwK2iVmKgA1Ko5OHyc/2M1fNKxGWqb/M35mFmQ1jHbhZLtoFg4kx7jr7V8MYcL/MJwJTSlrEFY0mYmFxyzPTxNA3Vp2KSBGrJeaIuOlEdfo0BQbI7j/koh4L2KtN5yYNX9rbOEdt9156VygFfOHBeyuwQYZ7ueeIiRrYYEkn+c5snrcNo5hB2RcFN1QFo3m6ztrM1s1qQfMqE=",
// Google logo
"AgV0AyCH4vltVQ4o8GYeFjUb0lyMZNAgDkGWf3IlYGA/dooDKAIHl+crn/i1PRIVfqUhFvyPUp4W37yDg0LhZwEI08lPsFTPBKWFptT5ywKTQ3a9cEzzhSy3ZwfjfIgl966D26jVz+caxrZ29mynYlvXfAooeeMIidXX0fpZkUQgbtI3TwCJg6flzDPEzyPWyd1dSsWNbHzy79gAxYI7llL4Is0F+/uhybAE3evJxj8dx15ze5DCd985rYkL2AI8rfcrmN7diK7anSwUomXv5sJVagGelMQeD6UEq1Goabg+yBnfe2w9a5w3ffGNaEISrB9um/OQFb9LglSdDAgBqklP/e4=",
// Grumpy cat
"AAKHk3aqfepiUppmgQTdxMz4bLjxZEgwGhpaiZwoBPeuhDabCH/DcGO091rMKb4VCgz7NC0js3EuYFlC7LmXOzTcmEyrgwEhy161dvqsz+TgVbs35EryEFSy64A6mbDx2j8IyUdAbZBSVCjVX+7MFVyov6BxDWZ1odtA0y6jTAE01/0Fq3XHbVVyik0A0pYbffbtlesMNTcO6erh4HWWtfYT+fexRhBc3okvmm/eROXnRSsNKKp/2nH++nq62DqoT/91wANeoMW0q44CuH0Slfdo",
// POTUS
"AZICmDDCqiP/P/uG3bdk0WFviwIpzlbnQnl/vj9CaF9zr+cCTZk6lXsOzfyGDDgwkw+3HFvFMG0mO3vqxLN8sr5IbSpyT05bu96QVDdkvNS7wPWI5wus57g8X8y4aLPj0Ykk+W0iqeG1wj5cgmzzYPgwO990cQUVcXw3pRHrOCkdVfE78YUUmXsKqJV+VVf9hIXIWA==",
// Am the one who knocks
"AfwrBdIb7WOGuXuLbgLk6782Fak3GzM6oce4qlORsFyE4HIUv1Wz6/5b48PlBYFjxGKwMz1emlTLcCgE+IF6t+1ji3smVVG1TpyqtFqkNKdatbPS85WSHwlPkmfK+rt/PEqLz8zufIyUeTwr6o9zm1OsikZ8HXXtJ946RAZ+yrk760Z/KWJrl+DdnkL36+AXQSG9o46Dq2xO4udsMt7RCumnpI01W6eCq6Bdsf9dGBnb9/sSSU5phdI3nj30Ryr1U/gRJ6u+OMZU8hc4ak6YRaQrmcn7CJYTsCT8BO7VcZM=",
// Wish you were there!
"AJvcW2AKgRIS5Tay3S+n42T1OjBDxJlwr+fOOqAj7/fJJLx15rWEvp15aygk1jL4WhrKSP+V9gotwN3FRnfuWMUxaRRmeyptjaWBS4ZSDk950YYuhaHVQMakZFEfs5YLDsthQA==",
];
idx = Math.max(0, Math.min(idx, precalc.length - 1));
document.getElementById('data').value = precalc[idx];
decode_text();
}
function main_launch() {
var idx = 0;
var preview_data = GetParamsFromURL()["preview"];
if (preview_data != undefined) {
idx = decodeURI(preview_data);
if (idx.length > 4) {
document.getElementById('data').value = idx;
decode_text();
return;
}
}
if (idx == undefined || idx == "") idx = 0;
setPrecalc(idx);
}
</script>
</head>
<body onload="main_launch();">
<center>
<h1><b>WebP2 triangle preview</b></h1>
</center>
<br />
We use triangulation + colormap to compress images into a very small
preview.
<br />
The compressed data is the base64 input of the text-area below.
The decoder is ~400 lines of javascript + WebGL.
<br />
<br />
The libwebp2 repository is located at
<a href="https://chromium.googlesource.com/codecs/libwebp2" target="_blank">https://chromium.googlesource.com/codecs/libwebp2</a>.
<br />
You can generate preview base64-strings using for instance: <code>extras/mk_preview input.png -b64</code>.
<br />
The <code>mk_preview</code> research tool contains a lot of optimizing options to play with.
<br />
<br />
Some pre-calculated examples:
<input type="button" value="Mona Lisa" onclick="setPrecalc(0);">
<input type="button" value="Van Gogh" onclick="setPrecalc(1);">
<input type="button" value="Logo" onclick="setPrecalc(2);">
<input type="button" value="No" onclick="setPrecalc(3);">
<input type="button" value="POTUS" onclick="setPrecalc(4);">
<input type="button" value="Knock" onclick="setPrecalc(5);">
<input type="button" value="you WISH you were there!" onclick="setPrecalc(6);">
<br />
<hr />
<p>
<table align="left">
<tr>
<td height="50" valign="top"><b>
<font size="25pt"><span id="text"></span><br /></font>
</b></td>
<td valign="top" rowspan="5"><img id="output" alt="Decoded result"><br />Decoded result<br />
</td>
</tr>
<tr>
<td height="150" valign="top">
<textarea id="data" onChange="decode(this.value);" rows="10" cols="40"></textarea>
<br />
<input type="button" style="height:30" value="GO! Decode the text." onclick="decode_text();">
<input type="button" value="clear" style="font-weight:bold" onclick="document.getElementById('data').value='';">
<br />
</td>
</tr>
<tr>
<td height="30" valign="top"><span id="info"></span><br /></td>
</tr>
<tr>
<td height="30" valign="top"><span id="cmap"></span><br /></td>
</tr>
<tr>
<td height="150" valign="bottom">
<input id="show_tri" type="checkbox" onchange="decode_text();"> show triangles<br />
<input id="amp" size="5" type="textarea" value="0.1" onchange="decode_text();"> noise amplitude<br />
<input id="size" size="5" type="textarea" value="320" onchange="decode_text();"> decoded image width<br />
<br />
</td>
</tr>
<tr>
<td colspan="2">
<h5>
Text version of the compressed data:
<input type="button" value="Copy!" onclick="copy_to_clipboard();">
<br />
<textarea id="comp_data"></textarea><br />
</h5>
</td>
</tr>
</table>
</p>
</body>
</html>