view light9/web/light9-color-picker.html @ 1635:0eb5f70bdde2

unawesome some overlapping css Ignore-this: 25b7173f67f9031387adc37300778f49
author Drew Perttula <drewp@bigasterisk.com>
date Sat, 10 Jun 2017 03:17:20 +0000
parents 99307e5a1f90
children cb39ef3dc1d5
line wrap: on
line source

<link rel="import" href="/lib/polymer/polymer.html">
<link rel="import" href="/lib/paper-slider/paper-slider.html">

<dom-module id="light9-color-picker">
  <template>
    <style>
     :host {
         position: relative;
         display: flex;
         align-items: center;
     }
     #smallRainbowComp, #largeRainbowComp {
         display: inline-block;
         overflow: hidden;
         position: relative;
     }
     #smallRainbow {
         background: url(/colorpick_rainbow_small.png);
         width: 150px; 
         height: 30px;
     }
     #largeRainbow {
         background: url(/colorpick_rainbow_large.png);
         width: 400px; 
         height: 200px;
     }
     #smallCrosshair, #largeCrosshair {
         position: absolute;
         left: -60px;
         top: -62px;
         pointer-events: none;
     }
     #smallCrosshair {
         background: url(/colorpick_crosshair_small.svg);
         /* this can't be too tall, or chrome will cull it in the 
            second column if its top goes above the top of the columns */
         width: 400px; 
         height: 60px;
     }
     #largeCrosshair {
         background: url(/colorpick_crosshair_large.svg);
         width: 1000px; 
         height: 1000px;
     }
     #smallRainbowComp {
         margin-right: 3px;
     }
     #largeRainbowComp {
         display: none;
         position: absolute;
         border: 4px solid #545454;
         box-shadow: 8px 11px 40px 0px rgba(0, 0, 0, 0.74);
         z-index: 10;
         left: -50px;
         top: -110px;
     }
     paper-slider {
         width: 170px;
     }
    </style>
    <div id="smallRainbowComp">
      <div id="smallRainbow" on-mouseenter="onEnterSmall"></div>
      <div id="smallCrosshair"></div>
    </div>
    <span>V:</span>
    <paper-slider min="0"
                  max="255"
                  step="1"
                  value="{{sliderWriteValue}}"
                  immediate-value="{{value}}"></paper-slider>
    <div id="largeRainbowComp">
      <div id="largeRainbow"
           on-mousemove="onCanvasMove"
           on-mouseup="hideLarge"
           on-mouseleave="hideLarge"></div>
      <div id="largeCrosshair"></div>
    </div>
  </template>
  <script src="/lib/color/one-color.js"></script>
  <script>
   class RainbowCanvas {
       constructor(url, size, onReady) {
           this.size = size;
           var elem = document.createElement('canvas');
           elem.width = size[0];
           elem.height = size[1];

           this.ctx = elem.getContext('2d');

           this.colorPos = {} // color: pos
           this._loaded = false;
           this._loadWatchers = []; // callbacks
           
           var img = new Image();
           img.onload = function() {
               this.ctx.drawImage(img, 0, 0);
               this._readImage();
               this._loaded = true;
               this._loadWatchers.forEach(function(cb) { cb(); });
               this._loadWatchers = [];
           }.bind(this);
           img.src = url;
       }
       onLoad(cb) {
           // we'll call this when posFor is available
           if (this._loaded) {
               cb();
               return;
           }
           this._loadWatchers.push(cb);
       }
       _readImage() {
           var data = this.ctx.getImageData(
               0, 0, this.size[0], this.size[1]).data;
           for (var y = 0; y < this.size[1]; y+=1) {
               for (var x = 0; x < this.size[0]; x+=1) {
                   var base = (y * this.size[0] + x) * 4;
                   var c = one.color([data[base + 0],
                                      data[base + 1],
                                      data[base + 2], 255]).hex();
                   this.colorPos[c] = [x, y];
               }
           }
       }       
       colorAt(pos) {
           var data = this.ctx.getImageData(pos[0], pos[1], 1, 1).data;
           return one.color([data[0], data[1], data[2], 255]).hex();
       }
       posFor(color) {
           if (color == '#000000') {
               throw new Error('no match');
           }
           
           let bright = one.color(color).value(1).hex();
           let r = parseInt(bright.substr(1, 2), 16),
               g = parseInt(bright.substr(3, 2), 16),
               b = parseInt(bright.substr(5, 2), 16);
           
           // We may not have a match for this color exactly (e.g. on
           // the small image), so we have to search for a near one.
           
           // 0, 1, -1, 2, -2, ...
           let walk = function(x) { return -x + (x > 0 ? 0 : 1); }
           
           var radius = 8;
           for (var dr = 0; dr < radius; dr = walk(dr)) {
               for (var dg = 0; dg < radius; dg = walk(dg)) {
                   for (var db = 0; db < radius; db = walk(db)) {
                       // Don't need bounds check- out of range
                       // corrupt colors just won't match.
                       color = one.color([r + dr, g + dg, b + db, 255]).hex();
                       var pos = this.colorPos[color];
                       if (pos !== undefined) {
                           return pos;
                       }
                   }
               }
           }
           throw new Error('no match');
       }
   }
   
   Polymer({
       is: "light9-color-picker",
       properties: {
           color: {
               type: String,
               notify: true,
           },
           hueSatColor: { type: String, notify: true, value: '#000000' },
           value: { type: Number, notify: true }, // 0..255
           sliderWriteValue: { type: Number, notify: true },
       },
       observers: [
           'readColor(color)',
           'onValue(value)',
           'writeColor(hueSatColor, value)'
       ],
       displayed: function() {
           // call this when the smallcrosshair first has a size
           this._updateSmallCrosshair();
       },
       attached: function() {
           if (!window.pickerCanvases) {
               window.pickerCanvases = {
                   large: new RainbowCanvas(
                       '/colorpick_rainbow_large.png', [400, 200]),
                   small: new RainbowCanvas(
                       '/colorpick_rainbow_small.png', [150, 30]),
               };
           }
           this.large = window.pickerCanvases.large;
           this.small = window.pickerCanvases.small;
           this.small.onLoad(function() {
               // color may have been set before our image came
               this._updateSmallCrosshair();
           }.bind(this));
       },
       onValue: function(value) {
           let neverBlack = .1 + .9 * value / 255;
           this.$.smallRainbow.style.filter = `brightness(${neverBlack})`;
       },
       writeColor: function(hueSatColor, value) {
           this.color = one.color(hueSatColor).value(value / 255).hex();
       },
       readColor: function(color) {
           if (this.$.largeRainbowComp.style.display == 'block') {
               // for performance, don't do color searches on covered widget
               return;
           }
           
           var colorValue = one.color(color).value() * 255;
           // writing back to immediate-value doesn't work on paper-slider
           this.sliderWriteValue = colorValue;
           
           this._updateSmallCrosshair();
       },
       _updateSmallCrosshair: function() {
           try {
               var pos = this.small.posFor(this.color);
           } catch(e) {
               this.moveSmallCrosshair([-999, -999]);
               return;
           }
           this.moveSmallCrosshair(pos);
       },
       _floatLarge: function() {
           // Large might span multiple columns, and chrome won't
           // send events for those parts. Workaround: take it out of
           // the columns.
           let large = this.$.largeRainbowComp;
           let rect = this.$.smallRainbowComp.getBoundingClientRect();
           document.body.append(large);
           large.style.position = 'fixed';
           large.style.left = (rect.left) + 'px';
           large.style.top = (rect.top - 50) + 'px';
       },
       showLarge: function() {
           this._floatLarge();
           this.$.largeRainbowComp.style.display = 'block';
           try {
               this.moveLargeCrosshair(this.large.posFor(this.color));
           } catch(e) {
               this.moveLargeCrosshair([-999, -999]);
               return;
           }
       },
       hideLarge: function() {
           this.$.largeRainbowComp.style.display = 'none';
           this.readColor(this.color);
       },
       onEnterSmall: function() {
           // not if we just closed the large one
           this.showLarge();
       },
       moveLargeCrosshair: function(pos, _elem) {
           _elem = _elem || this.$.largeCrosshair;
           _elem.style.left = (pos[0] - _elem.offsetWidth / 2) + 'px';
           _elem.style.top = (pos[1] - _elem.offsetHeight / 2) + 'px';
       },
       moveSmallCrosshair: function(pos) {
           this.moveLargeCrosshair(pos, this.$.smallCrosshair);
       },
       onCanvasMove: function(ev) {
           if (ev.buttons != 1) {
               return;
           }
           var canvas = this.$.largeRainbow;
           var pos = [ev.offsetX - canvas.offsetLeft,
                      ev.offsetY - canvas.offsetTop];
           this.moveLargeCrosshair(pos);
           this.hueSatColor = this.large.colorAt(pos);

           // special case: it's useless to adjust the hue/sat of black
           if (this.value == 0) {
               this.value = 255;
           }
       },
   });
  </script>
</dom-module>