chickadee » macaw

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

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? xprocedure

Returns #t if x is an rgb color, or #f if it is anything else.

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? xprocedure

Returns #t if x is an rgb8 color, or #f if it is anything else.

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? xprocedure

Returns #t if x is an hsl color, or #f if it is anything else.

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

In-place versions of rgb-add etc.

rgb-sub! rgb_1 color ...procedure
rgb8-sub! rgb8_1 color ...procedure
hsl-sub! hsl_1 color ...procedure

In-place versions of rgb-sub etc.

rgb-mul! rgb_1 color ...procedure
rgb8-mul! rgb8_1 color ...procedure
hsl-mul! hsl_1 color ...procedure

In-place versions of rgb-mul etc.

rgb-scale! rgb_1 nprocedure
rgb8-scale! rgb8_1 nprocedure
hsl-scale! hsl_1 nprocedure

In-place versions of rgb-scale etc.

rgb-lerp! rgb_1 color_2 tprocedure
rgb8-lerp! rgb8_1 color_2 tprocedure
hsl-lerp! hsl_1 color_2 tprocedure

In-place versions of rgb-lerp etc.

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

In-place versions of rgb-under etc.

rgb-over! rgb_1 color ...procedure
rgb8-over! rgb8_1 color ...procedure
hsl-over! hsl_1 color ...procedure

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 "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:

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-level versions of rgb->rgb8 etc.

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-level versions of rgb-add etc.

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-level versions of rgb-sub etc.

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-level versions of rgb-mul etc.

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-level versions of rgb-scale etc.

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-level versions of rgb-lerp etc.

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-level versions of rgb-under etc.

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

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 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)))))

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.

Contents »