Skip to content

Instantly share code, notes, and snippets.

@aristath
Created October 18, 2018 06:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aristath/8071b84e333d57089a361ec3956b58e8 to your computer and use it in GitHub Desktop.
Save aristath/8071b84e333d57089a361ec3956b58e8 to your computer and use it in GitHub Desktop.
var Colour = function( rgba ) {
var rgbaString = '';
if ( 'transparent' === rgba ) {
rgba = [ 0, 0, 0, 0 ];
} else if ( 'string' === typeof rgba ) {
rgbaString = rgba;
rgba = rgbaString.match( /rgba?\(([\d.]+), ([\d.]+), ([\d.]+)(?:, ([\d.]+))?\)/ );
if ( rgba ) {
rgba.shift();
} else {
throw new Error( 'Invalid string: ' + rgbaString );
}
}
rgba[3] = ( undefined === rgba[3] ) ? 1 : rgba[3];
rgba = rgba.map( function( a ) {
return floor( a, 3 );
} );
this.rgba = rgba;
};
Colour.prototype = {
get rgb() {
return this.rgba.slice( 0, 3 );
},
get alpha() {
return this.rgba[3];
},
set alpha( alpha ) {
this.rgba[3] = alpha;
},
get luminance() {
// Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
var rgba = this.rgba.slice(),
rgb,
i;
for ( i = 0; 3 > i; i++ ) {
rgb = rgba[i];
rgb /= 255;
rgb = 0.03928 > rgb ? rgb / 12.92 : Math.pow( ( rgb + 0.055 ) / 1.055, 2.4 );
rgba[i] = rgb;
}
return 0.2126 * rgba[0] + 0.7152 * rgba[1] + 0.0722 * rgba[2];
},
get inverse() {
return new Colour( [
255 - this.rgba[0],
255 - this.rgba[1],
255 - this.rgba[2],
this.alpha
] );
},
toString: function() {
return 'rgb' + ( 1 > this.alpha ? 'a' : '' ) + '(' + this.rgba.slice( 0, 1 >= this.alpha ? 3 : 4 ).join( ', ' ) + ')';
},
clone: function() {
return new Colour( this.rgba );
},
overlayOn: function( color ) {
var overlaid = this.clone(),
alpha = this.alpha,
i;
if ( 1 <= alpha ) {
return overlaid;
}
for ( i = 0; 3 > i; i++ ) {
overlaid.rgba[ i ] = overlaid.rgba[ i ] * alpha + color.rgba[ i ] * color.rgba[3] * ( 1 - alpha );
}
overlaid.rgba[3] = alpha + color.rgba[3] * ( 1 - alpha );
return overlaid;
},
contrast: function( color ) {
// Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
var alpha = this.alpha,
l1,
l2,
ratio,
onBlack,
onWhite,
contrastOnBlack,
contrastOnWhite,
max,
min,
closest;
if ( 1 <= alpha ) {
if ( 1 > color.alpha ) {
color = color.overlayOn( this );
}
l1 = this.luminance + 0.05;
l2 = color.luminance + 0.05;
ratio = l1 / l2;
if ( l2 > l1 ) {
ratio = 1 / ratio;
}
ratio = floor( ratio, 2 );
return {
ratio: ratio,
error: 0,
min: ratio,
max: ratio
};
}
// If we’re here, it means we have a semi-transparent background
// The text color may or may not be semi-transparent, but that doesn't matter
onBlack = this.overlayOn( Colour.BLACK );
onWhite = this.overlayOn( Colour.WHITE );
contrastOnBlack = onBlack.contrast( color ).ratio;
contrastOnWhite = onWhite.contrast( color ).ratio;
max = Math.max( contrastOnBlack, contrastOnWhite );
// This is here for backwards compatibility and not used to calculate
// `min`. Note that there may be other colors with a closer luminance to
// `color` if they have a different hue than `this`.
closest = this.rgb.map( function( c, i ) {
return Math.min( Math.max( 0, ( color.rgb[i] - c * alpha ) / ( 1 - alpha ) ), 255 );
} );
closest = new Colour( closest );
min = 1;
if ( onBlack.luminance > color.luminance ) {
min = contrastOnBlack;
} else if ( onWhite.luminance < color.luminance ) {
min = contrastOnWhite;
}
return {
ratio: floor( ( min + max ) / 2, 2 ),
error: floor( ( max - min ) / 2, 2 ),
min: min,
max: max,
closest: closest,
farthest: onWhite === max ? Colour.WHITE : Colour.BLACK
};
}
};
Colour.BLACK = new Colour( [ 0, 0, 0 ] );
Colour.GRAY = new Colour( [ 127.5, 127.5, 127.5 ] );
Colour.WHITE = new Colour( [ 255, 255, 255 ] );
// Math.floor with precision
function floor( number, decimals ) {
var multiplier;
decimals = +decimals || 0;
multiplier = Math.pow( 10, decimals );
return Math.floor( number * multiplier ) / multiplier;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment