All Manuals > CAPI User Guide and Reference Manual > 13 Drawing - Graphics Ports

13.10 Working with images

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.

13.10.1 Image formats supported for reading from disk and drawing

This table lists the formats supported at the time of writing:

Operating system and supported image types
OSSupported Image Types

Microsoft Windows

BMP, DIB, GIF, JPEG, PNG, TIFF, EMF, ICO

macOS

BMP, DIB, GIF, JPEG, TIFF, PICT and many others.
Also EPS, PDF

GTK+

BMP, DIB, GIF, JPEG, PNG, TIFF and many others.

X11/Motif

BMP, DIB, GIF, JPEG, PNG, TIFF, XPM, PGM, PPM

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.

13.10.2 Image formats supported for writing to disk

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

13.10.3 External images

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)))
13.10.3.1 Converting an external 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.

13.10.3.2 Transparency and the alpha channel

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.

13.10.4 Registering images

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)

13.10.5 Making an image that is suitable for drawing

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.

13.10.6 Querying image dimensions

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.

13.10.7 Drawing images

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.

13.10.8 Image access

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:

  1. Start with a Graphics Port (for example an output-pane) and an image object associated with it, which is a Plain Image. See above for how to create an image object.
  2. Construct an Image Access object by calling make-image-access.
  3. To read pixels from the image, first call image-access-transfer-from-image on the Image Access object. This notionally transfers all the pixel data from the window system into the access object. It might do nothing if the window system allows fast access to the pixel data directly. Then call image-access-pixel with the coordinates of each pixel (or use image-access-pixels-to-bgra). The values are color representations like those returned from convert-color and can be converted to RGB using unconvert-color if required.
  4. To write pixels to the image, you must have already called image-access-transfer-from-image. Then call (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.
  5. Free the image access object by calling free-image-access on it.

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")
13.10.8.1 Pre-multiplied pixel values in images

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

13.10.9 Creating external images from Graphics Ports operations

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 (Macintosh version) - 01 Dec 2021 19:31:22