计算 RGB 值的人眼对比度差异的有效方法是什么?

What is the efficient way to calculate human eye contrast difference for RGB values?

为了检查灰度中的两种颜色是否会太接近而无法被人眼区分。如果选择 'dangerous' 颜色,我希望能够向用户发出警告。因此,根据结果,我们可以决定对于视力不好的人,我们是否应该将两种颜色中的一种更改为白色或黑色,以增强可读对比度。 例如,十六进制颜色 #9d5fb0(紫色)和 #318261(绿色)将变成几乎相同的灰色调。在 HSB 中看到,B 值与另一个仅相差 1%,因此健康的人眼无法真正看到差异。或者对于相同的 8 位 K 值,在这种情况下相差 2%。

我了解到亮度法是更复杂的判断灰色调的方法,就像人眼看到颜色的方式一样。然而,如何以编程方式执行此操作超出了我目前的理解。一旦我理解了数学,我就可以写成 PHPJS

为了从 CSS、屏幕 pixel 或作为图像的文件 object 中选取值,我想我们应该始终将输入处理为 RGB,对吧?

类似于:

$result = grayScaleDifference('#9d5fb0','#318261');

$result = 8bitK_difference('#9d5fb0','#318261');

$result = luminanceDifference('#9d5fb0','#318261');

那么在不更改或转换实际图像或颜色对象的情况下比较它们的最佳脚本样式公式是什么?

亮度对比度和感知

您正在寻找的是如何评估亮度对比度。

你绝对是在正确的轨道上——6% 的男性有色盲,他们依赖于亮度对比而不是颜色对比。我有一个 chart here that demonstrates 这个问题。

仅供参考,术语 "luminance" 不是光度。亮度是指 随着时间的推移 发出的光,通常用于天文学。当我们谈论比色法时,我们使用术语亮度,这是一种不同的光量度,由 CIEXYZ (CIE 1931) 定义。

碰巧我正在研究对比度评估方法以提供一些新的更准确的标准。您可以在 GitHub, and on my perception research 页面关注一些进展。

它并不像人们想象的那么简单,因为有许多因素会影响人类对对比度的感知。目前zt的GitHub帖里有很多讨论。

确定亮度

亮度是一种光谱加权但线性的光量度。光谱加权基于人类三色视觉如何感知不同波长的光。这是 CIE 1931 实验和生成的色彩空间(例如 CIEXYZ(亮度是 XYZ 中的 Y))中测量的一部分。

虽然 XYZ 是光的线性模型,但人类的感知非常 non-linear。因此,XYZ 在感知上不是统一的。尽管如此,出于您的目的,您只想知道颜色与灰色块的等效亮度是多少。

假设您开始使用 sRGB 视频(即网络和计算机标准色彩空间),您首先需要删除伽马编码,然后应用光谱加权。

我在 Stack 上发表了很多关于 gamma 的帖子,但如果你想要一个明确的解释,我推荐 Poynton's Gamma FAQ.

将 sRGB 转换为线性 (gamma 1.0)。

1) 转换 通过将每个通道分别除以 255,将 R'G'B' 值从 8 位整数 (0-255) 转换为十进制 (0.0 - 1.0)。 R'G'B' 的值必须在 0 到 1 之间才能进行以下数学运算。此外,here's a link to a post 带有用于将单个数字(如 6 位十六进制)转换为 RGB 通道的代码片段。

2) 线性化 每个通道。懒惰的方法是应用2.2的功率曲线,这是计算机显示器显示图像数据的方式——为了判断颜色的亮度,这样就可以了:

R´^2.2 = Rlin G´^2.2 = Glin B´^2.2 = B

3) ALTERNATE(更准确)的方法:如果你正在做图像处理并在 sRGB 和线性之间来回切换,那么有一个更准确的方法,它是 on wikipedia. 而且,这是我用于类似目的的电子表格中的代码片段:

  =IF( A1 <= 0.04045 ; A1 / 12.92 ; POWER((( A1 + 0.055)/1.055) ; 2.4))

这显示的是对于小于 0.04045 的值,您只需除以 12.92,但对于大于 0.04045 的值,您抵消并应用 2.4 的幂 — 请注意,在 "lazy way" 中我们使用 2.2,但曲线是由于 offset/linearization,几乎完全相同。

执行第 2 步或第 3 步,但不能同时执行。

4) 最后,应用光谱加权系数,将三个通道相加:

