Graphics Ports supports drawing images, and also reading/writing them from/to file via your code. A wide range of image types is supported. Also, several CAPI classes support the same image types.
To draw an image with Graphics Ports, you need an image object which is associated with an instance of output-pane (or a subclass of this). You can create an image object from:
Draw the image to the pane by calling draw-image. Certain images ("Plain Images") can be manipulated via the Image Access API. The image should be freed by calling free-image when you are done with it.
The CAPI classes image-pinboard-object, button, list-panel, list-view, tree-view, toolbar, toolbar-button and toolbar-component all support images. There is also limited support for images in menu. These classes handle the drawing and freeing for you.
This table lists the formats supported at the time of writing:
Functions which load images from a file attempt to identify the image type from the file type.
Call the function list-known-image-formats to list the formats that the current platform supports for reading and drawing.
Note: On X11/Motif, LispWorks uses the freeware imlib2
library on Linux, FreeBSD and macOS, and imlib
on Solaris.
Note: On Microsoft Windows, ICO images are supported for certain situations such as buttons and drawing images. See button and draw-image for details.
Note: On Microsoft Windows, LispWorks additionally supports Windows Icon files with scaling - see load-icon-image for details.
Note: On Microsoft Windows, only bitmaps with maximum 24 bits per pixel are supported.
Note: LispWorks 4.3 and previous versions supported only Bitmap images.
Graphic images can be written to files in several formats, using externalize-and-write-image.
All platforms can write at least BMP, JPG, PNG and TIFF files. Call the function list-known-image-formats with optional argument for-writing-too t
to list the formats that the current platform supports for writing.
On Microsoft Windows and Cocoa you can also write GIF files, while on GTK+ you can also write ICO and CUR (cursor) files. The cursor files that are written with GTK+ can be used on Windows and Cocoa, although on Cocoa it does not recognize the hot-spot in a CUR file.
There is a simple example of writing a PNG image here:
(example-edit-file "capi/graphics/images-with-alpha")
An External Image is an intermediate object. It is a representation of a graphic but is not associated with a port and cannot be used directly for drawing. It is a Lisp object which can be loaded into Lisp and saved in a LispWorks image created by save-image or deliver.
An object of type external-image is created by reading an image from a file, or by externalizing an image object, or by copying an existing external-image. Or, if you have the image bitmap data, you can create one directly as in this example:
(example-edit-file "capi/buttons/buttons")
The external-image contains the bitmap data, potentially compressed. You can copy external-image objects, or write them to file, or compress the data.
You cannot query the size of the image in an external-image object directly. To get the dimensions without actually drawing it on screen see 13.8 Pixmap graphics ports.
An external-image can be written to a file using write-external-image. If you create an image and want to externalize it to write it to file, follow this example:
(let ((image (gp:make-image-from-port pane 10 10 200 200))) (unwind-protect (gp:externalize-and-write-image pane image filename) (gp:free-image pane image)))
Convert an external-image to an object of type image ready for drawing to a port in several ways as described in 13.10.5 Making an image that is suitable for drawing. Such conversions are cached but you can remove the caches by clear-external-image-conversions.
You can also convert an image to an external-image by calling externalize-image.
Graphics ports images support an alpha channel, as long as the image format does.
An External Image representing an image in a format with a color table but with no alpha channel (such as 8-bit BMP) can simulate transparency by specifying an index to represent the transparent color. When converted this color is replaced by the background color of the port (which is documented in simple-pane).
You can specify the transparent color by:
(gp:read-external-image file :transparent-color-index 42)
or by:
(setf (gp:external-image-transparent-color-index external-image) 42)
You can use an image tool such as Gimp (www.gimp.org
) to figure out the transparent color index.
On platforms other than Motif you can actually make the background of such an image format truly transparent when displayed. To do this, supply transparent-color-index as a cons (index . :transparent)
.
Note: transparent-color-index works only for images with a color map - those with 256 colors or less.
One way to load an image is via a registered image identifier.
Registering an external image is the way to pre-load images while building an application. To do this, establish a registered image identifier by calling register-image-translation at build time:
(gp:register-image-translation 'info-image (gp:read-external-image "info.bmp" :transparent-color-index 7))
Then at run time obtain the image object by:
(gp:load-image port 'info-image)
To create an image object suitable for drawing on a given pane, use one of convert-external-image, read-and-convert-external-image, load-image, make-image-from-port, make-sub-image, make-scaled-sub-image or (on Microsoft Windows) load-icon-image.
Images need to be freed after use. When the pane that an image was created for is destroyed, the image is freed automatically. However if you want to remove the image before the pane is destroyed, you must make an explicit call free-image. If the image is not freed, then a memory leak will occur.
Another way to create an image object is to supply a registered image identifier in a CAPI class that supports images. For example you can specify an image in an image-pinboard-object. Then, an image object is created implicitly when the pinboard object is displayed and freed implicitly when the pinboard object is destroyed.
In all cases, the functions that create the image object require the pane to be already created. So if you are displaying the image when first displaying your window, take care to create the image object late enough, for example in the :before
method of interface-display on the window's interface class, or in the first :display-callback
of the pane.
To obtain the pixel dimensions of an image, load the image using load-image and then use the readers image-width and image-height. The first argument to load-image must be a pane in a displayed interface.
To query the dimensions before displaying anything you can create and "display" an interface made with the :display-state :hidden
initarg. Call load-image with this hidden interface and your external-image object, and then use the readers image-width and image-height.
The function to draw an image is draw-image.
As with the other drawing functions, this must be called in the same process as the pane, as outlined in 13.4 Drawing functions.
You can read and write pixel values in an image via an Image Access object, but only if the image is a Plain Image. You can ensure you have a Plain Image by using the result of:
(load-image pane image :force-plain t)
To read and/or write pixel values, follow these steps:
(setf image-access-pixel)
with the coordinates of each pixel (or use image-access-pixels-from-bgra) to write pre-multiplied pixel RGB values and then call image-access-transfer-to-image on the Image Access object. This notionally transfers all the pixel data back to the window system from the access object. It might do nothing if the window system allows fast access to the pixel data directly.It is also possible to get all the pixels into a single vector, where each color is represented by four elements, using image-access-pixels-from-bgra, and to change all the pixels in the image to values from a vector using image-access-pixels-to-bgra. When accessing many pixels, using these functions and accessing the vector is much faster than using the single pixel access.
There is an example that demonstrates the uses of Image Access objects in:
(example-edit-file "capi/graphics/image-access")
This further example demonstrates the uses of Image Access objects with colors that have an alpha component:
(example-edit-file "capi/graphics/image-access-alpha")
The color values that are received and set using Image Access are premultiplied, which means that the value of each of the three components (Red, Green and Blue) are already multiplied by the value of the alpha. This is different from the way colors are represented elsewhere. The functions color-to-premultiplied and color-from-premultiplied can be used the convert between premultiplied colors and ordinary colors, although they lose some precision in the process.
For example, the form below creates an image from a pixmap filled with a color that has alpha 0.5. When accessing the image using Image Access, the values in the color that it returned are half of the values in the original color.
(let* ((initial-color (color:make-rgb 0.8 0.6 0.4 0.5)) (image-pixel (let ((pane (capi:editor-pane (capi:find-interface 'lw-tools:listener)))) ;; Make a temporary pixmap filled with the ;; initial-color and create a gp:image from it (let ((image (gp:with-pixmap-graphics-port (pixmap pane 10 10 :background initial-color :clear t) (gp:make-image-from-port pixmap)))) ;; Create a gp:image-access, read ;; a pixel and unconvert it (let ((image-access (gp:make-image-access pane image))) (gp:image-access-transfer-from-image image-access) (let ((pixel (color:unconvert-color pane (gp:image-access-pixel image-access 0 0)))) (gp:free-image-access image-access) (gp:free-image pane image) pixel)))))) (flet ((output-color (string color) (format t "~%~a~28t: Red ~4,2f, Green ~4,2f, Blue ~4,2f" string (color:color-red color) (color:color-green color) (color:color-blue color)))) (output-color "Initial-color" initial-color) (output-color "premultiplied" (color:color-to-premultiplied initial-color)) (output-color "In the image" image-pixel) (output-color "Pixel un-premultiplied" (color:color-from-premultiplied image-pixel))))
To create an external-image object from graphics ports operations, use with-pixmap-graphics-port, and in the scope of it do the drawing and then use make-image-from-port to create an image object. You can then use externalize-image or externalize-and-write-image to externalize the image.
(defun record-picture (output-pane) (gp:with-pixmap-graphics-port (port output-pane 400 400 :clear t :background :red) (gp:draw-rectangle port 0 0 200 200 :filled t :foreground :blue) (let ((image (gp:make-image-from-port port))) (gp:externalize-image port image))))
Here output-pane must be a displayed instance of output-pane (or a subclass). The code does not affect the displayed pane.
If you do not already display a suitable output pane, you can create an invisible one like this:
(defun record-picture-1 () (let* ((pl (make-instance 'capi:pinboard-layout)) (win (capi:display (make-instance 'capi:interface :display-state :hidden :layout pl)))) (prog1 (record-picture pl) (capi:destroy win))))
Note: There is no reason to create and destroy the invisible interface each time a new picture is recorded, so for efficiency you could cache the interface object and use it repeatedly.
CAPI User Guide and Reference Manual (Windows version) - 01 Dec 2021 19:33:53