1/*  ******Functions included in start/_NG_utilities.def:******
2 *
3 *  so_balance ref_meanmax im1 im2 mask blur gauss *
4 *  nonzero_mean im = no_out *
5 *  so_meanmax im = result *
6 *  so_calculate ref_meanmax im mask = result *
7 *  simple_frame frame im_w im_h ov cs ms bf option = result *
8 *  corner_frame frame im_w im_h ov cs ms bf = result *
9 *  build_frame r_tl r_tr r_bl r_br r_mt r_mb r_ml r_mr im_w im_h ov bf = result *
10 *  complex_frame frame im_w im_h ov cs es ms bf option= result *
11 *  complex_edge ra rb t bl d = rc *
12 *  frame_lr_min r_l r_r target bw = result *
13 *  frame_tb_min r_t r_b target bw = result *
14 *  frame_position_image im ref os colour= result *
15 *  merge_array bw arr = result *
16 *  merge_to_scale im target blend dir = result *
17 *  select_ellipse line width = mask *
18 *  select_tetragon p1 p2 p3 p4 = mask *
19 *  select_polygon pt_list = mask *
20 *  perspective_transform to from = trans'' *
21 *  sort_pts_clockwise l = l'' *
22 */
23
24/* Called from:
25* _NG_Extra.def Clone_area_item
26*/
27so_balance ref_meanmax im1 im2 mask gauss
28   = result
29   {
30   //ref_meanmax = so_meanmax im1;
31   so_values = so_calculate ref_meanmax im2 mask;
32   im2_cor_a = clip2fmt im2.format im2'', has_member "format" im2
33             = im2''
34           {im2'' = im2 * (so_values?0) + (so_values?1);}
35         // Option to convert replacement image to scaled gaussian noise
36         im2_cor = im2_cor_a, gauss == false
37           = clip2fmt im2_cor_a.format gauss_im
38       {gauss_im =
39           im_gaussnoise im2_cor_a.width im2_cor_a.height ref_meanmax?0
40(deviation im2_cor_a);}
41                           result = im_blend (get_image mask) (get_image
42im2_cor) (get_image im1);
43   };
44
45////////////////////////////////////////////////////////////////////////////////
46/* Calculates the mean of the non zero pixels.
47 *
48 * Called from:
49 * _NG_utilities so_meanmax
50 */
51nonzero_mean im = no_out
52	{
53	zero_im = (im == 0);
54	zero_mean = mean zero_im;
55	no_mean = mean im;
56	no_out = no_mean/(1 - (zero_mean/255));
57	};
58
59////////////////////////////////////////////////////////////////////////////////
60/* Calculates the max and nonzero mean of an image
61 *
62 * Called from:
63 * _NG_utilities so_balance
64 * _NG_utilities so_calculate
65 * _NG_Extra.def Clone_area_item
66 * _NG_Extra.def Balance_item.Balance_find_item
67 */
68so_meanmax im = result
69	{
70	mean_of_im = nonzero_mean im;
71	adjusted_im = im - mean_of_im;
72	max_of_im = max adjusted_im;
73
74	result = [mean_of_im, max_of_im];
75	};
76
77////////////////////////////////////////////////////////////////////////////////
78/* Calculates the scale and offset required to match a reference mean and max
79 *
80 * Called from:
81 * _NG_utilities so_balance
82 * _NG_Extra.def Balance_item.Balance_find_item
83 */
84so_calculate ref_meanmax im mask = result
85	{
86	im' = if_then_else mask im 0;
87	im_values = so_meanmax im';
88
89	mean_of_ref = ref_meanmax?0;
90	mean_of_im  = im_values?0;
91
92	max_of_ref = ref_meanmax?1;
93	max_of_im  = im_values?1;
94
95	scale = (max_of_ref)/(max_of_im);
96	offset = mean_of_ref - (mean_of_im * scale);
97	result = [ scale, offset ];
98	};
99
100////////////////////////////////////////////////////////////////////////////////
101/* Extends or shortens the central sections of a simple frame to fit round a given image.
102 *
103 * Called from:
104 * _NG_Extra.def Frame_item.Simple_frame_item
105 */
106simple_frame frame im_w im_h ov cs ms bf option = result
107		{
108		cs' = (1 - cs);
109		ms' = (0.5 - (ms/2));
110		ms'' = (1 - cs);
111
112		//Regions
113		r_tl = Region_relative frame 0 0 cs cs;
114		r_tr = fliplr r_tl, option == true
115			  = Region_relative frame cs' 0 cs cs;
116		r_bl = Region_relative frame 0 cs' cs cs;
117		r_br = fliplr r_bl, option == true
118		       = Region_relative frame cs' cs' cs cs;
119
120		r_mt = Region_relative frame ms' 0 ms cs;
121		r_mb = Region_relative frame ms' ms'' ms cs;
122		r_ml = Region_relative frame 0 ms' cs ms;
123		r_mr = fliplr r_ml, option == true
124		       = Region_relative frame ms'' ms' cs ms;
125
126		result = build_frame r_tl r_tr r_bl r_br r_mt r_mb r_ml r_mr im_w im_h ov bf;
127		};
128
129////////////////////////////////////////////////////////////////////////////////
130/* Copies and extends a simple frame corner to produce a complete frame to fit round a given image.
131 *
132 * Called from:
133 * _NG_Extra.def Frame_item.Frame_corner_item
134 */
135corner_frame frame im_w im_h ov cs ms bf = result
136	{
137	cs' = (1 - cs);
138	ms' = (0.5 - (ms/2));
139
140	//Regions
141	r_tl = Region_relative frame 0 0 cs cs;
142	r_tr = fliplr r_tl;
143	r_bl = fliptb r_tl;
144	r_br = fliplr r_bl;
145	r_mt = Region_relative frame ms' 0 ms cs;
146	r_mb = fliptb r_mt;
147	r_ml = Region_relative frame 0 ms' cs ms;;
148	r_mr = fliplr r_ml;
149	result = build_frame r_tl r_tr r_bl r_br r_mt r_mb r_ml r_mr im_w im_h ov bf;
150	};
151
152////////////////////////////////////////////////////////////////////////////////
153/* Completes the frame building process for simple_frame and corner_frame.
154 *
155 * _NG_utilities simple_frame
156 * _NG_utilities corner_frame
157 */
158build_frame r_tl r_tr r_bl r_br r_mt r_mb r_ml r_mr im_w im_h ov bf = result
159	{
160	//Find pixel thickness of frames section
161	s_width = r_ml.width - mean (im_profile (map_unary fliplr (r_ml.value)?0) 1);
162	s_height = r_mt.height - mean (im_profile (map_unary fliptb (r_mt.value)?0) 0);
163
164	w_target = im_w + (2 * (s_width - ov));
165	h_target = im_h + (2 * (s_height - ov));
166
167	blend = bf * r_tl.width;
168
169	cw_target = w_target - (2 * r_tl.width) + (2 * blend), w_target > (2 * r_tl.width)
170			  = w_target;
171	ch_target = h_target - (2 * r_tl.height) + (2 * blend), h_target > (2 * r_tl.height)
172			  = h_target;
173
174	//Use regions to produce sections
175	top 	= merge_to_scale r_mt cw_target blend 0;
176	bottom 	= merge_to_scale r_mb cw_target blend 0;
177	left 	= merge_to_scale r_ml ch_target blend 1;
178	right 	= merge_to_scale r_mr ch_target blend 1;
179	middle  = Image
180		(image_new cw_target ch_target left.bands left.format left.coding left.type 0 0 0);
181
182	//Build sections into full frame.
183	row_1 = frame_lr_min r_tl r_tr w_target blend, ( w_target < (r_tl.width * 2))
184		  = merge_array blend [[r_tl, top, r_tr]];
185	row_2 = frame_lr_min left right w_target blend, ( w_target < (r_tl.width * 2))
186		  = merge_array blend [[left, middle, right]];
187	row_3 = frame_lr_min r_bl r_br w_target blend, ( w_target < (r_tl.width * 2))
188		  = merge_array blend [[r_bl, bottom, r_br]];
189
190	result = frame_tb_min row_1 row_3 h_target blend, (h_target < (r_tl.height * 2))
191	 	   = merge_array blend [[row_1], [row_2], [row_3]];
192	};
193
194////////////////////////////////////////////////////////////////////////////////
195/* Extends or shortens the central sections of a frame, preserving any central details on each
196 * edge, to fit round a given image.
197 *
198 * Called from:
199 * _NG_Extra.def Frame_item.Complex_frame_item
200 */
201complex_frame frame im_w im_h ov cs es ms bf option= result
202	{
203	cs' = (1 - cs);
204	ms' = (0.5 - (ms/2));
205	es' = (0.25 - (es/2));
206
207	r_tl = Region_relative frame 0 0 cs cs;
208	r_tr = fliplr r_tl, option == true
209	   	 = Region_relative frame cs' 0 cs cs;
210	r_bl = Region_relative frame 0 cs' cs cs;
211	r_br = fliplr r_bl, option == true
212		 = Region_relative frame cs' cs' cs cs;
213
214	r_mt = Region_relative frame ms' 0 ms cs;
215	r_mb = Region_relative frame ms' cs' ms cs;
216	r_ml = Region_relative frame 0 ms' cs ms;
217	r_mr = fliplr r_ml, option == true
218	     = Region_relative frame cs' ms' cs ms;
219
220	r_et = Region_relative frame es' 0 es cs;
221	r_eb = Region_relative frame es' cs' es cs;
222	r_el = Region_relative frame 0 es' cs es;
223	r_er = fliplr r_el, option == true
224	     = Region_relative frame cs' es' cs es;
225
226	//Find pixel thickness of frames section
227	s_width = r_el.width - mean (im_profile (map_unary fliplr (r_el.value)?0) 1);
228	s_height = r_et.height - mean (im_profile (map_unary fliptb (r_et.value)?0) 0);
229
230	w_target = im_w + (2 * (s_width - ov));
231	h_target = im_h + (2 * (s_height - ov));
232	min_size = foldr1 min_pair [r_tl.width, r_tl.height,
233								r_mt.width, r_mt.height,
234								r_et.width, r_et.height];
235	blend = bf * min_size;
236
237	cw_target = w_target - (2 * r_tl.width) + (2 * blend);
238	ch_target = h_target - (2 * r_tl.height) + (2 * blend);
239
240	top 	= complex_edge r_mt r_et cw_target blend 0;
241	bottom 	= complex_edge r_mb r_eb cw_target blend 0;
242	left	= complex_edge r_ml r_el ch_target blend 1;
243	right	= complex_edge r_mr r_er ch_target blend 1;
244	middle  = Image
245		(image_new top.width left.height left.bands left.format left.coding left.type 0 0 0);
246
247	//Build regions into full frame.
248	row_1 = frame_lr_min r_tl r_tr w_target blend, ( w_target < (r_tl.width * 2))
249		  = merge_array blend [[r_tl, top, r_tr]];
250	row_2 = frame_lr_min left right w_target blend, ( w_target < (r_tl.width * 2))
251		  = merge_array blend [[left, middle, right]];
252	row_3 = frame_lr_min r_bl r_br w_target blend, ( w_target < (r_tl.width * 2))
253		  = merge_array blend [[r_bl, bottom, r_br]];
254	result = frame_tb_min row_1 row_3 h_target blend, (h_target < (r_tl.height * 2))
255		   = merge_array blend [[row_1], [row_2], [row_3]];
256	};
257
258////////////////////////////////////////////////////////////////////////////////
259/*  Function called by complex frame, used to produce section
260 *
261 * Called from:
262 * _NG_utilities.def complex_frame
263 */
264complex_edge ra rb t bl d = rc
265	{
266	e1 = ceil (ra.width - t)/2, d == 0
267	   = 0;
268	e2 = 0, d == 0
269	   = ceil (ra.height - t)/2;
270	e3 = t, d == 0
271       = ra.width;
272	e4 = ra.height, d == 0
273	   = t;
274
275	check = ra.width, d == 0;
276    	  = ra.height;
277
278	rai = get_image ra;
279
280	t2 = (t - ra.width + (2 * bl))/2, d == 0
281	   = (t - ra.height + (2 * bl))/2;
282
283	rc = ra , t <= 0
284	   = Image (im_extract_area rai e1 e2 e3 e4), t <= check
285	   = merge_array bl [[rb',ra,rb']], d == 0
286	   = merge_array bl [[rb'],[ra],[rb']]
287	   	{rb' = merge_to_scale rb t2 bl d;}
288	}
289
290//////////////////////////////////////////////////////////////////////////////
291/* Blends two images left/right to produce an image a specific width.
292 *
293 * _NG_utilities build_frame
294 * _NG_utilities complex_frame
295 */
296frame_lr_min r_l r_r target bw = result
297	{
298	//Calculating the new widh required for each image.
299	no = (target/2 + bw);
300	n_w = no, (r_l.width > no)
301		= r_l.width;
302
303	//Removing excess from what will be the middle of the final image.
304	n_l = im_extract_area r_l.value 0 0 n_w r_l.height;
305	n_r = im_extract_area r_r.value (r_r.width - n_w) 0 n_w r_l.height;
306
307	//Merge the two image together with a bw*2 pixel overlap.
308	result = Image (im_lrmerge n_l n_r ((bw*2) - n_w) 0 bw);
309	};
310
311//////////////////////////////////////////////////////////////////////////////
312/* Blends two images top/bottom to produce an image a specific width.
313 *
314 * _NG_utilities build_frame
315 * _NG_utilities complex_frame
316 */
317frame_tb_min r_t r_b target bw = result
318	{
319	//Calculating the new height required for each image.
320	no = (target/2 + bw);
321	n_h = no, (r_t.height > no)
322		= r_t.height;
323
324	//Removing excess from what will be the middle of the final image.
325	n_t = im_extract_area r_t.value 0 0 r_t.width n_h;
326	n_b = im_extract_area r_b.value 0 (r_b.height - n_h) r_b.width n_h;
327
328	//Merge the two image together with a 50 pixel overlap.
329	result = Image (im_tbmerge n_t n_b 0 ((bw*2) -n_h) bw);
330	};
331
332//////////////////////////////////////////////////////////////////////////////
333/* Resixe canvas of an image to accomodate a frame and possible mount
334 *
335 * Called from:
336 * _NG_Extra.def Frame_item.Frame_corner_item
337 * _NG_Extra.def Frame_item.Simple_frame_item
338 * _NG_Extra.def Frame_item.Complex_frame_item
339 */
340frame_position_image im ref os colour= result
341	{
342	background = image_new ref.width ref.height
343					im.bands im.format im.coding im.type colour 0 0;
344
345	result = insert_noexpand xp yp im background
346		{
347		xp = (ref.width - im.width)/2;
348		yp = (ref.height - im.height - os)/2;
349		}
350	};
351
352
353//////////////////////////////////////////////////////////////////////////////
354/* Merges an array of images together according to blend width bw
355 *
356 * Called from:
357 * _NG_Utilites.def build_frame
358 * _NG_Utilites.def complex_frame
359 * _NG_Utilites.def complex_edge
360 */
361merge_array bw arr = result
362	{
363	merge_lr bw im1 im2 = im3
364		{
365		bw' = im_header_int "Xsize" (get_image im1);
366		bw'' = -(bw' - bw);
367		im3 = im_lrmerge (get_image im1) (get_image im2) bw'' 0 bw;
368		}
369	merge_tb bw im1 im2 = im3
370		{
371		bw' = im_header_int "Ysize" (get_image im1);
372		bw'' = -(bw' - bw);
373		im3 = im_tbmerge (get_image im1) (get_image im2) 0 bw'' bw;
374		}
375
376	im_out 	= (image_set_origin 0 0 @
377			foldl1 (merge_tb bw) @
378			map (foldl1 (merge_lr bw))) arr;
379	result = Image im_out;
380	}
381
382//////////////////////////////////////////////////////////////////////////////
383/* Repeatably top/bottom add clones of im, with a defined overlap, until final height > target
384 *
385 * Called from:
386 * _NG_Utilites.def build_frame
387 * _NG_Utilites.def complex_edge
388 */
389merge_to_scale im target blend dir = result
390	{
391	blend' = floor blend;
392
393	//allow fir lr or tb process
394	var_a = im.width, dir == 0
395		  = im.height;
396
397	var_w = im.width, dir == 1
398	      = target, target > blend'
399		  = blend';
400	var_h = im.height, dir == 0
401	      = target, target > blend'
402		  = blend';
403
404	//total numner of copies of im requires, taking overlap into account.
405	no_loops = ceil ((log ((target - blend')/(var_a - blend')))/(log 2));
406
407	process im no = result
408		{
409		pr_a = im_header_int "Xsize" (get_image im), dir == 0
410	    	 = im_header_int "Ysize" (get_image im);
411		pr_b = -(pr_a - blend' + 1);
412
413		im' = im_lrmerge (get_image im) (get_image im) pr_b 0 blend', dir == 0
414	    	= im_tbmerge (get_image im) (get_image im) 0 pr_b blend';
415		no' = no - 1;
416
417		result = im', no' < 1
418		       = process im' no';
419		}
420
421	im_tmp 	= im.value, var_a > target
422		    = process im no_loops;
423
424	result = Image (im_extract_area (get_image im_tmp) 0 0 var_w var_h);
425	}
426
427//////////////////////////////////////////////////////////////////////////////
428/* Selects an elispe based on a line and a width
429 *
430 * Called from:
431 * _NG_Extra.def Select_item.Elipse
432 */
433select_ellipse line width = mask
434	{
435	im = line.image;
436
437	//Make a 2 band image whose value equals its coordinates.
438	im_coor = Image (make_xy im.width im.height);
439
440	//Adjust the values to center tham on (line.left, line.top)
441	im_cent = im_coor - Vector [line.left,line.top];
442
443	w = line.width;
444	h = line.height;
445
446	angle	= 270, w == 0 && h < 0
447			= 90, w == 0 && h >= 0
448			= 360 + atan (h/w), w > 0 && h < 0
449	 		= atan (h/w), w > 0 && h >= 0
450	 		= 180 + atan (h/w);
451
452	a = ( (h ** 2) + (w ** 2) )**0.5;
453	b = a * width;
454
455	x' = ( (cos angle) * im_cent?0) + ( (sin angle) * im_cent?1);
456	y' = ( (cos angle) * im_cent?1) - ( (sin angle) * im_cent?0);
457
458	mask =  ( (b**2) * (x'**2) ) +  ( (a**2) * (y'**2) ) <= (a * b)**2;
459	}
460
461//////////////////////////////////////////////////////////////////////////////
462/* Selects a tetragon based on four points.
463 *
464 * Called from:
465 * _NG_Extra.def Select_item.Tetragon
466 * _NG_Extra.def Perspective_item
467 */
468select_tetragon p1 p2 p3 p4 = mask
469	{
470	//Put points in clockwise order starting at the top left.
471	pt_list = sort_pts_clockwise [p1, p2, p3, p4];
472
473	pair_list = [
474    	[ pt_list?0, pt_list?1 ],
475    	[ pt_list?1, pt_list?2 ],
476    	[ pt_list?2, pt_list?3 ],
477    	[ pt_list?3, pt_list?0 ] ];
478
479	//Make xy image the same size as p1.image;
480   	im_xy = Image (make_xy p1.image.width p1.image.height);
481	white = Image (image_new p1.image.width p1.image.height 1 0 Image_coding.NOCODING 1 255 0 0);
482
483	mask = foldl process white pair_list;
484
485	/* Treat each pair of point as a vector going from p1 to p2,
486	 * then select all to right of line. This is done for each pair,
487	 * the results are all combined to select the area defined by
488	 * the four points.
489	 */
490	process im_in pair = im_out
491		{
492		x = (pair?0).left;
493		y = (pair?0).top;
494		x'= (pair?1).left;
495		y'= (pair?1).top;
496
497		w = x' - x;
498		h = y' - y;
499
500		m = 0, x == x'
501		  = (y-y')/(x-x');
502		c = 0, x == x'
503		  = ((y*x') - (y'*x))/(x' - x);
504
505		mask=  im_xy?1 - (im_xy?0 * m)  >= c, w > 0
506			=  im_xy?1 - (im_xy?0 * m)  <= c, w < 0
507			=  im_xy?0 <= x, w == 0 && h > 0
508			=  im_xy?0 >= x;
509
510		im_out = im_in & mask;
511		}
512	}
513
514//////////////////////////////////////////////////////////////////////////////
515/* Selects a tetragon based on four points.
516 *
517 * Called from:
518 * _NG_Extra.def Select_item.Polygon
519 */
520select_polygon pt_list = mask
521	{
522	group_check = is_instanceof "Group"  pt_list;
523	pt_l = pt_list.value, group_check
524		 = pt_list;
525
526	im = (pt_l?0).image;
527	im_xy = Image (make_xy im.width im.height);
528	black = Image (image_new im_xy.width im_xy.height 1 0 Image_coding.NOCODING 1 0 0 0);
529
530	x = im_xy?0;
531	y = im_xy?1;
532
533	pt_l' = grp_trip pt_l;
534
535	mask = foldl process black pt_l';
536
537	/*Takes a group adds the first two the end and then creates a lists of
538	 *lists [[a, b, c], [b, c, d] .... [x, a, b]]
539 	 */
540	grp_trip l = l''
541		{
542		px = take 2 l;
543		l' = join l px;
544		start = [(take 3 l')];
545		rest = drop 3 l';
546
547		process a b = c
548			{
549			x = (last a)?1;
550			x'= (last a)?2;
551			x'' = [[x, x', b]];
552			c = join a x'';
553			}
554
555		l'' = foldl process start rest;
556		};
557
558	process im_in triplet = im_out
559		{
560		p1 = triplet?0;
561		p2 = triplet?1;
562		p3 = triplet?2;
563
564		//check for change in x direction between p1-p2 and p2 -p3
565		dir_1 = sign (p2.left - p1.left);
566		dir_2 = sign (p3.left - p2.left);
567		dir = dir_1 + dir_2;
568
569		//define min x limit.
570		min_x = p1.left, p1.left < p2.left
571			  = p2.left + 1, dir != 0
572			  = p2.left;
573
574		//define max x limit.
575		max_x = p1.left, p1.left > p2.left
576	       	  = p2.left - 1, dir != 0
577			  = p2.left;
578
579		//equation of line defined by p1 and p2
580		m = line_m p1 p2;
581		c = line_c p1 p2;
582
583		//Every thing below the line
584		im_test = ((y >= (m * x) + c) & (x >= min_x) & (x <= max_x));
585
586		im_out = im_in ^ im_test;
587		}
588
589	line_c p1 p2 = c
590		{m = line_m p1 p2;
591		 c = p1.top - (m * p1.left);};
592
593	line_m p1 p2 = (p2.top - p1.top)/(p2.left - p1.left), p2.left != p1.left
594	    		 = 0;
595	}
596
597//////////////////////////////////////////////////////////////////////////////
598/* Selects a tetragon based on four points.
599 *
600 * Called from:
601 * _NG_Extra.def Perspective_match_item
602 * _NG_Extra.def Perspective_item
603 */
604perspective_transform to from = trans''
605	{
606	/*
607	 *  Tramsformation matrix is calculated on the bases of the following functions:
608	 *		x' = c0x + c1y + c2xy + c3
609	 *		y' = c4x + c5y + c6xy + c7
610	 *
611	 *  The functions used in vips im_transform works based on the functions:
612	 *		x = x' + b0 + b2x' + b4y' + b6x'y'
613	 *		y = y' + b1 + b3x' + b5y' + b7x'y'
614	 *
615	 *  and is applied in the form of the matrix:
616	 *
617	 *  	[[b0, b1],
618	 *   	 [b2, b3],
619	 *   	 [b4, b5],
620	 *   	 [b6, b7]]
621	 *
622	 * Therefore our required calculated matrix will be
623	 *
624	 *  	[[ c3		, c7],
625	 *   	 [(c0 - 1)	, c4],
626	 *   	 [ c1		, (c5 - 1)],
627	 *   	 [ c2		, c6]]
628	 *
629	 * to = [x1, y1, x2, y2, x3, y3, x4, y4]
630	 * from = [x1', y1', x2', y2', x3', y3', x4', y4']
631	 * trans = [[c0], [c1], [c2], [c3], [c4], [c5], [c6], [c7]]
632	 *
633	 */
634
635	to' = Matrix
636	   [[to?0, to?1, ((to?0)*(to?1)), 1, 0, 0, 0, 0],
637		[0, 0, 0, 0, to?0, to?1, ((to?0)*(to?1)), 1],
638		[to?2, to?3, ((to?2)*(to?3)), 1, 0, 0, 0, 0],
639		[0, 0, 0, 0, to?2, to?3, ((to?2)*(to?3)), 1],
640		[to?4, to?5, ((to?4)*(to?5)), 1, 0, 0, 0, 0],
641		[0, 0, 0, 0, to?4, to?5, ((to?4)*(to?5)), 1],
642		[to?6, to?7, ((to?6)*(to?7)), 1, 0, 0, 0, 0],
643		[0, 0, 0, 0, to?6, to?7, ((to?6)*(to?7)), 1]];
644
645	from' = Matrix (transpose [from]);
646
647	to'' = to' ** (-1);
648
649	trans = to'' * from';
650	trans' = trans.value;
651	trans''= Matrix [[(trans'?3)?0, 	  (trans'?7)?0		],
652					 [((trans'?0)?0 - 1), (trans'?4)?0		],
653					 [(trans'?1)?0, 	  ((trans'?5)?0 - 1)],
654					 [(trans'?2)?0, 	  (trans'?6)?0		]];
655	}
656
657
658
659//////////////////////////////////////////////////////////////////////////////
660/* Sort a list of points into clockwise order.
661 *
662 * Called from:
663 * _NG_utilities.def select_tetragon
664 * _NG_Extra.def Perspective_match_item
665 * _NG_Extra.def Perspective_item
666 */
667sort_pts_clockwise l = l''
668		{
669		// sort functions:
670		f_top a b = a.top < b.top;
671		f_left a b = a.left < b.left;
672		f_right a b = a.left > b.left;
673
674		l' = sortc f_top l;
675		l'_a = take 2 l';
676		l'_b = drop 2 l';
677
678		l''_a = sortc f_left l'_a;
679		l''_b = sortc f_right l'_b;
680		l'' = join l''_a l''_b;
681		}
682