Source: src/main/javascript/oracle/oj/ojvalidation-base/ColorConverter.js

Oracle® JavaScript Extension Toolkit (JET)
3.2.0

E87541-01

/**
 * Copyright (c) 2016, Oracle and/or its affiliates.
 * All rights reserved.
 */

/**
 * oj.ColorConverter Contract. 
 */


(function ()
{

  /**
   * @export
   * @constructor
   * @augments oj.Converter 
   * @name oj.ColorConverter
   
   * @classdesc An {@link oj.Color} object format converter.
   * @desc Creates a Converter that allows any color format to be obtained from an {@link oj.Color} object.
   * @since 0.6
   * @property {Object=} options - an object literal used to provide optional information to 
   * initialize the converter.
   * @property {string=} options.format - sets the format of the converted color specification.
   * Allowed values are "rgb" (the default, if omitted), "hsl", "hsv" "hex", and "hex3". "hex" returns six
   * hex digits ('#rrggbb'), and "hex3" returns three hex digits if possible ('#rgb') or six hex
   * digits if the value cannot be converted to three.
   * @example <caption>Create a color converter to convert an rgb specification to hsl format</caption>
   * var cvFactory = oj.Validation.converterFactory(oj.ConverterFactory.CONVERTER_TYPE_COLOR);
   * var cv        = cv.createConverter({format: "hsl");
   * var color     = new oj.Color("rgb(30, 87, 236)") ;
   * var hsl       = cv.format(color);   -->  "hsl(223, 84%, 52%)"
   * 
   */
  oj.ColorConverter = function (options)
  {
    this.Init(options);
  };

// Subclass from oj.Converter 
  oj.Object.createSubclass(oj.ColorConverter, oj.Converter, "oj.ColorConverter");

  /**
   * Initializes the color converter instance with the set options.
   * @param {Object=} options an object literal used to provide an optional information to 
   * initialize the converter.<p>
   * @export
   */
  oj.ColorConverter.prototype.Init = function (options)
  {
    options = options || {};
    options["format"] = options["format"] || "rgb";
    oj.ColorConverter.superclass.Init.call(this, options);
  };

  /**
   * Formats the color using the options provided into a string.
   * 
   * @param {oj.Color} color the {@link oj.Color} instance to be formatted to a color specification string
   * @return {(string | null)} the color value formatted to the color specification defined in the options.
   * @throws {Error} a ConverterError if formatting fails, or the color option is invalid.
   * @export
   */
  oj.ColorConverter.prototype.format = function (color)
  {
    var fmt = this._getFormat(),
    ret = null;

    if (fmt === "rgb")
    {
      ret = color.toString();
    }
    else if (fmt === "hsl")
    {
      ret = _toHslString(color);
    }
    else if (fmt === "hex")
    {
      ret = _toHexString(color);
    }
    else if (fmt === "hex3")
    {
      ret = _toHexString(color, true);
    }
    else if (fmt === "hsv")
    {
      ret = _toHsvString(color);
    }
    else
    {
      _throwInvalidColorFormatOption();
    }

    return ret ? ret : oj.ColorConverter.superclass.format.call(this, color);
  };


  /**
   * Parses a CSS3 color specification string and returns an oj.Color object.</br>
   * (Note that the "format" option used to create the Converter is not used
   * by this method, since the oj.Color object created is color agnostic.)
   * @param {string} value The color specification string to parse.
   * @return {oj.Color} the parsed value as an {@link oj.Color} object.
   * @throws {Error} a ConverterError if parsing fails
   * @export
   */
  oj.ColorConverter.prototype.parse = function (value)
  {
    try
    {
      return  new oj.Color(value);   //throws error if invalid
    }
    catch (e)
    {
      _throwInvalidColorSyntax();
    }
  };


  /**
   * Returns a hint that describes the color converter format.
   * @return {string} The expected format of a converted color.
   * @export
   */
  oj.ColorConverter.prototype.getHint = function ()
  {
    return this._getFormat();
  };


  /**
   * Returns an object literal with properties reflecting the color formatting options computed based 
   * on the options parameter.
   * 
   * @return {Object} An object literal containing the resolved values for the following options.
   * <ul>
   * <li><b>format</b>: A string value with the format of the color specification.
   * for formatting.</li>
   * </ul>
   * @export
   */
  oj.ColorConverter.prototype.resolvedOptions = function ()
  {
    return {
      "format": this._getFormat()
    };
  };


  /**
   *   @private
   */
  oj.ColorConverter.prototype._getFormat = function ()
  {
    return  oj.ColorConverter.superclass.getOptions.call(this)["format"];
  }



  /**-------------------------------------------------------------*/
  /*   Helpers                                                    */
  /**-------------------------------------------------------------*/


  /**
   *  Converts an oj.Color object to a 3 or 6 hex character string 
   *  @param {Object} color  The oj.Color object to be converted to a hex string.
   *  @param {boolean=} allow3Char  If true the representation is 3 hex characters
   *  (if possible). If false, or omitted, 6 hex characters are used.
   *  @return {string} The hex string representation of the color object.
   */
  function _toHexString(color, allow3Char)
  {
    return '#' + _toHex(color, allow3Char);
  };


  /**
   *  Converts an oj.Color object to an hsl/hsla string 
   *  @param {Object} color  The oj.Color object to be converted to an hsl/hsla string.
   *  @return {string} The hsl/hsla representation of the color object.
   */
  function _toHslString(color)
  {
    var hsl = _rgbToHsl(color._r, color._g, color._b);
    var h = Math.round(hsl.h * 360), s = Math.round(hsl.s * 100), l = Math.round(hsl.l * 100);

    return (color._a == 1) ? "hsl(" + h + ", " + s + "%, " + l + "%)" :
    "hsla(" + h + ", " + s + "%, " + l + "%, " + color._a + ")";
  };

  /**
   *  Converts an oj.Color object to a 3 or 6 hex character string 
   *  @param {Object} color  The oj.Color object to be converted to a hex string.
   *  @param {boolean=} allow3Char  If true the representation is 3 hex characters
   *                   (if possible). If false, or omitted, 6 hex characters are used.
   *  @return {string} The hex string representation of the color object.
   */
  function _toHex(color, allow3Char)
  {
    return _rgbToHex(color._r, color._g, color._b, allow3Char);
  };

  /**
   *  Converts an oj.Color object to an hsv/hsva string 
   *  @param {Object} color  The oj.Color object to be converted to an hsv/hsva string.
   *  @return {string} The hsv/hsva representation of the color object.
   */
  function _toHsvString(color)
  {
    var hsv = _rgbToHsv(color._r, color._g, color._b);

    var h = Math.round(hsv.h * 360),
    s = Math.round(hsv.s * 100),
    v = Math.round(hsv.v * 100);

    return (color._a == 1) ?
    "hsv(" + h + ", " + s + "%, " + v + "%)" :
//   "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")";
    "hsva(" + h + ", " + s + "%, " + v + "%, " + color._a + ")";
  };


  /**
   * Converts RGB color values to hex
   * @param {number} r the red value in the set [0,255]
   * @param {number} g the green value in the set [0,255]
   * @param {number} b the blue value in the set [0,255]
   * @param {boolean=} allow3Char  If true the representation is 3 hex characters
   *                   (if possible). If false, or omitted, 6 hex characters are used.
   * @returns {string} a 3 or 6 hex character string.
   */
  function _rgbToHex(r, g, b, allow3Char)
  {
    var hex = [
      _pad2(Math.round(r).toString(16)),
      _pad2(Math.round(g).toString(16)),
      _pad2(Math.round(b).toString(16))
    ];

    // Return a 3 character hex if possible
    if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) &&
    hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1))
    {
      return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
    }

    return hex.join("");
  };


  /**
   * Converts an RGB color value to HSL.
   * Handle bounds/percentage checking to conform to CSS color spec, and returns
   * an object containg the h,s,l values.
   * <http://www.w3.org/TR/css3-color/>
   * Assumes:  r, g, b in [0, 255] or [0, 1]
   * @param {number} r the red value
   * @param {number} g the green value
   * @param {number} b the blue value
   * @returns {Object} Object with properties h, s, l, in [0, 1].
   */
  function _rgbToHsl(r, g, b)
  {
    r = _bound01(r, 255);
    g = _bound01(g, 255);
    b = _bound01(b, 255);

    var max = Math.max(r, g, b), min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2;

    if (max == min)
    {
      h = s = 0; // achromatic
    }
    else
    {
      var d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      switch (max)
      {
        case r:
          h = (g - b) / d + (g < b ? 6 : 0);
          break;
        case g:
          h = (b - r) / d + 2;
          break;
        case b:
          h = (r - g) / d + 4;
          break;
      }

      h /= 6;
    }

    return {h: h, s: s, l: l};
  };

  /**
   * Converts an RGB color value to HSV.
   * Handle bounds/percentage checking to conform to CSS color spec, and returns
   * an object containg the h,s,v values.
   * <http://www.w3.org/TR/css3-color/>
   * Assumes:  r, g, and b are contained in the set [0,255] or [0,1]
   * @param {number} r the red value
   * @param {number} g the green value
   * @param {number} b the blue value
   * @returns {Object} Object with properties h, s, v, in [0,1].
   */
  function _rgbToHsv(r, g, b)
  {
    r = _bound01(r, 255);
    g = _bound01(g, 255);
    b = _bound01(b, 255);

    var max = Math.max(r, g, b), min = Math.min(r, g, b);
    var h, s, v = max;

    var d = max - min;
    s = max === 0 ? 0 : d / max;

    if (max == min)
    {
      h = 0; // achromatic
    }
    else
    {
      switch (max)
      {
        case r:
          h = (g - b) / d + (g < b ? 6 : 0);
          break;
        case g:
          h = (b - r) / d + 2;
          break;
        case b:
          h = (r - g) / d + 4;
          break;
      }
      h /= 6;
    }
    return {h: h, s: s, v: v};
  };


  /**
   * Converts an RGBA color plus alpha transparency to hex
   * Assumes r, g, b and a are contained in the set [0, 255]
   * @param {number} r the red value in the set [0, 255]
   * @param {number} g the green value in the set [0, 255]
   * @param {number} b the blue value in the set [0, 255]
   * @param {number} a the alpha value in the set [0,1]
   * Returns an 8 character hex string
   */
  /*    NOT USED currently
   function rgbaToHex(r, g, b, a)
   {
   var hex = [
   pad2(convertDecimalToHex(a)),
   pad2(mathRound(r).toString(16)),
   pad2(mathRound(g).toString(16)),
   pad2(mathRound(b).toString(16))
   ];
   
   return hex.join("");
   }
   */


  /**
   *   Take input from [0, n] and return it as [0, 1]
   */
  function _bound01(n, max)
  {
    if (_isOnePointZero(n))
    {
      n = "100%";
    }

    var processPercent = _isPercentage(n);
    n = Math.min(max, Math.max(0, parseFloat(n)));

    // Automatically convert percentage into number
    if (processPercent)
    {
      n = parseInt(n * max, 10) / 100;
    }

    // Handle floating point rounding errors
    if ((Math.abs(n - max) < 0.000001))
    {
      return 1;
    }

    // Convert into [0, 1] range if it isn't already
    return (n % max) / parseFloat(max);
  };


  /**
   *   Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
   *   <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
   */
  function _isOnePointZero(n)
  {
    return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
  };

  /**
   *  Check to see if string passed in is a percentage
   *  @param {string}  n  The number string
   *  @return {boolean}  True if the string contains a '%' character.
   */
  function _isPercentage(n)
  {
    return typeof n === "string" && n.indexOf('%') != -1;
  }
  ;


  /**
   *  Force a hex value string to have 2 characters by inserting a preceding zero
   *  if neccessary.  e.g. 'a' -> '0a'
   *  @param {string} c  The hex character(s) to be tested.
   *  @return {string} A two character hex string.
   */
  function _pad2(c)
  {
    return c.length == 1 ? '0' + c : '' + c;
  }
  ;


  /**
   *   Throw an invalid color specfication error.
   */
  function _throwInvalidColorSyntax()
  {
    var summary, detail, ce;

//  summary =  oj.Translations.getTranslatedString("oj-converter.color.invalidFormat.summary") ;
//  detail  = oj.Translations.getTranslatedString("oj-converter.color.invalidFormat.detail") ;
    summary = "Invalid color specification";
    detail = "Color specification does not conform to CSS3 standard";

    ce = new oj.ConverterError(summary, detail);

    throw ce;
  }
  ;


  /**
   *   Throw an invalid converter specfication error.
   */
  function _throwInvalidColorFormatOption()
  {
    var summary, detail, ce;

// summary =  oj.Translations.getTranslatedString("oj-converter.color.invalidFormat.summary") ;
// detail  = oj.Translations.getTranslatedString("oj-converter.color.invalidFormat.detail") ;
    summary = "Invalid color format";
    detail = "Invalid color format option specification";
    ce = new oj.ConverterError(summary, detail);

    throw ce;
  }
  ;


})();