REBOL
Docs Blog Get-it

REBOL/View Image Datatype

Describes the details of using the Image datatype.

Contents

History
Image Datatype Structure
Common Shortcuts
Creating Images
Load
Make
Copy
To
Image Access Refinements
/size
/rgb
/alpha
Traversing Images (Indexing)
Index? and Length? Functions
Getting Pixel Values
Modifying Images
Changing Pixels
Changing Multiple Pixels
Inserting Pixels
Removing Pixels
Image Comparison
Searching Images
ANDing, ORing, and XORing Images
Binary Conversion of Images
Saving and Molding Images
Wish List

History

In REBOL/View 1.3 it was necessary to redesign the image datatype in order to make image operations more consistent and less system dependent. Prior versions of the image datatype were kept as simple one-dimensional binary series encoded as blue, green, red, and alpha bytes. In REBOL/View 1.3 the image datatype works more appropriately for images and for the pixel-based two dimensional orientation that is standard with images.

These changes will affect some existing REBOL/View code that depended on images using the prior byte-based binary series methods. We would normally hesitate to make a change of this magnitude; however, the prior implementation was problematic, and the changes to the Image datatype are so important and fundamental that they needed to be made regardless of the compatibility consequences. It is better done now, rather than later.

Image Datatype Structure

The image datatype is structured as a standard REBOL series. It has a head, a tail, and can be positioned to any point in-between. In additional, the new image datatype allows two dimensional positioning and sizing through the use of an X Y pair.

The elements of an image data type are pixels. This is a change from the past version of REBOL/View where the individual elements were bytes. In the new version pixels are RGBA tuple values (r.g.b.a) and indexing is done on a pixel basis rather than on an individual byte basis. The values all range from 0 to 255 (dark to light) and for the alpha channel 0 is opaque and 255 is totally transparent. For example, 255.255.0.0 is opaque yellow.

Note that it is still possible to obtain a copy of the underlying RGBA binary series through the use of the TO-BINARY function. More about that later.

The pixel values within an image can be manipulated using the standard series functions of REBOL such as CHANGE, INSERT, REMOVE, CLEAR, etc. In addition, there are three datatype accessors that are available for the image datatype.

Common Shortcuts

For users who are too busy to read the details of this document, here are a few of the most common image function "idioms". But before reading this all users should understand that image effects should generally be done using the face EFFECT engine (see View and VID documentation), not by modifying the image data.

