Skip to content

Commit 9010601

Browse files
authored
Merge pull request #10 from phoe-trash/master
Add image composition, VECTO-IMAGO contrib, custom allocators and returning images
2 parents a1176a0 + d9312df commit 9010601

6 files changed

+197
-10
lines changed

doc/index.html

+48-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ <h3>Contents</h3>
6565
<ul>
6666
<li> <a href='#with-canvas'><tt>with-canvas</tt></a>
6767
<li> <a href='#clear-canvas'><tt>clear-canvas</tt></a>
68+
<li> <a href='#compose'><tt>compose</tt></a>
6869
<li> <a href='#save-png'><tt>save-png</tt></a>
6970
<li> <a href='#save-png-stream'><tt>save-png-stream</tt></a>
7071
</ul>
@@ -304,13 +305,33 @@ <h3>Contents</h3>
304305
<a name='sect-canvases'><h4>Canvases</h4></a>
305306

306307
<p><a name='with-canvas'>[Macro]</a><br>
307-
<b>with-canvas</b> (<tt>&amp;key</tt> <i>width</i> <i>height</i>)
308-
<tt>&amp;body</tt> <i>body</i>
308+
<b>with-canvas</b> (<tt>&amp;key</tt> <i>width</i> <i>height</i>
309+
<i>image-data-allocator</i>) <tt>&amp;body</tt> <i>body</i>
309310

