*suggestion* a better Colour::contrasting


#1

this respects that blue is a “darker” colour than green

float getWeightedBrightness( const Colour& col )
{
	return sqrt(	col.getFloatRed() * col.getFloatRed() * .241f + 
		col.getFloatGreen() * col.getFloatGreen() * .691f + 
		col.getFloatBlue() * col.getFloatBlue() * .068f);
}

Colour Colour::contrasting (const float amount) const noexcept
{
	return overlaidWith ( getWeightedBrightness(*this) > 0.5f
							? Colours::black
							: Colours::white).withAlpha (amount);
                    
}

#2

Interesting… What’s your source for the numbers?


#3

ooh, thats a long time ago, mmmmhh google :wink: http://alienryderflex.com/hsp.html


#4

Cool, I like that idea!


#5

oops, there was a little mistake (missing brackets… (alpha had no effect))

return overlaidWith (( getWeightedBrightness(*this) > 0.5f ? Colours::black : Colours::white).withAlpha (amount));


#6

I like converting RGB to XYZ to Lab (perceptually linear) and doing my operations in Lab space and then going back to RGB. In Lab space, adding a constant to L will produce a perceptually equal change no matter what the starting value.

.h

// Copyright (C) 2008-2011 by Vincent Falco, All rights reserved worldwide.
// This file is released under the MIT License:
// http://www.opensource.org/licenses/mit-license.php

class XYZColour
{
public:
  XYZColour ();
  XYZColour (float x, float y, float z);
  XYZColour (float x, float y, float z, float alpha);
  XYZColour (XYZColour const& xyz);
  XYZColour (Colour const& sRGB);

  XYZColour& operator= (XYZColour const& other);

  float getX () const { return m_x; }
  float getY () const { return m_y; }
  float getZ () const { return m_z; }
  float getAlpha () const { return m_alpha; }

  Colour const toRGB () const;

private:
  static XYZColour const from (Colour const& sRGB);

private:
  float m_x;
  float m_y;
  float m_z;
  float m_alpha;
};

//------------------------------------------------------------------------------

class LabColour
{
public:
  LabColour ();
  LabColour (float L, float a, float b);
  LabColour (float L, float a, float b, float alpha);
  LabColour (LabColour const& lab);
  LabColour (Colour const& sRGB);
  LabColour (XYZColour const& xyz);

  float getL () const { return m_L; }
  float getA () const { return m_a; }
  float getB () const { return m_b; }
  float getAlpha () const { return m_alpha; }

  XYZColour const toXYZ () const;
  Colour const toRGB () const;

  LabColour const withLuminance (float L) const; // L [0,1]
  LabColour const withAddedLuminance (float amount) const; // amount [0,1]
  LabColour const withMultipliedColour (float amount) const;

private:
  static LabColour const from (XYZColour const& xyz);

private:
  float m_L;
  float m_a;
  float m_b;
  float m_alpha;
};

.cpp

[code]// Copyright © 2008-2011 by Vincent Falco, All rights reserved worldwide.
// This file is released under the MIT License:
// http://www.opensource.org/licenses/mit-license.php

XYZColour::XYZColour ()
: m_x (0)
, m_y (0)
, m_z (0)
, m_alpha (0)
{
}

XYZColour::XYZColour (float x, float y, float z)
: m_x (x)
, m_y (y)
, m_z (z)
, m_alpha (1)
{
}

XYZColour::XYZColour (float x, float y, float z, float alpha)
: m_x (x)
, m_y (y)
, m_z (z)
, m_alpha (alpha)
{
}

XYZColour::XYZColour (XYZColour const& xyz)
: m_x (xyz.m_x)
, m_y (xyz.m_y)
, m_z (xyz.m_z)
, m_alpha (xyz.m_alpha)
{
}

XYZColour::XYZColour (Colour const& sRGB)
{
*this = from (sRGB);
}

