| <!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] + ") "; |
| } |
| 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> |