310311
<blockquote>
311312
Evaluates <i>body</i> with a canvas established with the specified
312313
dimensions as the target for drawing commands. The canvas is initially
313314
completely clear (all pixels have 0 alpha).
315+
316+
It is possible to provide a one-argument function to the
317+
<i>image-data-allocator</i> argument. That function will be called with
318+
one argument (the size of the resulting vector) and must return a vector
319+
of <i>(unsigned-byte 8)</i> which will be used as storage for the image
320+
data. This is useful e.g. for using a
321+
<a href='https://github.com/sionescu/static-vectors'>static vector</a>
322+
for the storage in order to make the resulting PNG data interoperable
323+
with foreign libraries:
324+
325+
<pre><img style='float: right' class='transparent'
326+
src='linear-gradient.png'>(flet ((allocate (size)
327+
(static-vectors:make-static-vector size :initial-element 0)))
328+
(with-canvas (:width ... :height ...
329+
:image-data-allocator #'allocate)
330+
...))
331+
</pre>
332+
333+
Also see <a href='#zpng-object'><tt>ZPNG-OBJECT</tt></a>.
334+
314335
</blockquote>
315336

316337

@@ -323,6 +344,24 @@ <h3>Contents</h3>
323344
</blockquote>
324345

325346

347+
<p><a name='compose'>[Generic Function]</a><br>
348+
<b>compose</b> <i>layer</i> <i>x</i> <i>y</i> => |
349+
350+
<blockquote>
351+
Performs image composition by superimposing <i>image</i> on top of the
352+
current canvas, relative to the coordinate center of the image.
353+
354+
<b>This composition disregards all transformations done to the image's
355+
coordinate system other than translations - so, skewing, rotations, etc..</b>
356+
Use systems that manipulate raster graphics (such as
357+
<a href='https://common-lisp.net/project/imago/'>Imago</a>) to adjust
358+
the raster image before compositing it with Vecto.
359+
360+
This GF has no default implementations and is meant to be implemented
361+
by users.
362+
</blockquote>
363+
364+
326365
<p><a name='save-png'>[Function]</a><br>
327366
<b>save-png</b> <i>file</i> => <i>truename</i>
328367

@@ -341,6 +380,13 @@ <h3>Contents</h3>
341380
</blockquote>
342381

343382

383+
<p><a name='zpng-object'>[Function]</a><br>
384+
<b>zpng-object</b> => <i>png</i>
385+
386+
<blockquote>
387+
Returns the ZPNG object representing the canvas.
388+
</blockquote>
389+
344390
<a name='sect-graphics-state'><h4>Graphics State</h4></a>
345391

346392
<p>The graphics state stores several parameters used for graphic

graphics-state.lisp

+14-6
Original file line numberDiff line numberDiff line change
@@ -155,19 +155,27 @@ with the result of premultiplying it with MATRIX.")
155155
(defmethod (setf paths) :after (new-value (state graphics-state))
156156
(setf (path state) (first new-value)))
157157

158-
(defun state-image (state width height)
158+
(defun state-image (state width height &optional image-data-allocator)
159159
"Set the backing image of the graphics state to an image of the
160160
specified dimensions."
161161
(setf (image state)
162-
(make-instance 'zpng:png
163-
:width width
164-
:height height
165-
:color-type +png-color-type+)
162+
(if image-data-allocator
163+
(make-instance 'zpng:png
164+
:width width
165+
:height height
166+
:color-type +png-color-type+
167+
:image-data (let ((samples (zpng:samples-per-pixel
168+
+png-color-type+)))
169+
(funcall image-data-allocator
170+
(* width height samples))))
171+
(make-instance 'zpng:png
172+
:width width
173+
:height height
174+
:color-type +png-color-type+))
166175
(width state) width
167176
(height state) height
168177
(clipping-path state) (make-clipping-path width height))
169178
(apply-matrix state (translation-matrix 0 (- height))))
170-
171179

172180
(defun find-font-loader (state file)
173181
(let* ((cache (font-loaders state))

package.lisp

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#:clear-canvas
4242
#:save-png
4343
#:save-png-stream
44+
#:zpng-object
4445
;; path construction
4546
#:move-to
4647
#:line-to
@@ -83,6 +84,8 @@
8384
#:bilinear-domain
8485
#:cartesian-coordinates
8586
#:polar-coordinates
87+
;; generic functions for handling foreign images
88+
#:compose
8689
;; graphics state coordinate transforms
8790
#:translate
8891
#:rotate

user-drawing.lisp

+9-2
Original file line numberDiff line numberDiff line change
@@ -325,15 +325,22 @@ through one control point."
325325
(defun rotate-degrees (degrees)
326326
(%rotate *graphics-state* (* (/ pi 180) degrees)))
327327

328+
(defgeneric compose (layer x y))
329+
328330
(defun save-png (file)
329331
(zpng:write-png (image *graphics-state*) file))
330332

331333
(defun save-png-stream (stream)
332334
(zpng:write-png-stream (image *graphics-state*) stream))
333335

334-
(defmacro with-canvas ((&key width height) &body body)
336+
(defun zpng-object ()
337+
(image *graphics-state*))
338+
339+
(defmacro with-canvas ((&key width height image-data-allocator)
340+
&body body)
335341
`(let ((*graphics-state* (make-instance 'graphics-state)))
336-
(state-image *graphics-state* ,width ,height)
342+
(state-image *graphics-state* ,width ,height
343+
,@(when image-data-allocator `(,image-data-allocator)))
337344
(unwind-protect
338345
(progn
339346
,@body)

vecto-imago/vecto-imago.asd

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
;;; Copyright (c) 2007 Michał "phoe" Herda, All Rights Reserved
2+
;;;
3+
;;; Redistribution and use in source and binary forms, with or without
4+
;;; modification, are permitted provided that the following conditions
5+
;;; are met:
6+
;;;
7+
;;; * Redistributions of source code must retain the above copyright
8+
;;; notice, this list of conditions and the following disclaimer.
9+
;;;
10+
;;; * Redistributions in binary form must reproduce the above
11+
;;; copyright notice, this list of conditions and the following
12+
;;; disclaimer in the documentation and/or other materials
13+
;;; provided with the distribution.
14+
;;;
15+
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
16+
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17+
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18+
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
19+
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20+
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
21+
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22+
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23+
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24+
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
27+
(asdf:defsystem #:vecto-imago
28+
:depends-on (#:vecto #:imago)
29+
:version "1.0"
30+
:author "Michał \"phoe\" Herda <[email protected]>"
31+
:description "Utilities for Imago and Vecto interoperability."
32+
:license "BSD"
33+
:components ((:file "vecto-imago")))
34+

vecto-imago/vecto-imago.lisp

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
(defpackage #:vecto-imago
2+
(:use #:cl)
3+
(:local-nicknames (#:v #:vecto) (#:i #:imago) (#:z #:zpng)))
4+
5+
(in-package #:vecto-imago)
6+
7+
(declaim (inline blend-alpha))
8+
(defun blend-alpha (src-a dest-a)
9+
(declare (optimize speed))
10+
(declare (type (unsigned-byte 8) src-a dest-a))
11+
(let* ((src-a (* 1f0 1/255 src-a))
12+
(dest-a (* 1f0 1/255 dest-a))
13+
(result (+ src-a (* dest-a (- 1f0 src-a)))))
14+
(round (* result 255f0))))
15+
16+
(declaim (inline blend))
17+
(defun blend (ca cb aa ab ar)
18+
(declare (optimize speed))
19+
(declare (type (unsigned-byte 8) ca cb aa ab ar))
20+
(cond ((= 0 aa) cb)
21+
((= 0 ab) ca)
22+
(t (let* ((aa (* 1f0 1/255 aa))
23+
(ab (* 1f0 1/255 ab))
24+
(ar (* 1f0 1/255 ar)))
25+
(let* ((left (* ca aa))
26+
(right (* cb ab (- 1f0 aa)))
27+
(result (/ (+ left right) ar)))
28+
(declare (type (single-float 0f0 255f0) left right result))
29+
(round result))))))
30+
31+
(deftype png-image-dimension () '(unsigned-byte 31))
32+
33+
(defmethod v:compose ((layer imago:rgb-image) x-offset y-offset)
34+
(declare (optimize speed))
35+
(declare (type (signed-byte 32) x-offset y-offset))
36+
(let* ((src (imago:image-pixels layer))
37+
(zpng (v:zpng-object))
38+
(dest (zpng:image-data zpng)))
39+
(declare (type (simple-array imago:rgb-pixel (* *)) src))
40+
(declare (type (simple-array (unsigned-byte 8) (*)) dest))
41+
(destructuring-bind (src-height src-width) (array-dimensions src)
42+
(declare (type png-image-dimension src-height src-width))
43+
(let* ((dest-height (z:height zpng))
44+
(dest-width (z:width zpng))
45+
;; TODO: stop being ugly here, write a reader function in vecto
46+
;; which returns the transform matrix
47+
;; and then export accessor functions for it
48+
(matrix (v::transform-matrix v::*graphics-state*))
49+
(matrix-x-offset (ceiling
50+
(the (single-float 0f0 #.(* (ash 1 31) 1f0))
51+
(v::transform-matrix-x-offset matrix))))
52+
(matrix-y-offset (ceiling
53+
(the (single-float 0f0 #.(* (ash 1 31) 1f0))
54+
(v::transform-matrix-y-offset matrix))))
55+
(x-offset (+ matrix-x-offset x-offset))
56+
(y-offset (- matrix-y-offset y-offset src-height)))
57+
(declare (type png-image-dimension dest-height dest-width))
58+
(dotimes (src-y src-height)
59+
(dotimes (src-x src-width)
60+
(let ((dest-y (+ src-y y-offset))
61+
(dest-x (+ src-x x-offset)))
62+
(declare (type (signed-byte 32) dest-x dest-x))
63+
(when (and (<= 0 dest-y (1- dest-height))
64+
(<= 0 dest-x (1- dest-width)))
65+
(let* ((src-color (aref src src-y src-x))
66+
(src-a (i:color-alpha src-color))
67+
(src-r (i:color-red src-color))
68+
(src-g (i:color-green src-color))
69+
(src-b (i:color-blue src-color))
70+
(dest-y-offset (* dest-y dest-width))
71+
(dest-xy-offset (+ dest-y-offset dest-x))
72+
(dest-offset (* (the (unsigned-byte 8)
73+
(z:samples-per-pixel zpng))
74+
dest-xy-offset))
75+
(dest-a (aref dest (+ dest-offset 3)))
76+
(dest-b (aref dest (+ dest-offset 2)))
77+
(dest-g (aref dest (+ dest-offset 1)))
78+
(dest-r (aref dest (+ dest-offset 0))))
79+
(declare (type png-image-dimension
80+
dest-y-offset dest-offset))
81+
(unless (= src-a 0)
82+
(let* ((a (blend-alpha src-a dest-a))
83+
(r (blend src-r dest-r src-a dest-a a))
84+
(g (blend src-g dest-g src-a dest-a a))
85+
(b (blend src-b dest-b src-a dest-a a)))
86+
(setf (aref dest (+ dest-offset 3)) a
87+
(aref dest (+ dest-offset 2)) b
88+
(aref dest (+ dest-offset 1)) g
89+
(aref dest (+ dest-offset 0)) r))))))))))))

0 commit comments

Comments
 (0)