Rlin * 0.2126 + Glin * 0.7152 + Blin * 0.0722 = Y

这会给出 Y,即给定颜色的亮度。 亮度也称为 L,但不要与 L 混淆* (Lstar) 这是感知亮度,而不是亮度)。

确定感知对比度

现在,如果你想确定两个样本之间的差异,有很多方法。韦伯对比度本质上是 ΔL/L,自 19 世纪以来一直是标准。但对于计算机显示器显示的刺激,我建议采用一些更现代的方法。例如以下修改以获得更好的感知结果:

(L – L) / (L + 0.1)

还有 "Perceptual Contrast Length," Bowman-Sapolinski 和其他一些,包括我正在处理的一些。您还可以转换为基于人类感知的 CIELAB (L*a*b*),然后您只需从 L*2 中减去 L*1 .

此外,还有许多其他因素会影响对比度感知,例如字体大小和粗细、填充(请参阅 Bartleson–Breneman 环绕效果)和其他因素。

如果您有任何问题,请告诉我。

也许这会有所帮助。 (从尘土飞扬的 olde js 地穴中提取)。

我相信这最初是为了从数学上确定彩色背景上的文本颜色是否真的可读而开发的。

颜色对比

由(WCAG 版本 2)定义

http://www.w3.org/TR/2008/REC-WCAG20-20081211

对比度范围为 1 到 21

第 1.4.3 节

  • 高度可见:(增强)最小对比度为 7 比 1 -- 7:1
  • 普通文本:最小对比度为 4.5 比 1 -- 4.5:1
  • 大文本:最小对比度为 3 比 1 -- 3:1

contrastRatio 函数吐出一个介于 1 和 21 之间的数字,作为比率中的第一个数字。

例如n:1 其中 "n" 是此方法的结果

数字越大,可读性越高。

function getLum(rgb) {

    var i, x;
    var a = []; // so we don't mutate
    for (i = 0; i < rgb.length; i++) {
        x = rgb[i] / 255;
        a[i] = x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
    }
    return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];

}


var RE_HEX_RGB = /[a-f0-9]{6}|[a-f0-9]{3}/i;

function HEX_RGB(str) {
    var match = str.toString(16).match(RE_HEX_RGB);
    if (!match) {
        return [0, 0, 0];
    }

    var colorString = match[0];

    // Expand 3 character shorthand triplet e.g. #FFF -> #FFFFFF
    if (match[0].length === 3) {
        var Astr = colorString.split('');
        for (var i = 0; i < Astr.length; i++) {
            var ch = Astr[i];
            Astr[i] = ch + ch;
        }
        colorString = Astr.join('');
    }

    var integer = parseInt(colorString, 16);

    return [
        (integer >> 16) & 0xFF,
        (integer >> 8) & 0xFF,
        integer & 0xFF
    ];
};


function contrastRatio(rgb1, rgb2) {
    var l1 = getLum(rgb1);
    var l2 = getLum(rgb2);
    return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
}


var c1 = '#9d5fb0';
var c2 = '#318261';

var cr = contrastRatio( HEX_RGB(c1), HEX_RGB(c2) );
console.log("cr", cr);

这是我根据 Myndex 之前写的内容更新的代码。

对于紫色的测试示例,我使用十六进制 #9d5fb0(代表 R:157、G:95、B:176)和绿色我使用十六进制 #318261(代表 R:49,G:130,B:97

JS:

    function HexToRGB(hex) {
      // to allow shorthand input like #FFF or FFFFFF without # sign make it #FFFFFF
      hex = String(hex);
      if(hex.length==3){hex='#'+hex.substr(0, 1)+hex.substr(0, 1)+hex.substr(1, 1)+hex.substr(1, 1)+hex.substr(2, 1)+hex.substr(2, 1);}
      if(hex.length==4){hex='#'+hex.substr(1, 1)+hex.substr(1, 1)+hex.substr(2, 1)+hex.substr(2, 1)+hex.substr(3, 1)+hex.substr(3, 1);}
      if(hex.length==6){hex='#'+hex;}
      let R = parseInt(hex.substr(1, 2),16);
      let G = parseInt(hex.substr(3, 2),16);
      let B = parseInt(hex.substr(5, 2),16);
      console.log("rgb from "+hex+" = "+[R,G,B]);   
      return [R,G,B];
    }

大多数常见文章提到的灰度程序平均方法是:

灰色=圆形((R + G + B) / 3);

