2 ;;; Copyright (c) 2007 Zachary Beane, All Rights Reserved
4 ;;; Redistribution and use in source and binary forms, with or without
5 ;;; modification, are permitted provided that the following conditions
8 ;;; * Redistributions of source code must retain the above copyright
9 ;;; notice, this list of conditions and the following disclaimer.
11 ;;; * Redistributions in binary form must reproduce the above
12 ;;; copyright notice, this list of conditions and the following
13 ;;; disclaimer in the documentation and/or other materials
14 ;;; provided with the distribution.
16 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
17 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
20 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
22 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 ((width :initarg
:width
:reader width
)
33 (height :initarg
:height
:reader height
)
34 (color-type :initform
:truecolor
:initarg
:color-type
:reader color-type
)
35 (bpp :initform
8 :initarg
:bpp
:reader bpp
)))
37 (defclass png
(base-png)
38 ((image-data :initarg
:image-data
:reader image-data
39 :writer
(setf %image-data
))
40 (data-array :reader data-array
41 :writer
(setf %data-array
))))
43 (defclass streamed-png
(base-png)
45 :initarg
:rows-written
46 :accessor rows-written
)
54 :initarg
:output-stream
55 :accessor output-stream
))
59 (defgeneric ihdr-color-type
(png))
60 (defgeneric samples-per-pixel
(png))
61 (defgeneric scanline-offset
(png scanline
))
62 (defgeneric rowstride
(png))
64 (defgeneric write-png-header
(png stream
))
65 (defgeneric write-ihdr
(png stream
))
66 (defgeneric write-idat
(png stream
))
67 (defgeneric write-iend
(png stream
))
69 (defgeneric copy-png
(png))
70 (defgeneric png
= (png1 png2
))
72 (defgeneric write-png-stream
(png stream
))
73 (defgeneric write-png
(png pathname
&key if-exists
))
75 (defgeneric start-png
(png stream
))
76 (defgeneric write-row
(row png
&key start end
))
77 (defgeneric finish-png
(png))
78 (defgeneric rows-left
(png))
80 (defmethod slot-unbound (class (png png
) (slot (eql 'data-array
)))
81 (let ((array (make-array (list (height png
)
83 (samples-per-pixel png
))
84 :displaced-to
(image-data png
)
85 :element-type
'(unsigned-byte 8))))
86 (setf (%data-array png
) array
)))
88 (defun check-size (png)
89 (let ((width (width png
))
90 (height (height png
)))
91 (unless (and (plusp width
) (plusp height
))
96 (defmethod initialize-instance :after
((png png
) &rest args
&key image-data
)
97 (declare (ignore args
))
99 (unless (or image-data
(slot-boundp png
'image-data
))
100 (setf (%image-data png
)
101 (make-array (* (height png
) (rowstride png
))
103 :element-type
'(unsigned-byte 8)))))
105 (defmethod ihdr-color-type (png)
106 (cdr (assoc (color-type png
) *color-types
*)))
108 (defmethod samples-per-pixel (png)
109 (ecase (color-type png
)
114 (:truecolor-alpha
4)))
116 (defmethod rowstride (png)
117 (* (width png
) (samples-per-pixel png
)))
119 (defmethod scanline-offset (png scanline
)
120 (* scanline
(rowstride png
)))
123 (defmethod write-png-header (png stream
)
124 (write-sequence *png-signature
* stream
))
126 (defmethod write-ihdr (png stream
)
127 (let ((chunk (make-chunk 73 72 68 82 13)))
128 (chunk-write-uint32 (width png
) chunk
)
129 (chunk-write-uint32 (height png
) chunk
)
130 (chunk-write-byte (bpp png
) chunk
)
131 (chunk-write-byte (ihdr-color-type png
) chunk
)
132 (chunk-write-byte +png-compression-method
+ chunk
)
133 (chunk-write-byte +png-filtering
+ chunk
)
134 (chunk-write-byte +png-interlace
+ chunk
)
135 (write-chunk chunk stream
)))
137 (defun make-idat-callback (stream)
138 (let* ((idat (make-chunk 73 68 65 84 16384))
139 (buffer (buffer idat
)))
141 (replace buffer data
:start1
4 :end2 end
)
142 (setf (pos idat
) (+ end
4))
143 (write-chunk idat stream
))))
145 (defmethod write-idat (png stream
)
146 (let ((callback (make-idat-callback stream
)))
147 (with-compressor (compressor 'zlib-compressor
149 (dotimes (i (height png
))
150 (let* ((start-offset (scanline-offset png i
))
151 (end-offset (+ start-offset
(rowstride png
))))
152 (compress-octet 0 compressor
)
153 (compress-octet-vector (image-data png
)
156 :end end-offset
))))))
159 (defmethod write-iend (png stream
)
160 (let ((chunk (make-chunk 73 69 78 68 0)))
161 (write-chunk chunk stream
)))
164 (defmethod write-png-stream (png stream
)
166 (write-png-header png stream
)
167 (write-ihdr png stream
)
168 (write-idat png stream
)
169 (write-iend png stream
))
171 (defmethod write-png (png file
&key
(if-exists :supersede
))
173 (with-open-file (stream file
176 :if-does-not-exist
:create
177 :element-type
'(unsigned-byte 8))
178 (write-png-stream png stream
)
181 (defmethod copy-png (orig)
184 :height
(height orig
)
185 :color-type
(color-type orig
)
187 :image-data
(copy-seq (image-data orig
))))
189 (defmethod png= (png1 png2
)
191 (and (= (width png1
) (width png2
))
192 (= (height png1
) (height png2
))
193 (= (bpp png1
) (bpp png2
))
194 (eq (color-type png1
) (color-type png2
))
195 (let ((png1.data
(image-data png1
))
196 (png2.data
(image-data png2
)))
197 (not (mismatch png1.data png2.data
))))))
200 ;;; Streamed PNG methods
202 (defmethod slot-unbound (class (png streamed-png
) (slot (eql 'row-data
)))
203 (let ((data (make-array (rowstride png
) :element-type
'(unsigned-byte 8)
204 :initial-element
0)))
205 (setf (row-data png
) data
)))
207 (defmethod start-png ((png streamed-png
) stream
)
208 (setf (output-stream png
) stream
)
209 (write-png-header png stream
)
210 (write-ihdr png stream
)
211 (setf (compressor png
)
212 (make-instance 'zlib-compressor
213 :callback
(make-idat-callback stream
)))
216 (defmethod write-row (row (png streamed-png
) &key
(start 0) end
)
217 (let ((rowstride (rowstride png
)))
218 (setf end
(or end
(+ start rowstride
)))
219 (let ((row-length (- end start
)))
220 (unless (= (- end start
) (rowstride png
))
221 (error 'invalid-row-length
222 :expected-length rowstride
223 :actual-length row-length
))
224 (unless (< (rows-written png
) (height png
))
225 (error 'too-many-rows
:count
(height png
)))
226 (let ((compressor (compressor png
)))
227 (compress-octet 0 compressor
)
228 (compress-octet-vector row compressor
:start start
:end end
)
229 (incf (rows-written png
))))))
231 (defun reset-streamed-png (png)
232 (setf (rows-written png
) 0)
233 (slot-makunbound png
'compressor
)
234 (slot-makunbound png
'output-stream
)
235 (fill (row-data png
) 0))
237 (defmethod finish-png ((png streamed-png
))
238 (when (/= (rows-written png
) (height png
))
239 (error 'insufficient-rows
240 :written
(rows-written png
)
241 :needed
(height png
)))
242 (finish-compression (compressor png
))
243 (write-iend png
(output-stream png
))
244 (reset-streamed-png png
)
247 (defmethod rows-left ((png streamed-png
))
248 (- (height png
) (rows-written png
)))