diff --git a/examples/axes.ml b/examples/axes.ml index aa8c2ef..1aee6d5 100644 --- a/examples/axes.ml +++ b/examples/axes.ml @@ -4,7 +4,6 @@ open Joy let _ = (* intialize rendering context with the axes flag set to true *) init ~axes:true (); - (* set background to opaque white *) let c = circle 50 in show [ c ]; (* Write to PNG! *) diff --git a/examples/circle_packing.ml b/examples/circle_packing.ml index a954639..fc3670b 100644 --- a/examples/circle_packing.ml +++ b/examples/circle_packing.ml @@ -16,19 +16,19 @@ let max_attempts = 100_000 let palette = [ (* purple *) - (107, 108, 163); + rgb 107 108 163; (* light blue *) - (135, 188, 189); + rgb 135 188 189; (* green *) - (111, 153, 84); + rgb 111 153 84; (* light purple *) - (150, 155, 199); + rgb 150 155 199; (* light green *) - (137, 171, 124); + rgb 137 171 124; (* dark purple *) - (67, 68, 117); + rgb 67 68 117; (* darker purple *) - (44, 45, 84); + rgb 44 45 84; ] (* utility Functions *) diff --git a/examples/dune b/examples/dune index 535d5cc..70ee599 100644 --- a/examples/dune +++ b/examples/dune @@ -58,6 +58,11 @@ (modules line) (libraries joy)) +(executable + (name parallel_lines) + (modules parallel_lines) + (libraries joy)) + (executable (name higher_transforms) (modules higher_transforms) @@ -132,3 +137,8 @@ (name smile) (modules smile) (libraries joy)) + +(executable + (name simple_rotate_ellipse) + (modules simple_rotate_ellipse) + (libraries joy)) diff --git a/examples/fill_rect.ml b/examples/fill_rect.ml index 18d5b28..bfafecc 100644 --- a/examples/fill_rect.ml +++ b/examples/fill_rect.ml @@ -2,6 +2,6 @@ open Joy let () = init (); - let r = rectangle 200 200 |> with_fill (255, 0, 0) in + let r = rectangle 200 200 |> with_fill (rgb 255 0 0) in show [ r ]; write ~filename:"fill-rect.png" () diff --git a/examples/flowfield.ml b/examples/flowfield.ml index e857ca3..4b05d0a 100644 --- a/examples/flowfield.ml +++ b/examples/flowfield.ml @@ -91,7 +91,8 @@ let add_color flowfield line (x, y) = |> ( *. ) (float_of_int (List.length palette)) |> int_of_float |> List.nth palette in - line |> Joy.with_stroke color + let r, g, b = color in + line |> Joy.with_stroke (Joy.rgb r g b) let () = let open Joy in diff --git a/examples/line.ml b/examples/line.ml index 2c250c1..68c3798 100644 --- a/examples/line.ml +++ b/examples/line.ml @@ -1,22 +1,9 @@ open Joy -let size = 800 -let interval = 16 -let line_interval = 800 / interval -let rec range a b = if a > b then [] else a :: range (a + 1) b -let inc x = x + 1 - let _ = - init ~size:(size, size) (); - let half_size = size / 2 in - let lines = - List.map - (fun i -> - let newx = i |> inc |> ( * ) line_interval in - line - ~a:(point (newx - half_size) (-half_size)) - (point (newx - half_size) half_size)) - (range 0 interval) - in - show lines; + init (); + let l1 = line (point 50 50) in + let l2 = line (point (-50) 50) in + let l3 = line ~a:(point (-50) 50) (point 50 50) in + show [ l1; l2; l3 ]; write ~filename:"line.png" () diff --git a/examples/parallel_lines.ml b/examples/parallel_lines.ml new file mode 100644 index 0000000..a557726 --- /dev/null +++ b/examples/parallel_lines.ml @@ -0,0 +1,22 @@ +open Joy + +let size = 800 +let interval = 16 +let line_interval = 800 / interval +let rec range a b = if a > b then [] else a :: range (a + 1) b +let inc x = x + 1 + +let _ = + init ~size:(size, size) (); + let half_size = size / 2 in + let lines = + List.map + (fun i -> + let newx = i |> inc |> ( * ) line_interval in + line + ~a:(point (newx - half_size) (-half_size)) + (point (newx - half_size) half_size)) + (range 0 interval) + in + show lines; + write ~filename:"parallel_lines.png" () diff --git a/examples/quadtree.ml b/examples/quadtree.ml index 59936a0..f0c2d54 100644 --- a/examples/quadtree.ml +++ b/examples/quadtree.ml @@ -6,8 +6,8 @@ let half_size = size /. 2. let max_leaf_points = 4 let clusters = 32 let point_size = 1 -let box_color = (0, 0, 0) -let point_color = (255, 1, 1) +let box_color = Joy.rgb 0 0 0 +let point_color = Joy.rgb 255 1 1 (* Point utils *) let splat n : point = { x = n; y = n } diff --git a/examples/rotate_ellipse.ml b/examples/rotate_ellipse.ml index 57b7eb4..e609cd6 100644 --- a/examples/rotate_ellipse.ml +++ b/examples/rotate_ellipse.ml @@ -4,12 +4,11 @@ let max = 32. let rec range a b = if a > b then [] else a :: range (a +. 1.) b let _ = - init (); - let rect = rectangle 100 50 |> translate 195 220 in + init ~size:(500, 500) ~axes:true (); let ell = ellipse 100 50 |> translate 60 60 in let nums = range 0. max in let rotated = List.map (fun i -> rotate (int_of_float (i /. max *. 360.0)) ell) nums in - show (rect :: rotated); + show rotated; write ~filename:"rotate_ellipse.png" () diff --git a/examples/simple_rotate_ellipse.ml b/examples/simple_rotate_ellipse.ml new file mode 100644 index 0000000..214a630 --- /dev/null +++ b/examples/simple_rotate_ellipse.ml @@ -0,0 +1,7 @@ +open Joy + +let _ = + init (); + let ell = ellipse 100 50 in + show [ ell; ell |> rotate 60 ]; + write ~filename:"simple_rotate_ellipse.png" () diff --git a/examples/smile.ml b/examples/smile.ml index 89ca1d3..85f2842 100644 --- a/examples/smile.ml +++ b/examples/smile.ml @@ -1,27 +1,26 @@ open Joy let make_nose () = - let l = line (point 0 50)|> translate 0 (-25) in - let l1 = l in - let l2 = l |> rotate 90|> translate 0 (-25) in - let nose = complex [l1; l2] in - nose + let l = line (point 0 50) |> translate 0 (-25) in + let l1 = l in + let l2 = l |> rotate 90 |> translate 0 (-25) in + let nose = complex [ l1; l2 ] in + nose -let make_arc rx ry = - let r = rectangle ~c:(point 0 (ry / 2)) (2 * rx) ry in - let col = r |> with_fill white |> with_stroke white in - let e = ellipse ~c:(point 0 0) rx ry in - complex [e; col] +let make_arc rx ry = + let r = rectangle ~c:(point 0 (ry / 2)) (2 * rx) ry in + let col = r |> with_fill white |> with_stroke white in + let e = ellipse ~c:(point 0 0) rx ry in + complex [ e; col ] let () = - init (); - let a = circle 200 in - let d = circle ~c:(point 50 50) 20 in - let b = circle ~c:(point (-50) 50) 20 in - let nose = make_nose () in - let leb = make_arc 26 14 |> rotate 180 |> translate 50 70 in - let reb = make_arc 26 14 |> rotate 180 |> translate (-50) 70 in - let mouth = make_arc 80 40 |>translate 0 (-60) in - show [mouth;leb;reb;a;d;b;nose]; - write () - + init (); + let a = circle 200 in + let d = circle ~c:(point 50 50) 20 in + let b = circle ~c:(point (-50) 50) 20 in + let nose = make_nose () in + let leb = make_arc 26 14 |> rotate 180 |> translate 50 70 in + let reb = make_arc 26 14 |> rotate 180 |> translate (-50) 70 in + let mouth = make_arc 80 40 |> translate 0 (-60) in + show [ mouth; leb; reb; a; d; b; nose ]; + write () diff --git a/lib/backend_cairo.ml b/lib/backend_cairo.ml new file mode 100644 index 0000000..3d485fa --- /dev/null +++ b/lib/backend_cairo.ml @@ -0,0 +1,109 @@ +type context = { + cairo_ctx : Cairo.context; + surface : Cairo.Surface.t; + size : int * int; + background_color : int * int * int * float; + axes : bool; +} + +let write ctx filename = + Cairo.PNG.write ctx.surface filename; + Cairo.Surface.finish ctx.surface + +let set_color ctx color = + let to_float i = float_of_int i /. 255. in + let r, g, b, a = color in + let r, g, b = Util.tmap3 to_float (r, g, b) in + Cairo.set_source_rgba ctx.cairo_ctx r g b a + +let set_background ctx color = + let r, g, b, a = color in + let to_float i = float_of_int i /. 255. in + let r, g, b = Util.tmap3 to_float (r, g, b) in + Cairo.set_source_rgba ctx.cairo_ctx r g b a; + Cairo.paint ctx.cairo_ctx ~alpha:a; + Cairo.fill ctx.cairo_ctx + +(** Sets the width of lines for both stroke of shapes and line primitives. + Can be any positive integer, with larger numbers producing thicker lines. *) +let set_line_width ctx line_width = + Cairo.set_line_width ctx.cairo_ctx (float_of_int line_width) + +let draw_circle ctx (cx, cy) radius stroke fill = + Cairo.arc ctx.cairo_ctx cx (Float.neg cy) ~r:radius ~a1:0. ~a2:(Float.pi *. 2.); + set_color ctx stroke; + Cairo.stroke_preserve ctx.cairo_ctx; + set_color ctx fill; + Cairo.fill_preserve ctx.cairo_ctx; + Cairo.Path.clear ctx.cairo_ctx + +let draw_ellipse ctx (cx, cy) rx ry rotation stroke fill = + (* Save the current transformation matrix *) + let save_matrix = Cairo.get_matrix ctx.cairo_ctx in + + (* Apply rotation *) + let radians = Util.to_radians rotation in + Cairo.rotate ctx.cairo_ctx radians; + + (* Translate and scale to create an ellipse from a circle *) + Cairo.translate ctx.cairo_ctx cx (Float.neg cy); + Cairo.scale ctx.cairo_ctx rx ry; + + (* Arc from 0 to 2pi is a circle *) + Cairo.arc ctx.cairo_ctx 0. 0. ~r:1. ~a1:0. ~a2:(2. *. Float.pi); + + (* Restore the original transformation matrix *) + Cairo.set_matrix ctx.cairo_ctx save_matrix; + + set_color ctx stroke; + Cairo.stroke_preserve ctx.cairo_ctx; + set_color ctx fill; + Cairo.fill_preserve ctx.cairo_ctx; + + Cairo.Path.clear ctx.cairo_ctx + +let draw_line ctx (x1, y1) (x2, y2) stroke = + set_color ctx stroke; + Cairo.move_to ctx.cairo_ctx x1 (Float.neg y1); + Cairo.line_to ctx.cairo_ctx x2 (Float.neg y2); + Cairo.stroke ctx.cairo_ctx + +let draw_polygon ctx vertices stroke fill = + let x, y = List.hd vertices in + let t = List.tl vertices in + Cairo.move_to ctx.cairo_ctx x (Float.neg y); + List.iter (fun (x', y') -> Cairo.line_to ctx.cairo_ctx x' (Float.neg y')) t; + Cairo.Path.close ctx.cairo_ctx; + set_color ctx stroke; + Cairo.stroke_preserve ctx.cairo_ctx; + set_color ctx fill; + Cairo.fill ctx.cairo_ctx + +let show ctx shapes = + let rec render = function + | Shape.Circle circle -> + draw_circle ctx (circle.c.x, circle.c.y) circle.radius circle.stroke + circle.fill + | Shape.Ellipse ellipse -> + draw_ellipse ctx (ellipse.c.x, ellipse.c.y) ellipse.rx ellipse.ry + ellipse.rotation ellipse.stroke ellipse.fill + | Shape.Line line -> + draw_line ctx (line.a.x, line.a.y) (line.b.x, line.b.y) line.stroke + | Shape.Polygon polygon -> + let to_tuple (point : float Shape.point) = (point.x, point.y) in + draw_polygon ctx + (List.map to_tuple polygon.vertices) + polygon.stroke polygon.fill + | Shape.Complex complex -> List.iter render complex + in + List.iter render shapes + +let create ~background_color ~line_width ~size ~axes = + let w, h = size in + let surface = Cairo.Image.create Cairo.Image.ARGB32 ~w ~h in + let cairo_ctx = Cairo.create surface in + Cairo.translate cairo_ctx (w / 2 |> float_of_int) (h / 2 |> float_of_int); + let ctx = { cairo_ctx; surface; size = (w, h); background_color; axes } in + set_background ctx background_color; + set_line_width ctx line_width; + ctx diff --git a/lib/backend_cairo.mli b/lib/backend_cairo.mli new file mode 100644 index 0000000..b6b23e5 --- /dev/null +++ b/lib/backend_cairo.mli @@ -0,0 +1,20 @@ +type context = { + cairo_ctx : Cairo.context; + surface : Cairo.Surface.t; + size : int * int; + background_color : Color.color; + axes : bool; +} + +val set_color : context -> Color.color -> unit +val set_background : context -> Color.color -> unit +val set_line_width : context -> int -> unit +val show : context -> Shape.shapes -> unit +val write : context -> string -> unit + +val create : + background_color:Color.color -> + line_width:int -> + size:int * int -> + axes:bool -> + context diff --git a/lib/backend_lazy.ml b/lib/backend_lazy.ml new file mode 100644 index 0000000..e0c304c --- /dev/null +++ b/lib/backend_lazy.ml @@ -0,0 +1,4 @@ +type context = unit + +let show _ctx _shapes = () +let create _ = () diff --git a/lib/backend_lazy.mli b/lib/backend_lazy.mli new file mode 100644 index 0000000..d7ff084 --- /dev/null +++ b/lib/backend_lazy.mli @@ -0,0 +1,4 @@ +type context = unit + +val show : context -> Shape.shapes -> unit +val create : unit -> context diff --git a/lib/backend_svg.ml b/lib/backend_svg.ml new file mode 100644 index 0000000..e0c304c --- /dev/null +++ b/lib/backend_svg.ml @@ -0,0 +1,4 @@ +type context = unit + +let show _ctx _shapes = () +let create _ = () diff --git a/lib/backend_svg.mli b/lib/backend_svg.mli new file mode 100644 index 0000000..d7ff084 --- /dev/null +++ b/lib/backend_svg.mli @@ -0,0 +1,4 @@ +type context = unit + +val show : context -> Shape.shapes -> unit +val create : unit -> context diff --git a/lib/color.ml b/lib/color.ml index 18a8caa..c7121d3 100644 --- a/lib/color.ml +++ b/lib/color.ml @@ -1,26 +1,28 @@ -type color = int * int * int +type color = int * int * int * float + +(** RGBA constant to set transparent background *) +let transparent = (0, 0, 0, 0.0) + +(** Converts RGB color into opaque RGBA color. + For use w/ `Context.background` *) +let rgb r g b = (r, g, b, 1.0) + +let color ?(a = 1.0) r g b = (r, g, b, a) (** RGB code for black *) -let black = (0, 0, 0) +let black = rgb 0 0 0 (** RGB code for white *) -let white = (255, 255, 255) +let white = rgb 255 255 255 (** RGB code for red *) -let red = (255, 1, 1) +let red = rgb 255 1 1 (** RGB code for green *) -let green = (1, 255, 1) +let green = rgb 1 255 1 (** RGB code for blue *) -let blue = (1, 1, 255) +let blue = rgb 1 1 255 (** RGB code for yellow *) -let yellow = (255, 255, 1) - -(** RGBA constant to set transparent background *) -let transparent = (0, 0, 0, 0) - -(** Converts RGB color into opaque RGBA color. - For use w/ `Context.background` *) -let opaque (r, g, b) = (r, g, b, 255) +let yellow = rgb 255 255 255 diff --git a/lib/color.mli b/lib/color.mli index 7ac3165..8984bac 100644 --- a/lib/color.mli +++ b/lib/color.mli @@ -1,10 +1,11 @@ -type color = int * int * int +type color = int * int * int * float +val rgb : int -> int -> int -> color +val color : ?a:float -> int -> int -> int -> color val black : color val white : color val red : color val green : color val blue : color val yellow : color -val transparent : int * int * int * int -val opaque : color -> int * int * int * int +val transparent : color diff --git a/lib/context.ml b/lib/context.ml index f380448..876a3db 100644 --- a/lib/context.ml +++ b/lib/context.ml @@ -1,71 +1,41 @@ -open Util - -(* Global rendering context singleton definition and instantiation *) -type context = { - ctx : Cairo.context; - surface : Cairo.Surface.t; - size : int * int; - axes : bool; -} - -(* Renders context to PNG *) -let write ctx filename = - Cairo.PNG.write ctx.surface filename; - Cairo.Surface.finish ctx.surface - -let context = ref None - -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") -let resolution () = match !context with Some ctx -> ctx.size | None -> fail () -let scale_channel n = n /. 255. -let scale_color_channel = float_of_int >> scale_channel - -let set_color color = - match !context with - | Some ctx -> - let r, g, b = tmap3 scale_color_channel color in - Cairo.set_source_rgb ctx.ctx r g b - | None -> fail () - -(* sets background color *) -let background color = - match !context with - | Some { ctx; _ } -> - let r, g, b, alpha = tmap4 scale_color_channel color in - Cairo.set_source_rgb ctx r g b; - Cairo.paint ctx ~alpha; - Cairo.fill ctx - | None -> fail () - -(** Sets the width of lines for both stroke of shapes and line primitives. - Can be any positive integer, with larger numbers producing thicker lines. - default is 2 *) -let set_line_width line_width = - match !context with - | Some ctx -> Cairo.set_line_width ctx.ctx (float_of_int line_width) - | None -> fail () - -let save () = - match !context with Some ctx -> Cairo.save ctx.ctx | None -> fail () - -let restore () = - match !context with Some ctx -> Cairo.restore ctx.ctx | None -> fail () - -let init_context background_color line_width (w, h) axes = - (* Fail if context has already been instantiated *) - if Option.is_some !context then - raise (Context "Cannot initialize context twice"); - - let surface = Cairo.Image.create Cairo.Image.ARGB32 ~w ~h in - let ctx = Cairo.create surface in - Cairo.set_line_width ctx line_width; - Cairo.translate ctx (w / 2 |> float_of_int) (h / 2 |> float_of_int); - context := Some { ctx; surface; size = (w, h); axes }; - background background_color +type context = + | CairoContext of Backend_cairo.context + | SVGContext of Backend_svg.context + | LazyContext of Backend_lazy.context + +exception No_context +exception Unsupported_output_format of string + +let default = ref (LazyContext (Backend_lazy.create ())) +let get_default _ = !default +let set_default ctx = default := ctx + +let show ?ctx shapes = + let ctx = match ctx with Some ctx -> ctx | None -> get_default () in + match ctx with + | CairoContext ctx -> Backend_cairo.show ctx shapes + | SVGContext ctx -> Backend_svg.show ctx shapes + | LazyContext ctx -> Backend_lazy.show ctx shapes + +let set_line_width ?ctx int = + let ctx = match ctx with Some ctx -> ctx | None -> get_default () in + match ctx with + | CairoContext ctx -> Backend_cairo.set_line_width ctx int + | SVGContext _ -> failwith "SVG.set_line_width ctx int" + | LazyContext _ -> failwith "Backend_lazy.set_line_width ctx int" + +let writePNG ?ctx filename = + let ctx = match ctx with Some ctx -> ctx | None -> get_default () in + match ctx with + | CairoContext ctx -> Backend_cairo.write ctx filename + | SVGContext _ -> + raise (Unsupported_output_format "SVG context cannot render to PNG") + | LazyContext _ -> failwith "Lazy.writePNG ctx filename" + +let writeSVG ?ctx = + let ctx = match ctx with Some ctx -> ctx | None -> get_default () in + match ctx with + | CairoContext _ -> + raise (Unsupported_output_format "Cairo context cannot render to SVG") + | SVGContext _ -> failwith "SVG.writeSVG ctx" + | LazyContext _ -> failwith "Lazy.writeSVG ctx" diff --git a/lib/context.mli b/lib/context.mli index d9f36a9..d789c84 100644 --- a/lib/context.mli +++ b/lib/context.mli @@ -1,20 +1,14 @@ -type context = { - ctx : Cairo.context; - surface : Cairo.Surface.t; - size : int * int; - axes : bool; -} +type context = + | CairoContext of Backend_cairo.context + | SVGContext of Backend_svg.context + | LazyContext of Backend_lazy.context -val context : context option ref -val fail : unit -> unit +exception No_context +exception Unsupported_output_format of string -exception Context of string - -val init_context : int * int * int * int -> float -> int * int -> bool -> unit -val resolution : unit -> int * int -val set_color : int * int * int -> unit -val background : int * int * int * int -> unit -val set_line_width : int -> unit -val write : context -> string -> unit -val save : unit -> unit -val restore : unit -> unit +val get_default : unit -> context +val set_default : context -> unit +val show : ?ctx:context -> Shape.shapes -> unit +val set_line_width : ?ctx:context -> int -> unit +val writeSVG : ?ctx:context -> string +val writePNG : ?ctx:context -> string -> unit diff --git a/lib/joy.ml b/lib/joy.ml index 56ada20..d8c19eb 100644 --- a/lib/joy.ml +++ b/lib/joy.ml @@ -2,20 +2,34 @@ include Random include Shape include Transform include Color +module Backend_cairo = Backend_cairo +module Backend_svg = Backend_svg +module Backend_lazy = Backend_lazy -let context = Context.context -let set_line_width = Context.set_line_width +type context = Context.context -let init ?(background = Color.white) ?(line_width = 2) ?(size = (500, 500)) - ?(axes = false) () = - Context.init_context (Color.opaque background) (float_of_int line_width) size - axes +let show = Context.show +let set_line_width = Context.set_line_width -let write ?(filename = "joy.png") () = - match !Context.context with - | Some ctx -> - if ctx.axes then Render.render_axes (); - Context.write ctx filename - | None -> Context.fail () +let init ?(size = (500, 500)) ?(line_width = 1) ?(axes = false) _ = + let ctx = + Backend_cairo.create ~background_color:Color.white ~size ~line_width ~axes + in + let ctx_container = Context.CairoContext ctx in + Context.set_default ctx_container; + if axes then + let half_w, half_h = + ctx.size |> Util.tmap float_of_int |> Util.tmap (fun x -> x /. 2.0) + in + let gray = Color.color 128 128 128 ~a:0.5 in + let x_axis = + line ~a:{ x = -.half_w; y = 0. } { x = half_w; y = 0. } + |> with_stroke gray + in + let y_axis = + line ~a:{ x = 0.; y = -.half_h } { x = 0.; y = half_h } + |> with_stroke gray + in + show ~ctx:ctx_container [ x_axis; y_axis ] -let show shapes = Render.show shapes +let write ?(filename = "joy.png") () = Context.writePNG filename diff --git a/lib/joy.mli b/lib/joy.mli index fee45c5..1b7be45 100644 --- a/lib/joy.mli +++ b/lib/joy.mli @@ -1,3 +1,8 @@ +module Backend_cairo = Backend_cairo +module Backend_svg = Backend_svg +module Backend_lazy = Backend_lazy + +type context = Context.context type 'a point = 'a Shape.point type shape = Shape.shape type shapes = Shape.shapes @@ -11,39 +16,28 @@ val ellipse : ?c:float point -> int -> int -> shape val line : ?a:float point -> float point -> shape val polygon : float point list -> shape val complex : shapes -> shape -val with_stroke : color -> shape -> shape -val with_fill : color -> shape -> shape -val no_stroke : shape -> shape -val no_fill : shape -> shape val rotate : int -> transformation val translate : int -> int -> transformation val scale : float -> transformation val compose : transformation -> transformation -> transformation val repeat : int -> transformation -> transformation -val map_stroke : (color -> color) -> shape -> shape -val map_fill : (color -> color) -> shape -> shape val random : ?min:int -> int -> int val frandom : ?min:float -> float -> float val noise : float list -> float val fractal_noise : ?octaves:int -> float list -> float -val context : Context.context option ref -val set_line_width : int -> unit +val with_stroke : color -> transformation +val with_fill : color -> transformation +val map_stroke : (color -> color) -> transformation +val map_fill : (color -> color) -> transformation val black : color val white : color val red : color val green : color val blue : color val yellow : color -val transparent : int * int * int * int -val opaque : color -> int * int * int * int - -val init : - ?background:color -> - ?line_width:int -> - ?size:int * int -> - ?axes:bool -> - unit -> - unit - -val show : shapes -> unit +val transparent : color +val rgb : int -> int -> int -> color +val init : ?size:int * int -> ?line_width:int -> ?axes:bool -> unit -> unit val write : ?filename:string -> unit -> unit +val show : ?ctx:context -> shapes -> unit +val set_line_width : ?ctx:context -> int -> unit diff --git a/lib/render.ml b/lib/render.ml deleted file mode 100644 index d65af67..0000000 --- a/lib/render.ml +++ /dev/null @@ -1,91 +0,0 @@ -open Shape -open Context -open Util - -let draw_circle ctx ({ c; radius; stroke; fill } : circle) = - let stroke_circle stroke = - set_color stroke; - Cairo.stroke_preserve ctx.ctx - in - let fill_circle fill = - set_color fill; - Cairo.fill_preserve ctx.ctx - in - Cairo.arc ctx.ctx c.x (Float.neg c.y) ~r:radius ~a1:0. ~a2:(Float.pi *. 2.); - Option.iter stroke_circle stroke; - Option.iter fill_circle fill; - Cairo.Path.clear ctx.ctx - -let draw_ellipse ctx { c; rx; ry; stroke; fill; rotation } = - let stroke_ellipse stroke = - set_color stroke; - Cairo.stroke_preserve ctx.ctx - in - let fill_ellipse fill = - set_color fill; - Cairo.fill_preserve ctx.ctx - in - - (* Save the current transformation matrix *) - let save_matrix = Cairo.get_matrix ctx.ctx in - - (* Translate and scale to create an ellipse from a circle *) - let radians = to_radians rotation in - Cairo.translate ctx.ctx c.x (Float.neg c.y); - Cairo.rotate ctx.ctx radians; - Cairo.scale ctx.ctx rx ry; - Cairo.arc ctx.ctx 0. 0. ~r:1. ~a1:0. ~a2:(2. *. Float.pi); - - (* Restore the original transformation matrix *) - Cairo.set_matrix ctx.ctx save_matrix; - - Option.iter stroke_ellipse stroke; - Option.iter fill_ellipse fill; - Cairo.Path.clear ctx.ctx - -let draw_line ctx { a; b; stroke } = - set_color stroke; - let { x; y } = a in - Cairo.move_to ctx.ctx x (Float.neg y); - let { x; y } = b in - Cairo.line_to ctx.ctx x (Float.neg y); - Cairo.stroke ctx.ctx - -let draw_polygon ctx { vertices; stroke; fill } = - let stroke_rect stroke = - set_color stroke; - Cairo.stroke_preserve ctx.ctx - in - let fill_rect fill = - set_color fill; - Cairo.fill_preserve ctx.ctx - in - let { x; y }, t = (List.hd vertices, List.tl vertices) in - Cairo.move_to ctx.ctx x (Float.neg y); - List.iter - (fun { x = x'; y = y' } -> Cairo.line_to ctx.ctx x' (Float.neg y')) - t; - Cairo.Path.close ctx.ctx; - Option.iter stroke_rect stroke; - Option.iter fill_rect fill; - Cairo.Path.clear ctx.ctx - -(* Validates context before rendering *) -let show shapes = - let rec render ctx = function - | 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 ctx) complex - in - match !context with - | Some ctx -> List.iter (render ctx) shapes - | None -> fail () - -let render_axes () = - let x, y = resolution () |> tmap float_of_int in - let half_x, half_y = (x /. 2., y /. 2.) in - let x_axis = line ~a:{ x = 0.; y = -.half_y } { x = 0.; y = half_y } in - let y_axis = line ~a:{ x = -.half_x; y = 0. } { x = half_x; y = 0. } in - show [ x_axis; y_axis ] diff --git a/lib/render.mli b/lib/render.mli deleted file mode 100644 index 87f72dd..0000000 --- a/lib/render.mli +++ /dev/null @@ -1,6 +0,0 @@ -val draw_circle : Context.context -> Shape.circle -> unit -val draw_ellipse : Context.context -> Shape.ellipse -> unit -val draw_line : Context.context -> Shape.line -> unit -val draw_polygon : Context.context -> Shape.polygon -> unit -val render_axes : unit -> unit -val show : Shape.shape list -> unit diff --git a/lib/shape.ml b/lib/shape.ml index f40b37f..4580fb4 100644 --- a/lib/shape.ml +++ b/lib/shape.ml @@ -1,28 +1,18 @@ type 'a point = { x : 'a; y : 'a } type color = Color.color type line = { a : float point; b : float point; stroke : color } - -type circle = { - c : float point; - radius : float; - stroke : color option; - fill : color option; -} +type circle = { c : float point; radius : float; stroke : color; fill : color } type ellipse = { c : float point; rx : float; ry : float; rotation : int; - stroke : color option; - fill : color option; + stroke : color; + fill : color; } -type polygon = { - vertices : float point list; - stroke : color option; - fill : color option; -} +type polygon = { vertices : float point list; stroke : color; fill : color } type shape = | Circle of circle @@ -37,15 +27,21 @@ let point x y = let x, y = (float_of_int x, float_of_int y) in { x; y } -let center = { x = 0.; y = 0. } +let origin = point 0 0 -let circle ?(c = center) r = - Circle { c; radius = float_of_int r; stroke = Some Color.black; fill = None } +let circle ?(c = origin) r = + Circle + { + c; + radius = float_of_int r; + stroke = Color.black; + fill = Color.transparent; + } let polygon vertices = - Polygon { vertices; stroke = Some Color.black; fill = None } + Polygon { vertices; stroke = Color.black; fill = Color.transparent } -let rectangle ?(c = center) width height = +let rectangle ?(c = origin) width height = let w, h = (float_of_int width, float_of_int height) in let x = c.x -. (w /. 2.) in let y = c.y -. (h /. 2.) in @@ -54,45 +50,27 @@ let rectangle ?(c = center) width height = { x; y }; { x; y = y +. h }; { x = x +. w; y = y +. h }; { x = x +. w; y }; ] -let ellipse ?(c = center) rx ry = +let ellipse ?(c = origin) rx ry = let rx, ry = (float_of_int rx, float_of_int ry) in - Ellipse { c; rx; ry; stroke = Some Color.black; fill = None; rotation = 0 } + Ellipse { c; rx; ry; stroke = Color.black; fill = Color.transparent; rotation = 0 } -let line ?(a = center) b = Line { a; b; stroke = Color.black } +let line ?(a = origin) b = Line { a; b; stroke = Color.black } let complex shapes = match shapes with _ :: _ -> Complex shapes | [] -> Complex [] let rec with_stroke stroke = function - | Circle circle' -> Circle { circle' with stroke = Some stroke } - | Ellipse ellipse' -> Ellipse { ellipse' with stroke = Some stroke } + | Circle circle' -> Circle { circle' with stroke } + | Ellipse ellipse' -> Ellipse { ellipse' with stroke } | Line line' -> Line { line' with stroke } - | Polygon polygon' -> Polygon { polygon' with stroke = Some stroke } + | Polygon polygon' -> Polygon { polygon' with stroke } | Complex complex' -> Complex (List.map (with_stroke stroke) complex') let rec with_fill fill = function - | Circle circle' -> Circle { circle' with fill = Some fill } - | Ellipse ellipse' -> Ellipse { ellipse' with fill = Some fill } - | Polygon polygon' -> Polygon { polygon' with fill = Some fill } + | Circle circle' -> Circle { circle' with fill } + | Ellipse ellipse' -> Ellipse { ellipse' with fill } + | Polygon polygon' -> Polygon { polygon' with fill } | Complex complex' -> Complex (List.map (with_fill fill) complex') | _ as line' -> print_endline "lines do not have a fill field!"; line' - -let rec no_stroke = function - | Circle circle' -> Circle { circle' with stroke = None } - | Ellipse ellipse' -> Ellipse { ellipse' with stroke = None } - | Polygon polygon' -> Polygon { polygon' with stroke = None } - | Complex complex' -> Complex (List.map no_stroke complex') - | _ as line' -> - print_endline "Cannot remove stroke from lines"; - line' - -let rec no_fill = function - | Circle circle' -> Circle { circle' with fill = None } - | Ellipse ellipse' -> Ellipse { ellipse' with fill = None } - | Polygon polygon' -> Polygon { polygon' with fill = None } - | Complex complex' -> Complex (List.map no_fill complex') - | _ as line' -> - print_endline "Lines do not have a fill field!"; - line' diff --git a/lib/shape.mli b/lib/shape.mli index 8d119a6..da3b8dc 100644 --- a/lib/shape.mli +++ b/lib/shape.mli @@ -1,28 +1,17 @@ type color = Color.color type 'a point = { x : 'a; y : 'a } - -type circle = { - c : float point; - radius : float; - stroke : color option; - fill : color option; -} +type circle = { c : float point; radius : float; stroke : color; fill : color } type ellipse = { c : float point; rx : float; ry : float; rotation : int; - stroke : color option; - fill : color option; -} - -type polygon = { - vertices : float point list; - stroke : color option; - fill : color option; + stroke : color; + fill : color; } +type polygon = { vertices : float point list; stroke : color; fill : color } type line = { a : float point; b : float point; stroke : color } type shape = @@ -43,5 +32,3 @@ val line : ?a:float point -> float point -> shape val polygon : float point list -> shape val with_stroke : color -> shape -> shape val with_fill : color -> shape -> shape -val no_stroke : shape -> shape -val no_fill : shape -> shape diff --git a/lib/transform.ml b/lib/transform.ml index 2da136d..9735a77 100644 --- a/lib/transform.ml +++ b/lib/transform.ml @@ -51,11 +51,11 @@ let rec scale factor = function } | Line line' -> Line - { - line' with - a = Util.pmap (scale_length factor) line'.a; - b = Util.pmap (scale_length factor) line'.b; - } + { + line' with + a = Util.pmap (scale_length factor) line'.a; + b = Util.pmap (scale_length factor) line'.b; + } | Polygon polygon' -> Polygon { @@ -85,9 +85,15 @@ let rec rotate degrees = function { ellipse' with c = rotate_point degrees ellipse'.c; - rotation = ellipse'.rotation + degrees; + rotation = ellipse'.rotation + degrees; + } + | Line line' -> + Line + { + line' with + a = rotate_point degrees line'.a; + b = rotate_point degrees line'.b; } - | Line line' -> Line { line' with a = rotate_point degrees line'.a; b = rotate_point degrees line'.b } | Polygon polygon' -> Polygon { @@ -110,21 +116,16 @@ let repeat n op shape = (** Takes a function and a shape and returns a new shape with the function applied to the original's color *) let rec map_stroke f = function - | Circle circle' -> - Circle { circle' with stroke = Option.map f circle'.stroke } - | Ellipse ellipse' -> - Ellipse { ellipse' with stroke = Option.map f ellipse'.stroke } + | Circle circle' -> Circle { circle' with stroke = f circle'.stroke } + | Ellipse ellipse' -> Ellipse { ellipse' with stroke = f ellipse'.stroke } | Line line' -> Line { line' with stroke = f line'.stroke } - | Polygon polygon' -> - Polygon { polygon' with stroke = Option.map f polygon'.stroke } + | Polygon polygon' -> Polygon { polygon' with stroke = f polygon'.stroke } | Complex complex' -> Complex (List.map (map_stroke f) complex') let rec map_fill f = function - | Circle circle' -> Circle { circle' with fill = Option.map f circle'.fill } - | Ellipse ellipse' -> - Ellipse { ellipse' with fill = Option.map f ellipse'.fill } - | Polygon polygon' -> - Polygon { polygon' with fill = Option.map f polygon'.fill } + | Circle circle' -> Circle { circle' with fill = f circle'.fill } + | Ellipse ellipse' -> Ellipse { ellipse' with fill = f ellipse'.fill } + | Polygon polygon' -> Polygon { polygon' with fill = f polygon'.fill } | Complex complex' -> Complex (List.map (map_fill f) complex') | _ as line' -> print_endline "Lines do not have a fill field!";