JS:

    function RGBToGRAY(rgb) {
      let avg = parseInt((rgb[0]+rgb[1]+rgb[2])/3);
      return [avg,avg,avg];
    }

这会将紫色变成#8f8f8f,因为平均值 = 143

这会将绿色变为#5c5c5c,因为平均值 = 92

92 和 143 之间的差异太大,会错误地通过我的预期测试。 Adobe 的模拟将相同的示例转换为灰度为:

Hex #777777 代表 R:119, G:119, B:119

Hex #747474 代表 R:116, G:116, B:116

116 和 119 之间的差异显然很小,应该无法通过我的预期差异测试。所以这里证明RGBToGRAY方法是不准确的。

现在,正如 Myndex 所解释的那样,我们应该使其呈线性并应用 gamma 2.2 校正。

R´^2.2 = Rlin G´^2.2 = Glin B´^2.2 = Blin

JS:

    function linearFromRGB(rgb) {
      // make it decimal
      let R = rgb[0]/255.0; // red channel decimal
      let G = rgb[1]/255.0; // green channel decimal
      let B = rgb[2]/255.0; // blue channel decimal
      // apply gamma
      let gamma = 2.2;
      R = Math.pow(R, gamma); // linearize red
      G = Math.pow(G, gamma); // linearize green
      B = Math.pow(B, gamma); // linearize blue
      let linear = [R,G,B];
      console.log('linearized rgb = '+linear);  
      return linear;
    }

紫色的伽玛校正线性结果现在是 R:0.3440,G:0.1139,B:0.4423,绿色的结果是 R:0.0265, G:0.2271, B:0.1192

现在通过应用系数获得亮度 L 或(XYZ 标度中的 Y)将是这样的:

Y = Rlin * 0.2126 + Glin * 0.7152 + Blin * 0.0722

JS

    function luminanceFromLin(rgblin) {
      let Y = (0.2126 * (rgblin[0])); // red channel
      Y = Y + (0.7152 * (rgblin[1])); // green channel
      Y = Y + (0.0722 * (rgblin[2])); // blue channel
      console.log('luminance from linear = '+Y);       
      return Y;
    }

现在两个 Y(或 L)值之间的感知对比:

(Llighter – Ldarker) / (Llighter + 0.1)

JS

    function perceivedContrast(Y1,Y2){
      let C = ((Math.max(Y1,Y2)-Math.min(Y1,Y2))/(Math.max(Y1,Y2)+0.1));
      console.log('perceived contrast from '+Y1+','+Y2+' = '+C); 
      return C;      
    }

现在以上所有功能合二为一in/output

    function perceivedContrastFromHex(hex1,hex2){
      let lin1 = linearFromRGB(HexToRGB(hex1));
      let lin2 = linearFromRGB(HexToRGB(hex2));
      let y1 = luminanceFromLin(lin1);
      let y2 = luminanceFromLin(lin2);
      return perceivedContrast(y1,y2);
    }

最后是测试

    var P = perceivedContrastFromHex('#318261','#9d5fb0');
    // compares the purple and green example
    alert(P);
    // shows 0.034369592139888626
    var P = perceivedContrastFromHex('#000','#fff'); 
    // compares pure black and white
    alert(P);
    // shows 0.9090909090909091

FOLLOW-UP 回答

我将此作为后续答案发布,不仅可以阐明我的初始答案(我也刚刚编辑过),还可以添加各种概念的代码片段。 R'G'B'到 Y 过程中的每个步骤都很重要,而且 必须按照描述的顺序 否则结果将失败。

定义:

sRGB:sRGB 是一种三刺激颜色模型,它是 Web 的标准,并用于大多数计算机显示器。它使用与 HDTV 标准 Rec709 相同的原色和白点。 sRGB 与 Rec709 的区别仅在于传输曲线,通常称为伽玛。

Gamma: 这是一条与各种图像编码方法一起使用的曲线,用于存储和传输。它往往类似于人类视觉的感知曲线。在数字中,伽玛的作用是为图像的较暗区域赋予更多权重,以便它们由更多位定义,以避免诸如“条带”之类的伪影。

亮度:(记为 LY):线性测量或表示光(即无伽马曲线)。作为度量,它通常是cd/m2。作为表示,它是 CIEXYZ 中的 Y,通常为 0 (黑色) 到 100 (白色)。亮度具有基于人类对不同波长光的感知的光谱加权。然而,亮度在 lightness/darkness 方面是线性的——也就是说,如果 100 个光子的测量值是 10,那么 20 就是 200 个光子。