To load an image (let's say we call it "img") from a file into memory:

img: load %photo.jpg

To clear an image to black:

img/rgb: black

To clear an image to white:

img/rgb: white

To make the image opaque (remove all transparency):

img/alpha: 0

To set an area (at 10x20 by 40x30) of an image to blue:

change/dup at img 10x20 blue 40x30

To copy an image (img2) into another image (img1) at a given location (xy):

xy: 10x10
change at img1 xy img2

To add an image (img2) to the bottom of another image (img1):

append img1 img2  ; uses insert

To extract part of an image as a separate image:

img2: copy/part img1 40x30 ; width and height

To extract a dozen "sub-images" from a single image (that is organized as a grid that is 4 images wide and 3 high):

imgs: copy []
size: 40x30  ; width and height of each subimage
repeat y 3 [
    repeat x 4 [
        xy: size * as-pair x - 1 y - 1
        append imgs copy/part at img xy size
    ]
]

To set the top line of an image to blue:

change/dup img blue img/size/x

To remove the top line of an image:

remove/part img img/size/x

To remove the bottom line of an image:

remove/part tail img negate img/size/x

To add a blue line to the top of an image:

insert/part img blue img/size/x

To add a blue line to the bottom of an image:

insert/part tail img blue img/size/x

Creating Images

Images can be created in a variety of ways using the LOAD, MAKE, COPY, and TO functions.

Load

The LOAD function will read and convert external file formats such as JPEG and PNG to the image datatype format. The LOAD function works as it did in prior versions of REBOL with the exception that the result is an image datatype that is accessed as pixels rather than as RGB bytes.

image: load %photo.jpg
print image? image

Make

The MAKE function is the primary constructor for new images. It allows you to specify the separate components of an image including the size, the RGB data, and the alpha data as separate values. In addition the MAKE function can perform conversion and copying operations from other datatypes. The general format is:

img: make image! [size-pair rgb-data alpha-data]

The MAKE function accepts as an argument either a size (width and height) or a block that contains a size, optional RGB binary, and optional alpha binary. The block is not normally evaluated, so a REDUCE is required if it contains variables or expressions.

Examples:

i1: make image! 10x20

rgb: read/binary %rgb.data ; 3 bytes per pixel
i2: make image! reduce [20x30 rgb]

alpha: read/binary %alpha.data ; 1 byte per pixel
i3: make image! reduce [20x30 rgb alpha]

The rgb-data can be an RGB tuple. If so, the optional alpha data can be an integer:

i1: make image! [10x10 255.0.0]
i1: make image! reduce [10x10 red]
i1: make image! reduce [10x10 red 128] ; semi-transparent

The argument can also be an image. This is similar to the COPY function:

i2: make image! i1

And, to be consistent with other REBOL datatypes, the image! datatype argument can be an actual image:

i2: make i1 []
i3: make i1 reduce [10x20 rgb2]

Copy

The COPY function works in the standard way as it does with all REBOL series, making an exact copy of the image provided.

i2: copy i1

The /part refinement allows you to copy part of an image. For example:

i2: copy/part i1 10

will copy only the first 10 pixels of the image.

COPY is also able to copy rectangular sub-areas of an image. This is useful for extracting sub-images or for cropping an image. This example copies a 5x5 subimage:

i2: copy/part i1 5x5

You can use the indexing functions to set the position of the COPY. The example below skips the first four lines and pixels before it copies a 5x5 subimage:

i2: copy/part skip i1 4x4 5x5

This example crops off the outer two pixels of an image on all sides:

i2: copy/part skip i1 2x2 (i1/size - 4x4)

Any of the series indexing functions can be used to position within the image. See the section below.

To

The TO function is used for conversion. Specifically, the TO-IMAGE function can create an image from a View face or a binary series.

Here is one of the more useful examples of using TO-IMAGE. This example creates a face (using the VID LAYOUT function), converts it to an image, and saves it to a file:

out: layout [
    text "Example"
    button "Button"
]
out-image: to-image out
save/png %out-image.png out-image

See the Binary Conversion section below for more information about the TO function.

Image Access Refinements

The image datatype provides three access refinements: /size, /rgb, and /alpha.

/size

The /size refinement will get and set the size (width and height) of an image.

For example, this prints the size of an image:

print i2/size
print ["Width" i2/size/x]
print ["Height" i2/size/y]

You can also set the size:

i2/size: 10x10

Changing the size will not change the actual RGB memory allocation that is used by the image. As a result, the height will be clipped to keep it within the bounds of the actual RGB data of the series. If what you want to do is actually expand the size of an image, use the INSERT function. See the Inserting Pixels section.

This example should help you to better understand what happens when you change an image size using /size:

i1: make image! 10x10
print length? i1  ; == returns 100

i1/size: 9x9
print i1/size     ; == returns 9x9
print length? i1  ; == returns 100

i1/size: 11x11
print i1/size     ; == returns 11x9 !
print length? i1  ; == returns 100

First, notice that the length of the image (the number of pixels in memory) has not changed. No new pixels were added or subtracted when the size was changed. This allows you to write code that loads a raw RGB binary that has no width and height information, then change the /size to make the image look correct. (See the Binary Conversion section below for an example.)

Second, notice that when the size was made too large (larger than the actual data in memory), the height of the image was reduced. REBOL does that to prevent errors in image functions that expect the image to be properly rectangular, and the partial line (at the end) will be ignored. If you want to compensate for such a change in size, insert the correct number of pixels at the tail of the image. For instance, to make the example above fully 11x11:

insert/dup tail i1 black (11 * 11) - (9 * 9)
i1/size: 11x11

/rgb

The /rgb refinement will get and set the RGB color values of an image as a binary series of three bytes (RGB) for each pixel.

rgb: i2/rgb
save %rgb.r rgb

rgb2: load %rgb.r
i2/rgb: rgb2

The alpha channel data is not affected when the /rgb refinement is used.

The /rgb refinement also performs high-speed "bulk" RGB setting over the entire image. To do this, provide an RGB tuple. For example to set the entire image to black, you can write:

i2/rgb: 0.0.0

To set the entire image to yellow, you can use either of these lines (because the word YELLOW is a constant color tuple defined by REBOL/View):

i2/rgb: yellow
i2/rgb: 255.255.0

/alpha

The /alpha refinement will get and set the alpha values of an image as a binary series of one byte per pixel.

a1: i2/alpha
save %alpha.r a1

a2: load %alpha.r
i2/alpha: a2

Bulk, high-speed setting of alpha is also allowed. It will set the alpha channel of all the pixels in an image from zero (opaque) to 255 (transparent). For example:

i2/alpha: 0   ; opaque
i2/alpha: 255 ; transparent
i2/alpha: 128 ; half transparent

Traversing Images (Indexing)

You can use all the standard REBOL series functions to index to any position within an image series. The functions include: HEAD, TAIL, NEXT, BACK, SKIP, AT, and others. Here are some common examples:

i2: next i2  ; move to next pixel
i2: back i2  ; move to prior pixel
i2: head i2  ; move to first pixel
i2: tail i2  ; move just past the last pixel
i2: back tail i2 ; move to the last pixel

The SKIP and AT functions allow you to move multiple pixels:

i2: skip i2 10 ; skip 10 (move to 11th pixel)
i2: at i2 10   ; move to 10th pixel

You can also use an XY pair for specifying a position within an image:

i2: skip i2 8x5 ; skip 8 pixels and 5 lines
i2: at i2 10x7  ; skip to the 10th pixel on the 7th line

Index? and Length? Functions

The INDEX? and LENGTH? functions are also supported with images.

The INDEX? function will return the index offset as the number of pixels from the head of the image (to be consistent with other series datatypes).

print index? i2
print index? skip i2 10

The index can be converted to an XY pair position by dividing by the width of the image (and accounting for the zero-base used for image offsets). Here is an example function that does that:

xy?: func [image [image!]] [
    as-pair 
        (index? image) - 1 // image/size/x
        (index? image) - 1 / image/size/x
]

i1: make image! 10x10
probe xy? i1 ; result is 0x0
probe xy? next i1 ; result is 1x0
probe xy? skip i1 5x5 ; result is 5x5
probe xy? tail i1 ; result is 0x10
probe xy? back tail i1 ; result is 9x9

The LENGTH? function returns the total number of pixels within the image. Note if the INSERT, REMOVE, or CLEAR functions have been used or if the /size accessor has been set, the total number of pixels might not be the same as the width times the height. In other words, there may be extra pixels after the last displayable line of the image. This is an acceptable condition for images because it allows modification of the series of image pixels as well as the size without unnecessary truncation of the trailing pixels.

print length? i1

Getting Pixel Values

You can use the standard REBOL series indexed access functions for getting the RGB and alpha pixel values for any pixel within a series. This includes the ordinal functions: FIRST, SECOND, THIRD, etc, as well as the PICK function. You can also use the path notation to access pixel values.

For each pixel, the RGBA tuple will be returned:

print first i2
print second i2
print last i2
print first skip i2 10
print first skip i2 10x10

The path notation can also be used:

print i2/1
print i2/10

Modifying Images

Images can be modified in two ways. They can be modified as individual pixels or they can be modified as a series of pixels. The series modifying functions as they are applied to images are consistent with how the series functions work for all other series in REBOL such as binary and string values.

Although you can make simple types of changes to images using the techniques described below, the View Face effect engine is capable of applying a wide range of image effects at much higher speed. When possible use the effect engine (it is also a lot easier to use).

For example, to scale an image larger or smaller, the effect engine will be many times faster than using the functions shown below.

Changing Pixels

To modify an image as individual pixels you can use POKE function or the set-path notation:

poke i2 10 255.0.0  ; set tenth pixel as red
i2/10: 255.0.0      ; same

poke i1 0x10 blue   ; set first pixel on tenth line blue
i1/100: blue        ; same (assuming width of 10)

The pixel value is represented as an RGB tuple and may also include an optional alpha channel value. If an RGB tuple does not contain the alpha value, then the alpha value in the image will not be affected.

poke i2 10 255.0.0.128  ; set tenth pixel as transparent red

If an integer is used for the pixel value it indicates an alpha channel change only. The RGB value will not be affected:

poke i2 10 128  ; set tenth pixel as half transparent
i2/10: 128      ; same

Using these two methods the RGB value and the alpha channel value of any pixel can be modified separately or they can be modified together through the use of a RGBA tuple.

Changing Multiple Pixels

Images can also be modified using the standard CHANGE, INSERT, REMOVE, and CLEAR series datatype functions. These functions can operate on linear sequences of pixels as well as rectangular blocks of pixels. (Please read Performance note above.)

The CHANGE function is by far the most useful image modification function. With change you can set a sequence of pixels (using the /dup refinement) to a specified RGB value or to a specified alpha channel value for any number of pixels.

This example changes the RGB color, but not the alpha channel:

change i2 red  ; change a single pixel at current position
change/dup i2 red 10 ; change next 10 pixels

This sets the alpha channel, but not the RGB color:

change i2 128  ; change a single pixel at current position
change/dup i2 128 10 ; change next 10 pixels

Or, you can change both RGB and alpha at the same time:

change i2 255.0.0.128
change/dup i2 255.0.0.128 10

The /only refinement can be used to change only the RGB color and leave the alpha channel as it is. This is useful if the pixel came from another image, but you don't want to use its alpha value:

color: pick src-img 10  ; includes alpha in tuple
change/only i2 color    ; does not change alpha of image
change/only/dup i2 color 10

You can specify changes to rectangular areas of an image to be set by specifying an X Y pair.

change/dup i2 red 10x10 ; set 10 pixels of 10 lines red
change/dup skip i2 5x5 red 10x10 ; with a 5x5 offset

The CHANGE function also accepts another image as an argument and will take pixels from the other image and will set them within the target image. You can also specify partial images or even single lines within the change operation.

change i2 i1  ; copy pixels from i1 to i2
change/part i2 i1 10 ; copy 10 pixels from i1 to i2
change/part i2 i1 10x10 ; copy 10x10 area from i1 to i2

For rectangular areas, the width and height will be clipped if they are too large (extend off the edges of the first image).

You can combine /dup and /part for linear sequences:

change/part/dup i2 i1 10 5 ; copy 10 pixels, 5 times
Restrictions on /part

There are some restrictions with using the /part refinement on images. You can only use /part when the source argument is a series (an image or binary), and you cannot use /part with a tuple or integer. Also, you cannot use /dup for rectangular areas when the /part size is an XY pair. If you want a tile effect, you can use the TILE word in the face effect engine.

The CHANGE function also will accept a binary series of RGB alpha bytes. For compatibility reasons the byte order is BGRA (blue, green, red, alpha) to be compatible with prior versions of REBOL.

Inserting Pixels

The INSERT function can be used to insert new pixels, lines, or rectangular areas within an image. Similar to CHANGE, the pixel data can be provided as a single pixel value that is inserted multiple times including RGB and alpha, or as a rectangular area of pixels to be affected.

The difference between CHANGE and INSERT is the same as it is with strings and other series. CHANGE overwrites pixels. INSERT will move pixels to the right, then add new pixels. (Keep this in mind for performance reasons. Using INSERT can be slow. Use CHANGE if possible, or INSERT at the end of the image if possible.)

Here are some examples:

insert tail i2 red  ; insert a single pixel
insert/dup tail i2 red 10 ; insert 10 pixels

Insert empty pixels that only set the alpha channel:

insert tail i2 128
insert/dup tail i2 128 10

Insert both RGB and alpha at the same time:

insert tail i2 255.0.0.128
insert/dup tail i2 255.0.0.128 10

You can specify inserts to rectangular areas of an image to be set by specifying an X Y pair. When this is done, entire lines are inserted and will be padded with new pixels (black) when the lines are shorter than the image width.

insert/dup tail i2 red 10x10
insert/dup skip i2 5x5 red 10x10

The INSERT function will also accept another image as data to be inserted as well as a binary series of BGRA bytes. Note that when you insert an image that has a different width than the target image, certain rules apply. If the inserted image is narrower than the target image, it will be padded with empty pixels to the width of the target image. If the inserted image is wider than the target image, it will be clipped at the width of the target image. Note that you can insert an image at an offset within the target image by using an index function such as next and skip as described earlier in this document.

insert tail i2 i1 ; insert i1 at end of i2
insert/part i2 i1 10 ; insert 10 pixels from i1 to i2
insert/part i2 i1 10x10 ; insert 10x10 area from i1 to i2

See the CHANGE function for more information and restrictions.

Removing Pixels

REMOVE and CLEAR functions can be used to remove pixels from an image. Note that removing pixels is not the same as clearing pixel colors. When you remove pixels, all remaining pixels in the image are shifted to the left. Similar to INSERT, this can be time consuming if it is done often. (See INSERT for more details about performance.) If what you want to do is clear the color of pixels, use the CHANGE function, not REMOVE or CLEAR.

The REMOVE function will remove individual pixels or any number of pixels.

remove i1  ; remove a single pixel from i1
remove back tail i1  ; remove last pixel from i1
remove/part i2 3 ; remove three pixels

When pixels are removed from an image, the height will be adjusted appropriately. For instance, if you delete half of an image using the remove function, the height value will reflect this in the /size refinement. You can remove pixels from any position within an image by first using the indexing functions such as skip and next.

remove/part at i1 0x10 10 ; remove ten pixels from line 10

The CLEAR function will remove all pixels from the current position to the end of the image. Again, you can set the position of the clear operation using the indexing functions (see the Traversing Images section above) as is done with any series.

clear i1 ; remove all pixels from current position
clear skip i1 0x10 ; Remove all lines beyond the tenth line

Image Comparison

The EQUAL?, NOT-EQUAL?, and SAME? series comparison functions work for images in the same way as they do for other series datatypes.

The EQUAL? and NOT-EQUAL? functions actually compare the individual size and pixel components of the images to determine if they are all identical. If they are, the functions return true and false respectively.

i2: copy i1
print i1 = i2
change i2 blue ; single pixel
print i1 <> i2

The SAME? function will return true only if the same exact image is provided. This is consistent with the use of equality and sameness of other series values.

i2: copy i1
print same? i1 i1
print same? i1 i2

The GREATER? and LESSER? operators are not supported for images. It is not clear whether they would refer to the size, length, or RGB components of an image. If that's what you need then compare using the image refinements, /size, /rgb, and /alpha.

Searching Images

The FIND function has changed substantially in this release of REBOL. In prior versions of REBOL, FIND for images was the same as FIND for binary strings. Because images are now represented as a series of pixel values, the FIND function has changed to reflect that. FIND is now much more restrictive; however, you can now search for pixels by specifying an RGB tuple value.

To find the position of the first pixel of a given RGB value, use the find function with a RGB tuple as its argument.

pos: find i1 red
pos: find i2 240.200.100

The result, pos, is the image indexed to the position where the color was found.

To find a sequence of pixels, search for a sub-image. That is, provide an image that contains the RGB values to search for.

i2: make image! 2x1
change i2 red
change next i2 yellow
pos: find i1 i2

You can also search for a binary BGRA series that specifies the byte values of the pixels:

bgra: #{ffe09000 4532FFEE}
pos: find i1 bgra

The /part, /only (alpha only), /match, /tail, /last, and /reverse refinements can also be used with FIND on images.

ANDing, ORing, and XORing Images

The AND, OR, and XOR logical RGB operators have not been changed in this release. You can perform these functions between two images to create a new third image that is a logical combination of the two.

i3: i1 and i2
i4: i1 or i2
i5: i1 xor i2

Real example: show the difference between two images:

i1: help.gif
i2: copy i1
change/dup at i2 24x24 red 3x3 ; add a red dot
i3: i1 xor i2
view layout [
    image i1
    image i2
    image i3
]

Binary Conversion of Images

To convert an image to a binary series, use the TO-BINARY function. The result will be a binary series of bytes in the BGRA order. This order is used for compatibility with previous versions of REBOL/View.

This line converts the i2 image above to a binary series:

bin: to-binary i1

To convert a binary series of BGRA bytes to an image, use the TO-IMAGE function. A temporary size will be assigned to the resulting image. Once the operation is complete, you can use the /size refinement to specify the width and height of the desired image.

i1: to-image bin
i1/size: 10x20

Note that if the number of pixels within the image is not consistent with the line width (there are not enough pixels to fill the image) the height of the image will be truncated.

Saving and Molding Images

The SAVE function works on images as it has in the past by allowing you to save an image as a REBOL datatype specification or in a standard image file format such as PNG.

i1: load %photo.jpg
save/png %photo.png i1

When saving as a REBOL datatype value, you can specify the /all refinement to allow the image to be loaded back into REBOL with simply a load function, not requiring the evaluation of the datatype (that is, the MAKE function is not needed).

save/all %photo.r i1
i2: load %photo.r

The image datatype can also be converted to a string representation using the MOLD function.

print mold i1
probe i1 ; same thing
print mold/all i1 ; no MAKE code

The result will include a block that separately specifies the size pair, RGB pixels and alpha values as separate binary series. This format is directly compatible with the MAKE function as described earlier in this document. Note that if the alpha channel is empty (totally opaque), the alpha binary series will not be shown (it is optimized out). These three components of a molded image, the size, the RGB, and alpha data are consistent with the image accessors /size, /RGB, and /alpha.

Wish List

There are a few other Image datatype enhancements that can be considered for future releases:

1. Accepting a block of pixel tuples to be used with MAKE, CHANGE, FIND, and other functions.

i1: make image! [200.0.0 0.0.100 0.200.0]
pos: find i2 [200.0.0 0.0.100 0.200.0]

2. More capability for FIND function. Inverse (not) and wildcards could be useful for finding simple patterns:

i2: find/not i1 0.0.0 ; scan past black pixels
i3: find/any i1 [[200.0.0 ? ? 0.0.100 * 0.200.0]

3. Specifying a find over a range. For instance, being able to find a pixel that is between 100.0.0 and 200.0.0.

4. Possibly adding CHANGE/pair/dup for image arguments.

5. REMOVE/part using a rectangular pair. (May be problematic, which is why it has not been included yet.)

6. An easy way to add one or more pixels to the width of an image (actually making it wider by a given amount). This is difficult to do otherwise.

Tell us about other functions that you would find useful.

About | Contact | PrivacyREBOL Technologies 2024