XYZColour& XYZColour::operator= (XYZColour const& other)
{
m_x = other.m_x;
m_y = other.m_y;
m_z = other.m_z;
m_alpha = other.m_alpha;

return *this;
}

XYZColour const XYZColour::from (Colour const& sRGB)
{
float r = sRGB.getRed () / 255.f;
float g = sRGB.getGreen () / 255.f;
float b = sRGB.getBlue () / 255.f;

if (r > 0.04045f) r = 100 * pow ((r + 0.055) / 1.055, 2.4); else r = r / 12.92;
if (g > 0.04045f) g = 100 * pow ((g + 0.055) / 1.055, 2.4); else g = g / 12.92;
if (b > 0.04045f) b = 100 * pow ((b + 0.055) / 1.055, 2.4); else b = b / 12.92;

// D65
float x = r * 0.4124 + g * 0.3576 + b * 0.1805;
float y = r * 0.2126 + g * 0.7152 + b * 0.0722;
float z = r * 0.0193 + g * 0.1192 + b * 0.9505;

return XYZColour (x, y, z, sRGB.getAlpha() / 255.f);
}

Colour const XYZColour::toRGB () const
{
float x = m_x / 100;
float y = m_y / 100;
float z = m_z / 100;

float r = x * 3.2406 + y * -1.5372 + z * -0.4986;
float g = x * -0.9689 + y * 1.8758 + z * 0.0415;
float b = x * 0.0557 + y * -0.2040 + z * 1.0570;

if (r > 0.0031308) r = 1.055 * pow (double ®, 1/2.4) - 0.055; else r = 12.92 * r;
if (g > 0.0031308) g = 1.055 * pow (double (g), 1/2.4) - 0.055; else g = 12.92 * g;
if (b > 0.0031308) b = 1.055 * pow (double (b), 1/2.4) - 0.055; else b = 12.92 * b;

return Colour::fromRGBAFloat (
jlimit (0, 255, int (r * 255 + .5f)),
jlimit (0, 255, int (g * 255 + .5f)),
jlimit (0, 255, int (b * 255 + .5f)),
m_alpha);
}

//------------------------------------------------------------------------------

LabColour::LabColour ()
: m_L (0)
, m_a (0)
, m_b (0)
, m_alpha (0)
{
}

LabColour::LabColour (float L, float a, float b)
: m_L (L)
, m_a (a)
, m_b (b)
, m_alpha (1)
{
}

LabColour::LabColour (float L, float a, float b, float alpha)
: m_L (L)
, m_a (a)
, m_b (b)
, m_alpha (alpha)
{
}

LabColour::LabColour (LabColour const& lab)
: m_L (lab.m_L)
, m_a (lab.m_a)
, m_b (lab.m_b)
, m_alpha (lab.m_alpha)
{
}

LabColour::LabColour (Colour const& sRGB)
{
*this = from (sRGB);
}

LabColour::LabColour (XYZColour const& xyz)
{
*this = from (xyz);
}

XYZColour const LabColour::toXYZ () const
{
// D65, Observer= 2°
float const x0 = 95.047f;
float const y0 = 100.000f;
float const z0 = 108.883f;

float y = (m_L + 16) / 116;
float x = m_a / 500 + y;
float z = y - m_b / 200;

float t;

if ((t = pow (x, 3)) > 0.008856) x = t; else x = (x - 16./116) / 7.787;
if ((t = pow (y, 3)) > 0.008856) y = t; else y = (y - 16./116) / 7.787;
if ((t = pow (z, 3)) > 0.008856) z = t; else z = (z - 16./116) / 7.787;

x *= x0;
y *= y0;
z *= z0;

return XYZColour (x, y, z, m_alpha);
}

Colour const LabColour::toRGB () const
{
return toXYZ ().toRGB ();
}

LabColour const LabColour::from (XYZColour const& xyz)
{
// D65, Observer= 2°
float const x0 = 95.047f;
float const y0 = 100.000f;
float const z0 = 108.883f;

float x = (xyz.getX () / x0);
float y = (xyz.getY () / y0);
float z = (xyz.getZ () / z0);

x = (x > 0.008856) ? pow (x, 1.f/3) : ((7.7787 * x) + 16/116);
y = (y > 0.008856) ? pow (y, 1.f/3) : ((7.7787 * y) + 16/116);
z = (z > 0.008856) ? pow (z, 1.f/3) : ((7.7787 * z) + 16/116);

float const L = (116 * y) - 16;
float const a = 500 * (x - y);
float const b = 200 * (y - z);

return LabColour (L, a, b, xyz.getAlpha ());
}

LabColour const LabColour::withLuminance (float L) const
{
return LabColour (
jlimit (0.f, 100.f, L),
getA (),
getB (),
getAlpha ());
}

LabColour const LabColour::withAddedLuminance (float amount) const
{
return LabColour (
jlimit (0.f, 100.f, getL() + amount * 100),
getA (),
getB (),
getAlpha ());
}

LabColour const LabColour::withMultipliedColour (float amount) const
{
return LabColour (
getL (),
getA () * amount,
getB () * amount,
getAlpha ());
}

[/code]

This is wonderful for doing things like, hiliting a control during a mouseover by making it brighter - no matter what the underlying RGB color, adding a small constant to it using Lab space always makes it appear brighter to the user by the same perceptual amount.


#7

Nice idea, I like that (used to deal with YUV colorspace and log based modification, but your solution looks better).


#8

Vinn:

works nice, but i changed it a little bit because of type conversion warnings (float<->double conversions) and there was a integer division which should be a float division i think?! ( 16./116 )

[code]XYZColour::XYZColour ()
: m_x (0)
, m_y (0)
, m_z (0)
, m_alpha (0)
{
}

XYZColour::XYZColour (float x, float y, float z)
: m_x (x)
, m_y (y)
, m_z (z)
, m_alpha (1)
{
}

XYZColour::XYZColour (float x, float y, float z, float alpha)
: m_x (x)
, m_y (y)
, m_z (z)
, m_alpha (alpha)
{
}

XYZColour::XYZColour (XYZColour const& xyz)
: m_x (xyz.m_x)
, m_y (xyz.m_y)
, m_z (xyz.m_z)
, m_alpha (xyz.m_alpha)
{
}

XYZColour::XYZColour (Colour const& sRGB)
{
*this = from (sRGB);
}

XYZColour& XYZColour::operator= (XYZColour const& other)
{
m_x = other.m_x;
m_y = other.m_y;
m_z = other.m_z;
m_alpha = other.m_alpha;

return *this;

}

XYZColour const XYZColour::from (Colour const& sRGB)
{
float r = sRGB.getRed () / 255.f;
float g = sRGB.getGreen () / 255.f;
float b = sRGB.getBlue () / 255.f;

if (r > 0.04045f) r = 100 * pow ((r + 0.055f) / 1.055f, 2.4f); else r = r / 12.92f;
if (g > 0.04045f) g = 100 * pow ((g + 0.055f) / 1.055f, 2.4f); else g = g / 12.92f;
if (b > 0.04045f) b = 100 * pow ((b + 0.055f) / 1.055f, 2.4f); else b = b / 12.92f;

// D65
float x = r * 0.4124f + g * 0.3576f + b * 0.1805f;
float y = r * 0.2126f + g * 0.7152f + b * 0.0722f;
float z = r * 0.0193f + g * 0.1192f + b * 0.9505f;

return XYZColour (x, y, z, sRGB.getAlpha() / 255.f);

}

