1/* Various operators as functions.
2 */
3
4logical_and a b = a && b;
5logical_or a b = a || b;
6bitwise_and a b = a & b;
7bitwise_or a b = a | b;
8eor a b = a ^ b;
9left_shift a b = a << b;
10right_shift a b = a >> b;
11not a = !a;
12
13less a b = a < b;
14more a b = a > b;
15less_equal a b = a <= b;
16more_equal a b = a >= b;
17equal a b = a == b;
18not_equal a b = a != b;
19pointer_equal a b = a === b;
20not_pointer_equal a b = a !== b;
21
22add a b = a + b;
23subtract a b = a - b;
24multiply a b = a * b;
25divide a b = a / b;
26power a b = a ** b;
27square x = x * x;
28remainder a b = a % b;
29
30cons a b = a : b;
31join a b = a ++ b;
32subscript a b = a ? b;
33
34generate s n f = [s, n .. f];
35comma r i = (r, i);
36
37compose f g = f @ g;
38
39cast_unsigned_char x = (unsigned char) x;
40cast_signed_char x = (signed char) x;
41cast_unsigned_short x = (unsigned short) x;
42cast_signed_short x = (signed short) x;
43cast_unsigned_int x = (unsigned int) x;
44cast_signed_int x = (signed int) x;
45cast_float x = (float) x;
46cast_double x = (double) x;
47cast_complex x = (complex) x;
48cast_double_complex x = (double complex) x;
49
50unary_minus x = -x;
51negate x = !x;
52complement x = ~x;
53unary_plus x = +x;
54
55if_then_else a b c = if a then b else c;
56
57// the vector ops ... im is an image, vec is a real_list
58vec op_name im vec
59	= im_lintra_vec ones im vec,
60		op_name == "add" || op_name == "add'"
61	= im_lintra_vec ones (-1 * im) vec,
62		op_name == "subtract'"
63	= im_lintra_vec ones im inv,
64		op_name == "subtract"
65	= im_lintra_vec vec im zeros,
66		op_name == "multiply" || op_name == "multiply'"
67	= im_lintra_vec vec (1 / im) zeros,
68		op_name == "divide'"
69	= im_lintra_vec recip im zeros,
70		op_name == "divide"
71	= im_expntra_vec im vec,
72		op_name == "power'"
73	= im_powtra_vec im vec,
74		op_name == "power"
75	= im_remainderconst_vec im vec,
76		op_name == "remainder"
77	= im_andimage_vec im vec,
78		op_name == "bitwise_and" || op_name == "bitwise_and'"
79	= im_orimage_vec im vec,
80		op_name == "bitwise_or" || op_name == "bitwise_or'"
81	= im_eorimage_vec im vec,
82		op_name == "eor" || op_name == "eor'"
83	= im_equal_vec im vec,
84		op_name == "equal" || op_name == "equal'"
85	= im_notequal_vec im vec,
86		op_name == "not_equal" || op_name == "not_equal'"
87	= im_less_vec im vec,
88		op_name == "less"
89	= im_moreeq_vec im vec,
90		op_name == "less'"
91	= im_lesseq_vec im vec,
92		op_name == "less_equal"
93	= im_more_vec im vec,
94		op_name == "less_equal'"
95	= error "unimplemented vector operation"
96{
97	zeros = replicate (len vec) 0;
98	ones = replicate (len vec) 1;
99	recip = map (divide 1) vec;
100	inv = map (multiply (-1)) vec;
101}
102
103/* Macbeth chart patch names.
104 */
105_macbeth_names = [
106	"Dark skin",
107	"Light skin",
108	"Blue sky",
109	"Foliage",
110	"Blue flower",
111	"Bluish green",
112	"Orange",
113	"Purplish blue",
114	"Moderate red",
115	"Purple",
116	"Yellow green",
117	"Orange yellow",
118	"Blue",
119	"Green",
120	"Red",
121	"Yellow",
122	"Magenta",
123	"Cyan",
124	"White (density 0.05)",
125	"Neutral 8 (density 0.23)",
126	"Neutral 6.5 (density 0.44)",
127	"Neutral 5 (density 0.70)",
128	"Neutral 3.5 (density 1.05)",
129	"Black (density 1.50)"
130];
131
132bandsplit x
133	= oo_unary_function bandsplit_op x, is_class x
134	= map (subscript x) [0 .. bands - 1], is_image x
135	= error (_ "bad arguments to " ++ "bandsplit")
136{
137	bands = im_header_int "Bands" x;
138	bandsplit_op = Operator "bandsplit" (map Image @ bandsplit)
139		Operator_type.COMPOUND false;
140}
141
142bandjoin l
143	= wrapper joined,
144		has_wrapper
145	= joined, is_listof has_image l
146	= error (_ "bad arguments to " ++ "bandjoin")
147{
148	has_wrapper = has_member_list (has_member "Image") l;
149	wrapper = get_member_list (has_member "Image") (get_member "Image") l;
150	joined = im_gbandjoin (map get_image l);
151}
152
153mean x
154	= oo_unary_function mean_op x, is_class x
155	= im_avg x, is_image x
156	= mean_list x, is_real_list x || is_matrix x
157	= error (_ "bad arguments to " ++ "mean")
158{
159	mean_op = Operator "mean" mean_object Operator_type.COMPOUND false;
160
161	mean_object x
162		= im_avg x, is_image x
163		= mean_list x, is_real_list x || is_matrix x
164		= error (_ "bad arguments to " ++ "mean");
165
166	mean_list l
167		= s / n
168	{
169		totals = sum l;
170		n = totals?0;
171		s = totals?1;
172	}
173
174	// return [n, sum] for a list of numbers, or a list of list of num
175	// etc.
176	sum x
177		= foldr accumulate [0, 0] x
178	{
179		accumulate x sofar
180			= [n + 1, x + s], is_real x
181			= [n + n', s + s'], is_list x
182			= error "mean_list: not real or [real]"
183		{
184			n = sofar?0;
185			s = sofar?1;
186
187			sub_acc = sum x;
188
189			n' = sub_acc?0;
190			s' = sub_acc?1;
191		}
192	}
193}
194
195deviation x
196	= oo_unary_function deviation_op x, is_class x
197	= im_deviate x, is_image x
198	= deviation_list x, is_real_list x || is_matrix x
199	= error (_ "bad arguments to " ++ "deviation")
200{
201	deviation_op = Operator "deviation"
202		deviation_object Operator_type.COMPOUND false;
203
204	deviation_object x
205		= im_deviate x, is_image x
206		= deviation_list x, is_real_list x || is_matrix x
207		= error (_ "bad arguments to " ++ "deviation");
208
209	deviation_list l
210		= (abs (s2 - (s * s / n)) / (n - 1)) ** 0.5
211	{
212		totals = sum_sum2_list l;
213		n = totals?0;
214		s = totals?1;
215		s2 = totals?2;
216	}
217
218	// return n, sum, sum of squares for a list of reals
219	sum_sum2_list x
220		= foldr accumulate [0, 0, 0] x
221	{
222		accumulate x sofar
223			= [n + 1, x + s, x * x + s2], is_real x
224			= [n + n', s + s', s2 + s2'], is_list x
225			= error "sum_sum2_list: not real or [real]"
226		{
227			n = sofar?0;
228			s = sofar?1;
229			s2 = sofar?2;
230
231			sub_acc = sum_sum2_list x;
232
233			n' = sub_acc?0;
234			s' = sub_acc?1;
235			s2' = sub_acc?2;
236		}
237	}
238}
239
240abs x
241	= oo_unary_function abs_op x, is_class x
242	= im_abs x, is_image x
243	= abs_cmplx x, is_complex x
244	= abs_num x, is_real x
245	= error (_ "bad arguments to " ++ "abs")
246{
247	abs_op = Operator "abs" abs_object Operator_type.COMPOUND false;
248
249	abs_object x
250		= im_abs x, is_image x
251		= abs_cmplx x, is_complex x
252		= abs_num x, is_real x
253		= abs_list x, is_real_list x
254		= abs_list (map abs_list x), is_matrix x
255		= error (_ "bad arguments to " ++ "abs");
256
257	abs_list l = (foldr1 add (map square l)) ** 0.5;
258
259	abs_num n
260		= n, n >= 0
261		= -n;
262
263	abs_cmplx c = ((re c)**2 + (im c)**2) ** 0.5;
264}
265
266copy x
267	= oo_unary_function copy_op x, is_class x
268	= im_copy x, is_image x
269	= x
270{
271	copy_op = Operator "copy" copy Operator_type.COMPOUND_REWRAP false;
272}
273
274// like abs, but treat pixels as vectors ... ie. always get a 1-band image
275// back ... also treat matricies as lists of vectors
276// handy for dE from object difference
277abs_vec x
278	= oo_unary_function abs_vec_op x, is_class x
279	= abs_vec_image x, is_image x
280	= abs_vec_cmplx x, is_complex x
281	= abs_vec_num x, is_real x
282	= error (_ "bad arguments to " ++ "abs_vec")
283{
284	abs_vec_op = Operator "abs_vec"
285		abs_vec_object Operator_type.COMPOUND false;
286
287	abs_vec_object x
288		= abs_vec_image x, is_image x
289		= abs_vec_cmplx x, is_complex x
290		= abs_vec_num x, is_real x
291		= abs_vec_list x, is_real_list x
292		= mean (Vector (map abs_vec_list x)), is_matrix x
293		= error (_ "bad arguments to " ++ "abs_vec");
294
295	abs_vec_list l = (foldr1 add (map square l)) ** 0.5;
296
297	abs_vec_num n
298		= n, n >= 0
299		= -n;
300
301	abs_vec_cmplx c = ((re c)**2 + (im c)**2) ** 0.5;
302
303	abs_vec_image im
304		= (foldr1 add (map square (bandsplit im))) ** 0.5;
305}
306
307transpose x
308	= oo_unary_function transpose_op x, is_class x
309	= transpose_image x, is_image x
310	= transpose_matrix x, is_list x && is_list (hd x)
311	= error (_ "bad arguments to " ++ "transpose")
312{
313	transpose_op = Operator "transpose"
314		transpose_object Operator_type.COMPOUND_REWRAP false;
315
316	transpose_object x
317		= transpose_matrix x, is_matrix x
318		= transpose_image x, is_image x
319		= error (_ "bad arguments to " ++ "transpose");
320
321	transpose_matrix l
322		= [], l' == []
323		= (map hd l') : (transpose_matrix (map tl l'))
324	{
325		l' = takewhile (not_equal []) l;
326	}
327
328	transpose_image = im_flipver @ im_rot270;
329}
330
331rot45 x
332	= oo_unary_function rot45_op x, is_class x
333	= error "rot45 image: not implemented", is_image x
334	= error (_ "bad arguments to " ++ "rot45")
335{
336	rot45_op = Operator "rot45"
337		rot45_object Operator_type.COMPOUND_REWRAP false;
338
339	rot45_object x
340		= rot45_matrix x, is_odd_square_matrix x
341		= error "rot45 image: not implemented", is_image x
342		= error (_ "bad arguments to " ++ "rot45");
343
344	// slow, but what the heck
345	rot45_matrix l = (im_rotate_dmask45 (Matrix l)).value;
346}
347
348// apply an image function to a [[real]] ... matrix is converted to a 1 band
349// image for processing
350apply_matrix_as_image fn m
351	= (get_value @ im_vips2mask @ fn @ im_mask2vips @ Matrix) m;
352
353rot90 x
354	= oo_unary_function rot90_op x, is_class x
355	= im_rot90 x, is_image x
356	= error (_ "bad arguments to " ++ "rot90")
357{
358	rot90_op = Operator "rot90"
359		rot90_object Operator_type.COMPOUND_REWRAP false;
360
361	rot90_object x
362		= rot90_matrix x, is_matrix x
363		= im_rot90 x, is_image x
364		= error (_ "bad arguments to " ++ "rot90");
365
366	// slow, but what the heck
367	// avoid im_rotate_dmask90(), it can only do square odd-sided matricies
368	rot90_matrix l = apply_matrix_as_image im_rot90 l;
369}
370
371rot180 x
372	= oo_unary_function rot180_op x, is_class x
373	= im_rot180 x, is_image x
374	= error (_ "bad arguments to " ++ "rot180")
375{
376	rot180_op = Operator "rot180"
377		rot180_object Operator_type.COMPOUND_REWRAP false;
378
379	rot180_object x
380		= rot180_matrix x, is_matrix x
381		= im_rot180 x, is_image x
382		= error (_ "bad arguments to " ++ "rot180");
383
384	// slow, but what the heck
385	rot180_matrix l = apply_matrix_as_image im_rot180 l;
386}
387
388rot270 x
389	= oo_unary_function rot270_op x, is_class x
390	= im_rot270 x, is_image x
391	= error (_ "bad arguments to " ++ "rot270")
392{
393	rot270_op = Operator "rot270"
394		rot270_object Operator_type.COMPOUND_REWRAP false;
395
396	rot270_object x
397		= rot270_matrix x, is_matrix x
398		= im_rot270 x, is_image x
399		= error (_ "bad arguments to " ++ "rot270");
400
401	// slow, but what the heck
402	rot270_matrix l = apply_matrix_as_image im_rot270 l;
403}
404
405image_set_type type x
406	= oo_unary_function image_set_type_op x, is_class x
407	= im_copy_set x (to_real type)
408		(im_header_double "Xres" x) (im_header_double "Yres" x)
409		(im_header_int "Xoffset" x) (im_header_int "Yoffset" x),
410		is_image x
411	= error (_ "bad arguments to " ++ "image_set_type:" ++
412		print type ++ " " ++ print x)
413{
414	image_set_type_op = Operator "image_set_type"
415		(image_set_type type) Operator_type.COMPOUND_REWRAP false;
416}
417
418image_set_origin xoff yoff x
419	= oo_unary_function image_set_origin_op x, is_class x
420	= im_copy_set x
421		(im_header_int "Type" x)
422		(im_header_double "Xres" x) (im_header_double "Yres" x)
423		(to_real xoff) (to_real yoff),
424		is_image x
425	= error (_ "bad arguments to " ++ "image_set_origin")
426{
427	image_set_origin_op = Operator "image_set_origin"
428		(image_set_origin xoff yoff)
429		Operator_type.COMPOUND_REWRAP false;
430}
431
432rotquad x
433	= oo_unary_function rotquad_op x, is_class x
434	= im_rotquad x, is_image x
435	= error (_ "bad arguments to " ++ "rotquad")
436{
437	rotquad_op = Operator "rotquad"
438		rotquad_object Operator_type.COMPOUND_REWRAP false;
439
440	rotquad_object x
441		= rotquad_matrix x, is_matrix x
442		= im_rotquad x, is_image x
443		= error (_ "bad arguments to " ++ "rotquad");
444
445	rotquad_matrix l = apply_matrix_as_image im_rotquad l;
446}
447
448cache tile_width tile_height max_tiles x
449	= oo_unary_function cache_op x, is_class x
450	= im_cache x (to_real tile_width) (to_real tile_height)
451		(to_real max_tiles), is_image x
452	= error (_ "bad arguments to " ++ "cache")
453{
454	cache_op = Operator "cache"
455		(cache tile_width tile_height max_tiles)
456		Operator_type.COMPOUND_REWRAP false;
457}
458
459tile across down x
460	= oo_unary_function tile_op x, is_class x
461	= im_replicate x (to_real across) (to_real down), is_image x
462	= error (_ "bad arguments to " ++ "tile")
463{
464	tile_op = Operator "tile"
465		(tile across down) Operator_type.COMPOUND_REWRAP false;
466}
467
468fliptb x
469	= oo_unary_function fliptb_op x, is_class x
470	= im_flipver x, is_image x
471	= error (_ "bad arguments to " ++ "fliptb")
472{
473	fliptb_op = Operator "fliptb"
474		fliptb_object Operator_type.COMPOUND_REWRAP false;
475
476	fliptb_object x
477		= fliptb_matrix x, is_matrix x
478		= im_flipver x, is_image x
479		= error (_ "bad arguments to " ++ "fliptb");
480
481	fliptb_matrix l = reverse l;
482}
483
484fliplr x
485	= oo_unary_function fliplr_op x, is_class x
486	= im_fliphor x, is_image x
487	= error (_ "bad arguments to " ++ "fliplr")
488{
489	fliplr_op = Operator "fliplr"
490		fliplr_object Operator_type.COMPOUND_REWRAP false;
491
492	fliplr_object x
493		= fliplr_matrix x, is_matrix x
494		= im_fliphor x, is_image x
495		= error (_ "bad arguments to " ++ "fliplr");
496
497	fliplr_matrix l = map reverse l;
498}
499
500max_pair a b
501	= a, a > b
502	= b;
503
504min_pair a b
505      =	a, a < b
506      =	b;
507
508range min value max = min_pair max (max_pair min value);
509
510max x
511	= oo_unary_function max_op x, is_class x
512	= im_max x, is_image x
513	= max_list x, is_real_list x || is_matrix x
514	= x, is_number x
515	= error (_ "bad arguments to " ++ "max")
516{
517	max_op = Operator "max" max Operator_type.COMPOUND false;
518
519	max_list x
520		= foldr1 max_pair x, is_real_list x
521		= foldr1 max_pair (map max_list x), is_matrix x
522		= max x;
523}
524
525min x
526	= oo_unary_function min_op x, is_class x
527	= im_min x, is_image x
528	= min_list x, is_real_list x || is_matrix x
529	= x, is_number x
530	= error (_ "bad arguments to " ++ "min")
531{
532	min_op = Operator "min" min Operator_type.COMPOUND false;
533
534	min_list x
535		= foldr1 min_pair x, is_real_list x
536		= foldr1 min_pair (map min_list x), is_matrix x
537		= min x;
538}
539
540maxpos x
541	= oo_unary_function maxpos_op x, is_class x
542	= im_maxpos x, is_image x
543	= maxpos_matrix x, is_matrix x
544	= error (_ "bad arguments to " ++ "maxpos")
545{
546	maxpos_op = Operator "maxpos" maxpos Operator_type.COMPOUND false;
547
548	maxpos_matrix m
549		= (indexes?row, row)
550	{
551		max_value = max (Matrix m);
552		indexes = map (index (equal max_value)) m;
553		row = index (not_equal (-1)) indexes;
554	}
555}
556
557minpos x
558	= oo_unary_function minpos_op x, is_class x
559	= im_minpos x, is_image x
560	= minpos_matrix x, is_matrix x
561	= error (_ "bad arguments to " ++ "minpos")
562{
563	minpos_op = Operator "minpos" minpos Operator_type.COMPOUND false;
564
565	minpos_matrix m
566		= (indexes?row, row)
567	{
568		min_value = min (Matrix m);
569		indexes = map (index (equal min_value)) m;
570		row = index (not_equal (-1)) indexes;
571	}
572}
573
574stats x
575	= oo_unary_function stats_op x, is_class x
576	= im_stats x, is_image x
577	= im_stats (to_image x).value, is_matrix x
578	= error (_ "bad arguments to " ++ "stats")
579{
580	stats_op = Operator "stats"
581		stats Operator_type.COMPOUND false;
582}
583
584e = 2.7182818284590452354;
585
586pi = 3.14159265358979323846;
587
588rad d = 2 * pi * (d / 360);
589
590deg r = 360 * r / (2 * pi);
591
592sign x
593	= oo_unary_function sign_op x, is_class x
594	= im_sign x, is_image x
595	= sign_cmplx x, is_complex x
596	= sign_num x, is_real x
597	= error (_ "bad arguments to " ++ "sign")
598{
599	sign_op = Operator "sign" sign Operator_type.COMPOUND_REWRAP false;
600
601	sign_num n
602		= 0, n == 0
603		= 1, n > 0
604		= -1;
605
606	sign_cmplx c
607		= (0, 0), mod == 0
608		= (re c / mod, im c / mod)
609	{
610		mod = abs c;
611	}
612}
613
614rint x
615	= oo_unary_function rint_op x, is_class x
616	= im_rint x, is_image x
617	= rint_value x, is_number x
618	= error (_ "bad arguments to " ++ "rint")
619{
620	rint_op = Operator "rint" rint Operator_type.ARITHMETIC false;
621
622	rint_value x
623		= (int) (x + 0.5), x > 0
624		= (int) (x - 0.5);
625}
626
627scale x
628	= oo_unary_function scale_op x, is_class x
629	= (unsigned char) x, is_number x
630	= im_scale x, is_image x
631	= scale_list x, is_real_list x || is_matrix x
632	= error (_ "bad arguments to " ++ "scale")
633{
634	scale_op = Operator "scale" scale Operator_type.COMPOUND_REWRAP false;
635
636	scale_list l
637		= apply_scale s o l
638	{
639		mn = find_limit min_pair l;
640		mx = find_limit max_pair l;
641		s = 255.0 / (mx - mn);
642		o = -(mn * s);
643	}
644
645	find_limit fn l
646		= find_limit fn (map (find_limit fn) l), is_listof is_list l
647		= foldr1 fn l;
648
649	apply_scale s o x
650		= x * s + o, is_number x
651		= map (apply_scale s o) x;
652}
653
654scaleps x
655	= oo_unary_function scale_op x, is_class x
656	= im_scaleps x, is_image x
657	= error (_ "bad arguments to " ++ "scale")
658{
659	scale_op = Operator "scaleps"
660		scaleps Operator_type.COMPOUND_REWRAP false;
661}
662
663fwfft x
664	= oo_unary_function fwfft_op x, is_class x
665	= im_fwfft x, is_image x
666	= error (_ "bad arguments to " ++ "fwfft")
667{
668	fwfft_op = Operator "fwfft"
669		fwfft Operator_type.COMPOUND_REWRAP false;
670}
671
672invfft x
673	= oo_unary_function invfft_op x, is_class x
674	= im_invfftr x, is_image x
675	= error (_ "bad arguments to " ++ "invfft")
676{
677	invfft_op = Operator "invfft"
678		invfft Operator_type.COMPOUND_REWRAP false;
679}
680
681falsecolour x
682	= oo_unary_function falsecolour_op x, is_class x
683	= image_set_type Image_type.sRGB (im_falsecolour x), is_image x
684	= error (_ "bad arguments to " ++ "falsecolour")
685{
686	falsecolour_op = Operator "falsecolour"
687		falsecolour Operator_type.COMPOUND_REWRAP false;
688}
689
690polar x
691	= oo_unary_function polar_op x, is_class x
692	= im_c2amph x, is_image x
693	= polar_cmplx x, is_complex x
694	= error (_ "bad arguments to " ++ "polar")
695{
696	polar_op = Operator "polar" polar Operator_type.COMPOUND false;
697
698	polar_cmplx r
699		= (l, a)
700	{
701		a
702			= 270, x == 0 && y < 0
703			= 90, x == 0 && y >= 0
704			= 360 + atan (y / x), x > 0 && y < 0
705			= atan (y / x), x > 0 && y >= 0
706			= 180 + atan (y / x);
707
708		l = (x ** 2 + y ** 2) ** 0.5;
709
710		x = re r;
711		y = im r;
712	}
713}
714
715rectangular x
716	= oo_unary_function rectangular_op x, is_class x
717	= im_c2rect x, is_image x
718	= rectangular_cmplx x, is_complex x
719	= error (_ "bad arguments to " ++ "rectangular")
720{
721	rectangular_op = Operator "rectangular"
722		rectangular Operator_type.COMPOUND false;
723
724	rectangular_cmplx p
725		= (x, y)
726	{
727		l = re p;
728		a = im p;
729
730		x = l * cos a;
731		y = l * sin a;
732	}
733}
734
735recomb matrix image
736	= colour_unary recomb_op image
737{
738	recomb_op x
739		= im_recomb x (to_matrix matrix), is_image x
740		= error (_ "bad arguments to " ++ "recomb");
741}
742
743extract_area x y w h obj
744	= oo_unary_function extract_area_op obj, is_class obj
745	= im_extract_area obj x' y' w' h', is_image obj
746	= map (extract_range x' w') (extract_range y' h' obj), is_matrix obj
747	= error (_ "bad arguments to " ++ "extract_area")
748{
749	x' = to_real x;
750	y' = to_real y;
751	w' = to_real w;
752	h' = to_real h;
753
754	extract_area_op = Operator "extract_area" (extract_area x y w h)
755		Operator_type.COMPOUND_REWRAP false;
756
757	extract_range from length list
758		= (take length @ drop from) list;
759}
760
761extract_band b obj = subscript obj b;
762
763extract_row y obj
764	= oo_unary_function extract_row_op obj, is_class obj
765	= extract_area 0 y' (get_width obj) 1 obj, is_image obj
766	= [obj?y'], is_matrix obj
767	= error (_ "bad arguments to " ++ "extract_row")
768{
769	y' = to_real y;
770
771	extract_row_op = Operator "extract_row" (extract_row y)
772		Operator_type.COMPOUND_REWRAP false;
773}
774
775extract_column x obj
776	= oo_unary_function extract_column_op obj, is_class obj
777	= extract_area x' 0 1 height obj, is_image obj
778	= map (converse cons [] @ converse subscript x') obj, is_matrix obj
779	= error (_ "bad arguments to " ++ "extract_column")
780{
781	x' = to_real x;
782	height = im_header_int "Ysize" obj;
783
784	extract_column_op = Operator "extract_column" (extract_column x)
785		Operator_type.COMPOUND_REWRAP false;
786}
787
788blend cond in1 in2
789	= oo_binary_function blend_op cond [in1,in2], is_class cond
790	= im_blend (get_image cond) (get_image in1) (get_image in2),
791		has_image cond && has_image in1 && has_image in2
792	= error (_ "bad arguments to " ++ "blend")
793{
794	blend_op = Operator "blend"
795		blend_obj Operator_type.COMPOUND_REWRAP false;
796
797	blend_obj cond x
798		= blend_result_image
799	{
800		then_part = x?0;
801		else_part = x?1;
802
803		// get things about our output from inputs in this order
804		objects = [then_part, else_part, cond];
805
806		// properties of our output image
807		target_width = get_member_list has_width get_width objects;
808		target_height = get_member_list has_height get_height objects;
809		target_bands = get_member_list has_bands get_bands objects;
810		target_format = get_member_list has_format get_format objects;
811		target_type = get_member_list has_type get_type objects;
812
813		to_image x
814			= x, is_image x
815			= x.value, is_Image x
816			= black + x
817		{
818			black = im_black target_width target_height target_bands;
819		}
820
821		then_image = to_image then_part;
822		else_image = to_image else_part;
823
824		then_image' = clip2fmt target_format then_image;
825		else_image' = clip2fmt target_format else_image;
826
827		resized = size_alike [cond, then_image', else_image'];
828
829		blend_result_image = image_set_type target_type
830			(im_blend resized?0 resized?1 resized?2);
831	}
832}
833
834insert x y small big
835	= oo_binary_function insert_op small big, is_class small
836	= oo_binary'_function insert_op small big, is_class big
837	= im_insert big small (to_real x) (to_real y),
838		is_image small && is_image big
839	= error (_ "bad arguments to " ++ "insert")
840{
841	insert_op = Operator "insert"
842		(insert x y) Operator_type.COMPOUND_REWRAP false;
843}
844
845insert_noexpand x y small big
846	= oo_binary_function insert_noexpand_op small big, is_class small
847	= oo_binary'_function insert_noexpand_op small big, is_class big
848	= im_insert_noexpand big small (to_real x) (to_real y),
849		is_image small && is_image big
850	= error (_ "bad arguments to " ++ "insert_noexpand")
851{
852	insert_noexpand_op = Operator "insert_noexpand"
853		(insert_noexpand x y) Operator_type.COMPOUND_REWRAP false;
854}
855
856measure x y w h u v image
857	= oo_unary_function measure_op image, is_class image
858	= im_measure image
859		(to_real x) (to_real y) (to_real w) (to_real h)
860		(to_real u) (to_real v),
861			is_image image
862	= error (_ "bad arguments to " ++ "measure")
863{
864	measure_op = Operator "measure"
865		(measure x y w h u v) Operator_type.COMPOUND_REWRAP false;
866}
867
868extract_bands b n obj
869	= oo_unary_function extract_bands_op obj, is_class obj
870	= im_extract_bands obj (to_real b) (to_real n), is_image obj
871	= error (_ "bad arguments to " ++ "extract_bands")
872{
873	extract_bands_op = Operator "extract_bands"
874		(extract_bands b n) Operator_type.COMPOUND_REWRAP false;
875}
876
877transform ipol wrap params image
878	= oo_unary_function transform_op image, is_class image
879	= im_transform image
880		(to_matrix params) (to_real ipol) (to_real wrap), is_image image
881	= error (_ "bad arguments to " ++ "transform")
882{
883	transform_op = Operator "transform"
884		(transform ipol wrap params)
885		Operator_type.COMPOUND_REWRAP false;
886}
887
888transform_search max_error max_iterations order ipol wrap sample reference
889	= oo_binary_function transform_search_op sample reference, is_class sample
890	= oo_binary'_function transform_search_op sample reference,
891		is_class reference
892	= im_transform_search sample reference
893		(to_real max_error) (to_real max_iterations) (to_real order)
894		(to_real ipol) (to_real wrap),
895			is_image sample && is_image reference
896	= error (_ "bad arguments to " ++ "transform_search")
897{
898	transform_search_op = Operator "transform_search"
899		(transform_search max_error max_iterations order ipol wrap)
900		Operator_type.COMPOUND false;
901}
902
903rotate angle image
904	= oo_binary_function rotate_op angle image, is_class angle
905	= oo_binary'_function rotate_op angle image, is_class image
906	= im_similarity image (cos angle) (sin angle) 0 0,
907		is_real angle && is_image image
908	= error (_ "bad arguments to " ++ "rotate")
909{
910	rotate_op = Operator "rotate"
911		rotate Operator_type.COMPOUND_REWRAP false;
912}
913
914conj x
915	= oo_unary_function conj_op x, is_class x
916	= (re x, -im x),
917		is_complex x ||
918		(is_image x && format == Image_format.COMPLEX) ||
919		(is_image x && format == Image_format.DPCOMPLEX)
920	// assume it's some sort of real
921	= x
922{
923	format = im_header_int "BandFmt" x;
924	conj_op = Operator "conj" conj Operator_type.COMPOUND false;
925}
926
927clip2fmt format image
928	= oo_unary_function clip2fmt_op image, is_class image
929	= im_clip2fmt image (to_real format), is_image image
930	= error (_ "bad arguments to " ++ "clip2fmt")
931{
932	clip2fmt_op = Operator "clip2fmt"
933		(clip2fmt format) Operator_type.COMPOUND_REWRAP false;
934}
935
936embed type x y w h im
937	= oo_unary_function embed_op im, is_class im
938	= im_embed im (to_real type)
939		(to_real x) (to_real y) (to_real w) (to_real h), is_image im
940	= error (_ "bad arguments to " ++ "embed")
941{
942	embed_op = Operator "embed"
943		(embed type x y w h) Operator_type.COMPOUND_REWRAP false;
944}
945
946/* Morph a mask with a [[real]] matrix ... turn m2 into an image, morph it
947 * with m1, turn it back to a matrix again.
948 */
949_morph_2_masks fn m1 m2
950	= m''
951{
952	image = (unsigned char) im_mask2vips (Matrix m2);
953	m2_width = get_width image;
954	m2_height = get_height image;
955
956	// need to embed m2 in an image large enough for us to be able to
957	// position m1 all around the edges, with a 1 pixel overlap
958	image' = embed 0
959		(m1.width / 2) (m1.height / 2)
960		(m2_width + (m1.width - 1)) (m2_height + (m1.height - 1))
961		image;
962
963	// morph!
964	image'' = fn m1 image';
965
966	// back to mask
967	m' = im_vips2mask ((double) image'');
968
969	// Turn 0 in output to 128 (don't care).
970	m''
971		= map (map fn) m'.value
972	{
973		fn a
974			= 128, a == 0;
975			= a;
976	}
977}
978
979dilate mask image
980	= oo_unary_function dilate_op image, is_class image
981	= im_dilate image (to_matrix mask), is_image image
982	= error (_ "bad arguments to " ++ "dilate")
983{
984	dilate_op = Operator "dilate"
985		dilate_object Operator_type.COMPOUND_REWRAP false;
986
987	dilate_object x
988		= _morph_2_masks dilate mask x, is_matrix x
989		= dilate mask x;
990}
991
992erode mask image
993	= oo_unary_function erode_op image, is_class image
994	= im_erode image (to_matrix mask), is_image image
995	= error (_ "bad arguments to " ++ "erode")
996{
997	erode_op = Operator "erode"
998		erode_object Operator_type.COMPOUND_REWRAP false;
999
1000	erode_object x
1001		= _morph_2_masks erode mask x, is_matrix x
1002		= erode mask x;
1003}
1004
1005conv mask image
1006	= oo_unary_function conv_op image, is_class image
1007	= im_conv image (to_matrix mask), is_image image
1008	= error (_ "bad arguments to " ++ "conv")
1009{
1010	conv_op = Operator "conv"
1011		(conv mask) Operator_type.COMPOUND_REWRAP false;
1012}
1013
1014convsep mask image
1015	= oo_unary_function convsep_op image, is_class image
1016	= im_convsep image (to_matrix mask), is_image image
1017	= error (_ "bad arguments to " ++ "convsep")
1018{
1019	convsep_op = Operator "convsep"
1020		(convsep mask) Operator_type.COMPOUND_REWRAP false;
1021}
1022
1023rank w h n image
1024	= oo_unary_function rank_op image, is_class image
1025	= im_rank image (to_real w) (to_real h) (to_real n), is_image image
1026	= error (_ "bad arguments to " ++ "rank")
1027{
1028	rank_op = Operator "rank"
1029		(rank w h n) Operator_type.COMPOUND_REWRAP false;
1030}
1031
1032rank_image n x
1033	// work for groups too (convenient)
1034	= rlist x.value, is_Group x
1035	= rlist x, is_list x
1036	= error (_ "bad arguments to " ++ "rank_image")
1037{
1038	rlist l
1039		= wrapper ranked, has_wrapper
1040		= ranked
1041	{
1042		has_wrapper = has_member_list (has_member "Image") l;
1043		wrapper = get_member_list (has_member "Image") (get_member "Image") l;
1044		ranked = im_rank_image (map get_image l) (to_real n);
1045	}
1046}
1047
1048hist_find image
1049	= oo_unary_function hist_find_op image, is_class image
1050	= im_histgr image (-1), is_image image
1051	= error (_ "bad arguments to " ++ "hist_find")
1052{
1053	hist_find_op = Operator "hist_find"
1054		hist_find Operator_type.COMPOUND_REWRAP false;
1055}
1056
1057hist_find_nD bins image
1058	= oo_unary_function hist_find_nD_op image, is_class image
1059	= im_histnD image (to_real bins), is_image image
1060	= error (_ "bad arguments to " ++ "hist_find_nD")
1061{
1062	hist_find_nD_op = Operator "hist_find_nD"
1063		(hist_find_nD bins) Operator_type.COMPOUND_REWRAP false;
1064}
1065
1066hist_map hist image
1067	= oo_binary_function hist_map_op hist image, is_class hist
1068	= oo_binary'_function hist_map_op hist image, is_class image
1069	= im_maplut image hist, is_image hist && is_image image
1070	= error (_ "bad arguments to " ++ "hist_map")
1071{
1072	hist_map_op = Operator "hist_map"
1073		hist_map Operator_type.COMPOUND_REWRAP false;
1074}
1075
1076hist_cum hist
1077	= oo_unary_function hist_cum_op hist, is_class hist
1078	= im_histcum hist, is_image hist
1079	= error (_ "bad arguments to " ++ "hist_cum")
1080{
1081	hist_cum_op = Operator "hist_cum"
1082		hist_cum Operator_type.COMPOUND_REWRAP false;
1083}
1084
1085hist_norm hist
1086	= oo_unary_function hist_norm_op hist, is_class hist
1087	= im_histnorm hist, is_image hist
1088	= error (_ "bad arguments to " ++ "hist_norm")
1089{
1090	hist_norm_op = Operator "hist_norm"
1091		hist_norm Operator_type.COMPOUND_REWRAP false;
1092}
1093
1094hist_match in ref
1095	= oo_binary_function hist_match_op in ref, is_class in
1096	= oo_binary'_function hist_match_op in ref, is_class ref
1097	= im_histspec in ref, is_image in && is_image ref
1098	= error (_ "bad arguments to " ++ "hist_match")
1099{
1100	hist_match_op = Operator "hist_match"
1101		hist_match Operator_type.COMPOUND_REWRAP false;
1102}
1103
1104hist_equalize x = hist_map ((hist_norm @ hist_cum @ hist_find) x) x;
1105
1106hist_equalize_local w h image
1107	= oo_unary_function hist_equalize_local_op image, is_class image
1108	= lhisteq image, is_image image
1109	= error (_ "bad arguments to " ++ "hist_equalize_local")
1110{
1111	hist_equalize_local_op = Operator "hist_equalize_local"
1112		(hist_equalize_local w h) Operator_type.COMPOUND_REWRAP false;
1113
1114	// loop over bands, if necessary
1115	lhisteq im
1116		= im_lhisteq im (to_real w) (to_real h), get_bands im == 1
1117		= (foldl1 join @ map lhisteq @ bandsplit) im;
1118}
1119
1120// find the threshold below which are percent of the image (percent in [0,1])
1121// eg. hist_thresh 0.1 x == 12, then x < 12 will light up 10% of the pixels
1122hist_thresh percent image
1123	= x
1124{
1125	// our own normaliser ... we don't want to norm channels separately
1126	// norm to [0,1]
1127	my_hist_norm h = h / max h;
1128	sum = foldr1 add;
1129
1130	// normalised cumulative hist
1131	// we sum the channels before we normalise, because we want to treat them
1132	// all the same
1133	h = (my_hist_norm @ sum @ bandsplit @ hist_cum @ hist_find)
1134		image.value;
1135
1136	// threshold that, then use im_profile to search for the x position in the
1137	// histogram
1138	x = mean (im_profile (h > percent) 1);
1139}
1140
1141resize xfac yfac interp image
1142	= oo_unary_function resize_op image, is_class image
1143	= resize_im image, is_image image
1144	= error (_ "bad arguments to " ++ "resize")
1145{
1146	resize_op = Operator "resize"
1147		resize_im Operator_type.COMPOUND_REWRAP false;
1148
1149	xfac' = to_real xfac;
1150	yfac' = to_real yfac;
1151
1152	rxfac' = 1 / xfac';
1153	ryfac' = 1 / yfac';
1154
1155	resize_im im
1156		// upscale by integer factor, nearest neighbour
1157		= im_zoom im xfac' yfac',
1158			is_int xfac' && is_int yfac' &&
1159			xfac' >= 1 && yfac' >= 1 &&
1160			interp == Interpolate.NEAREST_NEIGHBOUR
1161
1162		// downscale by integer factor, nearest neighbour
1163		= im_subsample im rxfac' ryfac',
1164			is_int rxfac' && is_int ryfac' &&
1165			rxfac' >= 1 && ryfac' >= 1 &&
1166			interp == Interpolate.NEAREST_NEIGHBOUR
1167
1168		// upscale by any factor, nearest neighbour
1169		// can't really do this right ... upscale by integer part, then
1170		// bilinear to exact size
1171		= scale (break xfac')?1 (break yfac')?1
1172			(im_zoom im (break xfac')?0 (break yfac')?0),
1173			xfac' >= 1 && yfac' >= 1 &&
1174			interp == Interpolate.NEAREST_NEIGHBOUR
1175
1176		// downscale by any factor, nearest neighbour
1177		// can't really do this right ... downscale by integer part,
1178		// then bilinear to exact size
1179		= scale (1 / (break rxfac')?1) (1 / (break ryfac')?1)
1180			(im_subsample im (break rxfac')?0 (break ryfac')?0),
1181			rxfac' >= 1 && ryfac' >= 1 &&
1182			interp == Interpolate.NEAREST_NEIGHBOUR
1183
1184		// upscale by any factor, bilinear
1185		= scale xfac' yfac' im,
1186			xfac' >= 1 && yfac' >= 1 &&
1187			interp == Interpolate.BILINEAR
1188
1189		// downscale by any factor, bilinear
1190		// block shrink by integer factor, then bilinear resample to
1191		// exact
1192		= scale (1 / (break rxfac')?1) (1 / (break ryfac')?1)
1193			(im_shrink im (break rxfac')?0 (break ryfac')?0),
1194			rxfac' >= 1 && ryfac' >= 1 &&
1195			interp == Interpolate.BILINEAR
1196
1197		= error ("resize: unimplemented argument combination:\n" ++
1198			"  xfac = " ++ print xfac' ++ "\n" ++
1199			"  yfac = " ++ print yfac' ++ "\n" ++
1200			"  interp = " ++ print interp ++ " (" ++
1201				Interpolate.names.lookup 1 0 interp ++ ")")
1202	{
1203		// convert a float scale to integer plus fraction
1204		// eg. scale by 2.5 becomes [2, 1.25] (x * 2.5 == x * 2 * 1.25)
1205		break f = [floor f, f / floor f];
1206
1207		// binlinear resize
1208		scale xfac yfac im
1209			= im_affine im
1210				xfac 0 0 yfac
1211				0 0
1212				0 0
1213				(rint (get_width im * xfac))
1214				(rint (get_height im * yfac));
1215	}
1216}
1217
1218sharpen radius x1 y2 y3 m1 m2 in
1219	= oo_unary_function sharpen_op in, is_class in
1220	= im_sharpen in (to_real radius)
1221		(to_real x1) (to_real y2) (to_real y3)
1222		(to_real m1) (to_real m2), is_image in
1223	= error (_ "bad arguments to " ++ "sharpen")
1224{
1225	sharpen_op = Operator "sharpen"
1226		(sharpen radius x1 y2 y3 m1 m2)
1227		Operator_type.COMPOUND_REWRAP false;
1228}
1229
1230tone_analyse s m h sa ma ha in
1231	= oo_unary_function tone_analyse_op in, is_class in
1232	= im_tone_analyse in
1233		(to_real s) (to_real m) (to_real h)
1234		(to_real sa) (to_real ma) (to_real ha), is_image in
1235	= error (_ "bad arguments to " ++ "tone_analyse")
1236{
1237	tone_analyse_op = Operator "tone_analyse"
1238		(tone_analyse s m h sa ma ha)
1239		Operator_type.COMPOUND_REWRAP false;
1240}
1241
1242tone_map hist image
1243	= oo_binary_function tone_map_op hist image, is_class hist
1244	= oo_binary'_function tone_map_op hist image, is_class image
1245	= im_tone_map image hist, is_image hist && is_image image
1246	= error (_ "bad arguments to " ++ "tone_map")
1247{
1248	tone_map_op = Operator "tone_map"
1249		tone_map Operator_type.COMPOUND_REWRAP false;
1250}
1251
1252tone_build fmt b w s m h sa ma ha
1253	= (Image @ clip2fmt fmt)
1254		(im_tone_build_range mx mx
1255			(to_real b) (to_real w)
1256			(to_real s) (to_real m) (to_real h)
1257			(to_real sa) (to_real ma) (to_real ha))
1258{
1259	mx = Image_format.maxval fmt;
1260}
1261
1262icc_export depth profile intent in
1263	= oo_unary_function icc_export_op in, is_class in
1264	= im_icc_export_depth in
1265		(to_real depth) (expand profile) (to_real intent), is_image in
1266	= error (_ "bad arguments to " ++ "icc_export")
1267{
1268	icc_export_op = Operator "icc_export"
1269		(icc_export depth profile intent)
1270		Operator_type.COMPOUND_REWRAP false;
1271}
1272
1273icc_import profile intent in
1274	= oo_unary_function icc_import_op in, is_class in
1275	= im_icc_import in
1276		(expand profile) (to_real intent), is_image in
1277	= error (_ "bad arguments to " ++ "icc_import")
1278{
1279	icc_import_op = Operator "icc_import"
1280		(icc_import profile intent)
1281		Operator_type.COMPOUND_REWRAP false;
1282}
1283
1284icc_transform in_profile out_profile intent in
1285	= oo_unary_function icc_transform_op in, is_class in
1286	= im_icc_transform in
1287		(expand in_profile) (expand out_profile)
1288		(to_real intent), is_image in
1289	= error (_ "bad arguments to " ++ "icc_transform")
1290{
1291	icc_transform_op = Operator "icc_transform"
1292		(icc_transform in_profile out_profile intent)
1293		Operator_type.COMPOUND_REWRAP false;
1294}
1295
1296icc_ac2rc profile in
1297	= oo_unary_function icc_ac2rc_op in, is_class in
1298	= im_icc_ac2rc in (expand profile), is_image in
1299	= error (_ "bad arguments to " ++ "icc_ac2rc")
1300{
1301	icc_ac2rc_op = Operator "icc_ac2rc"
1302		(icc_ac2rc profile)
1303		Operator_type.COMPOUND_REWRAP false;
1304}
1305
1306print_base base in
1307	= oo_unary_function print_base_op in, is_class in
1308	= map (print_base base) in, is_list in
1309	= print_base_real, is_real in
1310	= error (_ "bad arguments to " ++ "print_base")
1311{
1312	print_base_op
1313		= Operator "print_base" (print_base base) Operator_type.COMPOUND false;
1314
1315	print_base_real
1316		= error "print_base: bad base", base < 2 || base > 16
1317		= "0", in < 0 || chars == []
1318		= reverse chars
1319	{
1320		digits = map (converse remainder base)
1321			(takewhile (not_equal 0)
1322				(iterate (converse idiv base) in));
1323		chars = map tohd digits;
1324
1325		tohd x
1326			= (char) ((int) '0' + x), x < 10
1327			= (char) ((int) 'A' + (x - 10));
1328
1329		idiv a b = (int) (a / b);
1330	}
1331}
1332
1333/* id x: the identity function
1334 *
1335 * id :: * -> *
1336 */
1337id x = x;
1338
1339/* const x y: junk y, return x
1340 *
1341 * (const 3) is the function that always returns 3.
1342 * const :: * -> ** -> *
1343 */
1344const x y = x;
1345
1346/* converse fn a b: swap order of args to fn
1347 *
1348 * converse fn a b == fn b a
1349 * converse :: (* -> ** -> ***) -> ** -> * -> ***
1350 */
1351converse fn a b = fn b a;
1352
1353/* fix fn x: find the fixed point of a function
1354 */
1355fix fn x = limit (iterate fn x);
1356
1357/* until pred fn n: apply fn to n until pred succeeds; return that value
1358 *
1359 * until (more 1000) (multiply 2) 1 = 1024
1360 * until :: (* -> bool) -> (* -> *) -> * -> *
1361 */
1362until pred fn n
1363	= n, pred n
1364	= until pred fn (fn n);
1365
1366/* Infinite list of primes.
1367 */
1368primes
1369	= 1 : (sieve [2..])
1370{
1371	sieve l = hd l : sieve (filter (nmultiple (hd l)) (tl l));
1372	nmultiple n x = x / n != (int) (x / n);
1373}
1374
1375/* Map a 3-ary function over three objects.
1376 */
1377map_trinary fn a b c
1378	= wrap (map3 (map_trinary fn) a' b' c'),
1379		is_list a' && is_list b' && is_list c'
1380
1381	= wrap (map2 (map_trinary fn a') b' c'),
1382		is_list b' && is_list c'
1383	= wrap (map2 (map_trinary (converse31 fn) b') a' c'),
1384		is_list a' && is_list c'
1385	= wrap (map2 (map_trinary (converse32 fn) c') a' b'),
1386		is_list a' && is_list b'
1387
1388	= wrap (map (map_trinary fn a' b') c'),
1389		is_list c'
1390	= wrap (map (map_trinary (converse32 fn) a' c') b'),
1391		is_list b'
1392	= wrap (map (map_trinary (converse34 fn) b' c') a'),
1393		is_list a'
1394
1395	= fn a b c
1396{
1397	converse31 fn a b c = fn b a c;
1398	converse32 fn a b c = fn c a b;
1399	converse33 fn a b c = fn a c b;
1400	converse34 fn a b c = fn b c a;
1401
1402	a'
1403		= a.value, is_Group a
1404		= a;
1405	b'
1406		= b.value, is_Group b
1407		= b;
1408	c'
1409		= c.value, is_Group c
1410		= c;
1411	wrap
1412		= Group, is_Group a || is_Group b || is_Group c
1413		= id;
1414}
1415
1416/* Map a 2-ary function over a pair of objects.
1417 */
1418map_binary fn a b
1419	= wrap (map2 (map_binary fn) a' b'), is_list a' && is_list b'
1420	= wrap (map (map_binary fn a') b'), is_list b'
1421	= wrap (map (map_binary (converse fn) b') a'), is_list a'
1422	= fn a b
1423{
1424	a'
1425		= a.value, is_Group a
1426		= a;
1427	b'
1428		= b.value, is_Group b
1429		= b;
1430	wrap
1431		= Group, is_Group a || is_Group b
1432		= id;
1433}
1434
1435/* Map a 1-ary function over an object.
1436 */
1437map_unary fn a
1438	= wrap (map (map_unary fn) a'), is_list a'
1439	= fn a
1440{
1441	a'
1442		= a.value, is_Group a
1443		= a;
1444	wrap
1445		= Group, is_Group a
1446		= id;
1447}
1448
1449/* Remove features smaller than x pixels across from an image. This used to be
1450 * rather complex ... convsep is now good enough to use.
1451 */
1452smooth x image = convsep (matrix_gaussian_blur (to_real x * 2)) image;
1453
1454/* Chop up an image into a list of lists of smaller images. Pad edges with
1455 * black.
1456 */
1457imagearray_chop tile_width tile_height hoverlap voverlap i
1458	= map chop' [0, vstep .. height]
1459{
1460	width = get_width i;
1461	height = get_height i;
1462	bands = get_bands i;
1463	format = get_format i;
1464	type = get_type i;
1465
1466	tile_width' = to_real tile_width;
1467	tile_height' = to_real tile_height;
1468	hoverlap' = to_real hoverlap;
1469	voverlap' = to_real voverlap;
1470
1471	/* Unique pixels per tile.
1472	 */
1473	hstep = tile_width' - hoverlap';
1474	vstep = tile_height' - voverlap';
1475
1476	/* Calculate padding ... pad up to tile_size pixel boundary.
1477	 */
1478	sx = tile_width' + (width - width % hstep);
1479	sy = tile_height' + (height - height % vstep);
1480
1481	/* Expand image with black to pad size.
1482	 */
1483	pad = embed 0 0 0 sx sy i;
1484
1485	/* Chop up a row.
1486	 */
1487	chop' y
1488		= map chop'' [0, hstep .. width]
1489	{
1490		chop'' x = extract_area x y tile_width' tile_height' pad;
1491	}
1492}
1493
1494/* Reassemble image.
1495 */
1496imagearray_assemble hoverlap voverlap il
1497	= (image_set_origin 0 0 @ foldl1 tbj @ map (foldl1 lrj)) il
1498{
1499	lrj l r = insert (get_width l + hoverlap) 0 r l;
1500	tbj t b = insert 0 (get_height t + voverlap) b t;
1501}
1502
1503/* Generate an nxn identity matrix.
1504 */
1505identity_matrix n
1506	= error "identity_matrix: n > 0", n < 1
1507	= map line [0 .. n - 1]
1508{
1509	line p = take p [0, 0 ..] ++ [1] ++ take (n - p - 1) [0, 0 ..];
1510}
1511