ZPNG - Create PNG files from Common Lisp

Abstract

ZPNG is a Common Lisp library for creating PNG files. It uses Salza2 for compression. The latest version is 1.2.2, released on March 12th, 2015.

Download shortcut:

http://www.xach.com/lisp/zpng.tgz

Contents

  1. Overview and Limitations
  2. Examples
  3. Dictionary
  4. References
  5. Acknowledgements
  6. Feedback

Overview and Limitations

ZPNG provides three interfaces creating PNG files. The first is through a PNG object, which holds all image sample data and which may be written out to a file all at once. The second is through a STREAMED-PNG object, which writes a single output row of the image at a time. By working with only a single row at a time, images that are too big to fit in memory may still be written out incrementally as a PNG file. The third is through a PIXEL-STREAMED-PNG object which allows you to write a single pixel at a time. It still buffers a whole row at a time, but it manages all of the buffer handling for you.

The PNG file format has many options, and ZPNG supports only a subset of them.

Examples

(defun draw-mandelbrot (file) (let* ((png (make-instance 'png :color-type :grayscale-alpha :width 200 :height 200)) (image (data-array png)) (max 255)) (dotimes (y 200 (write-png png file)) (dotimes (x 200) (let ((c (complex (- (/ x 100.0) 1.5) (- (/ y 100.0) 1.0))) (z (complex 0.0 0.0)) (iteration 0)) (loop (setf z (+ (* z z) c)) (incf iteration) (cond ((< 4 (abs z)) (setf (aref image y x 1) iteration) (return)) ((= iteration max) (setf (aref image y x 1) 255) (return)))))))))
(defun draw-rgb (file) (let ((png (make-instance 'pixel-streamed-png :color-type :truecolor-alpha :width 200 :height 200))) (with-open-file (stream file :direction :output :if-exists :supersede :if-does-not-exist :create :element-type '(unsigned-byte 8)) (start-png png stream) (loop for a from 38 to 255 by 31 do (loop for b from 10 to 255 by 10 do (loop for g from 38 to 255 by 31 do (loop for r from 10 to 255 by 10 do (write-pixel (list r g b a) png))))) (finish-png png))))

Dictionary

The following symbols are exported from the ZPNG package.

[Function]
samples-per-pixel png => samples

Returns the number of octet samples that make up a single pixel.

Image Color Type Samples per Pixel
Grayscale1
Truecolor3
Grayscale with Alpha2
Truecolor with Alpha4

[Functions]
width png => width
height png => height

Returns the width or height of png.

[Function]
rowstride png => rowstride

Returns the number of samples in a single row of png. It is equivalent to (* (width png) (samples-per-pixel png)).

[Function]
color-type png => color-type

Returns the color type of png, one of of :grayscale, :truecolor, :grayscale-alpha, or :truecolor-alpha.

[Class]
png

Instances of this class may be created directly. Supported initargs:

[Function]
image-data png => octet-vector

Returns the image data of png. Samples are laid out from left to right, top to bottom, so the first samples of data affect the upper-left of the image and the final samples affect the lower-right.

Image Color Type Pixel Sample Layout
GrayscaleS|S|S...
TruecolorRGB|RGB|RGB...
Grayscale with AlphaSA|SA|SA...
Truecolor with AlphaRGBA|RGBA|RGBA...

Layout of the samples into pixels is done according to the image's color type and is fully documented in the Portable Network Graphics Specification.

[Function]
data-array png => data-array

Returns a multidimensional array representing the pixels of png. The dimensions correspond to the height, width, and pixel components, respectively. For example, to access the red component at <53,100> of a truecolor PNG, you could use this:
(aref (data-array png) 100 53 0)

Note the reversed order of the coordinate arguments; this is a consequence of Common Lisp's row-major ordering of elements in a multidimensional array and PNG's specified sample layout.

[Function]
copy-png png => png-copy

Create a copy of png.

[Function]
png= png1 png2 => boolean

Returns true if png1 and png2 are equal. Equality is defined as having the same dimensions, color type, and image data.

[Function]
write-png png file &key (if-exists :supersede) => pathname

Writes png to file and returns the truename of file. if-exists is passed to the underlying CL:OPEN call for opening the output file.

[Function]
write-png-stream png stream => |

Writes png to stream, which should be an output stream that can accept octets.

[Class]
streamed-png

Instances of this class may be created directly. Supports all the initargs of the PNG class except :IMAGE-DATA.

In contrast to PNG instances, STREAMED-PNG instances do not keep all the image data in one large vector. Instead, instances are used to write out the image data of a PNG file one row at a time. The protocol for incrementally writing out via a STREAMED-PNG involves these generic functions:

[Function]
row-data streamed-png => octet-vector

Returns a vector suitable for passing to WRITE-ROW for streamed-png; it has the appropriate number of entries for the image width and color type of the png. The initial contents are all zeroes. For a given streamed png, each call to row-data will return the same vector, not a fresh one.

[Function]
start-png png stream => png

Writes PNG file header data to stream, which must be an output stream that supports writing (unsigned-byte 8) data. The header data is taken from png, which must be a STREAMED-PNG instance.

[Function]
write-row row png &key (start 0) end => |

Writes row to the output stream of png. row must be an (unsigned-byte 8) vector with the appropriate number of entries for png. start defaults to 0 and end defaults to start + ROWSTRIDE. The difference between end and start should be equal to (rowstride png). png must be a STREAMED-PNG instance.

If the row length, as defined by start and end, is not the right size, an error of type INVALID-ROW-LENGTH is signaled.

If writing row would exceed the number of rows in the image (as defined by HEIGHT), an error of type TOO-MANY-ROWS is signaled.

[Function]
rows-written streamed-png => count

Returns the number of rows written to streamed-png so far.

[Function]
rows-left streamed-png => count

Returns the number of rows left to write to streamed-png. Equivalent to (- (height png) (rows-written png)).

[Function]
finish-png streamed-png => |

Concludes writing PNG file data to the output stream of streamed-png. The internal state of streamed-png is reset in such a way that it can be re-used to write another PNG file, with the same dimensions and color type parameters, using another START-PNG/WRITE-ROW/FINISH-PNG sequence.

This function must be called only after exactly HEIGHT rows have been written to streamed-png via WRITE-ROW. If too few rows have been written to streamed-png, an error of type INSUFFICIENT-ROWS is signaled.

[Class]
pixel-streamed-png

Instances of this class may be created directly. Supports all the initargs of the STREAMED-PNG class.

The PIXEL-STREAMED-PNG class extends the STREAMED-PNG class. Rather than preparing a row of pixels and caling the WRITE-ROW method, with PIXEL-STREAMED-PNG instances, you write a single pixel at a time with the WRITE-PIXEL method. The protocol for incrementally writing out via a PIXEL-STREAMED-PNG involves these generic functions:

[Function]
write-pixel pixel-samples pixel-streamed-png

Writes the samples for a single pixel from the sequence pixel-samples to the next available spot in the ROW-DATA buffer. When the buffer is full, this method invokes the WRITE-ROW method. The length of the PIXEL-SAMPLES sequence must be equal to the SAMPLES-PER-PIXEL.

[Function]
pixels-left-in-row pixel-streamed-png

Returns the number of pixels left to write to complete the current row of pixels.

[Condition]
zpng-error

All errors signaled by ZPNG are a subtype of ZPNG-ERROR, which is a subtype of CL:ERROR.

[Condition]
invalid-size

A condition of this type is signaled when a PNG with invalid size is created. Valid PNGs have positive width and height.

[Accessors]
invalid-size-width condition => width
invalid-size-height condition => height

These accessors provide the invalid size used for a PNG.

[Condition]
invalid-row-length

A condition of this type is signaled when a row with an incorrect size is passed to WRITE-ROW.

[Condition]
insufficient-rows

A condition of this type is signaled from FINISH-PNG when it is called before enough rows have been written via WRITE-ROW.

[Condition]
incomplete-row

A condition of this type is signaled from FINISH-PNG when it is called on a PIXEL-STREAMED-PNG before the current row of pixels has been completed.

[Condition]
too-many-rows

A condition of this type is signaled from WRITE-ROW if it is called more times than is necessary for the given PNG.

[Condition]
color-type-mismatch

A condition of this type is signaled from WRITE-PIXEL if the number of samples in the pixel is not the expected SAMPLES-PER-PIXEL for the given PNG.

References

Acknowledgements

Thanks to Patrick Stein for implementing pixel-at-a-time streamed output.

Feedback

Please direct any questions, comments, bug reports, or other feedback to Zach Beane.