1 ;;;; cl-vectors -- Rasterizer and paths manipulation library
2 ;;;; Copyright (C) 2007 Frédéric Jolliton <frederic@jolliton.com>
4 ;;;; This library is free software; you can redistribute it and/or
5 ;;;; modify it under the terms of the Lisp Lesser GNU Public License
6 ;;;; (http://opensource.franz.com/preamble.html), known as the LLGPL.
8 ;;;; This library is distributed in the hope that it will be useful, but
9 ;;;; WITHOUT ANY WARRANTY; without even the implied warranty of
10 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Lisp
11 ;;;; Lesser GNU Public License for more details.
13 (defpackage #:net.tuxee.aa-misc
15 (:nicknames
#:aa-misc
)
16 (:export
;; minimal image support (for testing purpose!)
20 ;; Rendering functions.
23 ;; Loading, saving and displaying image.
30 (in-package #:net.tuxee.aa-misc
)
32 (defvar *external-viewer
* "xv"
33 "Default program to run to display a PNM image.")
35 (deftype octet
() '(unsigned-byte 8))
37 (defun make-image (width height
&optional default-color
)
40 width -- width of the image
41 height -- height of the image
42 default-color -- if not NIL, then the image is filled with the
43 specified color. If unspecified, then the contents of the image
46 Return the newly created image."
47 (let ((image (make-array (list height width
3)
48 :element-type
'octet
)))
50 (loop for y below height
51 do
(loop for x below width
52 do
(loop for rgb below
3
53 do
(setf (aref image y x rgb
) (aref default-color rgb
))))))
56 (defun image-width (image)
57 (array-dimension image
1))
59 (defun image-height (image)
60 (array-dimension image
0))
62 ;;;--[ Rendering ]-----------------------------------------------------------
64 (declaim (inline blend-value
))
65 (defun blend-value (a b alpha
)
66 (max 0 (min 255 (floor (+ (* (- 256 alpha
) a
)
70 (defun alpha/normalized
(alpha)
71 (min 255 (abs alpha
)))
73 (defun alpha/even-odd
(alpha)
74 (min 255 (- 256 (abs (- 256 (mod (abs alpha
) 512))))))
76 (defun image-put-pixel (image &optional
(color #(0 0 0)) (opacity 1.0) (alpha-function :normalized
))
77 (check-type image
(array octet
(* * 3)))
78 (let ((width (image-width image
))
79 (height (image-height image
)))
82 (setf alpha-function
#'alpha
/normalized
))
84 (setf alpha-function
#'alpha
/even-odd
)))
87 (declare (optimize speed
(safety 0) (debug 0)))
88 (when (and (<= 0 x
(1- width
))
91 do
(setf (aref image y x rgb
)
92 (blend-value (aref image y x rgb
)
94 (floor (* opacity
(funcall alpha-function alpha
))))))))
96 (declare (optimize speed
(safety 0) (debug 0)))
97 (when (and (<= 0 x
(1- width
))
100 do
(setf (aref image y x rgb
)
101 (blend-value (aref image y x rgb
)
103 (funcall alpha-function alpha
)))))))))
105 (defun image-put-span (image &optional
(color #(0 0 0)) (opacity 1.0) (alpha-function :normalized
))
106 (check-type image
(array octet
(* * 3)))
107 (let ((width (image-width image
))
108 (height (image-height image
)))
111 (setf alpha-function
#'alpha
/normalized
))
113 (setf alpha-function
#'alpha
/even-odd
)))
115 (lambda (x1 x2 y alpha
)
116 (declare (optimize speed
(safety 0) (debug 0)))
117 (when (and (< x1 width
)
119 (<= 0 y
(1- height
)))
120 (setf alpha
(funcall alpha-function alpha
))
121 (loop for x from
(max 0 x1
) below
(min x2 width
)
122 do
(loop for rgb below
3
123 do
(setf (aref image y x rgb
)
124 (blend-value (aref image y x rgb
)
126 (floor (* opacity alpha
))))))))
127 (lambda (x1 x2 y alpha
)
128 (declare (optimize speed
(safety 0) (debug 0)))
129 (when (and (< x1 width
)
131 (<= 0 y
(1- height
)))
132 (setf alpha
(funcall alpha-function alpha
))
133 (loop for x from
(max 0 x1
) below
(min x2 width
)
134 do
(loop for rgb below
3
135 do
(setf (aref image y x rgb
)
136 (blend-value (aref image y x rgb
)
140 ;;;--[ load/save/display ]---------------------------------------------------
142 (defun %load-image
/pnm
(filename)
143 (with-open-file (file filename
:element-type
'octet
)
144 (flet ((read-word (&optional limit
)
145 "Read the next \"word\" (a sequence of non-space
146 characters) skipping initial blanks. The first blank
147 character after the word is also consumed."
148 (declare (ignore limit
)) ; FIXME
149 (let ((result (make-array 0
153 ;; skip blanks, extract the word, consume the following
155 (loop for byte
= (read-byte file
)
156 while
(member byte
'(9 10 13 32))
157 finally
(vector-push-extend byte result
))
158 (loop for byte
= (read-byte file
)
159 until
(member byte
'(9 10 13 32))
160 do
(vector-push-extend byte result
))
162 (parse-ascii-integer (seq)
163 "Parse an integer represented by the ASCII charset
166 (loop for digit in
(coerce seq
'list
)
167 unless
(<= 48 digit
57)
168 do
(error "Invalid ASCII integer")
169 do
(setf result
(+ (* 10 result
) (- digit
48))))
171 (let ((format (read-word 3)))
172 (unless (equalp format
#(80 54))
173 (error "Expected P6 image format (got ASCII sequence ~S)" (subseq format
0 16)))
174 (let ((width (parse-ascii-integer (read-word 10)))
175 (height (parse-ascii-integer (read-word 10)))
176 (maxval (parse-ascii-integer (read-word 10))))
177 (when (/= maxval
255)
178 (error "Expected 24 bits color image"))
179 (unless (and (<= 1 width
4096)
181 (error "Cowardly refusing to read an image of size ~Dx~D" width height
))
182 (let* ((image (make-array (list height width
3) :element-type
'octet
))
184 (end-index (apply #'* (array-dimensions image
))))
185 ;; skip blanks to find the first byte of data.
186 (loop for byte
= (read-byte file
)
187 while
(member byte
'(9 10 13 32))
188 finally
(setf (row-major-aref image index
) byte
))
190 ;; read the rest of the data.
191 (loop while
(< index end-index
)
192 for byte
= (read-byte file
)
193 do
(setf (row-major-aref image index
) byte
)
197 (defun load-image (filename format
)
200 (%load-image
/pnm filename
))))
202 (defun make-array-flat-displaced (array &optional
(start 0))
203 (make-array (apply #'* (array-dimensions array
))
204 :element-type
(array-element-type array
)
206 :displaced-index-offset start
))
208 (defun save-image/pnm
(filename image
)
209 "Save image with PNM format into the file with filename
210 FILENAME. IMAGE must be an (UNSIGNED-BYTE 8) array of
211 dimension (* * 3). Last axis represent the RGB component in that
213 (with-open-file (file filename
216 :if-does-not-exist
:create
217 :if-exists
:overwrite
)
218 (labels ((write-ascii-integer (n stream
)
220 (write-byte 45 stream
) ; #\-
222 (write-sequence (loop with digits
= ()
223 for digit
= (mod n
10)
224 do
(push (+ 48 digit
) digits
)
225 (setf n
(floor n
10))
227 finally
(return digits
))
229 ;; "P6" <width> <height> <maxval>
230 (write-sequence #(80 54) file
)
232 (write-ascii-integer (array-dimension image
1) file
)
234 (write-ascii-integer (array-dimension image
0) file
)
236 (write-ascii-integer 255 file
)
238 (write-sequence (make-array-flat-displaced image
) file
))))
240 (defun save-image (filename image format
)
243 (save-image/pnm filename image
)))
246 ;;; WARNING: Run external program.
247 (defun show-image (image &optional
(external-viewer *external-viewer
*))
248 "Display IMAGE using the specified external viewver."
249 (let ((temp-filename "/tmp/.cl-aa-tmp.pnm"))
250 (save-image temp-filename image
:pnm
)
251 (asdf:run-shell-command
"~S ~S" external-viewer temp-filename
)
252 (delete-file temp-filename
)))