Coverage report: /home/ellis/comp/core/lib/dat/png.lisp
Kind | Covered | All | % |
expression | 0 | 624 | 0.0 |
branch | 0 | 28 | 0.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; dat/png.lisp --- PNG image format
3
;; based on ZPNG by Zachary Beane
9
;;;; Copyright (c) 2007 Zachary Beane, All Rights Reserved
16
(:grayscale-alpha . 4)
17
(:truecolor-alpha . 6)))
19
(defvar *png-signature*
21
:element-type '(unsigned-byte 8)
22
:initial-contents '(137 80 78 71 13 10 26 10)))
24
(defconstant +png-compression-method+ 0)
25
(defconstant +png-filtering+ 0)
26
(defconstant +png-interlace+ 0)
29
(defun write-uint32 (integer stream)
30
(write-byte (ldb (byte 8 24) integer) stream)
31
(write-byte (ldb (byte 8 16) integer) stream)
32
(write-byte (ldb (byte 8 8) integer) stream)
33
(write-byte (ldb (byte 8 0) integer) stream))
35
(defun checksum (data count)
36
(let ((checksum (ironclad:make-digest :crc32)))
37
(ironclad:update-digest checksum data :start 0 :end count)
38
(ironclad:produce-digest checksum)))
41
((buffer :initarg :buffer :reader buffer)
42
(pos :initform 4 :accessor pos)))
44
(defun chunk-write-byte (byte chunk)
45
"Save one byte to CHUNK."
46
(setf (aref (buffer chunk) (pos chunk)) byte)
49
(defun chunk-write-uint32 (integer chunk)
50
"Save INTEGER to CHUNK as four bytes."
51
(let ((buffer (buffer chunk))
53
(setf (aref buffer (+ i 0)) (ldb (byte 8 24) integer)
54
(aref buffer (+ i 1)) (ldb (byte 8 16) integer)
55
(aref buffer (+ i 2)) (ldb (byte 8 8) integer)
56
(aref buffer (+ i 3)) (ldb (byte 8 0) integer)
57
(pos chunk) (+ i 4))))
59
(defun make-chunk (a b c d size)
60
"Make a chunk that uses A, B, C, and D as the signature bytes, with
62
(let ((buffer (make-array (+ size 4) :element-type '(unsigned-byte 8))))
63
(setf (aref buffer 0) a
70
(defun write-chunk (chunk stream)
71
(write-uint32 (- (pos chunk) 4) stream)
72
(write-sequence (buffer chunk) stream :end (pos chunk))
73
(write-uint32 (checksum (buffer chunk) (pos chunk)) stream))
76
(define-condition png-error (dat-error) ())
78
(define-condition invalid-size (png-error)
81
:reader invalid-size-width)
84
:reader invalid-size-height))
85
(:report (lambda (condition stream)
86
(format stream "Invalid PNG size ~Ax~A; ~
87
both width and height must be positive"
88
(invalid-size-width condition)
89
(invalid-size-height condition)))))
91
(define-condition invalid-row-length (png-error)
93
:initarg :expected-length
94
:accessor invalid-row-length-expected-length)
96
:initarg :actual-length
97
:accessor invalid-row-length-actual-length))
98
(:report (lambda (condition stream)
99
(format stream "Invalid row length ~A (expected ~A)"
100
(invalid-row-length-actual-length condition)
101
(invalid-row-length-expected-length condition)))))
103
(define-condition insufficient-rows (png-error)
106
:accessor insufficient-rows-written)
109
:accessor insufficient-rows-needed))
110
(:report (lambda (condition stream)
111
(format stream "Insufficient rows written; need ~A, but only ~A ~
113
(insufficient-rows-needed condition)
114
(insufficient-rows-written condition)))))
116
(define-condition incomplete-row (png-error)
119
:accessor incomplete-row-written)
122
:accessor incomplete-row-needed))
123
(:report (lambda (condition stream)
124
(format stream "Incomplete row started; need ~A, but only ~A ~
126
(incomplete-row-needed condition)
127
(incomplete-row-written condition)))))
129
(define-condition too-many-rows (png-error)
132
:accessor too-many-rows-count))
133
(:report (lambda (condition stream)
134
(format stream "Too many rows written for PNG; maximum row count ~
136
(too-many-rows-count condition)))))
138
(define-condition color-type-mismatch (png-error)
141
:accessor color-type-mismatch-given)
144
:accessor color-type-mismatch-expected))
145
(:report (lambda (condition stream)
146
(format stream "Wrong number of samples for PNG pixel; need ~A, ~
148
(color-type-mismatch-expected condition)
149
(color-type-mismatch-given condition)))))
152
(defclass base-png ()
153
((width :initarg :width :reader width)
154
(height :initarg :height :reader height)
155
(color-type :initform :truecolor :initarg :color-type :reader color-type)
156
(bpp :initform 8 :initarg :bpp :reader bpp)))
158
(defclass png (base-png)
159
((image-data :initarg :image-data :reader image-data
160
:writer (setf %image-data))
161
(data-array :reader data-array
162
:writer (setf %data-array))))
164
(defclass streamed-png (base-png)
166
:initarg :rows-written
167
:accessor rows-written)
173
:accessor compressor)
175
:initarg :output-stream
176
:accessor output-stream))
180
(defclass pixel-streamed-png (streamed-png)
183
:accessor current-offset)))
185
(defgeneric ihdr-color-type (png))
186
(defgeneric samples-per-pixel (png))
187
(defgeneric scanline-offset (png scanline))
188
(defgeneric rowstride (png))
190
(defgeneric write-png-header (png stream))
191
(defgeneric write-ihdr (png stream))
192
(defgeneric write-idat (png stream))
193
(defgeneric write-iend (png stream))
195
(defgeneric copy-png (png))
196
(defgeneric png= (png1 png2))
198
(defgeneric write-png-stream (png stream))
199
(defgeneric write-png (png pathname &key if-exists))
201
(defgeneric start-png (png stream))
202
(defgeneric write-row (row png &key start end))
203
(defgeneric finish-png (png))
204
(defgeneric rows-left (png))
205
(defgeneric reset-streamed-png (png))
207
(defgeneric write-pixel (pixel png))
208
(defgeneric pixels-left-in-row (png))
210
(defmethod slot-unbound (class (png png) (slot (eql 'data-array)))
211
(let ((array (make-array (list (height png)
213
(samples-per-pixel png))
214
:displaced-to (image-data png)
215
:element-type '(unsigned-byte 8))))
216
(setf (%data-array png) array)))
218
(defun check-size (png)
219
(let ((width (width png))
220
(height (height png)))
221
(unless (and (plusp width) (plusp height))
226
(defmethod initialize-instance :after ((png png) &rest args &key image-data)
227
(declare (ignore args))
229
(unless (or image-data (slot-boundp png 'image-data))
230
(setf (%image-data png)
231
(make-array (* (height png) (rowstride png))
233
:element-type '(unsigned-byte 8)))))
235
(defmethod ihdr-color-type (png)
236
(cdr (assoc (color-type png) *color-types*)))
238
(defmethod samples-per-pixel (png)
239
(ecase (color-type png)
244
(:truecolor-alpha 4)))
246
(defmethod rowstride (png)
247
(* (width png) (samples-per-pixel png)))
249
(defmethod scanline-offset (png scanline)
250
(* scanline (rowstride png)))
253
(defmethod write-png-header (png stream)
254
(write-sequence *png-signature* stream))
256
(defmethod write-ihdr (png stream)
257
(let ((chunk (make-chunk 73 72 68 82 13)))
258
(chunk-write-uint32 (width png) chunk)
259
(chunk-write-uint32 (height png) chunk)
260
(chunk-write-byte (bpp png) chunk)
261
(chunk-write-byte (ihdr-color-type png) chunk)
262
(chunk-write-byte +png-compression-method+ chunk)
263
(chunk-write-byte +png-filtering+ chunk)
264
(chunk-write-byte +png-interlace+ chunk)
265
(write-chunk chunk stream)))
267
(defun make-idat-callback (stream)
268
(let* ((idat (make-chunk 73 68 65 84 16384))
269
(buffer (buffer idat)))
271
(replace buffer data :start1 4 :end2 end)
272
(setf (pos idat) (+ end 4))
273
(write-chunk idat stream))))
275
(defmethod write-idat (png stream)
276
(let ((callback (make-idat-callback stream)))
278
(io/flate:with-compressor (compressor 'io/deflate:zlib-compressor
280
(dotimes (i (height png))
281
(let* ((start-offset (scanline-offset png i))
282
(end-offset (+ start-offset (rowstride png))))
283
(io/flate:compress-octet 0 compressor)
284
(io/flate:compress-octet-vector (image-data png)
287
:end end-offset))))))
289
(defmethod write-iend (png stream)
290
(let ((chunk (make-chunk 73 69 78 68 0)))
291
(write-chunk chunk stream)))
294
(defmethod write-png-stream (png stream)
296
(write-png-header png stream)
297
(write-ihdr png stream)
298
(write-idat png stream)
299
(write-iend png stream))
301
(defmethod write-png (png file &key (if-exists :supersede))
303
(with-open-file (stream file
306
:if-does-not-exist :create
307
:element-type '(unsigned-byte 8))
308
(write-png-stream png stream)
311
(defmethod copy-png (orig)
314
:height (height orig)
315
:color-type (color-type orig)
317
:image-data (copy-seq (image-data orig))))
319
(defmethod png= (png1 png2)
321
(and (= (width png1) (width png2))
322
(= (height png1) (height png2))
323
(= (bpp png1) (bpp png2))
324
(eq (color-type png1) (color-type png2))
325
(let ((png1.data (image-data png1))
326
(png2.data (image-data png2)))
327
(not (mismatch png1.data png2.data))))))
330
;;; Streamed PNG methods
332
(defmethod slot-unbound (class (png streamed-png) (slot (eql 'row-data)))
333
(let ((data (make-array (rowstride png) :element-type '(unsigned-byte 8)
334
:initial-element 0)))
335
(setf (row-data png) data)))
337
(defmethod start-png ((png streamed-png) stream)
338
(setf (output-stream png) stream)
339
(write-png-header png stream)
340
(write-ihdr png stream)
341
(setf (compressor png)
342
(make-instance 'zlib-compressor
343
:callback (make-idat-callback stream)))
346
(defmethod start-png ((png pixel-streamed-png) stream)
347
(setf (current-offset png) 0)
350
(defmethod write-row (row (png streamed-png) &key (start 0) end)
351
(let ((rowstride (rowstride png)))
352
(setf end (or end (+ start rowstride)))
353
(let ((row-length (- end start)))
354
(unless (= (- end start) (rowstride png))
355
(error 'invalid-row-length
356
:expected-length rowstride
357
:actual-length row-length))
358
(unless (< (rows-written png) (height png))
359
(error 'too-many-rows :count (height png)))
360
(let ((compressor (compressor png)))
361
(io/flate:compress-octet 0 compressor)
362
(io/flate:compress-octet-vector row compressor :start start :end end)
363
(incf (rows-written png))))))
365
(defmethod reset-streamed-png ((png streamed-png))
366
(setf (rows-written png) 0)
367
(slot-makunbound png 'compressor)
368
(slot-makunbound png 'output-stream)
369
(fill (row-data png) 0))
371
(defmethod reset-streamed-png ((png pixel-streamed-png))
372
(setf (current-offset png) 0)
375
(defmethod finish-png ((png streamed-png))
376
(when (/= (rows-written png) (height png))
377
(error 'insufficient-rows
378
:written (rows-written png)
379
:needed (height png)))
380
(io/flate:finish-compression (compressor png))
381
(write-iend png (output-stream png))
382
(reset-streamed-png png)
385
(defmethod finish-png ((png pixel-streamed-png))
386
(let* ((color-channels (samples-per-pixel png))
387
(columns (/ (current-offset png) color-channels)))
388
(unless (zerop columns)
389
(error 'incomplete-row
391
:needed (/ (length (row-data png)) color-channels))))
394
(defmethod rows-left ((png streamed-png))
395
(- (height png) (rows-written png)))
397
(defmethod write-pixel (pixel (png pixel-streamed-png))
398
(let ((row-data (row-data png))
399
(samples-per-pixel (length pixel))
400
(samples-per-pixel-expected (samples-per-pixel png)))
401
(unless (= samples-per-pixel samples-per-pixel-expected)
402
(error 'color-type-mismatch
403
:given samples-per-pixel
404
:expected samples-per-pixel-expected))
405
(replace row-data pixel :start1 (current-offset png))
406
(when (= (incf (current-offset png) samples-per-pixel) (rowstride png))
407
(write-row row-data png)
408
(setf (current-offset png) 0)))
411
(defmethod pixels-left-in-row ((png pixel-streamed-png))
412
(/ (- (current-offset png) (rowstride png)) (samples-per-pixel png)))