Skip to content

Instantly share code, notes, and snippets.

@apipkin
Created August 3, 2012 21:55
Show Gist options
  • Save apipkin/1da5e3fb2f66a7068727 to your computer and use it in GitHub Desktop.
Save apipkin/1da5e3fb2f66a7068727 to your computer and use it in GitHub Desktop.

Color

I have personally tried to find this tool inside YUI a few times over the years and have been unsuccessful. An API for color conversions would go a long way for a theme roller and color picker (possibly to place in an RTE UI). There has also been expresion that a unified, documented, and maintained API would be beneficial for Charts.

Proposal

To create a consistent cross module implementation for color conversion and color attributes. Provide a means to find similar, complementary, splits, hues, tints, and various other color groups from a provided color.

From Types

Converting from a given color value should specify the type it is to be converted from. In the event that no given type is provide, defined Regular Expressions will be used to find the best possible match.

Options include (with matched value type):

  • keyword (string) // blue
  • hex (string|array[3]) // 00f, 0000ff, #00f, #0000ff, [00, 00, 'ff']
  • rgb (array[3]) // [0, 0, 255]
  • rgbcss (string) // rgb(0, 0, 255)
  • rgba (array[4]) // [0, 0, 255, 1]
  • rgbacss (string) // rgba(0, 0, 255, 1)
  • hsl (array[3]) // [240, 100, 50]
  • hslcss (string) // hsl(240, 100%, 50%)
  • hsla (array[4]) // [240, 100, 50, 1]
  • hslacss (string) // hsla(240, 100%, 50%, 1)
  • auto // 'auto'

To Types

Converting to a color value should specify the desired return type. If no return type is specified, a default return type (to be identified) will be used. Options requesting an alpha channel when no initial alpha channel is provided in the initial value will return with 1 (opaque).