Colour const XYZColour::toRGB () const
{
float x = m_x / 100;
float y = m_y / 100;
float z = m_z / 100;

float r = x *  3.2406f + y * -1.5372f + z * -0.4986f;
float g = x * -0.9689f + y *  1.8758f + z *  0.0415f;
float b = x *  0.0557f + y * -0.2040f + z *  1.0570f;

if (r > 0.0031308) r = 1.055f * pow (r, 1/2.4f) - 0.055f; else r = 12.92f * r;
if (g > 0.0031308) g = 1.055f * pow ( g, 1/2.4f) - 0.055f; else g = 12.92f * g;
if (b > 0.0031308) b = 1.055f * pow ( b, 1/2.4f) - 0.055f; else b = 12.92f * b;

return Colour::fromRGBAFloat (
	(uint8)jlimit (0, 255, int (r * 255 + .5f)),
	(uint8)jlimit (0, 255, int (g * 255 + .5f)),
	(uint8)jlimit (0, 255, int (b * 255 + .5f)),
	m_alpha);

}

//------------------------------------------------------------------------------

LabColour::LabColour ()
: m_L (0)
, m_a (0)
, m_b (0)
, m_alpha (0)
{
}

LabColour::LabColour (float L, float a, float b)
: m_L (L)
, m_a (a)
, m_b (b)
, m_alpha (1)
{
}

LabColour::LabColour (float L, float a, float b, float alpha)
: m_L (L)
, m_a (a)
, m_b (b)
, m_alpha (alpha)
{
}

LabColour::LabColour (LabColour const& lab)
: m_L (lab.m_L)
, m_a (lab.m_a)
, m_b (lab.m_b)
, m_alpha (lab.m_alpha)
{
}

LabColour::LabColour (Colour const& sRGB)
{
*this = from (sRGB);
}

LabColour::LabColour (XYZColour const& xyz)
{
*this = from (xyz);
}

XYZColour const LabColour::toXYZ () const
{
// D65, Observer= 2°
float const x0 = 95.047f;
float const y0 = 100.000f;
float const z0 = 108.883f;

float y = (m_L + 16) / 116;
float x = m_a / 500 + y;
float z = y - m_b / 200;

float t;

if ((t = pow (x, 3.f)) > 0.008856f) x = t; else x = (x - 16.f/116.f) / 7.787f;
if ((t = pow (y, 3.f)) > 0.008856f) y = t; else y = (y - 16.f/116.f) / 7.787f;
if ((t = pow (z, 3.f)) > 0.008856f) z = t; else z = (z - 16.f/116.f) / 7.787f;

x *= x0;
y *= y0;
z *= z0;

return XYZColour (x, y, z, m_alpha);

}

Colour const LabColour::toRGB () const
{
return toXYZ ().toRGB ();
}

LabColour const LabColour::from (XYZColour const& xyz)
{
// D65, Observer= 2°
float const x0 = 95.047f;
float const y0 = 100.000f;
float const z0 = 108.883f;

float x = (xyz.getX () / x0);
float y = (xyz.getY () / y0);
float z = (xyz.getZ () / z0);

x = (x > 0.008856) ? pow (x, 1.f/3.f) : ((7.7787f * x) + 16/116.f);
y = (y > 0.008856) ? pow (y, 1.f/3.f) : ((7.7787f * y) + 16/116.f);
z = (z > 0.008856) ? pow (z, 1.f/3.f) : ((7.7787f * z) + 16/116.f);

float const L = (116 * y) - 16;
float const a = 500 * (x - y);
float const b = 200 * (y - z);

return LabColour (L, a, b, xyz.getAlpha ());

}

LabColour const LabColour::withLuminance (float L) const
{
return LabColour (
jlimit (0.f, 100.f, L),
getA (),
getB (),
getAlpha ());
}

LabColour const LabColour::withAddedLuminance (float amount) const
{
return LabColour (
jlimit (0.f, 100.f, getL() + amount * 100),
getA (),
getB (),
getAlpha ());
}

LabColour const LabColour::withMultipliedColour (float amount) const
{
return LabColour (
getL (),
getA () * amount,
getB () * amount,
getAlpha ());
}
[/code]


#9

good catch!


#10

not really, just warning level 4 and treat warnings as errors with VS2008 :wink:


#11

I rolled LabColour and XYZColour into VFLib along with some documentation: