Source file Shape_render_software.ml
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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
let rgb_of_color (color : Color.t) : int =
match color with
| Rgb (r, g, b) -> (r lsl 16) lor (g lsl 8) lor b
| Hex s when String.length s = 7 && s.[0] = '#' -> int_of_string ("0x" ^ String.sub s 1 6)
| Hex s -> failwith (Printf.sprintf "wrong color format: %s" s)
let image_placeholder_rgb = 0xc0c0c0
let screen_transform (fb : Framebuffer.t) : Affine.t =
Affine.compose
(Affine.translate (float fb.width /. 2.) (float fb.height /. 2.))
(Affine.scale 1. (-1.))
let shape_transform (shape : Playground.shape) : Affine.t =
let radians = shape.angle *. Float.pi /. 180. in
Affine.compose
(Affine.translate shape.x shape.y)
(Affine.compose (Affine.rotate radians) (Affine.scale shape.scale shape.scale))
let rectangle_corners w h =
let x = w /. 2. and y = h /. 2. in
[ (-.x, y); (x, y); (x, -.y); (-.x, -.y) ]
let ngon_corners n r =
List.init n (fun i ->
let degrees = 90. -. (360. *. float i /. float n) in
let radians = degrees *. Float.pi /. 180. in
(r *. cos radians, r *. sin radians))
let circle_in_pixels (m : Affine.t) (r : float) : ((float * float) * float) option =
let scale_x = Float.hypot m.a m.b and scale_y = Float.hypot m.c m.d in
let perpendicular = Float.abs ((m.a *. m.c) +. (m.b *. m.d)) < 1e-9 *. scale_x *. scale_y in
if perpendicular && Float.abs (scale_x -. scale_y) < 1e-9 *. scale_x then
Some (Affine.apply m (0., 0.), r *. scale_x)
else None
let ellipse_polygon (m : Affine.t) ~rx ~ry : (float * float) list =
let scale = Float.max (Float.hypot m.a m.b) (Float.hypot m.c m.d) in
let segments = Circle.segments_for_radius (Float.max rx ry *. scale) in
List.map (Affine.apply m) (Circle.ellipse_points ~rx ~ry ~segments)
let local_bounds (form : Playground.form) : (float * float * float * float) option =
let centered w h = Some (-.w /. 2., -.h /. 2., w /. 2., h /. 2.) in
match form with
| Circle (_, r) | Ngon (_, _, r) -> centered (2. *. r) (2. *. r)
| Oval (_, w, h) | Rectangle (_, w, h) | Image (w, h, _) | Bitmap (w, h, _) -> centered w h
| Polygon (_, []) -> None
| Polygon (_, points) ->
let xs = List.map fst points and ys = List.map snd points in
let min_of = List.fold_left min infinity and max_of = List.fold_left max neg_infinity in
Some (min_of xs, min_of ys, max_of xs, max_of ys)
| Words (_, str) ->
let size = Playground.words_font_size in
let _strokes, width = Hershey.layout str in
centered (width *. size /. Hershey.units_per_em) size
| Group _ -> None
let box_polygon (m : Affine.t) (xmin, ymin, xmax, ymax) : (float * float) list =
let corners =
List.map (Affine.apply m) [ (xmin, ymin); (xmax, ymin); (xmax, ymax); (xmin, ymax) ]
in
let xs = List.map fst corners and ys = List.map snd corners in
let x0 = List.fold_left min infinity xs and x1 = List.fold_left max neg_infinity xs in
let y0 = List.fold_left min infinity ys and y1 = List.fold_left max neg_infinity ys in
[ (x0, y0); (x1, y0); (x1, y1); (x0, y1) ]
let blit_image (img : Image_decode.image) : Blit.image =
{ width = img.width; height = img.height; rgba = img.rgba }
let image_to_local ~w ~h (image : Blit.image) : Affine.t =
Affine.compose
(Affine.translate (-.w /. 2.) (h /. 2.))
(Affine.scale (w /. float image.width) (-.h /. float image.height))
let pen_width = Hershey.units_per_em /. 12.
let text_to_local ~width : Affine.t =
let s = Playground.words_font_size /. Hershey.units_per_em in
Affine.compose (Affine.scale s (-.s)) (Affine.translate (-.width /. 2.) 0.)
let length_scale (m : Affine.t) : float = sqrt (Float.abs ((m.a *. m.d) -. (m.b *. m.c)))
type options = {
alpha_blending : bool;
bounding_boxes : bool;
wireframe : bool;
bilinear : bool;
antialiasing : bool;
}
let default_options =
{ alpha_blending = true; bounding_boxes = false; wireframe = false; bilinear = true; antialiasing = true }
let effective_alpha (options : options) (alpha : float) : float =
if options.alpha_blending then alpha else if alpha > 0. then 1. else 0.
let fill_polygon ~aa fb points ~rgb ~alpha =
if aa then Fill.polygons_aa fb [ points ] ~rgb ~alpha else Fill.polygon fb points ~rgb ~alpha
let line ~aa = if aa then Line.draw_aa else Line.draw
let outline_polygon ~aa fb points ~rgb ~alpha =
match points with
| [] -> ()
| first :: _ ->
let rec loop = function
| p :: (q :: _ as rest) ->
line ~aa fb p q ~rgb ~alpha;
loop rest
| [ last ] -> line ~aa fb last first ~rgb ~alpha
| [] -> ()
in
loop points
let circle_polygon ((cx, cy), r) =
Circle.ellipse_points ~rx:r ~ry:r ~segments:(Circle.segments_for_radius r)
|> List.map (fun (x, y) -> (cx +. x, cy +. y))
let fill_circle ~aa fb (((cx, cy), r) as circle) ~rgb ~alpha =
if aa then Fill.polygons_aa fb [ circle_polygon circle ] ~rgb ~alpha
else
let pixel v = int_of_float (Float.floor v) in
Circle.fill fb ~cx:(pixel cx) ~cy:(pixel cy) ~r:(int_of_float (Float.round r)) ~rgb ~alpha
let outline_circle ~aa fb (((cx, cy), r) as circle) ~rgb ~alpha =
if aa then outline_polygon ~aa fb (circle_polygon circle) ~rgb ~alpha
else
let pixel v = int_of_float (Float.floor v) in
Circle.outline fb ~cx:(pixel cx) ~cy:(pixel cy) ~r:(int_of_float (Float.round r)) ~rgb ~alpha
let draw_pixels options fb m ~w ~h (img : Image_decode.image) ~alpha =
let image = blit_image img in
let filter = if options.bilinear then Blit.Bilinear else Blit.Nearest in
Blit.draw fb image (Affine.compose m (image_to_local ~w ~h image)) ~filter ~alpha
let draw_image options fb m ~w ~h src ~alpha =
match Image_decode.image_of_url_at ~time:(Unix.gettimeofday ()) src with
| None -> ()
| Some img -> draw_pixels options fb m ~w ~h img ~alpha
let thin_polyline ~aa fb points ~rgb ~alpha =
let rec loop = function
| p :: (q :: _ as rest) ->
line ~aa fb p q ~rgb ~alpha;
loop rest
| [ _ ] | [] -> ()
in
loop points
let draw_words options fb m str ~rgb ~alpha =
let strokes, width = Hershey.layout str in
let m = Affine.compose m (text_to_local ~width) in
let lines = List.map (List.map (Affine.apply m)) strokes in
let pen = pen_width *. length_scale m in
let aa = options.antialiasing in
if options.wireframe || pen < 1.5 then List.iter (fun l -> thin_polyline ~aa fb l ~rgb ~alpha) lines
else if aa then Fill.polygons_aa fb (Stroke.contours lines ~width:pen) ~rgb ~alpha
else Stroke.polylines fb lines ~width:pen ~rgb ~alpha
let form_rgb (form : Playground.form) : int =
match form with
| Circle (color, _)
| Oval (color, _, _)
| Rectangle (color, _, _)
| Ngon (color, _, _)
| Polygon (color, _)
| Words (color, _) ->
rgb_of_color color
| Image _ | Bitmap _ | Group _ -> image_placeholder_rgb
let render_form (options : options) (fb : Framebuffer.t) (m : Affine.t) (form : Playground.form) ~rgb ~alpha =
let aa = options.antialiasing in
let draw_polygon = if options.wireframe then outline_polygon ~aa else fill_polygon ~aa in
let draw_circle = if options.wireframe then outline_circle ~aa else fill_circle ~aa in
let polygon local_corners = draw_polygon fb (List.map (Affine.apply m) local_corners) ~rgb ~alpha in
let box () = Option.iter (fun b -> draw_polygon fb (box_polygon m b) ~rgb ~alpha) (local_bounds form) in
if options.bounding_boxes then box ()
else
match form with
| Rectangle (_, w, h) -> polygon (rectangle_corners w h)
| Polygon (_, points) -> polygon points
| Ngon (_, n, r) -> polygon (ngon_corners n r)
| Circle (_, r) -> (
match circle_in_pixels m r with
| Some circle -> draw_circle fb circle ~rgb ~alpha
| None -> draw_polygon fb (ellipse_polygon m ~rx:r ~ry:r) ~rgb ~alpha)
| Oval (_, w, h) -> draw_polygon fb (ellipse_polygon m ~rx:(w /. 2.) ~ry:(h /. 2.)) ~rgb ~alpha
| Image (w, h, _) when options.wireframe -> polygon (rectangle_corners w h)
| Image (w, h, src) -> draw_image options fb m ~w ~h src ~alpha
| Bitmap (w, h, _) when options.wireframe -> polygon (rectangle_corners w h)
| Bitmap (w, h, img) -> draw_pixels options fb m ~w ~h img ~alpha
| Words (_, str) -> draw_words options fb m str ~rgb ~alpha
| Group _ -> ()
let rec render_shape (options : options) (fb : Framebuffer.t) (m : Affine.t) (shape : Playground.shape) : unit =
let m = Affine.compose m (shape_transform shape) in
match shape.form with
| Group shapes ->
List.iter (render_shape options fb m) shapes
| form ->
render_form options fb m form ~rgb:(form_rgb form) ~alpha:(effective_alpha options shape.alpha)
let render ?(options = default_options) ?(scale = 1.) (fb : Framebuffer.t) (shapes : Playground.shape list) : unit =
let m = if scale = 1. then screen_transform fb else Affine.compose (screen_transform fb) (Affine.scale scale scale) in
List.iter (render_shape options fb m) shapes
let render_region ?(options = default_options) ~window:((width, height) : int * int) ~origin:((x0, y0) : int * int)
(fb : Framebuffer.t) (shapes : Playground.shape list) : unit =
let m =
Affine.compose
(Affine.translate ((float width /. 2.) -. float x0) ((float height /. 2.) -. float y0))
(Affine.scale 1. (-1.))
in
List.iter (render_shape options fb m) shapes
let pixel_bounds ~(width : int) ~(height : int) (shapes : Playground.shape list) : (int * int * int * int) option =
let screen = Affine.compose (Affine.translate (float width /. 2.) (float height /. 2.)) (Affine.scale 1. (-1.)) in
let boxes = ref [] in
let add (points : (float * float) list) (margin : float) =
let xs = List.map fst points and ys = List.map snd points in
boxes :=
( List.fold_left min infinity xs -. margin, List.fold_left min infinity ys -. margin,
List.fold_left max neg_infinity xs +. margin, List.fold_left max neg_infinity ys +. margin )
:: !boxes
in
let rec walk (m : Affine.t) (shape : Playground.shape) =
let m = Affine.compose m (shape_transform shape) in
match shape.form with
| Group shapes -> List.iter (walk m) shapes
| Words (_, str) ->
let strokes, width = Hershey.layout str in
let m = Affine.compose m (text_to_local ~width) in
let points = List.concat_map (List.map (Affine.apply m)) strokes in
if points <> [] then add points ((pen_width *. length_scale m) +. 2.)
| form -> Option.iter (fun b -> add (box_polygon m b) 2.) (local_bounds form)
in
List.iter (walk screen) shapes;
match !boxes with
| [] -> None
| boxes ->
let x0, y0, x1, y1 =
List.fold_left
(fun (a, b, c, d) (x0, y0, x1, y1) -> (Float.min a x0, Float.min b y0, Float.max c x1, Float.max d y1))
(infinity, infinity, neg_infinity, neg_infinity) boxes
in
let clamp lo hi v = Int.max lo (Int.min hi v) in
let x0 = clamp 0 width (int_of_float (floor x0)) and x1 = clamp 0 width (int_of_float (ceil x1)) in
let y0 = clamp 0 height (int_of_float (floor y0)) and y1 = clamp 0 height (int_of_float (ceil y1)) in
if x1 <= x0 || y1 <= y0 then None else Some (x0, y0, x1, y1)