| <!-- The <multi-zoom-sk> custom element declaration. |
| |
| This element is a view that allows zooming into images. |
| It displays the three images, left, top and diff, and then allows zooming and |
| panning over various combinations of the images. There is a single pixel in |
| the center of the zoomed view which is highlighted, and its information is |
| displayed on the zoom dialog. |
| |
| There are three images that it deals with, called left, top, and diff. |
| |
| Attributes: |
| None |
| |
| Events: |
| None |
| |
| Methods: |
| setDetails(details) - Sets the information the zoom dialog needs, which |
| is an object of the following form, where each member is a |
| URL of the full size image. |
| |
| { |
| leftImgUrl: "...", |
| middleImgUrl: "...", |
| rightImgUrl: "...", |
| llabel: "Left Label", |
| rlabel: "Right Label" |
| } |
| |
| Mailboxes: |
| None |
| |
| --> |
| <link rel="import" href="bower_components/iron-flex-layout/iron-flex-layout-classes.html"> |
| <link rel="import" href="bower_components/polymer/polymer.html"> |
| <link rel="import" href="bower_components/paper-dialog/paper-dialog.html"> |
| <link rel="import" href="bower_components/paper-button/paper-button.html"> |
| <link rel="import" href="bower_components/paper-dialog-scrollable/paper-dialog-scrollable.html"> |
| <link rel="import" href="bower_components/paper-toggle-button/paper-toggle-button.html"> |
| |
| <link rel=import href="../common/imp/canvas-layers.html"> |
| <link rel=import href="../common/imp/crosshair.html"> |
| <link rel=import href="../common/imp/zoom.html"> |
| |
| <link rel="import" href="shared-styles.html"> |
| |
| <dom-module id="multi-zoom-sk"> |
| <style include="iron-flex iron-flex-alignment shared-styles"> |
| img, .imgContainer { |
| width: 128px; |
| } |
| |
| .imgContainer, .zoomContainer { |
| padding: 4px; |
| border: solid lightgray 1px; |
| margin: 4px; |
| } |
| |
| .zoomContainer { |
| width: 270px; |
| height: 270px; |
| padding: 0; |
| margin: 0; |
| } |
| |
| .imgColumn { |
| width: 12em; |
| } |
| |
| .selectorWrapper { |
| font-size: 16px; |
| } |
| |
| .imagesWrap { |
| margin-bottom: 2em; |
| } |
| |
| .showBold { |
| font-weight: bold; |
| } |
| |
| .multiZoomWrapper { |
| min-width: 20em; |
| min-height: 20em; |
| padding: 2em; |
| } |
| |
| .legendContainer { |
| margin: 2em; |
| font-size: 90%; |
| } |
| |
| </style> |
| |
| <template> |
| <div class="multiZoomWrapper"> |
| <div class="horizontal layout"> |
| <div class="vertical layout" hidden="{{_noImage(details)}}"> |
| <div class="horizontal layout wrap imagesWrap"> |
| <div class="vertical layout imgColumn"> |
| <div class="imgContainer"> |
| <canvas-layers-sk layers='["crosshairLeft"]' id="layersLeft"> |
| <img id="left_img" src$="{{details.leftImgUrl}}"/> |
| </canvas-layers-sk> |
| <crosshair-sk x="{{_x}}" |
| y="{{_y}}" |
| target="layersLeft" |
| name="crosshairLeft" |
| update_on="click"> |
| </crosshair-sk> |
| </div> |
| <div class="selectorWrapper"> |
| <paper-toggle-button checked={{_leftActive}}> |
| <span class$="{{_boldClass(_currSeq, 'left')}}">{{details.llabel}}</span> |
| </paper-toggle-button> |
| </div> |
| </div> |
| |
| <div class="layout vertical imgColumn" hidden="{{_hideImg(details.middleImgUrl)}}"> |
| <div class="imgContainer"> |
| <canvas-layers-sk layers='["crosshairMiddle"]' id="layersMiddle"> |
| <img id="middle_img" src$="{{details.middleImgUrl}}"/> |
| </canvas-layers-sk> |
| <crosshair-sk x="{{_x}}" |
| y="{{_y}}" |
| target="layersMiddle" |
| name="crosshairMiddle" |
| update_on="click"> |
| </crosshair-sk> |
| </div> |
| <div class="selectorWrapper"> |
| <paper-toggle-button checked={{_middleActive}}> |
| <span class$="{{_boldClass(_currSeq, 'middle')}}">Diff</span> |
| </paper-toggle-button> |
| </div> |
| |
| </div> |
| |
| <div class="layout vertical imgColumn" hidden="{{_hideImg(details.rightImgUrl)}}"> |
| <div class="imgContainer"> |
| <canvas-layers-sk layers='["crosshairRight"]' id="layersRight"> |
| <img id="right_img" src$="{{details.rightImgUrl}}"/> |
| </canvas-layers-sk> |
| <crosshair-sk x="{{_x}}" |
| y="{{_y}}" |
| target="layersRight" |
| name="crosshairRight" |
| update_on="click"> |
| </crosshair-sk> |
| </div> |
| |
| <div class="selectorWrapper"> |
| <paper-toggle-button checked={{_rightActive}}> |
| <span class$="{{_boldClass(_currSeq, 'right')}}">{{details.rlabel}}</span> |
| </paper-toggle-button> |
| </div> |
| </div> |
| </div> |
| |
| <div class="layout horizontal wrap"> |
| <div class="layout vertical zoomContainer"> |
| <zoom-sk hidden$="{{_hideMe(_currSeq, 'left')}}" |
| source="left_img" |
| pixels="{{_pixels}}" |
| id="zoomLeft" |
| x="{{_x}}" |
| y="{{_y}}"> |
| </zoom-sk> |
| |
| <zoom-sk hidden$="{{_hideMe(_currSeq, 'middle')}}" |
| source="middle_img" |
| pixels="{{_pixels}}" |
| id="zoomMiddle" |
| x="{{_x}}" |
| y="{{_y}}"> |
| </zoom-sk> |
| |
| <zoom-sk hidden$="{{_hideMe(_currSeq, 'right')}}" |
| source="right_img" |
| pixels="{{_pixels}}" |
| id="zoomRight" |
| x="{{_x}}" |
| y="{{_y}}"> |
| </zoom-sk> |
| </div> |
| </div> |
| </div> |
| <div class="vertical layout"> |
| <div class="legendContainer"> |
| <table border="0" cellspacing="5" cellpadding="5"> |
| <tr><th>Coord</th><td><pre>({{_x}}, {{_y}})</pre><td></tr> |
| <tr><th>{{details.llabel}}</th><td><pre>{{_centerPixel.zoomLeft}}</pre><td></tr> |
| <tr hidden="{{_hideImg(details.middleImgUrl)}}"><th>Diff</th><td><pre>{{_centerPixel.zoomMiddle}}</pre><td></tr> |
| <tr hidden="{{_hideImg(details.rightImgUrl)}}"><th>{{details.rlabel}}</th><td><pre>{{_centerPixel.zoomRight}}</pre><td></tr> |
| </table> |
| <div class="horizontal layout"> |
| <table border="0" cellspacing="5" cellpadding="5"> |
| <tr><th>Color Distance</th><th>RGBA</th><th>Alpha</th></tr> |
| <tr><td>1-1 </td><td><div style="background :#fdd0a2"> </div> </td><td><div style="background :#c6dbef"> </div></td></tr> |
| <tr><td>2-5 </td><td><div style="background :#fdae6b"> </div> </td><td><div style="background :#9ecae1"> </div></td></tr> |
| <tr><td>6-15 </td><td><div style="background :#fd8d3c"> </div> </td><td><div style="background :#6baed6"> </div></td></tr> |
| <tr><td>16-46 </td><td><div style="background :#f16913"> </div> </td><td><div style="background :#4292c6"> </div></td></tr> |
| <tr><td>47-140 </td><td><div style="background :#d94801"> </div> </td><td><div style="background :#2171b5"> </div></td></tr> |
| <tr><td>141-420 </td><td><div style="background :#a63603"> </div> </td><td><div style="background :#08519c"> </div></td></tr> |
| <tr><td>421-1024 </td><td><div style="background :#7f2704"> </div> </td><td></td></tr> |
| </table> |
| <table border="0" cellspacing="5" cellpadding="5" id=nav> |
| <tr><th colspan=2>Naviation</th></tr> |
| <tr><th>H</th><td>Left</td></tr> |
| <tr><th>J</th><td>Down</td></tr> |
| <tr><th>K</th><td>Up</td></tr> |
| <tr><th>L</th><td>Right</td></tr> |
| <tr><th>A</th><td>Zoom In</td></tr> |
| <tr><th>Z</th><td>Zoom Out</td></tr> |
| <tr><th>U</th><td>Jump To Largest Diff</td></tr> |
| <tr><th>M</th><td>Manual Toggle</td></tr> |
| </table> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </template> |
| |
| <script> |
| Polymer({ |
| is: "multi-zoom-sk", |
| |
| properties:{ |
| details: { |
| type: Object, |
| value: function() { return {}; } |
| }, |
| |
| _currSeq: '', |
| |
| _leftActive: { |
| type: Boolean, |
| value: true, |
| observer: "_updateTiming" |
| }, |
| |
| _middleActive: { |
| type: Boolean, |
| value: false, |
| observer: "_updateTiming" |
| }, |
| |
| _rightActive: { |
| type: Boolean, |
| value: true, |
| observer: "_updateTiming" |
| }, |
| |
| _x : { |
| type: Number, |
| value: 0, |
| notify: true |
| }, |
| |
| _y : { |
| type: Number, |
| value: 0, |
| notify: true |
| }, |
| |
| _pixels : { |
| type: Number, |
| value: 64, |
| notify: true |
| }, |
| |
| _centerPixel: { |
| type: Object, |
| value: function() { return {}; } |
| } |
| }, |
| |
| ready: function(){ |
| this._seq = []; |
| this.listen(this.$.zoomLeft, 'zoom-point', '_handleZoomPixels'); |
| this.listen(this.$.zoomMiddle, 'zoom-point', '_handleZoomPixels'); |
| this.listen(this.$.zoomRight, 'zoom-point', '_handleZoomPixels'); |
| this.listen(document, 'keydown', '_handleKeyDown'); |
| }, |
| |
| setDetails: function(details) { |
| details = sk.object.shallowCopy(details); |
| details.llabel = details.llabel || "Left"; |
| details.rlabel = details.rlabel || "Right"; |
| |
| this.set('_leftActive', true); |
| this.set('_middleActive', false); |
| this.set('_rightActive', true); |
| this.set('_x', 0); |
| this.set('_y', 0); |
| this.set('details', details); |
| this.set('_pixels', 64); |
| this._updateTiming(); |
| this._showNext(true); |
| this._active = true; |
| this.$.zoomLeft.notifyResize(); |
| this.$.zoomMiddle.notifyResize(); |
| this.$.zoomRight.notifyResize(); |
| }, |
| |
| clear: function(details) { |
| this.set('details', {}); |
| this._cancelAsync(); |
| this._active = false; |
| }, |
| |
| _cancelAsync: function() { |
| if (this._asyncHandle) { |
| this.cancelAsync(this._asyncHandle); |
| this._asyncHandle = null; |
| } |
| }, |
| |
| _showNext: function(triggerAsync) { |
| var currSeq = ''; |
| if (this._seq.length > 0) { |
| var idx = this._seq.indexOf(this._currSeq); |
| if (idx < 0) { |
| idx = 0; |
| } else { |
| idx = (idx+1) % this._seq.length; |
| } |
| currSeq = this._seq[idx]; |
| } |
| this.set('_currSeq', currSeq); |
| if (triggerAsync) { |
| this._asyncHandle = this.async(function() { |
| this._showNext(true); |
| }.bind(this), 500); |
| } |
| }, |
| |
| _updateTiming: function() { |
| var seq = []; |
| if (this._leftActive) { |
| seq.push("left"); |
| } |
| |
| if (this._middleActive && this.details.middleImgUrl && (this.details.middleImgUrl != '')) { |
| seq.push('middle'); |
| } |
| |
| if (this._rightActive && this.details.rightImgUrl && (this.details.rigthImgUrl != '')) { |
| seq.push('right'); |
| } |
| |
| this._seq = seq; |
| }, |
| |
| _handleZoomPixels: function(ev) { |
| ev.stopPropagation(); |
| this.set('_centerPixel.' + ev.target.id, ev.detail.rgb + ' ' + ev.detail.hex); |
| |
| // Always calculate the middle value. |
| var lc = this.$.zoomLeft.getPixelColor(ev.detail.x, ev.detail.y); |
| var rc = this.$.zoomRight.getPixelColor(ev.detail.x, ev.detail.y); |
| var color = [ |
| Math.abs(lc[0]-rc[0]), |
| Math.abs(lc[1]-rc[1]), |
| Math.abs(lc[2]-rc[2]), |
| Math.abs(lc[3]-rc[3]) |
| ]; |
| var diffStr = sk.colorRGB(color, 0, true) + ' ' + sk.colorHex(color, 0); |
| this.set("_centerPixel.zoomMiddle", diffStr); |
| }, |
| |
| _handleKeyDown: function(e) { |
| if (!this._active) { |
| return; |
| } |
| |
| var c = String.fromCharCode(e.keyCode); |
| switch (c) { |
| case "J": |
| this.set('_y', this._y+1); |
| break; |
| case "K": |
| this.set('_y', this._y-1); |
| break; |
| case "H": |
| this.set('_x', this._x-1); |
| break; |
| case "L": |
| this.set('_x', this._x+1); |
| break; |
| case "A": |
| this.set('_pixels', Math.max(this._pixels/2, 8)); |
| break; |
| case "Z": |
| var size = this.$.zoomLeft.ctx.canvas.width; |
| var maxPixels = Math.pow(2, Math.floor(Math.log(size)/Math.log(2))-1); |
| this.set('_pixels', Math.min(this._pixels*2, maxPixels)); |
| break; |
| case "U": |
| this._moveToLargestDiff(); |
| break; |
| case "M": |
| this._manualToggle(); |
| break; |
| } |
| |
| if ("JKHLAZUM".indexOf(c) != -1 ) { |
| e.stopPropagation(); |
| } |
| }, |
| |
| _moveToLargestDiff: function() { |
| if (!this.details || !this.details.middleImgUrl) { |
| return; |
| } |
| |
| var leftColors = this.$.zoomLeft.getImageData(); |
| var rightColors = this.$.zoomRight.getImageData(); |
| var width = leftColors.width; |
| var height = leftColors.height; |
| var lc = leftColors.data; |
| var rc = rightColors.data; |
| var max = 0; |
| var dx = 0; |
| var dy = 0 |
| for (var x=0; x<width; x++) { |
| for (var y=0; y<height; y++) { |
| var offset = (y*width+x)*4; // Offset into the colors array. |
| var dist = this._colorDist( |
| rc[offset+0]-lc[offset+0], |
| rc[offset+1]-lc[offset+1], |
| rc[offset+2]-lc[offset+2], |
| rc[offset+3]-lc[offset+3] |
| ); |
| if (dist > max) { |
| max = dist; |
| dx = x; |
| dy = y; |
| } |
| } |
| } |
| this._x = dx; |
| this._y = dy; |
| }, |
| |
| // colorDist returns the distance of a color from (0, 0, 0, 0) using a |
| // crude square distance per channel. |
| _colorDist: function(r, g, b, a) { |
| return r*r + g*g + b*b + a*a; |
| }, |
| |
| _manualToggle: function() { |
| if (!this.details || !this.details.middleImgUrl) { |
| return; |
| } |
| |
| this._cancelAsync(); |
| this._showNext(false); |
| }, |
| |
| _hideMe: function(currSeq, pos) { |
| return currSeq != pos; |
| }, |
| |
| _boldClass: function(currSeq, pos) { |
| return (currSeq==pos) ? 'showBold' : ''; |
| }, |
| |
| _hideImg: function(url) { |
| return !url; |
| }, |
| |
| _noImage: function(details) { |
| return !(details && details.leftImgUrl); |
| } |
| |
| |
| }); |
| </script> |
| </dom-module> |