From 923ddc038a0cb10834f8cb3d0305ec104a8c2b36 Mon Sep 17 00:00:00 2001 From: Fay Carsons Date: Thu, 14 Dec 2023 12:54:20 -0500 Subject: [PATCH 1/9] basic cairo prototype --- dune-project | 2 +- examples/cairo.ml | 35 +++++++++++++++++++++++++++++++++++ examples/dune | 5 +++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 examples/cairo.ml diff --git a/dune-project b/dune-project index 0e71b62..e0b7929 100644 --- a/dune-project +++ b/dune-project @@ -17,6 +17,6 @@ (name joy) (synopsis "Tiny creative coding library in OCaml") (description "Tiny creative coding library in OCaml") - (depends ocaml dune graphics)) + (depends ocaml dune graphics cairo2)) ; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project diff --git a/examples/cairo.ml b/examples/cairo.ml new file mode 100644 index 0000000..bbfd3f7 --- /dev/null +++ b/examples/cairo.ml @@ -0,0 +1,35 @@ +open Cairo + +type cairo_context = { + ctx: context; + surface: Surface.t +} + +let context : cairo_context option ref = ref None + +let draw () = + match !context with + | Some {ctx; surface} -> + Cairo.set_line_width ctx 0.01; + Cairo.set_source_rgb ctx 0. 0. 0.; + Cairo.rectangle ctx 0.25 0.25 ~w: 0.5 ~h: 0.5; + Cairo.stroke ctx; + + Cairo.set_source_rgb ctx 0. 0. 0.; + Cairo.arc ctx 0.5 0.5 ~r:0.25 ~a1: 0. ~a2: (2. *. Float.pi); + Cairo.stroke ctx; + Cairo.PNG.write surface "test2.png" + | None -> failwith "Context not found" + +let init ?size () = + let w, h = match size with | Some s -> s | None -> (800., 800.) in + let surface = Cairo.Image.create Cairo.Image.ARGB32 ~w: (int_of_float w) ~h: (int_of_float h) in + let ctx = (Cairo.create surface) in + Cairo.scale ctx w h; + Cairo.set_source_rgb ctx 1. 1. 1.; + Cairo.paint ctx; + context := Some {ctx; surface}; + draw () + +let () = + init () \ No newline at end of file diff --git a/examples/dune b/examples/dune index f8d4627..3087c6c 100644 --- a/examples/dune +++ b/examples/dune @@ -107,3 +107,8 @@ (name circle_packing) (modules circle_packing) (libraries joy)) + +(executable + (name cairo) + (modules cairo) + (libraries cairo2)) \ No newline at end of file From 880bbe3283035702473c262d92f469c29c3d3877 Mon Sep 17 00:00:00 2001 From: Fay Carsons Date: Mon, 18 Dec 2023 14:14:43 -0500 Subject: [PATCH 2/9] Prototype functioning for all shapes but ellipse --- examples/cairo.ml | 210 +++++++++++++++++++++++++++++++++++++++------- examples/dune | 4 +- joy.opam | 1 + 3 files changed, 185 insertions(+), 30 deletions(-) diff --git a/examples/cairo.ml b/examples/cairo.ml index bbfd3f7..de5bdb1 100644 --- a/examples/cairo.ml +++ b/examples/cairo.ml @@ -1,35 +1,189 @@ -open Cairo +module Cairo = Cairo +let tau = 2. *. Float.pi + +type point = { x : float; y : float } +type circle = { c : point; radius : float } +type rectangle = { c : point; width : float; height : float } +type line = { a : point; b : point } +type polygon = point list + +type shape = + | Circle of circle + | Rectangle of rectangle + | Line of line + | Polygon of polygon + | Complex of shape list + +(* Point arithmetic operators + + let ( +~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 +. x2; y = y1 +. y2} + let ( *~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 *. x2; y = y1 *. y2} + let ( /~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 /. x2; y = y1 /. y2} + let ( -~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 -. x2; y = y1 -. y2} + + let ( +! ) {x = x1; y = y1} scalar = {x = x1 +. scalar; y = y1 +. scalar} + let ( *! ) {x = x1; y = y1} scalar = {x = x1 *. scalar; y = y1 *. scalar} + let ( /! ) {x = x1; y = y1} scalar = {x = x1 /. scalar; y = y1 /. scalar} *) +let ( -! ) { x = x1; y = y1 } scalar = { x = x1 -. scalar; y = y1 -. scalar } + +(* Global rendering context singleton definition and instantiation *) type cairo_context = { - ctx: context; - surface: Surface.t + ctx : Cairo.context; + surface : Cairo.Surface.t; + size : point; + filename : string; } -let context : cairo_context option ref = ref None - -let draw () = - match !context with - | Some {ctx; surface} -> - Cairo.set_line_width ctx 0.01; - Cairo.set_source_rgb ctx 0. 0. 0.; - Cairo.rectangle ctx 0.25 0.25 ~w: 0.5 ~h: 0.5; - Cairo.stroke ctx; - - Cairo.set_source_rgb ctx 0. 0. 0.; - Cairo.arc ctx 0.5 0.5 ~r:0.25 ~a1: 0. ~a2: (2. *. Float.pi); - Cairo.stroke ctx; - Cairo.PNG.write surface "test2.png" - | None -> failwith "Context not found" - -let init ?size () = - let w, h = match size with | Some s -> s | None -> (800., 800.) in - let surface = Cairo.Image.create Cairo.Image.ARGB32 ~w: (int_of_float w) ~h: (int_of_float h) in - let ctx = (Cairo.create surface) in +let context = ref None + +(* Error message *) +let fail = "Context not initialized" + + +(* Context initialization, render, and update fns *) +let init_context ?line_width (w, h) filename = + let surface = + Cairo.Image.create Cairo.Image.ARGB32 ~w: (int_of_float w) + ~h: (int_of_float h) + in + let ctx = Cairo.create surface in Cairo.scale ctx w h; - Cairo.set_source_rgb ctx 1. 1. 1.; - Cairo.paint ctx; - context := Some {ctx; surface}; + Cairo.set_line_width ctx (match line_width with | Some n -> n | None -> 0.002); + context := Some { ctx; surface; size = {x = w; y = h}; filename } + +(* Renders context to PNG *) +let write ctx = Cairo.PNG.write ctx.surface ctx.filename + +let get_dimensions () = + match !context with Some ctx -> ctx.size | None -> failwith fail + +let set_color color = + match !context with + | Some ctx -> + let r, g, b = color in + Cairo.set_source_rgb ctx.ctx r g b + | None -> failwith fail + +let background color = + match !context with + | Some ctx -> + let r, g, b = color in + Cairo.set_source_rgb ctx.ctx r g b; + Cairo.paint ctx.ctx + | None -> failwith fail + +let scale_point size point = + let { x; y } = point in + let x, y = (x /. size.x, y /. size.y) in + List.iter (fun n -> print_float n |> print_newline) [ x; y ]; + (x, y) + +let draw_circle ctx (circle : circle) = + let x, y = scale_point ctx.size circle.c in + let radius = circle.radius /. min ctx.size.x ctx.size.y in + Cairo.arc ctx.ctx x y ~r:radius ~a1:0. ~a2:tau; + Cairo.stroke ctx.ctx + +let draw_rect ctx (rect : rectangle) = + let x, y = + scale_point ctx.size (rect.c -! ((rect.width +. rect.height) /. 4.)) + in + let w = rect.width /. ctx.size.x in + let h = rect.height /. ctx.size.y in + Cairo.rectangle ctx.ctx x y ~w ~h; + Cairo.stroke ctx.ctx + +let draw_line ctx line = + let x1, y1 = scale_point ctx.size line.a in + let x2, y2 = scale_point ctx.size line.b in + Cairo.move_to ctx.ctx x1 y1; + Cairo.line_to ctx.ctx x2 y2; + Cairo.stroke ctx.ctx; + Cairo.move_to ctx.ctx 0. 0. + +(* Polygon helper fns and rendering fn *) +let rec take n lst = + match (n, lst) with + | 0, _ -> ([], lst) + | _, [] -> ([], []) + | n, x :: xs -> + let taken, rest = take (n - 1) xs in + (x :: taken, rest) + +let rec partition n ?step lst = + match lst with + | [] -> [] + | _ -> + let taken, _ = take n lst in + if List.length taken = n then + taken + :: + (match step with + | Some s -> partition n ~step:s (List.tl lst) + | None -> partition n ~step:0 (List.tl lst)) + else [] + +let draw_polygon ctx (polygon : polygon) = + let points = partition 2 ~step:1 (polygon @ [ List.hd polygon ]) in + List.iter + (fun pair -> + let pair = List.map (scale_point ctx.size) pair in + let (x1, y1), (x2, y2) = (List.nth pair 0, List.nth pair 1) in + Cairo.move_to ctx.ctx x1 y1; + Cairo.line_to ctx.ctx x2 y2) + points; + Cairo.move_to ctx.ctx 0. 0.; + Cairo.stroke ctx.ctx + +(* Root render fn *) +let rec render_shape ctx shape = + let _ = + match shape with + | Circle circle -> draw_circle ctx circle + | Rectangle rectangle -> draw_rect ctx rectangle + | Line line -> draw_line ctx line + | Polygon polygon -> draw_polygon ctx polygon + | Complex complex -> List.iter (render_shape ctx) complex + in + write ctx + +(* Validates context before rendering *) +let render shape = + match !context with + | Some ctx -> render_shape ctx shape + | None -> failwith fail + +(* 'sketch' functions - this is a prototype of what the user would be writing *) +let draw () = + let { x = w; y = h } = get_dimensions () in + let c = { x = w /. 2.; y = h /. 2. } in + let circle = Circle { c; radius = 100. } in + let rect = Rectangle { c; width = w /. 4.; height = h /. 4. } in + let _cartesian_product l l' = + List.concat (List.map (fun e -> List.map (fun e' -> (e, e')) l') l) + in + let polygon = + Polygon (List.map (fun {x; y} -> {x = x +. 10.; y = y +. 10.})[ c; { x = c.x; y = c.y +. 100. }; { x = c.x +. 100.; y = c.y } ]) + in + + let axes = + Complex + [ + Line { a = { x = w /. 2.; y = 0. }; b = { x = w /. 2.; y = h } }; + Line { a = { x = 0.; y = h /. 2. }; b = { x = w; y = h /. 2. } }; + ] + in + let complex = Complex [ circle; rect; polygon; axes ] in + render complex + +let init ?size ?filename () = + let size = match size with Some s -> s | None -> { x = 800.; y = 800. } in + let { x; y } = size in + let filename = match filename with Some s -> s | None -> "cairo.png" in + init_context (x, y) filename; + background (1., 1., 1.); + set_color (0., 0., 0.); draw () -let () = - init () \ No newline at end of file +let () = init () diff --git a/examples/dune b/examples/dune index 3087c6c..2434364 100644 --- a/examples/dune +++ b/examples/dune @@ -108,7 +108,7 @@ (modules circle_packing) (libraries joy)) -(executable +(executable (name cairo) (modules cairo) - (libraries cairo2)) \ No newline at end of file + (libraries cairo2)) diff --git a/joy.opam b/joy.opam index 6b4f63f..c5bfbd8 100644 --- a/joy.opam +++ b/joy.opam @@ -11,6 +11,7 @@ depends: [ "ocaml" "dune" {>= "3.10"} "graphics" + "cairo2" "odoc" {with-doc} ] build: [ From 4f7e93e51d910f875249175645611618340d24d8 Mon Sep 17 00:00:00 2001 From: Fay Carsons Date: Mon, 18 Dec 2023 14:30:10 -0500 Subject: [PATCH 3/9] dune fmt --- examples/cairo.ml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/cairo.ml b/examples/cairo.ml index de5bdb1..01bfda8 100644 --- a/examples/cairo.ml +++ b/examples/cairo.ml @@ -40,17 +40,16 @@ let context = ref None (* Error message *) let fail = "Context not initialized" - (* Context initialization, render, and update fns *) -let init_context ?line_width (w, h) filename = +let init_context ?line_width (w, h) filename = let surface = - Cairo.Image.create Cairo.Image.ARGB32 ~w: (int_of_float w) - ~h: (int_of_float h) + Cairo.Image.create Cairo.Image.ARGB32 ~w:(int_of_float w) + ~h:(int_of_float h) in let ctx = Cairo.create surface in Cairo.scale ctx w h; - Cairo.set_line_width ctx (match line_width with | Some n -> n | None -> 0.002); - context := Some { ctx; surface; size = {x = w; y = h}; filename } + Cairo.set_line_width ctx (match line_width with Some n -> n | None -> 0.002); + context := Some { ctx; surface; size = { x = w; y = h }; filename } (* Renders context to PNG *) let write ctx = Cairo.PNG.write ctx.surface ctx.filename @@ -164,7 +163,10 @@ let draw () = List.concat (List.map (fun e -> List.map (fun e' -> (e, e')) l') l) in let polygon = - Polygon (List.map (fun {x; y} -> {x = x +. 10.; y = y +. 10.})[ c; { x = c.x; y = c.y +. 100. }; { x = c.x +. 100.; y = c.y } ]) + Polygon + (List.map + (fun { x; y } -> { x = x +. 10.; y = y +. 10. }) + [ c; { x = c.x; y = c.y +. 100. }; { x = c.x +. 100.; y = c.y } ]) in let axes = From 7787c8410e2e0dcf0a2f91c08fa62446ab340cd7 Mon Sep 17 00:00:00 2001 From: Fay Carsons Date: Mon, 18 Dec 2023 14:33:11 -0500 Subject: [PATCH 4/9] cleanup and adding explanatory comments --- examples/cairo.ml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/cairo.ml b/examples/cairo.ml index 01bfda8..339cb3d 100644 --- a/examples/cairo.ml +++ b/examples/cairo.ml @@ -60,8 +60,8 @@ let get_dimensions () = let set_color color = match !context with | Some ctx -> - let r, g, b = color in - Cairo.set_source_rgb ctx.ctx r g b + let r, g, b, a = color in + Cairo.set_source_rgba ctx.ctx r g b a | None -> failwith fail let background color = @@ -72,12 +72,14 @@ let background color = Cairo.paint ctx.ctx | None -> failwith fail +(* Scales points from 0 - image size to 0-1 *) let scale_point size point = let { x; y } = point in let x, y = (x /. size.x, y /. size.y) in List.iter (fun n -> print_float n |> print_newline) [ x; y ]; (x, y) +(* Shape rendering fns *) let draw_circle ctx (circle : circle) = let x, y = scale_point ctx.size circle.c in let radius = circle.radius /. min ctx.size.x ctx.size.y in @@ -159,9 +161,6 @@ let draw () = let c = { x = w /. 2.; y = h /. 2. } in let circle = Circle { c; radius = 100. } in let rect = Rectangle { c; width = w /. 4.; height = h /. 4. } in - let _cartesian_product l l' = - List.concat (List.map (fun e -> List.map (fun e' -> (e, e')) l') l) - in let polygon = Polygon (List.map @@ -185,7 +184,7 @@ let init ?size ?filename () = let filename = match filename with Some s -> s | None -> "cairo.png" in init_context (x, y) filename; background (1., 1., 1.); - set_color (0., 0., 0.); + set_color (0., 0., 0., 1.); draw () let () = init () From bc7da1bfe7aede41bd31d628c33ea999322ca07b Mon Sep 17 00:00:00 2001 From: Fay Carsons Date: Mon, 18 Dec 2023 15:06:10 -0500 Subject: [PATCH 5/9] removing debug statement --- examples/cairo.ml | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/cairo.ml b/examples/cairo.ml index 339cb3d..d3e5af8 100644 --- a/examples/cairo.ml +++ b/examples/cairo.ml @@ -76,7 +76,6 @@ let background color = let scale_point size point = let { x; y } = point in let x, y = (x /. size.x, y /. size.y) in - List.iter (fun n -> print_float n |> print_newline) [ x; y ]; (x, y) (* Shape rendering fns *) From a68a8d8e9082b87464d092c6d26a35c0f6871a5d Mon Sep 17 00:00:00 2001 From: Fay Carsons Date: Mon, 18 Dec 2023 18:47:32 -0500 Subject: [PATCH 6/9] added implmentation for ellipses --- examples/cairo.ml | 80 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/examples/cairo.ml b/examples/cairo.ml index d3e5af8..4182fd8 100644 --- a/examples/cairo.ml +++ b/examples/cairo.ml @@ -4,27 +4,38 @@ let tau = 2. *. Float.pi type point = { x : float; y : float } type circle = { c : point; radius : float } +type ellipse = { c : point; rx : float; ry : float } type rectangle = { c : point; width : float; height : float } type line = { a : point; b : point } type polygon = point list type shape = | Circle of circle + | Ellipse of ellipse | Rectangle of rectangle | Line of line | Polygon of polygon | Complex of shape list (* Point arithmetic operators + I think thesse would be useful, + but also undeerstand not everyone likes the arithmetic operator + symbol overload thing let ( +~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 +. x2; y = y1 +. y2} let ( *~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 *. x2; y = y1 *. y2} - let ( /~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 /. x2; y = y1 /. y2} + let ( -~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 -. x2; y = y1 -. y2} let ( +! ) {x = x1; y = y1} scalar = {x = x1 +. scalar; y = y1 +. scalar} let ( *! ) {x = x1; y = y1} scalar = {x = x1 *. scalar; y = y1 *. scalar} let ( /! ) {x = x1; y = y1} scalar = {x = x1 /. scalar; y = y1 /. scalar} *) + +(* point + point arithmetic *) +let ( /~ ) { x = x1; y = y1 } { x = x2; y = y2 } = + { x = x1 /. x2; y = y1 /. y2 } + +(* point + scalar arithmetic *) let ( -! ) { x = x1; y = y1 } scalar = { x = x1 -. scalar; y = y1 -. scalar } (* Global rendering context singleton definition and instantiation *) @@ -79,18 +90,48 @@ let scale_point size point = (x, y) (* Shape rendering fns *) -let draw_circle ctx (circle : circle) = - let x, y = scale_point ctx.size circle.c in - let radius = circle.radius /. min ctx.size.x ctx.size.y in +let draw_circle ctx ({ c; radius } : circle) = + let x, y = scale_point ctx.size c in + let radius = radius /. min ctx.size.x ctx.size.y in Cairo.arc ctx.ctx x y ~r:radius ~a1:0. ~a2:tau; Cairo.stroke ctx.ctx -let draw_rect ctx (rect : rectangle) = - let x, y = - scale_point ctx.size (rect.c -! ((rect.width +. rect.height) /. 4.)) - in - let w = rect.width /. ctx.size.x in - let h = rect.height /. ctx.size.y in +(* Ellipse helper fn & rendering fn *) + +let calculate_control_points (size : point) ({ c; rx; ry } : ellipse) = + let { x; y } = c in + let { x = w; y = h } = size in + let half_height = ry /. 2. in + let width_two_thirds = rx *. (2. /. 3.) in + ( { x; y = y -. half_height } /~ size, + ( (x +. width_two_thirds) /. w, + (y -. half_height) /. h, + (x +. width_two_thirds) /. w, + (y +. half_height) /. h, + x /. w, + (y +. half_height) /. h ), + ( (x -. width_two_thirds) /. w, + (y +. half_height) /. h, + (x -. width_two_thirds) /. w, + (y -. half_height) /. h, + x /. w, + (y -. half_height) /. h ) ) + +let draw_ellipse (ctx : cairo_context) (ellipse : ellipse) = + let start, curve_one, curve_two = calculate_control_points ctx.size ellipse in + Cairo.save ctx.ctx; + Cairo.move_to ctx.ctx start.x start.y; + let x1, y1, x2, y2, x3, y3 = curve_one in + Cairo.curve_to ctx.ctx x1 y1 x2 y2 x3 y3; + let x1, y1, x2, y2, x3, y3 = curve_two in + Cairo.curve_to ctx.ctx x1 y1 x2 y2 x3 y3; + Cairo.stroke ctx.ctx; + Cairo.restore ctx.ctx + +let draw_rect ctx ({ c; width; height } : rectangle) = + let x, y = scale_point ctx.size (c -! ((width +. height) /. 4.)) in + let w = width /. ctx.size.x in + let h = height /. ctx.size.y in Cairo.rectangle ctx.ctx x y ~w ~h; Cairo.stroke ctx.ctx @@ -138,14 +179,13 @@ let draw_polygon ctx (polygon : polygon) = (* Root render fn *) let rec render_shape ctx shape = - let _ = - match shape with - | Circle circle -> draw_circle ctx circle - | Rectangle rectangle -> draw_rect ctx rectangle - | Line line -> draw_line ctx line - | Polygon polygon -> draw_polygon ctx polygon - | Complex complex -> List.iter (render_shape ctx) complex - in + (match shape with + | Circle circle -> draw_circle ctx circle + | Rectangle rectangle -> draw_rect ctx rectangle + | Ellipse ellipse -> draw_ellipse ctx ellipse + | Line line -> draw_line ctx line + | Polygon polygon -> draw_polygon ctx polygon + | Complex complex -> List.iter (render_shape ctx) complex); write ctx (* Validates context before rendering *) @@ -160,13 +200,13 @@ let draw () = let c = { x = w /. 2.; y = h /. 2. } in let circle = Circle { c; radius = 100. } in let rect = Rectangle { c; width = w /. 4.; height = h /. 4. } in + let ellip = Ellipse { c; rx = 75.; ry = 50. } in let polygon = Polygon (List.map (fun { x; y } -> { x = x +. 10.; y = y +. 10. }) [ c; { x = c.x; y = c.y +. 100. }; { x = c.x +. 100.; y = c.y } ]) in - let axes = Complex [ @@ -174,7 +214,7 @@ let draw () = Line { a = { x = 0.; y = h /. 2. }; b = { x = w; y = h /. 2. } }; ] in - let complex = Complex [ circle; rect; polygon; axes ] in + let complex = Complex [ circle; rect; ellip; polygon; axes ] in render complex let init ?size ?filename () = From ec3b82b3a5c13e473cfdeb50bcb3c56ce0777c35 Mon Sep 17 00:00:00 2001 From: Fay Carsons Date: Mon, 18 Dec 2023 22:24:52 -0500 Subject: [PATCH 7/9] ellipse sclaing hacky fix, context singletont enforcement --- examples/cairo.ml | 108 ++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/examples/cairo.ml b/examples/cairo.ml index 4182fd8..5fbd2fb 100644 --- a/examples/cairo.ml +++ b/examples/cairo.ml @@ -18,22 +18,21 @@ type shape = | Complex of shape list (* Point arithmetic operators - I think thesse would be useful, - but also undeerstand not everyone likes the arithmetic operator - symbol overload thing + I think thesse would be useful, + but also undeerstand not everyone likes the arithmetic operator + symbol overload thing - let ( +~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 +. x2; y = y1 +. y2} - let ( *~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 *. x2; y = y1 *. y2} + let ( /~ ) { x = x1; y = y1 } { x = x2; y = y2 } = { x = x1 /. x2; y = y1 /. y2 } + let ( +~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 +. x2; y = y1 +. y2} + let ( *~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 *. x2; y = y1 *. y2} - let ( -~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 -. x2; y = y1 -. y2} + let ( -~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 -. x2; y = y1 -. y2} - let ( +! ) {x = x1; y = y1} scalar = {x = x1 +. scalar; y = y1 +. scalar} - let ( *! ) {x = x1; y = y1} scalar = {x = x1 *. scalar; y = y1 *. scalar} - let ( /! ) {x = x1; y = y1} scalar = {x = x1 /. scalar; y = y1 /. scalar} *) + let ( +! ) {x = x1; y = y1} scalar = {x = x1 +. scalar; y = y1 +. scalar} + let ( *! ) {x = x1; y = y1} scalar = {x = x1 *. scalar; y = y1 *. scalar} + let ( /! ) {x = x1; y = y1} scalar = {x = x1 /. scalar; y = y1 /. scalar} *) (* point + point arithmetic *) -let ( /~ ) { x = x1; y = y1 } { x = x2; y = y2 } = - { x = x1 /. x2; y = y1 /. y2 } (* point + scalar arithmetic *) let ( -! ) { x = x1; y = y1 } scalar = { x = x1 -. scalar; y = y1 -. scalar } @@ -47,27 +46,31 @@ type cairo_context = { } let context = ref None - -(* Error message *) let fail = "Context not initialized" (* Context initialization, render, and update fns *) -let init_context ?line_width (w, h) filename = +let init_context ?line_width (x, y) filename = + (* Fail if context has already been instantiated *) + if Option.is_some !context then + failwith "Context cannot be initialized twice!"; + let surface = - Cairo.Image.create Cairo.Image.ARGB32 ~w:(int_of_float w) - ~h:(int_of_float h) + Cairo.Image.create Cairo.Image.ARGB32 ~w:(int_of_float x) + ~h:(int_of_float y) in let ctx = Cairo.create surface in - Cairo.scale ctx w h; + Cairo.scale ctx x y; Cairo.set_line_width ctx (match line_width with Some n -> n | None -> 0.002); - context := Some { ctx; surface; size = { x = w; y = h }; filename } + context := Some { ctx; surface; size = { x; y }; filename } (* Renders context to PNG *) let write ctx = Cairo.PNG.write ctx.surface ctx.filename +(* gets surface size in range 0..pixels *) let get_dimensions () = match !context with Some ctx -> ctx.size | None -> failwith fail +(* sets global color *) let set_color color = match !context with | Some ctx -> @@ -75,6 +78,7 @@ let set_color color = Cairo.set_source_rgba ctx.ctx r g b a | None -> failwith fail +(* sets background color *) let background color = match !context with | Some ctx -> @@ -83,7 +87,7 @@ let background color = Cairo.paint ctx.ctx | None -> failwith fail -(* Scales points from 0 - image size to 0-1 *) +(* Scales points from 0-image size to 0-1 on both axes *) let scale_point size point = let { x; y } = point in let x, y = (x /. size.x, y /. size.y) in @@ -96,38 +100,6 @@ let draw_circle ctx ({ c; radius } : circle) = Cairo.arc ctx.ctx x y ~r:radius ~a1:0. ~a2:tau; Cairo.stroke ctx.ctx -(* Ellipse helper fn & rendering fn *) - -let calculate_control_points (size : point) ({ c; rx; ry } : ellipse) = - let { x; y } = c in - let { x = w; y = h } = size in - let half_height = ry /. 2. in - let width_two_thirds = rx *. (2. /. 3.) in - ( { x; y = y -. half_height } /~ size, - ( (x +. width_two_thirds) /. w, - (y -. half_height) /. h, - (x +. width_two_thirds) /. w, - (y +. half_height) /. h, - x /. w, - (y +. half_height) /. h ), - ( (x -. width_two_thirds) /. w, - (y +. half_height) /. h, - (x -. width_two_thirds) /. w, - (y -. half_height) /. h, - x /. w, - (y -. half_height) /. h ) ) - -let draw_ellipse (ctx : cairo_context) (ellipse : ellipse) = - let start, curve_one, curve_two = calculate_control_points ctx.size ellipse in - Cairo.save ctx.ctx; - Cairo.move_to ctx.ctx start.x start.y; - let x1, y1, x2, y2, x3, y3 = curve_one in - Cairo.curve_to ctx.ctx x1 y1 x2 y2 x3 y3; - let x1, y1, x2, y2, x3, y3 = curve_two in - Cairo.curve_to ctx.ctx x1 y1 x2 y2 x3 y3; - Cairo.stroke ctx.ctx; - Cairo.restore ctx.ctx - let draw_rect ctx ({ c; width; height } : rectangle) = let x, y = scale_point ctx.size (c -! ((width +. height) /. 4.)) in let w = width /. ctx.size.x in @@ -143,6 +115,38 @@ let draw_line ctx line = Cairo.stroke ctx.ctx; Cairo.move_to ctx.ctx 0. 0. +(* Ellipse helper fn & rendering fn + currently just multiplying radii by 2 to offset scaling issue + feels hacky *) +let calculate_control_points (size : point) ({ c; rx; ry } : ellipse) = + let x, y = scale_point size c in + let half_height = ry /. size.y in + let width_two_thirds = rx /. size.x *. (2. /. 3.) *. 2. in + ( { x; y = y -. half_height }, + ( x +. width_two_thirds, + y -. half_height, + x +. width_two_thirds, + y +. half_height, + x, + y +. half_height ), + ( x -. width_two_thirds, + y +. half_height, + x -. width_two_thirds, + y -. half_height, + x, + y -. half_height ) ) + +let draw_ellipse (ctx : cairo_context) (ellipse : ellipse) = + let start, curve_one, curve_two = calculate_control_points ctx.size ellipse in + Cairo.save ctx.ctx; + Cairo.move_to ctx.ctx start.x start.y; + let x1, y1, x2, y2, x3, y3 = curve_one in + Cairo.curve_to ctx.ctx x1 y1 x2 y2 x3 y3; + let x1, y1, x2, y2, x3, y3 = curve_two in + Cairo.curve_to ctx.ctx x1 y1 x2 y2 x3 y3; + Cairo.stroke ctx.ctx; + Cairo.restore ctx.ctx + (* Polygon helper fns and rendering fn *) let rec take n lst = match (n, lst) with @@ -200,7 +204,7 @@ let draw () = let c = { x = w /. 2.; y = h /. 2. } in let circle = Circle { c; radius = 100. } in let rect = Rectangle { c; width = w /. 4.; height = h /. 4. } in - let ellip = Ellipse { c; rx = 75.; ry = 50. } in + let ellip = Ellipse { c; rx = 100.; ry = 90. } in let polygon = Polygon (List.map From 89c095175e971d0dfae8ea36ce4222fc2f6a8f87 Mon Sep 17 00:00:00 2001 From: Fay Carsons Date: Wed, 20 Dec 2023 20:00:50 -0500 Subject: [PATCH 8/9] refactored ellipse representation, added constructor fns --- examples/cairo.ml | 154 ++++++++++++++++------------- temp.ml | 240 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 329 insertions(+), 65 deletions(-) create mode 100644 temp.ml diff --git a/examples/cairo.ml b/examples/cairo.ml index 5fbd2fb..1cb3eb9 100644 --- a/examples/cairo.ml +++ b/examples/cairo.ml @@ -4,41 +4,28 @@ let tau = 2. *. Float.pi type point = { x : float; y : float } type circle = { c : point; radius : float } -type ellipse = { c : point; rx : float; ry : float } -type rectangle = { c : point; width : float; height : float } + +type ellipse = { + start : point; + curve_one : float * float * float * float * float * float; + curve_two : float * float * float * float * float * float; +} + type line = { a : point; b : point } type polygon = point list type shape = | Circle of circle | Ellipse of ellipse - | Rectangle of rectangle | Line of line | Polygon of polygon | Complex of shape list -(* Point arithmetic operators - I think thesse would be useful, - but also undeerstand not everyone likes the arithmetic operator - symbol overload thing - - let ( /~ ) { x = x1; y = y1 } { x = x2; y = y2 } = { x = x1 /. x2; y = y1 /. y2 } - let ( +~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 +. x2; y = y1 +. y2} - let ( *~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 *. x2; y = y1 *. y2} - - let ( -~ ) {x = x1; y = y1} {x = x2; y = y2} = {x = x1 -. x2; y = y1 -. y2} - - let ( +! ) {x = x1; y = y1} scalar = {x = x1 +. scalar; y = y1 +. scalar} - let ( *! ) {x = x1; y = y1} scalar = {x = x1 *. scalar; y = y1 *. scalar} - let ( /! ) {x = x1; y = y1} scalar = {x = x1 /. scalar; y = y1 /. scalar} *) - -(* point + point arithmetic *) - (* point + scalar arithmetic *) let ( -! ) { x = x1; y = y1 } scalar = { x = x1 -. scalar; y = y1 -. scalar } (* Global rendering context singleton definition and instantiation *) -type cairo_context = { +type joy_context = { ctx : Cairo.context; surface : Cairo.Surface.t; size : point; @@ -46,9 +33,14 @@ type cairo_context = { } let context = ref None -let fail = "Context not initialized" +let fail () = failwith "Context not initialized" (* Context initialization, render, and update fns *) +(* Currently, function signature does not match the canvas + backend function signature, which is a problem. + + Having the end-user call 'set_filename', 'set_size', and + 'set_line_width' isn't particularly satisfying either though *) let init_context ?line_width (x, y) filename = (* Fail if context has already been instantiated *) if Option.is_some !context then @@ -67,8 +59,8 @@ let init_context ?line_width (x, y) filename = let write ctx = Cairo.PNG.write ctx.surface ctx.filename (* gets surface size in range 0..pixels *) -let get_dimensions () = - match !context with Some ctx -> ctx.size | None -> failwith fail +let get_window_size () = + match !context with Some ctx -> ctx.size | None -> fail () (* sets global color *) let set_color color = @@ -76,7 +68,7 @@ let set_color color = | Some ctx -> let r, g, b, a = color in Cairo.set_source_rgba ctx.ctx r g b a - | None -> failwith fail + | None -> fail () (* sets background color *) let background color = @@ -85,7 +77,7 @@ let background color = let r, g, b = color in Cairo.set_source_rgb ctx.ctx r g b; Cairo.paint ctx.ctx - | None -> failwith fail + | None -> fail () (* Scales points from 0-image size to 0-1 on both axes *) let scale_point size point = @@ -94,18 +86,41 @@ let scale_point size point = (x, y) (* Shape rendering fns *) + +(* Circle *) +let circle ?point radius = + match point with + | Some c -> Circle { c; radius } + | None -> Circle { c = { x = 0.; y = 0. }; radius } + let draw_circle ctx ({ c; radius } : circle) = let x, y = scale_point ctx.size c in let radius = radius /. min ctx.size.x ctx.size.y in Cairo.arc ctx.ctx x y ~r:radius ~a1:0. ~a2:tau; Cairo.stroke ctx.ctx -let draw_rect ctx ({ c; width; height } : rectangle) = - let x, y = scale_point ctx.size (c -! ((width +. height) /. 4.)) in - let w = width /. ctx.size.x in - let h = height /. ctx.size.y in - Cairo.rectangle ctx.ctx x y ~w ~h; - Cairo.stroke ctx.ctx +(* Rectangle *) +let make_rectangle c width height = + let width, height = (width *. 2., height *. 2.) in + let { x; y } = c -! ((width +. height) /. 4.) in + Polygon + [ + { x; y }; + { x; y = y +. height }; + { x = x +. width; y = y +. height }; + { x = x +. width; y }; + ] + +let rectangle ?point width height = + match point with + | Some c -> make_rectangle c width height + | None -> make_rectangle { x = 0.; y = 0. } width height + +(* Line *) +let line ?point b = + match point with + | Some a -> Line { a; b } + | None -> Line { a = { x = 0.; y = 0. }; b } let draw_line ctx line = let x1, y1 = scale_point ctx.size line.a in @@ -115,29 +130,36 @@ let draw_line ctx line = Cairo.stroke ctx.ctx; Cairo.move_to ctx.ctx 0. 0. -(* Ellipse helper fn & rendering fn - currently just multiplying radii by 2 to offset scaling issue +(* Ellipse helper fn & rendering fn + + currently just multiplying radii by 2 to offset scaling issue feels hacky *) -let calculate_control_points (size : point) ({ c; rx; ry } : ellipse) = +let ellipse ?point rx ry = + let c = match point with Some p -> p | None -> { x = 0.; y = 0. } in + let size = get_window_size () in let x, y = scale_point size c in let half_height = ry /. size.y in let width_two_thirds = rx /. size.x *. (2. /. 3.) *. 2. in - ( { x; y = y -. half_height }, - ( x +. width_two_thirds, - y -. half_height, - x +. width_two_thirds, - y +. half_height, - x, - y +. half_height ), - ( x -. width_two_thirds, - y +. half_height, - x -. width_two_thirds, - y -. half_height, - x, - y -. half_height ) ) - -let draw_ellipse (ctx : cairo_context) (ellipse : ellipse) = - let start, curve_one, curve_two = calculate_control_points ctx.size ellipse in + Ellipse + { + start = { x; y = y -. half_height }; + curve_one = + ( x +. width_two_thirds, + y -. half_height, + x +. width_two_thirds, + y +. half_height, + x, + y +. half_height ); + curve_two = + ( x -. width_two_thirds, + y +. half_height, + x -. width_two_thirds, + y -. half_height, + x, + y -. half_height ); + } + +let draw_ellipse (ctx : joy_context) { start; curve_one; curve_two } = Cairo.save ctx.ctx; Cairo.move_to ctx.ctx start.x start.y; let x1, y1, x2, y2, x3, y3 = curve_one in @@ -148,6 +170,8 @@ let draw_ellipse (ctx : cairo_context) (ellipse : ellipse) = Cairo.restore ctx.ctx (* Polygon helper fns and rendering fn *) +let polygon points = Polygon points + let rec take n lst = match (n, lst) with | 0, _ -> ([], lst) @@ -181,11 +205,13 @@ let draw_polygon ctx (polygon : polygon) = Cairo.move_to ctx.ctx 0. 0.; Cairo.stroke ctx.ctx +(* Complex *) +let complex shapes = Complex shapes + (* Root render fn *) let rec render_shape ctx shape = (match shape with | Circle circle -> draw_circle ctx circle - | Rectangle rectangle -> draw_rect ctx rectangle | Ellipse ellipse -> draw_ellipse ctx ellipse | Line line -> draw_line ctx line | Polygon polygon -> draw_polygon ctx polygon @@ -194,34 +220,32 @@ let rec render_shape ctx shape = (* Validates context before rendering *) let render shape = - match !context with - | Some ctx -> render_shape ctx shape - | None -> failwith fail + match !context with Some ctx -> render_shape ctx shape | None -> fail () (* 'sketch' functions - this is a prototype of what the user would be writing *) let draw () = - let { x = w; y = h } = get_dimensions () in + let { x = w; y = h } = get_window_size () in let c = { x = w /. 2.; y = h /. 2. } in - let circle = Circle { c; radius = 100. } in - let rect = Rectangle { c; width = w /. 4.; height = h /. 4. } in - let ellip = Ellipse { c; rx = 100.; ry = 90. } in + let circle = circle ~point:c 100. in + let rect = rectangle ~point:c (w /. 4.) (h /. 4.) in + let ellip = ellipse ~point:c 100. 75. in let polygon = - Polygon + polygon (List.map (fun { x; y } -> { x = x +. 10.; y = y +. 10. }) [ c; { x = c.x; y = c.y +. 100. }; { x = c.x +. 100.; y = c.y } ]) in let axes = - Complex + complex [ - Line { a = { x = w /. 2.; y = 0. }; b = { x = w /. 2.; y = h } }; - Line { a = { x = 0.; y = h /. 2. }; b = { x = w; y = h /. 2. } }; + line ~point:{ x = w /. 2.; y = 0. } { x = w /. 2.; y = h }; + line ~point:{ x = 0.; y = h /. 2. } { x = w; y = h /. 2. }; ] in let complex = Complex [ circle; rect; ellip; polygon; axes ] in render complex -let init ?size ?filename () = +let setup ?size ?filename () = let size = match size with Some s -> s | None -> { x = 800.; y = 800. } in let { x; y } = size in let filename = match filename with Some s -> s | None -> "cairo.png" in @@ -230,4 +254,4 @@ let init ?size ?filename () = set_color (0., 0., 0., 1.); draw () -let () = init () +let () = setup () diff --git a/temp.ml b/temp.ml new file mode 100644 index 0000000..9927b1d --- /dev/null +++ b/temp.ml @@ -0,0 +1,240 @@ +(* JS deps *) +module Html = Js_of_ocaml.Dom_html +module Dom = Js_of_ocaml.Dom +module Js = Js_of_ocaml.Js +module G = Graphics_js + +type point = { x : float; y : float } + +let ( -! ) point scalar = { x = point.x -. scalar; y = point.y -. scalar } + +type circle = { c : point; radius : float } + +type ellipse = { + start : point; + curve_one : float * float * float * float * float * float; + curve_two : float * float * float * float * float * float; +} + +type line = { a : point; b : point } +type polygon = point list + +type shape = + | Circle of circle + | Ellipse of ellipse + | Line of line + | Polygon of polygon + | Complex of shape list + +(* JS type conversion helpers *) +let str = Js.string +let bl = Js.bool + +(* aliases for globals *) +let doc = Html.document +let window = Html.window + +(* Context *) +type joy_context = { + context : Html.canvasRenderingContext2D Js.t; + canvas : Html.canvasElement Js.t; +} + +let context : joy_context option ref = ref None +let fail () = window##alert (str "Context not initialized!") + +let init_context canvas = + if Option.is_some !context then + window##alert (str "Context cannot be initialized twice!") + else ( + G.open_canvas canvas; + Dom.appendChild doc##.body canvas; + let ctx = canvas##getContext Html._2d_ in + context := Some { context = ctx; canvas }) + +let get_window_size () = + let w = float_of_int window##.innerWidth in + let h = float_of_int window##.innerHeight in + (w, h) + +let maximize_canvas () = + match !context with + | Some ctx -> + let w, h = get_window_size () in + ctx.canvas##.width := int_of_float w; + ctx.canvas##.height := int_of_float h + | None -> fail () + +let create_canvas () = + let w, h = get_window_size () in + let canvas = Html.createCanvas doc in + canvas##.width := int_of_float w; + canvas##.height := int_of_float h; + canvas + +let color_str (r, g, b) = + str (Printf.sprintf "rgb(%f, %f, %f)" (r *. 255.) (g *. 255.) (b *. 255.)) + +(* Sets global color *) +let set_color color = + match !context with + | Some { context; canvas = _canvas } -> + let color_string = color_str color in + context##.fillStyle := color_string + | None -> fail () + +(* sets background color *) +let background color = + match !context with + | Some { context; canvas = _canvas } -> + let w, h = get_window_size () in + let _color_string = color_str color in + context##.fillStyle := str "white"; + context##fillRect 0. 0. w h + | None -> fail () + +let circle ?point radius = + match point with + | Some c -> Circle { c; radius } + | None -> Circle { c = { x = 0.; y = 0. }; radius } + +let draw_circle ctx { c; radius } = + let { x; y } = c in + ctx##beginPath; + ctx##arc x y radius 0. (2. *. Float.pi) (bl false); + ctx##stroke + +(* Rectangle *) + +let line ?point b = + match point with + | Some a -> Line { a; b } + | None -> Line { a = { x = 0.; y = 0. }; b } + +let draw_line ctx { a = { x = x1; y = y1 }; b = { x = x2; y = y2 } } = + ctx##moveTo x1 y1; + ctx##lineTo x2 y2; + ctx##stroke; + ctx##moveTo 0. 0. + +(* Ellipse constructor fn & rendering fn + currently just multiplying radii by 2 to offset scaling issue + feels hacky *) +let ellipse ?point rx ry = + let { x; y } = match point with Some p -> p | None -> { x = 0.; y = 0. } in + let rx, ry = (rx *. 2., ry *. 2.) in + let half_height = ry /. 2. in + let width_two_thirds = rx *. (2. /. 3.) in + Ellipse + { + start = { x; y = y -. half_height }; + curve_one = + ( x +. width_two_thirds, + y -. half_height, + x +. width_two_thirds, + y +. half_height, + x, + y +. half_height ); + curve_two = + ( x -. width_two_thirds, + y +. half_height, + x -. width_two_thirds, + y -. half_height, + x, + y -. half_height ); + } + +let draw_ellipse ctx (ellipse : ellipse) = + let { start; curve_one; curve_two } = ellipse in + ctx##moveTo start.x start.y; + let x1, y1, x2, y2, x3, y3 = curve_one in + ctx##bezierCurveTo x1 y1 x2 y2 x3 y3; + let x1, y1, x2, y2, x3, y3 = curve_two in + ctx##bezierCurveTo x1 y1 x2 y2 x3 y3; + ctx##stroke; + ctx##moveTo 0. 0. + +(* Polygon helper fns and rendering fn *) +let polygon points = Polygon points + +let rec take n lst = + match (n, lst) with + | 0, _ -> ([], lst) + | _, [] -> ([], []) + | n, x :: xs -> + let taken, rest = take (n - 1) xs in + (x :: taken, rest) + +let rec partition n ?step lst = + match lst with + | [] -> [] + | _ -> + let taken, _ = take n lst in + if List.length taken = n then + taken + :: + (match step with + | Some s -> partition n ~step:s (List.tl lst) + | None -> partition n ~step:0 (List.tl lst)) + else [] + +let draw_polygon ctx (polygon : polygon) = + let points = partition 2 ~step:1 (polygon @ [ List.hd polygon ]) in + List.iter + (fun pair -> + let { x = x1; y = y1 }, { x = x2; y = y2 } = + (List.nth pair 0, List.nth pair 1) + in + ctx##moveTo x1 y1; + ctx##lineTo x2 y2) + points; + ctx##stroke; + ctx##moveTo 0. 0. + +let complex shapes = Complex shapes + +let rec render_shape ctx shape = + match shape with + | Circle circle -> draw_circle ctx circle + | Ellipse ellipse -> draw_ellipse ctx ellipse + | Line line -> draw_line ctx line + | Polygon polygon -> draw_polygon ctx polygon + | Complex complex -> List.iter (render_shape ctx) complex + +let render shape = + match !context with + | Some ctx -> render_shape ctx.context shape + | None -> fail () + +let draw () = + let w, h = get_window_size () in + let c = { x = w /. 2.; y = h /. 2. } in + background (1., 1., 1.); + set_color (0., 0., 0.); + let circle = circle ~point:c 100. in + let rect = rectangle ~point:c 100. 100. in + let ellip = ellipse ~point:c 100. 75. in + let polygon = + polygon + (List.map + (fun { x; y } -> { x = x +. 10.; y = y +. 10. }) + [ c; { x = c.x; y = c.y +. 100. }; { x = c.x +. 100.; y = c.y } ]) + in + let axes = + complex + [ + line ~point:{ x = w /. 2.; y = 0. } { x = w /. 2.; y = h }; + line ~point:{ x = 0.; y = h /. 2. } { x = w; y = h /. 2. }; + ] + in + let complex = complex [ rect; ellip; circle; polygon; axes ] in + render complex + +let onload _ = + let canvas = create_canvas () in + init_context canvas; + maximize_canvas (); + draw (); + Js._true + +let _ = window##.onload := Html.handler onload From 35225f8ab9f5808600d80a9c8602d7938ca6bb8c Mon Sep 17 00:00:00 2001 From: Fay Carsons Date: Wed, 20 Dec 2023 20:31:49 -0500 Subject: [PATCH 9/9] Created exception type for context errors, need help w custom printer --- examples/cairo.ml | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/examples/cairo.ml b/examples/cairo.ml index 1cb3eb9..01d5c3c 100644 --- a/examples/cairo.ml +++ b/examples/cairo.ml @@ -33,7 +33,16 @@ type joy_context = { } let context = ref None -let fail () = failwith "Context not initialized" + +exception Context of string +(* Not working, could use help fixing *) +let () = + Printexc.register_printer (fun e -> match e with + | Context err -> Some ("Context: " ^ err) + | _ -> None +) + +let fail () = raise (Context "not initialized") (* Context initialization, render, and update fns *) (* Currently, function signature does not match the canvas @@ -44,7 +53,7 @@ let fail () = failwith "Context not initialized" let init_context ?line_width (x, y) filename = (* Fail if context has already been instantiated *) if Option.is_some !context then - failwith "Context cannot be initialized twice!"; + raise (Context "Cannot initialize context twice"); let surface = Cairo.Image.create Cairo.Image.ARGB32 ~w:(int_of_float x) @@ -53,7 +62,7 @@ let init_context ?line_width (x, y) filename = let ctx = Cairo.create surface in Cairo.scale ctx x y; Cairo.set_line_width ctx (match line_width with Some n -> n | None -> 0.002); - context := Some { ctx; surface; size = { x; y }; filename } + context := Some { ctx; surface; size = { x; y }; filename } (* Renders context to PNG *) let write ctx = Cairo.PNG.write ctx.surface ctx.filename @@ -245,13 +254,13 @@ let draw () = let complex = Complex [ circle; rect; ellip; polygon; axes ] in render complex -let setup ?size ?filename () = - let size = match size with Some s -> s | None -> { x = 800.; y = 800. } in - let { x; y } = size in - let filename = match filename with Some s -> s | None -> "cairo.png" in +let setup () = + let x, y = (800., 800.) in + let filename = "cairo.png" in init_context (x, y) filename; background (1., 1., 1.); set_color (0., 0., 0., 1.); draw () -let () = setup () +let _ = setup () +