1
2/* Try to make a Matrix ... works for Vector/Image/Real, plus image/real
3 */
4to_matrix x
5	= to_matrix x.expr, is_Expression x
6	= x, is_Matrix x
7	= oo_unary_function to_matrix_op x, is_class x
8	= tom x
9{
10	to_matrix_op = Operator "to_matrix" tom Operator_type.COMPOUND false;
11
12	tom x
13		= Matrix (itom x), is_image x
14		= Matrix [[x]], is_real x
15		= Matrix [x], is_real_list x
16		= Matrix x, is_matrix x
17		= error (_ "bad arguments to " ++ "to_matrix");
18
19	itom i
20		= (im_vips2mask ((double) i)).value, is_image i
21		= error (_ "not image");
22}
23
24/* Try to make an Image ... works for Vector/Matrix/Real, plus image/real
25 * Special case for Colour ... pull out the colour_space and set Type in the
26 * image.
27 */
28to_image x
29	= to_image x.expr, is_Expression x
30	= x, is_Image x
31	= Image (image_set_type
32			(Image_type.colour_spaces.lookup 0 1 x.colour_space)
33			(mtoi [x.value])),
34		is_Colour x
35	= oo_unary_function to_image_op x, is_class x
36	= toi x
37{
38	to_image_op = Operator "to_image" toi Operator_type.COMPOUND false;
39
40	toi x
41		= Image x, is_image x
42		= Image (mtoi [[x]]), is_real x
43		= Image (mtoi [x]), is_real_list x
44		= Image (mtoi x), is_matrix x
45		= error (_ "bad arguments to " ++ "to_image");
46
47	// [[real]] -> image
48	mtoi m
49		= im_mask2vips (Matrix m), width != 3
50		= joinup (im_mask2vips (Matrix m))
51	{
52		width = len m?0;
53		height = len m;
54		joinup i
55			= b1 ++ b2 ++ b3
56		{
57			b1 = extract_area 0 0 1 height i;
58			b2 = extract_area 1 0 1 height i;
59			b3 = extract_area 2 0 1 height i;
60		}
61	}
62}
63
64// like to_image, but we do 1x1 pixel + x, then embed it up
65// always make an unwrapped image for speed ... this gets used by ifthenelse
66// and stuff like that
67// format can be NULL, meaning set format from x
68to_image_size width height bands format x
69	= x, is_image x
70	= x.value, is_Image x
71	= im''
72{
73	// we want x to set the target format if we don't have one, so we
74	// can't use image_new
75	im = im_black 1 1 bands + x;
76	im'
77		= clip2fmt format im, format != NULL
78		= im;
79	im'' = embed 1 0 0 width height im';
80}
81
82/* Try to make a Colour.
83 */
84to_colour x
85	= to_colour x.expr, is_Expression x
86	= x, is_Colour x
87	= to_colour (extract_area x.left x.top 1 1 x.image), is_Mark x
88	= oo_unary_function to_colour_op x, is_class x
89	= toc x
90{
91	to_colour_op = Operator "to_colour" toc Operator_type.COMPOUND false;
92
93	toc x
94		= Colour (colour_space (get_type x))
95			(map mean (bandsplit (get_image x))),
96			has_image x && has_type x
97		= Colour "sRGB" [x, x, x], is_real x	// since Colour can't do mono
98		= Colour "sRGB" x, is_real_list x && is_list_len 3 x
99		= map toc x, is_matrix x
100		= error (_ "bad arguments to " ++ "to_colour");
101
102	colour_space type
103		= table.get_name type, table.has_name type
104		= error (_ "unable to make Colour from " ++ table.get_name type ++
105			_ " image")
106	{
107		table = Image_type.colour_spaces;
108	}
109}
110
111/* Try to make a real. (not a Real!)
112 */
113to_real x
114	= to_real x.expr, is_Expression x
115	= oo_unary_function to_real_op x, is_class x
116	= tor x
117{
118	to_real_op = Operator "to_real" tor Operator_type.COMPOUND false;
119
120	tor x
121		= x, is_real x
122		= abs x, is_complex x
123		= 1, is_bool x && x
124		= 0, is_bool x && !x
125		= error (_ "bad arguments to " ++ "to_real");
126}
127
128to_int x = (int) (to_real x);
129
130/* Try to make a list ... ungroup, basically. We remove the innermost layer of
131 * Groups.
132 */
133to_list x
134	= x.value, is_Group x && !contains_Group x.value
135	= Group (map to_list x.value), is_Group x
136	= x;
137
138/* Try to make a group. The innermost list objects become Group()'d.
139 */
140to_group x
141	= Group x, is_list x && !contains_Group x
142	= Group (map to_group x.value), is_Group x
143	= x;
144
145/* Parse a positive integer.
146 */
147parse_pint l
148	= foldl acc 0 l
149{
150	acc sofar ch = sofar * 10 + parse_c ch;
151
152	/* Turn a char digit to a number.
153	 */
154	parse_c ch
155		= error (_ "not a digit"), !is_digit ch
156		= (int) ch - (int) '0';
157}
158
159/* Parse an integer, with an optional sign character.
160 */
161parse_int l
162	= error (_ "badly formed number"), !is_list_len 2 parts
163	= sign * n
164{
165	parts = splitpl [member "+-", is_digit] l;
166
167	n = parse_pint parts?1;
168	sign
169		= 1, parts?0 == [] || parts?0 == "+"
170		= -1;
171}
172
173/* Parse a float.
174 *	[+-]?[0-9]*([.][0-9]*)?(e[0-9]+)?
175 */
176parse_float l
177	= err, !is_list_len 4 parts
178	= (ipart + fpart) * 10 ** exp
179{
180	err = error (_ "badly formed number");
181
182	parts = splitpl [
183		member "+-0123456789", member ".0123456789",
184		member "eE", member "+-0123456789"
185	] l;
186
187	ipart = parse_int parts?0;
188	fpart
189		= 0, parts?1 == [];
190		= err, parts?1?0 != '.'
191		= parse_pint (tl parts?1) / 10 ** (len parts?1 - 1);
192	exp
193		= 0, parts?2 == [] && parts?3 == []
194		= err, parts?2 == []
195		= parse_int parts?3;
196
197}
198
199/* Parse a time in "hh:mm:ss" into seconds.
200
201We could do this in one line :)
202
203	= (sum @ map2 multiply (iterate (multiply 60) 1) @ reverse @ map
204	parse_pint @ map (subscript (splitpl [is_digit, equal ':', is_digit,
205	equal ':', is_digit] l))) [0,2,4];
206
207but it's totally unreadable.
208
209 */
210parse_time l
211	= error (_ "badly formed time"), !is_list_len 5 parts
212	= s + 60 * m + 60 * 60 * h
213{
214	parts = splitpl [is_digit, equal ':', is_digit, equal ':', is_digit] l;
215	h = parse_int parts?0;
216	m = parse_int parts?2;
217	s = parse_int parts?4;
218}
219
220/* matrix to convert D65 XYZ to D50 XYZ ... direct conversion, found by
221 * measuring a macbeth chart in D50 and D65 and doing a LMS to get a matrix
222 */
223D652D50_direct = Matrix
224	[[ 1.13529, -0.0604663, -0.0606321 ],
225	 [ 0.0975399, 0.935024, -0.0256156 ],
226	 [ -0.0336428, 0.0414702, 0.994135 ]];
227
228D502D65_direct = D652D50_direct ** -1;
229
230/* Convert normalised XYZ to bradford RGB.
231 */
232XYZ2RGBbrad = Matrix
233	[[0.8951,  0.2664, -0.1614],
234	 [-0.7502,  1.7135,  0.0367],
235	 [0.0389, -0.0685,  1.0296]];
236
237/* Convert bradford RGB to normalised XYZ.
238 */
239RGBbrad2XYZ = XYZ2RGBbrad ** -1;
240
241D93_whitepoint = Vector [89.7400, 100, 130.7700];
242D75_whitepoint = Vector [94.9682, 100, 122.5710];
243D65_whitepoint = Vector [95.0470, 100, 108.8827];
244D55_whitepoint = Vector [95.6831, 100, 92.0871];
245D50_whitepoint = Vector [96.4250, 100, 82.4680];
246A_whitepoint = Vector [109.8503, 100, 35.5849]; 	// 2856K
247B_whitepoint = Vector [99.0720, 100, 85.2230];		// 4874K
248C_whitepoint = Vector [98.0700, 100, 118.2300];		// 6774K
249E_whitepoint = Vector [100, 100, 100];			// ill. free
250D3250_whitepoint = Vector [105.6590, 100, 45.8501];
251
252Whitepoints = Enum [
253	$D93 => D93_whitepoint,
254	$D75 => D75_whitepoint,
255	$D65 => D65_whitepoint,
256	$D55 => D55_whitepoint,
257	$D50 => D50_whitepoint,
258	$A => A_whitepoint,
259	$B => B_whitepoint,
260	$C => C_whitepoint,
261	$E => E_whitepoint,
262	$D3250 => D3250_whitepoint
263];
264
265/* Convert D50 XYZ to D65 using the bradford chromatic adaptation approx.
266 */
267im_D502D65 xyz
268	= xyz'''
269{
270	xyz' = xyz / D50_whitepoint;
271
272	rgb = recomb XYZ2RGBbrad xyz';
273
274	// move white in bradford RGB
275	rgb' = rgb / Vector [0.94, 1.02, 1.33];
276
277	xyz'' = recomb RGBbrad2XYZ rgb';
278
279	// back to D65
280	xyz''' = xyz'' * D65_whitepoint;
281}
282
283/* Convert D65 XYZ to D50 using the bradford approx.
284 */
285im_D652D50 xyz
286	= xyz'''
287{
288	xyz' = xyz / D65_whitepoint;
289
290	rgb = recomb XYZ2RGBbrad xyz';
291
292	// move white in bradford RGB
293	rgb' = rgb * Vector [0.94, 1.02, 1.33];
294
295	xyz'' = recomb RGBbrad2XYZ rgb';
296
297	xyz''' = xyz'' * D50_whitepoint;
298}
299
300/* Convert D50 XYZ to Lab.
301 */
302im_D50XYZ2Lab xyz
303	= im_XYZ2Lab_temp xyz
304		D50_whitepoint.value?0
305		D50_whitepoint.value?1
306		D50_whitepoint.value?2;
307im_D50Lab2XYZ lab
308	= im_Lab2XYZ_temp lab
309		D50_whitepoint.value?0
310		D50_whitepoint.value?1
311		D50_whitepoint.value?2;
312
313/* ... and mono conversions
314 */
315im_sRGB2mono in
316	= (image_set_type Image_type.B_W @
317		clip2fmt (get_header "BandFmt" in) @
318			recomb (Matrix [[.3, .6, .1]])) in;
319im_mono2sRGB in
320	= image_set_type Image_type.sRGB (in ++ in ++ in);
321
322im_sRGB2Lab = im_XYZ2Lab @ im_sRGB2XYZ;
323
324im_Lab2sRGB = im_XYZ2sRGB @ im_Lab2XYZ;
325
326// from the 16 bit RGB and GREY formats
327im_1628 x = im_clip (x >> 8);
328im_162f x = x / 256;
329
330im_8216 x = (im_clip2us x) << 8;
331im_f216 x = im_clip2us (x * 256);
332
333im_RGB162GREY16 in
334	= (image_set_type Image_type.GREY16 @
335		clip2fmt (get_header "BandFmt" in) @
336			recomb (Matrix [[.3, .6, .1]])) in;
337im_GREY162RGB16 in
338	= image_set_type Image_type.RGB16 (in ++ in ++ in);
339
340/* apply a func to an image ... make it 1 or 3 bands, and reapply other bands
341 * on the way out. Except if it's LABPACK.
342 */
343colour_apply fn x
344	= fn x, b == 1 || b == 3 || c == Image_coding.LABPACK
345	= x''
346{
347	b = get_bands x;
348	c = get_coding x;
349
350	first
351		= extract_bands 0 3 x, b > 3
352		= extract_bands 0 1 x;
353	tail
354		= extract_bands 3 (b - 3) x, b > 3
355		= extract_bands 1 (b - 1) x;
356	x' = fn first;
357	x'' = x' ++ clip2fmt (get_format x') tail;
358}
359
360/* Any 1-ary colour op, applied to Vector/Image/Matrix or image
361 */
362colour_unary fn x
363	= oo_unary_function colour_op x, is_class x
364	= colour_apply fn x, is_image x
365	= colour_apply fn [x], is_real x
366	= error (_ "bad arguments to " ++ "colour_unary")
367{
368	// COMPOUND_REWRAP ... signal to the colour class to go to image and
369	// back
370	colour_op = Operator "colour_unary"
371		colour_object Operator_type.COMPOUND_REWRAP false;
372
373	colour_object x
374		= colour_real_list x, is_real_list x
375		= map colour_real_list x, is_matrix x
376		= colour_apply fn x, is_image x
377		= error (_ "bad arguments to " ++ "colour_unary");
378
379	colour_real_list l
380		= (to_matrix (fn (float) (to_image (Vector l)).value)).value?0;
381}
382
383/* Any symmetric 2-ary colour op, applied to Vector/Image/Matrix or image ...
384 * name is op name for error messages etc.
385 */
386colour_binary name fn x y
387	= oo_binary_function colour_op x y, is_class x
388	= oo_binary'_function colour_op x y, is_class y
389	= fn x y, is_image x && is_image y
390	= error (_ "bad arguments to " ++ name)
391{
392	colour_op = Operator name
393		colour_object Operator_type.COMPOUND_REWRAP true;
394
395	colour_object x y
396		= fn x y, is_image x && is_image y
397		= colour_real_list fn x y, is_real_list x && is_real_list y
398		= map (colour_real_list fn x) y, is_real_list x && is_matrix y
399		= map (colour_real_list (converse fn) y) x,
400			is_matrix x && is_real_list y
401		= map2 (colour_real_list fn) x y, is_matrix x && is_matrix y
402		= error (_ "bad arguments to " ++ name);
403
404	colour_real_list fn l1 l2
405		= (to_matrix (fn i1 i2)).value?0
406	{
407		i1 = (float) (to_image (Vector l1)).value;
408		i2 = (float) (to_image (Vector l2)).value;
409	}
410}
411
412_colour_conversion_table = [
413	/* Lines are [space-from, space-to, conversion function]. Could do
414	 * this as a big array, but table lookup feels safer.
415	 */
416	[B_W, B_W, image_set_type B_W],
417	[B_W, XYZ, im_sRGB2XYZ @ im_mono2sRGB @ im_clip],
418	[B_W, YXY, im_XYZ2Yxy @ im_sRGB2XYZ @ im_mono2sRGB @ im_clip],
419	[B_W, LAB, im_sRGB2Lab @ im_mono2sRGB @ im_clip],
420	[B_W, LCH, im_Lab2LCh @ im_sRGB2Lab @ im_mono2sRGB @ im_clip],
421	[B_W, UCS, im_XYZ2UCS @ im_sRGB2XYZ @ im_mono2sRGB @ im_clip],
422	[B_W, RGB, im_XYZ2disp @ im_sRGB2XYZ @ im_mono2sRGB @ im_clip],
423	[B_W, sRGB, im_mono2sRGB @ im_clip],
424	[B_W, RGB16, image_set_type RGB16 @ im_8216 @ im_mono2sRGB],
425	[B_W, GREY16, image_set_type GREY16 @ im_8216],
426	[B_W, LABQ, im_Lab2LabQ @ im_sRGB2Lab @ im_mono2sRGB @ im_clip],
427	[B_W, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_sRGB2Lab @
428		im_mono2sRGB @ im_clip],
429
430	[XYZ, B_W, im_sRGB2mono @ im_XYZ2sRGB @ im_clip2f],
431	[XYZ, XYZ, image_set_type XYZ],
432	[XYZ, YXY, im_XYZ2Yxy @ im_clip2f],
433	[XYZ, LAB, im_XYZ2Lab @ im_clip2f],
434	[XYZ, LCH, im_Lab2LCh @ im_XYZ2Lab],
435	[XYZ, UCS, im_XYZ2UCS @ im_clip2f],
436	[XYZ, RGB, im_XYZ2disp @ im_clip2f],
437	[XYZ, sRGB, im_XYZ2sRGB @ im_clip2f],
438	[XYZ, LABQ, im_Lab2LabQ @ im_XYZ2Lab @ im_clip2f],
439	[XYZ, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_XYZ2Lab @ im_clip2f],
440
441	[YXY, B_W, im_sRGB2mono @ im_XYZ2sRGB @ im_Yxy2XYZ @ im_clip2f],
442	[YXY, XYZ, im_Yxy2XYZ @ im_clip2f],
443	[YXY, YXY, image_set_type YXY],
444	[YXY, LAB, im_XYZ2Lab @ im_Yxy2XYZ @ im_clip2f],
445	[YXY, LCH, im_Lab2LCh @ im_XYZ2Lab @ im_Yxy2XYZ @ im_clip2f],
446	[YXY, UCS, im_XYZ2UCS @ im_Yxy2XYZ @ im_clip2f],
447	[YXY, RGB, im_XYZ2disp @ im_Yxy2XYZ @ im_clip2f],
448	[YXY, sRGB, im_XYZ2sRGB @ im_Yxy2XYZ @ im_clip2f],
449	[YXY, LABQ, im_Lab2LabQ @ im_XYZ2Lab @ im_Yxy2XYZ @ im_clip2f],
450	[YXY, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_XYZ2Lab @ im_Yxy2XYZ @
451		im_clip2f],
452
453	[LAB, B_W, im_sRGB2mono @ im_Lab2sRGB @ im_Lab2XYZ @ im_clip2f],
454	[LAB, XYZ, im_Lab2XYZ @ im_clip2f],
455	[LAB, YXY, im_XYZ2Yxy @ im_Lab2XYZ @ im_clip2f],
456	[LAB, LAB, image_set_type LAB @ im_clip2f],
457	[LAB, LCH, im_Lab2LCh @ im_clip2f],
458	[LAB, UCS, im_Lab2UCS @ im_clip2f],
459	[LAB, RGB, im_Lab2disp @ im_clip2f],
460	[LAB, sRGB, im_Lab2sRGB @ im_clip2f],
461	[LAB, LABQ, im_Lab2LabQ @ im_clip2f],
462	[LAB, LABS, im_Lab2LabS @ im_clip2f],
463
464	[LCH, B_W, im_sRGB2mono @ im_Lab2sRGB @ im_LCh2Lab @ im_clip2f],
465	[LCH, XYZ, im_Lab2XYZ @ im_LCh2Lab @ im_clip2f],
466	[LCH, YXY, im_XYZ2Yxy @ im_Lab2sRGB @ im_LCh2Lab @ im_clip2f],
467	[LCH, LAB, im_LCh2Lab @ im_clip2f],
468	[LCH, LCH, image_set_type LCH],
469	[LCH, UCS, im_LCh2UCS @ im_clip2f],
470	[LCH, RGB, im_Lab2disp @ im_LCh2Lab @ im_clip2f],
471	[LCH, sRGB, im_Lab2sRGB @ im_LCh2Lab @ im_clip2f],
472	[LCH, LABQ, im_Lab2LabQ @ im_LCh2Lab @ im_clip2f],
473	[LCH, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_LCh2Lab @ im_clip2f],
474
475	[UCS, B_W, im_sRGB2mono @ im_XYZ2sRGB @ im_UCS2XYZ @ im_clip2f],
476	[UCS, XYZ, im_UCS2XYZ @ im_clip2f],
477	[UCS, YXY, im_XYZ2Yxy @ im_Lab2XYZ @ im_UCS2Lab @ im_clip2f],
478	[UCS, LAB, im_UCS2Lab @ im_clip2f],
479	[UCS, LCH, im_UCS2LCh @ im_clip2f],
480	[UCS, UCS, image_set_type UCS],
481	[UCS, RGB, im_Lab2disp @ im_UCS2Lab @ im_clip2f],
482	[UCS, sRGB, im_Lab2sRGB @ im_UCS2Lab @ im_clip2f],
483	[UCS, LABQ, im_Lab2LabQ @ im_UCS2Lab @ im_clip2f],
484	[UCS, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_UCS2Lab @ im_clip2f],
485
486	[RGB, B_W, im_sRGB2mono @ im_XYZ2sRGB @ im_disp2XYZ @ im_clip],
487	[RGB, XYZ, im_disp2XYZ @ im_clip],
488	[RGB, YXY, im_XYZ2Yxy @ im_disp2XYZ @ im_clip],
489	[RGB, LAB, im_disp2Lab @ im_clip],
490	[RGB, LCH, im_Lab2LCh @ im_disp2Lab @ im_clip],
491	[RGB, UCS, im_Lab2UCS @ im_disp2Lab @ im_clip],
492	[RGB, RGB, image_set_type RGB],
493	[RGB, sRGB, im_XYZ2sRGB @ im_disp2XYZ @ im_clip],
494	[RGB, RGB16, image_set_type RGB16 @ im_8216],
495	[RGB, GREY16, image_set_type GREY16 @ im_8216 @ im_sRGB2mono],
496	[RGB, LABQ, im_Lab2LabQ @ im_disp2Lab @ im_clip],
497	[RGB, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_disp2Lab @ im_clip],
498
499	[sRGB, B_W, im_sRGB2mono],
500	[sRGB, XYZ, im_sRGB2XYZ @ im_clip],
501	[sRGB, YXY, im_XYZ2Yxy @ im_sRGB2XYZ @ im_clip],
502	[sRGB, LAB, im_sRGB2Lab @ im_clip],
503	[sRGB, LCH, im_Lab2LCh @ im_sRGB2Lab @ im_clip],
504	[sRGB, UCS, im_XYZ2UCS @ im_sRGB2XYZ @ im_clip],
505	[sRGB, RGB, im_XYZ2disp @ im_sRGB2XYZ @ im_clip],
506	[sRGB, sRGB, image_set_type sRGB],
507	[sRGB, RGB16, image_set_type RGB16 @ im_8216],
508	[sRGB, GREY16, image_set_type GREY16 @ im_8216 @ im_sRGB2mono],
509	[sRGB, LABQ, im_Lab2LabQ @ im_sRGB2Lab @ im_clip],
510	[sRGB, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_sRGB2Lab @ im_clip],
511
512	[RGB16, B_W, im_1628 @ im_sRGB2mono],
513	[RGB16, RGB, image_set_type RGB @ im_1628],
514	[RGB16, sRGB, image_set_type sRGB @ im_1628],
515	[RGB16, RGB16, image_set_type RGB16],
516	[RGB16, GREY16, im_RGB162GREY16],
517
518	[GREY16, B_W, image_set_type B_W @ im_1628],
519	[GREY16, RGB, im_mono2sRGB @ im_1628],
520	[GREY16, sRGB, im_mono2sRGB @ im_1628],
521	[GREY16, RGB16, im_GREY162RGB16],
522	[GREY16, GREY16, image_set_type GREY16],
523
524	[LABQ, B_W, im_sRGB2mono @ im_Lab2sRGB @ im_LabQ2Lab],
525	[LABQ, XYZ, im_Lab2XYZ @ im_LabQ2Lab],
526	[LABQ, YXY, im_XYZ2Yxy @ im_Lab2XYZ @ im_LabQ2Lab],
527	[LABQ, LAB, im_LabQ2Lab],
528	[LABQ, LCH, im_Lab2LCh @ im_LabQ2Lab],
529	[LABQ, UCS, im_Lab2UCS @ im_LabQ2Lab],
530	[LABQ, RGB, im_LabQ2disp],
531	[LABQ, sRGB, im_Lab2sRGB @ im_LabQ2Lab],
532	[LABQ, LABQ, image_set_type LABQ],
533	[LABQ, LABS, im_LabQ2LabS],
534
535	[LABS, B_W, im_sRGB2mono @ im_Lab2sRGB @ im_LabQ2Lab @
536		im_LabS2LabQ @ im_clip2s],
537	[LABS, XYZ, im_Lab2XYZ @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
538	[LABS, YXY, im_XYZ2Yxy @
539		im_Lab2XYZ @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
540	[LABS, LAB, im_LabS2Lab],
541	[LABS, LCH, im_Lab2LCh @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
542	[LABS, UCS, im_Lab2UCS @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
543	[LABS, RGB, im_LabQ2disp @ im_LabS2LabQ @ im_clip2s],
544	[LABS, sRGB, im_XYZ2sRGB @
545		im_Lab2XYZ @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
546	[LABS, LABQ, im_LabS2LabQ @ im_clip2s],
547	[LABS, LABS, image_set_type LABS]
548]
549{
550	/* From Image_type ... repeat here for brevity. Use same ordering as
551	 * in Colour menu for consistency.
552	 */
553	B_W = 1;
554	XYZ = 12;
555	YXY = 23;
556	LAB = 13;
557	LCH = 19;
558	UCS = 18;
559	RGB = 17;
560	sRGB = 22;
561	RGB16 = 25;
562	GREY16 = 26;
563	LABQ = 16;
564	LABS = 21;
565}
566
567/* Transform between two colour spaces.
568 */
569colour_transform from to in
570	= colour_unary _colour_conversion_table?i?2 in, i >= 0
571	= error (_ "unable to convert " ++ Image_type.type_names.get_name from ++
572		_ " to " ++ Image_type.type_names.get_name to)
573{
574	match x = x?0 == from && x?1 == to;
575	i = index match _colour_conversion_table;
576}
577
578/* Transform to a colour space, assuming the type field in the input is
579 * correct
580 */
581colour_transform_to to in = colour_transform (get_type in) to in;
582
583/* String for path separator on this platform.
584 */
585path_separator = expand "$SEP";
586
587/* Form a relative pathname.
588 * 	path_relative ["home", "john"] == "home/john"
589 * 	path_relative [] == ""
590 */
591path_relative l = join_sep path_separator l;
592
593/* Form an absolute pathname.
594 * 	path_absolute ["home", "john"] == "/home/john"
595 * 	path_absolute [] == "/"
596 * If the first component looks like 'A:', don't add an initial separator.
597 */
598path_absolute l
599	= path_relative l,
600		len l?0 > 1 && is_letter l?0?0 && l?0?1 == ':'
601	= path_separator ++ path_relative l;
602
603/* Parse a pathname.
604 *	path_parse "/home/john" == ["home", "john"]
605 *	path_parse "home/john" == ["home", "john"]
606 */
607path_parse str
608	= split (equal path_separator?0) str;
609
610