3 <title>ZPNG - Create PNG files from Common Lisp
</title>
4 <style type=
"text/css">
5 a
, a:visited
{ text-decoration: none
}
6 a
[href
]:hover
{ text-decoration: underline
}
7 pre
{ background: #DDD; padding: 0.25em }
8 p
.download
{ color: red
}
9 .transparent { background-image: url
(background.gif) }
10 tt
.code
{ background: #DDD }
16 <h2>ZPNG - Create PNG files from Common Lisp
</h2>
18 <blockquote class='abstract'
>
21 <p>ZPNG is a Common Lisp library for creating PNG files. It
22 uses
<a href='http://www.xach.com/lisp/salza2/'
>Salza2
</a> for
25 The current version is
1.2, released on September
17,
2008.
27 <p class='download'
>Download shortcut:
29 <p><a href='http://www.xach.com/lisp/zpng.tgz'
>http://www.xach.com/lisp/zpng.tgz
</a>
36 <li> <a href='#sect-overview'
>Overview and Limitations
</a>
37 <li> <a href='#sect-examples'
>Examples
</a>
38 <li> <a href='#sect-dictionary'
>Dictionary
</a>
45 <li> <a href='#samples-per-pixel'
><tt>samples-per-pixel
</tt></a>
46 <li> <a href='#width'
><tt>width
</tt></a>
47 <li> <a href='#height'
><tt>height
</tt></a>
48 <li> <a href='#rowstride'
><tt>rowstride
</tt></a>
49 <li> <a href='#color-type'
><tt>color-type
</tt></a>
56 <li> <a href='#png'
><tt>png
</tt></a>
57 <li> <a href='#image-data'
><tt>image-data
</tt></a>
58 <li> <a href='#data-array'
><tt>data-array
</tt></a>
59 <li> <a href='#copy-png'
><tt>copy-png
</tt></a>
60 <li> <a href='#png='
><tt>png=
</tt></a>
61 <li> <a href='#write-png'
><tt>write-png
</tt></a>
62 <li> <a href='#write-png-stream'
><tt>write-png-stream
</tt></a>
65 <li> Streamed, row-at-a-time PNGs
68 <li> <a href='#streamed-png'
><tt>streamed-png
</tt></a>
69 <li> <a href='#row-data'
><tt>row-data
</tt></a>
70 <li> <a href='#start-png'
><tt>start-png
</tt></a>
71 <li> <a href='#write-row'
><tt>write-row
</tt></a>
72 <li> <a href='#rows-written'
><tt>rows-written
</tt></a>
73 <li> <a href='#rows-left'
><tt>rows-left
</tt></a>
74 <li> <a href='#finish-png'
><tt>finish-png
</tt></a>
77 <li> Streamed, pixel-at-a-time PNGs
80 <li> <a href='#pixel-streamed-png'
><tt>pixel-streamed-png
</tt></a>
81 <li> <a href='#write-pixel'
><tt>write-pixel
</tt></a>
82 <li> <a href='#pixels-left-in-row'
><tt>pixels-left-in-row
</tt></a>
89 <li> <a href='#zpng-error'
><tt>zpng-error
</tt></a>
90 <li> <a href='#invalid-size'
><tt>invalid-size
</tt></a>
91 <li> <a href='#invalid-size-width'
><tt>invalid-size-width
</tt></a>
92 <li> <a href='#invalid-size-height'
><tt>invalid-size-height
</tt></a>
93 <li> <a href='#invalid-row-length'
><tt>invalid-row-length
</tt></a>
94 <li> <a href='#insufficient-rows'
><tt>insufficient-rows
</tt></a>
95 <li> <a href='#incomplete-row'
><tt>incomplete-row
</tt></a>
96 <li> <a href='#too-many-rows'
><tt>too-many-rows
</tt></a>
97 <li> <a href='#color-type-mismatch'
><tt>color-type-mismatch
</tt></a>
102 <li> <a href='#sect-references'
>References
</a>
103 <li> <a href='#sect-acknowledgements'
>Acknowledgements
</a>
104 <li> <a href='#sect-feedback'
>Feedback
</a>
107 <a name='sect-overview'
><h3>Overview and Limitations
</h3></a>
109 <p>ZPNG provides three interfaces creating PNG files. The first is through a
110 <a href='#png'
><tt>PNG
</tt></a> object, which holds all image sample
111 data and which may be written out to a file all at once. The second is
112 through a
<a href='#streamed-png'
><tt>STREAMED-PNG
</tt></a> object,
113 which writes a single output row of the image at a time. By working
114 with only a single row at a time, images that are too big to fit in
115 memory may still be written out incrementally as a PNG file. The third
116 is through a
<a href='#pixel-streamed-png'
><tt>PIXEL-STREAMED-PNG
</tt></a>
117 object which allows you to write a single pixel at a time. It still
118 buffers a whole row at a time, but it manages all of the buffer
121 <p>The PNG file format has many options, and ZPNG supports only a
125 <li> does not load PNG files
126 <li> supports only
8 bits per sample
127 <li> does not support filtering
128 <li> does not support indexed color
132 <a name='sect-examples'
><h3>Examples
</h3></a>
135 <pre><div style='float: right' class='transparent'
><img src='mandelbrot.png'
136 ></div>(defun draw-mandelbrot (file)
137 (let* ((png (make-instance '
<a href='#png'
>png
</a>
138 :color-type :grayscale-alpha
141 (image (
<a href='#data-array'
>data-array
</a> png))
143 (dotimes (y
200 (
<a href='#write-png'
>write-png
</a> png file))
145 (let ((c (complex (- (/ x
100.0)
1.5) (- (/ y
100.0)
1.0)))
146 (z (complex
0.0 0.0))
149 (setf z (+ (* z z) c))
152 (setf (aref image y x
1) iteration)
155 (setf (aref image y x
1)
255)
159 <pre><div style='float: right' class='transparent'
><img src='rgb.png'
160 ></div>(defun draw-rgb (file)
161 (let ((png (make-instance '
<a
162 href='#pixel-streamed-png'
>pixel-streamed-png
</a>
163 :color-type :truecolor-alpha
166 (with-open-file (stream file
168 :if-exists :supersede
169 :if-does-not-exist :create
170 :element-type '(unsigned-byte
8))
171 (
<a href='#start-png'
>start-png
</a> png stream)
172 (loop for a from
38 to
255 by
31
173 do (loop for b from
10 to
255 by
10
174 do (loop for g from
38 to
255 by
31
175 do (loop for r from
10 to
255 by
10
176 do (
<a href='#write-pixel'
177 >write-pixel
</a> (list r g b a) png)))))
178 (
<a href='#finish-png'
>finish-png
</a> png))))
181 <a name='sect-dictionary'
><h3>Dictionary
</h3></a>
183 <p>The following symbols are exported from the ZPNG package.
187 <p><a name='samples-per-pixel'
>[Function]
</a><br>
188 <b>samples-per-pixel
</b> <i>png
</i> =
> <i>samples
</i>
191 Returns the number of octet samples that make up a single pixel.
193 <p><table cellpadding=
0 cellspacing=
5>
195 <th>Image Color Type
</th>
196 <th>Samples per Pixel
</th>
199 <td>Grayscale
</td><td align=center
>1</td>
202 <td>Truecolor
</td><td align=center
>3</td>
205 <td>Grayscale with Alpha
</td><td align=center
>2</td>
208 <td>Truecolor with Alpha
</td><td align=center
>4</td>
215 <p><a name='width'
><a name='height'
>[Functions]
</a></a><br>
216 <b>width
</b> <i>png
</i> =
> <i>width
</i><br>
217 <b>height
</b> <i>png
</i> =
> <i>height
</i>
220 Returns the width or height of
<i>png
</i>.
223 <p><a name='rowstride'
>[Function]
</a><br>
224 <b>rowstride
</b> <i>png
</i> =
> <i>rowstride
</i>
227 Returns the number of samples in a single row of
<i>png
</i>. It is
229 to
<tt class=code
>(*
(
<a href='#width'
>width
</a> png)
(
<a href='#samples-per-pixel'
>samples-per-pixel
</a> png))
</tt>.
232 <p><a name='color-type'
>[Function]
</a><br>
233 <b>color-type
</b> <i>png
</i> =
> <i>color-type
</i>
236 Returns the color type of
<i>png
</i>, one of
237 of
<tt>:grayscale
</tt>,
<tt>:truecolor
</tt>,
238 <tt>:grayscale-alpha
</tt>, or
<tt>:truecolor-alpha
</tt>.
242 <p><a name='png'
>[Class]
</a><br>
246 Instances of this class may be created directly. Supported initargs:
250 <li> <tt>:width
</tt> - required, the width of the image
252 <li> <tt>:height
</tt> - required, the height of the image
254 <li> <tt>:color-type
</tt> - optional, the color type of the image, one
255 of
<tt>:grayscale
</tt>,
<tt>:truecolor
</tt> (the
256 default),
<tt>:grayscale-alpha
</tt>, or
<tt>:truecolor-alpha
</tt>
258 <li> <tt>:image-data
</tt> - optional, the sample data of the image. If
259 specified, this must be an octet vector with a length of
260 <i>width
</i> × <i>height
</i> × <i>samples-per-pixel
</i>. If
261 not specified, an image data vector of the appropriate size will be
262 created automatically.
269 <p><a name='image-data'
>[Function]
</a><br>
270 <b>image-data
</b> <i>png
</i> =
> <i>octet-vector
</i>
273 Returns the image data of
<i>png
</i>. Samples are laid out from left
274 to right, top to bottom, so the first samples of data affect the
275 upper-left of the image and the final samples affect the lower-right.
277 <p><table cellpadding=
0 cellspacing=
5>
279 <th>Image Color Type
</th>
280 <th>Pixel Sample Layout
</th>
283 <td>Grayscale
</td><td>S|S|S...
</td>
286 <td>Truecolor
</td><td>RGB|RGB|RGB...
</td>
289 <td>Grayscale with Alpha
</td><td>SA|SA|SA...
</td>
292 <td>Truecolor with Alpha
</td><td>RGBA|RGBA|RGBA...
</td>
296 <p>Layout of the samples into pixels is done according to the image's
297 color type and is fully documented in
298 the
<a href=
"http://www.w3.org/TR/PNG/">Portable Network Graphics
303 <p><a name='data-array'
>[Function]
</a><br>
304 <b>data-array
</b> <i>png
</i> =
> <i>data-array
</i>
307 Returns a multidimensional array representing the pixels
308 of
<i>png
</i>. The dimensions correspond to the height, width, and
309 pixel components, respectively. For example, to access the red
310 component at
<53,
100> of a truecolor PNG, you could use this:
313 (aref (data-array png)
100 53 0)
316 <p>Note the reversed order of the coordinate arguments; this is a
317 consequence of Common Lisp's row-major ordering of elements in a
318 multidimensional array and PNG's specified sample layout.
323 <p><a name='copy-png'
>[Function]
</a><br>
324 <b>copy-png
</b> <i>png
</i> =
> <i>png-copy
</i>
327 Create a copy of
<i>png
</i>.
331 <p><a name='png='
>[Function]
</a><br>
332 <b>png=
</b> <i>png1
</i> <i>png2
</i> =
> <i>boolean
</i>
335 Returns true if png1 and png2 are equal. Equality is defined as having
336 the same dimensions, color type, and image data.
340 <p><a name='write-png'
>[Function]
</a><br>
341 <b>write-png
</b> <i>png
</i> <i>file
</i>
342 <tt>&key
</tt> (
<i>if-exists
</i> <tt>:supersede
</tt>) =
> pathname
345 Writes
<i>png
</i> to
<i>file
</i> and returns the truename
346 of
<i>file
</i>.
<i>if-exists
</i> is passed to the
347 underlying
<tt>CL:OPEN
</tt> call for opening the output file.
351 <p><a name='write-png-stream'
>[Function]
</a><br>
352 <b>write-png-stream
</b> <i>png
</i> <i>stream
</i> =
> |
355 Writes
<i>png
</i> to
<i>stream
</i>, which should be an output stream
356 that can accept octets.
360 <p><a name='streamed-png'
>[Class]
</a><br>
364 Instances of this class may be created directly. Supports all the
365 initargs of the
<a href='#png'
><tt>PNG
</tt></a> class
366 except
<tt>:IMAGE-DATA
</tt>.
368 <p>In contrast to
<tt>PNG
</TT> instances,
<tt>STREAMED-PNG
</tt>
369 instances do not keep all the image data in one large vector. Instead,
370 instances are used to write out the image data of a PNG file one row
371 at a time. The protocol for incrementally writing out via
372 a
<tt>STREAMED-PNG
</tt> involves these generic functions:
375 <li> <a href='#row-data'
><tt>ROW-DATA
</tt></a>
376 <li> <a href='#start-png'
><tt>START-PNG
</tt></a>
377 <li> <a href='#write-row'
><tt>WRITE-ROW
</tt></a>
378 <li> <a href='#rows-written'
><tt>ROWS-WRITTEN
</tt></a>
379 <li> <a href='#rows-left'
><tt>ROWS-LEFT
</tt></a>
380 <li> <a href='#finish-png'
><tt>FINISH-PNG
</tt></a>
386 <p><a name='row-data'
>[Function]
</a><br>
387 <b>row-data
</b> <i>streamed-png
</i> =
> <i>octet-vector
</i>
390 Returns a vector suitable for passing
391 to
<a href='#write-row'
><tt>WRITE-ROW
</tt></a>
392 for
<i>streamed-png
</i>; it has the appropriate number of entries for
393 the image width and color type of the png. The initial contents are
394 all zeroes. For a given streamed png, each call to
<tt>row-data
</tt>
395 will return the
<i>same
</i> vector, not a fresh one.
399 <p><a name='start-png'
>[Function]
</a><br>
400 <b>start-png
</b> <i>png
</i> <i>stream
</i> =
> <i>png
</i>
403 Writes PNG file header data to
<i>stream
</i>, which must be an output
404 stream that supports writing
<tt>(unsigned-byte
8)
</tt> data. The
405 header data is taken from
<i>png
</i>, which must be
406 a
<a href='#streamed-png'
><tt>STREAMED-PNG
</tt></a> instance.
410 <p><a name='write-row'
>[Function]
</a><br>
411 <b>write-row
</b> <i>row
</i> <i>png
</i>
412 <tt>&key
</tt> (
<i>start
</i> 0)
<i>end
</i> =
> |
415 Writes
<i>row
</i> to the output stream of
<i>png
</i>.
<i>row
</i> must
417 <tt>(unsigned-byte
8)
</tt> vector with the appropriate number of
418 entries for
<i>png
</i>.
<i>start
</i> defaults to
0 and
<i>end
</i>
419 defaults to
<i>start
</i>
420 +
<a href='#rowstride'
><tt>ROWSTRIDE
</tt></a>.
422 The difference between
<i>end
</i> and
<i>start
</i> should be equal to
423 <tt class=code
>(
<a href='#rowstride'
>rowstride
</a> png
</i>)
</tt>.
<i>png
</i>
424 must be a
<a href='#streamed-png'
><tt>STREAMED-PNG
</tt></a> instance.
426 <p>If the row length, as defined by
<i>start
</i> and
<i>end
</i>, is
427 not the right size, an error of
428 type
<a href='#invalid-row-length'
><tt>INVALID-ROW-LENGTH
</tt></a> is
431 <p>If writing
<i>row
</i> would exceed the number of rows in the image
432 (as defined by
<a href='#height'
><tt>HEIGHT
</tt></a>), an error of
433 type
<a href='#too-many-rows'
><tt>TOO-MANY-ROWS
</tt></a> is signaled.
439 <p><a name='rows-written'
>[Function]
</a><br>
440 <b>rows-written
</b> <i>streamed-png
</i> =
> <i>count
</i>
443 Returns the number of rows written to
<i>streamed-png
</i> so far.
447 <p><a name='rows-left'
>[Function]
</a><br>
448 <b>rows-left
</b> <i>streamed-png
</i> =
> <i>count
</i>
451 Returns the number of rows left to write
452 to
<i>streamed-png
</i>. Equivalent to
453 <tt class=code
>(- (
<a href='#height'
>height
</a> png)
454 (
<a href='#rows-written'
>rows-written
</a> png))
</tt>.
459 <p><a name='finish-png'
>[Function]
</a><br>
460 <b>finish-png
</b> <i>streamed-png
</i> =
> |
463 Concludes writing PNG file data to the output stream
464 of
<i>streamed-png
</i>. The internal state of streamed-png is reset in
465 such a way that it can be re-used to write another PNG file, with the
466 same dimensions and color type parameters, using
467 another
<a href='#start-png'
><tt>START-PNG
</tt></a>/
<a href='#write-row'
><tt>WRITE-ROW
</tt></a>/
<a href='#finish-png'
><tt>FINISH-PNG
</tt></a>
470 <p>This function must be called only after
471 exactly
<a href='#height'
><tt>HEIGHT
</tt></a> rows have been written
472 to
<i>streamed-png
</i>
473 via
<a href='#write-row'
><tt>WRITE-ROW
</tt></a>. If too few rows have
474 been written to
<i>streamed-png
</i>, an error of
475 type
<a href='#insufficient-rows'
><tt>INSUFFICIENT-ROWS
</tt></a> is
480 <p><a name='pixel-streamed-png'
>[Class]
</a><br>
481 <b>pixel-streamed-png
</b>
484 Instances of this class may be created directly. Supports all the
485 initargs of the
<a href='#streamed-png'
><tt>STREAMED-PNG
</tt></a> class.
487 <p>The
<tt>PIXEL-STREAMED-PNG
</tt> class extends the
<tt>STREAMED-PNG
</TT>
488 class. Rather than preparing a row of pixels and caling the
489 <a href='#write-row'
><tt>WRITE-ROW
</tt></a> method, with
490 <tt>PIXEL-STREAMED-PNG
</tt> instances, you write a single pixel
491 at a time with the
<tt>WRITE-PIXEL
</tt> method. The protocol for incrementally writing out via
492 a
<tt>PIXEL-STREAMED-PNG
</tt> involves these generic functions:
495 <li> <a href='#start-png'
><tt>START-PNG
</tt></a>
496 <li> <a href='#write-pixel'
><tt>WRITE-PIXEL
</tt></a>
497 <li> <a href='#rows-written'
><tt>ROWS-WRITTEN
</tt></a>
498 <li> <a href='#rows-left'
><tt>ROWS-LEFT
</tt></a>
499 <li> <a href='#pixels-left-in-row'
><tt>PIXELS-LEFT-IN-ROW
</tt></a>
500 <li> <a href='#finish-png'
><tt>FINISH-PNG
</tt></a>
506 <p><a name='write-pixel'
>[Function]
</a><br>
507 <b>write-pixel
</b> <i>pixel-samples
</i> <i>pixel-streamed-png
</i>
510 Writes the samples for a single pixel from the sequence
511 <i>pixel-samples
</i> to the next available spot in
512 the
<a href='#row-data'
><tt>ROW-DATA
</tt></a> buffer. When the buffer
513 is full, this method invokes the
514 <a href='#write-row'
><tt>WRITE-ROW
</tt></a> method. The length of the
515 <tt>PIXEL-SAMPLES
</tt> sequence must be equal to the
<a
516 href='#samples-per-pixel'
><tt>SAMPLES-PER-PIXEL
</tt></a>.
519 <p><a name='pixels-left-in-row'
>[Function]
</a><br>
520 <b>pixels-left-in-row
</b> <i>pixel-streamed-png
</i>
523 Returns the number of pixels left to write to complete the current row
528 <p><a name='zpng-error'
>[Condition]
</a><br>
532 All errors signaled by ZPNG are a subtype of
<tt>ZPNG-ERROR
</tt>,
533 which is a subtype of
<tt>CL:ERROR
</tt>.
537 <p><a name='invalid-size'
>[Condition]
</a><br>
541 A condition of this type is signaled when a PNG with invalid size is
542 created. Valid PNGs have positive width and height.
546 <p><a name='invalid-size-width'
>[Accessors]
</a><br>
547 <b>invalid-size-width
</b> <i>condition
</i> =
> <i>width
</i><br>
548 <b>invalid-size-height
</b> <i>condition
</i> =
> <i>height
</i>
551 These accessors provide the invalid size used for a PNG.
555 <p><a name='invalid-row-length'
>[Condition]
</a><br>
556 <b>invalid-row-length
</b>
559 A condition of this type is signaled when a row with an incorrect size
560 is passed to
<a href='#write-row'
><tt>WRITE-ROW
</tt></a>.
564 <p><a name='insufficient-rows'
>[Condition]
</a><br>
565 <b>insufficient-rows
</b>
568 A condition of this type is signaled
569 from
<a href='#finish-png'
><tt>FINISH-PNG
</tt></a> when it is called
570 before enough rows have been written
571 via
<a href='#write-row'
><tt>WRITE-ROW
</tt></a>.
575 <p><a name='incomplete-row'
>[Condition]
</a><br>
576 <b>incomplete-row
</b>
579 A condition of this type is signaled from
<a
580 href='#finish-png'
><tt>FINISH-PNG
</tt></a> when it is called on a
<a
581 href='#pixel-streamed-png'
><tt>PIXEL-STREAMED-PNG
</tt></a> before the
582 current row of pixels has been completed.
585 <p><a name='too-many-rows'
>[Condition]
</a><br>
589 A condition of this type is signaled
590 from
<a href='#write-row'
><tt>WRITE-ROW
</tt></a> if it is called more
591 times than is necessary for the given PNG.
594 <p><a name='color-type-mismatch'
>[Condition]
</a><br>
595 <b>color-type-mismatch
</b>
598 A condition of this type is signaled from
<a
599 href='#write-pixel'
><tt>WRITE-PIXEL
</tt></a> if the number of samples
600 in the pixel is not the expected
<a
601 href='#samples-per-pixel'
><tt>SAMPLES-PER-PIXEL
</tt></a> for the given
606 <a name='sect-references'
><h3>References
</h3></a>
610 <li> W3C,
<a href=
"http://www.w3.org/TR/PNG/">Portable Network
611 Graphics Specification, Second Edition
</a>
614 Wikipedia,
<a href='http://en.wikipedia.org/wiki/Mandelbrot_set'
>Mandelbrot
620 <a name='sect-acknowledgements'
><h3>Acknowledgements
</h3></a>
622 <p>Thanks to Patrick Stein for implementing pixel-at-a-time streamed
626 <a name='sect-feedback'
><h3>Feedback
</h3></a>
628 <p>Please direct any questions, comments, bug reports, or other
629 feedback to
<a href='mailto:xach@xach.com'
>Zach Beane
</a>.