Created
August 25, 2013 21:44
-
-
Save jarek-foksa/6336503 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # @copyright | |
| # © 2012-2013 Jarosław Foksa | |
| # | |
| # @info | |
| # Prototype object representing CSS color, based on http://www.w3.org/TR/css3-color/ and | |
| # https://developer.mozilla.org/en/CSS/color_value | |
| # | |
| # rgb - 'rgb(255, 255, 255)' | |
| # rgba - 'rgba(255, 255, 255, 1)' | |
| # hsl - 'hsl(360, 100%, 100%)' | |
| # hsla - 'hsla(360, 100%, 100%, 1)' | |
| # hex - '#ffffff' | |
| # name - 'white' | |
| # | |
| # @usage | |
| # fillColor = color.clone().init r: 0, g: 255, b: 0, a: 1 | |
| # fillColor.toString 'hsla' | |
| # > "hsl(120, 100%, 50%)" | |
| # | |
| # strokeColor = color.clone().init name: 'yellow' | |
| # fill.toString 'hex' | |
| # > "#ffff00" | |
| # | |
| # transparentStrokeColor = strokeColor.copy().init a: 0.3 | |
| # transparentStrokeColor.toString() | |
| # > "rgba(255, 255, 0, 0.3)" | |
| {hslToRgb, rgbToHsl} = imports 'utils/color' | |
| {normalize, round} = imports 'utils/math' | |
| {isNumeric} = imports 'utils/type' | |
| {namedColors} = imports 'spec' | |
| exports color = | |
| r: 0 # red, 0 ~ 255 | |
| g: 0 # green, 0 ~ 255 | |
| b: 0 # blue, 0 ~ 255 | |
| h: 0 # hue, 0 ~ 360 | |
| s: 0 # saturation, 0.0 - 100.0 | |
| l: 0 # lightness, 0.0 - 100.0 | |
| a: 1 # alpha, 0.00 - 1.00 | |
| init: (desc = {}) -> | |
| for key of desc | |
| @[key] = desc[key] | |
| if desc.r || desc.g || desc.b | |
| @_syncHslValues() | |
| else if desc.h || desc.s || desc.l | |
| @_syncRgbValues() | |
| return @ | |
| # Accepts any color format from CSS3 | |
| initWithString: (string) -> | |
| if string.startsWith 'rgba(' | |
| if string.contains '%' | |
| @_initWithRgbaPercentsString string | |
| else | |
| @_initWithRgbaString string | |
| else if string.startsWith 'rgb(' | |
| if string.contains '%' | |
| @_initWithRgbPercentsString string | |
| else | |
| @_initWithRgbString string | |
| else if string.startsWith '#' | |
| @_initWithHexString string | |
| else if string.startsWith 'hsla(' | |
| @_initWithHslaString string | |
| else if string.startsWith 'hsl(' | |
| @_initWithHslString string | |
| else | |
| @_initWithNameString string | |
| return @ | |
| toString: (format = 'rgba') -> | |
| if format == 'rgba' | |
| return @_toRgbaString() | |
| else if format == 'rgb' | |
| return @_toRgbString() | |
| else if format == 'rgbaPercents' | |
| return @_toRgbaPercentsString() | |
| else if format == 'rgbPercents' | |
| return @_toRgbPercentsString() | |
| else if format == 'hsla' | |
| return @_toHslaString() | |
| else if format == 'hsl' | |
| return @_toHslString() | |
| else if format == 'hex' | |
| return @_toHexString() | |
| else if format == 'name' | |
| return @_toNameString() | |
| else | |
| console.error 'Invalid string format name:', format | |
| return null | |
| copy: -> | |
| copy = color.clone() | |
| for key in ['r', 'g', 'b', 'h', 's', 'l', 'a'] | |
| copy[key] = @[key] | |
| return copy | |
| @a = normalize a, 0, 1, 2 | |
| return @ | |
| setA: (a) -> | |
| @a = normalize a, 0, 1, 2 | |
| return @ | |
| setR: (r) -> | |
| @r = normalize r, 0, 255, 0 | |
| @_syncHslValues() | |
| return @ | |
| setG: (g) -> | |
| @g = normalize g, 0, 255, 0 | |
| @_syncHslValues() | |
| return @ | |
| setB: (b) -> | |
| @b = normalize b, 0, 255, 0 | |
| @_syncHslValues() | |
| return @ | |
| setH: (h) -> | |
| @h = normalize h, 0, 360, 0 | |
| @_syncRgbValues() | |
| return @ | |
| setS: (s) -> | |
| @s = normalize s, 0, 100, 1 | |
| @_syncRgbValues() | |
| return @ | |
| setL: (l) -> | |
| @l = normalize l, 0, 100, 1 | |
| @_syncRgbValues() | |
| return @ | |
| setRPercent: (rPercentage) -> | |
| @r = normalize ((rPercentage/100) * 255), 0, 100, 0 | |
| @_syncHslValues() | |
| return @ | |
| setGPercent: (gPercentage) -> | |
| @g = normalize ((gPercentage/100) * 255), 0, 100, 0 | |
| @_syncHslValues() | |
| return @ | |
| setBPercent: (bPercentage) -> | |
| @b = normalize ((bPercentage/100) * 255), 0, 100, 0 | |
| @_syncHslValues() | |
| return @ | |
| getRPercent: -> | |
| return round ((@r/255) * 100), 1 | |
| getGPercent: -> | |
| return round ((@g/255) * 100), 1 | |
| getBPercent: -> | |
| @_syncRgbValues() if @b == null | |
| return round ((@b/255) * 100), 1 | |
| _toRgbaString: -> | |
| string = "rgba(#{@r}, #{@g}, #{@b}, #{@a})" | |
| return string | |
| _toRgbString: -> | |
| string = "rgb(#{@r}, #{@g}, #{@b})" | |
| return string | |
| _toRgbaPercentsString: -> | |
| string = "rgba(#{@getRPercent()}%, #{@getGPercent()}%, #{@getBPercent()}%, #{@a})" | |
| return string | |
| _toRgbPercentsString: -> | |
| string = "rgb(#{@getRPercent()}%, #{@getGPercent()}%, #{@getBPercent()}%)" | |
| return string | |
| _toHslaString: -> | |
| string = "hsla(#{@h}, #{@s}%, #{@l}%, #{@a})" | |
| return string | |
| _toHslString: -> | |
| string = "hsl(#{@h}, #{@s}%, #{@l}%)" | |
| return string | |
| _toHexString: -> | |
| hexRed = @r.toString 16 | |
| hexGreen = @g.toString 16 | |
| hexBlue = @b.toString 16 | |
| if hexRed.length == 1 | |
| hexRed = '0' + hexRed | |
| if hexGreen.length == 1 | |
| hexGreen = '0' + hexGreen | |
| if hexBlue.length == 1 | |
| hexBlue = '0' + hexBlue | |
| hex = '#' + hexRed + hexGreen + hexBlue | |
| return hex | |
| _toNameString: -> | |
| name = null | |
| for key, value of namedColors | |
| if value[0] == @r | |
| if value[1] == @g | |
| if value[2] == @b | |
| name = key | |
| break | |
| return name | |
| _initWithRgbString: (rgbString) -> | |
| values = @_extractColorFunctionArgs rgbString, 'rgb' | |
| error = "Invalid rgb() value assigned: '#{rgbString}'" | |
| if values == null | |
| console.error error | |
| else if values.length == 3 && isNumeric(values[0]) && isNumeric(values[1]) && isNumeric(values[2]) | |
| @setR parseFloat(values[0]) | |
| @setG parseFloat(values[1]) | |
| @setB parseFloat(values[2]) | |
| @setA 1 | |
| @_syncHslValues() | |
| else | |
| console.error error | |
| return @ | |
| _initWithRgbaString: (rgbaString) -> | |
| values = @_extractColorFunctionArgs rgbaString, 'rgba' | |
| error = "Invalid rgba() value assigned: '#{rgbaString}'" | |
| if values == null | |
| console.error error | |
| else if (values.length == 4 && isNumeric(values[0]) && isNumeric(values[1]) && | |
| isNumeric(values[2]) && isNumeric(values[3])) | |
| @setR parseFloat values[0] | |
| @setG parseFloat values[1] | |
| @setB parseFloat values[2] | |
| @setA parseFloat values[3] | |
| @_syncHslValues() | |
| else | |
| console.error error | |
| return @ | |
| _initWithRgbPercentsString: (rgbString) -> | |
| values = @_extractColorFunctionArgs rgbString, 'rgb' | |
| error = "Invalid rgb() value assigned: '#{rgbString}'" | |
| if values == null | |
| console.error error | |
| else if values.length == 3 && values[0].contains('%') && values[1].contains('%') && values[2].contains('%') | |
| values[0] = values[0].replace '%', '' | |
| values[1] = values[1].replace '%', '' | |
| values[2] = values[2].replace '%', '' | |
| if isNumeric(values[0]) && isNumeric(values[1]) && isNumeric(values[2]) | |
| @setRPercent parseFloat values[0] | |
| @setGPercent parseFloat values[1] | |
| @setBPercent parseFloat values[2] | |
| @setA 1 | |
| @_syncHslValues() | |
| else | |
| console.error error | |
| else | |
| console.error error | |
| return @ | |
| _initWithRgbaPercentsString: (rgbaString) -> | |
| values = @_extractColorFunctionArgs rgbaString, 'rgba' | |
| error = "Invalid rgba() value assigned: '#{rgbaString}'" | |
| if values == null | |
| console.error error | |
| else if (values.length == 4 && values[0].contains('%') && values[1].contains('%') && | |
| values[2].contains('%') && isNumeric(values[3])) | |
| values[0] = values[0].replace '%', '' | |
| values[1] = values[1].replace '%', '' | |
| values[2] = values[2].replace '%', '' | |
| if isNumeric(values[0]) && isNumeric(values[1]) && isNumeric(values[2]) | |
| @setRPercent parseFloat values[0] | |
| @setGPercent parseFloat values[1] | |
| @setBPercent parseFloat values[2] | |
| @setA parseFloat values[3] | |
| @_syncHslValues() | |
| else | |
| console.error error | |
| else | |
| console.error error | |
| return @ | |
| _initWithHslString: (hslString) -> | |
| values = @_extractColorFunctionArgs hslString, 'hsl' | |
| error = "Invalid hsl() value assigned: '#{hslString}'" | |
| if values == null | |
| console.error error | |
| else if values.length == 3 && isNumeric(values[0]) && values[1].contains('%') && values[2].contains('%') | |
| values[1] = values[1].replace '%', '' | |
| values[2] = values[2].replace '%', '' | |
| if isNumeric(values[1]) && isNumeric(values[2]) | |
| @setH parseFloat values[0] | |
| @setS parseFloat values[1] | |
| @setL parseFloat values[2] | |
| @setA 1 | |
| @_syncRgbValues() | |
| else | |
| console.error error | |
| else | |
| console.error error | |
| return null | |
| _initWithHslaString: (hslaString) -> | |
| values = @_extractColorFunctionArgs hslaString, 'hsla' | |
| error = "Invalid hsl() value assigned: '#{hslaString}'" | |
| if values == null | |
| console.error error | |
| else if (values.length == 4 && isNumeric(values[0]) && values[1].contains('%') && | |
| values[2].contains('%') && isNumeric(values[3])) | |
| values[1] = values[1].replace '%', '' | |
| values[2] = values[2].replace '%', '' | |
| if isNumeric(values[1]) && isNumeric(values[2]) | |
| @setH parseFloat values[0] | |
| @setS parseFloat values[1] | |
| @setL parseFloat values[2] | |
| @setA parseFloat values[3] | |
| @_syncRgbValues() | |
| else | |
| console.error error | |
| else | |
| console.error error | |
| return @ | |
| _initWithHexString: (hexString) -> | |
| hexString = hexString.toLowerCase() | |
| hexString = hexString.substring(1) # get rid of '#' | |
| hexDigits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'] | |
| for digit in hexString | |
| if digit not in hexDigits | |
| console.error 'color._initWithHexString(): Invalid hex digit ' + digit | |
| return null | |
| if hexString.length == 3 | |
| hexRed = hexString[0] + hexString[0] | |
| hexGreen = hexString[1] + hexString[1] | |
| hexBlue = hexString[2] + hexString[2] | |
| else | |
| hexRed = hexString[0] + hexString[1] | |
| hexGreen = hexString[2] + hexString[3] | |
| hexBlue = hexString[4] + hexString[5] | |
| @setR parseInt hexRed, 16 | |
| @setG parseInt hexGreen, 16 | |
| @setB parseInt hexBlue, 16 | |
| @_syncHslValues() | |
| return @ | |
| _initWithNameString: (name) -> | |
| if namedColors[name] | |
| @r = namedColors[name][0] | |
| @g = namedColors[name][1] | |
| @b = namedColors[name][2] | |
| @a = 1 | |
| @_syncHslValues() | |
| else | |
| console.error "Invalid color string: '#{name}'" | |
| return @ | |
| _extractColorFunctionArgs: (colorFunctionString, colorFunctionName = 'rgb') -> | |
| normalizedString = colorFunctionString.replace /\s+/g, '' | |
| normalizedString = normalizedString.toLowerCase() | |
| if normalizedString.startsWith(colorFunctionName + '(') == false || normalizedString.endsWith(')') == false | |
| return null | |
| argsString = normalizedString.between colorFunctionName + '(', ')' | |
| args = argsString.split ',' | |
| return args | |
| _syncRgbValues: -> | |
| rgb = hslToRgb @h, @s, @l | |
| @r = rgb[0] | |
| @g = rgb[1] | |
| @b = rgb[2] | |
| _syncHslValues: -> | |
| hsl = rgbToHsl @r, @g, @b | |
| @h = hsl[0] | |
| @s = hsl[1] | |
| @l = hsl[2] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment