layout/tools/reftest/reftest-analyzer.xhtml

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

michael@0 1 <?xml version="1.0" encoding="UTF-8"?>
michael@0 2 <!-- -*- Mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
michael@0 3 <!-- vim: set shiftwidth=2 tabstop=2 autoindent expandtab: -->
michael@0 4 <!-- This Source Code Form is subject to the terms of the Mozilla Public
michael@0 5 - License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 6 - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
michael@0 7 <!--
michael@0 8
michael@0 9 Features to add:
michael@0 10 * make the left and right parts of the viewer independently scrollable
michael@0 11 * make the test list filterable
michael@0 12 ** default to only showing unexpecteds
michael@0 13 * add other ways to highlight differences other than circling?
michael@0 14 * add zoom/pan to images
michael@0 15 * Add ability to load log via XMLHttpRequest (also triggered via URL param)
michael@0 16 * color the test list based on pass/fail and expected/unexpected/random/skip
michael@0 17 * ability to load multiple logs ?
michael@0 18 ** rename them by clicking on the name and editing
michael@0 19 ** turn the test list into a collapsing tree view
michael@0 20 ** move log loading into popup from viewer UI
michael@0 21
michael@0 22 -->
michael@0 23 <!DOCTYPE html>
michael@0 24 <html lang="en-US" xml:lang="en-US" xmlns="http://www.w3.org/1999/xhtml">
michael@0 25 <head>
michael@0 26 <title>Reftest analyzer</title>
michael@0 27 <style type="text/css"><![CDATA[
michael@0 28
michael@0 29 html, body { margin: 0; }
michael@0 30 html { padding: 0; }
michael@0 31 body { padding: 4px; }
michael@0 32
michael@0 33 #pixelarea, #itemlist, #images { position: absolute; }
michael@0 34 #itemlist, #images { overflow: auto; }
michael@0 35 #pixelarea { top: 0; left: 0; width: 320px; height: 84px; overflow: visible }
michael@0 36 #itemlist { top: 84px; left: 0; width: 320px; bottom: 0; }
michael@0 37 #images { top: 0; bottom: 0; left: 320px; right: 0; }
michael@0 38
michael@0 39 #leftpane { width: 320px; }
michael@0 40 #images { position: fixed; top: 10px; left: 340px; }
michael@0 41
michael@0 42 form#imgcontrols { margin: 0; display: block; }
michael@0 43
michael@0 44 #itemlist > table { border-collapse: collapse; }
michael@0 45 #itemlist > table > tbody > tr > td { border: 1px solid; padding: 1px; }
michael@0 46
michael@0 47 /*
michael@0 48 #itemlist > table > tbody > tr.pass > td.url { background: lime; }
michael@0 49 #itemlist > table > tbody > tr.fail > td.url { background: red; }
michael@0 50 */
michael@0 51
michael@0 52 #magnification > svg { display: block; width: 84px; height: 84px; }
michael@0 53
michael@0 54 #pixelinfo { font: small sans-serif; position: absolute; width: 200px; left: 84px; }
michael@0 55 #pixelinfo table { border-collapse: collapse; }
michael@0 56 #pixelinfo table th { white-space: nowrap; text-align: left; padding: 0; }
michael@0 57 #pixelinfo table td { font-family: monospace; padding: 0 0 0 0.25em; }
michael@0 58
michael@0 59 #pixelhint { display: inline; color: #88f; cursor: help; }
michael@0 60 #pixelhint > * { display: none; position: absolute; margin: 8px 0 0 8px; padding: 4px; width: 400px; background: #ffa; color: black; box-shadow: 3px 3px 2px #888; z-index: 1; }
michael@0 61 #pixelhint:hover { color: #000; }
michael@0 62 #pixelhint:hover > * { display: block; }
michael@0 63 #pixelhint p { margin: 0; }
michael@0 64 #pixelhint p + p { margin-top: 1em; }
michael@0 65
michael@0 66 ]]></style>
michael@0 67 <script type="text/javascript"><![CDATA[
michael@0 68
michael@0 69 var XLINK_NS = "http://www.w3.org/1999/xlink";
michael@0 70 var SVG_NS = "http://www.w3.org/2000/svg";
michael@0 71 var IMAGE_NOT_AVAILABLE = "data:image/png;base64,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";
michael@0 72
michael@0 73 var gPhases = null;
michael@0 74
michael@0 75 var gIDCache = {};
michael@0 76
michael@0 77 var gMagPixPaths = []; // 2D array of array-of-two <path> objects used in the pixel magnifier
michael@0 78 var gMagWidth = 5; // number of zoomed in pixels to show horizontally
michael@0 79 var gMagHeight = 5; // number of zoomed in pixels to show vertically
michael@0 80 var gMagZoom = 16; // size of the zoomed in pixels
michael@0 81 var gImage1Data; // ImageData object for the reference image
michael@0 82 var gImage2Data; // ImageData object for the test output image
michael@0 83 var gFlashingPixels = []; // array of <path> objects that should be flashed due to pixel color mismatch
michael@0 84
michael@0 85 function ID(id) {
michael@0 86 if (!(id in gIDCache))
michael@0 87 gIDCache[id] = document.getElementById(id);
michael@0 88 return gIDCache[id];
michael@0 89 }
michael@0 90
michael@0 91 function hash_parameters() {
michael@0 92 var result = { };
michael@0 93 var params = window.location.hash.substr(1).split(/[&;]/);
michael@0 94 for (var i = 0; i < params.length; i++) {
michael@0 95 var parts = params[i].split("=");
michael@0 96 result[parts[0]] = unescape(unescape(parts[1]));
michael@0 97 }
michael@0 98 return result;
michael@0 99 }
michael@0 100
michael@0 101 function load() {
michael@0 102 gPhases = [ ID("entry"), ID("loading"), ID("viewer") ];
michael@0 103 build_mag();
michael@0 104 var params = hash_parameters();
michael@0 105 if (params.log) {
michael@0 106 ID("logentry").value = params.log;
michael@0 107 log_pasted();
michael@0 108 }
michael@0 109 window.addEventListener('keypress', maybe_load_image, false);
michael@0 110 ID("image1").addEventListener('error', image_load_error, false);
michael@0 111 ID("image2").addEventListener('error', image_load_error, false);
michael@0 112 }
michael@0 113
michael@0 114 function image_load_error(e) {
michael@0 115 e.target.setAttributeNS(XLINK_NS, "xlink:href", IMAGE_NOT_AVAILABLE);
michael@0 116 }
michael@0 117
michael@0 118 function build_mag() {
michael@0 119 var mag = ID("mag");
michael@0 120
michael@0 121 var r = document.createElementNS(SVG_NS, "rect");
michael@0 122 r.setAttribute("x", gMagZoom * -gMagWidth / 2);
michael@0 123 r.setAttribute("y", gMagZoom * -gMagHeight / 2);
michael@0 124 r.setAttribute("width", gMagZoom * gMagWidth);
michael@0 125 r.setAttribute("height", gMagZoom * gMagHeight);
michael@0 126 mag.appendChild(r);
michael@0 127
michael@0 128 mag.setAttribute("transform", "translate(" + (gMagZoom * (gMagWidth / 2) + 1) + "," + (gMagZoom * (gMagHeight / 2) + 1) + ")");
michael@0 129
michael@0 130 for (var x = 0; x < gMagWidth; x++) {
michael@0 131 gMagPixPaths[x] = [];
michael@0 132 for (var y = 0; y < gMagHeight; y++) {
michael@0 133 var p1 = document.createElementNS(SVG_NS, "path");
michael@0 134 p1.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "h" + -gMagZoom + "v" + gMagZoom);
michael@0 135 p1.setAttribute("stroke", "black");
michael@0 136 p1.setAttribute("stroke-width", "1px");
michael@0 137 p1.setAttribute("fill", "#aaa");
michael@0 138
michael@0 139 var p2 = document.createElementNS(SVG_NS, "path");
michael@0 140 p2.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "v" + gMagZoom + "h" + -gMagZoom);
michael@0 141 p2.setAttribute("stroke", "black");
michael@0 142 p2.setAttribute("stroke-width", "1px");
michael@0 143 p2.setAttribute("fill", "#888");
michael@0 144
michael@0 145 mag.appendChild(p1);
michael@0 146 mag.appendChild(p2);
michael@0 147 gMagPixPaths[x][y] = [p1, p2];
michael@0 148 }
michael@0 149 }
michael@0 150
michael@0 151 var flashedOn = false;
michael@0 152 setInterval(function() {
michael@0 153 flashedOn = !flashedOn;
michael@0 154 flash_pixels(flashedOn);
michael@0 155 }, 500);
michael@0 156 }
michael@0 157
michael@0 158 function show_phase(phaseid) {
michael@0 159 for (var i in gPhases) {
michael@0 160 var phase = gPhases[i];
michael@0 161 phase.style.display = (phase.id == phaseid) ? "" : "none";
michael@0 162 }
michael@0 163
michael@0 164 if (phase == "viewer")
michael@0 165 ID("images").style.display = "none";
michael@0 166 }
michael@0 167
michael@0 168 function fileentry_changed() {
michael@0 169 show_phase("loading");
michael@0 170 var input = ID("fileentry");
michael@0 171 var files = input.files;
michael@0 172 if (files.length > 0) {
michael@0 173 // Only handle the first file; don't handle multiple selection.
michael@0 174 // The parts of the log we care about are ASCII-only. Since we
michael@0 175 // can ignore lines we don't care about, best to read in as
michael@0 176 // iso-8859-1, which guarantees we don't get decoding errors.
michael@0 177 var fileReader = new FileReader();
michael@0 178 fileReader.onload = function(e) {
michael@0 179 var log = null;
michael@0 180
michael@0 181 log = e.target.result;
michael@0 182
michael@0 183 if (log)
michael@0 184 process_log(log);
michael@0 185 else
michael@0 186 show_phase("entry");
michael@0 187 }
michael@0 188 fileReader.readAsText(files[0], "iso-8859-1");
michael@0 189 }
michael@0 190 // So the user can process the same filename again (after
michael@0 191 // overwriting the log), clear the value on the form input so we
michael@0 192 // will always get an onchange event.
michael@0 193 input.value = "";
michael@0 194 }
michael@0 195
michael@0 196 function log_pasted() {
michael@0 197 show_phase("loading");
michael@0 198 var entry = ID("logentry");
michael@0 199 var log = entry.value;
michael@0 200 entry.value = "";
michael@0 201 process_log(log);
michael@0 202 }
michael@0 203
michael@0 204 var gTestItems;
michael@0 205
michael@0 206 function process_log(contents) {
michael@0 207 var lines = contents.split(/[\r\n]+/);
michael@0 208 gTestItems = [];
michael@0 209 for (var j in lines) {
michael@0 210 var line = lines[j];
michael@0 211 var match = line.match(/^.*?REFTEST (.*)$/);
michael@0 212 if (!match)
michael@0 213 continue;
michael@0 214 line = match[1];
michael@0 215 match = line.match(/^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL|TEST-DEBUG-INFO)(\(EXPECTED RANDOM\)|) \| ([^\|]+) \|(.*)/);
michael@0 216 if (match) {
michael@0 217 var state = match[1];
michael@0 218 var random = match[2];
michael@0 219 var url = match[3];
michael@0 220 var extra = match[4];
michael@0 221 gTestItems.push(
michael@0 222 {
michael@0 223 pass: !state.match(/DEBUG-INFO$|FAIL$/),
michael@0 224 // only one of the following three should ever be true
michael@0 225 unexpected: !!state.match(/^TEST-UNEXPECTED/),
michael@0 226 random: (random == "(EXPECTED RANDOM)"),
michael@0 227 skip: (extra == " (SKIP)"),
michael@0 228 url: url,
michael@0 229 images: []
michael@0 230 });
michael@0 231 continue;
michael@0 232 }
michael@0 233 match = line.match(/^ IMAGE[^:]*: (.*)$/);
michael@0 234 if (match) {
michael@0 235 var item = gTestItems[gTestItems.length - 1];
michael@0 236 item.images.push(match[1]);
michael@0 237 }
michael@0 238 }
michael@0 239
michael@0 240 build_viewer();
michael@0 241 }
michael@0 242
michael@0 243 function build_viewer() {
michael@0 244 if (gTestItems.length == 0) {
michael@0 245 show_phase("entry");
michael@0 246 return;
michael@0 247 }
michael@0 248
michael@0 249 var cell = ID("itemlist");
michael@0 250 while (cell.childNodes.length > 0)
michael@0 251 cell.removeChild(cell.childNodes[cell.childNodes.length - 1]);
michael@0 252
michael@0 253 var table = document.createElement("table");
michael@0 254 var tbody = document.createElement("tbody");
michael@0 255 table.appendChild(tbody);
michael@0 256
michael@0 257 for (var i in gTestItems) {
michael@0 258 var item = gTestItems[i];
michael@0 259
michael@0 260 // XXX skip expected pass items until we have filtering UI
michael@0 261 if (item.pass && !item.unexpected)
michael@0 262 continue;
michael@0 263
michael@0 264 var tr = document.createElement("tr");
michael@0 265 var rowclass = item.pass ? "pass" : "fail";
michael@0 266 var td;
michael@0 267 var text;
michael@0 268
michael@0 269 td = document.createElement("td");
michael@0 270 text = "";
michael@0 271 if (item.unexpected) { text += "!"; rowclass += " unexpected"; }
michael@0 272 if (item.random) { text += "R"; rowclass += " random"; }
michael@0 273 if (item.skip) { text += "S"; rowclass += " skip"; }
michael@0 274 td.appendChild(document.createTextNode(text));
michael@0 275 tr.appendChild(td);
michael@0 276
michael@0 277 td = document.createElement("td");
michael@0 278 td.className = "url";
michael@0 279 // Only display part of URL after "/mozilla/".
michael@0 280 var match = item.url.match(/\/mozilla\/(.*)/);
michael@0 281 text = document.createTextNode(match ? match[1] : item.url);
michael@0 282 if (item.images.length > 0) {
michael@0 283 var a = document.createElement("a");
michael@0 284 a.href = "javascript:show_images(" + i + ")";
michael@0 285 a.appendChild(text);
michael@0 286 td.appendChild(a);
michael@0 287 } else {
michael@0 288 td.appendChild(text);
michael@0 289 }
michael@0 290 tr.appendChild(td);
michael@0 291
michael@0 292 tbody.appendChild(tr);
michael@0 293 }
michael@0 294
michael@0 295 cell.appendChild(table);
michael@0 296
michael@0 297 show_phase("viewer");
michael@0 298 }
michael@0 299
michael@0 300 function get_image_data(src, whenReady) {
michael@0 301 var img = new Image();
michael@0 302 img.onload = function() {
michael@0 303 var canvas = document.createElement("canvas");
michael@0 304 canvas.width = 800;
michael@0 305 canvas.height = 1000;
michael@0 306
michael@0 307 var ctx = canvas.getContext("2d");
michael@0 308 ctx.drawImage(img, 0, 0);
michael@0 309
michael@0 310 whenReady(ctx.getImageData(0, 0, 800, 1000));
michael@0 311 };
michael@0 312 img.src = src;
michael@0 313 }
michael@0 314
michael@0 315 function show_images(i) {
michael@0 316 var item = gTestItems[i];
michael@0 317 var cell = ID("images");
michael@0 318
michael@0 319 ID("image1").style.display = "";
michael@0 320 ID("image2").style.display = "none";
michael@0 321 ID("diffrect").style.display = "none";
michael@0 322 ID("imgcontrols").reset();
michael@0 323
michael@0 324 ID("image1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]);
michael@0 325 // Making the href be #image1 doesn't seem to work
michael@0 326 ID("feimage1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]);
michael@0 327 if (item.images.length == 1) {
michael@0 328 ID("imgcontrols").style.display = "none";
michael@0 329 } else {
michael@0 330 ID("imgcontrols").style.display = "";
michael@0 331
michael@0 332 ID("image2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]);
michael@0 333 // Making the href be #image2 doesn't seem to work
michael@0 334 ID("feimage2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]);
michael@0 335 }
michael@0 336
michael@0 337 cell.style.display = "";
michael@0 338
michael@0 339 get_image_data(item.images[0], function(data) { gImage1Data = data });
michael@0 340 get_image_data(item.images[1], function(data) { gImage2Data = data });
michael@0 341 }
michael@0 342
michael@0 343 function show_image(i) {
michael@0 344 if (i == 1) {
michael@0 345 ID("image1").style.display = "";
michael@0 346 ID("image2").style.display = "none";
michael@0 347 } else {
michael@0 348 ID("image1").style.display = "none";
michael@0 349 ID("image2").style.display = "";
michael@0 350 }
michael@0 351 }
michael@0 352
michael@0 353 function maybe_load_image(event) {
michael@0 354 switch (event.charCode) {
michael@0 355 case 49: // "1" key
michael@0 356 document.getElementById("radio1").checked = true;
michael@0 357 show_image(1);
michael@0 358 break;
michael@0 359 case 50: // "2" key
michael@0 360 document.getElementById("radio2").checked = true;
michael@0 361 show_image(2);
michael@0 362 break;
michael@0 363 }
michael@0 364 }
michael@0 365
michael@0 366 function show_differences(cb) {
michael@0 367 ID("diffrect").style.display = cb.checked ? "" : "none";
michael@0 368 }
michael@0 369
michael@0 370 function flash_pixels(on) {
michael@0 371 var stroke = on ? "red" : "black";
michael@0 372 var strokeWidth = on ? "2px" : "1px";
michael@0 373 for (var i = 0; i < gFlashingPixels.length; i++) {
michael@0 374 gFlashingPixels[i].setAttribute("stroke", stroke);
michael@0 375 gFlashingPixels[i].setAttribute("stroke-width", strokeWidth);
michael@0 376 }
michael@0 377 }
michael@0 378
michael@0 379 function cursor_point(evt) {
michael@0 380 var m = evt.target.getScreenCTM().inverse();
michael@0 381 var p = ID("svg").createSVGPoint();
michael@0 382 p.x = evt.clientX;
michael@0 383 p.y = evt.clientY;
michael@0 384 p = p.matrixTransform(m);
michael@0 385 return { x: Math.floor(p.x), y: Math.floor(p.y) };
michael@0 386 }
michael@0 387
michael@0 388 function hex2(i) {
michael@0 389 return (i < 16 ? "0" : "") + i.toString(16);
michael@0 390 }
michael@0 391
michael@0 392 function canvas_pixel_as_hex(data, x, y) {
michael@0 393 var offset = (y * data.width + x) * 4;
michael@0 394 var r = data.data[offset];
michael@0 395 var g = data.data[offset + 1];
michael@0 396 var b = data.data[offset + 2];
michael@0 397 return "#" + hex2(r) + hex2(g) + hex2(b);
michael@0 398 }
michael@0 399
michael@0 400 function hex_as_rgb(hex) {
michael@0 401 return "rgb(" + [parseInt(hex.substring(1, 3), 16), parseInt(hex.substring(3, 5), 16), parseInt(hex.substring(5, 7), 16)] + ")";
michael@0 402 }
michael@0 403
michael@0 404 function magnify(evt) {
michael@0 405 var { x: x, y: y } = cursor_point(evt);
michael@0 406 var centerPixelColor1, centerPixelColor2;
michael@0 407
michael@0 408 var dx_lo = -Math.floor(gMagWidth / 2);
michael@0 409 var dx_hi = Math.floor(gMagWidth / 2);
michael@0 410 var dy_lo = -Math.floor(gMagHeight / 2);
michael@0 411 var dy_hi = Math.floor(gMagHeight / 2);
michael@0 412
michael@0 413 flash_pixels(false);
michael@0 414 gFlashingPixels = [];
michael@0 415 for (var j = dy_lo; j <= dy_hi; j++) {
michael@0 416 for (var i = dx_lo; i <= dx_hi; i++) {
michael@0 417 var px = x + i;
michael@0 418 var py = y + j;
michael@0 419 var p1 = gMagPixPaths[i + dx_hi][j + dy_hi][0];
michael@0 420 var p2 = gMagPixPaths[i + dx_hi][j + dy_hi][1];
michael@0 421 if (px < 0 || py < 0 || px >= 800 || py >= 1000) {
michael@0 422 p1.setAttribute("fill", "#aaa");
michael@0 423 p2.setAttribute("fill", "#888");
michael@0 424 } else {
michael@0 425 var color1 = canvas_pixel_as_hex(gImage1Data, x + i, y + j);
michael@0 426 var color2 = canvas_pixel_as_hex(gImage2Data, x + i, y + j);
michael@0 427 p1.setAttribute("fill", color1);
michael@0 428 p2.setAttribute("fill", color2);
michael@0 429 if (color1 != color2) {
michael@0 430 gFlashingPixels.push(p1, p2);
michael@0 431 p1.parentNode.appendChild(p1);
michael@0 432 p2.parentNode.appendChild(p2);
michael@0 433 }
michael@0 434 if (i == 0 && j == 0) {
michael@0 435 centerPixelColor1 = color1;
michael@0 436 centerPixelColor2 = color2;
michael@0 437 }
michael@0 438 }
michael@0 439 }
michael@0 440 }
michael@0 441 flash_pixels(true);
michael@0 442 show_pixelinfo(x, y, centerPixelColor1, hex_as_rgb(centerPixelColor1), centerPixelColor2, hex_as_rgb(centerPixelColor2));
michael@0 443 }
michael@0 444
michael@0 445 function show_pixelinfo(x, y, pix1rgb, pix1hex, pix2rgb, pix2hex) {
michael@0 446 var pixelinfo = ID("pixelinfo");
michael@0 447 ID("coords").textContent = [x, y];
michael@0 448 ID("pix1hex").textContent = pix1hex;
michael@0 449 ID("pix1rgb").textContent = pix1rgb;
michael@0 450 ID("pix2hex").textContent = pix2hex;
michael@0 451 ID("pix2rgb").textContent = pix2rgb;
michael@0 452 }
michael@0 453
michael@0 454 ]]></script>
michael@0 455
michael@0 456 </head>
michael@0 457 <body onload="load()">
michael@0 458
michael@0 459 <div id="entry">
michael@0 460
michael@0 461 <h1>Reftest analyzer: load reftest log</h1>
michael@0 462
michael@0 463 <p>Either paste your log into this textarea:<br />
michael@0 464 <textarea cols="80" rows="10" id="logentry"/><br/>
michael@0 465 <input type="button" value="Process pasted log" onclick="log_pasted()" /></p>
michael@0 466
michael@0 467 <p>... or load it from a file:<br/>
michael@0 468 <input type="file" id="fileentry" onchange="fileentry_changed()" />
michael@0 469 </p>
michael@0 470 </div>
michael@0 471
michael@0 472 <div id="loading" style="display:none">Loading log...</div>
michael@0 473
michael@0 474 <div id="viewer" style="display:none">
michael@0 475 <div id="pixelarea">
michael@0 476 <div id="pixelinfo">
michael@0 477 <table>
michael@0 478 <tbody>
michael@0 479 <tr><th>Pixel at:</th><td colspan="2" id="coords"/></tr>
michael@0 480 <tr><th>Image 1:</th><td id="pix1rgb"></td><td id="pix1hex"></td></tr>
michael@0 481 <tr><th>Image 2:</th><td id="pix2rgb"></td><td id="pix2hex"></td></tr>
michael@0 482 </tbody>
michael@0 483 </table>
michael@0 484 <div>
michael@0 485 <div id="pixelhint">★
michael@0 486 <div>
michael@0 487 <p>Move the mouse over the reftest image on the right to show
michael@0 488 magnified pixels on the left. The color information above is for
michael@0 489 the pixel centered in the magnified view.</p>
michael@0 490 <p>Image 1 is shown in the upper triangle of each pixel and Image 2
michael@0 491 is shown in the lower triangle.</p>
michael@0 492 </div>
michael@0 493 </div>
michael@0 494 </div>
michael@0 495 </div>
michael@0 496 <div id="magnification">
michael@0 497 <svg xmlns="http://www.w3.org/2000/svg" width="84" height="84" shape-rendering="optimizeSpeed">
michael@0 498 <g id="mag"/>
michael@0 499 </svg>
michael@0 500 </div>
michael@0 501 </div>
michael@0 502 <div id="itemlist"></div>
michael@0 503 <div id="images" style="display:none">
michael@0 504 <form id="imgcontrols">
michael@0 505 <label title="1"><input id="radio1" type="radio" name="which" value="0" onchange="show_image(1)" checked="checked" />Image 1</label>
michael@0 506 <label title="2"><input id="radio2" type="radio" name="which" value="1" onchange="show_image(2)" />Image 2</label>
michael@0 507 <label><input type="checkbox" onchange="show_differences(this)" />Circle differences</label>
michael@0 508 </form>
michael@0 509 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800px" height="1000px" viewBox="0 0 800 1000" id="svg">
michael@0 510 <defs>
michael@0 511 <!-- use sRGB to avoid loss of data -->
michael@0 512 <filter id="showDifferences" x="0%" y="0%" width="100%" height="100%"
michael@0 513 style="color-interpolation-filters: sRGB">
michael@0 514 <feImage id="feimage1" result="img1" xlink:href="#image1" />
michael@0 515 <feImage id="feimage2" result="img2" xlink:href="#image2" />
michael@0 516 <!-- inv1 and inv2 are the images with RGB inverted -->
michael@0 517 <feComponentTransfer result="inv1" in="img1">
michael@0 518 <feFuncR type="linear" slope="-1" intercept="1" />
michael@0 519 <feFuncG type="linear" slope="-1" intercept="1" />
michael@0 520 <feFuncB type="linear" slope="-1" intercept="1" />
michael@0 521 </feComponentTransfer>
michael@0 522 <feComponentTransfer result="inv2" in="img2">
michael@0 523 <feFuncR type="linear" slope="-1" intercept="1" />
michael@0 524 <feFuncG type="linear" slope="-1" intercept="1" />
michael@0 525 <feFuncB type="linear" slope="-1" intercept="1" />
michael@0 526 </feComponentTransfer>
michael@0 527 <!-- w1 will have non-white pixels anywhere that img2
michael@0 528 is brighter than img1, and w2 for the reverse.
michael@0 529 It would be nice not to have to go through these
michael@0 530 intermediate states, but feComposite
michael@0 531 type="arithmetic" can't transform the RGB channels
michael@0 532 and leave the alpha channel untouched. -->
michael@0 533 <feComposite result="w1" in="img1" in2="inv2" operator="arithmetic" k2="1" k3="1" />
michael@0 534 <feComposite result="w2" in="img2" in2="inv1" operator="arithmetic" k2="1" k3="1" />
michael@0 535 <!-- c1 will have non-black pixels anywhere that img2
michael@0 536 is brighter than img1, and c2 for the reverse -->
michael@0 537 <feComponentTransfer result="c1" in="w1">
michael@0 538 <feFuncR type="linear" slope="-1" intercept="1" />
michael@0 539 <feFuncG type="linear" slope="-1" intercept="1" />
michael@0 540 <feFuncB type="linear" slope="-1" intercept="1" />
michael@0 541 </feComponentTransfer>
michael@0 542 <feComponentTransfer result="c2" in="w2">
michael@0 543 <feFuncR type="linear" slope="-1" intercept="1" />
michael@0 544 <feFuncG type="linear" slope="-1" intercept="1" />
michael@0 545 <feFuncB type="linear" slope="-1" intercept="1" />
michael@0 546 </feComponentTransfer>
michael@0 547 <!-- c will be nonblack (and fully on) for every pixel+component where there are differences -->
michael@0 548 <feComposite result="c" in="c1" in2="c2" operator="arithmetic" k2="255" k3="255" />
michael@0 549 <!-- a will be opaque for every pixel with differences and transparent for all others -->
michael@0 550 <feColorMatrix result="a" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0" />
michael@0 551
michael@0 552 <!-- a, dilated by 1 pixel -->
michael@0 553 <feMorphology result="dila1" in="a" operator="dilate" radius="1" />
michael@0 554 <!-- a, dilated by 2 pixels -->
michael@0 555 <feMorphology result="dila2" in="dila1" operator="dilate" radius="1" />
michael@0 556
michael@0 557 <!-- all the pixels in the 2-pixel dilation of a but not in the 1-pixel dilation, to highlight the diffs -->
michael@0 558 <feComposite result="highlight" in="dila2" in2="dila1" operator="out" />
michael@0 559
michael@0 560 <feFlood result="red" flood-color="red" />
michael@0 561 <feComposite result="redhighlight" in="red" in2="highlight" operator="in" />
michael@0 562 <feFlood result="black" flood-color="black" flood-opacity="0.5" />
michael@0 563 <feMerge>
michael@0 564 <feMergeNode in="black" />
michael@0 565 <feMergeNode in="redhighlight" />
michael@0 566 </feMerge>
michael@0 567 </filter>
michael@0 568 </defs>
michael@0 569 <g onmousemove="magnify(evt)">
michael@0 570 <image x="0" y="0" width="100%" height="100%" id="image1" />
michael@0 571 <image x="0" y="0" width="100%" height="100%" id="image2" />
michael@0 572 </g>
michael@0 573 <rect id="diffrect" filter="url(#showDifferences)" pointer-events="none" x="0" y="0" width="100%" height="100%" />
michael@0 574 </svg>
michael@0 575 </div>
michael@0 576 </div>
michael@0 577
michael@0 578 </body>
michael@0 579 </html>

mercurial