1Tasks_capture_item = class
2	Menupullright "_Capture"
3		"useful stuff for capturing and preprocessing images" {
4	Video_item = class
5		Menuaction "Capture _Video Frame" "capture a frame of still video" {
6		// shortcut to prefs
7		prefs = Workspaces.Preferences;
8
9		action = class
10			_result {
11			_vislevel = 3;
12
13			device = prefs.VIDEO_DEVICE;
14			channel = Option "Input channel" [
15				"TV",
16				"Composite 1",
17				"Composite 2",
18				"Composite 3"
19			] prefs.VIDEO_CHANNEL;
20			brightness = Slider 0 32767 prefs.VIDEO_BRIGHTNESS;
21			colour = Slider 0 32767 prefs.VIDEO_COLOUR;
22			contrast = Slider 0 32767 prefs.VIDEO_CONTRAST;
23			hue = Slider 0 32767 prefs.VIDEO_HUE;
24			frames_hint = "Average this many frames:";
25			frames = Slider 0 100 prefs.VIDEO_FRAMES;
26			mono = Toggle "Monochrome grab" prefs.VIDEO_MONO;
27			crop = Toggle "Crop image" prefs.VIDEO_CROP;
28
29			// grab, but hide it ... if we let the crop edit
30			_raw_grab = Image (im_video_v4l1 device channel.value
31				brightness.value colour.value contrast.value
32				hue.value frames.value);
33
34			edit_crop
35				= Region _raw_grab left top width height
36			{
37				left = prefs.VIDEO_CROP_LEFT;
38				top = prefs.VIDEO_CROP_TOP;
39				width = min_pair
40					prefs.VIDEO_CROP_WIDTH (_raw_grab.width + left);
41				height = min_pair
42					prefs.VIDEO_CROP_HEIGHT (_raw_grab.height + top);
43			}
44
45			aspect_ratio = Expression "Stretch vertically by"
46				prefs.VIDEO_ASPECT;
47
48			_result
49				= frame'
50			{
51				frame
52					= edit_crop, crop
53					= _raw_grab;
54
55				frame'
56					= colour_transform_to Image_type.B_W frame, mono
57					= frame;
58			}
59		}
60	}
61
62	Smooth_image_item = class
63		Menuaction "_Smooth" "remove small features from image" {
64		action in = class
65			_result {
66			_vislevel = 3;
67
68			feature = Slider 1 50 20;
69
70			_result = map_unary (smooth feature.value) in;
71		}
72	}
73
74	Light_correct_item = class
75		Menuaction "_Flatfield" "use white image w to flatfield image i" {
76		action w i
77			= map_binary wc w i
78		{
79			wc w i
80				= clip2fmt i.format (w' * i)
81			{
82				fac = mean w / max w;
83				w' = fac * (max w / w);
84			}
85		}
86	}
87
88	Image_rank_item = Filter_rank_item.Image_rank_item;
89
90	Tilt_item = Filter_tilt_item;
91
92	sep1 = Menuseparator;
93
94	White_balance_item = class
95		Menuaction "_White Balance"
96			"use average of small image to set white of large image" {
97		action a b = class
98			_result {
99			_vislevel = 3;
100
101			white_hint = "Set image white to:";
102			white = Colour_picker "Lab" [100, 0, 0];
103
104			_result
105					= map_binary wb a b
106			{
107				wb a b
108					= colour_transform_to (get_type image) image_xyz'
109				{
110					area x = x.width * x.height;
111					larger x y = area x > area y;
112					args = sortc larger [a, b];
113					image = args?0;
114					patch = args?1;
115					to_xyz = colour_transform_to Image_type.XYZ;
116
117					// white balance in XYZ
118					patch_xyz = to_colour (to_xyz patch);
119					white_xyz = to_xyz white;
120
121					facs = (mean patch_xyz / mean white_xyz) *
122						(white_xyz / patch_xyz);
123
124					image_xyz = to_xyz image;
125					image_xyz' = image_xyz * facs;
126				}
127			}
128		}
129	}
130
131	Gamma_item = Image_levels_item.Gamma_item;
132
133	Tone_item = Image_levels_item.Tone_item;
134
135	sep2 = Menuseparator;
136
137	Crop_item = Image_crop_item;
138
139	Rotate_item = Image_transform_item.Rotate_item;
140
141	Flip_item = Image_transform_item.Flip_item;
142
143	Resize_item = Image_transform_item.Resize_item;
144
145	Rubber_item = Image_transform_item.Image_rubber_item;
146
147	sep3 = Menuseparator;
148
149	ICC_item = Colour_icc_item;
150
151	Temp_item = Colour_temperature_item;
152
153	Find_calib_item = class
154		Menuaction "Find _Colour Calibration"
155			"find an RGB -> XYZ transform from an image of a colour chart" {
156		action image = class
157			_result {
158			_check_args = [
159				[image, "image", check_Image]
160			];
161			_vislevel = 3;
162
163			// get macbeth data file to use
164			macbeth = Pathname "Pick a Macbeth data file"
165				"$VIPSHOME/share/$PACKAGE/data/macbeth_lab_d65.mat";
166
167			// get max of input image
168			_max_value = Image_format.maxval image.format;
169
170			// measure chart image
171			_camera = im_measure image.value 0 0 image.width image.height 6 4;
172
173			// load true values
174			_true_Lab = Matrix_file macbeth.value;
175			_true_XYZ = colour_transform
176				Image_type.LAB Image_type.XYZ _true_Lab;
177
178			// get Ys of greyscale
179			_true_grey_Y = map (extract 1) (drop 18 _true_XYZ.value);
180
181			// camera greyscale (all bands)
182			_camera_grey = drop 18 _camera.value;
183
184			// normalise both to 0-1 and combine
185			_camera_grey' = map (map (multiply (1 / _max_value))) _camera_grey;
186			_true_grey_Y' = map (multiply (1 / 100)) _true_grey_Y;
187			_comb = Matrix (map2 cons _true_grey_Y' _camera_grey');
188
189			// make a linearising lut ... zero on left
190			_linear_lut = im_invertlut _comb (_max_value + 1);
191
192			// and display it
193			linearising_lut = Image _linear_lut;
194
195			// map the original image through the lineariser to
196			// get linear 0-1 RGB image
197			_image' = hist_map linearising_lut.value image.value;
198
199			// remeasure and solve for RGB -> XYZ
200			_camera' = im_measure _image' 0 0 image.width image.height 6 4;
201			_pinv = (transpose _camera' * _camera') ** -1;
202			M = transpose (_pinv * transpose _camera' * _true_XYZ);
203
204			// convert linear RGB camera to Lab
205			_result = (Image @
206				colour_transform Image_type.XYZ Image_type.LAB @
207				cast_float @
208				recomb M) _image';
209
210			// measure again and compute dE76
211			_camera'' = im_measure _result.value 0 0
212				image.width image.height 6 4;
213
214			_dEs = map abs_vec (map Vector (_camera'' - _true_Lab).value);
215			final_dE76 = mean (Vector _dEs);
216			_max_dE = foldr max_pair 0 _dEs;
217			_worst = index (equal _max_dE) _dEs;
218			worst_patch = _macbeth_names ? _worst ++ " (patch " ++
219				print (_worst + 1) ++ ", " ++
220			print _max_dE ++ " dE)";
221		}
222	}
223
224	Apply_calib_item = class
225		Menuaction "_Apply Colour Calibration"
226			"apply an RGB -> LAB transform to an image" {
227		action a b
228			= result, is_instanceof calib_name calib && is_Image image
229			= error (_ "bad arguments to " ++ "Calibrate_image")
230		{
231			// the name of the calib object we need
232			calib_name = "Tasks_capture_item.Find_calib_item.action";
233
234			// get the Calibrate_chart arg first
235			args = sortc (const (is_instanceof calib_name)) [a, b];
236			calib = args?1;
237			image = args?0;
238
239			// map the original image through the lineariser to get
240			// linear 0-1 RGB image
241			image' = hist_map calib.linearising_lut image;
242
243			// convert linear RGB camera to Lab
244			result = colour_transform Image_type.XYZ Image_type.LAB
245				((float) (recomb calib.M image'));
246		}
247	}
248}
249
250Tasks_mosaic_item = class
251	Menupullright "_Mosaic" "building image mosaics" {
252	/* Check and group a point list by image.
253	 */
254	mosaic_sort_test l
255		= error "mosaic: not all points",
256			!is_listof is_Mark l
257		= error "mosaic: points not on two images",
258			len images != 2
259		= error "mosaic: images do not match in format and coding",
260			!all_equal (map get_format l) || !all_equal (map get_coding l)
261		= error "mosaic: not same number of points on each image",
262			!foldr1 equal (map len l')
263		= l'
264	{
265		// test for all elements of a list equal
266		all_equal l = land (map (equal (hd l)) (tl l));
267
268		// all the different images
269		images = mkset pointer_equal (map get_image l);
270
271		// find all points defined on image
272		test_image image p = (get_image p) === image;
273		find l image = filter (test_image image) l;
274
275		// group point list by image
276		l' = map (find l) images;
277	}
278
279	/* Sort a point group to get right before left, and within each group to
280	 * get above before below.
281	 */
282	mosaic_sort_lr l
283		= l''
284	{
285		// sort to get upper point first
286		above a b = a.top < b.top;
287		l' = map (sortc above) l;
288
289		// sort to get right group before left group
290		right a b = a?0.left > b?0.left;
291		l'' = sortc right l';
292	}
293
294	/* Sort a point group to get top before bottom, and within each group to
295	 * get left before right.
296	 */
297	mosaic_sort_tb l
298		= l''
299	{
300		// sort to get upper point first
301		left a b = a.left < b.left;
302		l' = map (sortc left) l;
303
304		// sort to get right group before left group
305		below a b = a?0.top > b?0.top;
306		l'' = sortc below l';
307	}
308
309	/* Put 'em together! Group by image, sort vertically (or horizontally) with
310	 * one of the above, transpose to get pairs matched up, and flatten again.
311	 */
312	mosaic_sort fn = concat @ transpose @ fn @ mosaic_sort_test;
313
314	Mosaic_1point_item = class
315		Menupullright "_One Point" "join two images with a single tie point" {
316		check_ab_args a b = [
317			[a, "a", check_Mark],
318			[b, "b", check_Mark]
319		];
320
321		// shortcut to prefs
322		prefs = Workspaces.Preferences;
323		search_area = prefs.MOSAIC_WINDOW_SIZE;
324		object_size = prefs.MOSAIC_OBJECT_SIZE;
325		blend_width_widget = Slider 0 100 prefs.MOSAIC_MAX_BLEND_WIDTH;
326		refine_widget = Toggle "Refine selected tie-points" prefs.MOSAIC_REFINE;
327
328		lr_mos _refine a b = class
329			Image _result {
330			_check_args = check_ab_args a b;
331
332			blend_width = blend_width_widget;
333			refine = _refine;
334
335			_result
336				= im_lrmosaic a'.image.value b'.image.value 0
337					a'.left a'.top b'.left b'.top
338					(object_size / 2) (search_area / 2) 0 blend_width.value,
339						refine
340				= im_lrmerge a'.image.value b'.image.value
341					(b'.left - a'.left) (b'.top - a'.top) blend_width.value
342			{
343				sorted = mosaic_sort mosaic_sort_lr [a, b];
344				a' = sorted?0;
345				b' = sorted?1;
346			}
347		}
348
349		tb_mos _refine a b = class
350			Image _result {
351			_check_args = check_ab_args a b;
352
353			blend_width = blend_width_widget;
354			refine = _refine;
355
356			_result
357				= im_tbmosaic a'.image.value b'.image.value 0
358					a'.left a'.top b'.left b'.top
359					(object_size / 2) (search_area / 2) 0 blend_width.value,
360						refine
361				= im_tbmerge a'.image.value b'.image.value
362					(b'.left - a'.left) (b'.top - a'.top) blend_width.value
363			{
364				sorted = mosaic_sort mosaic_sort_tb [a, b];
365				a' = sorted?0;
366				b' = sorted?1;
367			}
368		}
369
370		Left_right_item = class
371			Menuaction "_Left to Right"
372				"join two images left-right with a single tie point" {
373			action a b = lr_mos refine_widget a b;
374		}
375
376		Top_bottom_item = class
377			Menuaction "_Top to Bottom"
378				"join two images top-bottom with a single tie point" {
379			action a b = tb_mos refine_widget a b;
380		}
381
382		sep1 = Menuseparator;
383
384		Left_right_manual_item = class
385			Menuaction "Manual L_eft to Right"
386				"join left-right, no auto-adjust of tie points" {
387			action a b = lr_mos false a b;
388		}
389
390		Top_bottom_manual_item = class
391			Menuaction "Manual T_op to Bottom"
392				"join top-bottom, no auto-adjust of tie points" {
393			action a b = tb_mos false a b;
394		}
395	}
396
397	Mosaic_2point_item = class
398		Menupullright "_Two Point" "join two images with two tie points" {
399		check_abcd_args a b c d = [
400			[a, "a", check_Mark],
401			[b, "b", check_Mark],
402			[c, "c", check_Mark],
403			[d, "d", check_Mark]
404		];
405
406		// shortcut to prefs
407		prefs = Workspaces.Preferences;
408		search_area = prefs.MOSAIC_WINDOW_SIZE;
409		object_size = prefs.MOSAIC_OBJECT_SIZE;
410		blend_width_widget = Slider 0 100 prefs.MOSAIC_MAX_BLEND_WIDTH;
411		refine_widget = Toggle "Refine selected tie-points" prefs.MOSAIC_REFINE;
412
413		Left_right_item = class
414			Menuaction "_Left to Right"
415				"join two images left-right with a pair of tie points" {
416			action a b c d = class
417				Image _result {
418				_check_args = check_abcd_args a b c d;
419
420				blend_width = blend_width_widget;
421				refine = refine_widget;
422
423				_result
424					= im_lrmosaic1 a'.image.value b'.image.value 0
425						a'.left a'.top b'.left b'.top
426						c'.left c'.top d'.left d'.top
427						(object_size / 2) (search_area / 2)
428						0 blend_width.value,
429							refine
430					= im_lrmerge1 a'.image.value b'.image.value
431						a'.left a'.top b'.left b'.top
432						c'.left c'.top d'.left d'.top
433						blend_width.value
434				{
435					sorted = mosaic_sort mosaic_sort_lr [a, b, c, d];
436					a' = sorted?0;
437					b' = sorted?1;
438					c' = sorted?2;
439					d' = sorted?3;
440				}
441			}
442		}
443
444		Top_bottom_item = class
445			Menuaction "_Top to Bottom"
446				"join two images top-bottom with a pair of tie points" {
447			action a b c d = class
448				Image _result {
449				_check_args = check_abcd_args a b c d;
450
451				blend_width = blend_width_widget;
452				refine = refine_widget;
453
454				_result
455					= im_tbmosaic1 a'.image.value b'.image.value 0
456						a'.left a'.top b'.left b'.top
457						c'.left c'.top d'.left d'.top
458						(object_size / 2) (search_area / 2)
459						0 blend_width.value,
460							refine
461					= im_tbmerge1 a'.image.value b'.image.value
462						a'.left a'.top b'.left b'.top
463						c'.left c'.top d'.left d'.top
464						blend_width.value
465				{
466					sorted = mosaic_sort mosaic_sort_tb [a, b, c, d];
467					a' = sorted?0;
468					b' = sorted?1;
469					c' = sorted?2;
470					d' = sorted?3;
471				}
472			}
473		}
474	}
475
476	sep1 = Menuseparator;
477
478	Balance_item = class
479		Menuaction "Mosaic _Balance"
480			"disassemble mosaic, scale brightness to match, reassemble" {
481		action x
482			= map_unary balance x
483		{
484			balance x
485				= oo_unary_function balance_op x, is_class x
486				= im_global_balancef x
487					Workspaces.Preferences.MOSAIC_BALANCE_GAMMA,
488					is_image x
489				= error (_ "bad arguments to " ++ "balance")
490			{
491				balance_op = Operator "balance" balance
492					Operator_type.COMPOUND_REWRAP false;
493			}
494		}
495	}
496
497////////////////////////////////////////////////////////////////////////////////////
498////////////////////////////////////////////////////////////////////////////////////
499Manual_balance_item = class
500	Menupullright "Manual B_alance" "balance tonality of user defined areas" {
501	prefs = Workspaces.Preferences;
502
503	////////////////////////////////////////////////////////////////////////////////////
504	Balance_find_item = class
505		Menuaction "_Find Values"
506			 "calculates values required to scale and offset balance user defined areas in a given image"
507			 /* Outputs a matrix of scale and offset values. Eg. Values required to balance the secondary
508			  * structure in an X-ray image.  Takes an X-ray image an 8-bit control mask and a list of
509			  * 8-bit reference masks, where the masks are white on a black background.
510			  */
511	{
512		action im_in m_control m_group = class
513			Matrix values{
514			_vislevel = 1;
515
516			_control_im = if_then_else m_control im_in 0;
517			_control_meanmax = so_meanmax _control_im;
518			_group_check = is_instanceof "Group"  m_group;
519			_m_list = m_group.value, _group_check
520		     		= m_group;
521
522			process m_current mat_in = mat_out
523				{so_values  = so_calculate _control_meanmax im_in m_current;
524				 mat_out = join [so_values] mat_in;}
525
526			values = (foldr process [] _m_list);
527		}
528	}
529
530	////////////////////////////////////////////////////////////////////////////////////
531	Balance_check_item = class
532		Menuaction "_Check Values"
533			 "allows calculated set of scale and offset values to be checked and adjusted if required"
534			 /* Outputs adjusted matrix of scale and offset values and scale and offset image maps.
535			  * Eg. Check values required to balance the secondary structure in an X-ray image.
536			  * Takes an X-ray image an 8-bit control mask and a list of 8-bit reference masks,
537			  * where the masks are white on a black background.
538			  */
539	{
540		action im_in m_matrix m_group = class
541			Image value
542			{
543			_vislevel = 3;
544
545			blur = Slider 1 10 1;
546			_blur = (blur.value/2 + 0.5), blur.value > 1
547				  =  1;
548
549			_group_check = is_instanceof "Group"  m_group;
550			_m_list = m_group.value, _group_check
551		     		= m_group;
552
553			adjust = Matrix_rec mat_a
554				{
555				no_masks = len _m_list;
556				mat_a = replicate no_masks [0, 0];
557				}
558
559		// Apply the user defined adjustments to the inputted matrix of scale and offset values
560			_adjusted = map2 fn_adjust m_matrix.value adjust.value;
561				fn_adjust a b = [(a?0 + b?0), (a?1 + (a?1 * b?1))];
562
563			_scaled_ims = map (fn_so_apply im_in) _adjusted;
564				fn_so_apply im so = map_unary adj im
565					{adj im = im * (so?0) + (so?1);}
566			_im_pairs = zip2 _m_list _scaled_ims;
567
568		// Prepare black images as starting point. ////////////
569			_blank = image_new (_m_list?0).width (_m_list?0).height 1 6 Image_coding.NOCODING 1 0 0 0;
570			_pair_start = [(_blank + 1), _blank];
571
572			Build = Toggle "Build Scale and Offset Correction Images" false;
573
574			Output = class
575				{
576				_vislevel = 1;
577				scale_im = _build?0;
578				offset_im = _build?1;
579				so_values = Matrix _adjusted;
580
581				_build = [Image so_images?0, Image so_images?1], Build
582				       = ["Scale image not built.", "Offset image not built."]
583					{
584					m_list' = transpose [_m_list];
585					m_all = map2 join m_list' _adjusted;
586					so_images = foldr process_2 _pair_start m_all;
587					}
588				}
589
590			value = (foldr process_1 im_in_b _im_pairs).value
591				{im_in_b = map_unary cast_float im_in;}
592
593			process_1 m_current im_start
594				= im_out
595				{
596				bl_mask    = convsep (matrix_blur _blur) (get_image m_current?0);
597				blended_im  = im_blend bl_mask (m_current?1).value im_start.value;
598				im_out      = Image (clip2fmt im_start.format blended_im);
599				}
600
601			// Process for building scale and offset image.
602			process_2 current p_start
603				= p_out
604				{
605				im_s = if ((current?0) > 128) then current?1 else _blank;
606				im_o = if ((current?0) > 128) then current?2 else _blank;
607
608				im_s' = convsep (matrix_blur _blur) (im_s != 0);
609				im_o' = convsep (matrix_blur _blur) (im_o != 0);
610
611				im_s'' = im_blend im_s'.value im_s.value p_start?0;
612				im_o'' = im_blend im_o'.value im_o.value p_start?1;
613
614				p_out  = [im_s'', im_o''];
615				}
616		}
617	}
618
619	////////////////////////////////////////////////////////////////////////////////////
620	Balance_apply_item = class
621		Menuaction "_Apply Values"
622			 "apply scale and offset corrections, defined as image maps, to a given image"
623			 /* Outputs the balanced image. Eg. Balance the secondary structure in an X-ray image.  Takes an
624			  * X-ray image an 32-bit float scale image and a 32-bit offset image.
625			  */
626	{
627		action im_in scale_im offset_im = class
628			Image value
629			{
630			_vislevel = 1;
631
632			xfactor = im_in.width/scale_im.width;
633			yfactor = im_in.height/scale_im.height;
634
635			_scale_im = resize xfactor yfactor 1 scale_im;
636			_offset_im = resize xfactor yfactor 1 offset_im;
637
638			value = get_image ( clip2fmt im_in.format ( ( im_in * _scale_im ) +  _offset_im ) );
639			}
640		}
641}
642
643    Tilt_item = Filter_tilt_item;
644
645	sep2 = Menuseparator;
646
647	Rebuild_item = class
648		Menuaction "_Rebuild"
649			"disassemble mosaic, substitute image files and reassemble" {
650		action x = class
651			_result {
652			_vislevel = 3;
653
654			old = String "In each filename, replace" "foo";
655			new = String "With" "bar";
656
657			_result
658				= map_unary remosaic x
659			{
660				remosaic image
661					= Image (im_remosaic image.value old.value new.value);
662			}
663		}
664	}
665
666	sep3 = Menuseparator;
667
668Clone_area_item = class
669   Menuaction "_Clone Area"
670       "replace dark or light section of im1 with pixels from im2"
671   {
672   action im1 im2 = class
673       _result {
674       _check_args = [
675           [im1, "im1", check_Image],
676           [im2, "im2", check_Image]
677       ];
678                 _vislevel = 3;                            /* Region on first
679image placed in the top left hand corner,
680        * positioned and size relative to the height and width of im1.
681        */
682       r1 = Region_relative im1 0.05 0.05 0.05 0.05;
683             /* Mark on second image placed in the top left hand corner,
684        * positioned relative to the height and width of im2. Used to
685        * define _r2, the region from which the section of image is cloned
686        * from.
687        */
688       p2 = Mark_relative im2 0.05 0.05;              _r2 = Region im2 p2.left
689p2.top r1.width r1.height;
690             mask = [r1 <= Options.scale_cutoff, r1 >=
691Options.scale_cutoff]?(Options.replace);
692                 Options = class
693           {
694           _vislevel = 3;
695                     pause = Toggle "Pause process" true;
696                         /* Option toggle used to define whether the user is
697            * replacing a dark or a light area.
698            */
699           replace = Option "Replace" [ "A Dark Area", "A Light Area" ] 1;
700
701           // Used to select the area to be replaced.
702            scale_cutoff = Slider 0.01 mx (mx / 2)
703               {mx = Image_format.maxval im1.format;}
704             //Allows replacement with scale&offset balanced gaussian noise.
705           balance = Toggle "Balance cloned data to match surroundings." true;
706                             //Allows replacement with scale&offset balanced
707                             //gaussian noise.
708           process = Toggle "Replace area with Gaussian noise." false;
709           }
710                     _result    = im1, Options.pause
711               = Image (im_insert im1.value patch r1.left r1.top)
712           {              r2 = Region im2 p2.left p2.top r1.width r1.height;
713           ref_meanmax = so_meanmax (if mask then 0 else r1);
714                     mask8 = Matrix_mor [[255, 255, 255], [255, 255, 255],
715[255, 255, 255]];
716           mask_a = map_unary (dilate mask8) mask;
717           mask_b = convsep (matrix_blur 2) mask_a;
718                     patch = so_balance ref_meanmax r1 r2 mask_b
719Options.process, Options.balance
720                 = so_balance ref_meanmax r1 r2 mask_b Options.process,
721Options.process
722                 = im_blend (get_image mask_b) (get_image r2) (get_image r1);
723           }
724       }
725   }
726}
727
728Tasks_frame_item = Frame_item;
729
730Tasks_print_item = class
731	Menupullright "_Print" "useful stuff for image output" {
732
733	Rotate_item = Image_transform_item.Rotate_item;
734
735	Flip_item = Image_transform_item.Flip_item;
736
737	Resize_item = Image_transform_item.Resize_item;
738
739	Tone_item = class
740		Menuaction "_Adjust Tone Curve" "adjust tone curve on L*" {
741		action x = class
742			_result {
743			_vislevel = 3;
744
745			auto = Option "Set black and white point" [
746				"Manually",
747				"Automatically from image histogram"
748			] 0;
749			black = Slider 0 100 0;
750			white = Slider 0 100 100;
751
752			shadow_point = Slider 0.1 0.3 0.2;
753			mid_point = Slider 0.4 0.6 0.5;
754			highlight_point = Slider 0.7 0.9 0.8;
755
756			shadow_adjust = Slider (-15) 15 0;
757			mid_adjust = Slider (-30) 30 0;
758			highlight_adjust = Slider (-15) 15 0;
759
760			preview_curve = Image (im_tone_build black.value white.value
761				shadow_point.value mid_point.value highlight_point.value
762				shadow_adjust.value mid_adjust.value highlight_adjust.value);
763
764			_result
765				= map_unary process x
766			{
767				process image
768					= tone_map curve lab
769				{
770					lab = colour_transform_to Image_type.LABQ image;
771					curve
772						= preview_curve, auto == 0
773						= tone_analyse
774							shadow_point mid_point highlight_point
775							shadow_adjust mid_adjust highlight_adjust
776							lab;
777				}
778			}
779		}
780	}
781
782	Sharpen_item = class
783		Menuaction "_Sharpen"
784			"unsharp filter tuned for typical inkjet printers" {
785		action x = class
786			_result {
787			_vislevel = 3;
788
789			// strange order forced on us by need to keep numbering backwards
790			// compatible though 7.10
791			target_dpi = Option "Sharpen for print at" [
792				"300 dpi",
793				"150 dpi",
794				"75 dpi",
795				"400 dpi"
796			] 0;
797
798			_result
799				= map_unary process x
800			{
801				process image
802					= sharpen params?0 params?1
803						params?2 params?3 params?4 params?5
804						(colour_transform_to Image_type.LABQ image)
805				{
806					// sharpen params for various dpi
807					// just change the size of the area we search
808					param_table = [
809						[7, 2.5, 40, 20, 0.5, 1.5],
810						[5, 2.5, 40, 20, 0.5, 1.5],
811						[3, 2.5, 40, 20, 0.5, 1.5],
812						[11, 2.5, 40, 20, 0.5, 1.5]
813					];
814					params = param_table?target_dpi;
815				}
816			}
817		}
818	}
819
820	sep1 = Menuseparator;
821
822	Temp_item = Colour_temperature_item;
823
824	ICC_item = Colour_icc_item;
825}
826