module Vg:sig
..end
Vg
is a declarative 2D vector graphics library. In Vg
, images
are values that denote functions mapping points of
the cartesian plane to colors. The library provides
combinators to define and compose them. Renderers for
PDF, SVG and the HTML canvas
are distributed with the library. An API allows to implement
new renderers.
Consult the basics, the semantics and examples.
Open the module to use it, this defines only modules and types in your scope and a single composition operator.
Release 0.8.1 — Daniel Bünzli <daniel.buenzl i@erratique.ch>
module Font:sig
..end
typefont =
Font.t
typeglyph =
int
type
path
type
image
val (>>) : 'a -> ('a -> 'b) -> 'b
x >> f
is f x
, associates to left.
Used to build paths and compose images.module P:sig
..end
module I:sig
..end
type
renderer
module Vgr:sig
..end
Vg
is designed to be opened in your module. This defines only
types and modules in your scope and a single value, the
composition operator Vg.(>>)
. Thus to use Vg
start with :
open Gg
open Vg
Gg
gives us types for points (Gg.p2
), vectors (Gg.v2
), 2D
extents (Gg.size2
), rectangles (Gg.box2
) and colors
(Gg.color
). Later you may want to read Gg
's documentation
basics but for now it is sufficient to know that each of these
types has a constructor v
in a module named after the
capitalized type name (Gg.P2.v
, Gg.V2.v
, etc.).
Usual vector graphics libraries follow a painter model in
which paths are filled, stroked and blended on top of each other
to produce a final image. Vg
departs from that, it has a collage model in which paths define 2D areas in infinite images
that are cut to define new infinite images to be blended on
top of each other.
The collage model maps very well to a declarative imaging model.
It is also very clear from a specification point of view, both
mathematically and metaphorically. This cannot be said from the
painter model where the semantics of an operation like stroking a
self-intersecting translucent path — which usually applies the
paint only once — doesn't directly map to the underlying paint
stroke metaphor. The collage model is also more economical from a
conceptual point view since image cuts and blends naturally unify
the distinct concepts of clipping paths, path strokes, path fills
and compositing groups (unsupported for now in Vg
) of the
painter model.
The collage model introduced in the following sections was stolen and adapted from the following works.
Images in Vg
are immutable and abstract value of type
Vg.image
. Conceptually, images are seen as functions mapping
points of the infinite 2D plane to colors:
type Vg.image
≈ Gg.p2 -> Gg.color
The simplest image is a constant image: an image that associates the same color to every point in the plane. For a constant gray of intensity 0.5 this would be expressed by the function:
fun _ -> Color.gray 0.5
In Vg
the combinator Vg.I.const
represents constant infinite images
and the above function is written:
let gray = I.const (Color.gray 0.5)
The module Vg.I
contains all the combinators to define and compose
infinite images, we will explore some of them later. But for now
let's just render that fascinating image.
An infinite image alone cannot be rendered. We need a finite
view rectangle and a specification of that view's physical size on
the render target. These informations are coupled together with an
image to form a Vg.Vgr.renderable
.
Renderables can be given to a renderer for display via the
function Vg.Vgr.render
. Renderers are created with Vg.Vgr.create
and need a render target value that defines the
concrete renderer implementation used (PDF, SVG, HTML canvas etc.).
The following function outputs the unit square of gray
on a
30x30 millimeters SVG target in the file /tmp/vg-basics.svg
:
let svg_of_usquare i =
let size = Size2.v 30. 30. in
let view = Box2.unit in
try
let oc = open_out "/tmp/vg-basics.svg" in
let r = Vgr.create (Vgr_svg.target ()) (`Channel oc) in
try
ignore (Vgr.render r (`Image (size, view, i)));
ignore (Vgr.render r `End);
close_out oc
with e -> close_out oc; raise e
with Sys_error e -> prerr_endline e
let () = svg_of_usquare gray
The result should be an SVG image with a gray square
like this:
Vg
's cartesian coordinate space has its origin at the bottom
left with the x-axis pointing right, the y-axis pointing up. It
has no units, you define what they mean to you. However a
renderable implicitely defines a physical unit
for Vg
's coordinate space: the corners of the specified view
rectangle are mapped on a rectangular area of the given physical
size on the target.
Constant images can be boring. To make things more interesting
Vg
gives you scissors: the Vg.I.cut
combinator.
This combinator takes a finite area of the plane defined by a path
path
(more on paths later) and a source image img
to define the
image I.cut path img
that has the color of the source image in the
area defined by the path and the invisible transparent black color
(Gg.Color.void
) everywhere else. In other words I.cut path img
represents this function:
fun pt -> if inside path pt then img pt else Color.void
The following code cuts a circle of radius 0.4
centered in the
unit square in the gray
image defined before.
let circle = P.empty >> P.circle (P2.v 0.5 0.5) 0.4
let gray_circle = I.cut circle gray
Rendered by svg_of_usquare
the result is:
Note that the background white color surrounding the circle does
not belong to the image itself, it is the color of the webpage
background against which the image is composited. Your eyes
require a wavelength there and Gg.Color.void
cannot provide it.
Vg.I.cut
has an optional area
argument of type Vg.P.area
that
determines how a path should be interpreted as an area of the
plane. The default value is `Anz
, which means that it uses the
non-zero winding number rule and for circle
that defines its
interior.
But the circle
path can also be seen as defining a thin outline
area around the ideal mathematical circle of circle
. This can be
specified by using an outline area `O o. The value o
of type
Vg.P.outline
defines various parameters that define the outline
area; for example its width. The following code cuts the circle
outline area of width 0.04
in an infinite black image.
let circle_outline =
let area = `O { P.o with P.width = 0.04 } in
let black = I.const Color.black in
I.cut ~area circle black
Below is the result and again, the white you see here is in
fact Gg.Color.void
.
Vg.I.cut
gives us scissors but to combine the results of cuts we
need some glue: the Vg.I.blend
combinator. This combinator takes
two infinite images front
and back
and defines an image
I.blend front back
that has the colors of front
alpha blended
on top of those of back
. I.blend front back
represents
this function:
let i' = fun pt -> Color.blend (front pt) (back pt)
If we blend circle_outline
on top of gray_circle
:
let dot = I.blend circle_outline gray_circle
We get:
The order of arguments in Vg.I.blend
is defined so that images can
be blended using the left-associative composition operator
Vg.(>>)
. That is dot
can also be written as follows:
let dot = gray_circle >> I.blend circle_outline
This means that with Vg.(>>)
and Vg.I.blend
left to right order in
code maps to back to front image blending.
The combinators Vg.I.move
, Vg.I.rot
, Vg.I.scale
, and Vg.I.tr
allow
to perform arbitrary
affine
transformations on an image. For example the image I.move v i
is i
but translated by the vector v
, that is the following
function:
fun pt -> img (V2.(pt - v))
The following example uses I.move
. The function scatter_plot
takes a list of points and returns a scatter plot of the
points. First we define a dot
around the origin, just a black
circle of diameter pt_width
. Second we define the function mark
that given a point returns an image with dot
at that
point and blend_mark
that blends a mark
at a point on an image.
Finally we blend all the marks toghether.
let scatter_plot pts pt_width =
let dot =
let circle = P.empty >> P.circle P2.o (0.5 *. pt_width) in
I.const Color.black >> I.cut circle
in
let mark pt = dot >> I.move pt in
let blend_mark acc pt = acc >> I.blend (mark pt) in
List.fold_left blend_mark I.void pts
Note that dot
is defined outside mark
, this means that all mark
s
share the same dot
, doing so allows renderers to perform space
and time optimizations. For example the SVG renderer will output a single
circle
path shared by all marks.
Here's the result of scatter_point
on 800 points with coordinates
on independent normal distributions.
Paths are used to define areas of the plane. A path is an
immutable value of type Vg.path
which is a list of disconnected
subpaths. A subpath is a list of directed and connected curved
segments.
To build a path you start with the empty path Vg.P.empty
, give it
to Vg.P.sub
to start a new subpath and give the result to
Vg.P.line
, Vg.P.qcurve
, Vg.P.ccurve
, Vg.P.earc
or Vg.P.close
to
add a new segment and so forth.
Path combinators take the path they act upon as the last argument
so that the left-associative operator Vg.(>>)
can be used to
construct paths.
The image below is made by cutting the outline of the single path p
defined hereafter.
let p =
let rel = true in
P.empty >>
P.sub (P2.v 0.1 0.5) >>
P.line (P2.v 0.3 0.5) >>
P.qcurve ~rel (P2.v 0.2 0.5) (P2.v 0.2 0.0) >>
P.ccurve ~rel (P2.v 0.0 (-. 0.5)) (P2.v 0.1 (-. 0.5)) (P2.v 0.3 0.0) >>
P.earc ~rel (Size2.v 0.1 0.2) (P2.v 0.15 0.0) >>
P.sub (P2.v 0.18 0.26) >>
P.qcurve ~rel (P2.v (0.01) (-0.1)) (P2.v 0.1 (-. 0.05)) >>
P.close >>
P.sub (P2.v 0.65 0.8) >>
P.line ~rel (P2.v 0.1 (-. 0.05))
in
let area = `O { P.o with P.width = 0.01 } in
I.const Color.black >> I.cut ~area p
Except for Vg.P.close
which has no other argument but a path, the
last point argument before the path argument is always the concrete end
point of the segment. When true
the optional rel
argument
indicates that the coordinates given to the constructor are
expressed relative to end point of the last segment (or P2.o
if
there is no such segment).
Note that after a P.close
or on the P.empty
path, the
call to Vg.P.sub
can be omitted. In that case an implicit
P.sub P2.o
is introduced.
For more information about how paths are intepreted as areas, consult their semantics.
Gg
's conventions.Vg.P.tr
and Vg.I.tr
are supposed to
be affine and as such ignore the last row of the matrix.to_string
functions are not thread-safe. Thread-safety
can be achieved with pp
functions.The following notations and definitions are used to give precise meaning to the images and the combinators.
The semantics of colors is the one ascribed to
Gg.color
: colors are in a linearized sRGBA space.
A value of type Gg.Color.stops
specifies a color at each point
of the 1D unit space. It is defined by a list of pairs
(t
i, c
i)
where t
i is a value from 0
to 1
and
c
i the corresponding color at that value. Colors at points
between t
i and t
i+1 are linearly interpolated between
c
i and c
i+1. If t
i lies outside 0
to 1
or if t
i-1 >= t
i the semantics is undefined.
Given a stops value stops = [
(t
0, c
0);
(t
1,c
1);
... (t
n, c
n)
]
and any point
t
of 1D space, the semantic function:
[] : Gg.Color.stops -> float -> Gg.color
maps them to a color value written [stops
]t
as follows.
(0, 0, 0, 0)
for any t
stops
]t = c
0 if t < t
0.stops
]t = c
n if t >= t
n.stops
]t = (1-u)c
i + uc
i+1
with u = (t - t
i)/(t
i+1-t
i)
if t
i <= t <
t
i+1
Values of type Vg.image
represent maps from the infinite
2D euclidian space to colors. Given an image i
and
a point pt
of the plane the semantic function
[]: image -> Gg.p2 -> Gg.color
maps them to a color value written [i
]pt
representing the
image's color at this point.
A value of type Vg.path
is a list of subpaths. A subpath is a list
of directed and connected curved segments. Subpaths are
disconnected from each other and may (self-)intersect.
A path and a value of type Vg.P.area
defines a finite area of the
2D euclidian space. Given an area specification a
, a path p
and a point pt
, the semantic function:
[]: P.area -> path -> Gg.p2 -> bool
maps them to a boolean value written [a
, p
]pt
that indicates whether pt
belongs to the area or not.
The semantics of area rules is as follows:
`Anz
, p
]pt
is true
iff the winding number of p
around pt
is non zero. To determine the winding number cast
a ray from pt
to infinity in any direction (just make sure
the ray doesn't intersect p
tangently or at a
singularity). Starting with zero add one for each intersection
with a counter-clockwise oriented segment of p
and substract
one for each clockwise ones. The resulting sum is the
winding number. This is usually refered to as the non-zero winding
rule and is the default for Vg.I.cut
.
`Aeo
, p
]pt
is true
iff the number of
intersections of p
with a ray cast from pt
to infinity in
any direction is odd (just make sure the ray doesn't intersect
p
tangently or at a singularity). This is usually refered
to as the even-odd rule.
`O o
, p
]pt
is true
iff pt
is in the outline
area of p
as defined by the value o
of type Vg.P.outline
.
The outline area of a path is the union of its subpaths
outline areas. A subpath outline area is inside the parallel
curves at a distance o.width / 2
of its path segments that
are joined accoring to the join style o.join
(see below) and
closed at the subpath end points with a cap style o.cap
(see
below). The outline area of a subpath can also be chopped at
regular intervals according to the o.dashes
parameter (see
below).
The shape of subpath segment jointures is specified in
o.join
by a value of type Vg.P.join
. From left to right:
`Miter
, the outer parallel curves are extended until they
meet unless the joining angle is smaller than
o.miter_angle
in which case the join is converted to a
bevel.`Round
, joins the outer parallel curves by a semicircle
centered at the end point with a diameter equal to o.width
.`Bevel
, joins the outer parallel curves by a segment.
The shape of subpath (or dashes) end points is specified in
o.cap
by a value of type Vg.P.cap
. From left to right:
`Butt
, end points are square and extend only to the
exact end point of the path.`Round
, end points are rounded by a semicircle at
the end point with a diameter equal to o.width
.`Square
, end points are square and extend by a distance
equal to half o.width
.
The path outline area can be chopped at regular intervals by
spefiying a value (off, pat)
of type Vg.P.dashes
in o.dashes
.
The dash pattern pat
is a list of lengths that specify
the length of alternating dashes and gaps (starting with
dashes). The dash offset off
is a positive offset
that indicates where to start in the dash pattern at the
beginning of a subpath.
Many examples of images and their source can be found in the
online version
of Vg
's test image database. Clicking on the title of an image brings
you to its definition.
The following examples show for each renderer the minimal code
needed to output an image. This code can also be found in the test
directory of the distribution.
The file min_pdf.ml
contains the following mostly self-explanatory
code. We first define an image and then render it. For the latter
step we define some meta-data for the image, a function to print
rendering warnings and then render the image on stdout.
open Gg
open Vg
(* 1. Define your image *)
let aspect = 1.618
let size = Size2.v (aspect *. 100.) 100. (* mm *)
let view = Box2.v P2.o (Size2.v aspect 1.)
let image = I.const (Color.v_srgb 0.314 0.784 0.471)
(* 2. Render *)
let () =
let title = "Vgr_pdf minimal example" in
let description = "Emerald Color" in
let xmp = Vgr.xmp ~title ~description () in
let warn w = Vgr.pp_warning Format.err_formatter w in
let r = Vgr.create ~warn (Vgr_pdf.target ~xmp ()) (`Channel stdout) in
ignore (Vgr.render r (`Image (size, view, image)));
ignore (Vgr.render r `End)
This can be compiled with:
> ocamlfind ocamlopt -package gg,vg,vg.pdf \
-linkpkg -o min_pdf.native min_pdf.ml
The file min_svg.ml
contains the following mostly self-explanatory
code. We first define an image and then render it. For the latter
step we define some meta-data for the image, a function to print
rendering warnings and then render the image on stdout.
open Gg
open Vg
(* 1. Define your image *)
let aspect = 1.618
let size = Size2.v (aspect *. 100.) 100. (* mm *)
let view = Box2.v P2.o (Size2.v aspect 1.)
let image = I.const (Color.v_srgb 0.314 0.784 0.471)
(* 2. Render *)
let () =
let title = "Vgr_svg minimal example" in
let description = "Emerald Color" in
let xmp = Vgr.xmp ~title ~description () in
let warn w = Vgr.pp_warning Format.err_formatter w in
let r = Vgr.create ~warn (Vgr_svg.target ~xmp ()) (`Channel stdout) in
ignore (Vgr.render r (`Image (size, view, image)));
ignore (Vgr.render r `End)
This can be compiled with:
> ocamlfind ocamlopt -package gg,vg,vg.svg \
-linkpkg -o min_svg.native min_svg.ml
The file min_htmlc.ml
contains the following code. Step by step we have:
a
that will parent the canvas.
This will allow to download a (usually PNG) file of the image.c
and add it as a child of a
.r
targeting the canvas c
.open Gg
open Vg
(* 1. Define your image *)
let aspect = 1.618
let size = Size2.v (aspect *. 100.) 100. (* mm *)
let view = Box2.v P2.o (Size2.v aspect 1.)
let image = I.const (Color.v_srgb 0.314 0.784 0.471)
(* Browser bureaucracy. *)
let main _ =
let d = Dom_html.window ## document in
let a = (* 2 *)
let a = Dom_html.createA d in
a ## title <- Js.string "Download PNG file";
a ## href <- Js.string "#";
a ## setAttribute (Js.string "download", Js.string "min_htmlc.png");
Dom.appendChild (d ## body) a; a
in
let c = (* 3 *)
let c = Dom_html.createCanvas d in
Dom.appendChild a c; c
in
let r = Vgr.create (Vgr_htmlc.target c) `Other in (* 4 *)
ignore (Vgr.render r (`Image (size, view, image))); (* 5 *)
ignore (Vgr.render r `End);
a ## href <- (c ## toDataURL ()); (* 6 *)
Js._false
let () = Dom_html.window ## onload <- Dom_html.handler main
This file needs to be compiled to byte code and then js_of_ocaml
must be applied. This can be achieved with:
> ocamlfind ocamlc \
-package js_of_ocaml,js_of_ocaml.syntax \
-package gg,vg,vg.htmlc \
-syntax camlp4o -linkpkg -o min_htmlc.byte min_htmlc.ml \
&& js_of_ocaml min_htmlc.byte
Finally we need a minimal HTML file that references our final
javascript min_htmlc.js
. The following one will do:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script type="text/javascript" defer="defer" src="min_htmlc.js"></script> <style type="text/css"> body { background-color: black; margin: 3em; }</style> <title>Vgr_htmlc minimal example</title> </head> <body> <noscript>Sorry, you need to enable JavaScript to see this page.</noscript> </body> </html>