Rename *ll* and *ul* to ll and ul in easy-subs
[maxima.git] / share / draw / picture.lisp
blob61e5bbb2de1dba50f535e929622cedcecf61f74f
1 ;; COPYRIGHT NOTICE
2 ;;
3 ;; Copyright (C) 2007-2016 Mario Rodriguez Riotorto
4 ;;
5 ;; This program is free software; you can redistribute
6 ;; it and/or modify it under the terms of the
7 ;; GNU General Public License as published by
8 ;; the Free Software Foundation; either version 2
9 ;; of the License, or (at your option) any later version.
10 ;;
11 ;; This program is distributed in the hope that it
12 ;; will be useful, but WITHOUT ANY WARRANTY;
13 ;; without even the implied warranty of MERCHANTABILITY
14 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details at
16 ;; http://www.gnu.org/copyleft/gpl.html
18 ;; The picture package. UNSTABLE !!
20 ;; For questions, suggestions, bugs and the like, feel free
21 ;; to contact me at
22 ;; mario @@@ edu DOT xunta DOT es
23 ;; http://tecnostats.net/Maxima/gnuplot
27 (defun cut-and-round (seq)
28 (map 'list
29 #'(lambda (z)
30 (let ((fz (meval `($float ,z))))
31 (cond
32 ((< fz 0) 0)
33 ((> fz 255) 255)
34 (t (round fz)))))
35 seq) )
39 ;; Constructs a levels picture. This object contains four parts:
40 ;; 1) symbol 'level
41 ;; 2) image width
42 ;; 3) image height
43 ;; 4) an integer array with pixel data ranging from 0 to 255.
44 ;; Argument data must contain only numbers ranged from 0 to 255;
45 ;; negative numbers are substituted by 0, and those which are
46 ;; greater than 255 are set to 255. If data is a Maxima list,
47 ;; width and height must be given.
48 (defun $make_level_picture (data &optional (wi nil) (he nil))
49 (let (width height picarray)
50 (cond
51 (($matrixp data)
52 (setf width (length (cdadr data))
53 height (length (cdr data)))
54 (setf picarray (make-array (* height width)
55 :element-type 'integer
56 :initial-contents (cut-and-round (rest ($flatten ($args data)))))) )
57 ((and ($listp data)
58 (integerp wi)
59 (integerp he)
60 (= (* wi he) ($length data)))
61 (setf width wi
62 height he
63 picarray (make-array (* wi he)
64 :element-type 'integer
65 :initial-contents (cut-and-round (rest data)))))
67 (merror "make_level_picture: argument must be a matrix, or a list of integers with length = width*height.")))
68 (list '($picture) '$level width height picarray) ))
72 ;; Returns true if the argument is a well formed image,
73 ;; and false otherwise
75 (defun $picturep (x)
76 (and
77 (consp x)
78 (eq (caar x) '$picture)
79 (= (length x) 5)
80 (member (second x) '($level $rgb $rgb_alpha))
81 (integerp (third x))
82 (integerp (fourth x))
83 (vectorp (fifth x))
84 (cond
85 ((eq (second x) '$level)
86 (= (length (fifth x)) (* (third x) (fourth x))))
87 ((eq (second x) '$rgb)
88 (= (length (fifth x)) (* 3 (third x) (fourth x))))
89 ((eq (second x) '$rgb_alpha)
90 (= (length (fifth x)) (* 4 (third x) (fourth x)))))
91 (every #'(lambda (z) (and (integerp z) (>= z 0) (<= z 255))) (fifth x))))
95 ;; Returns true in case of equal pictures, and false otherwise.
96 (defun $picture_equalp (pic1 pic2)
97 (if (and ($picturep pic1) ($picturep pic2))
98 (alike1 pic1 pic2)
99 (merror "picture_equalp: both arguments must be picture objects.")))
103 ;; Constructs a coloured rgb picture. This object contains four parts:
104 ;; 1) symbol 'rgb
105 ;; 2) image width
106 ;; 3) image height
107 ;; 4) an integer array of length 3*width*height with pixel data ranging
108 ;; from 0 to 255. Each pixel is represented by three consecutive numbers
109 ;; (red, green, blue). Arguments must contain the three channels in
110 ;; level_picture.
111 (defun $make_rgb_picture (redlevel greenlevel bluelevel) ;; TODO: let alpha be an optional argument
112 (when (not (and ($picturep redlevel)
113 (equal (cadr redlevel) '$level)
114 ($picturep greenlevel)
115 (equal (cadr greenlevel) '$level)
116 ($picturep bluelevel)
117 (equal (cadr bluelevel) '$level)))
118 (merror "make_rgb_picture: every color channel must be a picture object with type = level."))
119 (when (not (and (= (caddr redlevel) (caddr greenlevel) (caddr bluelevel))
120 (= (cadddr redlevel) (cadddr greenlevel) (cadddr bluelevel)) ))
121 (merror "make_rgb_picture: color channels are not of equal dimensions."))
122 (let (width height leng picarray i3)
123 (setf width (caddr redlevel)
124 height (cadddr redlevel))
125 (setf leng (* width height))
126 (setf picarray (make-array (* 3 leng) :element-type 'integer))
127 (loop for i from 0 below leng do
128 (setf i3 (* 3 i))
129 (setf (aref picarray i3) (aref (nth 4 redlevel) i))
130 (setf (aref picarray (incf i3)) (aref (nth 4 greenlevel) i))
131 (setf (aref picarray (incf i3)) (aref (nth 4 bluelevel) i)))
132 (list '($picture) '$rgb width height picarray) ))
136 ;; Extracts color channel ('red, 'green or 'blue) from a coloured picture.
137 ;; TODO: handle CHN = '$ALPHA as well.
138 ;; Returns a levels picture.
139 (defun $take_channel (pic chn)
140 (when (not (and ($picturep pic)
141 (or (equal (cadr pic) '$rgb) (equal (cadr pic) '$rgb_alpha))))
142 (merror "take_channel: first argument must be a picture object with type = rgb or rgb_alpha."))
143 (when (not (member chn '($red $green $blue)))
144 (merror "take_channel: color channel must be red, green, or blue."))
145 (let* ((width (caddr pic))
146 (height (cadddr pic))
147 (dim (* width height))
148 (img (make-array dim :element-type 'integer))
149 (stride (if (eq (second pic) '$rgb) 3 4))
150 idx)
151 (setf idx
152 (case chn
153 ($red 0)
154 ($green 1)
155 ($blue 2)))
156 (loop for i from 0 below dim do
157 (setf (aref img i) (aref (nth 4 pic) (+ (* stride i) idx))))
158 (list '($picture) '$level width height img) ))
162 ;; Returns the negative of a (level or rgb) picture.
164 ;; For RGB images with alpha channel, take the negative of RGB channels,
165 ;; and return alpha channel unmodified.
167 (defun $negative_picture (pic)
168 (if (not ($picturep pic))
169 (merror "negative_picture: argument must be a picture object."))
170 (let ((dim (array-dimension (nth 4 pic) 0))
171 (stride (if (eq (second pic) '$rgb) 3 (if (eq (second pic) '$level) 1 4)))
172 (arr (copy-seq (nth 4 pic))))
174 (loop for i from 0 below (/ dim stride) do
175 (let ((base (* i stride)))
176 (if (eq (second pic) '$level)
177 (setf (aref arr (+ base 0)) (- 255 (aref arr (+ base 0))))
178 (setf
179 (aref arr (+ base 0)) (- 255 (aref arr (+ base 0)))
180 (aref arr (+ base 1)) (- 255 (aref arr (+ base 1)))
181 (aref arr (+ base 2)) (- 255 (aref arr (+ base 2)))))))
183 (list '($picture)
184 (nth 1 pic)
185 (nth 2 pic)
186 (nth 3 pic)
187 arr)))
191 ;; Transforms an rgb picture into a level one by
192 ;; averaging the red, green and blue values.
193 ;; The alpha channel, if present, is ignored.
194 ;; TODO: I wonder if it makes sense to copy alpha into a would-be
195 ;; "level + alpha" image.
196 (defun $rgb2level (pic)
197 (if (or (not ($picturep pic))
198 (not (or (equal (nth 1 pic) '$rgb) (equal (nth 1 pic) '$rgb_alpha))))
199 (merror "rgb2level: argument must be a picture object with type = rgb or rgb_alpha."))
200 (let* ((level-dim (* (nth 2 pic) (nth 3 pic)))
201 (rgb-stride (if (eq (nth 1 pic) '$rgb) 3 4))
202 (rgb-array (nth 4 pic))
203 (level-array (make-array level-dim :element-type 'integer)))
205 (loop for i from 0 below level-dim do
206 (let ((rgb-base (* rgb-stride i)))
207 (setf (aref level-array i)
208 (round (/ (+
209 (aref rgb-array (+ rgb-base 0))
210 (aref rgb-array (+ rgb-base 1))
211 (aref rgb-array (+ rgb-base 2)))
212 3)))))
214 (list '($picture)
215 '$level
216 (nth 2 pic)
217 (nth 3 pic)
218 level-array)))
222 ;; Returns pixel from picture. Coordinates x and y range from 0 to
223 ;; (width-1) and (height-1), respectively. We are working
224 ;; with arrays, not with lists.
226 ;; For RGB pictures, return a list of 3 elements, R, G, and B.
227 ;; For RGB + alpha pictures, return a list of 4 elements, R, G, B, and alpha.
229 (defun $get_pixel (pic x y)
230 (when (not ($picturep pic))
231 (merror "get_pixel: first argument must be a picture object."))
232 (when (not (and (integerp x) (integerp y)))
233 (merror "get_pixel: pixel coordinates must be integers."))
234 (when (not (and (> x -1)
235 (< x (nth 2 pic))
236 (> y -1)
237 (< y (nth 3 pic))))
238 (merror "get_pixel: pixel coordinates out of range."))
239 (let ((stride (if (eq (second pic) '$rgb) 3 (if (eq (second pic) '$level) 1 4))))
240 (case (nth 1 pic)
241 ($level
242 (aref (nth 4 pic) (+ x (* y (nth 2 pic)))))
243 ($rgb
244 (let
245 ((pos (* stride (+ x (* y (nth 2 pic))))))
246 (list
247 '(mlist)
248 (aref (nth 4 pic) pos)
249 (aref (nth 4 pic) (incf pos))
250 (aref (nth 4 pic) (incf pos)))))
251 ($rgb_alpha
252 (let
253 ((pos (* stride (+ x (* y (nth 2 pic))))))
254 (list
255 '(mlist)
256 (aref (nth 4 pic) pos)
257 (aref (nth 4 pic) (incf pos))
258 (aref (nth 4 pic) (incf pos))
259 (aref (nth 4 pic) (incf pos))))))))
268 ;;; XPM I M A G E F O R M A T S U P P O R T
270 ;; The following functions have been taken from
271 ;; http://common-lisp.net/project/gamelib/ (MIT license)
272 ;; Changes have been made to fit the Maxima environment.
275 (defvar *xpm-readtable* nil)
278 (defun init-readtable ()
279 (unless *xpm-readtable*
280 (setf *xpm-readtable* (copy-readtable))
281 (set-macro-character #\/ #'(lambda (s c) (declare (ignore c)) (let ((*parse-stream* s)) (gobble-comment)) (values)) nil *xpm-readtable*)
282 (set-syntax-from-char #\, #\Space *xpm-readtable*)))
285 (defun skip-whitespace (f)
286 (loop for c = (read-char f nil)
287 while (and c (member c '(#\space #\tab) :test #'char=))
288 finally (when c (unread-char c f))
292 (defun extract-r-g-b-bits (n-bits-per-color-output n-hex-digits-input value)
293 (let*
294 ((n-bits-per-color-input (ceiling (/ (* n-hex-digits-input 4) 3)))
295 (r-bits-input (ash value (- (* n-bits-per-color-input 2))))
296 (g-bits-input (mod (ash value (- n-bits-per-color-input)) (ash 1 n-bits-per-color-input)))
297 (b-bits-input (mod value (ash 1 n-bits-per-color-input)))
298 (r-bits-output (ash r-bits-input (- n-bits-per-color-output n-bits-per-color-input)))
299 (g-bits-output (ash g-bits-input (- n-bits-per-color-output n-bits-per-color-input)))
300 (b-bits-output (ash b-bits-input (- n-bits-per-color-output n-bits-per-color-input))))
301 (list r-bits-output g-bits-output b-bits-output)))
304 (defun read-colour (f)
305 (let ((ctype (read-char f)))
306 (case ctype
308 (#\#
310 ; We have encountered an RGB color in hexadecimal format.
311 ; Return a list of two elements comprising packed rgb bits and alpha = 255 (fully opaque color).
313 (let (a)
314 (loop for c = (read-char f nil) while (and c (digit-char-p c 16)) finally (when c (unread-char c f)) do (push c a))
315 (multiple-value-bind (value ndigits)
316 (parse-integer (coerce (reverse a) 'string) :radix 16)
317 ; Some XPM images contain 12 hex digits per color;
318 ; the XPM spec itself seems to be silent about how many are allowed.
319 ; To accommodate code for picture objects here in share/draw,
320 ; truncate colors to 8 bits (i.e., 2 hex digits per color, 6 hex digits in all).
321 ; When NDIGITS = 6, EXTRACT-R-G-B-BITS just splits COLOR-BITS without shifting.
322 (list (extract-r-g-b-bits 8 ndigits value) 255))))
324 ; color name:
325 ; 0. read the rest of the name and append the first letter
326 ; 1. get the hexadecimal code from *color-table* defined in grcommon.lisp
327 ; 2. remove # and transform the code to an integer in base 10
328 (otherwise
329 (let ((color-name (atom-to-downcased-string (format nil "~a~a" ctype (read f)))) alpha color-bits)
330 (if (string= color-name "none")
331 (setq alpha 0 color-bits 0)
332 (let ((color-table-value (gethash color-name *color-table*)))
333 (setq alpha 255)
334 (if (null color-table-value)
335 (merror "read_xpm: unrecognized color name ~M" color-name)
336 (setq color-bits (parse-integer (subseq color-table-value 1) :radix 16)))))
337 ; For call to EXTRACT-R-G-B-BITS, assume all color table items are 6 hex digits.
338 ; Given these arguments, EXTRACT-R-G-B-BITS just splits COLOR-BITS without shifting.
339 (list (extract-r-g-b-bits 8 6 color-bits) alpha))))))
342 (defun read-charspec (f cnt)
343 (format nil "~{~c~}"
344 (loop for n from 0 below cnt
345 collect (read-char f))))
348 (defun read-color-spec-for-type (str cnt hash type-char)
349 (with-input-from-string (cs str)
350 (let (c (chars (read-charspec cs cnt)))
351 (skip-whitespace cs)
352 (setq c (read-char cs))
353 (loop while (and c (char/= c type-char))
354 do (read cs nil)
355 (skip-whitespace cs)
356 (setq c (read-char cs nil)))
357 (if (and c (char= c type-char))
358 (let ((color-spec (progn (skip-whitespace cs) (read-colour cs))))
359 (setf (gethash chars hash) color-spec))))))
361 (defun read-color-spec (str cnt hash)
362 (or (read-color-spec-for-type str cnt hash #\c)
363 (read-color-spec-for-type str cnt hash #\m)
364 (read-color-spec-for-type str cnt hash #\g)
365 (merror "read_xpm: failed to parse color specification ''~M''" str)))
367 (defun $read_xpm (mfspec)
368 (cond
369 ((streamp mfspec)
370 (read-xpm-from-stream mfspec))
372 (let ((fspec (string-trim "\"" (coerce (mstring mfspec) 'string))))
373 (with-open-file (image fspec :direction :input)
374 (read-xpm-from-stream image))))))
376 (defun read-xpm-from-stream (image)
377 (init-readtable)
378 (let ((*readtable* *xpm-readtable*))
379 (let
380 ((first-line-raw (ignore-errors (read-line image))))
381 (if first-line-raw
382 (let ((first-line (string-trim '(#\Space #\Tab #\Return #\Newline) first-line-raw)))
383 (when (string/= first-line "/* XPM */")
384 (if (string= first-line "! XPM2")
385 (merror "read_xpm: I don't know how to read XPM2 format.")
386 (merror "read_xpm: input doesn't appear to be XPM3 format; first line: ~M" first-line))))
387 (merror "read_xpm: failed to read first line; are you sure this is an XPM image?")))
388 ; Burn off any additional comment or comments, and C code ending in left curly brace.
389 (loop for x = (read image) while (not (eq x '{)))
390 (let ((colspec (read image))
391 width
392 height
393 (chartab (make-hash-table :test #'equal))
394 img)
395 (with-input-from-string (cspec colspec)
396 (setf width (read cspec))
397 (setf height (read cspec))
398 (let ((colours (read cspec))
399 (cpp (read cspec))
400 rgb+alpha (counter -1))
401 (loop for cix from 0 below colours
402 for c = (read image)
403 do (read-color-spec c cpp chartab))
404 (setf img (make-array (* 4 width height) :element-type 'integer))
405 (loop for y from 0 below height
406 for line = (read image)
407 do (progn
408 (when (not (stringp line))
409 (merror "read_xpm: failed to read ~M'th line of image; found: ~M" (1+ y) line))
410 (with-input-from-string (data line)
411 (loop for x from 0 below width
412 for cs = (read-charspec data cpp) do
413 (setf rgb+alpha (gethash cs chartab))
414 (let
415 ((rgb (first rgb+alpha))
416 (alpha (second rgb+alpha)))
417 (setf (aref img (incf counter)) (first rgb))
418 (setf (aref img (incf counter)) (second rgb))
419 (setf (aref img (incf counter)) (third rgb))
420 (setf (aref img (incf counter)) alpha)))) ))
421 (list '($picture) '$rgb_alpha width height img))))))