colors: fix mix, add documentation (#11295)
This commit is contained in:
parent
1251e1ad16
commit
68b5e3e3fe
1 changed files with 104 additions and 20 deletions
|
|
@ -13,10 +13,18 @@ import strutils
|
||||||
from algorithm import binarySearch
|
from algorithm import binarySearch
|
||||||
|
|
||||||
type
|
type
|
||||||
Color* = distinct int ## a color stored as RGB
|
Color* = distinct int ## A color stored as RGB, e.g. `0xff00cc`.
|
||||||
|
|
||||||
proc `==` *(a, b: Color): bool {.borrow.}
|
proc `==` *(a, b: Color): bool {.borrow.}
|
||||||
## compares two colors.
|
## Compares two colors.
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## 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) =
|
template extract(a: Color, r, g, b: untyped) =
|
||||||
var r = a.int shr 16 and 0xff
|
var r = a.int shr 16 and 0xff
|
||||||
|
|
@ -40,24 +48,58 @@ proc satMinus(a, b: int): int {.inline.} =
|
||||||
if result < 0: result = 0
|
if result < 0: result = 0
|
||||||
|
|
||||||
proc `+`*(a, b: Color): Color =
|
proc `+`*(a, b: Color): Color =
|
||||||
## adds two colors: This uses saturated artithmetic, so that each color
|
## Adds two colors.
|
||||||
|
##
|
||||||
|
## This uses saturated artithmetic, so that each color
|
||||||
## component cannot overflow (255 is used as a maximum).
|
## component cannot overflow (255 is used as a maximum).
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## var
|
||||||
|
## a = Color(0xaa_00_ff)
|
||||||
|
## b = Color(0x11_cc_cc)
|
||||||
|
## assert a + b == Color(0xbb_cc_ff)
|
||||||
|
##
|
||||||
colorOp(satPlus)
|
colorOp(satPlus)
|
||||||
|
|
||||||
proc `-`*(a, b: Color): Color =
|
proc `-`*(a, b: Color): Color =
|
||||||
## subtracts two colors: This uses saturated artithmetic, so that each color
|
## Subtracts two colors.
|
||||||
## component cannot overflow (255 is used as a maximum).
|
##
|
||||||
|
## This uses saturated artithmetic, so that each color
|
||||||
|
## component cannot underflow (0 is used as a minimum).
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## var
|
||||||
|
## a = Color(0xff_33_ff)
|
||||||
|
## b = Color(0x11_ff_cc)
|
||||||
|
## assert a - b == Color(0xee_00_33)
|
||||||
|
##
|
||||||
colorOp(satMinus)
|
colorOp(satMinus)
|
||||||
|
|
||||||
proc extractRGB*(a: Color): tuple[r, g, b: range[0..255]] =
|
proc extractRGB*(a: Color): tuple[r, g, b: range[0..255]] =
|
||||||
## extracts the red/green/blue components of the color `a`.
|
## Extracts the red/green/blue components of the color `a`.
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## var
|
||||||
|
## a = Color(0xff_00_ff)
|
||||||
|
## b = Color(0x00_ff_cc)
|
||||||
|
## echo extractRGB(a) # => (r: 255, g: 0, b: 255)
|
||||||
|
## echo extractRGB(b) # => (r: 0, g: 255, b: 204)
|
||||||
|
##
|
||||||
result.r = a.int shr 16 and 0xff
|
result.r = a.int shr 16 and 0xff
|
||||||
result.g = a.int shr 8 and 0xff
|
result.g = a.int shr 8 and 0xff
|
||||||
result.b = a.int and 0xff
|
result.b = a.int and 0xff
|
||||||
|
|
||||||
proc intensity*(a: Color, f: float): Color =
|
proc intensity*(a: Color, f: float): Color =
|
||||||
## returns `a` with intensity `f`. `f` should be a float from 0.0 (completely
|
## Returns `a` with intensity `f`. `f` should be a float from 0.0 (completely
|
||||||
## dark) to 1.0 (full color intensity).
|
## dark) to 1.0 (full color intensity).
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## 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 r = toInt(toFloat(a.int shr 16 and 0xff) * f)
|
||||||
var g = toInt(toFloat(a.int shr 8 and 0xff) * f)
|
var g = toInt(toFloat(a.int shr 8 and 0xff) * f)
|
||||||
var b = toInt(toFloat(a.int and 0xff) * f)
|
var b = toInt(toFloat(a.int and 0xff) * f)
|
||||||
|
|
@ -67,10 +109,22 @@ proc intensity*(a: Color, f: float): Color =
|
||||||
result = rawRGB(r, g, b)
|
result = rawRGB(r, g, b)
|
||||||
|
|
||||||
template mix*(a, b: Color, fn: untyped): untyped =
|
template mix*(a, b: Color, fn: untyped): untyped =
|
||||||
## uses `fn` to mix the colors `a` and `b`. `fn` is invoked for each component
|
## Uses `fn` to mix the colors `a` and `b`.
|
||||||
## R, G, and B. This is a template because `fn` should be inlined and the
|
##
|
||||||
## compiler cannot inline proc pointers yet. If `fn`'s result is not in the
|
## `fn` is invoked for each component R, G, and B.
|
||||||
## range[0..255], it will be saturated to be so.
|
## If `fn`'s result is not in the `range[0..255]`,
|
||||||
|
## it will be saturated to be so.
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## 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 =
|
template `><` (x: untyped): untyped =
|
||||||
# keep it in the range 0..255
|
# keep it in the range 0..255
|
||||||
block:
|
block:
|
||||||
|
|
@ -79,9 +133,10 @@ template mix*(a, b: Color, fn: untyped): untyped =
|
||||||
y = if y < 0: 0 else: 255
|
y = if y < 0: 0 else: 255
|
||||||
y
|
y
|
||||||
|
|
||||||
(bind extract)(a, ar, ag, ab)
|
var ar, ag, ab, br, bg, bb: int
|
||||||
(bind extract)(b, br, bg, bb)
|
extract(a, ar, ag, ab)
|
||||||
(bind rawRGB)(><fn(ar, br), ><fn(ag, bg), ><fn(ab, bb))
|
extract(b, br, bg, bb)
|
||||||
|
rawRGB(><fn(ar, br), ><fn(ag, bg), ><fn(ab, bb))
|
||||||
|
|
||||||
|
|
||||||
const
|
const
|
||||||
|
|
@ -369,15 +424,31 @@ const
|
||||||
("yellowgreen", colYellowGreen)]
|
("yellowgreen", colYellowGreen)]
|
||||||
|
|
||||||
proc `$`*(c: Color): string =
|
proc `$`*(c: Color): string =
|
||||||
## converts a color into its textual representation. Example: ``#00FF00``.
|
## Converts a color into its textual representation.
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## assert $colFuchsia == "#FF00FF"
|
||||||
|
##
|
||||||
result = '#' & toHex(int(c), 6)
|
result = '#' & toHex(int(c), 6)
|
||||||
|
|
||||||
proc colorNameCmp(x: tuple[name: string, col: Color], y: string): int =
|
proc colorNameCmp(x: tuple[name: string, col: Color], y: string): int =
|
||||||
result = cmpIgnoreCase(x.name, y)
|
result = cmpIgnoreCase(x.name, y)
|
||||||
|
|
||||||
proc parseColor*(name: string): Color =
|
proc parseColor*(name: string): Color =
|
||||||
## parses `name` to a color value. If no valid color could be
|
## Parses `name` to a color value.
|
||||||
## parsed ``ValueError`` is raised. Case insensitive.
|
##
|
||||||
|
## If no valid color could be parsed ``ValueError`` is raised.
|
||||||
|
## Case insensitive.
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## var
|
||||||
|
## a = "silver"
|
||||||
|
## b = "#0179fc"
|
||||||
|
## c = "#zzmmtt"
|
||||||
|
## assert parseColor(a) == Color(0xc0_c0_c0)
|
||||||
|
## assert parseColor(b) == Color(0x01_79_fc)
|
||||||
|
## doAssertRaises(ValueError): discard parseColor(c)
|
||||||
|
##
|
||||||
if name[0] == '#':
|
if name[0] == '#':
|
||||||
result = Color(parseHexInt(name))
|
result = Color(parseHexInt(name))
|
||||||
else:
|
else:
|
||||||
|
|
@ -386,8 +457,18 @@ proc parseColor*(name: string): Color =
|
||||||
result = colorNames[idx][1]
|
result = colorNames[idx][1]
|
||||||
|
|
||||||
proc isColor*(name: string): bool =
|
proc isColor*(name: string): bool =
|
||||||
## returns true if `name` is a known color name or a hexadecimal color
|
## Returns true if `name` is a known color name or a hexadecimal color
|
||||||
## prefixed with ``#``. Case insensitive.
|
## prefixed with ``#``. Case insensitive.
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## var
|
||||||
|
## a = "silver"
|
||||||
|
## b = "#0179fc"
|
||||||
|
## c = "#zzmmtt"
|
||||||
|
## assert a.isColor
|
||||||
|
## assert b.isColor
|
||||||
|
## assert not c.isColor
|
||||||
|
##
|
||||||
if name[0] == '#':
|
if name[0] == '#':
|
||||||
for i in 1 .. name.len-1:
|
for i in 1 .. name.len-1:
|
||||||
if name[i] notin {'0'..'9', 'a'..'f', 'A'..'F'}: return false
|
if name[i] notin {'0'..'9', 'a'..'f', 'A'..'F'}: return false
|
||||||
|
|
@ -396,6 +477,9 @@ proc isColor*(name: string): bool =
|
||||||
result = binarySearch(colorNames, name, colorNameCmp) >= 0
|
result = binarySearch(colorNames, name, colorNameCmp) >= 0
|
||||||
|
|
||||||
proc rgb*(r, g, b: range[0..255]): Color =
|
proc rgb*(r, g, b: range[0..255]): Color =
|
||||||
## constructs a color from RGB values.
|
## Constructs a color from RGB values.
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## assert rgb(0, 255, 128) == Color(0x00_ff_80)
|
||||||
|
##
|
||||||
result = rawRGB(r, g, b)
|
result = rawRGB(r, g, b)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue