macaw
- Project / Source Code Repository
- https://gitlab.com/jcroisant/macaw
- Issue Tracker
- https://gitlab.com/jcroisant/macaw/issues
- Maintainer
- John Croisant
- License
- 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 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? xprocedure
Returns #t if x is a color of any type defined in this library, or #f if it is anything else.
- color->rgb color_1procedure
- color->rgb8 color_1procedure
- color->hsl color_1procedure
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_1procedure
- color->rgb8/new color_1procedure
- color->hsl/new color_1procedure
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 compressed. Its alpha is not 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)procedure
Creates a new rgb color. All values are floats, usually in the interval [0,1].
- rgb-r rgb_1procedure
- rgb-g rgb_1procedure
- rgb-b rgb_1procedure
- rgb-a rgb_1procedure
- rgb-r-set! rgb_1 rsetter
- rgb-g-set! rgb_1 gsetter
- rgb-b-set! rgb_1 bsetter
- rgb-a-set! rgb_1 asetter
- set! (rgb-r rgb_1) rsetter
- set! (rgb-g rgb_1) gsetter
- set! (rgb-b rgb_1) bsetter
- set! (rgb-a rgb_1) asetter
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 aprocedure
Sets every component of the rgb color, then returns the modified color. This is more efficient than setting each component individually.
- rgb= rgb_1 rgb_2procedure
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-05)procedure
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_1procedure
Returns a new copy of the rgb color.
- rgb-copy! rgb_src rgb_dstprocedure
Copies data from rgb_src to rgb_dst. Modifies and returns rgb_dst.
- rgb-normalize rgb_1procedure
- rgb-normalize! rgb_1procedure
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_1procedure
Returns a list of the rgb color's values, (r g b a).
- rgb->values rgb_1procedure
Returns the rgb color's values as multiple values.
- rgb->rgb8 rgb_1procedure
- rgb->hsl rgb_1procedure
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 compressed. Its alpha is not 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)procedure
Creates a new rgb8 color. All arguments must be integers in the interval [0,255].
- rgb8-r rgb8_1procedure
- rgb8-g rgb8_1procedure
- rgb8-b rgb8_1procedure
- rgb8-a rgb8_1procedure
- rgb8-r-set! rgb8_1 rsetter
- rgb8-g-set! rgb8_1 gsetter
- rgb8-b-set! rgb8_1 bsetter
- rgb8-a-set! rgb8_1 asetter
- set! (rgb8-r rgb8_1) rsetter
- set! (rgb8-g rgb8_1) gsetter
- set! (rgb8-b rgb8_1) bsetter
- set! (rgb8-a rgb8_1) asetter
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 aprocedure
Sets every component of the rgb8 color, then returns the modified color. This is more efficient than setting each component individually.
- rgb8= rgb8_1 rgb8_2procedure
Returns #t if rgb8_1 and rgb8_2 have exactly the same values.
- rgb8-copy rgb8_1procedure
Returns a new copy of the rgb8 color.
- rgb8-copy! rgb8_src rgb8_dstprocedure
Copies data from rgb8_src to rgb8_dst. Modifies and returns rgb8_dst.
- rgb8->list rgb8_1procedure
Returns a list of the rgb8 color's values, (r g b a).
- rgb8->values rgb8_1procedure
Returns the rgb8 color's values as multiple values.
- rgb8->rgb rgb8_1procedure
- rgb8->hsl rgb8_1procedure
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 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)procedure
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_1procedure
- hsl-s hsl_1procedure
- hsl-l hsl_1procedure
- hsl-a hsl_1procedure
- hsl-h-set! hsl_1 hsetter
- hsl-s-set! hsl_1 ssetter
- hsl-l-set! hsl_1 lsetter
- hsl-a-set! hsl_1 asetter
- set! (hsl-h hsl_1) hsetter
- set! (hsl-s hsl_1) ssetter
- set! (hsl-l hsl_1) lsetter
- set! (hsl-a hsl_1) asetter
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 aprocedure
Sets every component of the hsl color, then returns the modified color. This is more efficient than setting each component individually.
- hsl= hsl_1 hsl_2procedure
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-05)procedure
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_1procedure
Returns a new copy of the hsl color.
- hsl-copy! hsl_src hsl_dstprocedure
Copies data from hsl_src to hsl_dst. Modifies and returns hsl_dst.
- hsl-normalize hsl_1procedure
- hsl-normalize! hsl_1procedure
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_1procedure
Returns a list of the hsl color's values, (h s l a).
- hsl->values hsl_1procedure
Returns the hsl color's values as multiple values.
- hsl->rgb hsl_1procedure
- hsl->rgb8 hsl_1procedure
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 section for how to use rgb8-array to access the pixels of an sdl2:surface.
Generic array procedures
- array? xprocedure
Returns #t if x is an array of any type defined in this library, or #f if it is anything else.
- array-width array_1procedure
- array-height array_1procedure
- array-pitch array_1procedure
Returns the width, height, or pitch of an array of any type.
- array-ref array_1 x yprocedure
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 ...procedure
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 pitchprocedure
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? xprocedure
Returns #t if x is an rgb-array, otherwise #f.
- rgb-array-width rgb-array_1procedure
- rgb-array-height rgb-array_1procedure
- rgb-array-pitch rgb-array_1procedure
Returns the width, height, or pitch of the rgb-array.
- rgb-array-ref rgb-array_1 x yprocedure
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 ...procedure
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 pitchprocedure
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? xprocedure
Returns #t if x is an rgb8-array, otherwise #f.
- rgb8-array-width rgb8-array_1procedure
- rgb8-array-height rgb8-array_1procedure
- rgb8-array-pitch rgb8-array_1procedure
Returns the width, height, or pitch of the rgb8-array.
- rgb8-array-ref rgb8-array_1 x yprocedure
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 ...procedure
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 pitchprocedure
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? xprocedure
Returns #t if x is an hsl-array, otherwise #f.
- hsl-array-width hsl-array_1procedure
- hsl-array-height hsl-array_1procedure
- hsl-array-pitch hsl-array_1procedure
Returns the width, height, or pitch of the hsl-array.
- hsl-array-ref hsl-array_1 x yprocedure
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 ...procedure
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 section for versions of these operations which modify in-place (destructively), and the Low-level color math section for versions of these operations which work directly on pointers or locatives.
- rgb-add color_1 color ...procedure
- rgb8-add color_1 color ...procedure
- hsl-add color_1 color ...procedure
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 ...procedure
- rgb8-sub color_1 color ...procedure
- hsl-sub color_1 color ...procedure
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 ...procedure
- rgb8-mul color_1 color ...procedure
- hsl-mul color_1 color ...procedure
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 nprocedure
- rgb8-scale color_1 nprocedure
- hsl-scale color_1 nprocedure
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 tprocedure
- rgb8-lerp color_1 color_2 tprocedure
- hsl-lerp color_1 color_2 tprocedure
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-listprocedure
- rgb8-mix color-list #!optional weight-listprocedure
- hsl-mix color-list #!optional weight-listprocedure
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 ...procedure
- rgb8-under color_1 color ...procedure
- hsl-under color_1 color ...procedure
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 ...procedure
- rgb8-over color_1 color ...procedure
- hsl-over color_1 color ...procedure
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 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 ...procedure
- rgb8-add! rgb8_1 color ...procedure
- hsl-add! hsl_1 color ...procedure
- rgb-sub! rgb_1 color ...procedure
- rgb8-sub! rgb8_1 color ...procedure
- hsl-sub! hsl_1 color ...procedure
- rgb-mul! rgb_1 color ...procedure
- rgb8-mul! rgb8_1 color ...procedure
- hsl-mul! hsl_1 color ...procedure
- rgb-scale! rgb_1 nprocedure
- rgb8-scale! rgb8_1 nprocedure
- hsl-scale! hsl_1 nprocedure
- rgb-lerp! rgb_1 color_2 tprocedure
- rgb8-lerp! rgb8_1 color_2 tprocedure
- hsl-lerp! hsl_1 color_2 tprocedure
- rgb-mix! color-list #!optional weight-listprocedure
- rgb8-mix! color-list #!optional weight-listprocedure
- hsl-mix! color-list #!optional weight-listprocedure
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 ...procedure
- rgb8-under! rgb8_1 color ...procedure
- hsl-under! hsl_1 color ...procedure
- rgb-over! rgb_1 color ...procedure
- rgb8-over! rgb8_1 color ...procedure
- hsl-over! hsl_1 color ...procedure
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 "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 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 nprocedure
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 nprocedure
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 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 floats, 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 chars, 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 described below for each array type.
Colors from memory
The color types in this library usually hold a locative of a 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. 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 parentprocedure
- rgb-pointer rgb_1procedure
- set! (rgb-pointer rgb_1) float*_pointersetter
- rgb-parent rgb_1procedure
- set! (rgb-parent rgb_1) parentsetter
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 parentprocedure
- rgb8-pointer rgb8_1procedure
- set! (rgb8-pointer rgb8_1) uchar*_pointersetter
- rgb8-parent rgb8_1procedure
- set! (rgb8-parent rgb8_1) parentsetter
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 parentprocedure
- hsl-pointer hsl_1procedure
- set! (hsl-pointer hsl_1) float*_pointersetter
- hsl-parent hsl_1procedure
- set! (hsl-parent hsl_1) parentsetter
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 locative of a 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. 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 pitchprocedure
- rgb-array-parent rgb-array_1procedure
- set! (rgb-array-parent rgb-array_1) parentsetter
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 pitchprocedure
- rgb8-array-parent rgb8-array_1procedure
- set! (rgb8-array-parent rgb8-array_1) parentsetter
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 pitchprocedure
- hsl-array-parent hsl-array_1procedure
- set! (hsl-array-parent hsl-array_1) parentsetter
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 yprocedure
- rgb-array-ref-pointer rgb-array_1 x yprocedure
- rgb8-array-ref-pointer rgb8-array_1 x yprocedure
- hsl-array-ref-pointer hsl-array_1 x yprocedure
Like array-ref etc. but returns a pointer or locative instead of a color object. 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 procedures.
- array-for-each-pointer proc array ...procedure
- rgb-array-for-each-pointer proc rgb-array ...procedure
- rgb8-array-for-each-pointer proc rgb8-array ...procedure
- hsl-array-for-each-pointer proc hsl-array ...procedure
Like array-for-each etc. but proc is called with pointers or locatives instead of color objects. 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 procedures, but they operate directly on pointers or locatives. 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*_outprocedure
- low-rgb->hsl! float*_in float*_outprocedure
- low-rgb8->rgb! uchar*_in float*_outprocedure
- low-rgb8->hsl! uchar*_in float*_outprocedure
- low-hsl->rgb! float*_in float*_outprocedure
- low-hsl->rgb8! float*_in uchar*_outprocedure
- low-rgb-add! float*_in1 float*_in2 float*_outprocedure
- low-rgb8-add! uchar*_in1 uchar*_in2 uchar*_outprocedure
- low-hsl-add! float*_in1 float*_in2 float*_outprocedure
- low-rgb-sub! float*_in1 float*_in2 float*_outprocedure
- low-rgb8-sub! uchar*_in1 uchar*_in2 uchar*_outprocedure
- low-hsl-sub! float-in1 float*_in2 float*_outprocedure
- low-rgb-mul! float*_in1 float*_in2 float*_outprocedure
- low-rgb8-mul! uchar*_in1 uchar*_in2 uchar*_outprocedure
- low-hsl-mul! float*_in1 float*_in2 float*_outprocedure
- low-rgb-scale! float*_in float_n float*_outprocedure
- low-rgb8-scale! uchar*_in float_n uchar*_outprocedure
- low-hsl-scale! float*_in float_n float*_outprocedure
- low-rgb-lerp! float*_in1 float*_in2 float_t float*_outprocedure
- low-rgb8-lerp! uchar*_in1 uchar*_in2 float_t uchar*_outprocedure
- low-hsl-lerp! float*_in1 float*_in2 float_t float*_outprocedure
- low-hsl-mix! float*_accum float*_color float_weight float*_outprocedure
- low-rgb-mix! float*_accum float*_color float_weight float*_outprocedure
- low-rgb8-mix! uchar*_accum uchar*_color float_weight uchar*_outprocedure
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*_outprocedure
- low-rgb8-under! uchar*_in1 uchar*_in2 uchar*_outprocedure
- low-hsl-under! float*_in1 float*_in2 float*_outprocedure
- low-rgb-over! float*_in1 float*_in2 float*_outprocedure
- low-rgb8-over! uchar*_in1 uchar*_in2 uchar*_outprocedure
- low-hsl-over! float*_in1 float*_in2 float*_outprocedure
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 sdl2 egg, but much greater performance is possible because the rgb8 type has the same memory layout as sdl2:color. Likewise, the rgb8-array type has the same memory layout as 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 web-colors egg. This process is similar to the 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)))))