Algorithm to convert film negative RGB to positive RGB - c++

Assuming I have a photographic film negative scanned as an RGB image, I'm trying to find an algorithm that will convert the color values to an RGB positive.
Due to the orange bias ( http://photo.net/learn/orange-negative-mask ) if I simply say redPositive = 255 - redNegative I get a final image that has a strong cyan tint to it, and is very washed out. That means the answers given here: Convert negative image to positive are NOT correct.
So how would I craft the following routine:
struct RGB
{
unsigned byte red;
unsigned byte green;
unsigned byte blue;
};
void FilmNegativeToPositive(RGB const &negative, RGB &positive)
{
// What goes here?
}

I don’t have data to test, but according to the link you gave, the negative is a mixture of cyan, magenta and yellow dyes that are impure:
The yellow dye layer is the most pure. The magenta dye layer has a noticeable amount of yellow in it. The cyan dye layer has noticeable amounts of both yellow and magenta in it.
Therefore, you want to do something like this (untested pseudocode):
Let I_MY be the ratio of yellow impurity to pure magenta dye
Let I_CY be the ratio of yellow impurity to pure cyan dye
Let I_CM be the ratio of magenta impurity to pure cyan dye
Given R, G, B in [0, 255]
Convert to CMY:
C = 1.0 - R/255.0
M1 = 1.0 - G/255.0
Y1 = 1.0 - B/255.0
Calculate the impurities in the cyan dye and remove them, since we assume no other dye has cyan impurities:
M = M1 - I_CM×C
Y2 = Y1 - I_CY×C
Now the amount of magenta dye is correct, so subtract its yellow impurity:
Y = Y2 - I_MY×M
Convert the corrected CMY values back to RGB:
R' = 255×(1.0-C)
G' = 255×(1.0-M)
B' = 255×(1.0-Y)
If it turns out there’s more complicated contamination than that, you get a linear algebra problem:
[ 1 I_MC I_YC] [C'] [C]
[I_CM 1 I_YM] × [M'] = [M]
[I_CY I_MY 1] [Y'] [Y]
Where you want to solve for C', M', and Y', then convert back to the RGB color space.

Related

Map a pixel color found in OpenCV to a pre-determined list of colors

I have a scenario where I have obtained one or more colors from an image, but now I need to determine which one of my existing color options it is closest to.
For example, I may have red(255,0,0), green(0,255,0) and blue(0,0,255) as my three choices, but the image may contain orange(255,165,0).
What I need then is a way to determine which one of those three values I should choose as my output color to replace orange.
One approach I have considered is to measure the range from those three values and see which one is the smallest & select that color.
Example:
orange -> red
abs(255 - 255) = 0, abs(165 - 0) = 165, abs(0 - 0) = 0
0 + 165 + 0 = 165
orange -> green
abs(255 - 0) = 255, abs(165 - 255) = 90, abs(0 - 0) = 0
255 + 90 + 0 = 345
orange -> blue
abs(255 - 0) = 255, abs(165 - 0) = 165, abs(0 - 255) = 255
255 + 165 + 255 = 675
Under this approach, I would pick red.
However, I am not sure if this is the best, or even a particularly valid, one so was wondering if there is something out there that is more accurate & would scale better to an increased color pallete.
Update The reduction answer linked in here does not help as it reduces things across the board. I need the ability to link a broad range of colors to several specific options.
I think you should represent and compare colors in different color space. I suggest space, that represent human color perception. Therefore L*a*b color space will be the best.
https://en.wikipedia.org/wiki/Lab_color_space/
Color distances in that coordinate space are represented by delta e value. You could find different standards for delta e below:
https://en.wikipedia.org/wiki/Color_difference#CIELAB_Delta_E.2A/
In order to change color space you have to use cv::cvtColor() method. Color conversion for single pixel is described below:
https://stackoverflow.com/a/35737319/8682088/
After calculating pixel coordinates in L*a*b space, you could easily calculate delta e and compare colors with any reference and pick the one with the smallest error.

The order of list elements seems to be changing

I am trying to make a little script to switch the rgb values of an image. The code I have works to alter the image, but the list of images is out of order when compared to the list of permutations. I have been unable to figure out why the images at indices 3 and 4 are switched in order.
from skimage import io
import numpy as np
import itertools
def create_permutations_auto(image):
im_col = np.split(image, 3, axis = -1)
color_keys = ["red", "green", "blue"]
colors = dict(zip(color_keys, im_col))
cp = list(itertools.permutations(color_keys))
im_list = []
for perm in cp:
color_data = [colors.get(k) for k in perm]
image = np.squeeze(np.stack(color_data, axis = -1))
im_list.append(image)
return cp, im_list
orig = io.imread(filename)
text, image_perms = create_permutations_auto(orig)
for i in range(len(image_perms)):
print text[i]
io.imshow(image_perms[i])
io.imsave(filepath + "{}_{}_{}.png".format(text[i][0], text[i][1], text[i][2]), image_perms[i])
io.show()
I would expect this code to output the original image with the green values replacing the red, the blue values replacing the green and the red values replacing the blue for the fourth image created. However, what I get is blue → red, red → green, and green → blue. The fifth image created seems like it should be the fourth.
Third Image (should be green, blue, red):
Fourth Image(should be blue, red, green):
To see if it was the something to do with the order of the dictionary, I tried to do the permutations manually. Using the following:
def create_permutations(image):
red, green, blue = np.split(image, 3, axis = -1)
perms = []
perms.append(np.squeeze(np.stack((red, blue, green), axis = -1)))
perms.append(np.squeeze(np.stack((green, red, blue), axis = -1)))
perms.append(np.squeeze(np.stack((green, blue, red), axis = -1)))
perms.append(np.squeeze(np.stack((blue, green, red), axis = -1)))
perms.append(np.squeeze(np.stack((blue, red, green), axis = -1)))
return perms
This code also seems to switch the placements of the two same permutations although it is the images at index 2 and 4. For this simple example, I can switch them around but it seems like I am missing something fundamental here. I am using a Python(x,y) distribution with Python 2.7.10 and numpy 1.12.1 on a machine running windows 10.
The effect of spectral permutation is different from that of spatial permutation. When you split the original image into its chromatic bands, referred to as red, green, and blue, and then you rearrange them as blue, red, and green, the order of the colors from left to right in the resulting image is green-blue-red (spectral permutation) rather than blue-red-green (spatial permutation).
The figure below is intended to schematically explain such a counter-intuitive result. For the sake of clarity let us denote the red, green, and blue chromatic channels with indices 0, 1, and 2, respectively. It clearly emerges from the figure that the intensities of the pixels in the leftmost region of the image are transformed through permutattion from [255, 0, 0] to [0, 255, 0], Indeed, in the leftmost region of the permuted image pixels' intesities are 0 for channel 0 (red component), 255 for channel 1 (green component), and 0 for channel 2 (blue component). That's the reason why the color of the leftmost region of the image changes from red to green. Similar arguments apply to the central and rightmost regions.
Bonus
You could simplify your function like this:
def create_permutations_auto(img):
colors = {0: 'red', 1: 'green', 2: 'blue'}
index_perm = list(itertools.permutations(colors.keys()))
cp = [tuple(colors[i] for i in perm) for perm in index_perm]
im_list = [np.dstack([img[:, :, i] for i in perm]) for perm in index_perm]
return cp, im_list

C++ Color calculation from grayscape to color

I have a program that generates an grayscale image. The grayshading for every pixel is set with the following code sniped:
//loop over all pixel
*PixelColorPointer = Number * 0x010101;
in this case the Number is an integer number between 0 and 255. Which generates all grayscale colors from black to white.
What I try to do is have an colored image (in order to have false colors), but I don't really understand the calculation with the hex value. I figured out if I assign e.g. Number * 0xFFFFFF I have a gradient/variety from white to yellow.
Can someone explain me how the calculation of this colors is working? Please remember that (as said already) I want/have to pass the Number variable to get variety.
RGB color are stored byte by byte (0 to 255).
When you say 0x010203 it is (in hex) 01 red, 02 green, and 03 blue. It can also be inverted (03 red, 02 green, 01 blue) depending on your Endianness.
Here you have to separate your 3 colors. Then you shoud multiply every color by it's coefficient.
You can store your color in an union, it's the easiest way.
union Color
{
struct
{
unsigned char b;
unsigned char g;
unsigned char r;
unsigned char a; // Useless here
};
unsigned int full;
};
Color c;
c.full = 0x102030;
unsigned char res = 0.229 * c.r + 0.587 * c.g + 0.114 * c.b;
Color grey;
grey.r = grey.g = grey.b = res;
0.229, 0.587 and 0.114 are the Relative luminance.
Remember, you might need to invert the rgba order and move the a place :)
You need to give a little more information. What library of function are you using to do this??
But by just seeing the problems I think the hexadecimal number refers to the color in the Color-hex code, the Number would refer to the brightness 0 being blank and 255 max color intensity (white)
EDIT: Actually I think the whole number resulting from:
Number * 0x010101
Is the hexadecimal color code, in the particular case of 0x010101 the Number works as the intensity. But any other hexadecimal would give you some weird result.
Use an Color-hex code table choose any random color and just input :
*PixelColorPointer = 0XHEXCODE;
if the output is the desired color then I'm right

How do you calculate a "highlight color"? [duplicate]

Given a system (a website for instance) that lets a user customize the background color for some section but not the font color (to keep number of options to a minimum), is there a way to programmatically determine if a "light" or "dark" font color is necessary?
I'm sure there is some algorithm, but I don't know enough about colors, luminosity, etc to figure it out on my own.
I encountered similar problem. I had to find a good method of selecting contrastive font color to display text labels on colorscales/heatmaps. It had to be universal method and generated color had to be "good looking", which means that simple generating complementary color was not good solution - sometimes it generated strange, very intensive colors that were hard to watch and read.
After long hours of testing and trying to solve this problem, I found out that the best solution is to select white font for "dark" colors, and black font for "bright" colors.
Here's an example of function I am using in C#:
Color ContrastColor(Color color)
{
int d = 0;
// Counting the perceptive luminance - human eye favors green color...
double luminance = (0.299 * color.R + 0.587 * color.G + 0.114 * color.B)/255;
if (luminance > 0.5)
d = 0; // bright colors - black font
else
d = 255; // dark colors - white font
return Color.FromArgb(d, d, d);
}
This was tested for many various colorscales (rainbow, grayscale, heat, ice, and many others) and is the only "universal" method I found out.
Edit
Changed the formula of counting a to "perceptive luminance" - it really looks better! Already implemented it in my software, looks great.
Edit 2
#WebSeed provided a great working example of this algorithm: http://codepen.io/WebSeed/full/pvgqEq/
Based on Gacek's answer but directly returning color constants (additional modifications see below):
public Color ContrastColor(Color iColor)
{
// Calculate the perceptive luminance (aka luma) - human eye favors green color...
double luma = ((0.299 * iColor.R) + (0.587 * iColor.G) + (0.114 * iColor.B)) / 255;
// Return black for bright colors, white for dark colors
return luma > 0.5 ? Color.Black : Color.White;
}
Note: I removed the inversion of the luma value to make bright colors have a higher value, what seems more natural to me and is also the 'default' calculation method.
(Edit: This has since been adopted in the original answer, too)
I used the same constants as Gacek from here since they worked great for me.
You can also implement this as an Extension Method using the following signature:
public static Color ContrastColor(this Color iColor)
You can then easily call it via
foregroundColor = backgroundColor.ContrastColor().
Thank you #Gacek. Here's a version for Android:
#ColorInt
public static int getContrastColor(#ColorInt int color) {
// Counting the perceptive luminance - human eye favors green color...
double a = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
int d;
if (a < 0.5) {
d = 0; // bright colors - black font
} else {
d = 255; // dark colors - white font
}
return Color.rgb(d, d, d);
}
And an improved (shorter) version:
#ColorInt
public static int getContrastColor(#ColorInt int color) {
// Counting the perceptive luminance - human eye favors green color...
double a = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
return a < 0.5 ? Color.BLACK : Color.WHITE;
}
My Swift implementation of Gacek's answer:
func contrastColor(color: UIColor) -> UIColor {
var d = CGFloat(0)
var r = CGFloat(0)
var g = CGFloat(0)
var b = CGFloat(0)
var a = CGFloat(0)
color.getRed(&r, green: &g, blue: &b, alpha: &a)
// Counting the perceptive luminance - human eye favors green color...
let luminance = 1 - ((0.299 * r) + (0.587 * g) + (0.114 * b))
if luminance < 0.5 {
d = CGFloat(0) // bright colors - black font
} else {
d = CGFloat(1) // dark colors - white font
}
return UIColor( red: d, green: d, blue: d, alpha: a)
}
Javascript [ES2015]
const hexToLuma = (colour) => {
const hex = colour.replace(/#/, '');
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
return [
0.299 * r,
0.587 * g,
0.114 * b
].reduce((a, b) => a + b) / 255;
};
Ugly Python if you don't feel like writing it :)
'''
Input a string without hash sign of RGB hex digits to compute
complementary contrasting color such as for fonts
'''
def contrasting_text_color(hex_str):
(r, g, b) = (hex_str[:2], hex_str[2:4], hex_str[4:])
return '000' if 1 - (int(r, 16) * 0.299 + int(g, 16) * 0.587 + int(b, 16) * 0.114) / 255 < 0.5 else 'fff'
Thanks for this post.
For whoever might be interested, here's an example of that function in Delphi:
function GetContrastColor(ABGColor: TColor): TColor;
var
ADouble: Double;
R, G, B: Byte;
begin
if ABGColor <= 0 then
begin
Result := clWhite;
Exit; // *** EXIT RIGHT HERE ***
end;
if ABGColor = clWhite then
begin
Result := clBlack;
Exit; // *** EXIT RIGHT HERE ***
end;
// Get RGB from Color
R := GetRValue(ABGColor);
G := GetGValue(ABGColor);
B := GetBValue(ABGColor);
// Counting the perceptive luminance - human eye favors green color...
ADouble := 1 - (0.299 * R + 0.587 * G + 0.114 * B) / 255;
if (ADouble < 0.5) then
Result := clBlack // bright colors - black font
else
Result := clWhite; // dark colors - white font
end;
This is such a helpful answer. Thanks for it!
I'd like to share an SCSS version:
#function is-color-light( $color ) {
// Get the components of the specified color
$red: red( $color );
$green: green( $color );
$blue: blue( $color );
// Compute the perceptive luminance, keeping
// in mind that the human eye favors green.
$l: 1 - ( 0.299 * $red + 0.587 * $green + 0.114 * $blue ) / 255;
#return ( $l < 0.5 );
}
Now figuring out how to use the algorithm to auto-create hover colors for menu links. Light headers get a darker hover, and vice-versa.
Short Answer:
Calculate the luminance (Y) of the given color, and flip the text either black or white based on a pre-determined middle contrast figure. For a typical sRGB display, flip to white when Y < 0.4 (i.e. 40%)
Longer Answer
Not surprisingly, nearly every answer here presents some misunderstanding, and/or is quoting incorrect coefficients. The only answer that is actually close is that of Seirios, though it relies on WCAG 2 contrast which is known to be incorrect itself.
If I say "not surprisingly", it is due in part to the massive amount of misinformation on the internet on this particular subject. The fact this field is still a subject of active research and unsettled science adds to the fun. I come to this conclusion as the result of the last few years of research into a new contrast prediction method for readability.
The field of visual perception is dense and abstract, as well as developing, so it is common for misunderstandings to exist. For instance, HSV and HSL are not even close to perceptually accurate. For that you need a perceptually uniform model such as CIELAB or CIELUV or CIECAM02 etc.
Some misunderstandings have even made their way into standards, such as the contrast part of WCAG 2 (1.4.3), which has been demonstrated as incorrect over much of its range.
First Fix:
The coefficients shown in many answers here are (.299, .587, .114) and are wrong, as they pertain to a long obsolete system known as NTSC YIQ, the analog broadcast system in North America some decades ago. While they may still be used in some YCC encoding specs for backwards compatibility, they should not be used in an sRGB context.
The coefficients for sRGB and Rec.709 (HDTV) are:
Red: 0.2126
Green: 0.7152
Blue: 0.0722
Other color spaces like Rec2020 or AdobeRGB use different coefficients, and it is important to use the correct coefficients for a given color space.
The coefficients can not be applied directly to 8 bit sRGB encoded image or color data. The encoded data must first be linearized, then the coefficients applied to find the luminance (light value) of the given pixel or color.
For sRGB there is a piecewise transform, but as we are only interested in the perceived lightness contrast to find the point to "flip" the text from black to white, we can take a shortcut via the simple gamma method.
Andy's Shortcut to Luminance & Lightness
Divide each sRGB color by 255.0, then raise to the power of 2.2, then multiply by the coefficients and sum them to find estimated luminance.
let Ys = Math.pow(sR/255.0,2.2) * 0.2126 +
Math.pow(sG/255.0,2.2) * 0.7152 +
Math.pow(sB/255.0,2.2) * 0.0722; // Andy's Easy Luminance for sRGB. For Rec709 HDTV change the 2.2 to 2.4
Here, Y is the relative luminance from an sRGB monitor, on a 0.0 to 1.0 scale. This is not relative to perception though, and we need further transforms to fit our human visual perception of the relative lightness, and also of the perceived contrast.
The 40% Flip
But before we get there, if you are only looking for a basic point to flip the text from black to white or vice versa, the cheat is to use the Y we just derived, and make the flip point about Y = 0.40;. so for colors higher than 0.4 Y, make the text black #000 and for colors darker than 0.4 Y, make the text white #fff.
let textColor = (Ys < 0.4) ? "#fff" : "#000"; // Low budget down and dirty text flipper.
Why 40% and not 50%? Our human perception of lightness/darkness and of contrast is not linear. For a self illuminated display, it so happens that 0.4 Y is about middle contrast under most typical conditions.
Yes it varies, and yes this is an over simplification. But if you are flipping text black or white, the simple answer is a useful one.
Perceptual Bonus Round
Predicting the perception of a given color and lightness is still a subject of active research, and not entirely settled science. The L* (Lstar) of CIELAB or LUV has been used to predict perceptual lightness, and even to predict perceived contrast. However, L* works well for surface colors in a very defined/controlled environment, and does not work as well for self illuminated displays.
While this varies depending on not only the display type and calibration, but also your environment and the overall page content, if you take the Y from above, and raise it by around ^0.685 to ^0.75, you'll find that 0.5 is typically the middle point to flip the text from white to black.
let textColor = (Math.pow(Ys,0.75) < 0.5) ? "#fff" : "#000"; // perceptually based text flipper.
Using the exponent 0.685 will make the text color swap on a darker color, and using 0.8 will make the text swap on a lighter color.
Spatial Frequency Double Bonus Round
It is useful to note that contrast is NOT just the distance between two colors. Spatial frequency, in other words font weight and size, are also CRITICAL factors that cannot be ignored.
That said, you may find that when colors are in the midrange, that you'd want to increase the size and or weight of the font.
let textSize = "16px";
let textWeight = "normal";
let Ls = Math.pow(Ys,0.7);
if (Ls > 0.33 && Ls < 0.66) {
textSize = "18px";
textWeight = "bold";
} // scale up fonts for the lower contrast mid luminances.
Hue R U
It's outside the scope of this post to delve deeply, but above we are ignoring hue and chroma. Hue and chroma do have an effect, such as Helmholtz Kohlrausch, and the simpler luminance calculations above do not always predict intensity due to saturated hues.
To predict these more subtle aspects of perception, a complete appearance model is needed. R. Hunt, M. Fairshild, E. Burns are a few authors worth looking into if you want to plummet down the rabbit hole of human visual perception...
For this narrow purpose, we could re-weight the coefficients slightly, knowing that green makes up the majority of of luminance, and pure blue and pure red should always be the darkest of two colors. What tends to happen using the standard coefficients, is middle colors with a lot of blue or red may flip to black at a lower than ideal luminance, and colors with a high green component may do the opposite.
That said, I find this is best addressed by increasing font size and weight in the middle colors.
Putting it all together
So we'll assume you'll send this function a hex string, and it will return a style string that can be sent to a particular HTML element.
Check out the CODEPEN, inspired by the one Seirios did:
CodePen: Fancy Font Flipping
One of the things the Codepen code does is increase the text size for the lower contrast midrange. Here's a sample:
And if you want to play around with some of these concepts, see the SAPC development site at https://www.myndex.com/SAPC/ clicking on "research mode" provides interactive experiments to demonstrate these concepts.
Terms of enlightenment
Luminance: Y (relative) or L (absolute cd/m2) a spectrally weighted but otherwise linear measure of light. Not to be confused with "Luminosity".
Luminosity: light over time, useful in astronomy.
Lightness: L* (Lstar) perceptual lightness as defined by the CIE. Some models have a related lightness J*.
I had the same problem but i had to develop it in PHP. I used #Garek's solution and i also used this answer:
Convert hex color to RGB values in PHP to convert HEX color code to RGB.
So i'm sharing it.
I wanted to use this function with given Background HEX color, but not always starting from '#'.
//So it can be used like this way:
$color = calculateColor('#804040');
echo $color;
//or even this way:
$color = calculateColor('D79C44');
echo '<br/>'.$color;
function calculateColor($bgColor){
//ensure that the color code will not have # in the beginning
$bgColor = str_replace('#','',$bgColor);
//now just add it
$hex = '#'.$bgColor;
list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x");
$color = 1 - ( 0.299 * $r + 0.587 * $g + 0.114 * $b)/255;
if ($color < 0.5)
$color = '#000000'; // bright colors - black font
else
$color = '#ffffff'; // dark colors - white font
return $color;
}
Flutter implementation
Color contrastColor(Color color) {
if (color == Colors.transparent || color.alpha < 50) {
return Colors.black;
}
double luminance = (0.299 * color.red + 0.587 * color.green + 0.114 * color.blue) / 255;
return luminance > 0.5 ? Colors.black : Colors.white;
}
Based on Gacek's answer, and after analyzing #WebSeed's example with the WAVE browser extension, I've come up with the following version that chooses black or white text based on contrast ratio (as defined in W3C's Web Content Accessibility Guidelines (WCAG) 2.1), instead of luminance.
This is the code (in javascript):
// As defined in WCAG 2.1
var relativeLuminance = function (R8bit, G8bit, B8bit) {
var RsRGB = R8bit / 255.0;
var GsRGB = G8bit / 255.0;
var BsRGB = B8bit / 255.0;
var R = (RsRGB <= 0.03928) ? RsRGB / 12.92 : Math.pow((RsRGB + 0.055) / 1.055, 2.4);
var G = (GsRGB <= 0.03928) ? GsRGB / 12.92 : Math.pow((GsRGB + 0.055) / 1.055, 2.4);
var B = (BsRGB <= 0.03928) ? BsRGB / 12.92 : Math.pow((BsRGB + 0.055) / 1.055, 2.4);
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
};
var blackContrast = function(r, g, b) {
var L = relativeLuminance(r, g, b);
return (L + 0.05) / 0.05;
};
var whiteContrast = function(r, g, b) {
var L = relativeLuminance(r, g, b);
return 1.05 / (L + 0.05);
};
// If both options satisfy AAA criterion (at least 7:1 contrast), use preference
// else, use higher contrast (white breaks tie)
var chooseFGcolor = function(r, g, b, prefer = 'white') {
var Cb = blackContrast(r, g, b);
var Cw = whiteContrast(r, g, b);
if(Cb >= 7.0 && Cw >= 7.0) return prefer;
else return (Cb > Cw) ? 'black' : 'white';
};
A working example may be found in my fork of #WebSeed's codepen, which produces zero low contrast errors in WAVE.
As Kotlin / Android extension:
fun Int.getContrastColor(): Int {
// Counting the perceptive luminance - human eye favors green color...
val a = 1 - (0.299 * Color.red(this) + 0.587 * Color.green(this) + 0.114 * Color.blue(this)) / 255
return if (a < 0.5) Color.BLACK else Color.WHITE
}
An implementation for objective-c
+ (UIColor*) getContrastColor:(UIColor*) color {
CGFloat red, green, blue, alpha;
[color getRed:&red green:&green blue:&blue alpha:&alpha];
double a = ( 0.299 * red + 0.587 * green + 0.114 * blue);
return (a > 0.5) ? [[UIColor alloc]initWithRed:0 green:0 blue:0 alpha:1] : [[UIColor alloc]initWithRed:255 green:255 blue:255 alpha:1];
}
iOS Swift 3.0 (UIColor extension):
func isLight() -> Bool
{
if let components = self.cgColor.components, let firstComponentValue = components[0], let secondComponentValue = components[1], let thirdComponentValue = components[2] {
let firstComponent = (firstComponentValue * 299)
let secondComponent = (secondComponentValue * 587)
let thirdComponent = (thirdComponentValue * 114)
let brightness = (firstComponent + secondComponent + thirdComponent) / 1000
if brightness < 0.5
{
return false
}else{
return true
}
}
print("Unable to grab components and determine brightness")
return nil
}
Swift 4 Example:
extension UIColor {
var isLight: Bool {
let components = cgColor.components
let firstComponent = ((components?[0]) ?? 0) * 299
let secondComponent = ((components?[1]) ?? 0) * 587
let thirdComponent = ((components?[2]) ?? 0) * 114
let brightness = (firstComponent + secondComponent + thirdComponent) / 1000
return !(brightness < 0.6)
}
}
UPDATE - Found that 0.6 was a better test bed for the query
Note there is an algorithm for this in the google closure library that references a w3c recommendation: http://www.w3.org/TR/AERT#color-contrast. However, in this API you provide a list of suggested colors as a starting point.
/**
* Find the "best" (highest-contrast) of the suggested colors for the prime
* color. Uses W3C formula for judging readability and visual accessibility:
* http://www.w3.org/TR/AERT#color-contrast
* #param {goog.color.Rgb} prime Color represented as a rgb array.
* #param {Array<goog.color.Rgb>} suggestions Array of colors,
* each representing a rgb array.
* #return {!goog.color.Rgb} Highest-contrast color represented by an array.
*/
goog.color.highContrast = function(prime, suggestions) {
var suggestionsWithDiff = [];
for (var i = 0; i < suggestions.length; i++) {
suggestionsWithDiff.push({
color: suggestions[i],
diff: goog.color.yiqBrightnessDiff_(suggestions[i], prime) +
goog.color.colorDiff_(suggestions[i], prime)
});
}
suggestionsWithDiff.sort(function(a, b) { return b.diff - a.diff; });
return suggestionsWithDiff[0].color;
};
/**
* Calculate brightness of a color according to YIQ formula (brightness is Y).
* More info on YIQ here: http://en.wikipedia.org/wiki/YIQ. Helper method for
* goog.color.highContrast()
* #param {goog.color.Rgb} rgb Color represented by a rgb array.
* #return {number} brightness (Y).
* #private
*/
goog.color.yiqBrightness_ = function(rgb) {
return Math.round((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000);
};
/**
* Calculate difference in brightness of two colors. Helper method for
* goog.color.highContrast()
* #param {goog.color.Rgb} rgb1 Color represented by a rgb array.
* #param {goog.color.Rgb} rgb2 Color represented by a rgb array.
* #return {number} Brightness difference.
* #private
*/
goog.color.yiqBrightnessDiff_ = function(rgb1, rgb2) {
return Math.abs(
goog.color.yiqBrightness_(rgb1) - goog.color.yiqBrightness_(rgb2));
};
/**
* Calculate color difference between two colors. Helper method for
* goog.color.highContrast()
* #param {goog.color.Rgb} rgb1 Color represented by a rgb array.
* #param {goog.color.Rgb} rgb2 Color represented by a rgb array.
* #return {number} Color difference.
* #private
*/
goog.color.colorDiff_ = function(rgb1, rgb2) {
return Math.abs(rgb1[0] - rgb2[0]) + Math.abs(rgb1[1] - rgb2[1]) +
Math.abs(rgb1[2] - rgb2[2]);
};
base R version of #Gacek's answer to get luminance (you can apply your own threshold easily)
# vectorized
luminance = function(col) c(c(.299, .587, .114) %*% col2rgb(col)/255)
Usage:
luminance(c('black', 'white', '#236FAB', 'darkred', '#01F11F'))
# [1] 0.0000000 1.0000000 0.3730039 0.1629843 0.5698039
If you're manipulating color spaces for visual effect it's generally easier to work in HSL (Hue, Saturation and Lightness) than RGB. Moving colours in RGB to give naturally pleasing effects tends to be quite conceptually difficult, whereas converting into HSL, manipulating there, then converting back out again is more intuitive in concept and invariably gives better looking results.
Wikipedia has a good introduction to HSL and the closely related HSV. And there's free code around the net to do the conversion (for example here is a javascript implementation)
What precise transformation you use is a matter of taste, but personally I'd have thought reversing the Hue and Lightness components would be certain to generate a good high contrast colour as a first approximation, but you can easily go for more subtle effects.
You can have any hue text on any hue background and ensure that it is legible. I do it all the time. There's a formula for this in Javascript on Readable Text in Colour – STW*
As it says on that link, the formula is a variation on the inverse-gamma adjustment calculation, though a bit more manageable IMHO.
The menus on the right-hand side of that link and its associated pages use randomly-generated colours for text and background, always legible. So yes, clearly it can be done, no problem.
An Android variation that captures the alpha as well.
(thanks #thomas-vos)
/**
* Returns a colour best suited to contrast with the input colour.
*
* #param colour
* #return
*/
#ColorInt
public static int contrastingColour(#ColorInt int colour) {
// XXX https://stackoverflow.com/questions/1855884/determine-font-color-based-on-background-color
// Counting the perceptive luminance - human eye favors green color...
double a = 1 - (0.299 * Color.red(colour) + 0.587 * Color.green(colour) + 0.114 * Color.blue(colour)) / 255;
int alpha = Color.alpha(colour);
int d = 0; // bright colours - black font;
if (a >= 0.5) {
d = 255; // dark colours - white font
}
return Color.argb(alpha, d, d, d);
}
I would have commented on the answer by #MichaelChirico but I don't have enough reputation. So, here's an example in R with returning the colours:
get_text_colour <- function(
background_colour,
light_text_colour = 'white',
dark_text_colour = 'black',
threshold = 0.5
) {
background_luminance <- c(
c( .299, .587, .114 ) %*% col2rgb( background_colour ) / 255
)
return(
ifelse(
background_luminance < threshold,
light_text_colour,
dark_text_colour
)
)
}
> get_text_colour( background_colour = 'blue' )
[1] "white"
> get_text_colour( background_colour = c( 'blue', 'yellow', 'pink' ) )
[1] "white" "black" "black"
> get_text_colour( background_colour = c('black', 'white', '#236FAB', 'darkred', '#01F11F') )
[1] "white" "black" "white" "white" "black"

I'm looking for a blend mode that gives 'realistic' paint colors. (Subtractive) [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I've been looking for a blend mode to (well ...) blend two RGB pixels in order to build colors in the samw way that a painter builds them (i.e: subtractive).
Here are quick examples of the type of results that I'm expecting:
CYAN + MAGENTA = BLUE
CYAN + YELLOW = GREEN
MAGENTA + YELLOW = RED
RED + YELLOW = ORANGE
RED + BLUE = PURPLE
YELLOW + BLUE = GREEN
I'm looking for a formula, like:
dest_red = first_red + second_red;
dest_green = first_green + second_green;
dest_blue = first_blue + second_blue;
I've tried with the commonly used 'multiply' formula but it doesn't work; I've tried with custom made formulas but I'm still not able to 'crack' how it should work. And I know already a lot of color theory so please refrain from answers like:
Check this link: http://the_difference_betweeen_additive_and_subtractive_lighting.html
Note: Check that your blend method works with YELLOW + BLUE = GREEN and YELLOW + RED = ORANGE
The CMY color space, which addresses this kind of subtractive blending, is basically the inverted RGB space. You can add colors in CMY space and convert them back into RGB.
CYAN (100 CMY) + MAGENTA (010 CMY) = (110 CMY) = (001 RGB) = BLUE
CYAN (100 CMY) + YELLOW (001 CMY) = (101 CMY) = (010 RGB) = GREEN
...
RED (100 RGB) + YELLOW (001 CMY) = (011 CMY) + (001 CMY) = (012 CMY) => (0 0.5 1 CMY) = (1 0.5 0 RGB) = ORANGE
RED (011 CMY) + BLUE (110 CMY) = (121 CMY) => (0.5 1 0.5 CMY) = (0.5 0 0.5 RGB) = PURPLE
As you see, you have to normalize the color, if there are components with values greater than 1.
I just realized that the last addition (YELLOW + BLUE) does not work with this model. I leave the answer here, though. Maybe it can help you. That's probably because your examples may contain an inconsistency. If CYAN+YELLOW=GREEN, it is very unlikely that the same GREEN can be generated with BLUE+YELLOW.
I doubt there is one best answer for this question. If you only want substractive color space, CMY(K) could be enough. If you however want to create something similar to ArtRage rather than pure Photoshop, implementing your own blending curves is a must for realistic effects.