browser/metro/modules/colorUtils.jsm

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
     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 'use strict';
     6 Components.utils.import("resource://gre/modules/Services.jsm");
     7 Components.utils.import("resource://gre/modules/Promise.jsm");
     9 const ColorAnalyzer = Components.classes["@mozilla.org/places/colorAnalyzer;1"]
    10                 .getService(Components.interfaces.mozIColorAnalyzer);
    12 this.EXPORTED_SYMBOLS = ["ColorUtils"];
    14 let ColorUtils = {
    15   _initialized: false,
    16   init: function() {
    17     if (this._initialized)
    18         return;
    19     Services.obs.addObserver(this, "idle-daily", false);
    20     Services.obs.addObserver(this, "quit-application", false);
    21     this._initialized = true;
    22   },
    23   uninit: function() {
    24     Services.obs.removeObserver(this, "idle-daily");
    25     Services.obs.removeObserver(this, "quit-application");
    26   },
    28   // default to keeping icon colorInfo for max 1 day
    29   iconColorCacheMaxAge: 86400000,
    31   // in-memory store for favicon color data
    32   _uriColorsMap: (function() {
    33     let cache = new Map();
    34     // remove stale entries
    35     cache.purge = function(maxAgeMs = 0) {
    36       let cuttoff = Date.now() - (maxAgeMs || ColorUtils.iconColorCacheMaxAge);
    37       for (let [key, value] of this) {
    38         if (value.timestamp && value.timestamp >= cuttoff) {
    39           continue;
    40         }
    41         this.delete(key);
    42       }
    43     }
    44     return cache;
    45   })(),
    46   get iconColorCache() {
    47     return ColorUtils._uriColorsMap;
    48   },
    50   observe: function (aSubject, aTopic, aData) {
    51     switch (aTopic) {
    52       case "idle-daily":
    53         this.iconColorCache.purge();
    54         break;
    55       case "quit-application":
    56         this.uninit();
    57         break;
    58     }
    59   },
    61   /** Takes an icon and returns either an object with foreground and background color properties
    62    *  or a promise for the same.
    63    *  The foreground is the contrast color, the background is the primary/dominant one
    64    */
    65   getForegroundAndBackgroundIconColors: function getForegroundAndBackgroundIconColors(aIconURI) {
    66     let colorKey = aIconURI.spec;
    67     let colorInfo = this._uriColorsMap.get(colorKey);
    68     if (colorInfo) {
    69       return colorInfo;
    70     }
    72     let deferred = Promise.defer();
    73     let wrappedIcon = aIconURI;
    74     this._uriColorsMap.set(colorKey, deferred.promise);
    76     ColorAnalyzer.findRepresentativeColor(wrappedIcon, (success, color) => {
    77       if (!success) {
    78         this._uriColorsMap.delete(colorKey);
    79         deferred.reject();
    80       } else {
    81         colorInfo = {
    82           foreground: this.bestTextColorForContrast(color),
    83           background: this.convertDecimalToRgbColor(color),
    84           timestamp: Date.now()
    85         };
    86         deferred.resolve(colorInfo);
    87       }
    88     });
    89     return deferred.promise;
    90   },
    92   /** returns the best color for text readability on top of aColor
    93    * return color is in rgb(r,g,b) format, suitable to csss
    94    * The color bightness algorithm is currently: http://www.w3.org/TR/AERT#color-contrast
    95    */
    96   bestTextColorForContrast: function bestTextColorForContrast(aColor) {
    97     let r = (aColor & 0xff0000) >> 16;
    98     let g = (aColor & 0x00ff00) >> 8;
    99     let b = (aColor & 0x0000ff);
   101     let w3cContrastValue = ((r*299)+(g*587)+(b*114))/1000;
   102     w3cContrastValue = Math.round(w3cContrastValue);
   103     let textColor = "rgb(255,255,255)";
   105     if (w3cContrastValue > 125) {
   106       // bright/light, use black text
   107       textColor = "rgb(0,0,0)";
   108     }
   109     return textColor;
   110   },
   112   toCSSRgbColor: function toCSSRgbColor(r, g, b, a) {
   113     var values = [Math.round(r), Math.round(g), Math.round(b)];
   114     if (undefined !== a && a < 1) {
   115       values.push(a);
   116       return 'rgba('+values.join(',')+')';
   117     }
   118     return 'rgb('+values.join(',')+')';
   119   },
   121   /**
   122    * converts a decimal(base10) number into CSS rgb color value string
   123    */
   124   convertDecimalToRgbColor: function convertDecimalToRgbColor(aColor) {
   125     let [r,g,b,a] = this.unpackDecimalColorWord(aColor);
   126     return this.toCSSRgbColor(r,g,b,a);
   127   },
   129   /**
   130    * unpack a decimal(base10) word for r,g,b,a values
   131    */
   132   unpackDecimalColorWord: function unpackDecimalColorWord(aColor) {
   133     let a = (aColor & 0xff000000) >> 24;
   134     let r = (aColor & 0x00ff0000) >> 16;
   135     let g = (aColor & 0x0000ff00) >> 8;
   136     let b = (aColor & 0x000000ff);
   137     // NB: falsy alpha treated as undefined, fully opaque
   138     return a ? [r,g,b,a/255] : [r,g,b];
   139   },
   141   /**
   142    * create a decimal(base10) word for r,g,b values
   143    */
   144   createDecimalColorWord: function createDecimalColorWord(r, g, b, a) {
   145     let rgb = 0;
   146     rgb |= b;
   147     rgb |= (g << 8);
   148     rgb |= (r << 16);
   149     // pack alpha value if one is given
   150     if (undefined !== a && a < 1)
   151       rgb |= (Math.round(a*255) << 24);
   152     return rgb;
   153   },
   155   /**
   156    * Add 2 rgb(a) colors to get a flat color
   157    */
   158   addRgbColors: function addRgbColors(color1, color2) {
   159     let [r1, g1, b1] = this.unpackDecimalColorWord(color1);
   160     let [r2, g2, b2, alpha] = this.unpackDecimalColorWord(color2);
   162     let color = {};
   163     // early return if 2nd color is opaque
   164     if (!alpha || alpha >= 1)
   165       return color2;
   167     return this.createDecimalColorWord(
   168       Math.min(255, alpha * r2 + (1 - alpha) * r1),
   169       Math.min(255, alpha * g2 + (1 - alpha) * g1),
   170       Math.min(255, alpha * b2 + (1 - alpha) * b1)
   171     );
   172   }
   173 };

mercurial