Options include (with matched value type):

  • hex (array[3]) // [00, 00, 'ff']
  • hexcss (#string[6]) // #0000ff
  • rgb (array[3]) // [0, 0, 255]
  • rgbcss (string) // rgb(0, 0, 255)
  • rgba (array[4]) // [0, 0, 255, 1]
  • rgbacss (string) // rgba(0, 0, 255, 1)
  • hsl (array[3]) // [240, 100, 50]
  • hslcss (string) // hsl(240, 100%, 50%)
  • hsla (array[4]) // [240, 100, 50, 1]
  • hslacss (string) // hsla(240, 100%, 50%, 1)

Converting colors

Converting colors from Hex, RGB, HSL, or keyword to any given type.

Y.Color.convert({ 
    'value': 'blue', 
    'type': 'keyword', 
    'to': 'rgb' 
}); // [ 0, 0, 255 ]

Converting from a given type to a specified type.

Y.Color.toRGB({
    'value': 'blue',
    'type': 'keyword'
}); // [ 0, 0, 255 ]

Because there are already two methods in YUI relating to color conversion, toRGB() and toHex(), these methods would need to support backwards compatibility with a single value provided. These methods also take in the CSS value for the color property – rgb(0,0,255) – and will need to carry over this value type conversion.

Retrieving Color Groups

A color group is a collection of colors harmonious to the provided color that may return a fixed number of values (complementary, triad, etc) or a varying number of values (hues, saturations, etc).

For methods returning a varying number of options, count is provided to specify the number of options to return. The default value for this count is 4. The maximum value is 10. The first value returned is always the converted value initially provided.

Y.Color.getComplementary({
    'value': 'blue',
    'type': 'keyword',
    'to': 'rgb'
}); // [ 255, 184, 0 ]
Y.Color.getSaturations({
    'value': 'blue',
    'type': 'keyword',
    'to': 'hslcss',
    'count': 5
}); // ['hsl(240, 100%, 50%)', 'hsl(240, 75%, 50%)', 'hsl(240, 50%, 50%)', 'hsl(240, 25%, 50%)', 'hsl(240, 0%, 50%)' ]

Color Keywords

The existing set of keywords in Y.Color [https://github.com/yui/yui3/blob/master/src/dom/js/color.js] contains 16 keywords matched with their hex value. This list will be expanded to the extended list containing 147 svg colors from the W3C [http://www.w3.org/TR/css3-color/#svg-color] matched with hex values.

Proposed API

Y.Color = {
    KEYWORDS: {},
    
    REXP: {},
    
    // Conversions
    convert: function() {},
    
    toHex: function() {},
    
    toHexCSS: function() {},
    
    toRGB: function() {},
    
    toRGBCss: function() {},
        
    toRGBA: function() {},
    
    toRGBACss: function() {},
    
    toHSL: function() {},
    
    toHSLCss: function() {},
        
    toHSLA: function() {},
    
    toHSLACss: function() {},
    
    // Color Groups
    getComplementary: function() {},
    
    getSplit: function() {},
    
    getAnalogous: function() {},
    
    getTriad: function() {},
    
    getTetrad: function() {},
    
    getMonochrome: function() {},
    
    getSimilar: function() {},
    
    getHues: function() {},
    
    getSaturations: function() {}, // returns values from provided color to 0% saturation
    
    getTints: function () {}, // returns values from provided color to 100% lightness

    getShades: function () {}, // returns values from provided color to 0% lightness

    getOffset: function () {}, // takes type (h,s,l) and an offset % and adjusts the color in that direction

    getSimilarBrightness: function () {} // adjusts the provided color to the visible brightness of the match color
    
};
@allenrabinovich
Copy link

For conversion, why not have one function with a string spec of the conversion type? Is that what convert() is? All the other methods seem like unnecessary sugar?

@apipkin
Copy link
Author

apipkin commented Aug 3, 2012

Yeah, that's what convert() is for. Take in the value type and the return as (to) in the options for the method. The other methods are "sugar" but would be called under the hood from convert(). It's just a matter of making them public or protected (underscored). I was choosing to keep them public for simplicity and speed of going to the function directly rather than having to go through convert() for conversions.

In the case where convert() sounds redundant, given a situation where a function calculates the return value and the return type is based on some other condition, it can stay with convert() internally, and change the to option in the call.

@jconniff
Copy link

jconniff commented Aug 3, 2012

Can we discuss the function getTints. To some people, tints are only lighter versions of a color and shades are a darker version. I'm guessing you're looking for a function that does both. If so another name may be better.

I'm not sure what getTriad and getTetrad do. Would they return 2 and 3 colors respectively?

Some of the other group color functions could warrant more discussion too.

I found it very valuable to have a function that, given a color, would return a color of a different hue and saturation that were equivalent in perceived darkness. For example, I had a color yellow used for text that was readable on a dark background. I wanted to find a blue color that was light enough to be equally readable. A blue of equal numeric value was not the answer. You can take a look at my function getEyeballLightness in the code I sent you.

@apipkin
Copy link
Author

apipkin commented Aug 3, 2012

Yes, I was looking at getTints() to do both darker and lighter shades of the color. I am not opposed to having getTints() return lighert shades and getShades() return darker shades.

A Triad is a three point color return where one point is as the hue where you selected, and the other two points are 120 degrees from that hue in either direction. A Tetrad is a four point color return, similar to a triad, but moving around the hues at 90 degrees.

getTriad() and getTetrad() would return an array of three or four colors respectively. The first being the color your provided with the remaining colors coming from the calculations of the hue going in a clockwise fashion.

I would be thrilled to have something like getEyeballLightness() maybe something under the name of findSimilarBrightness() where you provide one color that's getting adjusted and a second color that the first is being adjusted to? (note: I have not looked at the method you spoke of yet to see if that's even close to what you have created)

Similar to that, if there is a way to offset a color based off another color to maintain a readable contrast, I could see that being a valuable method in some cases (ie. if the background color changes and you want to maintain readability of the text).

@apipkin
Copy link
Author

apipkin commented Aug 3, 2012

Looking at http://en.wikipedia.org/wiki/Tints_and_shades it could also be said that getTones() would also fit into this set of methods.

@jconniff
Copy link

jconniff commented Aug 4, 2012

It seems tedious to have separate getTints and getShades methods. The job will be finding the right name.

Regarding offset from a base color:
This is a key concept in the tool I sent you.
The code I sent has a method that takes an input color in hsl, and outputs a color which is modified by any combination of h s l offset values. see function doC2(...) line 282 in colormix.js
This function also handles getEyeballLit() and headroom which is important for text readability (we can talk more).

@apipkin
Copy link
Author

apipkin commented Aug 6, 2012

*** UPDATED GIST ***

There is also the idea of just providing the conversion functions in Y.Color. Then providing the harmony methods by using 'color-extras', 'color-plus', or 'color-harmonies'. Splitting the methods could be useful for those just needing conversion for their site/app and not wanting or caring for the bulk the extra methods would create. I like this idea and I am in favor of splitting the API based on this.

@eduardolundgren
Copy link

Color utility is definitely an important addition to YUI core, thank you for working on that.

Our proposal to the color API is slightly different from the current proposed API though. In order to provide a low level utility that have good performance and simple API that needs to be as simple as possible, no need for over-complicated apis for color conversion. One example of a simple and robust color utility is the one from Google Closure Library.

I went a head and have created a summary of the most important features and its method signatures that worth us discussing:

Y.Color = {
    // Parses a color out of a string.
    parse: function(str) {},

    // Determines if the given string can be parsed as a color.
    isValidColor: function(str) {},

    // Parses red, green, blue components out of a valid rgb color string.
    parseRgb: function(str) {},

    // Converts a hex representation of a color to RGB.
    hexToRgb: function(hexColor) {},

    //Converts a hex representation of a color to RGB.
    hexToRgbStyle: function(hexColor) {},

    // Converts a color from RGB to hex representation.
    rgbToHex: function(r, g, b) {},

    // Converts a color from RGB color space to HSL color space.
    rgbToHsl: function(r, g, b) {},

    // Converts a color from HSL color space to RGB color space.
    hslToRgb: function(h, s, l) {},

    // Converts an HSV triplet to an RGB array.
    hsvToRgb: function(h, s, brightness) {},

    //Converts from RGB values to an array of HSV values.
    rgbToHsv: function(r, g, b) {},

    // Converts a hex representation of a color to HSL.
    hexToHsl: function(hex) {},

    // Converts from h,s,l values to a hex string
    hslToHex: function(h, s, l) {},

    // Converts a hex representation of a color to HSV
    hexToHsv: function(hex) {},

    // Converts from h,s,v values to a hex string
    hsvToHex: function(h, s, v) {},

    // Calculates the Euclidean distance between two color vectors on an HSL sphere.
    hslDistance: function(hsl1, hsl2) {},

    // Blend two colors together, using the specified factor to indicate the weight given to the first color
    blend: function(rgb1, rgb2, factor) {},

    // Adds black to the specified color, darkening it
    darken: function(rgb, factor) {},

    // Adds white to the specified color, lightening it
    lighten: function(rgb, factor) {},

    // Find the "best" (highest-contrast) of the suggested colors for the prime color.
    highContrast: function(prime, suggestions) {},

    //
    // Alpha
    //

    // Parses an alpha color out of a string.
    parseAlpha: function(str) {},

    //Converts a hex representation of a color to RGBA.
    hexToRgbaStyle: function(hexColor) {},

    // Gets the hex color part of an alpha hex color.
    extractHexColor: function(colorWithAlpha) {},

    // Gets the alpha color part of an alpha hex color.
    extractAlpha: function(colorWithAlpha) {},

    // Converts a hex representation of a color to RGBA.
    hexToRgba: function(hexColor) {},

    // Converts a color from RGBA to hex representation.
    rgbaToHex: function(r, g, b, a) {},

    // Converts from h,s,l,a values to a hex string
    hslaToHex: function(h, s, l, a) {},

    // Converts a color from RGBA to an RGBA style string.
    rgbaToRgbaStyle: function(r, g, b, a) {},

    // Converts a color from HSLA to an RGBA style string.
    hslaToRgbaStyle: function(h, s, l, a) {},

    // Converts a color from HSLA color space to RGBA color space.
    hslaToRgba: function(h, s, l, a) {},

    // Converts a color from RGBA color space to HSLA color space.
    rgbaToHsla: function(r, g, b, a) {},

    // Converts from h,s,v,a values to a hex string
    hsvaToHex: function(h, s, v, a) {}
};

@solmsted
Copy link

I am very excited to see a Color module coming to YUI. I do a lot of work with computer graphics and I do a lot of work with YUI so I'm happy when they come together. I'm going to throw out some ideas and some wish list items. I know my use cases are on the extreme end of the spectrum and I don't want to add too much noise to this discussion. I don't expect most of these ideas to be accepted into core but as you build this please consider how Y.Color could be extended.

It would be great to have compositing features. Methods which combine multiple colors in interesting ways, like the layer modes in Photoshop or GIMP. Methods like over, under, add, subtract, multiply, divide, screen, overlay, difference, lighten, darken, dodge, burn, hue, saturation, and value should be included for starters. The math involved with these operations is usually pretty trivial but the issue is getting the colors into and out of these methods, in the right formats, while keeping good performance. How are you intending to solve this in the proposed color groups methods?

It would be great to support more color modes. Not all colors are made with 3 or 4 channels of unsigned 8-bit integers. For example HDR images may store colors as 32-bit floats. RAW images and video formats can store colors with differing bit widths per channel. Many 3D lighting and rendering engines will produce images with many more color channels; in addition to rgba channels there might be other encoded information like z-depth, xyz normals, velocity vectors, etc. CMYK and single channel grayscale are also common color modes. Also HSV is a good color mode to support; it's only slightly different from HSL and despite the CSS standard I feel like HSV is actually more commonly used in graphics.

I would definitely like to be able to see a Y.Image or Y.Video object in the future with the ability to manipulate individual pixels. While it seems like you're building Y.Color mostly for operating on some CSS rules, please consider how it will scale when operating on megapixels.

Other comments:

I don't understand how most of the proposed color groups methods are intended to be used. I feel like most designers like to manually pick their colors and won't try to find colors programmatically. For some applications I can understand something like, find me a text color that will be readable on this background color or vice-versa.

A method like getEyeballLightness sounds really awesome, but isn't that very dependant upon the display device that's being used? Especially if someone has screwed with a monitor's advanced color settings, which I've seen happens frequently and usually improperly. I'm not sure how this could be implemented reliably. Maybe I'm wrong and there are enough displays that render color lightness the same way, but I know just switching from my laptop screen to my monitor makes the whole world look a bit different.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment