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