L*(又名 Lstar):感知亮度,由 CIELAB (L*a*b*) 定义,其中亮度与光量 L 成线性关系* 基于感知,因此在光量方面是非线性的,曲线旨在匹配人眼的明视觉(大约伽玛为 ^0.43)。

亮度 vs L:* 0 和 100 在两个亮度上 相同 (写成 Y 或 L) 和明度 (写成 L*),但在中间它们有很大不同。我们认为中灰色位于 L* 的正中间 50,但这与亮度 (Y) 中的 18.4 相关。在 sRGB 中是 #777777 或 46.7%。

对比度: 定义两个 L 值或两个 Y 值之间差异的术语。对比度有多种方法和标准。一种常用的方法是韦伯对比,即ΔL/L。对比度通常表示为比率 (3:1) 或百分比 (70%)。

从 sRGB 导出亮度 (Y)

零步(非十六进制)

如果需要,将十六进制颜色值转换为整数值的三元组,其中 #00 = 0#FF = 255

第一步(8 位到十进制)

通过除以 255 将 8 位 sRGB 值转换为十进制:

十进制 = R´8bit / 255 G´十进制 = G´8bit / 255 B´十进制 = B´8bit / 255

如果您的 sRGB 值是 16 位,则除以 65535 转换为十进制。

第二步(线性化,简单版本)

将每个颜色通道提高到 2.2 的幂,与 sRGB 显示器相同。这对大多数应用程序来说都很好。但是,如果您需要多次往返 sRGB 伽马编码 space,请使用下面更准确的版本。

R´^2.2 = Rlin G´^2.2 = Glin B´^2.2 = B

第二步(线性化,精确版本)

如果您要进行图像处理和多次往返伽玛编码 space。

,请使用此版本而不是上面的简单 ^2.2 版本
function sRGBtoLin(colorChannel) {
        // Send this function a decimal sRGB gamma encoded color value
        // between 0.0 and 1.0, and it returns a linearized value.

    if ( colorChannel <= 0.04045 ) {
            return colorChannel / 12.92;
        } else {
            return Math.pow((( colorChannel + 0.055)/1.055),2.4));
        }
    }

编辑以添加说明:我在上面引用的 sRGB 线性化使用了来自官方 IEC 标准的正确阈值,而旧的 WCAG2 数学使用了不正确的阈值阈值(一个已知的,开放的错误)。尽管如此,阈值差异并不影响WCAG 2结果,反而受到其他因素的困扰。

第三步(光谱加权亮度)

正常人眼有三种视锥细胞,分别对红光、绿光和蓝光敏感。但是我们的光谱灵敏度并不统一,因为我们对绿色 (555 nm) 最敏感,而蓝色则遥遥领先。使用以下系数对亮度进行光谱加权以反映这一点:

Rlin * 0.2126 + Glin * 0.7152 + Blin * 0.0722 = Y = L

将每个线性化颜色通道乘以它们的系数,然后将它们加在一起得到 L,亮度。

第四步(对比度确定)

确定对比度的方法有很多种,标准也各不相同。根据具体应用,某些方程式比其他方程式更有效。

WCAG2.x
WCAG 2.0 和 2.1 中列出的当前网页对比度指南是带有偏移量的简单对比:

C = ((L浅色 + 0.05) / (L深色 + 0.05)) : 1

这给出了一个比率,WCAG 指定 3:1 为 non-text,文本为 4.5:1 以满足“AA”级别。

