-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathgraphics-state.lisp
More file actions
250 lines (229 loc) · 9.68 KB
/
graphics-state.lisp
File metadata and controls
250 lines (229 loc) · 9.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
;; graphics-state.lisp -- The vagaries of dealing with a mutable graphics state of mutable objects...
;;
;; DM/RAL 02/24
;; --------------------------------------
(in-package :plotter)
;; ================================================================
;; Graphics State - more than meets the eye...
;; ================================================================
;; Saving and Switching the Graphic State
;;
;; ------------------------------------------------------------------
;; Uh oh... this passes:
;;
;; (assert (eq (gp:port-graphics-state port)
;; (slot-value port 'gp:graphics-state)))
;;
;; So we better be making struture copies of the graphics state.
;; Else save/restore does nothing useful.
;;
;; It seems sad that we need to know that graphics-state is a STRUCT.
;; This is not future-proof design.
;;
(defun do-with-saved-graphics-state (pane fn)
(let ((sav (get-graphics-state pane)))
(unwind-protect
(funcall fn)
(set-graphics-state pane sav))
))
(defmacro with-saved-graphics-state (pane &body body)
`(do-with-saved-graphics-state ,pane (lambda ()
,@body)))
;; -------------------------------------------------------
;; It gets worse... not only is the graphics state a mutable struct,
;; but the transform inside is also a mutable thing.
;;
;; *AND* You must carry along the current GS Mask for clipping, and
;; not restore what it was when you saved the GS.
(defstruct saved-gs
state xform)
(defun get-graphics-state (pane)
(let* ((gs (copy-structure (gp:port-graphics-state pane)))
(xform (gp:copy-transform (gp:graphics-state-transform gs))))
(make-saved-gs
:state gs
:xform xform)
))
(defun set-graphics-state (pane saved)
(let ((new-gs (copy-structure (saved-gs-state saved)))
(new-xform (gp:copy-transform (saved-gs-xform saved)))
(mask (gp:graphics-state-mask (gp:port-graphics-state pane))))
(setf (gp:graphics-state-transform new-gs) new-xform ;; restore the transform
(gp:graphics-state-mask new-gs) mask ;; carry along the current clipping mask
(gp:port-graphics-state pane) new-gs) ;; restore the state
))
;; -----------------------
(defun do-with-new-graphics-state (pane new-gs fn)
(with-saved-graphics-state pane
(set-graphics-state pane new-gs)
(funcall fn)))
(defmacro with-new-graphics-state ((pane new-state) &body body)
`(do-with-new-graphics-state ,pane ,new-state (lambda ()
,@body)))
; -------------------------------------------------------------
(defgeneric do-with-plotview-coords (port plt fn)
(:method ((port plotter-pane) plt fn)
;;
;; Inside here we should work, with unscaled screen-level coords
;; for, the plotview - a viewport that assumes origin at top-left
;; corner, with positive measure in x pixels to the right, and y
;; pixels toward the bottom.
;;
;; The transform wrapped around the body will translate into
;; absolute scaled coords for the parent port.
;;
;; NOTE: LW states that they use transform pre-mult, which I take to
;; mean: mult from left (but their language is ambiguous). And they
;; demonstrate that transform is used by multiplying a row vector on
;; the left of the transform matrix.
;;
;; But after experimenting above, the net effect is to have the
;; transforms apply in order from innermost to outermost nesting
;; order, i.e., the first one (innermost) applied happens first to
;; coords.
;;
;; !! THIS IS OPPOSITE OF THE EFFECT OF SUCCESSIVE TRANFORMATIONS
;; WHEN CONSTRUCTING AN OVERT COMPOSITE TRANSFORM MATRIX.
;;
;; (0,0) --> plus x
;; | +------------------------------------------+
;; | | Parent frame |
;; v | +----------------------------------+ |
;; | | Plotting region | |
;; p | | | |
;; l | | | |
;; u | | | |
;; s | | | |
;; | | | |
;; y | | | |
;; | | | |
;; | +----------------------------------+ |
;; | |
;; +------------------------------------------+
;;
;; This code transforms from pixel space inside the plotting region
;; to pixel space coords inside the parent frame.
;;
;; NB: Regarding PLOTTER-BOX. It is mutated along the way to
;; indicate the size of the plotting region in unscaled pixel
;; coords.
;;
;; Only the right and bottom are modified according to aspect ratio.
;; The top-left is adjusted to always be the location of
;; the plotting region within the parent graphport, before any
;; scaling has been applied. The true location of the parent
;; graphport is indicated by (LEFT - +LEFT-INSET+) and (TOP -
;; +TOP-INSET+) prior to any scaling.
;;
;; Masks, for clipping regions, appear to be unaffected by graphic
;; transforms, and so must be explicitly computed in parent frame
;; coordinates.
;;
(with-accessors ((plot-state plotter-plotting-gs)) port
(with-new-graphics-state (port plot-state)
(funcall fn))
)))
(defmacro with-plotview-coords ((port plt) &body body)
`(do-with-plotview-coords ,port ,plt (lambda ()
,@body)))
;; ---------------------------------------------------
;; We don't have a PORT until we get called during REDRAW callback
;; time. So we can't precompute this information ahead of time, to
;; save cycles in the main CAPI thread. But we can compute just once
;; during the REDRAW and save on repeated needs.
;;
(defun compute-graphics-state (pane)
;; Precompute and cache state for the necessary plotting transforms
;; for use inside the PlotView region.
(with-accessors ((sf plotter-sf)
(box plotter-box)) pane
(with-saved-graphics-state pane
(gp:with-graphics-state (pane
;; :transform (gp:make-transform)
:scale-thickness t)
(gp:with-graphics-scale (pane sf sf)
(gp:with-graphics-translation (pane (box-left box) (box-top box))
(get-graphics-state pane)
)))
)))
;; ------------------------------------------------------------
;; To be used at the top of the REDRAW callback
(defun do-with-drawing-graphics-state (pane fn)
(with-accessors ((init-state plotter-initial-gs)
(plot-state plotter-plotting-gs)) pane
(unless init-state
;; what else can we do? just grab first seen instance as our
;; initial state
(setf init-state (get-graphics-state pane)))
(with-new-graphics-state (pane init-state)
;; we have to recompute because the port might have changed size
;; and scaling
(setf plot-state (compute-graphics-state pane))
(funcall fn))
))
(defun recompute-plotting-state (pane)
(do-with-drawing-graphics-state pane #'lw:do-nothing))
(defmacro with-drawing-graphics-state ((pane) &body body)
`(do-with-drawing-graphics-state ,pane (lambda ()
,@body)))
;; ---------------------------------------------------------
#| ... Unused...
(defun do-with-data-coords (pane port fn)
;; Inside here we should work with data coords (xmin,xmax) and
;; (ymin,ymax)
;;
;; This code converts from data coords to pixel space coords inside
;; the plotting region.
(with-accessors ((xmin plotter-xmin)
(xmax plotter-xmax)
(ymin plotter-ymin)
(ymax plotter-ymax)
(box plotter-box)) pane
(let* ((dx (- xmax xmin))
(dy (- ymin ymax))
(xscale (/ (box-width box) dx))
(yscale (/ (box-height box) dy)))
(gp:with-graphics-scale (port xscale yscale)
(gp:with-graphics-translation (port (- xmin) (- ymax))
(funcall fn)
)))))
(defmacro with-data-coords ((pane port) &body body)
`(do-with-data-coords ,pane ,port (lambda ()
,@body)))
|#
#|
;; This fails the assertion... which means that each dummy port has
;; its own graphics state.
;;
;; That's good news for parallel code handing different portions of a
;; preview computation in prep for rapid playback on the main CAPI
;; thread.
;;
;; So even though the graphics state in a port cannot be safely shared
;; - a mutable struct of mutable elements - we can safely spin off
;; separate dummy ports for each computation thread, and they won't
;; step on each other.
;;
;; Still... anything that depends on transform scaling can't be
;; precomputed ahead of the REDRAW callback, since we only find out at
;; that time just what the transform scaling needs to be.
;;
;; And that comprises all of the computing, save for initial scan of
;; NaN's and Infinities, and finding out the data coord extrema
;; values. But that is already being performed ahead of the CAPI
;; thread.
;;
;; So, no... we really can't invoke parallel precomputation in any
;; meaningful way.
;;
;; ...unless... If we can sense whether or not the port frame size has
;; changed, or the frame has moved, then for unchanged plotting we
;; could precompute everything and use over and over again, except for
;; fresh data.
;;
(let* ((screen (capi:convert-to-screen))
(p1 (capi:create-dummy-graphics-port screen))
(p2 (capi:create-dummy-graphics-port screen)))
(assert (eq (gp:port-graphics-state p1)
(gp:port-graphics-state p2))))
|#