# # Nim's Runtime Library # (c) Copyright 2010 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This module implements color handling for Nim, ## namely color mixing and parsing the CSS color names. import std/strutils from std/algorithm import binarySearch type Color* = distinct int ## A color stored as RGB, e.g. `0xff00cc`. proc `==`*(a, b: Color): bool {.borrow.} ## Compares two colors. ## ## ```Nim ## var ## a = Color(0xff_00_ff) ## b = colFuchsia ## c = Color(0x00_ff_cc) ## assert a == b ## assert not (a == c) ## ``` template extract(a: Color, r, g, b: untyped) = var r = a.int shr 16 and 0xff var g = a.int shr 8 and 0xff var b = a.int and 0xff template rawRGB(r, g, b: int): Color = Color(r shl 16 or g shl 8 or b) template colorOp(op): Color = extract(a, ar, ag, ab) extract(b, br, bg, bb) rawRGB(op(ar, br), op(ag, bg), op(ab, bb)) proc satPlus(a, b: int): int {.inline.} = result = a +% b if result > 255: result = 255 proc satMinus(a, b: int): int {.inline.} = result = a -% b if result < 0: result = 0 proc `+`*(a, b: Color): Color = ## Adds two colors. ## ## This uses saturated arithmetic, so that each color ## component cannot overflow (255 is used as a maximum). ## runnableExamples: var a = Color(0xaa_00_ff) b = Color(0x11_cc_cc) assert a + b == Color(0xbb_cc_ff) colorOp(satPlus) proc `-`*(a, b: Color): Color = ## Subtracts two colors. ## ## This uses saturated arithmetic, so that each color ## component cannot underflow (0 is used as a minimum). ## runnableExamples: var a = Color(0xff_33_ff) b = Color(0x11_ff_cc) assert a - b == Color(0xee_00_33) colorOp(satMinus) proc extractRGB*(a: Color): tuple[r, g, b: range[0..255]] = ## Extracts the red/green/blue components of the color `a`. ## runnableExamples: var a = Color(0xff_00_ff) b = Color(0x00_ff_cc) type Col = range[0..255] # assert extractRGB(a) == (r: 255.Col, g: 0.Col, b: 255.Col) # assert extractRGB(b) == (r: 0.Col, g: 255.Col, b: 204.Col) echo extractRGB(a) echo typeof(extractRGB(a)) echo extractRGB(b) echo typeof(extractRGB(b)) result.r = a.int shr 16 and 0xff result.g = a.int shr 8 and 0xff result.b = a.int and 0xff proc intensity*(a: Color, f: float): Color = ## Returns `a` with intensity `f`. `f` should be a float from 0.0 (completely ## dark) to 1.0 (full color intensity). ## runnableExamples: var a = Color(0xff_00_ff) b = Color(0x00_42_cc) assert a.intensity(0.5) == Color(0x80_00_80) assert b.intensity(0.5) == Color(0x00_21_66) var r = toInt(toFloat(a.int shr 16 and 0xff) * f) var g = toInt(toFloat(a.int shr 8 and 0xff) * f) var b = toInt(toFloat(a.int and 0xff) * f) if r >% 255: r = 255 if g >% 255: g = 255 if b >% 255: b = 255 result = rawRGB(r, g, b) template mix*(a, b: Color, fn: untyped): untyped = ## Uses `fn` to mix the colors `a` and `b`. ## ## `fn` is invoked for each component R, G, and B. ## If `fn`'s result is not in the `range[0..255]`, ## it will be saturated to be so. ## runnableExamples: var a = Color(0x0a2814) b = Color(0x050a03) proc myMix(x, y: int): int = 2 * x - 3 * y assert mix(a, b, myMix) == Color(0x05_32_1f) template `><` (x: untyped): untyped = # keep it in the range 0..255 block: var y = x # eval only once if y >% 255: y = if y < 0: 0 else: 255 y extract(a, ar, ag, ab) extract(b, br, bg, bb) rawRGB(> 0 and name[0] == '#': result = Color(parseHexInt(name)) else: var idx = binarySearch(colorNames, name, colorNameCmp) if idx < 0: raise newException(ValueError, "unknown color: " & name) result = colorNames[idx][1] proc isColor*(name: string): bool = ## Returns true if `name` is a known color name or a hexadecimal color ## prefixed with `#`. Case insensitive. ## runnableExamples: var a = "silver" b = "#0179fc" c = "#zzmmtt" assert a.isColor assert b.isColor assert not c.isColor if name.len == 0: return false if name[0] == '#': for i in 1 .. name.len-1: if name[i] notin HexDigits: return false result = true else: result = binarySearch(colorNames, name, colorNameCmp) >= 0 proc rgb*(r, g, b: range[0..255]): Color = ## Constructs a color from RGB values. ## runnableExamples: assert rgb(0, 255, 128) == Color(0x00_ff_80) result = rawRGB(r, g, b)