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 ;;;; This file implement the AA algorithm from the AntiGrain project
14 ;;;; (http://antigrain.com/).
18 ;;;; 2007-03-11: Extended the protocol to provide a way to sweep only
19 ;;;; a rectangular zone of the resulting state. This was
20 ;;;; done with some new functions: FREEZE-STATE,
21 ;;;; SCANLINE-SWEEP, SCANLINE-Y and CELLS-SWEEP/RECTANGLE.
22 ;;;; The function CELLS-SWEEP is now based on them.
24 ;;;; 2007-02-25: Released under LLGPL this time. Future changes made
25 ;;;; in this file will be thus covered by this license.
27 ;;;; 2007-01-20: Minors updates to comments and code.
29 ;;;; 2007-01-11: I chose to release the code in this file in public
30 ;;;; domain. You can do whatever you want with the code.
32 ;;;; 2007-01-07: fixed 2 bugs related to cells reuse. The first bug
33 ;;;; was that the cell after the last reused one was kept
34 ;;;; in the list. The second bug occured when the latest
35 ;;;; cell (the current one) was empty. The code was
36 ;;;; failing to correctly eliminate unused cells in such
39 ;;;; 2007-01-05: +cell-width+ is no longer passed as parameter to let
40 ;;;; the CL compiler optimize various computation
41 ;;;; involving this value. Added docstrings and
42 ;;;; (hopefully) clarified some points.
44 ;;;; 2006-12-31: moved examples to a separate file
46 ;;;; 2006-12-30: added animated GIF (using Skippy) example
48 ;;;; 2006-12-30: cleaned the code, factorized, simplified
50 ;;;; 2006-12-30: map-grid-spans rewritten in term of map-line-spans
52 ;;;; 2006-12-30: first release
54 ;;;; About AntiGrain: "Anti-Grain Geometry (AGG) is an Open Source,
55 ;;;; free of charge graphic library, written in industrially standard
56 ;;;; C++." "A High Quality Rendering Engine for C++". Its main author
57 ;;;; is Maxim Shemanarev. Project home page is at http://antigrain.com/
61 ;;;; 1) create a state with MAKE-STATE, or reuse a previous state by
62 ;;;; calling STATE-RESET on it.
64 ;;;; 2) call LINE-F (or LINE) to draw each line of one or several
65 ;;;; closed polygons. It is very important to close them to get a
66 ;;;; coherent result. Note that nothing is really drawn at this
67 ;;;; stage (not until the call to CELLS-SWEEP.)
69 ;;;; 3) finally, call CELLS-SWEEP to let it call your own function for
70 ;;;; each pixels covered by the polygon(s), where the callback
71 ;;;; function take 3 arguments: x, y, alpha. Pixels are scanned on
72 ;;;; increasing y, then on increasing x. Optionnaly, CELLS-SWEEP
73 ;;;; can take another callback function as parameter. See its
74 ;;;; documentation for details.
76 ;;;; The alpha value passed to the callback function can be used in
77 ;;;; various way. Usually you want:
79 ;;;; (defun normalize-alpha (alpha)
80 ;;;; (min 255 (abs alpha)))
82 ;;;; to get a normalized alpha value between 0 and 255. But you may
83 ;;;; also be interested by:
85 ;;;; (defun even-odd-alpha (alpha)
86 ;;;; (let ((value (mod alpha 512)))
87 ;;;; (min 255 (if (< value 256) value (- 512 value)))))
89 ;;;; to simulate "even/odd" fill. You can also use the alpha value
90 ;;;; to render polygons without anti-aliasing by using:
92 ;;;; (defun bool-alpha (value)
93 ;;;; (if (>= (abs value) 128) 255 0))
95 ;;;; or, for "even/odd" fill:
97 ;;;; (defun bool-even-odd-alpha (value)
98 ;;;; (if (<= 128 (mod (abs value) 256) 384) 255 0))
100 ;;;; Note: Drawing direction (clockwise or counter-clockwise) is only
101 ;;;; important if polygons overlap during a single
102 ;;;; cells-state. Opposite directions produce hole at the intersection
103 ;;;; (coverage is canceled), while identical directions does not
104 ;;;; (coverage overflow.)
106 ;;;; The latest version can be downloaded from:
108 ;;;; http://tuxee.net/cl-aa.lisp
109 ;;;; http://tuxee.net/cl-aa-sample.lisp
113 ;;;; http://projects.tuxee.net/cl-aa-path/
115 ;;;; See examples of output at:
117 ;;;; http://tuxee.net/cl-aa-1.png
118 ;;;; http://tuxee.net/cl-aa-2.png (this one was a bug.)
119 ;;;; http://tuxee.net/cl-aa-3.png
120 ;;;; http://tuxee.net/cl-aa-4.png
121 ;;;; http://tuxee.net/cl-aa-5.png (when testing transparency, but looks bad.)
122 ;;;; http://tuxee.net/cl-aa-6.png
123 ;;;; http://tuxee.net/cl-aa-7.png
124 ;;;; http://tuxee.net/cl-aa-8.png
125 ;;;; http://tuxee.net/cl-aa-stroke-0.png (using stroke functions not provided here.)
126 ;;;; http://tuxee.net/cl-aa-stroke-1.png
127 ;;;; http://tuxee.net/cl-aa-stroke-2.png
128 ;;;; http://tuxee.net/cl-aa-skippy-1.gif (animated GIF, thanks to Skippy library)
129 ;;;; http://tuxee.net/cl-aa-skippy-2.gif
131 ;;;; The code is absolutely NOT optimized in any way. It was mainly to
132 ;;;; figure how the algorithm was working. Also, I don't have tested
133 ;;;; many corner cases. It is absolutely NOT for production use.
135 ;;;; About the example, note that the resulting image is exported as a
136 ;;;; PNM file. Not great, but no need for any external lib. You can
137 ;;;; use pnmtopng to convert it to PNG afterward.
139 ;;;; Inspiration come from agg/include/agg_rasterizer_cells_aa.h and
140 ;;;; agg/include/agg_rasterizer_scanline_aa.h sources files from the
141 ;;;; AntiGrain project (version 2.5 at this date.)
143 ;;;; For animated GIF, see Zach Beane's Skippy project at:
144 ;;;; http://www.cliki.net/Skippy
146 ;;;; Naming convention:
147 ;;;; foo-m for fixed-point mantissa,
148 ;;;; foo-f for fixed-point fractional part.
150 #+nil
(error "This file assume that #+NIL is never defined.")
152 (defpackage #:net.tuxee.aa
155 (:export
#:make-state
163 #:cells-sweep
/rectangle
))
165 (in-package #:net.tuxee.aa
)
167 ;;;--[ Utility function ]-----------------------------------------------------
169 (defconstant +cell-width
+ 256
170 "A cell represent a pixel square, and the width is the
171 fractional part of the fixed-point coordinate. A large value
172 increase precision. 256 should be enough though. Note that
173 smaller value should NOT increase performance.")
175 ;;; This function is used to split a line at each pixel boundaries
176 ;;; (when using sub-pixel coordinates.) Since the function only cut
177 ;;; along one axis, it must be called twice (with the second call with
178 ;;; coordinates swapped) to split along X and Y axis.
180 ;;; In the comments below, by "main axis" I mean the X axis if A1 and
181 ;;; A2 are the X coordinates, or the Y axis otherwise.
182 (declaim (inline map-line-spans
))
183 (defun map-line-spans (function a1 b1 a2 b2
)
184 "Call FUNCTION for each segment of a line with integer
185 coordinates (A1,B1)-(A2,B2) cut by a grid of spacing
187 (multiple-value-bind (b1-m b1-f
) (floor b1
+cell-width
+)
188 (multiple-value-bind (b2-m b2-f
) (floor b2
+cell-width
+)
190 ;; The line doesn't cross the grid in the main axis. We have a
191 ;; single segment. Just call FUNCTION.
193 (funcall function b1-m a1 b1-f a2 b2-f
))
194 ;; The line cross the grid in the main axis. We have at least
199 (delta-b (abs (- b2 b1
)))
200 (b-increment (signum (- b2 b1
)))
201 (from-boundary (if (< b1 b2
) 0 +cell-width
+))
202 (to-boundary (if (< b1 b2
) +cell-width
+ 0)))
203 (multiple-value-bind (a ma
) (floor (+ (* delta-a
(if (< b1 b2
)
204 (- +cell-width
+ b1-f
)
206 ;; a littre change compared to
207 ;; AntiGrain AA algorithm. Used
208 ;; to round to the nearest integer
209 ;; instead of the "floor" one.
213 ;; The first segment (to reach the first grid boundary)
214 (funcall function b1-m a1 b1-f a to-boundary
)
215 (incf b-m b-increment
)
217 (multiple-value-bind (step mod
) (floor (* +cell-width
+ delta-a
) delta-b
)
222 (when (>= ma delta-b
)
225 ;; A segment from one grid boundary to the other.
226 (funcall function b-m prev-a from-boundary a to-boundary
)
227 (incf b-m b-increment
))
228 while
(/= b-m b2-m
))))
229 ;; The last segment (from the latest grid boundary up to
230 ;; the final coordinates.)
231 (funcall function b-m a from-boundary a2 b2-f
))))))))
233 (defun map-grid-spans (function x1 y1 x2 y2
)
234 "Call FUNCTION for each segments of the line from (X1,Y1)
235 to (X2,Y2) cut by a grid with spacing +CELL-WIDTH+."
236 (check-type x1 integer
)
237 (check-type y1 integer
)
238 (check-type x2 integer
)
239 (check-type y2 integer
)
240 (flet ((hline (y-m x1 y1-f x2 y2-f
)
241 (declare (integer y-m x1 y1-f x2 y2-f
))
242 (flet ((pixel (x-m y1-f x1-f y2-f x2-f
)
243 (declare (integer x-m y1-f x1-f y2-f x2-f
))
244 (funcall function x-m y-m x1-f y1-f x2-f y2-f
)))
245 ;; further split along Y axis
246 (map-line-spans #'pixel y1-f x1 y2-f x2
))))
247 ;; first split along X axis
248 (map-line-spans #'hline x1 y1 x2 y2
)))
250 ;;;--[ cell ]-----------------------------------------------------------------
252 ;;; Note that cover and area are unbound and could take any value
253 ;;; while drawing polygons (even negative values), especially when
254 ;;; drawing multiple overlapping polygons. However, for non
255 ;;; overlapping polygons, cover is in the range (-width,width) and
256 ;;; area in the range (-2*width*width,2*width*width), where width is
257 ;;; +cell-width+ defined above.
259 "A cell used to represent the partial area covered by a line
260 passing by a corresponding pixel. The cell alone doesn't hold all
261 the information to calculate the area."
264 (cover 0 :type integer
)
265 (area 0 :type integer
))
267 (declaim (inline cell-empty-p
))
268 (defun cell-empty-p (cell)
269 "Test if the cell is empty. A cell is empty when COVER and AREA
271 (and (zerop (cell-cover cell
))
272 (zerop (cell-area cell
))))
274 (declaim (inline cell-reset
))
275 (defun cell-reset (cell)
276 "Reset the cell such that CELL-EMPTY-P is true."
277 (setf (cell-area cell
) 0
278 (cell-cover cell
) 0))
280 (declaim (inline compare-cells
))
281 (defun compare-cells (a b
)
282 "Compare coordinates between 2 cells. Used to sort cells by Y,
284 (or (< (cell-y a
) (cell-y b
))
285 (and (= (cell-y a
) (cell-y b
))
286 (< (cell-x a
) (cell-x b
)))))
288 (declaim (inline update-cell
))
289 (defun update-cell (cell fx1 fy1 fx2 fy2
)
290 "Update COVER and AREA given a segment inside the corresponding
291 cell. FX1, FY1, FX2 and FY2 must be subpixel coordinates between
292 0 and +CELL-WIDTH+ included."
293 (let ((delta (- fy2 fy1
)))
294 (incf (cell-cover cell
) delta
)
295 ;; Note: increase by twice the area, for optimization
296 ;; purpose. Will be divided by 2 in the final pass.
297 (incf (cell-area cell
) (* (+ fx1 fx2
) delta
))))
299 ;;;-------------------------------------------------------------------------
301 (defconstant +alpha-range
+ 256
302 "For non overlapping polygons, the alpha value will be in the
303 range (-limit,limit) where limit is +alpha-range+. The value is
304 negative or positive accordingly to the polygon
305 orientation (clockwise or counter-clockwise.)")
307 (defconstant +alpha-divisor
+ (floor (* 2 +cell-width
+ +cell-width
+)
309 "Constant used to translate value computed by AREA and COVER to
313 "AA state. Hold all the cells generated when drawing lines."
314 (current-cell (make-cell) :type cell
)
317 ;; these slots for reusing cells with state-reset
320 (recycling-cells (cons nil nil
)))
322 (defun state-reset (state)
323 "Reset the state, losing all accumulated cells. It can be
324 faster or less memory consuming to reset a state and reuse it,
325 rather than creating a new state."
326 (cell-reset (state-current-cell state
))
327 (when (state-end-of-lines state
)
328 ;; join back the scanlines to form a single list
329 (loop for line in
(rest (state-scanlines state
))
330 for eol in
(state-end-of-lines state
)
331 do
(setf (cdr eol
) line
)))
332 (let ((cells (nconc (state-dropped-cells state
)
333 (state-cells state
))))
334 (setf (state-recycling-cells state
) (cons nil cells
)
335 (state-scanlines state
) nil
336 (state-end-of-lines state
) nil
337 (state-dropped-cells state
) nil
338 (state-cells state
) cells
)))
340 (declaim (inline state-push-current-cell
))
341 (defun state-push-cell (state cell
)
342 "Store a copy of the current cell into the cells list. If the
343 state was reset, possibly reuse previous cells."
344 (unless (cell-empty-p cell
)
345 (let ((recycling-cells (cdr (state-recycling-cells state
))))
348 (let ((target-cell (car recycling-cells
)))
349 (setf (cell-x target-cell
) (cell-x cell
)
350 (cell-y target-cell
) (cell-y cell
)
351 (cell-cover target-cell
) (cell-cover cell
)
352 (cell-area target-cell
) (cell-area cell
)))
353 (setf (state-recycling-cells state
) recycling-cells
))
355 (push (copy-cell cell
) (state-cells state
)))))))
357 (defun state-finalize (state)
358 "Finalize the state."
359 ;; Ensure that the current cell is stored with other cells and that
360 ;; old cells (before the last reset) that were not reused are
361 ;; correctly removed from the result.
362 (let ((current-cell (state-current-cell state
)))
363 (unless (cell-empty-p current-cell
)
364 (state-push-cell state current-cell
)
365 (cell-reset current-cell
))
366 (when (cdr (state-recycling-cells state
))
367 (setf (cdr (state-recycling-cells state
)) nil
)
368 (unless (car (state-recycling-cells state
))
369 (setf (state-cells state
) nil
)))))
371 (defun set-current-cell (state x y
)
372 "Ensure current cell is one at coordinate X and Y. If not,
373 the current cell is stored, then reset accordingly to new
376 Returns the current cell."
377 (let ((current-cell (state-current-cell state
)))
378 (declare (cell current-cell
))
379 (when (or (/= x
(cell-x current-cell
))
380 (/= y
(cell-y current-cell
)))
381 ;; Store the current cell, then reset it.
382 (state-push-cell state current-cell
)
383 (setf (cell-x current-cell
) x
384 (cell-y current-cell
) y
385 (cell-cover current-cell
) 0
386 (cell-area current-cell
) 0))
389 (defun state-sort-cells (state)
390 "Sort the cells by Y, then by X."
391 (setf (state-cells state
)
392 (sort (state-cells state
) #'compare-cells
)))
394 (defun line (state x1 y1 x2 y2
)
395 "Draw a line from (X1,Y1) to (X2,Y2). All coordinates are
396 integers with subpixel accuracy (a pixel width is given by
397 +CELL-WIDTH+.) The line must be part of a closed polygon."
398 (declare (integer x1 y1 x2 y2
))
399 (map-grid-spans (lambda (x y fx1 fy1 fx2 fy2
)
400 (update-cell (set-current-cell state x y
)
404 (defun line-f (state x1 y1 x2 y2
)
405 "Draw a line, whose coordinates are translated to fixed-point
406 as expected by function LINE. This is a convenient function to
407 not depend on +CELL-WIDTH+."
408 (labels ((float-to-fixed (n)
409 (values (round (* +cell-width
+ n
)))))
411 (float-to-fixed x1
) (float-to-fixed y1
)
412 (float-to-fixed x2
) (float-to-fixed y2
))))
414 (declaim (inline compute-alpha
))
415 (defun compute-alpha (cover area
)
416 "Compute the alpha value given the accumulated cover and the
417 actual area of a cell."
418 (truncate (- (* 2 +cell-width
+ cover
) area
)
421 (defun freeze-state (state)
422 "Freeze the state and return a list of scanlines. A scanline is
423 an object which can be examined with SCANLINE-Y and processed
424 with SCANLINE-SWEEP."
425 (unless (state-scanlines state
)
426 (state-finalize state
)
427 (state-sort-cells state
)
431 (cells (state-cells state
)))
434 (let ((previous-cell (first cells
)))
438 (let ((cell (second cells
))
441 ((/= (cell-y previous-cell
) (cell-y cell
))
442 ;; different y, break the cells list, begin a new
444 (push cells end-of-lines
)
446 (setf (cdr cells
) nil
449 ((/= (cell-x previous-cell
) (cell-x cell
))
450 ;; same y, different x, do nothing special, move to
452 (setf previous-cell cell
)
455 ;; same coordinates, accumulate current cell into
456 ;; the previous, and remove current from the list.
457 (incf (cell-cover previous-cell
) (cell-cover cell
))
458 (incf (cell-area previous-cell
) (cell-area cell
))
459 (push cell dropped-cells
)
460 (setf (cdr cells
) (cdr rest
))))))))
461 (setf (state-scanlines state
) (nreverse lines
)
462 (state-end-of-lines state
) (nreverse end-of-lines
)
463 (state-dropped-cells state
) dropped-cells
)))
464 (state-scanlines state
))
466 (declaim (inline scanline-y
))
467 (defun scanline-y (scanline)
468 "Get the Y position of SCANLINE."
469 (cell-y (first scanline
)))
471 (defun scanline-sweep (scanline function function-span
&key start end
)
472 "Call FUNCTION for each pixel on the polygon covered by
473 SCANLINE. The pixels are scanned in increasing X. The sweep can
474 be limited to a range by START (included) or/and END (excluded)."
475 (declare (optimize speed
(debug 0) (safety 0) (space 2)))
477 (y (scanline-y scanline
))
481 ;; skip initial cells that are before START
482 (loop while
(and cells
(< (cell-x (car cells
)) start
))
483 do
(incf cover
(cell-cover (car cells
)))
484 (setf last-x
(cell-x (car cells
))
488 (let ((x (cell-x cell
)))
489 (when (and last-x
(> x
(1+ last-x
)))
490 (let ((alpha (compute-alpha cover
0)))
491 (unless (zerop alpha
)
492 (let ((start-x (if start
(max start
(1+ last-x
)) (1+ last-x
)))
493 (end-x (if end
(min end x
) x
)))
495 (funcall function-span start-x end-x y alpha
)
496 (loop for ix from start-x below end-x
497 do
(funcall function ix y alpha
)))))))
498 (when (and end
(>= x end
))
500 (incf cover
(cell-cover cell
))
501 (let ((alpha (compute-alpha cover
(cell-area cell
))))
502 (unless (zerop alpha
)
503 (funcall function x y alpha
)))
506 (defun cells-sweep/rectangle
(state x1 y1 x2 y2 function
&optional function-span
)
507 "Call FUNCTION for each pixel on the polygon described by
508 previous call to LINE or LINE-F. The pixels are scanned in
509 increasing Y, then on increasing X. This is limited to the
510 rectangle region specified with (X1,Y1)-(X2,Y2) (where X2 must be
511 greater than X1 and Y2 must be greater than Y1, to describe a
514 For optimization purpose, the optional FUNCTION-SPAN, if
515 provided, is called for a full span of identical alpha pixel. If
516 not provided, a call is made to FUNCTION for each pixel in the
518 (let ((scanlines (freeze-state state
)))
519 (dolist (scanline scanlines
)
520 (when (<= y1
(scanline-y scanline
) (1- y2
))
521 (scanline-sweep scanline function function-span
:start x1
:end x2
))))
524 (defun cells-sweep (state function
&optional function-span
)
525 "Call FUNCTION for each pixel on the polygon described by
526 previous call to LINE or LINE-F. The pixels are scanned in
527 increasing Y, then on increasing X.
529 For optimization purpose, the optional FUNCTION-SPAN, if
530 provided, is called for a full span of identical alpha pixel. If
531 not provided, a call is made to FUNCTION for each pixel in the
533 (let ((scanlines (freeze-state state
)))
534 (dolist (scanline scanlines
)
535 (scanline-sweep scanline function function-span
)))