但是,由于各种原因,它是一个薄弱的例子。我在记录中指出了当前 GitHub 问题 (WCAG #695) 中的缺陷,并且一直在研究替代方案。

编辑添加(2021 年 1 月):

旧 WCAG 2 对比的替代品是 APCA:

"A高级P感知C反差A算法

新 WCAG 3 的一部分。这是一个实质性的飞跃。虽然稳定,但我仍然认为它是测试版,并且因为它有点复杂,暂时 link 到 SAPC/APCA GitHub repo 可能更好。

文献中其他一些先前开发的对比方法:

修正韦伯
Hwang/Peli Modified Weber 提供了更好的对比度评估,因为它适用于计算机显示器/sRGB。

C = (L浅色 – L深色) / (L浅色 + 0.1)

请注意,根据最近的一些实验,我选择了 0.1 而不是 0.05 的耀斑因子。不过该值尚未确定,不同的值可能会更好。

实验室差异
我碰巧比其他人更喜欢的另一种选择是将线性化亮度 (L) 转换为 L*,即感知亮度,然后减去一个从另一个找不同。

将 Y 转换为 L*:

function YtoLstar(Y) {
        // Send this function a luminance value between 0.0 and 1.0,
        // and it returns L* - perceptual lightness

    if ( Y <= (216/24389) {       // The CIE standard states 0.008856 but 216/24389 is the intent for 0.008856451679036
            return Y * (24389/27);  // The CIE standard states 903.3, but 24389/27 is the intent, making 903.296296296296296
        } else {
            return Math.pow(Y,(1/3)) * 116 - 16;
        }
    }

将 L 转换为 L* 后,有用的对比图就是:

C = L浅色 – L深色**

此处的结果可能需要缩放以与其他方法相似。大约 1.6 或 1.7 的缩放比例似乎效果不错。

还有许多其他方法可以确定对比度,但这些是最常见的。不过,有些应用程序会使用其他对比方法做得更好。其他一些是迈克尔逊对比度、感知对比度长度 (PCL) 和 Bowman/Sapolinski.

此外,如果您正在寻找 颜色差异 除了亮度或亮度差异,那么 CIELAB 在这方面有一些有用的方法。

旁注:

平均 RGB No Bueno!

OP 2x2p 提到了一个常被引用的用于制作颜色灰度的方程式:

灰色 = 圆形((R + G + B) / 3);

他指出它看起来多么不准确,事实上 — 它是完全错误的。 R、G 和 B 的光谱权重很大,不容忽视。绿色的亮度比蓝色高 数量级。 您不能将所有三个通道相加并除以三,并得到接近特定颜色实际亮度的值。

我相信对此的混淆可能来自称为 HSI (色调、饱和度、强度) 的颜色控制。但是这种控制不是(而且从来没有打算)在感知上是统一的!!! HSI 和 HSV 一样,只是在计算机中操作颜色值的“便利”。两者在感知上都不统一,它们使用的数学严格来说是为了支持一种在软件中调整颜色值的“简单”方法。

OP 的样本颜色

2x2p 使用“#318261”、“#9d5fb0”作为测试颜色发布了他的代码。下面是它们在我的电子表格中的样子,以及转换过程中每一步中的每个值(使用“准确的”sRGB 方法):

两者都接近#777777 的中间灰色。另请注意,虽然亮度 L 仅为 18,但感知亮度 L* 为 50。

为了更好的语法和易用性,我将整个理论放入一个对象内的单个解析器中,该解析器按如下方式运行。

解析器将从颜色 318261:

一步计算出这些值

返回的对象如下所示:

hex: "#318261"
rgb: {
  r: 49,
  g: 130,
  b: 97
}
int: 10313648
dec: {
  r: 0.19215686274509805,
  g: 0.5098039215686274,
  b: 0.3803921568627451
}
lin: {
  r: 0.030713443732993635,
  g: 0.2232279573168085,
  b: 0.11953842798834562
}
y: 0.17481298771137443
lstar: 48.86083783595441

JavaScript 可以以十六进制颜色字符串作为参数调用对象内部解析器。十六进制字符串可以看起来像 000#000000000#000000。有两种方法可以处理结果。

A:将返回的对象作为一个整体放入一个变量中:

var result = Color_Parser.parseHex('318261');
var lstar = result.lstar;

B: 解析一次,然后访问上次解析器结果的部分内容。例如只选择 L* 对比度值将是:

Color_Parser.parseHex('#ABC');
var lstar = Color_Parser.result.lstar;

完整代码如下:

const Color_Parser = {
  version: '1.0.0.beta',
  name: 'Color_Parser',
  result: null, // the parser output
  loging: true, // set to false to disable writing each step to console log
  parseHex: function(_input) {
    if (this.loging) {
      console.log(this.name + ', input: ' + _input);
    }
    this.result = {};
    // pre flight checks
    if (!_input) {
      this.result.error = true;
      console.log(this.name + ', error');
      return this.result;
    }
    // first convert shorthand Hex strings to full strings
    this.result.hex = String(_input);
    if (this.result.hex.length == 3) {
      this.result.hex = '#' + this.result.hex.substr(0, 1) + this.result.hex.substr(0, 1) + this.result.hex.substr(1, 1) + this.result.hex.substr(1, 1) + this.result.hex.substr(2, 1) + this.result.hex.substr(2, 1);
    }
    if (this.result.hex.length == 4) {
      this.result.hex = '#' + this.result.hex.substr(1, 1) + this.result.hex.substr(1, 1) + this.result.hex.substr(2, 1) + this.result.hex.substr(2, 1) + this.result.hex.substr(3, 1) + this.result.hex.substr(3, 1);
    }
    if (this.result.hex.length == 6) {
      this.result.hex = '#' + this.result.hex;
    }
    if (this.loging) {
      console.log(this.name + ', added to result: ' + this.result.hex);
    }
    // second get int values from the string segments as channels
    this.result.rgb = {
      r: null,
      g: null,
      b: null
    };
    this.result.rgb.r = parseInt(this.result.hex.substr(1, 2), 16);
    this.result.rgb.g = parseInt(this.result.hex.substr(3, 2), 16);
    this.result.rgb.b = parseInt(this.result.hex.substr(5, 2), 16);
    if (this.loging) {
      console.log(this.name + ', added to result: ' + this.result.rgb);
    }
    // third get the combined color int value
    this.result.int = ((this.result.rgb.r & 0x0ff) << 16) | ((this.result.rgb.g & 0x0ff) << 8) | (this.result.rgb.b & 0x0ff);
    if (this.loging) {
      console.log(this.name + ', added to result: ' + this.result.int);
    }
    // fourth turn 8 bit channels to decimal
    this.result.dec = {
      r: null,
      g: null,
      b: null
    };
    this.result.dec.r = this.result.rgb.r / 255.0; // red channel to decimal
    this.result.dec.g = this.result.rgb.g / 255.0; // green channel to decimal
    this.result.dec.b = this.result.rgb.b / 255.0; // blue channel to decimal
    if (this.loging) {
      console.log(this.name + ', added to result: ' + this.result.dec);
    }
    // fifth linearize each channel
    this.result.lin = {
      r: null,
      g: null,
      b: null
    };
    for (var i = 0, len = 3; i < len; i++) {
      if (this.result.dec[['r', 'g', 'b'][i]] <= 0.04045) {
        this.result.lin[['r', 'g', 'b'][i]] = this.result.dec[['r', 'g', 'b'][i]] / 12.92;
      } else {
        this.result.lin[['r', 'g', 'b'][i]] = Math.pow(((this.result.dec[['r', 'g', 'b'][i]] + 0.055) / 1.055), 2.4);
      }
    }
    if (this.loging) {
      console.log(this.name + ', added to result: ' + this.result.lin);
    }
    // get Y from linear result
    this.result.y = (0.2126 * (this.result.lin.r)); // red channel
    this.result.y += (0.7152 * (this.result.lin.g)); // green channel
    this.result.y += (0.0722 * (this.result.lin.b)); // blue channel
    if (this.loging) {
      console.log(this.name + ', added to result: ' + this.result.y);
    }
    // get L* contrast from Y 
    if (this.result.y <= (216 / 24389)) {
      this.result.lstar = this.result.y * (24389 / 27);
    } else {
      this.result.lstar = Math.pow(this.result.y, (1 / 3)) * 116 - 16;
    }
    if (this.loging) {
      console.log(this.name + ', added to result: ' + this.result.lstar);
    }
    // compute grayscale is to be continued hereafter
    // compute inverted rgb color
    this.result.invert = {
      r: null,
      g: null,
      b: null,
      hex: null
    };
    this.result.invert.r = (255 - this.result.rgb.r);
    this.result.invert.g = (255 - this.result.rgb.g);
    this.result.invert.b = (255 - this.result.rgb.b);
    // reverse compute hex from inverted rgb          
    this.result.invert.hex = this.result.invert.b.toString(16); // begin with blue channel
    if (this.result.invert.hex.length < 2) {
      this.result.invert.hex = '0' + this.result.invert.hex;
    }
    this.result.invert.hex = this.result.invert.g.toString(16) + this.result.invert.hex;
    if (this.result.invert.hex.length < 4) {
      this.result.invert.hex = '0' + this.result.invert.hex;
    }
    this.result.invert.hex = this.result.invert.r.toString(16) + this.result.invert.hex;
    if (this.result.invert.hex.length < 6) {
      this.result.invert.hex = '0' + this.result.invert.hex;
    }
    this.result.invert.hex = '#' + this.result.invert.hex;
    this.result.error = false;
    if (this.loging) {
      console.log(this.name + ', final output:');
    }
    if (this.loging) {
      console.log(this.result);
    }
    return this.result;
  }
}