Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
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> |