text: /**
   * Deals with color format conversions and color adjustments.
   *
   * Contains code taken from https://github.com/antimatter15/rgb-lab/
   * @namespace qui.utils.colors
   */
  
  /* eslint-disable no-multi-spaces */
  
  
  let cachedColorsRGBAByName = {}
  
  
  /**
   * Convert a tuple of (*hue*, *saturation*, *value*) components into (*red*, *green*, *blue*) equivalent.
   *
   * *red*, *green* and *blue* range from `0` to `255`. *hue* ranges from `0` to `359`, while *saturation* and *value*
   * range from `0` to `1`.
   *
   * @alias qui.utils.colors.hsv2rgb
   * @param {Number[]} hsv a 3 elements array representing the *hue*, *saturation* and *value* components
   * @returns {Number[]} a 3 elements array representing the *red*, *green* and *blue* components
   */
  export function hsv2rgb(hsv) {
      let h = hsv[0], s = hsv[1], v = hsv[2]
      let hi = Math.floor(h / 60.0) % 6
      let f = (h / 60.0) - Math.floor(h / 60.0)
      let p = v * (1.0 - s)
      let q = v * (1.0 - (f * s))
      let t = v * (1.0 - ((1.0 - f) * s))
  
      v *= 255
      t *= 255
      p *= 255
      q *= 255
  
      let rgb = [
          [v, t, p],
          [q, v, p],
          [p, v, t],
          [p, q, v],
          [t, p, v],
          [v, p, q]
      ][hi]
  
      return [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2])]
  }
  
  /**
   * Convert a tuple of (*red*, *green*, *blue*) components into (*hue*, *saturation*, *value*) equivalent.
   *
   * *red*, *green* and *blue* range from `0` to `255`. *hue* ranges from `0` to `359`, while *saturation* and *value*
   * range from `0` to `1`.
   *
   * @alias qui.utils.colors.rgb2hsv
   * @param {Number[]} rgb a 3 elements array representing the *red*, *green* and *blue* components
   * @returns {Number[]} a 3 elements array representing the *hue*, *saturation* and *value* components
   */
  export function rgb2hsv(rgb) {
      let r = rgb[0] / 255
      let g = rgb[1] / 255
      let b = rgb[2] / 255
  
      let min = Math.min(r, g, b)
      let max = Math.max(r, g, b)
      let delta = max - min
  
      let h = 0
      let s = 0
      let v = max
  
      if (delta !== 0) {
          s = delta / max
          let dr = (((max - r) / 6) + (delta / 2)) / delta
          let dg = (((max - g) / 6) + (delta / 2)) / delta
          let db = (((max - b) / 6) + (delta / 2)) / delta
  
          if (r === max) {
              h = db - dg
          }
          else if (g === max) {
              h = (1 / 3) + dr - db
          }
          else if (b === max) {
              h = (2 / 3) + dg - dr
          }
  
          if (h < 0) {
              h += 1
          }
          if (h > 1) {
              h -= 1
          }
      }
  
      h = Math.round(h * 360)
  
      return [h, s, v]
  }
  
  /**
   * Convert a color string into (*red*, *green*, *blue*, *alpha*) equivalent.
   *
   * *red*, *green* and *blue* range from `0` to `255`, while *alpha* ranges from `0` to `1`. Accepted string color
   * formats are `rgb(r, g, b)`, `rgba(r, g, b, a)`, `#rrggbb` and an HTML color name.
   *
   * @alias qui.utils.colors.str2rgba
   * @param {String} strColor
   * @returns {Number[]} a 4 elements array representing the *red*, *green*, *blue* and *alpha* components
   */
  export function str2rgba(strColor) {
      if (!strColor) {
          return [0, 0, 0, 1] /* Defaults to black */
      }
  
      let r = 0, g = 0, b = 0, a = 1
      let start, stop, parts
  
      if (strColor.startsWith('#')) {
          if (strColor.length === 4) {
              r = strColor.substr(1, 1)
              g = strColor.substr(2, 1)
              b = strColor.substr(3, 1)
  
              r = parseInt(r + r, 16)
              g = parseInt(g + g, 16)
              b = parseInt(b + b, 16)
          }
          else if (strColor.length === 7) {
              r = parseInt(strColor.substr(1, 2), 16)
              g = parseInt(strColor.substr(3, 2), 16)
              b = parseInt(strColor.substr(5, 2), 16)
          }
      }
      else if (strColor.startsWith('rgba')) {
          start = strColor.indexOf('(')
          stop = strColor.indexOf(')')
  
          if (start !== -1 && stop !== -1) {
              strColor = strColor.substring(start + 1, stop)
              parts = strColor.split(',')
              if (parts.length === 4) {
                  r = parseInt(parts[0].trim())
                  g = parseInt(parts[1].trim())
                  b = parseInt(parts[2].trim())
                  a = parseFloat(parts[3].trim())
              }
          }
      }
      else if (strColor.startsWith('rgb')) {
          start = strColor.indexOf('(')
          stop = strColor.indexOf(')')
  
          if (start !== -1 && stop !== -1) {
              strColor = strColor.substring(start + 1, stop)
              parts = strColor.split(',')
              if (parts.length === 3) {
                  r = parseInt(parts[0].trim())
                  g = parseInt(parts[1].trim())
                  b = parseInt(parts[2].trim())
              }
          }
      }
      else { /* A color name */
          let colorRGBA = cachedColorsRGBAByName[strColor]
          if (colorRGBA) {
              return colorRGBA
          }
  
          let div = document.createElement('div')
          div.style.display = 'none'
          div.style.color = strColor
          document.body.appendChild(div)
  
          let style = window.getComputedStyle(div)
          let result = str2rgba(style.color)
  
          document.body.removeChild(div)
  
          cachedColorsRGBAByName[strColor] = result
          return result
      }
  
      return [r, g, b, a]
  }
  
  /**
   * Convert a tuple of (*red*, *green*, *blue*, *alpha*) components into color string equivalent.
   *
   * *red*, *green* and *blue* range from `0` to `255`, while *alpha* ranges from `0` to `1`. Returned string color format
   * is `rgba(r, g, b, a)` or `#rrggbb`, depending on the presence of the alpha factor.
   *
   * @alias qui.utils.colors.rgba2str
   * @param {Number[]} rgba a 4 elements array representing the *red*, *green*, *blue* and *alpha* components,
   * respectively
   * @returns {String}
   */
  export function rgba2str(rgba) {
      function byte2hex(value) {
          value = parseInt(value).toString(16)
          if (value.length < 2) {
              value = `0${value}`
          }
  
          return value
      }
  
      if (rgba.length === 3 || (rgba.length === 4 && rgba[3] === 1)) {
          return `#${byte2hex(rgba[0])}${byte2hex(rgba[1])}${byte2hex(rgba[2])}`
      }
      else { /* Assuming the length is 4 */
          return `rgba(${rgba[0]}, ${rgba[1]}, ${rgba[2]}, ${rgba[3]})`
      }
  }
  
  /**
   * Convert a tuple of (*lightness*, *a*, *b*) components into (*red*, *green*, *blue*) equivalent. The *lightness*, *a*
   * and *b* components are defined by the CIELAB color space.
   *
   * *red*, *green* and *blue* range from `0` to `255`. *lightness* ranges from `0` to `100`, while *a* and *b* range from
   * `-100` to `100`.
   *
   * @alias qui.utils.colors.lab2rgb
   * @param {Number[]} lab a 3 elements array representing the *lightness*, *a* and *b* components
   * @returns {Number[]} a 3 elements array representing the *red*, *green* and *blue* components
   */
  export function lab2rgb(lab) {
      let y = (lab[0] + 16) / 116
      let x = lab[1] / 500 + y
      let z = y - lab[2] / 200
      let r, g, b
  
      x = 0.95047 * ((x * x * x > 0.008856) ? x * x * x : (x - 16 / 116) / 7.787)
      y = 1.00000 * ((y * y * y > 0.008856) ? y * y * y : (y - 16 / 116) / 7.787)
      z = 1.08883 * ((z * z * z > 0.008856) ? z * z * z : (z - 16 / 116) / 7.787)
  
      r = x *  3.2406 + y * -1.5372 + z * -0.4986
      g = x * -0.9689 + y *  1.8758 + z *  0.0415
      b = x *  0.0557 + y * -0.2040 + z *  1.0570
  
      r = (r > 0.0031308) ? (1.055 * Math.pow(r, 1 / 2.4) - 0.055) : 12.92 * r
      g = (g > 0.0031308) ? (1.055 * Math.pow(g, 1 / 2.4) - 0.055) : 12.92 * g
      b = (b > 0.0031308) ? (1.055 * Math.pow(b, 1 / 2.4) - 0.055) : 12.92 * b
  
      return [
          Math.max(0, Math.min(1, r)) * 255,
          Math.max(0, Math.min(1, g)) * 255,
          Math.max(0, Math.min(1, b)) * 255
      ]
  }
  
  /**
   * Convert a tuple of (*red*, *green*, *blue*) components into (*lightness*, *a*, *b*) equivalent. The *lightness*, *a*
   * and *b* components are defined by the CIELAB color space.
   *
   * *red*, *green* and *blue* range from `0` to `255`. *lightness* ranges from `0` to `100`, while *a* and *b* range from
   * `-100` to `100`.
   *
   * @alias qui.utils.colors.rgb2lab
   * @param {Number[]} rgb a 3 elements array representing the *red*, *green* and *blue* components
   * @returns {Number[]} lab a 3 elements array representing the *lightness*, *a* and *b* components
   */
  export function rgb2lab(rgb) {
      let r = rgb[0] / 255
      let g = rgb[1] / 255
      let b = rgb[2] / 255
      let x, y, z
  
      r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92
      g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92
      b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92
  
      x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047
      y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000
      z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883
  
      x = (x > 0.008856) ? Math.pow(x, 1 / 3) : (7.787 * x) + 16 / 116
      y = (y > 0.008856) ? Math.pow(y, 1 / 3) : (7.787 * y) + 16 / 116
      z = (z > 0.008856) ? Math.pow(z, 1 / 3) : (7.787 * z) + 16 / 116
  
      return [(116 * y) - 16, 500 * (x - y), 200 * (y - z)]
  }
  
  /**
   * Compute the luminance of a color, as defined by https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef.
   *
   * *red*, *green* and *blue* range from `0` to `255`. *luminance* ranges from `0` to `1`.
   *
   * @alias qui.utils.colors.luminance
   * @param {Number[]} rgb a 3 elements array representing the *red*, *green* and *blue* components
   * @returns {Number}
   */
  export function luminance(rgb) {
      let a = rgb.map(function (v) {
          v /= 255
  
          return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)
      })
  
      return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722
  }
  
  /**
   * Compute the contrast ratio between two colors, as defined by
   * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef.
   *
   * *red*, *green* and *blue* range from `0` to `255`. *contrast* ranges from `1` to `21`.
   *
   * @alias qui.utils.colors.contrast
   * @param {Number[]} rgb1 a 3 elements array representing the *red*, *green* and *blue* components of the first color
   * @param {Number[]} rgb2 a 3 elements array representing the *red*, *green* and *blue* components of the second color
   * @returns {Number}
   */
  export function contrast(rgb1, rgb2) {
      let c = (luminance(rgb1) + 0.05) / (luminance(rgb2) + 0.05)
      if (c < 1) {
          c = 1 / c
      }
  
      return c
  }
  
  /**
   * Compute the distance between two colors, as defined by the *deltaE* CIE94 specification.
   *
   * *lightness* ranges from `0` to `100`, while *a* and *b* range from `-100` to `100`. *deltaE* ranges from `0` to
   * `255`.
   *
   * @alias qui.utils.colors.deltaE
   * @param {Number[]} lab1 a 3 elements array representing the *lightness*, *a* and *b* components of the first color
   * @param {Number[]} lab2 a 3 elements array representing the *lightness*, *a* and *b* components of the second color
   * @returns {Number}
   */
  export function deltaE(lab1, lab2) {
      /* CIE94 implementation */
      let deltaL = lab1[0] - lab2[0]
      let deltaA = lab1[1] - lab2[1]
      let deltaB = lab1[2] - lab2[2]
      let c1 = Math.sqrt(lab1[1] * lab1[1] + lab1[2] * lab1[2])
      let c2 = Math.sqrt(lab2[1] * lab2[1] + lab2[2] * lab2[2])
      let deltaC = c1 - c2
      let deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC
      deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH)
      let sc = 1.0 + 0.045 * c1
      let sh = 1.0 + 0.015 * c1
      let deltaLklsl = deltaL / (1.0)
      let deltaCkcsc = deltaC / (sc)
      let deltaHkhsh = deltaH / (sh)
      let i = deltaLklsl * deltaLklsl + deltaCkcsc * deltaCkcsc + deltaHkhsh * deltaHkhsh
  
      return i < 0 ? 0 : Math.min(Math.sqrt(i), 255)
  }
  
  /**
   * Return a darker variant of a given color.
   *
   * Accepted string color formats are `rgb(r, g, b)`, `rgba(r, g, b, a)`, `#rrggbb` and an HTML color name.
   *
   * @alias qui.utils.colors.darker
   * @param {String} strColor
   * @param {Number} amount the fraction used to alter the value component (from `0` to `1`)
   * @returns {String}
   */
  export function darker(strColor, amount) {
      let rgba = str2rgba(strColor)
      let alpha = rgba[3]
      let hsv = rgb2hsv(rgba.slice(0, 3))
      hsv[2] /= amount
  
      rgba = hsv2rgb(hsv)
      rgba.push(alpha)
  
      return rgba2str(rgba)
  }
  
  /**
   * Return a lighter variant of a given color.
   *
   * Accepted string color formats are `rgb(r, g, b)`, `rgba(r, g, b, a)`, `#rrggbb` and an HTML color name.
   *
   * @alias qui.utils.colors.lighter
   * @param {String} strColor
   * @param {Number} amount the fraction used to alter the value component (from `0` to `1`)
   * @returns {String}
   */
  export function lighter(strColor, amount) {
      return darker(strColor, 1 / amount)
  }
  
  /**
   * Alter the alpha factor of a color.
   *
   * Accepted string color formats are `rgb(r, g, b)`, `rgba(r, g, b, a)`, `#rrggbb` and an HTML color name.
   *
   * @alias qui.utils.colors.alpha
   * @param {String} strColor
   * @param {Number} factor the alpha factor (from `0` to `1`)
   * @returns {String}
   */
  export function alpha(strColor, factor) {
      let rgba = str2rgba(strColor)
      rgba[3] = factor
  
      return rgba2str(rgba)
  }
  
  /**
   * Mix two colors.
   *
   * Accepted string color formats are `rgb(r, g, b)`, `rgba(r, g, b, a)`, `#rrggbb` and an HTML color name.
   *
   * @alias qui.utils.colors.mix
   * @param {String} strColor1
   * @param {String} strColor2
   * @param {Number} factor the ratio by which the two input colors contribute to the output color; `0` results in 100% of
   * `strColor1` and `1` results in 100% of `strColor2`
   * @returns {String}
   */
  export function mix(strColor1, strColor2, factor) {
      let rgba1 = str2rgba(strColor1)
      let rgba2 = str2rgba(strColor2)
  
      let r = rgba1[0] * (1 - factor) + rgba2[0] * factor
      let g = rgba1[1] * (1 - factor) + rgba2[1] * factor
      let b = rgba1[2] * (1 - factor) + rgba2[2] * factor
      let a = rgba1[3] * (1 - factor) + rgba2[3] * factor
  
      return rgba2str([r, g, b, a])
  }
  
  /**
   * Normalize a color by converting it to the most appropriate string format.
   *
   * Accepted string color formats are `rgb(r, g, b)`, `rgba(r, g, b, a)`, `#rrggbb` and an HTML color name.
   *
   * Colors with alpha factor are represented as `rgba(r, g, b, a)`, while those without alpha factor are represented as
   * `#rrggbb`.
   *
   * @alias qui.utils.colors.normalize
   * @param {String} strColor
   * @returns {String}
   */
  export function normalize(strColor) {
      return rgba2str(str2rgba(strColor))
  }
  