Source file imageCanvas.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
open ImageBase
module type CanvasImage = sig
type t
val width : t -> int
val height : t -> int
val size : t -> int
val create : int -> int -> t
val blank : t -> unit
val copy : t -> t
val add : t -> ?x:int -> ?y:int -> t -> unit
val has_alpha : t -> bool
val fill_alpha : t -> int -> unit
val set_pixel_rgba : t -> int -> int -> Pixel.rgba -> unit
val randomize : t -> unit
val scale : t -> t -> unit
end
(** A canvas of images. The structure is immutable but its elements might be
returned and therefore should not be used in place. *)
module Canvas (I : CanvasImage) = struct
module Element = struct
type t = Image of (int * int) * I.t (** An image at given offset. *)
let size = function Image (_, img) -> I.size img
let translate dx dy = function
| Image ((x, y), img) -> Image ((x + dx, y + dy), img)
end
module E = Element
type t = { width : int; height : int; elements : E.t list }
let create width height = { width; height; elements = [] }
let width c = c.width
let height c = c.height
let size c = List.fold_left (fun n e -> n + E.size e) 0 c.elements
let planes c = List.length c.elements
let make ?width ?height ?(x = 0) ?(y = 0) image =
let width = Option.value ~default:(I.width image) width in
let height = Option.value ~default:(I.height image) height in
{ width; height; elements = [E.Image ((x, y), image)] }
let add c c' =
{
width = c'.width;
height = c'.height;
elements = c.elements @ c'.elements;
}
let covering c =
let width = width c in
let height = height c in
let covering_element = function
| E.Image ((x, y), img) ->
let w = I.width img in
let h = I.height img in
x <= 0 && y <= 0
&& x + w >= width
&& y + h >= height
&& not (I.has_alpha img)
in
List.exists covering_element c.elements
let render ?(fresh = false) ?(transparent = true) c =
assert (width c >= 0 && height c >= 0);
match c.elements with
| [Image ((0, 0), img)]
when (not fresh) && I.width img = width c && I.height img = height c ->
img
| elements ->
let r = I.create (width c) (height c) in
if not (covering c) then (
I.blank r;
if transparent then I.fill_alpha r 0);
let add = function E.Image ((x, y), img) -> I.add img ~x ~y r in
List.iter_right add elements;
r
let rendered ?transparent c = make (render ?transparent c)
let map f c = make (f (render c))
let iter f c =
let img = render ~fresh:true c in
f img;
make img
let translate dx dy c =
if dx = 0 && dy = 0 then c
else { c with elements = List.map (E.translate dx dy) c.elements }
let viewport ?(x = 0) ?(y = 0) width height c =
translate (-x) (-y) { c with width; height }
let bounding_box c =
let p = (width c, height c) in
let d = (0, 0) in
List.fold_left
(fun (p, d) -> function
| E.Image ((x, y), img) ->
(Point.min p (x, y), Point.max d (I.width img, I.height img)))
(p, d) c.elements
let scale ?(scaler = I.scale) (nx, dx) (ny, dy) c =
if nx = dx && ny = dy then c
else (
let elements =
List.map
(function
| E.Image ((x, y), img) ->
let scl =
I.create (I.width img * nx / dx) (I.height img * ny / dy)
in
scaler img scl;
E.Image ((x * nx / dx, y * ny / dy), scl))
c.elements
in
{ width = c.width; height = c.height; elements })
let resize ?(proportional = true) ?scaler w' h' img =
let w = width img in
let h = height img in
let (nx, dx), (ny, dy) =
if proportional then (
let f = Fraction.min (w', w) (h', h) in
(f, f))
else ((w', w), (h', h))
in
let x, y =
if proportional then ((w' - (w * nx / dx)) / 2, (h' - (h * ny / dy)) / 2)
else (0, 0)
in
scale ?scaler (nx, dx) (ny, dy) img |> translate x y |> viewport w' h'
module Draw = struct
let line color (x1, y1) (x2, y2) =
let dx = min x1 x2 in
let dy = min y1 y2 in
let w = abs (x2 - x1) in
let h = abs (y2 - y1) in
let buf = I.create w h in
I.blank buf;
I.fill_alpha buf 0;
Draw.line
(fun i j ->
if 0 <= i && i < w && 0 <= j && j < h then
I.set_pixel_rgba buf i j color)
(x1 - dx, y1 - dy)
(x2 - dx, y2 - dy);
make ~x:dx ~y:dy ~width:(-1) ~height:(-1) buf
end
end
module CanvasYUV420 = Canvas (struct
include ImageYUV420
let create w h = create w h
let scale = scale ~proportional:false
end)