1 //
2 //  Little cms
3 //  Copyright (C) 1998-2010 Marti Maria, Ignacio Ruiz de Conejo
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the "Software"),
7 // to deal in the Software without restriction, including without limitation
8 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 // and/or sell copies of the Software, and to permit persons to whom the Software
10 // is furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
17 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 
23 
24 #include "mex.h"
25 
26 #include "lcms2mt.h"
27 #include "string.h"
28 #include "stdarg.h"
29 
30 // xgetopt() interface -----------------------------------------------------
31 
32 static int   xoptind;
33 static char *xoptarg;
34 static int   xopterr;
35 static char  *letP;
36 static char   SW = '-';
37 
38 // ------------------------------------------------------------------------
39 
40 
41 static int  Verbose ;			// Print some statistics
42 static char *cInProf;			// Input profile
43 static char *cOutProf;			// Output profile
44 static char *cProofing;			// Softproofing profile
45 
46 
47 static int  Intent;				// Rendering Intent
48 static int  ProofingIntent;		// RI for proof
49 
50 static int  PrecalcMode;		// 0 = Not, 1=Normal, 2=Accurate, 3=Fast
51 
52 static cmsBool BlackPointCompensation;
53 static cmsBool lIsDeviceLink;
54 static cmsBool lMultiProfileChain;		// Multiple profile chain
55 
56 static cmsHPROFILE hInput, hOutput, hProof;
57 static cmsHTRANSFORM hColorTransform;
58 static cmsHPROFILE hProfiles[255];
59 static int nProfiles;
60 
61 static cmsColorSpaceSignature InputColorSpace, OutputColorSpace;
62 static int OutputChannels, InputChannels, nBytesDepth;
63 
64 
65 // Error. Print error message and abort
66 
67 static
FatalError(const char * frm,...)68 cmsBool FatalError(const char *frm, ...)
69 {
70 	va_list args;
71 	char Buffer[1024];
72 
73 	va_start(args, frm);
74 	vsprintf(Buffer, frm, args);
75 	mexErrMsgTxt(Buffer);
76 	va_end(args);
77 
78 	return FALSE;
79 }
80 
81 // This is the handler passed to lcms
82 
83 static
MatLabErrorHandler(cmsContext ContextID,cmsUInt32Number ErrorCode,const char * Text)84 void MatLabErrorHandler(cmsContext ContextID, cmsUInt32Number ErrorCode,
85 						const char *Text)
86 {
87 	mexErrMsgTxt(Text);
88 }
89 //
90 //  Parse the command line options, System V style.
91 //
92 
93 static
xoptinit()94 void xoptinit()
95 {
96 	xoptind = 1;
97 	xopterr = 0;
98 	letP = NULL;
99 }
100 
101 
102 static
xgetopt(int argc,char * argv[],char * optionS)103 int xgetopt(int argc, char *argv[], char *optionS)
104 {
105 	unsigned char ch;
106 	char *optP;
107 
108 	if (SW == 0) {
109 		SW = '/';
110 	}
111 
112 	if (argc > xoptind) {
113 		if (letP == NULL) {
114 			if ((letP = argv[xoptind]) == NULL ||
115 				*(letP++) != SW)  goto gopEOF;
116 			if (*letP == SW) {
117 				xoptind++;  goto gopEOF;
118 			}
119 		}
120 		if (0 == (ch = *(letP++))) {
121 			xoptind++;  goto gopEOF;
122 		}
123 		if (':' == ch  ||  (optP = strchr(optionS, ch)) == NULL)
124 			goto gopError;
125 		if (':' == *(++optP)) {
126 			xoptind++;
127 			if (0 == *letP) {
128 				if (argc <= xoptind)  goto  gopError;
129 				letP = argv[xoptind++];
130 			}
131 			xoptarg = letP;
132 			letP = NULL;
133 		} else {
134 			if (0 == *letP) {
135 				xoptind++;
136 				letP = NULL;
137 			}
138 			xoptarg = NULL;
139 		}
140 		return ch;
141 	}
142 gopEOF:
143 	xoptarg = letP = NULL;
144 	return EOF;
145 
146 gopError:
147 	xoptarg = NULL;
148 	if (xopterr)
149 		FatalError ("get command line option");
150 	return ('?');
151 }
152 
153 
154 // Return Mathlab type by depth
155 
156 static
SizeOfArrayType(const mxArray * Array)157 size_t SizeOfArrayType(const mxArray *Array)
158 {
159 
160 	switch (mxGetClassID(Array))  {
161 
162 	 case mxINT8_CLASS:   return 1;
163 	 case mxUINT8_CLASS:  return 1;
164 	 case mxINT16_CLASS:  return 2;
165 	 case mxUINT16_CLASS: return 2;
166 	 case mxSINGLE_CLASS: return 4;
167 	 case mxDOUBLE_CLASS: return 0; // Special case -- lcms handles double as size=0
168 
169 
170 	 default:
171 		 FatalError("Unsupported data type");
172 		 return 0;
173 	}
174 }
175 
176 
177 // Get number of pixels of input array. Supported arrays are
178 // organized as NxMxD, being N and M the size of image and D the
179 // number of components.
180 
181 static
GetNumberOfPixels(const mxArray * In)182 size_t GetNumberOfPixels(const mxArray* In)
183 {
184 	int nDimensions  = mxGetNumberOfDimensions(In);
185 	const int  *Dimensions   = mxGetDimensions(In);
186 
187 	switch (nDimensions) {
188 
189 		case 1: return 1;                            // It is just a spot color
190 		case 2: return Dimensions[0];                // A scanline
191 		case 3: return Dimensions[0]*Dimensions[1];  // A image
192 
193 		default:
194 			FatalError("Unsupported array of %d dimensions", nDimensions);
195 			return 0;
196 	}
197 }
198 
199 
200 // Allocates the output array. Copies the input array modifying the pixel
201 // definition to match "OutputChannels".
202 
203 static
AllocateOutputArray(const mxArray * In,int OutputChannels)204 mxArray* AllocateOutputArray(const mxArray* In, int OutputChannels)
205 {
206 
207 	mxArray*	Out			  = mxDuplicateArray(In);   // Make a "deep copy" of Input array
208 	int         nDimensions   = mxGetNumberOfDimensions(In);
209 	const int*	Dimensions    = mxGetDimensions(In);
210 	int         InputChannels = Dimensions[nDimensions-1];
211 
212 
213 	// Modify pixel size only if needed
214 
215 	if (InputChannels != OutputChannels) {
216 
217 
218 		int i, NewSize;
219 		int *ModifiedDimensions = (int*) mxMalloc(nDimensions * sizeof(int));
220 
221 
222 		memmove(ModifiedDimensions, Dimensions, nDimensions * sizeof(int));
223 		ModifiedDimensions[nDimensions - 1] = OutputChannels;
224 
225 		switch (mxGetClassID(In))  {
226 
227 		case mxINT8_CLASS:   NewSize = sizeof(char); break;
228 		case mxUINT8_CLASS:  NewSize = sizeof(unsigned char); break;
229 		case mxINT16_CLASS:  NewSize = sizeof(short); break;
230 		case mxUINT16_CLASS: NewSize = sizeof(unsigned short); break;
231 
232 		default:
233 		case mxDOUBLE_CLASS: NewSize = sizeof(double); break;
234 		}
235 
236 
237 		// NewSize = 1;
238 		for (i=0; i < nDimensions; i++)
239 			NewSize *= ModifiedDimensions[i];
240 
241 
242 		mxSetDimensions(Out, ModifiedDimensions, nDimensions);
243 		mxFree(ModifiedDimensions);
244 
245 		mxSetPr(Out, mxRealloc(mxGetPr(Out), NewSize));
246 
247 	}
248 
249 
250 	return Out;
251 }
252 
253 
254 
255 // Does create a format descriptor. "Bytes" is the sizeof type in bytes
256 //
257 //  Bytes  Meaning
258 //  ------ --------
259 //   0      Floating point (double)
260 //   1      8-bit samples
261 //   2      16-bit samples
262 
263 static
MakeFormatDescriptor(cmsColorSpaceSignature ColorSpace,int Bytes)264 cmsUInt32Number MakeFormatDescriptor(cmsColorSpaceSignature ColorSpace, int Bytes)
265 {
266 	int IsFloat = (Bytes == 0 || Bytes == 4) ? 1 : 0;
267 	int Channels = cmsChannelsOf(ColorSpace);
268 	return FLOAT_SH(IsFloat)|COLORSPACE_SH(_cmsLCMScolorSpace(ColorSpace))|BYTES_SH(Bytes)|CHANNELS_SH(Channels)|PLANAR_SH(1);
269 }
270 
271 
272 // Opens a profile or proper built-in
273 
274 static
OpenProfile(const char * File)275 cmsHPROFILE OpenProfile(const char* File)
276 {
277 
278 	cmsContext ContextID = 0;
279 
280 	   if (!File)
281             return cmsCreate_sRGBProfileTHR(ContextID);
282 
283        if (cmsstrcasecmp(File, "*Lab2") == 0)
284                 return cmsCreateLab2ProfileTHR(ContextID, NULL);
285 
286        if (cmsstrcasecmp(File, "*Lab4") == 0)
287                 return cmsCreateLab4ProfileTHR(ContextID, NULL);
288 
289        if (cmsstrcasecmp(File, "*Lab") == 0)
290                 return cmsCreateLab4ProfileTHR(ContextID, NULL);
291 
292        if (cmsstrcasecmp(File, "*LabD65") == 0) {
293 
294            cmsCIExyY D65xyY;
295 
296            cmsWhitePointFromTemp( &D65xyY, 6504);
297            return cmsCreateLab4ProfileTHR(ContextID, &D65xyY);
298        }
299 
300        if (cmsstrcasecmp(File, "*XYZ") == 0)
301                 return cmsCreateXYZProfileTHR(ContextID);
302 
303        if (cmsstrcasecmp(File, "*Gray22") == 0) {
304 
305            cmsToneCurve* Curve = cmsBuildGamma(ContextID, 2.2);
306            cmsHPROFILE hProfile = cmsCreateGrayProfileTHR(ContextID, cmsD50_xyY(), Curve);
307            cmsFreeToneCurve(Curve);
308            return hProfile;
309        }
310 
311         if (cmsstrcasecmp(File, "*Gray30") == 0) {
312 
313            cmsToneCurve* Curve = cmsBuildGamma(ContextID, 3.0);
314            cmsHPROFILE hProfile = cmsCreateGrayProfileTHR(ContextID, cmsD50_xyY(), Curve);
315            cmsFreeToneCurve(Curve);
316            return hProfile;
317        }
318 
319        if (cmsstrcasecmp(File, "*srgb") == 0)
320                 return cmsCreate_sRGBProfileTHR(ContextID);
321 
322        if (cmsstrcasecmp(File, "*null") == 0)
323                 return cmsCreateNULLProfileTHR(ContextID);
324 
325 
326        if (cmsstrcasecmp(File, "*Lin2222") == 0) {
327 
328             cmsToneCurve*  Gamma = cmsBuildGamma(0, 2.2);
329             cmsToneCurve*  Gamma4[4];
330             cmsHPROFILE hProfile;
331 
332             Gamma4[0] = Gamma4[1] = Gamma4[2] = Gamma4[3] = Gamma;
333             hProfile = cmsCreateLinearizationDeviceLink(cmsSigCmykData, Gamma4);
334             cmsFreeToneCurve(Gamma);
335             return hProfile;
336        }
337 
338 
339         return cmsOpenProfileFromFileTHR(ContextID, File, "r");
340 }
341 
342 
343 static
GetFlags()344 cmsUInt32Number GetFlags()
345 {
346 	cmsUInt32Number dwFlags = 0;
347 
348 	switch (PrecalcMode) {
349 
350 	case 0: dwFlags = cmsFLAGS_NOOPTIMIZE; break;
351 	case 2: dwFlags = cmsFLAGS_HIGHRESPRECALC; break;
352 	case 3: dwFlags = cmsFLAGS_LOWRESPRECALC; break;
353 	case 1: break;
354 
355 	default: FatalError("Unknown precalculation mode '%d'", PrecalcMode);
356 	}
357 
358 	if (BlackPointCompensation)
359 		dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
360 
361 	return dwFlags;
362 }
363 
364 // Create transforms
365 
366 static
OpenTransforms(int argc,char * argv[])367 void OpenTransforms(int argc, char *argv[])
368 {
369 
370 	cmsUInt32Number dwIn, dwOut, dwFlags;
371 
372 
373 	if (lMultiProfileChain) {
374 
375 		int i;
376 		cmsHTRANSFORM hTmp;
377 
378 
379 		nProfiles = argc - xoptind;
380 		for (i=0; i < nProfiles; i++) {
381 
382 			hProfiles[i] = OpenProfile(argv[i+xoptind]);
383 		}
384 
385 
386 		// Create a temporary devicelink
387 
388 		hTmp = cmsCreateMultiprofileTransform(hProfiles, nProfiles,
389 			0, 0, Intent, GetFlags());
390 
391 		hInput = cmsTransform2DeviceLink(hTmp, 4.2, 0);
392 		hOutput = NULL;
393 		cmsDeleteTransform(hTmp);
394 
395 		InputColorSpace  = cmsGetColorSpace(hInput);
396 		OutputColorSpace = cmsGetPCS(hInput);
397 		lIsDeviceLink = TRUE;
398 
399 	}
400 	else
401 		if (lIsDeviceLink) {
402 
403 			hInput  = cmsOpenProfileFromFile(cInProf, "r");
404 			hOutput = NULL;
405 			InputColorSpace  = cmsGetColorSpace(hInput);
406 			OutputColorSpace = cmsGetPCS(hInput);
407 
408 
409 		}
410 		else {
411 
412 			hInput  = OpenProfile(cInProf);
413 			hOutput = OpenProfile(cOutProf);
414 
415 			InputColorSpace   = cmsGetColorSpace(hInput);
416 			OutputColorSpace  = cmsGetColorSpace(hOutput);
417 
418 			if (cmsGetDeviceClass(hInput) == cmsSigLinkClass ||
419 				cmsGetDeviceClass(hOutput) == cmsSigLinkClass)
420 				FatalError("Use %cl flag for devicelink profiles!\n", SW);
421 
422 		}
423 
424 
425 		/*
426 
427 		if (Verbose) {
428 
429 		mexPrintf("From: %s\n", cmsTakeProductName(hInput));
430 		if (hOutput) mexPrintf("To  : %s\n\n", cmsTakeProductName(hOutput));
431 
432 		}
433 		*/
434 
435 
436 		OutputChannels = cmsChannelsOf(OutputColorSpace);
437 		InputChannels  = cmsChannelsOf(InputColorSpace);
438 
439 
440 		dwIn  = MakeFormatDescriptor(InputColorSpace, nBytesDepth);
441 		dwOut = MakeFormatDescriptor(OutputColorSpace, nBytesDepth);
442 
443 
444 		dwFlags = GetFlags();
445 
446 		if (cProofing != NULL) {
447 
448 			hProof = OpenProfile(cProofing);
449 			dwFlags |= cmsFLAGS_SOFTPROOFING;
450 		}
451 
452 
453 
454 
455 		hColorTransform = cmsCreateProofingTransform(hInput, dwIn,
456 			hOutput, dwOut,
457 			hProof, Intent,
458 			ProofingIntent,
459 			dwFlags);
460 
461 }
462 
463 
464 
465 static
ApplyTransforms(const mxArray * In,mxArray * Out)466 void ApplyTransforms(const mxArray *In, mxArray *Out)
467 {
468 	double *Input  = mxGetPr(In);
469 	double *Output = mxGetPr(Out);
470 	size_t nPixels = GetNumberOfPixels(In);;
471 
472 	cmsDoTransform(hColorTransform, Input, Output, nPixels );
473 
474 }
475 
476 
477 static
CloseTransforms(void)478 void CloseTransforms(void)
479 {
480 	int i;
481 
482 	if (hColorTransform) cmsDeleteTransform(hColorTransform);
483 	if (hInput) cmsCloseProfile(hInput);
484 	if (hOutput) cmsCloseProfile(hOutput);
485 	if (hProof) cmsCloseProfile(hProof);
486 
487 	for (i=0; i < nProfiles; i++)
488 		cmsCloseProfile(hProfiles[i]);
489 
490 	hColorTransform = NULL; hInput = NULL; hOutput = NULL; hProof = NULL;
491 }
492 
493 
494 static
HandleSwitches(int argc,char * argv[])495 void HandleSwitches(int argc, char *argv[])
496 {
497 	int  s;
498 
499 	xoptinit();
500 
501 	while ((s = xgetopt(argc, argv,"C:c:VvbBI:i:O:o:T:t:L:l:r:r:P:p:Mm")) != EOF) {
502 
503 
504 		switch (s){
505 
506 		case 'b':
507 		case 'B':
508 			BlackPointCompensation = TRUE;
509 			break;
510 
511 		case 'c':
512 		case 'C':
513 			PrecalcMode = atoi(xoptarg);
514 			if (PrecalcMode < 0 || PrecalcMode > 3)
515 				FatalError("Unknown precalc mode '%d'", PrecalcMode);
516 			break;
517 
518 		case 'v':
519 		case 'V':
520 			Verbose = TRUE;
521 			break;
522 
523 		case 'i':
524 		case 'I':
525 			if (lIsDeviceLink)
526 				FatalError("Device-link already specified");
527 			cInProf = xoptarg;
528 			break;
529 
530 		case 'o':
531 		case 'O':
532 			if (lIsDeviceLink)
533 				FatalError("Device-link already specified");
534 			cOutProf = xoptarg;
535 			break;
536 
537 		case 't':
538 		case 'T':
539 			Intent = atoi(xoptarg);
540 			// if (Intent > 3) Intent = 3;
541 			if (Intent < 0) Intent = 0;
542 			break;
543 
544 
545 		case 'l':
546 		case 'L':
547 			cInProf = xoptarg;
548 			lIsDeviceLink = TRUE;
549 			break;
550 
551 		case 'p':
552 		case 'P':
553 			cProofing = xoptarg;
554 			break;
555 
556 
557 
558 		case 'r':
559 		case 'R':
560 			ProofingIntent = atoi(xoptarg);
561 			// if (ProofingIntent > 3) ProofingIntent = 3;
562 			if (ProofingIntent < 0) ProofingIntent = 0;
563 			break;
564 
565 
566 		case 'm':
567 		case 'M':
568 			lMultiProfileChain = TRUE;
569 			break;
570 
571 		default:
572 			FatalError("Unknown option.");
573 		}
574 	}
575 
576 	// For multiprofile, need to specify -m
577 
578 	if (xoptind < argc) {
579 
580 		if (!lMultiProfileChain)
581 			FatalError("Use %cm for multiprofile transforms", SW);
582 	}
583 
584 }
585 
586 
587 
588 // -------------------------------------------------- Print some fancy help
589 static
PrintHelp(void)590 void PrintHelp(void)
591 {
592 	mexPrintf("(MX) little cms ColorSpace conversion tool - v2.0\n\n");
593 
594 	mexPrintf("usage: icctrans (mVar, flags)\n\n");
595 
596 	mexPrintf("mVar : Matlab array.\n");
597 	mexPrintf("flags: a string containing one or more of following options.\n\n");
598 	mexPrintf("\t%cv - Verbose\n", SW);
599 	mexPrintf("\t%ci<profile> - Input profile (defaults to sRGB)\n", SW);
600 	mexPrintf("\t%co<profile> - Output profile (defaults to sRGB)\n", SW);
601 	mexPrintf("\t%cl<profile> - Transform by device-link profile\n", SW);
602 	mexPrintf("\t%cm<profiles> - Apply multiprofile chain\n", SW);
603 
604 	mexPrintf("\t%ct<n> - Rendering intent\n", SW);
605 
606 	mexPrintf("\t%cb - Black point compensation\n", SW);
607 	mexPrintf("\t%cc<0,1,2,3> - Optimize transform (0=Off, 1=Normal, 2=Hi-res, 3=Lo-Res) [defaults to 1]\n", SW);
608 
609 	mexPrintf("\t%cp<profile> - Soft proof profile\n", SW);
610 	mexPrintf("\t%cr<0,1,2,3> - Soft proof intent\n", SW);
611 
612 	mexPrintf("\nYou can use following built-ins as profiles:\n\n");
613 
614 	mexPrintf("\t*Lab2  -- D50-based v2 CIEL*a*b\n"
615 	"\t*Lab4  -- D50-based v4 CIEL*a*b\n"
616 	"\t*Lab   -- D50-based v4 CIEL*a*b\n"
617 	"\t*XYZ   -- CIE XYZ (PCS)\n"
618 	"\t*sRGB  -- IEC6 1996-2.1 sRGB color space\n"
619 	"\t*Gray22 - Monochrome of Gamma 2.2\n"
620 	"\t*Gray30 - Monochrome of Gamma 3.0\n"
621 	"\t*null   - Monochrome black for all input\n"
622 	"\t*Lin2222- CMYK linearization of gamma 2.2 on each channel\n\n");
623 
624 	mexPrintf("For suggestions, comments, bug reports etc. send mail to info@littlecms.com\n\n");
625 
626 }
627 
628 
629 
630 // Main entry point
631 
mexFunction(int nlhs,mxArray * plhs[],int nrhs,const mxArray * prhs[])632 void mexFunction(
633 				 int nlhs,              // Number of left hand side (output) arguments
634 				 mxArray *plhs[],       // Array of left hand side arguments
635 				 int nrhs,              // Number of right hand side (input) arguments
636 				 const mxArray *prhs[]  // Array of right hand side arguments
637 )
638 {
639 
640 	char CommandLine[4096+1];
641 	char *pt, *argv[128];
642 	int argc = 1;
643 
644 
645 	if (nrhs != 2) {
646 
647 		PrintHelp();
648 		return;
649 	}
650 
651 
652 	if(nlhs > 1) {
653 		FatalError("Too many output arguments.");
654 	}
655 
656 
657 	// Setup error handler
658 
659 	cmsSetLogErrorHandler(MatLabErrorHandler);
660 
661 	// Defaults
662 
663 	Verbose     = 0;
664 	cInProf     = NULL;
665 	cOutProf    = NULL;
666 	cProofing   = NULL;
667 
668 	lMultiProfileChain = FALSE;
669 	nProfiles   = 0;
670 
671 	Intent                  = INTENT_PERCEPTUAL;
672 	ProofingIntent          = INTENT_ABSOLUTE_COLORIMETRIC;
673 	PrecalcMode = 1;
674 	BlackPointCompensation  = FALSE;
675 	lIsDeviceLink           = FALSE;
676 
677 	// Check types. Fist parameter is array of values, second parameter is command line
678 
679 	if (!mxIsNumeric(prhs[0]))
680 		FatalError("Type mismatch on argument 1 -- Must be numeric");
681 
682 	if (!mxIsChar(prhs[1]))
683 		FatalError("Type mismatch on argument 2 -- Must be string");
684 
685 
686 
687 
688 	// Unpack string to command line buffer
689 
690 	if (mxGetString(prhs[1], CommandLine, 4096))
691 		FatalError("Cannot unpack command string");
692 
693 	// Separate to argv[] convention
694 
695 	argv[0] = NULL;
696 	for (pt = strtok(CommandLine, " ");
697 		pt;
698 		pt = strtok(NULL, " ")) {
699 
700 			argv[argc++] = pt;
701 	}
702 
703 
704 
705 	// Parse arguments
706 	HandleSwitches(argc, argv);
707 
708 
709 	nBytesDepth = SizeOfArrayType(prhs[0]);
710 
711 	OpenTransforms(argc, argv);
712 
713 
714 	plhs[0] = AllocateOutputArray(prhs[0], OutputChannels);
715 
716 
717 	ApplyTransforms(prhs[0], plhs[0]);
718 
719 	CloseTransforms();
720 
721 	// Done!
722 }
723 
724 
725