This file contains an archive of the macaw egg wiki page. The most up-to-date version is available at: https://wiki.call-cc.org/eggref/5/macaw == macaw ; Project / Source Code Repository: [[https://gitlab.com/jcroisant/macaw]] ; Issue Tracker: [[https://gitlab.com/jcroisant/macaw/issues]] ; Maintainer: [[/users/john-croisant|John Croisant]] ; License: [[https://gitlab.com/jcroisant/macaw/blob/master/LICENSE.txt|BSD 2-Clause]] Macaw provides efficient color types, math operations, and color space conversion. It is primarily meant for computer graphics, data visualization, image processing, games, etc. For a more scientific library, see the [[/egg/color|color egg]]. Macaw provides a variety of arithmetic, blending, and compositing operations using linear RGB (floats), perceptual sRGB (bytes), and perceptual HSL (floats). More operations and color types may be added in the future. Color objects can either be self-contained for convenience, or can point to locations in memory to efficiently work with large blocks of image data. Low-level operations are also provided which directly modify memory, without needing to create color objects. '''Table of Contents''' [[toc:]] === Color types ==== Generic color procedures (color? x) → boolean Returns {{#t}} if {{x}} is a color of any type defined in this library, or {{#f}} if it is anything else. (color->rgb color_1) → color_1 or new rgb (color->rgb8 color_1) → color_1 or new rgb8 (color->hsl color_1) → color_1 or new hsl Converts a color of any type into the target type. If {{color_1}} is already the target type, it is returned without copying. Otherwise, a newly allocated color of the target type is returned. (color->rgb/new color_1) → new rgb (color->rgb8/new color_1) → new rgb8 (color->hsl/new color_1) → new hsl Like {{color->rgb}} etc. except it always returns a newly allocated color, even if {{color_1}} is already the target type. Equivalent to e.g. (if (rgb? color_1) (rgb-copy color_1) (color->rgb color_1)) ==== rgb The {{rgb}} type represents colors in linear RGB space. It is not [[#gamma|gamma compressed]]. Its alpha is not [[https://en.wikipedia.org/wiki/Alpha_compositing#Straight_versus_premultiplied|premultiplied]]. It has red, green, blue, and alpha (opacity) components stored as 32-bit floating-point numbers ({{float}}), which are usually in the interval [0,1]. Values outside that interval are allowed, but type conversion procedures may behave as if the values were clamped to that interval. (rgb r g b #!optional (a 1.0)) → new rgb Creates a new {{rgb}} color. All values are floats, usually in the interval [0,1]. (rgb-r rgb_1) → float (rgb-g rgb_1) → float (rgb-b rgb_1) → float (rgb-a rgb_1) → float (rgb-r-set! rgb_1 r) → rgb_1 (rgb-g-set! rgb_1 g) → rgb_1 (rgb-b-set! rgb_1 b) → rgb_1 (rgb-a-set! rgb_1 a) → rgb_1 (set! (rgb-r rgb_1) r) → rgb_1 (set! (rgb-g rgb_1) g) → rgb_1 (set! (rgb-b rgb_1) b) → rgb_1 (set! (rgb-a rgb_1) a) → rgb_1 Gets or sets a component of the {{rgb}} color. All values are floats, usually in the interval [0,1]. The setters return the modified color. (rgb-set! rgb_1 r g b a) → rgb_1 Sets every component of the {{rgb}} color, then returns the modified color. This is more efficient than setting each component individually. (rgb? x) → boolean Returns {{#t}} if {{x}} is an {{rgb}} color, or {{#f}} if it is anything else. (rgb= rgb_1 rgb_2) → boolean Returns {{#t}} if {{rgb_1}} and {{rgb_2}} have exactly the same values. This uses floating point equality, which is notoriously fickle due to rounding errors. Consider using {{rgb-near?}} instead. (rgb-near? rgb_1 rgb_2 #!optional (e 1e-5)) → boolean Returns {{#t}} if {{rgb_1}} and {{rgb_2}} have approximately the same values. This checks if each value of {{rgb_1}} is within ±{{e}} of the value of {{rgb_2}}. This is slower than {{rgb=}} but more resilient against rounding errors. (rgb-copy rgb_1) → new rgb Returns a new copy of the {{rgb}} color. (rgb-copy! rgb_src rgb_dst) → rgb_dst Copies data from {{rgb_src}} to {{rgb_dst}}. Modifies and returns {{rgb_dst}}. (rgb-normalize rgb_1) → new rgb (rgb-normalize! rgb_1) → rgb_1 Normalizes the {{rgb}} color by clamping its components to the interval [0,1]. {{rgb-normalize}} returns a new color. {{rgb-normalize!}} modifies and returns its argument. (rgb->list rgb_1) → list of 4 floats Returns a list of the {{rgb}} color's values, {{(r g b a)}}. (rgb->values rgb_1) → 4 float values Returns the {{rgb}} color's values as multiple values. (rgb->rgb8 rgb_1) → new rgb8 (rgb->hsl rgb_1) → new hsl Converts the {{rgb}} color into a new color of a different type. ==== rgb8 The {{rgb8}} type represents colors in perceptual (non-linear) sRGB space. It is [[#gamma|gamma compressed]]. Its alpha is not [[https://en.wikipedia.org/wiki/Alpha_compositing#Straight_versus_premultiplied|premultiplied]]. It has red, green, blue, and alpha (opacity) components stored as unsigned 8-bit integers ({{unsigned char}}), which are limited to the interval [0,255]. Values outside that interval are not allowed. (rgb8 r g b #!optional (a 255)) → new rgb8 Creates a new {{rgb8}} color. All arguments must be integers in the interval [0,255]. (rgb8-r rgb8_1) → integer (rgb8-g rgb8_1) → integer (rgb8-b rgb8_1) → integer (rgb8-a rgb8_1) → integer (rgb8-r-set! rgb8_1 r) → rgb8_1 (rgb8-g-set! rgb8_1 g) → rgb8_1 (rgb8-b-set! rgb8_1 b) → rgb8_1 (rgb8-a-set! rgb8_1 a) → rgb8_1 (set! (rgb8-r rgb8_1) r) → rgb8_1 (set! (rgb8-g rgb8_1) g) → rgb8_1 (set! (rgb8-b rgb8_1) b) → rgb8_1 (set! (rgb8-a rgb8_1) a) → rgb8_1 Gets or sets a component of the {{rgb8}} color. Values are exact integers, and must be in the interval [0,255]. The setters return the modified color. (rgb8-set! rgb8_1 r g b a) → rgb8_1 Sets every component of the {{rgb8}} color, then returns the modified color. This is more efficient than setting each component individually. (rgb8? x) → boolean Returns {{#t}} if {{x}} is an {{rgb8}} color, or {{#f}} if it is anything else. (rgb8= rgb8_1 rgb8_2) → boolean Returns {{#t}} if {{rgb8_1}} and {{rgb8_2}} have exactly the same values. (rgb8-copy rgb8_1) → new rgb8 Returns a new copy of the {{rgb8}} color. (rgb8-copy! rgb8_src rgb8_dst) → rgb8_dst Copies data from {{rgb8_src}} to {{rgb8_dst}}. Modifies and returns {{rgb8_dst}}. (rgb8->list rgb8_1) → list of 4 integers Returns a list of the {{rgb8}} color's values, {{(r g b a)}}. (rgb8->values rgb8_1) → 4 integer values Returns the {{rgb8}} color's values as multiple values. (rgb8->rgb rgb8_1) → new rgb (rgb8->hsl rgb8_1) → new hsl Converts the {{rgb8}} color into a new color of a different type. ==== hsl The {{hsl}} type represents colors in cylindrical HSL space. It has hue, saturation, lightness, and alpha (opacity) components stored as 32-bit floating-point numbers ({{float}}). The lightness component is effectively [[#gamma|gamma compressed]]. Hue is usually in the interval [0,360), and the other components are usually in the interval [0,1]. Values outside that interval are allowed, but type conversion procedures may behave as if values were wrapped (hue) or clamped (other components) to those intervals. (hsl h s l #!optional (a 1.0)) → new hsl Creates a new {{hsl}} color. {{h}} is a float, usually in the interval [0,360). Other components are floats, usually in the interval [0,1]. (hsl-h hsl_1) → float (hsl-s hsl_1) → float (hsl-l hsl_1) → float (hsl-a hsl_1) → float (hsl-h-set! hsl_1 h) → hsl_1 (hsl-s-set! hsl_1 s) → hsl_1 (hsl-l-set! hsl_1 l) → hsl_1 (hsl-a-set! hsl_1 a) → hsl_1 (set! (hsl-h hsl_1) h) → hsl_1 (set! (hsl-s hsl_1) s) → hsl_1 (set! (hsl-l hsl_1) l) → hsl_1 (set! (hsl-a hsl_1) a) → hsl_1 Gets or sets a component of the {{hsl}} color. {{h}} is a float, usually in the interval [0,360). Other components are floats, usually in the interval [0,1]. (hsl-set! hsl_1 h s l a) → hsl_1 Sets every component of the {{hsl}} color, then returns the modified color. This is more efficient than setting each component individually. (hsl? x) → boolean Returns {{#t}} if {{x}} is an {{hsl}} color, or {{#f}} if it is anything else. (hsl= hsl_1 hsl_2) → boolean Returns {{#t}} if {{hsl_1}} and {{hsl_2}} have exactly the same values. This uses floating point equality, which is notoriously fickle due to rounding errors. Consider using {{hsl-near?}} instead. (hsl-near? hsl_1 hsl_2 #!optional (e 1e-5)) → boolean Returns {{#t}} if {{hsl_1}} and {{hsl_2}} have approximately the same values. This checks if each value of {{hsl_1}} is within ±{{e}} of the value of {{hsl_2}}. This is slower than {{hsl=}} but more resilient against rounding errors. (hsl-copy hsl_1) → new hsl Returns a new copy of the {{hsl}} color. (hsl-copy! hsl_src hsl_dst) → hsl_dst Copies data from {{hsl_src}} to {{hsl_dst}}. Modifies and returns {{hsl_dst}}. (hsl-normalize hsl_1) → new hsl (hsl-normalize! hsl_1) → hsl_1 Normalizes the {{hsl}} color by wrapping its hue to the interval [0,360) and clamping its other components to the interval [0,1]. {{hsl-normalize}} returns a new color. {{hsl-normalize!}} modifies and returns its argument. (hsl->list hsl_1) → list of 4 floats Returns a list of the {{hsl}} color's values, {{(h s l a)}}. (hsl->values hsl_1) → 4 float values Returns the {{hsl}} color's values as multiple values. (hsl->rgb hsl_1) → new rgb (hsl->rgb8 hsl_1) → new rgb8 Converts the {{hsl}} color into a new color of a different type. === Array types Arrays represent a two-dimensional grid of colors, such as pixels in an image. The color data is stored in a contiguous block of memory, which is more efficient than storing a list or vector of color objects. It also allows arrays to directly access blocks of pixel data from other sources. For example, see the [[#sdl2|sdl2 section]] for how to use {{rgb8-array}} to access the pixels of an {{sdl2:surface}}. ==== Generic array procedures (array? x) → boolean Returns {{#t}} if {{x}} is an array of any type defined in this library, or {{#f}} if it is anything else. (array-width array_1) → integer (array-height array_1) → integer (array-pitch array_1) → integer Returns the width, height, or pitch of an array of any type. (array-ref array_1 x y) → color Returns an color object which directly accesses the memory of the array at the coordinates {{x}}, {{y}}. The returned color type depends on the given array type (e.g. giving an {{hsl-array}} will result in an {{hsl}} color). Modifying the color will modify the array data. The color's parent will be automatically set to the array, so that the array will not be garbage collected while the color is using its memory. Signals an exception if {{x}} or {{y}} are out of bounds. (array-for-each proc array ...) Calls {{proc}} once for each color in the given array(s). If the arrays are different sizes, iterates over the overlapping area (i.e. the smallest width and smallest height). The iteration proceeds in row-major order. {{proc}} is called with the x coordinate, y coordinate, and the corresponding color from each given array. Modifying the color will modify the array data. The color's parent will be automatically set to the array, so that the array will not be garbage collected while the color is using its memory. The arrays can be different types. The color types passed to {{proc}} depend on the given array types (e.g. if the first array is an {{hsl-array}}, the first color argument to {{proc}} will be an {{hsl}} color). If all the arrays are the same type, or if there is only one array, it is more efficient to use the type-specific procedures such as {{rgb-array-for-each}} and {{hsl-array-for-each}}. (array-for-each (lambda (x y rgb8_1 hsl_2) (rgb8-lerp! rgb8_1 hsl_2 (/ x 100.0))) rgb8-array_1 hsl-array_2) ==== rgb-array (make-rgb-array width height #!optional pitch) → new rgb-array Allocates and returns a new rgb-array with the given width and height. {{pitch}} is the number of bytes per row, including any padding at the end. If {{pitch}} is omitted, a pitch is automatically chosen that is ''at least'' {{(* width 16)}}. Use {{rgb-array-pitch}} to get the pitch that was chosen. (rgb-array? x) → boolean Returns {{#t}} if {{x}} is an {{rgb-array}}, otherwise {{#f}}. (rgb-array-width rgb-array_1) → integer (rgb-array-height rgb-array_1) → integer (rgb-array-pitch rgb-array_1) → integer Returns the width, height, or pitch of the {{rgb-array}}. (rgb-array-ref rgb-array_1 x y) → rgb Returns an {{rgb}} color object which directly accesses the memory of the array at the coordinates {{x}}, {{y}}. Modifying the color will modify the array data. The color's parent will be automatically set to the array, so that the array will not be garbage collected while the color is using its memory. See {{rgb-parent}}. Signals an exception if {{x}} or {{y}} are out of bounds. (rgb-array-for-each proc rgb-array ...) Calls {{proc}} once for each color in the given {{rgb-array}}(s). If the arrays are different sizes, it iterates over the overlapping area (i.e. the smallest width and smallest height). The iteration proceeds in row-major order. {{proc}} is called with the x coordinate, y coordinate, and the corresponding {{rgb}} color from each given array. Modifying the color(s) will modify the array data. The color's parent will be automatically set to the array, so that the array will not be garbage collected while the color is using its memory. (rgb-array-for-each (lambda (x y rgb_1 rgb_2) (rgb-lerp! rgb_1 rgb_2 (/ x 100.0))) rgb-array_1 rgb-array_2) ==== rgb8-array (make-rgb8-array width height #!optional pitch) → new rgb8-array Allocates and returns a new rgb8-array with the given width and height. {{pitch}} is the number of bytes per row, including any padding at the end. If {{pitch}} is omitted, a pitch is automatically chosen that is ''at least'' {{(* width 4)}}. Use {{rgb8-array-pitch}} to get the pitch that was chosen. (rgb8-array? x) → boolean Returns {{#t}} if {{x}} is an {{rgb8-array}}, otherwise {{#f}}. (rgb8-array-width rgb8-array_1) → integer (rgb8-array-height rgb8-array_1) → integer (rgb8-array-pitch rgb8-array_1) → integer Returns the width, height, or pitch of the {{rgb8-array}}. (rgb8-array-ref rgb8-array_1 x y) → rgb8 Returns an {{rgb8}} color object which directly accesses the memory of the array at the coordinates {{x}}, {{y}}. Modifying the color will modify the array data. The color's parent will be automatically set to the array, so that the array will not be garbage collected while the color is using its memory. See {{rgb8-parent}}. Signals an exception if {{x}} or {{y}} are out of bounds. (rgb8-array-for-each proc rgb8-array ...) Calls {{proc}} once for each color in the given {{rgb8-array}}(s). If the arrays are different sizes, it iterates over the overlapping area (i.e. the smallest width and smallest height). The iteration proceeds in row-major order. {{proc}} is called with the x coordinate, y coordinate, and the corresponding {{rgb8}} color from each given array. Modifying the color(s) will modify the array data. The color's parent will be automatically set to the array, so that the array will not be garbage collected while the color is using its memory. (rgb8-array-for-each (lambda (x y rgb8_1 rgb8_2) (rgb8-lerp! rgb8_1 rgb8_2 (/ x 100.0))) rgb8-array_1 rgb8-array_2) ==== hsl-array (make-hsl-array width height #!optional pitch) → new hsl-array Allocates and returns a new hsl-array with the given width and height. {{pitch}} is the number of bytes per row, including any padding at the end. If {{pitch}} is omitted, a pitch is automatically chosen that is ''at least'' {{(* width 16)}}. Use {{hsl-array-pitch}} to get the pitch that was chosen. (hsl-array? x) → boolean Returns {{#t}} if {{x}} is an {{hsl-array}}, otherwise {{#f}}. (hsl-array-width hsl-array_1) → integer (hsl-array-height hsl-array_1) → integer (hsl-array-pitch hsl-array_1) → integer Returns the width, height, or pitch of the {{hsl-array}}. (hsl-array-ref hsl-array_1 x y) → hsl Returns an {{hsl}} color object which directly accesses the memory of the array at the coordinates {{x}}, {{y}}. Modifying the color will modify the array data. The color's parent will be automatically set to the array, so that the array will not be garbage collected while the color is using its memory. See {{hsl-parent}}. Signals an exception if {{x}} or {{y}} are out of bounds. (hsl-array-for-each proc hsl-array ...) Calls {{proc}} once for each color in the given {{hsl-array}}(s). If the arrays are different sizes, it iterates over the overlapping area (i.e. the smallest width and smallest height). The iteration proceeds in row-major order. {{proc}} is called with the x coordinate, y coordinate, and the corresponding {{hsl}} color from each given array. Modifying the color(s) will modify the array data. The color's parent will be automatically set to the array, so that the array will not be garbage collected while the color is using its memory. (hsl-array-for-each (lambda (x y hsl_1 hsl_2) (hsl-lerp! hsl_1 hsl_2 (/ x 100.0))) hsl-array_1 hsl-array_2) === Color math This library provides many color math operations. Many of them are equivalent to layer blend modes found in image editing software such as GIMP or Adobe Photoshop. Most operations treat the ''first'' argument as if it were the ''bottom layer'', and later arguments as if they were above it. Each operation has multiple versions, one per color type. Every version accepts arguments of any color type, but they operate in different color spaces, and return different color types. For example, you can call {{rgb-lerp}} with an {{rgb8}} color and a {{hsl}} color. The arguments will be automatically converted to {{rgb}}, then interpolated in linear RGB space, and an {{rgb}} color will be returned. If you called {{hsl-lerp}} with the same arguments, they would be converted to {{hsl}}, then interpolated in HSL space, which could result in a very different color. Math in HSL space can be counterintuitive. For example, if you use {{hsl-add}} to add dark dull yellow {{(hsl 60 0.5 0.25)}} with dark dull green {{(hsl 120 0.5 0.25)}}, the result will be a bright saturated cyan {{(hsl 180 1.0 0.5)}}. But this behavior can also be useful. For example, {{(hsl-add color (hsl 180 0 0))}} will give the complement (opposite hue) of {{color}}, and {{(hsl-mul color (hsl 1 0 1))}} will give a desaturated (grayscale) version of {{color}}. See also the [[#in-place-color-math|In-place color math]] section for versions of these operations which modify in-place (destructively), and the [[#low-level-color-math|Low-level color math]] section for versions of these operations which work directly on pointers or locatives. (rgb-add color_1 color ...) → new rgb (rgb8-add color_1 color ...) → new rgb8 (hsl-add color_1 color ...) → new hsl Add zero or more colors to {{color_1}}, similar to the "addition" or "linear dodge" layer blend mode in image editing software. The result type and color space used depends on the procedure. The result will have the same alpha as {{color_1}}. The alphas of the other colors determine how much effect they have. (rgb-sub color_1 color ...) → new rgb (rgb8-sub color_1 color ...) → new rgb8 (hsl-sub color_1 color ...) → new hsl Subtract zero or more colors from {{color_1}}, similar to the "subtract" layer blend mode in image editing software. The result type and color space used depends on the procedure. The result will have the same alpha as {{color_1}}. The alphas of the other colors determine how much effect they have. (rgb-mul color_1 color ...) → new rgb (rgb8-mul color_1 color ...) → new rgb8 (hsl-mul color_1 color ...) → new hsl Multiply each ''non-alpha'' component of {{color_1}} by the corresponding component of zero or more colors, similar to the "multiply" layer blend mode in image editing software. Be aware that {{rgb8-mul}} behaves as if the components were divided by 255, i.e. multiplying by white {{(rgb8 255 255 255)}} has no effect. Also, {{rgb8-mul}} truncates each component to an integer. The result type and color space used depends on the procedure. The result will have the same alpha as {{color_1}}. The alphas of the other colors determine how much effect they have. (rgb-scale color_1 n) → new rgb (rgb8-scale color_1 n) → new rgb8 (hsl-scale color_1 n) → new hsl Multiply each ''non-alpha'' component of {{color}} by the real number {{n}}. {{rgb8-scale}} truncates each component to an integer. Unlike {{rgb8-mul}}, scaling by 1.0 (not 255) has no effect. The result type and color space used depends on the procedure. The result will have the same alpha as {{color_1}}. (rgb-lerp color_1 color_2 t) → new rgb (rgb8-lerp color_1 color_2 t) → new rgb8 (hsl-lerp color_1 color_2 t) → new hsl Mix {{color_1}} and {{color_2}} using linear interpolation. {{rgb8-lerp}} truncates each component to an integer. The result type and color space used depends on the procedure. The alpha values are interpolated. {{t}} is the interpolation factor, which controls the mix proportion. 0 means use only {{color_1}}, 1 means use only {{color_2}}, 0.5 means halfway between {{color_1}} and {{color_2}}. You can also perform linear ''extrapolation'' by passing a {{t}} less than 0 or greater than 1. (rgb-mix color-list #!optional weight-list) → new rgb (rgb8-mix color-list #!optional weight-list) → new rgb8 (hsl-mix color-list #!optional weight-list) → new hsl Proportionally mixes one or more colors, similar to the "convolution matrix" operation in image editing software. Returns a color whose components (including alpha) are the weighted sum of the given colors' components. {{color-list}} is a list of one or more colors of any type. They are automatically converted to the target type before mixing. {{weight-list}} is an optional list of real numbers, giving the weight of each color. It must be the same length as {{color-list}}. Usually the weights should total 1, but that is not required. Negative weights and weights greater than 1 are allowed. If {{weight-list}} is omitted, the colors are mixed equally. (rgb-under color_1 color ...) → new rgb (rgb8-under color_1 color ...) → new rgb8 (hsl-under color_1 color ...) → new hsl Composite zero or more colors over {{color_1}}, similar to "normal" layer blend mode in image editing software. Same as {{rgb-over}} etc. except the order is reversed: the first color is on bottom and each later color is higher up. The result type and color space used depends on the procedure. The result's alpha will be based on the inputs. (rgb-over color_1 color ...) → new rgb (rgb8-over color_1 color ...) → new rgb8 (hsl-over color_1 color ...) → new hsl Composite {{color_1}} over zero or more colors, similar to "normal" layer blend mode in image editing software. Same as {{rgb-under}} etc. except the order is reversed: the first color is on top and each later color is lower down. The result type and color space used depends on the procedure. The result's alpha will be based on the inputs. ==== In-place color math These procedures produce the same result as the [[#color-math|color math operations]] described above, but they modify the first argument in-place (destructively) and return it, instead of allocating a new color object. Unlike the operations described above, the first argument to an in-place operation must already be the target type. For example, the first argument to {{hsl-add!}} must be an {{hsl}} color, and the first argument to {{rgb8-add!}} must be an {{rgb8}} color. Later color arguments can be any color type. (rgb-add! rgb_1 color ...) → rgb_1 (rgb8-add! rgb8_1 color ...) → rgb8_1 (hsl-add! hsl_1 color ...) → hsl_1 [[#in-place-color-math|In-place]] versions of {{rgb-add}} etc. (rgb-sub! rgb_1 color ...) → rgb_1 (rgb8-sub! rgb8_1 color ...) → rgb8_1 (hsl-sub! hsl_1 color ...) → hsl_1 [[#in-place-color-math|In-place]] versions of {{rgb-sub}} etc. (rgb-mul! rgb_1 color ...) → rgb_1 (rgb8-mul! rgb8_1 color ...) → rgb8_1 (hsl-mul! hsl_1 color ...) → hsl_1 [[#in-place-color-math|In-place]] versions of {{rgb-mul}} etc. (rgb-scale! rgb_1 n) → rgb_1 (rgb8-scale! rgb8_1 n) → rgb8_1 (hsl-scale! hsl_1 n) → hsl_1 [[#in-place-color-math|In-place]] versions of {{rgb-scale}} etc. (rgb-lerp! rgb_1 color_2 t) → rgb_1 (rgb8-lerp! rgb8_1 color_2 t) → rgb8_1 (hsl-lerp! hsl_1 color_2 t) → hsl_1 [[#in-place-color-math|In-place]] versions of {{rgb-lerp}} etc. (rgb-mix! color-list #!optional weight-list) → car of color-list (rgb8-mix! color-list #!optional weight-list) → car of color-list (hsl-mix! color-list #!optional weight-list) → car of color-list [[#in-place-color-math|In-place]] versions of {{rgb-mix}} etc. The first color in {{color-list}} must already be the target type. That color will be modified and returned. (rgb-under! rgb_1 color ...) → rgb_1 (rgb8-under! rgb8_1 color ...) → rgb8_1 (hsl-under! hsl_1 color ...) → hsl_1 [[#in-place-color-math|In-place]] versions of {{rgb-under}} etc. (rgb-over! rgb_1 color ...) → rgb_1 (rgb8-over! rgb8_1 color ...) → rgb8_1 (hsl-over! hsl_1 color ...) → hsl_1 [[#in-place-color-math|In-place]] versions of {{rgb-over}} etc. ==== Gamma Gamma compression is a process of transforming RGB color data to match the way humans perceive light. This allows computers to efficiently store and display images, but math performed with gamma compressed colors will not produce correct results. For more information about gamma, read [[https://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/|"What every coder should know about gamma"]] by John Novak. In this library, the {{rgb8}} type is gamma compressed, and the {{rgb}} type is not. The {{hsl}} type's lightness component is effectively gamma compressed. The color type conversion procedures such as {{rgb->rgb8}} handle gamma automatically. You can also use {{gamma-compress}} and {{gamma-expand}} (see below) to handle gamma manually. For the best looking and most correct results, you should perform [[#color-math|math operations]] using {{rgb}}, not {{rgb8}}. However, {{rgb}} uses more memory and CPU, so you might want to use {{rgb8}} in cases where maximimum performance is more important than appearance or correctness. You should also use {{rgb8}} if you want to match the output of software that does not support linear RGB. (gamma-compress n) → float Performs gamma compression (encoding) of the real number {{n}}, converting from linear RGB space to sRGB space. This implements the sRGB forward transfer function, which approximates a gamma of 2.2. {{n}} is usually in the interval [0,1], and the result is usually in the interval [0,1]. To convert from a linear RGB component to an 8-bit sRGB component, do this: {{(truncate (* 255 (gamma-compress n)))}} (gamma-expand n) → float Performs gamma expansion (decoding) of the real number {{n}}, converting from sRGB space to linear RGB space. This implements the sRGB reverse transfer function, which approximates a gamma of 2.2. {{n}} is usually in the interval [0,1], and the result is usually in the interval [0,1]. To convert from an 8-bit sRGB component to a linear RGB component, do this: {{(gamma-expand (/ n 255.0))}} === Low-level For advanced users, macaw provides low-level versions of many procedures. These low-level procedures work directly with pointers or [[/man/5/Module (chicken locative)|locatives]]. They give you more control to optimize performance-critical parts of your code, but they are less convenient and less safe. ==== Caution! '''Pointers and locatives must be used with caution to avoid buffer overflows, segmentation faults, and other serious problems! These problems can crash your program or cause security vulnerabilities!''' You must ensure that pointers and locatives refer to data of the correct type and size. For pointers and ''weak'' locatives, you must also ensure that the memory has not been garbage collected or freed. For colors, you can get a compatible pointer from a color object using {{rgb-pointer}} etc. Or you can use a pointer to static memory, a locative of a blob, etc. The argument name indicates what type is required: * Arguments starting with {{float*_}} must be a pointer to memory containing at least four {{float}}s, or a locative of a f32vector with at least four elements, or a locative of a blob of at least 16 bytes. * Arguments starting with {{uchar*_}} must be a pointer to memory containing at least four {{unsigned char}}s, or a locative of a u8vector with at least four elements, or a locative of a blob of at least 4 bytes. * Arguments starting with {{float_}} must be a real number (not a pointer or locative). For arrays, the size of memory required depends on the array's pitch (number of bytes per row, including padding), and the array's height (number of rows). The exact requirements are [[#arrays-from-memory|described below]] for each array type. ==== Colors from memory The color types in this library usually hold a [[/man/5/Module (chicken locative)|locative]] of a [[/man/5/Module srfi-4|SRFI-4 numeric vector]] (e.g. f32vector). But advanced users can use the procedures below to create colors from pointers to static memory, locatives of blobs, etc. '''[[#caution|This must be used with caution!]]''' For example, this can be used to efficiently access parts of a large block of pixel data in memory. The color will directly read and write to the memory, without the expense of copying back and forth. (rgb-at float*_pointer #!optional parent) → rgb (rgb-pointer rgb_1) → pointer or locative (set! (rgb-pointer rgb_1) float*_pointer) (rgb-parent rgb_1) → any (set! (rgb-parent rgb_1) parent) {{rgb-at}} creates an {{rgb}} color which reads and writes to the memory at {{pointer}}, which can be a pointer or a locative. The memory starting at {{pointer}} must contain four 32-bit floats in the order {{r}}, {{g}}, {{b}}, {{a}}. You can use {{rgb-pointer}} to get or set the pointer. The optional {{parent}} can be any object that the color depends on. This reference prevents the parent from being garbage collected while the color is still accessing the parent's memory. (rgb8-at uchar*_pointer #!optional parent) → rgb8 (rgb8-pointer rgb8_1) → pointer or locative (set! (rgb8-pointer rgb8_1) uchar*_pointer) (rgb8-parent rgb8_1) → any (set! (rgb8-parent rgb8_1) parent) {{rgb8-at}} creates an {{rgb8}} color which reads and writes to the memory at {{pointer}}, which can be a pointer or a locative. The memory starting at {{pointer}} must contain four unsigned 8-bit integers in the order {{r}}, {{g}}, {{b}}, {{a}}. You can use {{rgb8-pointer}} to get or set the pointer. The optional {{parent}} can be any object that the color depends on. This reference prevents the parent from being garbage collected while the color is still accessing the parent's memory. (hsl-at float*_pointer #!optional parent) → hsl (hsl-pointer hsl_1) → pointer or locative (set! (hsl-pointer hsl_1) float*_pointer) (hsl-parent hsl_1) → any (set! (hsl-parent hsl_1) parent) {{hsl-at}} creates an {{hsl}} color which reads and writes to the memory at {{pointer}}, which can be a pointer or a locative. The memory starting at {{pointer}} must contain four 32-bit floats in the order {{h}}, {{s}}, {{l}}, {{a}}. You can use {{hsl-pointer}} to get or set the pointer. The optional {{parent}} can be any object that the color depends on. This reference prevents the parent from being garbage collected while the color is still accessing the parent's memory. ==== Arrays from memory The array types in this library usually hold a [[/man/5/Module (chicken locative)|locative]] of a [[/man/5/Module srfi-4|SRFI-4 numeric vector]] (e.g. f32vector). But advanced users can use the procedures below to create arrays from pointers to static memory, locatives of blobs, etc. '''[[#caution|This must be used with caution!]]''' For example, this can be used to efficiently access a large block of pixel data in memory. The array will directly read and write to the memory, without the expense of copying back and forth. (rgb-array-at pointer width height #!optional pitch) → rgb-array (rgb-array-parent rgb-array_1) → any (set! (rgb-array-parent rgb-array_1) parent) {{rgb-array-at}} creates an {{rgb-array}} which reads and writes to the memory at {{pointer}}, which can be a pointer or a locative. If {{pitch}} is omitted, it defaults to {{(* width 16)}}. The memory starting at {{pointer}} must be at least {{(* pitch height)}} bytes long, containing groups of four 32-bit floats in the order {{r}}, {{g}}, {{b}}, {{a}}. {{rgb-array-parent}} gets or sets the {{rgb-array}}'s parent, which can be any object that the array depends on. This reference prevents the parent from being garbage collected while the array is still accessing the parent's memory. (rgb8-array-at pointer width height #!optional pitch) → rgb8-array (rgb8-array-parent rgb8-array_1) → any (set! (rgb8-array-parent rgb8-array_1) parent) {{rgb8-array-at}} creates an {{rgb8-array}} which reads and writes to the memory at {{pointer}}, which can be a pointer or a locative. If {{pitch}} is omitted, it defaults to {{(* width 4)}}. The memory starting at {{pointer}} must be at least {{(* pitch height)}} bytes long, containing groups of four unsigned 8-bit integers in the order {{r}}, {{g}}, {{b}}, {{a}}. {{rgb8-array-parent}} gets or sets the {{rgb8-array}}'s parent, which can be any object that the array depends on. This reference prevents the parent from being garbage collected while the array is still accessing the parent's memory. (hsl-array-at pointer width height #!optional pitch) → hsl-array (hsl-array-parent hsl-array_1) → any (set! (hsl-array-parent hsl-array_1) parent) {{hsl-array-at}} creates an {{hsl-array}} which reads and writes to the memory at {{pointer}}, which can be a pointer or a locative. If {{pitch}} is omitted, it defaults to {{(* width 16)}}. The memory starting at {{pointer}} must be at least {{(* pitch height)}} bytes long, containing groups of four 32-bit floats in the order {{h}}, {{s}}, {{l}}, {{a}}. {{hsl-array-parent}} gets or sets the {{hsl-array}}'s parent, which can be any object that the array depends on. This reference prevents the parent from being garbage collected while the array is still accessing the parent's memory. ==== Low-level array operations (array-ref-pointer array_1 x y) → pointer or locative (rgb-array-ref-pointer rgb-array_1 x y) → pointer or locative (rgb8-array-ref-pointer rgb8-array_1 x y) → pointer or locative (hsl-array-ref-pointer hsl-array_1 x y) → pointer or locative Like {{array-ref}} etc. but returns a pointer or locative instead of a color object. '''[[#caution|This must be used with caution!]]''' The return value will be a pointer if the array holds a pointer, or a locative if the array holds a locative. These procedures are equivalent to e.g. {{(rgb-pointer (rgb-array-ref rgb-array_1 x y))}} but much more efficient. They are useful for optimization when using [[#low-level-color-math|low-level color math procedures]]. (array-for-each-pointer proc array ...) (rgb-array-for-each-pointer proc rgb-array ...) (rgb8-array-for-each-pointer proc rgb8-array ...) (hsl-array-for-each-pointer proc hsl-array ...) Like {{array-for-each}} etc. but {{proc}} is called with pointers or locatives instead of color objects. '''[[#caution|This must be used with caution!]]''' The proc will be called with a pointer if the array holds a pointer, or a locative if the array holds a locative. (array-for-each-pointer (lambda (x y hsl-pointer_1 rgb8-pointer_2) (low-hsl->rgb8! hsl-pointer_1 rgb8-pointer_2)) hsl-array_1 rgb8-array_2) ==== Low-level color math These procedures perform the same computations as the [[#color-math|color math]] procedures, but they operate directly on pointers or locatives. '''[[#caution|These procedures must be used with caution!]]''' The last argument to every procedure below is the output pointer, where the result of the operation will be written. The output pointer can be the same as an input pointer, which will cause the input to be overwritten with the result. (low-rgb->rgb8! float*_in uchar*_out) (low-rgb->hsl! float*_in float*_out) (low-rgb8->rgb! uchar*_in float*_out) (low-rgb8->hsl! uchar*_in float*_out) (low-hsl->rgb! float*_in float*_out) (low-hsl->rgb8! float*_in uchar*_out) [[#low-level|Low-level]] versions of {{rgb->rgb8}} etc. (low-rgb-add! float*_in1 float*_in2 float*_out) (low-rgb8-add! uchar*_in1 uchar*_in2 uchar*_out) (low-hsl-add! float*_in1 float*_in2 float*_out) [[#low-level|Low-level]] versions of {{rgb-add}} etc. (low-rgb-sub! float*_in1 float*_in2 float*_out) (low-rgb8-sub! uchar*_in1 uchar*_in2 uchar*_out) (low-hsl-sub! float-in1 float*_in2 float*_out) [[#low-level|Low-level]] versions of {{rgb-sub}} etc. (low-rgb-mul! float*_in1 float*_in2 float*_out) (low-rgb8-mul! uchar*_in1 uchar*_in2 uchar*_out) (low-hsl-mul! float*_in1 float*_in2 float*_out) [[#low-level|Low-level]] versions of {{rgb-mul}} etc. (low-rgb-scale! float*_in float_n float*_out) (low-rgb8-scale! uchar*_in float_n uchar*_out) (low-hsl-scale! float*_in float_n float*_out) [[#low-level|Low-level]] versions of {{rgb-scale}} etc. (low-rgb-lerp! float*_in1 float*_in2 float_t float*_out) (low-rgb8-lerp! uchar*_in1 uchar*_in2 float_t uchar*_out) (low-hsl-lerp! float*_in1 float*_in2 float_t float*_out) [[#low-level|Low-level]] versions of {{rgb-lerp}} etc. (low-hsl-mix! float*_accum float*_color float_weight float*_out) (low-rgb-mix! float*_accum float*_color float_weight float*_out) (low-rgb8-mix! uchar*_accum uchar*_color float_weight uchar*_out) [[#low-level|Low-level]] versions of {{rgb-mix}} etc. This performs a weighted addition of {{color}} onto {{accum}}, writing the result to {{out}} (which is usually the same pointer as {{accum}}). Normally {{accum}} should start with all values set to zero, and the procedure should be called once per color being mixed. (low-rgb-under! float*_in1 float*_in2 float*_out) (low-rgb8-under! uchar*_in1 uchar*_in2 uchar*_out) (low-hsl-under! float*_in1 float*_in2 float*_out) [[#low-level|Low-level]] versions of {{rgb-under}} etc. (low-rgb-over! float*_in1 float*_in2 float*_out) (low-rgb8-over! uchar*_in1 uchar*_in2 uchar*_out) (low-hsl-over! float*_in1 float*_in2 float*_out) [[#low-level|Low-level]] versions of {{rgb-over}} etc. === Compatibility with other libraries Macaw is designed to be easy to use with other libraries. Explanations and helper procedures are provided below. This code is released by its author(s) to the public domain. ==== Generic conversion Most color libraries have an equivalent of macaw's {{rgb8}} type, with red, green, blue, and possibly alpha, represented as integers from 0 to 255. You can convert any macaw color into {{rgb8}} using {{color->rgb8}}, then destructure it using {{rgb8->values}}, then call the other library's constructor, like so: (import (prefix macaw "macaw:")) ;;; Converts any macaw color to rgb8, destructures it, then calls ;;; make-foo with the r g b a values. (define (macaw->foo color) (let-values (((r g b a) (macaw:rgb8->values (macaw:color->rgb8 color)))) (make-foo r g b a))) ;;; Converts a foo color to a macaw rgb8, assuming that foo->list ;;; returns an (r g b) or (r g b a) list. (define (foo->rgb8 color) (apply macaw:rgb8 (foo->list color))) ==== sdl2 The generic approach described above can be used with the [[/egg/sdl2|sdl2 egg]], but much greater performance is possible because the [[#rgb8|{{rgb8}}]] type has the same memory layout as {{sdl2:color}}. Likewise, the [[#rgb8-array|{{rgb8-array}}]] type has the same memory layout as [[/egg/sdl2#surface|{{sdl2:surface}}]] pixel data with the {{abgr8888}} pixel format on little-endian computers (most common), or the {{rgba8888}} pixel format on big-endian computers (less common). This means that macaw and sdl2 can directly access the same memory, with no destructuring or copying, and only minimal allocation of memory to create a new record object. You can use these helper procedures to interoperate between macaw and sdl2: ;;; This code is released by its author(s) to the public domain. (import (prefix macaw "macaw:") (prefix sdl2 "sdl2:") (prefix (only sdl2-internals wrap-color unwrap-color) "sdl2:")) ;;; Creates a new macaw:rgb8 copied from an sdl2:color. (define (sdl2->rgb8 c) (macaw:rgb8-at (sdl2:unwrap-color (sdl2:color-copy c)))) ;;; Creates a macaw:rgb8 which shares memory with an sdl2:color. (define (sdl2->rgb8/shared c) (macaw:rgb8-at (sdl2:unwrap-color c) c)) ;;; Creates a new sdl2:color copied from any type of macaw color. (define (macaw->sdl2 c) (sdl2:wrap-color (macaw:rgb8-pointer (macaw:color->rgb8/new c)))) ;;; Creates an sdl2:color which shares memory with a macaw:rgb8. ;;; You must prevent the rgb8 from being garbage collected while ;;; the sdl2:color is still using its memory. (define (rgb8->sdl2/shared c) (sdl2:wrap-color (macaw:rgb8-pointer c))) ;;; Create an sdl2:surface with a pixel format that is compatible with ;;; macaw:rgb8-array. The compatible pixel format depends on whether ;;; this computer is little-endian (most common) or big-endian. (define (make-rgb8-surface width height) (let-values (((bpp rmask gmask bmask amask) (sdl2:pixel-format-enum-to-masks (cond-expand (little-endian 'abgr8888) (else 'rgba8888))))) (sdl2:create-rgb-surface* 0 width height bpp rmask gmask bmask amask))) ;;; Creates a macaw:rgb8-array that accesses the surface's pixel data. ;;; The surface must have the correct pixel format for this computer. (define (surface->rgb8-array/shared surface) (assert (eq? (sdl2:pixel-format-format (sdl2:surface-format surface)) (cond-expand (little-endian 'abgr8888) (else 'rgba8888))) "surface pixel format not compatible with rgb8-array" (sdl2:pixel-format-format (sdl2:surface-format surface))) (let ((array (macaw:rgb8-array-at (sdl2:surface-pixels-raw surface) (sdl2:surface-w surface) (sdl2:surface-h surface) (sdl2:surface-pitch surface)))) ;; Set surface as array's parent to prevent surface from being ;; garbage collected while array is still using its memory. (set! (macaw:rgb8-array-parent array) surface) array)) ;;; Creates an sdl2:surface that accesses the rgb8-array's memory. ;;; You must prevent the rgb8-array from being garbage collected ;;; while the surface is still using its memory. (define (rgb8-array->surface/shared array) (let-values (((bpp rmask gmask bmask amask) (sdl2:pixel-format-enum-to-masks (cond-expand (little-endian 'abgr8888) (else 'rgba8888))))) (sdl2:create-rgb-surface-from* (macaw:rgb8-array-pointer array) (macaw:rgb8-array-width array) (macaw:rgb8-array-height array) bpp (macaw:rgb8-array-pitch array) rmask gmask bmask amask))) ==== web-colors Macaw colors are easily converted to lists compatible with the [[/egg/web-colors|web-colors]] egg. This process is similar to the [[#generic-conversion|generic conversion]] process except that web colors always use an alpha range of 0.0 to 1.0, whereas macaw's {{rgb8}} type uses an alpha range of 0 to 255. You can use these helper procedures to interoperate between macaw and web-colors: ;;; This code is released by its author(s) to the public domain. (import (prefix macaw "macaw:") (prefix web-colors "wc:")) ;;; Creates a new macaw color from any web color list. (define (web-color->macaw wc) (case (and (wc:color-list? wc) (car wc)) ((rgb) (macaw:rgb8 (list-ref wc 1) (list-ref wc 2) (list-ref wc 3) (inexact->exact (floor (* 255 (list-ref wc 4)))))) ((rgb%) (apply macaw:rgb (map exact->inexact (cdr wc)))) ((hsl) (apply macaw:hsl (map exact->inexact (cdr wc)))) (else (error "Not a web-color list" wc)))) ;;; Creates a new web color list from any macaw color. (define (macaw->web-color c) (cond ((macaw:rgb8? c) (let-values (((r g b a) (macaw:rgb8->values c))) (list 'rgb r g b (/ a 255.0)))) ((macaw:rgb? c) (cons 'rgb% (macaw:rgb->list c))) ((macaw:hsl? c) (cons 'hsl (macaw:hsl->list c))) (else (error "Not a macaw color" c)))) ;;; Attempts to parse the given web color string and return it as ;;; a new macaw color. Raises an exception if parsing fails. (define (color-string->macaw s) (web-color->macaw (wc:parse-web-color s))) ;;; Returns a web color string from any macaw color. ;;; Values are rounded to specified precision to compensate for ;;; floating point rounding error, so the output is more friendly. (define (macaw->color-string c #!optional (precision 3)) (define (round-float n) (/ (round (* n (expt 10 precision))) (expt 10 precision))) (let ((wc (macaw->web-color c))) (wc:web-color->string (cons (car wc) (map round-float (cdr wc)))))) ;;; Returns a #RRGGBB or #RRGGBBAA string from any macaw color. ;;; The macaw color is converted to rgb8. (define (macaw->hex-string c) (let-values (((r g b a) (macaw:rgb8->values (macaw:color->rgb8 c)))) (wc:rgb-color->hex-string (list 'rgb r g b (/ a 255.0))))) === Version history ; 0.1.0 (2020-04-20): Initial release. {{rgb}}, {{rgb8}}, and {{hsl}} color types. {{rgb-array}}, {{rgb8-array}}, and {{hsl-array}} types. {{add}}, {{sub}}, {{mul}}, {{scale}}, {{lerp}}, {{mix}}, {{under}}, and {{over}} math operations.