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;
26idivide a b = (int) ((int) a / (int) b);
27power a b = a ** b;
28square x = x * x;
29remainder a b = a % b;
30
31cons a b = a : b;
32dot a b = a . ( b );
33join a b = a ++ b;
34// 'difference' is defined in _list
35subscript a b = a ? b;
36
37generate s n f = [s, n .. f];
38comma r i = (r, i);
39
40compose f g = f @ g;
41
42// our only trinary operator is actually a binary operator
43if_then_else a x = if a then x?0 else x?1;
44
45cast_unsigned_char x = (unsigned char) x;
46cast_signed_char x = (signed char) x;
47cast_unsigned_short x = (unsigned short) x;
48cast_signed_short x = (signed short) x;
49cast_unsigned_int x = (unsigned int) x;
50cast_signed_int x = (signed int) x;
51cast_float x = (float) x;
52cast_double x = (double) x;
53cast_complex x = (complex) x;
54cast_double_complex x = (double complex) x;
55
56unary_minus x = -x;
57negate x = !x;
58complement x = ~x;
59unary_plus x = +x;
60
61// the function we call for "a -> v" expressions
62mksvpair s v
63	= [s, v], is_string s
64	= error "not str on lhs of ->";
65
66// the vector ops ... im is an image, vec is a real_list
67vec op_name im vec
68	= im_lintra_vec ones im vec,
69		op_name == "add" || op_name == "add'"
70	= im_lintra_vec ones (-1 * im) vec,
71		op_name == "subtract'"
72	= im_lintra_vec ones im inv,
73		op_name == "subtract"
74	= im_lintra_vec vec im zeros,
75		op_name == "multiply" || op_name == "multiply'"
76	= im_lintra_vec vec (1 / im) zeros,
77		op_name == "divide'"
78	= im_lintra_vec recip im zeros,
79		op_name == "divide"
80	= im_expntra_vec im vec,
81		op_name == "power'"
82	= im_powtra_vec im vec,
83		op_name == "power"
84	= im_remainderconst_vec im vec,
85		op_name == "remainder"
86	= im_andimage_vec im vec,
87		op_name == "bitwise_and" || op_name == "bitwise_and'"
88	= im_orimage_vec im vec,
89		op_name == "bitwise_or" || op_name == "bitwise_or'"
90	= im_eorimage_vec im vec,
91		op_name == "eor" || op_name == "eor'"
92	= im_equal_vec im vec,
93		op_name == "equal" || op_name == "equal'"
94	= im_notequal_vec im vec,
95		op_name == "not_equal" || op_name == "not_equal'"
96	= im_less_vec im vec,
97		op_name == "less"
98	= im_moreeq_vec im vec,
99		op_name == "less'"
100	= im_lesseq_vec im vec,
101		op_name == "less_equal"
102	= im_more_vec im vec,
103		op_name == "less_equal'"
104	= error ("unimplemented vector operation: " ++ op_name)
105{
106	zeros = replicate (len vec) 0;
107	ones = replicate (len vec) 1;
108	recip = map (divide 1) vec;
109	inv = map (multiply (-1)) vec;
110}
111
112// make a name value pair
113mknvpair n v
114	= [n, v], is_string n
115	= error "not [char] on LHS of =>";
116
117/* Macbeth chart patch names.
118 */
119macbeth_names = [
120	"Dark skin",
121	"Light skin",
122	"Blue sky",
123	"Foliage",
124	"Blue flower",
125	"Bluish green",
126	"Orange",
127	"Purplish blue",
128	"Moderate red",
129	"Purple",
130	"Yellow green",
131	"Orange yellow",
132	"Blue",
133	"Green",
134	"Red",
135	"Yellow",
136	"Magenta",
137	"Cyan",
138	"White (density 0.05)",
139	"Neutral 8 (density 0.23)",
140	"Neutral 6.5 (density 0.44)",
141	"Neutral 5 (density 0.70)",
142	"Neutral 3.5 (density 1.05)",
143	"Black (density 1.50)"
144];
145
146bandsplit x
147	= oo_unary_function bandsplit_op x, is_class x
148	= map (subscript x) [0 .. bands - 1], is_image x
149	= error (_ "bad arguments to " ++ "bandsplit")
150{
151	bands = get_header "Bands" x;
152	bandsplit_op = Operator "bandsplit" (map Image @ bandsplit)
153		Operator_type.COMPOUND false;
154}
155
156bandjoin l
157	= wrapper joined, has_wrapper
158	= joined, is_listof has_image l
159	= error (_ "bad arguments to " ++ "bandjoin")
160{
161	has_wrapper = has_member_list (has_member "Image") l;
162	wrapper = get_member_list (has_member "Image") (get_member "Image") l;
163	joined = im_gbandjoin (map get_image l);
164}
165
166sum x
167	= oo_unary_function sum_op x, is_class x
168	= im_avg x * (get_width x) * (get_height x) * (get_bands x), is_image x
169	= sum_list x, is_list x
170	= error (_ "bad arguments (" ++ print x ++ ") to " ++ "sum")
171{
172	sum_op = Operator "sum" sum Operator_type.COMPOUND false;
173
174	// add elements in a nested-list thing
175	sum_list l
176		= foldr acc 0 l
177	{
178		acc x total
179			= total + sum x, is_list x
180			= total + x;
181	}
182}
183
184product x
185	= oo_unary_function product_op x, is_class x
186	= product_list x, is_list x
187	// (product image) doesn't make much sense :(
188	= error (_ "bad arguments (" ++ print x ++ ") to " ++ "product")
189{
190	product_op = Operator "product" product Operator_type.COMPOUND false;
191
192	product_list l
193		= foldr prod 1 l
194	{
195		prod x total
196			= total * product x, is_list x
197			= total * x;
198	}
199}
200
201mean x
202	= oo_unary_function mean_op x, is_class x
203	= im_avg x, is_image x
204	= mean_list x, is_list x
205	= error (_ "bad arguments (" ++ print x ++ ") to " ++ "mean")
206{
207	mean_op = Operator "mean" mean Operator_type.COMPOUND false;
208
209	mean_list l = sum l / size l;
210
211	// number of elements in some sort of nested-list thing
212	size l
213		= foldr acc 0 l
214	{
215		acc x total
216			= total + size x, is_list x
217			= total + 1;
218	}
219}
220
221meang x
222	= (appl (power e) @ mean @ appl log) x
223{
224	appl fn x
225		= map fn x, is_list x
226		= fn x;
227}
228
229// zero-excluding mean
230meanze x
231	= oo_unary_function meanze_op x, is_class x
232	= meanze_image_hist x, is_image x &&
233		(fmt == Image_format.UCHAR || fmt == Image_format.USHORT)
234	= meanze_image x, is_image x
235	= meanze_list x, is_list x
236	= error (_ "bad arguments (" ++ print x ++ ") to " ++ "meanze")
237{
238	fmt = get_format x;
239
240	meanze_op = Operator "meanze" meanze Operator_type.COMPOUND false;
241
242	meanze_list l = sum l / size l;
243
244	// number of non-zero elements in some sort of nested-list thing
245	size l
246		= foldr acc 0 l
247	{
248		acc x total
249			= total + size x, is_list x
250			= total + 1, x != 0;
251			= total;
252	}
253
254	// add elements in a nested-list thing
255	sum l
256		= foldr acc 0 l
257	{
258		acc x total
259			= total + sum x, is_list x
260			= total + x;
261	}
262
263	// image mean, for any image type
264	meanze_image i
265		= sum / N
266	{
267		w = get_width i;
268		h = get_height i;
269		b = get_bands i;
270
271		st = stats i;
272		sum = st.value?0?2;
273
274		// find non-zero pixels (not zero in all bands)
275		zp = im_notequal_vec i (replicate b 0);
276
277		// number of non-zero pixels
278		N = b * (mean zp * w * h) / 255;
279	}
280
281	// image mean for 8 and 16-bit unsigned images
282	// we can use a histogram, yay, and save a pass through the image
283	meanze_image_hist i
284		= sum / N
285	{
286		// histogram, knock out zeros
287		hist = hist_find i;
288		black = image_new 1 1 (get_bands hist)
289			(get_format hist) (get_coding hist) (get_type hist) 0 0 0;
290		histze = insert 0 0 black hist;
291
292		// matching identity
293		iden
294			= im_identity_ushort (get_bands hist) (get_width hist),
295				(get_width hist) > 256
296			= im_identity (get_bands hist);
297
298		// number of non-zero pixels
299		N = mean histze * 256;
300
301		// sum of pixels
302		sum = mean (hist * iden) * 256;
303	}
304}
305
306deviation x
307	= oo_unary_function deviation_op x, is_class x
308	= im_deviate x, is_image x
309	= deviation_list x, is_real_list x || is_matrix x
310	= error (_ "bad arguments to " ++ "deviation")
311{
312	deviation_op = Operator "deviation"
313		deviation Operator_type.COMPOUND false;
314
315	deviation_list l
316		= (abs (s2 - (s * s / n)) / (n - 1)) ** 0.5
317	{
318		[n, s, s2] = sum_sum2_list l;
319	}
320
321	// return n, sum, sum of squares for a list of reals
322	sum_sum2_list x
323		= foldr accumulate [0, 0, 0] x
324	{
325		accumulate x sofar
326			= [n + 1, x + s, x * x + s2], is_real x
327			= [n + n', s + s', s2 + s2'], is_list x
328			= error "sum_sum2_list: not real or [real]"
329		{
330			[n, s, s2] = sofar;
331			[n', s', s2'] = sum_sum2_list x;
332		}
333	}
334}
335
336deviationze x
337	= oo_unary_function deviationze_op x, is_class x
338	= deviationze_image x, is_image x
339	= deviationze_list x, is_real_list x || is_matrix x
340	= error (_ "bad arguments to " ++ "deviationze")
341{
342	deviationze_op = Operator "deviationze"
343		deviationze Operator_type.COMPOUND false;
344
345	deviationze_list l
346		= (abs (s2 - (s * s / n)) / (n - 1)) ** 0.5
347	{
348		[n, s, s2] = sum_sum2_list l;
349	}
350
351	// return number of non-zero elements, sum, sum of squares for a list of
352	// reals
353	sum_sum2_list x
354		= foldr accumulate [0, 0, 0] x
355	{
356		accumulate x sofar
357			= sofar, is_real x && x == 0
358			= [n + 1, x + s, x * x + s2], is_real x
359			= [n + n', s + s', s2 + s2'], is_list x
360			= error "sum_sum2_list: not real or [real]"
361		{
362			[n, s, s2] = sofar;
363			[n', s', s2'] = sum_sum2_list x;
364		}
365	}
366
367	deviationze_image i
368		= ((sum2 - sum * sum / N) / (N - 1)) ** 0.5
369	{
370		w = get_width i;
371		h = get_height i;
372		b = get_bands i;
373
374		st = stats i;
375		sum = st.value?0?2;
376		sum2 = st.value?0?3;
377
378		// find non-zero pixels (not zero in all bands)
379		zp = im_notequal_vec i (replicate b 0);
380
381		// number of non-zero pixels
382		N = b * (mean zp * w * h) / 255;
383	}
384}
385
386// find the centre of gravity of a histogram
387gravity x
388	= oo_unary_function gravity_op x, is_class x
389	= im_hist_gravity x, is_hist x
390	= gravity_list x, is_list x
391	= error (_ "bad arguments to " ++ "gravity")
392{
393	gravity_op = Operator "gravity" gravity Operator_type.COMPOUND false;
394
395	// centre of gravity of a histogram... use the histogram to weight an
396	// identity, then sum, then find the mean element
397	im_hist_gravity h
398		= m
399	{
400		// make horizontal
401		h'
402			= rot270 h, get_width h == 1
403			= h, get_height h == 1
404			= error "width or height not 1";
405
406		// number of elements
407		w = get_width h';
408
409		// matching identity
410		i
411			= im_identity_ushort 1 w, w <= 2 ** 16 - 1
412			= make_xy w 1 ? 0;
413
414		// weight identity and sum
415		s = mean (i * h') * w;
416
417		// sum of original histogram
418		s' = mean h * w;
419
420		// weighted mean
421		m = s / s';
422	}
423
424	gravity_list l
425		= m
426	{
427		w = len l;
428
429		// matching identity
430		i = [0, 1 .. w - 1];
431
432		// weight identity and sum
433		s = sum (map2 multiply i l);
434
435		// sum of original histogram
436		s' = sum l;
437
438		// weighted mean
439		m = s / s';
440	}
441}
442
443project x
444	= oo_unary_function project_op x, is_class x
445	= im_project x, is_image x
446	= error (_ "bad arguments to " ++ "project")
447{
448	project_op = Operator "project" project Operator_type.COMPOUND false;
449}
450
451abs x
452	= oo_unary_function abs_op x, is_class x
453	= im_abs x, is_image x
454	= abs_cmplx x, is_complex x
455	= abs_num x, is_real x
456	= abs_list x, is_real_list x
457	= abs_list (map abs_list x), is_matrix x
458	= error (_ "bad arguments to " ++ "abs")
459{
460	abs_op = Operator "abs" abs Operator_type.COMPOUND false;
461
462	abs_list l = (sum (map square l)) ** 0.5;
463
464	abs_num n
465		= n, n >= 0
466		= -n;
467
468	abs_cmplx c = ((re c)**2 + (im c)**2) ** 0.5;
469}
470
471copy x
472	= oo_unary_function copy_op x, is_class x
473	= im_copy x, is_image x
474	= x
475{
476	copy_op = Operator "copy" copy Operator_type.COMPOUND_REWRAP false;
477}
478
479// like abs, but treat pixels as vectors ... ie. always get a 1-band image
480// back ... also treat matricies as lists of vectors
481// handy for dE from object difference
482abs_vec x
483	= oo_unary_function abs_vec_op x, is_class x
484	= abs_vec_image x, is_image x
485	= abs_vec_cmplx x, is_complex x
486	= abs_vec_num x, is_real x
487	= abs_vec_list x, is_real_list x
488	= mean (map abs_vec_list x), is_matrix x
489	= error (_ "bad arguments to " ++ "abs_vec")
490{
491	abs_vec_op = Operator "abs_vec"
492		abs_vec Operator_type.COMPOUND false;
493
494	abs_vec_list l = (sum (map square l)) ** 0.5;
495
496	abs_vec_num n
497		= n, n >= 0
498		= -n;
499
500	abs_vec_cmplx c = ((re c)**2 + (im c)**2) ** 0.5;
501
502	abs_vec_image im
503		= (sum (map square (bandsplit im))) ** 0.5;
504}
505
506transpose x
507	= oo_unary_function transpose_op x, is_class x
508	= transpose_image x, is_image x
509	= transpose_list x, is_listof is_list x
510	= error (_ "bad arguments to " ++ "transpose")
511{
512	transpose_op = Operator "transpose"
513		transpose Operator_type.COMPOUND_REWRAP false;
514
515	transpose_list l
516		= [], l' == []
517		= (map hd l') : (transpose_list (map tl l'))
518	{
519		l' = takewhile (not_equal []) l;
520	}
521
522	transpose_image = im_flipver @ im_rot270;
523}
524
525rot45 x
526	= oo_unary_function rot45_op x, is_class x
527	= error "rot45 image: not implemented", is_image x
528	= error (_ "bad arguments to " ++ "rot45")
529{
530	rot45_op = Operator "rot45"
531		rot45_object Operator_type.COMPOUND_REWRAP false;
532
533	rot45_object x
534		= rot45_matrix x, is_odd_square_matrix x
535		= error "rot45 image: not implemented", is_image x
536		= error (_ "bad arguments to " ++ "rot45");
537
538	// slow, but what the heck
539	rot45_matrix l = (im_rotate_dmask45 (Matrix l)).value;
540}
541
542// apply an image function to a [[real]] ... matrix is converted to a 1 band
543// image for processing
544apply_matrix_as_image fn m
545	= (get_value @ im_vips2mask @ fn @ im_mask2vips @ Matrix) m;
546
547// a general image/matrix operation where the mat version is most easily done
548// by converting mat->image->mat
549apply_matim_operation name fn x
550	= oo_unary_function class_op x, is_class x
551	= fn x, is_image x
552	= apply_matrix_as_image fn x, is_matrix x
553	= error (_ "bad arguments to " ++ name)
554{
555	class_op = Operator name
556		(apply_matim_operation name fn) Operator_type.COMPOUND_REWRAP false;
557}
558
559rot90 = apply_matim_operation "rot90" im_rot90;
560rot180 = apply_matim_operation "rot180" im_rot180;
561rot270 = apply_matim_operation "rot270" im_rot270;
562rotquad = apply_matim_operation "rotquad" im_rotquad;
563fliplr = apply_matim_operation "fliplr" im_fliphor;
564fliptb = apply_matim_operation "flipud" im_flipver;
565
566image_set_type type x
567	= oo_unary_function image_set_type_op x, is_class x
568	= im_copy_set x (to_real type)
569		(get_header "Xres" x) (get_header "Yres" x)
570		(get_header "Xoffset" x) (get_header "Yoffset" x),
571		is_image x
572	= error (_ "bad arguments to " ++ "image_set_type:" ++
573		print type ++ " " ++ print x)
574{
575	image_set_type_op = Operator "image_set_type"
576		(image_set_type type) Operator_type.COMPOUND_REWRAP false;
577}
578
579image_set_origin xoff yoff x
580	= oo_unary_function image_set_origin_op x, is_class x
581	= im_copy_set x
582		(get_header "Type" x)
583		(get_header "Xres" x) (get_header "Yres" x)
584		(to_real xoff) (to_real yoff),
585		is_image x
586	= error (_ "bad arguments to " ++ "image_set_origin")
587{
588	image_set_origin_op = Operator "image_set_origin"
589		(image_set_origin xoff yoff)
590		Operator_type.COMPOUND_REWRAP false;
591}
592
593cache tile_width tile_height max_tiles x
594	= oo_unary_function cache_op x, is_class x
595	= im_cache x (to_real tile_width) (to_real tile_height)
596		(to_real max_tiles), is_image x
597	= error (_ "bad arguments to " ++ "cache")
598{
599	cache_op = Operator "cache"
600		(cache tile_width tile_height max_tiles)
601		Operator_type.COMPOUND_REWRAP false;
602}
603
604tile across down x
605	= oo_unary_function tile_op x, is_class x
606	= im_replicate x (to_real across) (to_real down), is_image x
607	= error (_ "bad arguments to " ++ "tile")
608{
609	tile_op = Operator "tile"
610		(tile across down) Operator_type.COMPOUND_REWRAP false;
611}
612
613grid tile_height across down x
614	= oo_unary_function grid_op x, is_class x
615	= im_grid x (to_real tile_height) (to_real across) (to_real down),
616		is_image x
617	= error (_ "bad arguments to " ++ "grid")
618{
619	grid_op = Operator "grid"
620		(grid tile_height across down) Operator_type.COMPOUND_REWRAP false;
621}
622
623max_pair a b
624	= a, a > b
625	= b;
626
627min_pair a b
628      =	a, a < b
629      =	b;
630
631range min value max = min_pair max (max_pair min value);
632
633max x
634	= oo_unary_function max_op x, is_class x
635	= im_max x, is_image x
636	= max_list x, is_list x
637	= x, is_number x
638	= error (_ "bad arguments to " ++ "max")
639{
640	max_op = Operator "max" max Operator_type.COMPOUND false;
641
642	max_list x
643		= error "max []", x == []
644		= foldr1 max_pair x, is_real_list x
645		= foldr1 max_pair (map max_list x), is_list x
646		= max x;
647}
648
649min x
650	= oo_unary_function min_op x, is_class x
651	= im_min x, is_image x
652	= min_list x, is_list x
653	= x, is_number x
654	= error (_ "bad arguments to " ++ "min")
655{
656	min_op = Operator "min" min Operator_type.COMPOUND false;
657
658	min_list x
659		= error "min []", x == []
660		= foldr1 min_pair x, is_real_list x
661		= foldr1 min_pair (map min_list x), is_list x
662		= min x;
663}
664
665maxpos x
666	= oo_unary_function maxpos_op x, is_class x
667	= im_maxpos x, is_image x
668	= maxpos_matrix x, is_matrix x
669	= maxpos_list x, is_list x
670	= error (_ "bad arguments to " ++ "maxpos")
671{
672	maxpos_op = Operator "maxpos" maxpos Operator_type.COMPOUND false;
673
674	maxpos_matrix m
675		= (-1, -1), m == [[]]
676		= (indexes?row, row)
677	{
678		max_value = max (Matrix m);
679		indexes = map (index (equal max_value)) m;
680		row = index (not_equal (-1)) indexes;
681	}
682
683	maxpos_list l
684		= -1, l == []
685		= index (equal (max l)) l;
686}
687
688minpos x
689	= oo_unary_function minpos_op x, is_class x
690	= im_minpos x, is_image x
691	= minpos_matrix x, is_matrix x
692	= minpos_list x, is_list x
693	= error (_ "bad arguments to " ++ "minpos")
694{
695	minpos_op = Operator "minpos" minpos Operator_type.COMPOUND false;
696
697	minpos_matrix m
698		= (-1, -1), m == [[]]
699		= (indexes?row, row)
700	{
701		min_value = min (Matrix m);
702		indexes = map (index (equal min_value)) m;
703		row = index (not_equal (-1)) indexes;
704	}
705
706	minpos_list l
707		= -1, l == []
708		= index (equal (min l)) l;
709}
710
711stats x
712	= oo_unary_function stats_op x, is_class x
713	= im_stats x, is_image x
714	= im_stats (to_image x).value, is_matrix x
715	= error (_ "bad arguments to " ++ "stats")
716{
717	stats_op = Operator "stats"
718		stats Operator_type.COMPOUND false;
719}
720
721e = 2.7182818284590452354;
722
723pi = 3.14159265358979323846;
724
725rad d = 2 * pi * (d / 360);
726
727deg r = 360 * r / (2 * pi);
728
729sign x
730	= oo_unary_function sign_op x, is_class x
731	= im_sign x, is_image x
732	= sign_cmplx x, is_complex x
733	= sign_num x, is_real x
734	= error (_ "bad arguments to " ++ "sign")
735{
736	sign_op = Operator "sign" sign Operator_type.COMPOUND_REWRAP false;
737
738	sign_num n
739		= 0, n == 0
740		= 1, n > 0
741		= -1;
742
743	sign_cmplx c
744		= (0, 0), mod == 0
745		= (re c / mod, im c / mod)
746	{
747		mod = abs c;
748	}
749}
750
751rint x
752	= oo_unary_function rint_op x, is_class x
753	= im_rint x, is_image x
754	= rint_value x, is_number x
755	= error (_ "bad arguments to " ++ "rint")
756{
757	rint_op = Operator "rint" rint Operator_type.ARITHMETIC false;
758
759	rint_value x
760		= (int) (x + 0.5), x > 0
761		= (int) (x - 0.5);
762}
763
764scale x
765	= oo_unary_function scale_op x, is_class x
766	= (unsigned char) x, is_number x
767	= im_scale x, is_image x
768	= scale_list x, is_real_list x || is_matrix x
769	= error (_ "bad arguments to " ++ "scale")
770{
771	scale_op = Operator "scale" scale Operator_type.COMPOUND_REWRAP false;
772
773	scale_list l
774		= apply_scale s o l
775	{
776		mn = find_limit min_pair l;
777		mx = find_limit max_pair l;
778		s = 255.0 / (mx - mn);
779		o = -(mn * s);
780	}
781
782	find_limit fn l
783		= find_limit fn (map (find_limit fn) l), is_listof is_list l
784		= foldr1 fn l;
785
786	apply_scale s o x
787		= x * s + o, is_number x
788		= map (apply_scale s o) x;
789}
790
791scaleps x
792	= oo_unary_function scale_op x, is_class x
793	= im_scaleps x, is_image x
794	= error (_ "bad arguments to " ++ "scale")
795{
796	scale_op = Operator "scaleps"
797		scaleps Operator_type.COMPOUND_REWRAP false;
798}
799
800fwfft x
801	= oo_unary_function fwfft_op x, is_class x
802	= im_fwfft x, is_image x
803	= error (_ "bad arguments to " ++ "fwfft")
804{
805	fwfft_op = Operator "fwfft"
806		fwfft Operator_type.COMPOUND_REWRAP false;
807}
808
809invfft x
810	= oo_unary_function invfft_op x, is_class x
811	= im_invfftr x, is_image x
812	= error (_ "bad arguments to " ++ "invfft")
813{
814	invfft_op = Operator "invfft"
815		invfft Operator_type.COMPOUND_REWRAP false;
816}
817
818falsecolour x
819	= oo_unary_function falsecolour_op x, is_class x
820	= image_set_type Image_type.sRGB (im_falsecolour x), is_image x
821	= error (_ "bad arguments to " ++ "falsecolour")
822{
823	falsecolour_op = Operator "falsecolour"
824		falsecolour Operator_type.COMPOUND_REWRAP false;
825}
826
827polar x
828	= oo_unary_function polar_op x, is_class x
829	= im_c2amph x, is_image x
830	= polar_cmplx x, is_complex x
831	= error (_ "bad arguments to " ++ "polar")
832{
833	polar_op = Operator "polar" polar Operator_type.COMPOUND false;
834
835	polar_cmplx r
836		= (l, a)
837	{
838		a
839			= 270, x == 0 && y < 0
840			= 90, x == 0 && y >= 0
841			= 360 + atan (y / x), x > 0 && y < 0
842			= atan (y / x), x > 0 && y >= 0
843			= 180 + atan (y / x);
844
845		l = (x ** 2 + y ** 2) ** 0.5;
846
847		x = re r;
848		y = im r;
849	}
850}
851
852rectangular x
853	= oo_unary_function rectangular_op x, is_class x
854	= im_c2rect x, is_image x
855	= rectangular_cmplx x, is_complex x
856	= error (_ "bad arguments to " ++ "rectangular")
857{
858	rectangular_op = Operator "rectangular"
859		rectangular Operator_type.COMPOUND false;
860
861	rectangular_cmplx p
862		= (x, y)
863	{
864		l = re p;
865		a = im p;
866
867		x = l * cos a;
868		y = l * sin a;
869	}
870}
871
872// we can't use colour_unary: that likes 3 band only
873recomb matrix x
874	= oo_unary_function recomb_op x, is_class x
875	= im_recomb x matrix, is_image x
876	= recomb_real_list x, is_real_list x
877	= map recomb_real_list x, is_matrix x
878	= error (_ "bad arguments to " ++ "recomb")
879{
880	// COMPOUND_REWRAP ... signal to the colour class to go to image and
881	// back
882	recomb_op = Operator "recomb"
883		(recomb matrix) Operator_type.COMPOUND_REWRAP false;
884
885	// process [1,2,3 ..] as an image
886	recomb_real_list l
887		= (to_matrix im').value?0
888	{
889		im = (float) (to_image (Vector l)).value;
890		im' = recomb matrix im;
891	}
892}
893
894extract_area x y w h obj
895	= oo_unary_function extract_area_op obj, is_class obj
896	= im_extract_area obj x' y' w' h', is_image obj
897	= map (extract_range x' w') (extract_range y' h' obj), is_matrix obj
898	= error (_ "bad arguments to " ++ "extract_area")
899{
900	x' = to_real x;
901	y' = to_real y;
902	w' = to_real w;
903	h' = to_real h;
904
905	extract_area_op = Operator "extract_area" (extract_area x y w h)
906		Operator_type.COMPOUND_REWRAP false;
907
908	extract_range from length list
909		= (take length @ drop from) list;
910}
911
912extract_band b obj = subscript obj b;
913
914extract_row y obj
915	= oo_unary_function extract_row_op obj, is_class obj
916	= extract_area 0 y' (get_width obj) 1 obj, is_image obj
917	= [obj?y'], is_matrix obj
918	= error (_ "bad arguments to " ++ "extract_row")
919{
920	y' = to_real y;
921
922	extract_row_op = Operator "extract_row" (extract_row y)
923		Operator_type.COMPOUND_REWRAP false;
924}
925
926extract_column x obj
927	= oo_unary_function extract_column_op obj, is_class obj
928	= extract_area x' 0 1 height obj, is_image obj
929	= map (\row [row?x']) obj, is_matrix obj
930	= error (_ "bad arguments to " ++ "extract_column")
931{
932	x' = to_real x;
933	height = get_header "Ysize" obj;
934
935	extract_column_op = Operator "extract_column" (extract_column x)
936		Operator_type.COMPOUND_REWRAP false;
937}
938
939blend cond in1 in2
940	= oo_binary_function blend_op cond [in1,in2], is_class cond
941	= im_blend (get_image cond) (get_image in1) (get_image in2),
942		has_image cond && has_image in1 && has_image in2
943	= error (_ "bad arguments to " ++ "blend")
944{
945	blend_op = Operator "blend"
946		blend_obj Operator_type.COMPOUND_REWRAP false;
947
948	blend_obj cond x
949		= blend_result_image
950	{
951		[then_part, else_part] = x;
952
953		// get things about our output from inputs in this order
954		objects = [then_part, else_part, cond];
955
956		// properties of our output image
957		target_width = get_member_list has_width get_width objects;
958		target_height = get_member_list has_height get_height objects;
959		target_bands = get_member_list has_bands get_bands objects;
960		target_format = get_member_list has_format get_format objects;
961		target_type = get_member_list has_type get_type objects;
962
963		to_image x
964			= to_image_size target_width target_height
965				target_bands target_format x;
966
967		[then_image, else_image] = map to_image [then_part, else_part];
968
969		blend_result_image = image_set_type target_type
970			(im_blend cond then_image else_image);
971	}
972}
973
974// do big first: we want to keep big's class, if possible
975// eg. big is a Plot, small is a 1x1 Image
976insert x y small big
977	= oo_binary'_function insert_op small big, is_class big
978	= oo_binary_function insert_op small big, is_class small
979	= im_insert big small (to_real x) (to_real y),
980		is_image small && is_image big
981	= error (_ "bad arguments to " ++ "insert")
982{
983	insert_op = Operator "insert"
984		(insert x y) Operator_type.COMPOUND_REWRAP false;
985}
986
987insert_noexpand x y small big
988	= oo_binary_function insert_noexpand_op small big, is_class small
989	= oo_binary'_function insert_noexpand_op small big, is_class big
990	= im_insert_noexpand big small (to_real x) (to_real y),
991		is_image small && is_image big
992	= error (_ "bad arguments to " ++ "insert_noexpand")
993{
994	insert_noexpand_op = Operator "insert_noexpand"
995		(insert_noexpand x y) Operator_type.COMPOUND_REWRAP false;
996}
997
998measure x y w h u v image
999	= oo_unary_function measure_op image, is_class image
1000	= im_measure image
1001		(to_real x) (to_real y) (to_real w) (to_real h)
1002		(to_real u) (to_real v),
1003			is_image image
1004	= error (_ "bad arguments to " ++ "measure")
1005{
1006	measure_op = Operator "measure"
1007		(measure x y w h u v) Operator_type.COMPOUND_REWRAP false;
1008}
1009
1010extract_bands b n obj
1011	= oo_unary_function extract_bands_op obj, is_class obj
1012	= im_extract_bands obj (to_real b) (to_real n), is_image obj
1013	= error (_ "bad arguments to " ++ "extract_bands")
1014{
1015	extract_bands_op = Operator "extract_bands"
1016		(extract_bands b n) Operator_type.COMPOUND_REWRAP false;
1017}
1018
1019invert x
1020	= oo_unary_function invert_op x, is_class x
1021	= im_invert x, is_image x
1022	= 255 - x, is_real x
1023	= error (_ "bad arguments to " ++ "invert")
1024{
1025	invert_op = Operator "invert" invert Operator_type.COMPOUND false;
1026}
1027
1028transform ipol wrap params image
1029	= oo_unary_function transform_op image, is_class image
1030	= im_transform image
1031		(to_matrix params) (to_real ipol) (to_real wrap), is_image image
1032	= error (_ "bad arguments to " ++ "transform")
1033{
1034	transform_op = Operator "transform"
1035		(transform ipol wrap params)
1036		Operator_type.COMPOUND_REWRAP false;
1037}
1038
1039transform_search max_error max_iterations order ipol wrap sample reference
1040	= oo_binary_function transform_search_op sample reference, is_class sample
1041	= oo_binary'_function transform_search_op sample reference,
1042		is_class reference
1043	= im_transform_search sample reference
1044		(to_real max_error) (to_real max_iterations) (to_real order)
1045		(to_real ipol) (to_real wrap),
1046			is_image sample && is_image reference
1047	= error (_ "bad arguments to " ++ "transform_search")
1048{
1049	transform_search_op = Operator "transform_search"
1050		(transform_search max_error max_iterations order ipol wrap)
1051		Operator_type.COMPOUND false;
1052}
1053
1054rotate interp angle image
1055	= oo_binary_function rotate_op angle image, is_class angle
1056	= oo_binary'_function rotate_op angle image, is_class image
1057	= im_affinei_all image interp.value a (-b) b a 0 0,
1058		is_real angle && is_image image
1059	= error (_ "bad arguments to " ++ "rotate")
1060{
1061	rotate_op = Operator "rotate"
1062		(rotate interp) Operator_type.COMPOUND_REWRAP false;
1063	a = cos angle;
1064	b = sin angle;
1065}
1066
1067matrix_binary fn a b
1068	= itom (fn (mtoi a) (mtoi b))
1069{
1070	mtoi x = im_mask2vips (Matrix x);
1071	itom x = (im_vips2mask x).value;
1072}
1073
1074join_lr a b
1075	= oo_binary_function join_lr_op a b, is_class a
1076	= oo_binary'_function join_lr_op a b, is_class b
1077	= join_im a b, is_image a && is_image b
1078	= matrix_binary join_im a b, is_matrix a && is_matrix b
1079	= error (_ "bad arguments to " ++ "join_lr")
1080{
1081	join_lr_op = Operator "join_lr"
1082		join_lr Operator_type.COMPOUND_REWRAP false;
1083
1084	join_im a b	= insert (get_width a) 0 b a;
1085}
1086
1087join_tb a b
1088	= oo_binary_function join_tb_op a b, is_class a
1089	= oo_binary'_function join_tb_op a b, is_class b
1090	= join_im a b, is_image a && is_image b
1091	= matrix_binary join_im a b, is_matrix a && is_matrix b
1092	= error (_ "bad arguments to " ++ "join_tb")
1093{
1094	join_tb_op = Operator "join_tb"
1095		join_tb Operator_type.COMPOUND_REWRAP false;
1096
1097	join_im a b	= insert 0 (get_height a) b a;
1098}
1099
1100conj x
1101	= oo_unary_function conj_op x, is_class x
1102	= (re x, -im x),
1103		is_complex x ||
1104		(is_image x && format == Image_format.COMPLEX) ||
1105		(is_image x && format == Image_format.DPCOMPLEX)
1106	// assume it's some sort of real
1107	= x
1108{
1109	format = get_header "BandFmt" x;
1110	conj_op = Operator "conj" conj Operator_type.COMPOUND false;
1111}
1112
1113clip2fmt format image
1114	= oo_unary_function clip2fmt_op image, is_class image
1115	= im_clip2fmt image (to_real format), is_image image
1116	= error (_ "bad arguments to " ++ "clip2fmt")
1117{
1118	clip2fmt_op = Operator "clip2fmt"
1119		(clip2fmt format) Operator_type.COMPOUND_REWRAP false;
1120}
1121
1122embed type x y w h im
1123	= oo_unary_function embed_op im, is_class im
1124	= im_embed im (to_real type)
1125		(to_real x) (to_real y) (to_real w) (to_real h), is_image im
1126	= error (_ "bad arguments to " ++ "embed")
1127{
1128	embed_op = Operator "embed"
1129		(embed type x y w h) Operator_type.COMPOUND_REWRAP false;
1130}
1131
1132/* Morph a mask with a [[real]] matrix ... turn m2 into an image, morph it
1133 * with m1, turn it back to a matrix again.
1134 */
1135_morph_2_masks fn m1 m2
1136	= m''
1137{
1138	// The [[real]] can contain 128 values ... squeeze them out!
1139	image = im_mask2vips (Matrix m2) == 255;
1140	m2_width = get_width image;
1141	m2_height = get_height image;
1142
1143	// need to embed m2 in an image large enough for us to be able to
1144	// position m1 all around the edges, with a 1 pixel overlap
1145	image' = embed 0
1146		(m1.width / 2) (m1.height / 2)
1147		(m2_width + (m1.width - 1)) (m2_height + (m1.height - 1))
1148		image;
1149
1150	// morph!
1151	image'' = fn m1 image';
1152
1153	// back to mask
1154	m' = im_vips2mask ((double) image'');
1155
1156	// Turn 0 in output to 128 (don't care).
1157	m''
1158		= map (map fn) m'.value
1159	{
1160		fn a
1161			= 128, a == 0;
1162			= a;
1163	}
1164}
1165
1166dilate mask image
1167	= oo_unary_function dilate_op image, is_class image
1168	= im_dilate image (to_matrix mask), is_image image
1169	= error (_ "bad arguments to " ++ "dilate")
1170{
1171	dilate_op = Operator "dilate"
1172		dilate_object Operator_type.COMPOUND_REWRAP false;
1173
1174	dilate_object x
1175		= _morph_2_masks dilate mask x, is_matrix x
1176		= dilate mask x;
1177}
1178
1179erode mask image
1180	= oo_unary_function erode_op image, is_class image
1181	= im_erode image (to_matrix mask), is_image image
1182	= error (_ "bad arguments to " ++ "erode")
1183{
1184	erode_op = Operator "erode"
1185		erode_object Operator_type.COMPOUND_REWRAP false;
1186
1187	erode_object x
1188		= _morph_2_masks erode mask x, is_matrix x
1189		= erode mask x;
1190}
1191
1192conv mask image
1193	= oo_unary_function conv_op image, is_class image
1194	= im_conv image (to_matrix mask), is_image image
1195	= error (_ "bad arguments to " ++ "conv" ++ ": " ++
1196		"conv (" ++ print mask ++ ") (" ++ print image ++ ")")
1197{
1198	conv_op = Operator "conv"
1199		(conv mask) Operator_type.COMPOUND_REWRAP false;
1200}
1201
1202convf mask image
1203	= oo_unary_function convf_op image, is_class image
1204	= im_conv_f image (to_matrix mask), is_image image
1205	= error (_ "bad arguments to " ++ "convf" ++ ": " ++
1206		"convf (" ++ print mask ++ ") (" ++ print image ++ ")")
1207{
1208	convf_op = Operator "convf"
1209		(convf mask) Operator_type.COMPOUND_REWRAP false;
1210}
1211
1212convsep mask image
1213	= oo_unary_function convsep_op image, is_class image
1214	= im_convsep image (to_matrix mask), is_image image
1215	= error (_ "bad arguments to " ++ "convsep")
1216{
1217	convsep_op = Operator "convsep"
1218		(convsep mask) Operator_type.COMPOUND_REWRAP false;
1219}
1220
1221aconvsep layers mask image
1222	= oo_unary_function aconvsep_op image, is_class image
1223	= im_aconvsep image (to_matrix mask) (to_real layers), is_image image
1224	= error (_ "bad arguments to " ++ "aconvsep")
1225{
1226	aconvsep_op = Operator "aconvsep"
1227		(aconvsep layers mask) Operator_type.COMPOUND_REWRAP false;
1228}
1229
1230convsepf mask image
1231	= oo_unary_function convsepf_op image, is_class image
1232	= im_convsep_f image (to_matrix mask), is_image image
1233	= error (_ "bad arguments to " ++ "convsepf")
1234{
1235	convsepf_op = Operator "convsepf"
1236		(convsepf mask) Operator_type.COMPOUND_REWRAP false;
1237}
1238
1239rank w h n image
1240	= oo_unary_function rank_op image, is_class image
1241	= im_rank image (to_real w) (to_real h) (to_real n), is_image image
1242	= error (_ "bad arguments to " ++ "rank")
1243{
1244	rank_op = Operator "rank"
1245		(rank w h n) Operator_type.COMPOUND_REWRAP false;
1246}
1247
1248rank_image n x
1249	= rlist x.value, is_Group x
1250	= rlist x, is_list x
1251	= error (_ "bad arguments to " ++ "rank_image")
1252{
1253	rlist l
1254		= wrapper ranked, has_wrapper
1255		= ranked
1256	{
1257		has_wrapper = has_member_list (has_member "Image") l;
1258		wrapper = get_member_list (has_member "Image") (get_member "Image") l;
1259		ranked = im_rank_image (map get_image l) (to_real n);
1260	}
1261}
1262
1263greyc iterations amplitude sharpness anisotropy alpha
1264	sigma dl da gauss_prec interpolation fast_approx x
1265	= oo_unary_function greyc_op x, is_class x
1266	= greyc_im x, is_image x
1267	= error (_ "bad argument" ++ " (" ++ print x ++ ") to " ++ "greyc")
1268{
1269	greyc_op = Operator "greyc" (greyc
1270			iterations amplitude sharpness anisotropy alpha
1271			sigma dl da gauss_prec interpolation fast_approx)
1272		Operator_type.COMPOUND_REWRAP false;
1273	greyc_im x = im_greyc x
1274		iterations amplitude sharpness anisotropy alpha
1275		sigma dl da gauss_prec interpolation fast_approx;
1276}
1277
1278greyc_mask iterations amplitude sharpness anisotropy alpha
1279	sigma dl da gauss_prec interpolation fast_approx mask x
1280	= oo_binary_function greyc_mask_op mask x, is_class mask
1281	= oo_binary'_function greyc_mask_op mask x, is_class x
1282	= greyc_im mask x, is_image mask && is_image x
1283	= error (_ "bad arguments" ++
1284		" (" ++ print mask ++ ", " ++ print x ++ ") " ++
1285		"to " ++ "greyc")
1286{
1287	greyc_mask_op = Operator "greyc_mask" (greyc_mask
1288			iterations amplitude sharpness anisotropy alpha
1289			sigma dl da gauss_prec interpolation fast_approx)
1290		Operator_type.COMPOUND_REWRAP false;
1291	greyc_im mask x = im_greyc_mask x mask
1292		iterations amplitude sharpness anisotropy alpha
1293		sigma dl da gauss_prec interpolation fast_approx;
1294}
1295
1296// find the correlation surface for a small image within a big one
1297correlate small big
1298	= oo_binary_function correlate_op small big, is_class small
1299	= oo_binary'_function correlate_op small big, is_class big
1300	= im_spcor big small, is_image small && is_image big
1301	= error (_ "bad arguments to " ++ "correlate")
1302{
1303	correlate_op = Operator "correlate"
1304		correlate Operator_type.COMPOUND_REWRAP false;
1305}
1306
1307// just sum-of-squares-of-differences
1308correlate_fast small big
1309	= oo_binary_function correlate_fast_op small big, is_class small
1310	= oo_binary'_function correlate_fast_op small big, is_class big
1311	= im_fastcor big small, is_image small && is_image big
1312	= error (_ "bad arguments to " ++ "correlate_fast")
1313{
1314	correlate_fast_op = Operator "correlate_fast"
1315		correlate_fast Operator_type.COMPOUND_REWRAP false;
1316}
1317
1318// set Type, wrap as Plot_hist if it's a class
1319hist_tag x
1320	= oo_unary_function hist_tag_op x, is_class x
1321	= image_set_type Image_type.HISTOGRAM x, is_image x
1322	= error (_ "bad arguments to " ++ "hist_tag")
1323{
1324	hist_tag_op = Operator "hist_tag"
1325		(Plot_histogram @ hist_tag) Operator_type.COMPOUND false;
1326}
1327
1328hist_find x
1329	= oo_unary_function hist_find_op x, is_class x
1330	= im_histgr x (-1), is_image x
1331	= error (_ "bad arguments to " ++ "hist_find")
1332{
1333	hist_find_op = Operator "hist_find"
1334		(Plot_histogram @ hist_find) Operator_type.COMPOUND false;
1335}
1336
1337hist_find_nD bins image
1338	= oo_unary_function hist_find_nD_op image, is_class image
1339	= im_histnD image (to_real bins), is_image image
1340	= error (_ "bad arguments to " ++ "hist_find_nD")
1341{
1342	hist_find_nD_op = Operator "hist_find_nD"
1343		(hist_find_nD bins) Operator_type.COMPOUND_REWRAP false;
1344}
1345
1346hist_find_indexed index value
1347	= oo_binary_function hist_find_indexed_op index value, is_class index
1348	= oo_binary'_function hist_find_indexed_op index value, is_class value
1349	= im_hist_indexed index value, is_image index && is_image value
1350	= error (_ "bad arguments to " ++ "hist_find_indexed")
1351{
1352	hist_find_indexed_op = Operator "hist_find_indexed"
1353		(compose (compose Plot_histogram) hist_find_indexed)
1354			Operator_type.COMPOUND false;
1355}
1356
1357hist_map hist image
1358	= oo_binary_function hist_map_op hist image, is_class hist
1359	= oo_binary'_function hist_map_op hist image, is_class image
1360	= im_maplut image hist, is_image hist && is_image image
1361	= error (_ "bad arguments to " ++ "hist_map")
1362{
1363	// can't use rewrap, as we want to always wrap as image
1364	hist_map_op = Operator "hist_map"
1365		(compose (compose Image) hist_map) Operator_type.COMPOUND false;
1366}
1367
1368hist_cum hist
1369	= oo_unary_function hist_cum_op hist, is_class hist
1370	= im_histcum hist, is_image hist
1371	= error (_ "bad arguments to " ++ "hist_cum")
1372{
1373	hist_cum_op = Operator "hist_cum"
1374		hist_cum Operator_type.COMPOUND_REWRAP false;
1375}
1376
1377hist_diff hist
1378	= oo_unary_function hist_diff_op hist, is_class hist
1379	= im_histdiff hist, is_image hist
1380	= error (_ "bad arguments to " ++ "hist_diff")
1381{
1382	hist_diff_op = Operator "hist_diff"
1383		hist_diff Operator_type.COMPOUND_REWRAP false;
1384
1385	im_histdiff h
1386		= (conv (Matrix [[-1, 1]]) @ clip2fmt (fmt (get_format h))) h
1387	{
1388		// up the format so it can represent the whole range of
1389		// possible values from this mask
1390		fmt x
1391			= Image_format.SHORT,
1392				x == Image_format.UCHAR || x == Image_format.CHAR
1393			= Image_format.INT,
1394				x == Image_format.USHORT || x == Image_format.SHORT ||
1395				x == Image_format.UINT
1396			= x;
1397	}
1398}
1399
1400hist_norm hist
1401	= oo_unary_function hist_norm_op hist, is_class hist
1402	= im_histnorm hist, is_image hist
1403	= error (_ "bad arguments to " ++ "hist_norm")
1404{
1405	hist_norm_op = Operator "hist_norm"
1406		hist_norm Operator_type.COMPOUND_REWRAP false;
1407}
1408
1409hist_match in ref
1410	= oo_binary_function hist_match_op in ref, is_class in
1411	= oo_binary'_function hist_match_op in ref, is_class ref
1412	= im_histspec in ref, is_image in && is_image ref
1413	= error (_ "bad arguments to " ++ "hist_match")
1414{
1415	hist_match_op = Operator "hist_match"
1416		hist_match Operator_type.COMPOUND_REWRAP false;
1417}
1418
1419hist_equalize x = hist_map ((hist_norm @ hist_cum @ hist_find) x) x;
1420
1421hist_equalize_local w h image
1422	= oo_unary_function hist_equalize_local_op image, is_class image
1423	= lhisteq image, is_image image
1424	= error (_ "bad arguments to " ++ "hist_equalize_local")
1425{
1426	hist_equalize_local_op = Operator "hist_equalize_local"
1427		(hist_equalize_local w h) Operator_type.COMPOUND_REWRAP false;
1428
1429	// loop over bands, if necessary
1430	lhisteq im
1431		= im_lhisteq im (to_real w) (to_real h), get_bands im == 1
1432		= (foldl1 join @ map lhisteq @ bandsplit) im;
1433}
1434
1435// find the threshold below which are percent of the image (percent in [0,1])
1436// eg. hist_thresh 0.1 x == 12, then x < 12 will light up 10% of the pixels
1437hist_thresh percent image
1438	= x
1439{
1440	// our own normaliser ... we don't want to norm channels separately
1441	// norm to [0,1]
1442	my_hist_norm h = h / max h;
1443
1444	// normalised cumulative hist
1445	// we sum the channels before we normalise, because we want to treat them
1446	// all the same
1447	h = (my_hist_norm @ sum @ bandsplit @ hist_cum @ hist_find)
1448		image.value;
1449
1450	// threshold that, then use im_profile to search for the x position in the
1451	// histogram
1452	x = mean (im_profile (h > percent) 1);
1453}
1454
1455/* Sometimes useful, despite plotting now being built in, for making
1456 * diagnostic images.
1457 */
1458hist_plot hist
1459	= oo_unary_function hist_plot_op hist, is_class hist
1460	= im_histplot hist, is_image hist
1461	= error (_ "bad arguments to " ++ "hist_plot")
1462{
1463	hist_plot_op = Operator "hist_plot"
1464		(Image @ hist_plot) Operator_type.COMPOUND false;
1465}
1466
1467zerox d x
1468	= oo_unary_function zerox_op x, is_class x
1469	= im_zerox x (to_real d), is_image x
1470	= error (_ "bad arguments to " ++ "zerox")
1471{
1472	zerox_op = Operator "zerox"
1473		(zerox d) Operator_type.COMPOUND_REWRAP false;
1474}
1475
1476resize interp xfac yfac image
1477	= oo_unary_function resize_op image, is_class image
1478	= resize_im image, is_image image
1479	= error (_ "bad arguments to " ++ "resize")
1480{
1481	resize_op = Operator "resize"
1482		resize_im Operator_type.COMPOUND_REWRAP false;
1483
1484	xfac' = to_real xfac;
1485	yfac' = to_real yfac;
1486
1487	rxfac' = 1 / xfac';
1488	ryfac' = 1 / yfac';
1489
1490	// is this interpolation nearest-neighbour?
1491	is_nn x = x.type == Interpolate_type.NEAREST_NEIGHBOUR;
1492
1493	resize_im im
1494		// upscale by integer factor, nearest neighbour
1495		= im_zoom im xfac' yfac',
1496			is_int xfac' && is_int yfac' &&
1497			xfac' >= 1 && yfac' >= 1 &&
1498			is_nn interp
1499
1500		// downscale by integer factor, nearest neighbour
1501		= im_subsample im rxfac' ryfac',
1502			is_int rxfac' && is_int ryfac' &&
1503			rxfac' >= 1 && ryfac' >= 1 &&
1504			is_nn interp
1505
1506		// upscale by any factor, nearest neighbour
1507		// upscale by integer part, then affine to exact size
1508		= scale xg?1 yg?1 (im_zoom im xg?0 yg?0),
1509			xfac' >= 1 && yfac' >= 1 &&
1510			is_nn interp
1511
1512		// downscale by any factor, nearest neighbour
1513		// downscale by integer part, then affine to exact size
1514		= scale xs?1 ys?1 (im_subsample im xs?0 ys?0),
1515			rxfac' >= 1 && ryfac' >= 1 &&
1516			is_nn interp
1517
1518		// upscale by any factor with affine
1519		= scale xfac' yfac' im,
1520			xfac' >= 1 && yfac' >= 1
1521
1522		// downscale by any factor, bilinear
1523		// block shrink by integer factor, then resample to
1524		// exact with affine
1525		= scale xs?1 ys?1 (im_shrink im xs?0 ys?0),
1526			rxfac' >= 1 && ryfac' >= 1
1527
1528		= error ("resize: unimplemented argument combination:\n" ++
1529			"  xfac = " ++ print xfac' ++ "\n" ++
1530			"  yfac = " ++ print yfac' ++ "\n" ++
1531			"  interp = " ++ print interp ++ " (" ++
1532				Interpolate_type.descriptions?interp.type ++ ")")
1533	{
1534		// convert a float scale to integer plus fraction
1535		// eg. scale by 2.5 becomes [2, 1.25] (x * 2.5 == x * 2 * 1.25)
1536		break f = [floor f, f / floor f];
1537
1538		// same, but for downsizing ... turn a float scale which is less than
1539		// 1 into an int shrink and a float scale
1540
1541		// complicated: the int shrink may round the size down (eg. imagine
1542		// subsampling a 11 pixel wide image by 3, we'd get a 3 pixel wide
1543		// image, not a 3.666 pixel wide image), so pass in the size of image
1544		// we are operating on and adjust for any rounding
1545
1546		// so ... x is (eg.) 467, f is (eg. 128/467, about 0.274)
1547		rbreak x f
1548			= [int_shrink, float_resample]
1549		{
1550			// the size of image we are aiming for after the combined int and
1551			// float resample
1552			x' = x * f;
1553
1554			// int part
1555			int_shrink = floor (1 / f);
1556
1557			// size after int shrink
1558			x'' = floor (x / int_shrink);
1559
1560			// therefore what we need for the float part
1561			float_resample = x' / x'';
1562		}
1563
1564		width = get_width im;
1565		height = get_height im;
1566
1567		// grow and shrink factors
1568		xg = break xfac';
1569		yg = break yfac';
1570		xs = rbreak width xfac';
1571		ys = rbreak height yfac';
1572
1573		// resize
1574		scale xfac yfac im
1575			= im_affinei_all im interp.value xfac 0 0 yfac 0 0;
1576	}
1577}
1578
1579sharpen radius x1 y2 y3 m1 m2 in
1580	= oo_unary_function sharpen_op in, is_class in
1581	= im_sharpen in (to_real radius)
1582		(to_real x1) (to_real y2) (to_real y3)
1583		(to_real m1) (to_real m2), is_image in
1584	= error (_ "bad arguments to " ++ "sharpen")
1585{
1586	sharpen_op = Operator "sharpen"
1587		(sharpen radius x1 y2 y3 m1 m2)
1588		Operator_type.COMPOUND_REWRAP false;
1589}
1590
1591tone_analyse s m h sa ma ha in
1592	= oo_unary_function tone_analyse_op in, is_class in
1593	= im_tone_analyse in
1594		(to_real s) (to_real m) (to_real h)
1595		(to_real sa) (to_real ma) (to_real ha), is_image in
1596	= error (_ "bad arguments to " ++ "tone_analyse")
1597{
1598	tone_analyse_op = Operator "tone_analyse"
1599		(Plot_histogram @ tone_analyse s m h sa ma ha)
1600		Operator_type.COMPOUND false;
1601}
1602
1603tone_map hist image
1604	= oo_binary_function tone_map_op hist image, is_class hist
1605	= oo_binary'_function tone_map_op hist image, is_class image
1606	= im_tone_map image hist, is_image hist && is_image image
1607	= error (_ "bad arguments to " ++ "tone_map")
1608{
1609	tone_map_op = Operator "tone_map"
1610		tone_map Operator_type.COMPOUND_REWRAP false;
1611}
1612
1613tone_build fmt b w s m h sa ma ha
1614	= (Plot_histogram @ clip2fmt fmt)
1615		(im_tone_build_range mx mx
1616			(to_real b) (to_real w)
1617			(to_real s) (to_real m) (to_real h)
1618			(to_real sa) (to_real ma) (to_real ha))
1619{
1620	mx = Image_format.maxval fmt;
1621}
1622
1623icc_export depth profile intent in
1624	= oo_unary_function icc_export_op in, is_class in
1625	= im_icc_export_depth in
1626		(to_real depth) (expand profile) (to_real intent), is_image in
1627	= error (_ "bad arguments to " ++ "icc_export")
1628{
1629	icc_export_op = Operator "icc_export"
1630		(icc_export depth profile intent)
1631		Operator_type.COMPOUND_REWRAP false;
1632}
1633
1634icc_import profile intent in
1635	= oo_unary_function icc_import_op in, is_class in
1636	= im_icc_import in
1637		(expand profile) (to_real intent), is_image in
1638	= error (_ "bad arguments to " ++ "icc_import")
1639{
1640	icc_import_op = Operator "icc_import"
1641		(icc_import profile intent)
1642		Operator_type.COMPOUND_REWRAP false;
1643}
1644
1645icc_import_embedded intent in
1646	= oo_unary_function icc_import_embedded_op in, is_class in
1647	= im_icc_import_embedded in (to_real intent), is_image in
1648	= error (_ "bad arguments to " ++ "icc_import_embedded")
1649{
1650	icc_import_embedded_op = Operator "icc_import_embedded"
1651		(icc_import_embedded intent)
1652		Operator_type.COMPOUND_REWRAP false;
1653}
1654
1655icc_transform in_profile out_profile intent in
1656	= oo_unary_function icc_transform_op in, is_class in
1657	= im_icc_transform in
1658		(expand in_profile) (expand out_profile)
1659		(to_real intent), is_image in
1660	= error (_ "bad arguments to " ++ "icc_transform")
1661{
1662	icc_transform_op = Operator "icc_transform"
1663		(icc_transform in_profile out_profile intent)
1664		Operator_type.COMPOUND_REWRAP false;
1665}
1666
1667icc_ac2rc profile in
1668	= oo_unary_function icc_ac2rc_op in, is_class in
1669	= im_icc_ac2rc in (expand profile), is_image in
1670	= error (_ "bad arguments to " ++ "icc_ac2rc")
1671{
1672	icc_ac2rc_op = Operator "icc_ac2rc"
1673		(icc_ac2rc profile)
1674		Operator_type.COMPOUND_REWRAP false;
1675}
1676
1677draw_flood_blob x y ink image
1678	= oo_unary_function draw_flood_blob_op image, is_class image
1679	= im_draw_flood_blob image (to_real x) (to_real y) ink, is_image image
1680	= error (_ "bad arguments to " ++ "draw_flood_blob")
1681{
1682	draw_flood_blob_op = Operator "draw_flood_blob"
1683		(draw_flood_blob x y ink) Operator_type.COMPOUND_REWRAP false;
1684}
1685
1686draw_flood x y ink image
1687	= oo_unary_function draw_flood_op image, is_class image
1688	= im_draw_flood image (to_real x) (to_real y) ink, is_image image
1689	= error (_ "bad arguments to " ++ "draw_flood")
1690{
1691	draw_flood_op = Operator "draw_flood"
1692		(draw_flood x y ink) Operator_type.COMPOUND_REWRAP false;
1693}
1694
1695draw_rect x y w h f ink image
1696	= oo_unary_function draw_rect_op image, is_class image
1697	= im_draw_rect image (to_real x) (to_real y)
1698		(to_real w) (to_real h) (to_real f) ink, is_image image
1699	= error (_ "bad arguments to " ++ "draw_rect")
1700{
1701	draw_rect_op = Operator "draw_rect"
1702		(draw_rect x y w h f ink) Operator_type.COMPOUND_REWRAP false;
1703}
1704
1705draw_circle x y r f ink image
1706	= oo_unary_function draw_circle_op image, is_class image
1707	= im_draw_circle image (to_real x) (to_real y)
1708		(to_real r) (to_real f) ink, is_image image
1709	= error (_ "bad arguments to " ++ "draw_circle")
1710{
1711	draw_circle_op = Operator "draw_circle"
1712		(draw_circle x y r f ink) Operator_type.COMPOUND_REWRAP false;
1713}
1714
1715draw_line x1 y1 x2 y2 ink image
1716	= oo_unary_function draw_line_op image, is_class image
1717	= im_draw_line image (to_real x1) (to_real y1)
1718		(to_real x2) (to_real y2) ink, is_image image
1719	= error (_ "bad arguments to " ++ "draw_line")
1720{
1721	draw_line_op = Operator "draw_line"
1722		(draw_line x1 y1 x2 y2 ink) Operator_type.COMPOUND_REWRAP false;
1723}
1724
1725print_base base in
1726	= oo_unary_function print_base_op in, is_class in
1727	= map (print_base base) in, is_list in
1728	= print_base_real, is_real in
1729	= error (_ "bad arguments to " ++ "print_base")
1730{
1731	print_base_op
1732		= Operator "print_base" (print_base base) Operator_type.COMPOUND false;
1733
1734	print_base_real
1735		= error "print_base: bad base", base < 2 || base > 16
1736		= "0", in < 0 || chars == []
1737		= reverse chars
1738	{
1739		digits = map (\x x % base)
1740			(takewhile (not_equal 0)
1741				(iterate (\x idivide x base) in));
1742		chars = map tohd digits;
1743
1744		tohd x
1745			= (char) ((int) '0' + x), x < 10
1746			= (char) ((int) 'A' + (x - 10));
1747	}
1748}
1749
1750/* id x: the identity function
1751 *
1752 * id :: * -> *
1753 */
1754id x = x;
1755
1756/* const x y: junk y, return x
1757 *
1758 * (const 3) is the function that always returns 3.
1759 * const :: * -> ** -> *
1760 */
1761const x y = x;
1762
1763/* converse fn a b: swap order of args to fn
1764 *
1765 * converse fn a b == fn b a
1766 * converse :: (* -> ** -> ***) -> ** -> * -> ***
1767 */
1768converse fn a b = fn b a;
1769
1770/* fix fn x: find the fixed point of a function
1771 */
1772fix fn x = limit (iterate fn x);
1773
1774/* until pred fn n: apply fn to n until pred succeeds; return that value
1775 *
1776 * until (more 1000) (multiply 2) 1 = 1024
1777 * until :: (* -> bool) -> (* -> *) -> * -> *
1778 */
1779until pred fn n
1780	= n, pred n
1781	= until pred fn (fn n);
1782
1783/* Infinite list of primes.
1784 */
1785primes
1786	= 1 : (sieve [2 ..])
1787{
1788	sieve l = hd l : sieve (filter (nmultiple (hd l)) (tl l));
1789	nmultiple n x = x / n != (int) (x / n);
1790}
1791
1792/* Map an n-ary function (pass the args as a list) over groups of objects.
1793 * The objects can be single objects or groups. If more than one
1794 * object is a group, we iterate for the length of the smallest group.
1795 * Don't loop over plain lists, since we want (eg.) "mean [1, 2, 3]" to work.
1796 * Treat [] as no-value --- ie. if any of the inputs are [] we put [] into the
1797 * output and don't apply the function.
1798
1799		copy-pasted into _types, keep in sync
1800
1801 */
1802map_nary fn args
1803	= fn args, groups == []
1804	= Group (map process [0, 1 .. shortest - 1])
1805{
1806	// find all the group arguments
1807	groups = filter is_Group args;
1808
1809	// what's the length of the shortest group arg?
1810	shortest = foldr1 min_pair (map (len @ get_value) groups);
1811
1812	// process index n ... pull that member from each argument
1813	// recurse to handle application, so we work for nested groups too
1814	process n
1815		= NULL, any (map (is_noval n) args)
1816		= map_nary fn (map (extract n) args)
1817	{
1818		extract n arg
1819			= arg.value?n, is_Group arg
1820			= arg;
1821
1822		is_noval n arg = is_Group arg && arg.value?n == NULL;
1823	}
1824}
1825
1826/* Map a 1-ary function over an object.
1827 */
1828map_unary fn a = map_nary (list_1ary fn) [a];
1829
1830/* Map a 2-ary function over a pair of objects.
1831 */
1832map_binary fn a b = map_nary (list_2ary fn) [a, b];
1833
1834/* Map a 3-ary function over three objects.
1835 */
1836map_trinary fn a b c = map_nary (list_3ary fn) [a, b, c];
1837
1838/* Map a 4-ary function over three objects.
1839 */
1840map_quaternary fn a b c d = map_nary (list_4ary fn) [a, b, c, d];
1841
1842/* Same as map_nary, but for lists. Handy for (eg.) implementing arith ops on
1843 * vectors and matricies.
1844 */
1845map_nary_list fn args
1846	= fn args, lists == []
1847	= map process [0, 1 .. shortest - 1]
1848{
1849	// find all the list arguments
1850	lists = filter is_list args;
1851
1852	// what's the length of the shortest list arg?
1853	shortest = foldr1 min_pair (map len lists);
1854
1855	// process index n ... pull that member from each argument
1856	// recurse to handle application, so we work for nested lists too
1857	process n
1858		= map_nary_list fn (map (extract n) args)
1859	{
1860		extract n arg
1861			= arg?n, is_list arg
1862			= arg;
1863	}
1864}
1865
1866map_unaryl fn a = map_nary_list (list_1ary fn) [a];
1867
1868map_binaryl fn a b = map_nary_list (list_2ary fn) [a, b];
1869
1870/* Remove features smaller than x pixels across from an image. This used to be
1871 * rather complex ... convsep is now good enough to use.
1872 */
1873smooth x image = convsep (matrix_gaussian_blur (to_real x * 2)) image;
1874
1875/* Chop up an image into a list of lists of smaller images. Pad edges with
1876 * black.
1877 */
1878imagearray_chop tile_width tile_height hoverlap voverlap i
1879	= map chop' [0, vstep .. last_y]
1880{
1881	width = get_width i;
1882	height = get_height i;
1883	bands = get_bands i;
1884	format = get_format i;
1885	type = get_type i;
1886
1887	tile_width' = to_real tile_width;
1888	tile_height' = to_real tile_height;
1889	hoverlap' = to_real hoverlap;
1890	voverlap' = to_real voverlap;
1891
1892	/* Unique pixels per tile.
1893	 */
1894	hstep = tile_width' - hoverlap';
1895	vstep = tile_height' - voverlap';
1896
1897	/* Number of tiles across and down. Remember the case where width ==
1898	 * hstep.
1899	 */
1900	across = (int) ((width - 1) / hstep) + 1;
1901	down = (int) ((height - 1) / vstep) + 1;
1902
1903	/* top/left of final tile.
1904	 */
1905	last_x = hstep * (across - 1);
1906	last_y = vstep * (down - 1);
1907
1908	/* How much do we need to pad by?
1909	 */
1910	sx = last_x + tile_width';
1911	sy = last_y + tile_height';
1912
1913	/* Expand image with black to pad size.
1914	 */
1915	pad = embed 0 0 0 sx sy i;
1916
1917	/* Chop up a row.
1918	 */
1919	chop' y
1920		= map chop'' [0, hstep .. last_x]
1921	{
1922		chop'' x = extract_area x y tile_width' tile_height' pad;
1923	}
1924}
1925
1926/* Reassemble image.
1927 */
1928imagearray_assemble hoverlap voverlap il
1929	= (image_set_origin 0 0 @ foldl1 tbj @ map (foldl1 lrj)) il
1930{
1931	lrj l r = insert (get_width l + hoverlap) 0 r l;
1932	tbj t b = insert 0 (get_height t + voverlap) b t;
1933}
1934
1935/* Generate an nxn identity matrix.
1936 */
1937identity_matrix n
1938	= error "identity_matrix: n > 0", n < 1
1939	= map line [0 .. n - 1]
1940{
1941	line p = take p [0, 0 ..] ++ [1] ++ take (n - p - 1) [0, 0 ..];
1942}
1943
1944/* Incomplete gamma function Q(a, x) == 1 - P(a, x)
1945
1946	FIXME ... this is now a builtin, until we can get a nice List class
1947	(requires overloadable ':')
1948
1949gammq a x
1950	= error "bad args", x < 0 || a <= 0
1951	= 1 - gamser, x < a + 1
1952	= gammcf
1953{
1954	gamser = (gser a x)?0;
1955	gammcf = (gcf a x)?0;
1956}
1957 */
1958
1959/* Incomplete gamma function P(a, x) evaluated as series representation. Also
1960 * return ln(gamma(a)) ... nr in c, pp 218
1961 */
1962gser a x
1963	= [gamser, gln]
1964{
1965	gln = gammln a;
1966	gamser
1967		= error "bad args", x < 0
1968		= 0, x == 0
1969		= 1		// fix this
1970	{
1971		// maximum iterations
1972		maxit = 100;
1973
1974		ap = List [a + 1, a + 2 ...];
1975		xoap = x / ap;
1976
1977		del = map product (prefixes xoap.value);
1978
1979
1980
1981
1982
1983
1984
1985/*
1986		del = map (multiply (1 / a)) (map product (prefixes xoap))
1987
1988		del =
1989
1990		xap = iterate (multiply
1991 */
1992
1993		/* Generate all prefixes of a list ... [1,2,3] -> [[1], [1, 2], [1, 2,
1994		 * 3], [1, 2, 3, 4] ...]
1995		 */
1996		prefixes l = map (converse take l) [1..];
1997
1998	}
1999}
2000
2001/* ln(gamma(xx)) ... nr in c, pp 214
2002 */
2003gammln xx
2004	= gln
2005{
2006	cof = [76.18009172947146, -86.50532032941677, 24.01409824083091,
2007		-1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5];
2008	y = take 6 (iterate (add 1) (xx + 1));
2009	ser = 1.000000000190015 + sum (map2 divide cof y);
2010	tmp = xx + 0.5;
2011	tmp' = tmp - ((xx + 0.5) * log tmp);
2012	gln = -tmp + log (2.5066282746310005 * ser / xx);
2013}
2014
2015/* make a LUT from a scatter
2016 */
2017buildlut x
2018	= Plot_histogram (im_buildlut x), is_Matrix x && x.width > 1
2019	= im_buildlut (Matrix x), is_matrix x && is_list_len_more 1 x?0
2020	= error (_ "bad arguments to " ++ "buildlut");
2021
2022/* Linear regression. Return a class with the stuff we need in.
2023 * from s15.2, p 665 NR in C
2024 */
2025linreg xes yes
2026    = obj
2027{
2028    obj = class {
2029        // in case we ever get shown in the workspace
2030        _vislevel = 2;
2031
2032        slope = sum [t * y :: [t, y] <- zip2 tes yes] / st2;
2033        intercept = (sy - sx * slope) / ss;
2034
2035        chi2 = sum [(y - intercept - slope * x) ** 2 :: [x, y] <- zip2 xes yes];
2036
2037        siga = (chi2 / (ss - 2)) ** 0.5 *
2038            ((1 + sx ** 2 / (ss * st2)) / ss) ** 0.5;
2039        sigb = (chi2 / (ss - 2)) ** 0.5 * (1 / st2) ** 0.5;
2040
2041        // for compat with linregw, see below
2042        q = 1.0;
2043    }
2044
2045    ss = len xes;
2046    sx = sum xes;
2047    sy = sum yes;
2048    sxoss = sx / ss;
2049
2050    tes = [x - sxoss :: x <- xes];
2051    st2 = sum [t ** 2 :: t <- tes];
2052}
2053
2054/* Weighted linear regression. Xes, yes and a list of deviations.
2055 */
2056linregw xes yes devs
2057	= obj
2058{
2059	obj = class {
2060		// in case we ever get shown in the workspace
2061		_vislevel = 2;
2062
2063		slope = sum [(t * y) / sd :: [t, y, sd] <- zip3 tes yes devs] / st2;
2064		intercept = (sy - sx * slope) / ss;
2065
2066		chi2 = sum [((y - intercept - slope * x) / sd) ** 2 ::
2067			[x, y, sd] <- zip3 xes yes devs];
2068
2069		siga = ((1 + sx * sx / (ss * st2)) / ss) ** 0.5;
2070		sigb = (1 / st2) ** 0.5;
2071
2072		q = gammq (0.5 * (len xes - 2)) (0.5 * chi2);
2073	}
2074
2075	wt = [sd ** -0.5 :: sd <- devs];
2076
2077	ss = sum wt;
2078	sx = sum [x * w :: [x, w] <- zip2 xes wt];
2079	sy = sum [y * w :: [y, w] <- zip2 yes wt];
2080	sxoss = sx / ss;
2081
2082	tes = [(x - sxoss) / sd :: [x, sd] <- zip2 xes devs];
2083	st2 = sum [t ** 2 :: t <- tes];
2084}
2085
2086/* Clustering: pass in a list of points, repeatedly merge the
2087 * closest two points until no two points are closer than the threshold.
2088 * Return [merged-points, corresponding-weights]. A weight is a list of the
2089 * indexes we merged to make that point, ie. len weight == how significant
2090 * this point is.
2091 *
2092 * eg.
2093 *	cluster 12 [152,154,155,42,159] ==
2094 *		[[155,42],[[1,2,0,4],[3]]]
2095 */
2096cluster thresh points
2097	= oo_unary_function cluster_op points, is_class points
2098	// can't use [0..len points - 1], in case len points == 0
2099	= merge [points, map (converse cons []) (take (len points) [0 ..])],
2100		is_list points
2101	= error (_ "bad arguments to " ++ "cluster")
2102{
2103	cluster_op = Operator "cluster"
2104		(cluster thresh) Operator_type.COMPOUND false;
2105
2106	merge x
2107		= x, m < 2 || d > thresh
2108		= merge [points', weights']
2109	{
2110		[points, weights] = x;
2111		m = len points;
2112
2113		// generate indexes of all possible pairs, avoiding comparing a thing
2114		// to itself, and assuming that dist is reflexive
2115		// first index is always less than 2nd index
2116		// the +1,+2 makes sure we have an increasing generator, otherwise we
2117		// can get [3 .. 4] (for example), which will make a decreasing
2118		// sequence
2119		pairs = [[x, y] :: x <- [0 .. m - 1]; y <- [x + 1, x + 2 .. m - 1]];
2120
2121		// distance function
2122		// arg is eg. [3,1], meaning get distance from point 3 to point 1
2123		dist x
2124			= abs (points?i - points?j)
2125		{
2126			[i, j] = x;
2127		}
2128
2129		// smallest distance, then the two points we merge
2130		p = minpos (map dist pairs);
2131		d = dist pairs?p;
2132		[i, j] = pairs?p;
2133
2134		// new point and new weight
2135		nw = weights?i ++ weights?j;
2136		np = (points?i * len weights?i + points?j * len weights?j) / len nw;
2137
2138		// remove element i from a list
2139		remove i l = take i l ++ drop (i + 1) l;
2140
2141		// remove two old points, add the new merged one
2142		// i < j (see "pairs", above)
2143		points' = np : remove i (remove j points);
2144		weights' = nw : remove i (remove j weights);
2145	}
2146}
2147
2148/* Extract the area of an image around an arrow.
2149 * Transform the image to make the arrow horizontal, then displace by hd and
2150 * vd pxels, then cut out a bit h pixels high centered on the arrow.
2151 */
2152extract_arrow hd vd h arrow
2153	= extract_area (re p' + hd) (im p' - h / 2 + vd) (re pv) h im'
2154{
2155	// the line as a polar vector
2156	pv = polar (arrow.width, arrow.height);
2157	a = im pv;
2158
2159	// smallest rotation that will make the line horizontal
2160	a'
2161		= 360 - a, a > 270
2162		= 180 - a, a > 90
2163		= -a;
2164
2165	im' = rotate Interpolate_bilinear a' arrow.image;
2166
2167	// look at the start and end of the arrow, pick the leftmost
2168	p
2169		= (arrow.left, arrow.top), arrow.left <= arrow.right
2170		= (arrow.right, arrow.bottom);
2171
2172	// transform that point to im' space
2173	p' = rectangular (polar p + (0, a')) + (im'.xoffset, im'.yoffset);
2174}
2175
2176/* You'd think these would go in _convert, but they are not really colour ops,
2177 * so put them here.
2178 */
2179rad2float image
2180	= oo_unary_function rad2float_op image, is_class image
2181	= im_rad2float image, is_image image
2182	= error (_ "bad arguments to " ++ "rad2float")
2183{
2184	rad2float_op = Operator "rad2float"
2185		rad2float Operator_type.COMPOUND_REWRAP false;
2186}
2187
2188float2rad image
2189	= oo_unary_function float2rad_op image, is_class image
2190	= im_float2rad image, is_image image
2191	= error (_ "bad arguments to " ++ "float2rad")
2192{
2193	float2rad_op = Operator "float2rad"
2194		float2rad Operator_type.COMPOUND_REWRAP false;
2195}
2196
2197segment x
2198	= oo_unary_function segment_op x, is_class x
2199	= image', is_image x
2200	= error (_ "bad arguments to " ++ "segment")
2201{
2202	segment_op = Operator "segment"
2203		segment Operator_type.COMPOUND_REWRAP false;
2204
2205	[image, nsegs] = im_segment x;
2206	image' = im_copy_set_meta image "n-segments" nsegs;
2207}
2208
2209point a b
2210	= oo_binary_function point_op a b, is_class a
2211	= oo_binary'_function point_op a b, is_class b
2212	= im_read_point b x y, is_image b
2213	= [b?x?y], is_matrix b
2214	= [b?x], is_real_list b && y == 0
2215	= [b?y], is_real_list b && x == 0
2216	= error (_ "bad arguments to " ++ "point")
2217{
2218	point_op = Operator "point"
2219		(\a\b Vector (point a b)) Operator_type.COMPOUND false;
2220
2221	(x, y)
2222		= a, is_complex a;
2223		= (a?0, a?1), is_real_list a && is_list_len 2 a
2224		= error "bad position format";
2225}
2226
2227/* Generate an ImageMagick (or GraphicsMagick) command suitable for
2228 * im_system_image. Use convert.exe in $VIPSHOME/bin, if it exists, otherwise
2229 * assume it's on the path somewhere.
2230 */
2231magick_command switch
2232	= join_sep " " [convert, "\"%s\"", switch, "\"%s\""]
2233{
2234	prefs = Workspaces.Preferences;
2235	use_gm = prefs.USE_GRAPHICSMAGICK;
2236	name = if use_gm then "gm" else "convert";
2237	exe = concat [name, expand "$EXEEXT"];
2238	vipsexe = path_absolute [expand "$VIPSHOME", "bin", exe];
2239	final_exe
2240		= vipsexe, search vipsexe != []
2241		= exe;
2242	convert
2243		= join_sep " " [final_exe, "convert"], use_gm
2244		= final_exe;
2245}
2246
2247/* Run a command on an image. See magick_command, for example.
2248 */
2249system_image command x
2250	= oo_unary_function system_image_op x, is_class x
2251	= system x, is_image x
2252	= error (_ "bad arguments to " ++ "system_image")
2253{
2254	system_image_op = Operator "system_image"
2255		(system_image command) Operator_type.COMPOUND_REWRAP false;
2256
2257	system im
2258		= image_out
2259	{
2260		[image_out, log] =
2261			im_system_image (get_image im) "%s.tif" "%s.tif" command;
2262	}
2263}
2264