1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 %                                                                             %
4 %                                                                             %
5 %                                                                             %
6 %                     IIIIIIIIII    PPPPPPPP      LL                          %
7 %                         II        PP      PP    LL                          %
8 %                         II        PP       PP   LL                          %
9 %                         II        PP      PP    LL                          %
10 %                         II        PPPPPPPP      LL                          %
11 %                         II        PP            LL                          %
12 %                         II        PP            LL                          %
13 %                     IIIIIIIIII    PP            LLLLLLLL                    %
14 %                                                                             %
15 %                                                                             %
16 %                                                                             %
17 %                   Read/Write Scanalytics IPLab Image Format                 %
18 %                                  Sean Burke                                 %
19 %                                  2008.05.07                                 %
20 %                                     v 0.9                                   %
21 %                                                                             %
22 %  Copyright 1999-2019 ImageMagick Studio LLC, a non-profit organization      %
23 %  dedicated to making software imaging solutions freely available.           %
24 %                                                                             %
25 %  You may not use this file except in compliance with the License.  You may  %
26 %  obtain a copy of the License at                                            %
27 %                                                                             %
28 %    https://imagemagick.org/script/license.php                               %
29 %                                                                             %
30 %  Unless required by applicable law or agreed to in writing, software        %
31 %  distributed under the License is distributed on an "AS IS" BASIS,          %
32 %  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
33 %  See the License for the specific language governing permissions and        %
34 %  limitations under the License.                                             %
35 %                                                                             %
36 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
37 %
38 %
39 */
40 
41 /*
42  Include declarations.
43  */
44 #include "MagickCore/studio.h"
45 #include "MagickCore/blob.h"
46 #include "MagickCore/blob-private.h"
47 #include "MagickCore/cache.h"
48 #include "MagickCore/colorspace.h"
49 #include "MagickCore/colorspace-private.h"
50 #include "MagickCore/exception.h"
51 #include "MagickCore/exception-private.h"
52 #include "MagickCore/image.h"
53 #include "MagickCore/image-private.h"
54 #include "MagickCore/list.h"
55 #include "MagickCore/magick.h"
56 #include "MagickCore/memory_.h"
57 #include "MagickCore/monitor.h"
58 #include "MagickCore/monitor-private.h"
59 #include "MagickCore/option.h"
60 #include "MagickCore/property.h"
61 #include "MagickCore/quantum-private.h"
62 #include "MagickCore/static.h"
63 #include "MagickCore/string_.h"
64 #include "MagickCore/module.h"
65 
66 /*
67 Tyedef declarations
68  */
69 
70 typedef struct _IPLInfo
71 {
72   unsigned int
73     tag,
74     size,
75     time,
76     z,
77     width,
78     height,
79     colors,
80     depth,
81     byteType;
82 } IPLInfo;
83 
84 static MagickBooleanType
85   WriteIPLImage(const ImageInfo *,Image *,ExceptionInfo *);
86 
87 /*
88 static void increase (void *pixel, int byteType){
89   switch(byteType){
90     case 0:(*((unsigned char *) pixel))++; break;
91     case 1:(*((signed int *) pixel))++; break;
92     case 2:(*((unsigned int *) pixel))++; break;
93     case 3:(*((signed long *) pixel))++; break;
94     default:(*((unsigned int *) pixel))++; break;
95   }
96 }
97 */
98 
99 /*
100  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
101  %                                                                             %
102  %                                                                             %
103  %                                                                             %
104  %   I s I P L                                                                 %
105  %                                                                             %
106  %                                                                             %
107  %                                                                             %
108  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
109  %
110  %  IsIPL() returns MagickTrue if the image format type, identified by the
111  %  magick string, is IPL.
112  %
113  %  The format of the IsIPL method is:
114  %
115  %      MagickBooleanType IsIPL(const unsigned char *magick,const size_t length)
116  %
117  %  A description of each parameter follows:
118  %
119  %    o magick: compare image format pattern against these bytes.
120  %
121  %    o length: Specifies the length of the magick string.
122  %
123  */
IsIPL(const unsigned char * magick,const size_t length)124 static MagickBooleanType IsIPL(const unsigned char *magick,const size_t length)
125 {
126   if (length < 4)
127     return(MagickFalse);
128   if (LocaleNCompare((const char *) magick,"data",4) == 0)
129     return(MagickTrue);
130   return(MagickFalse);
131 }
132 
133 /*
134  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
135  %                                                                             %
136  %                                                                             %
137  %                                                                             %
138  %    R e a d I P L I m a g e                                                  %
139  %                                                                             %
140  %                                                                             %
141  %                                                                             %
142  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
143  %
144  %  ReadIPLImage() reads a Scanalytics IPLab image file and returns it.  It
145  %  allocates the memory necessary for the new Image structure and returns a
146  %  pointer to the new image.
147  %
148  %  According to the IPLab spec, the data is blocked out in five dimensions:
149  %  { t, z, c, y, x }.  When we return the image, the latter three are folded
150  %  into the standard "Image" structure.  The "scenes" (image_info->scene)
151  %  correspond to the order: { {t0,z0}, {t0, z1}, ..., {t1,z0}, {t1,z1}... }
152  %  The number of scenes is t*z.
153  %
154  %  The format of the ReadIPLImage method is:
155  %
156  %      Image *ReadIPLImage(const ImageInfo *image_info,ExceptionInfo *exception)
157  %
158  %  A description of each parameter follows:
159  %
160  %    o image_info: The image info.
161  %
162  %    o exception: return any errors or warnings in this structure.
163  %
164  */
165 
SetHeaderFromIPL(Image * image,IPLInfo * ipl)166 static void SetHeaderFromIPL(Image *image, IPLInfo *ipl){
167   image->columns = ipl->width;
168   image->rows = ipl->height;
169   image->depth = ipl->depth;
170   image->resolution.x = 1;
171   image->resolution.y = 1;
172 }
173 
174 
ReadIPLImage(const ImageInfo * image_info,ExceptionInfo * exception)175 static Image *ReadIPLImage(const ImageInfo *image_info,ExceptionInfo *exception)
176 {
177 
178   /*
179   Declare variables
180    */
181   Image *image;
182 
183   MagickBooleanType status;
184   register Quantum *q;
185   unsigned char magick[12], *pixels;
186   ssize_t count;
187   ssize_t y;
188   size_t t_count=0;
189   size_t length;
190   IPLInfo
191     ipl_info;
192   QuantumFormatType
193     quantum_format;
194   QuantumInfo
195     *quantum_info;
196   QuantumType
197     quantum_type;
198 
199   size_t
200     extent;
201 
202   /*
203    Open Image
204    */
205 
206   assert(image_info != (const ImageInfo *) NULL);
207   assert(image_info->signature == MagickCoreSignature);
208   if ( image_info->debug != MagickFalse)
209     (void) LogMagickEvent(TraceEvent, GetMagickModule(), "%s",
210                 image_info->filename);
211   assert(exception != (ExceptionInfo *) NULL);
212   assert(exception->signature == MagickCoreSignature);
213   image=AcquireImage(image_info,exception);
214   status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
215   if (status == MagickFalse)
216   {
217     image=DestroyImageList(image);
218     return((Image *) NULL);
219   }
220 
221   /*
222    Read IPL image
223    */
224 
225   /*
226     Determine endianness
227    If we get back "iiii", we have LSB,"mmmm", MSB
228    */
229   count=ReadBlob(image,4,magick);
230   if (count != 4)
231     ThrowReaderException(CorruptImageError, "ImproperImageHeader");
232   if((LocaleNCompare((char *) magick,"iiii",4) == 0))
233     image->endian=LSBEndian;
234   else{
235     if((LocaleNCompare((char *) magick,"mmmm",4) == 0))
236       image->endian=MSBEndian;
237     else{
238       ThrowReaderException(CorruptImageError, "ImproperImageHeader");
239     }
240   }
241   /* Skip o'er the next 8 bytes (garbage) */
242   count=ReadBlob(image, 8, magick);
243   /*
244    Excellent, now we read the header unimpeded.
245    */
246   count=ReadBlob(image,4,magick);
247   if((count != 4) || (LocaleNCompare((char *) magick,"data",4) != 0))
248     ThrowReaderException(CorruptImageError, "ImproperImageHeader");
249   ipl_info.size=ReadBlobLong(image);
250   ipl_info.width=ReadBlobLong(image);
251   ipl_info.height=ReadBlobLong(image);
252   if((ipl_info.width == 0UL) || (ipl_info.height == 0UL))
253     ThrowReaderException(CorruptImageError,"ImproperImageHeader");
254   ipl_info.colors=ReadBlobLong(image);
255   if(ipl_info.colors == 3){ SetImageColorspace(image,sRGBColorspace,exception);}
256   else { image->colorspace = GRAYColorspace; }
257   ipl_info.z=ReadBlobLong(image);
258   ipl_info.time=ReadBlobLong(image);
259 
260   ipl_info.byteType=ReadBlobLong(image);
261 
262 
263   /* Initialize Quantum Info */
264 
265   switch (ipl_info.byteType) {
266     case 0:
267       ipl_info.depth=8;
268       quantum_format = UnsignedQuantumFormat;
269       break;
270     case 1:
271       ipl_info.depth=16;
272       quantum_format = SignedQuantumFormat;
273       break;
274     case 2:
275       ipl_info.depth=16;
276       quantum_format = UnsignedQuantumFormat;
277       break;
278     case 3:
279       ipl_info.depth=32;
280       quantum_format = SignedQuantumFormat;
281       break;
282     case 4: ipl_info.depth=32;
283       quantum_format = FloatingPointQuantumFormat;
284       break;
285     case 5:
286       ipl_info.depth=8;
287       quantum_format = UnsignedQuantumFormat;
288       break;
289     case 6:
290       ipl_info.depth=16;
291       quantum_format = UnsignedQuantumFormat;
292       break;
293     case 10:
294       ipl_info.depth=64;
295       quantum_format = FloatingPointQuantumFormat;
296       break;
297     default:
298       ipl_info.depth=16;
299       quantum_format = UnsignedQuantumFormat;
300       break;
301   }
302   extent=ipl_info.width*ipl_info.height*ipl_info.z*ipl_info.depth/8;
303   if (extent > GetBlobSize(image))
304     ThrowReaderException(CorruptImageError,"InsufficientImageDataInFile");
305 
306   /*
307     Set number of scenes of image
308   */
309   SetHeaderFromIPL(image, &ipl_info);
310 
311   /* Thats all we need if we are pinging. */
312   if (image_info->ping != MagickFalse)
313     {
314       (void) CloseBlob(image);
315       return(GetFirstImageInList(image));
316     }
317   length=image->columns;
318   quantum_type=GetQuantumType(image,exception);
319  do
320   {
321     SetHeaderFromIPL(image, &ipl_info);
322 
323     if ((image_info->ping != MagickFalse) && (image_info->number_scenes != 0))
324       if (image->scene >= (image_info->scene+image_info->number_scenes-1))
325         break;
326     status=SetImageExtent(image,image->columns,image->rows,exception);
327     if (status == MagickFalse)
328       return(DestroyImageList(image));
329 /*
330    printf("Length: %.20g, Memory size: %.20g\n", (double) length,(double)
331      image->depth);
332 */
333      quantum_info=AcquireQuantumInfo(image_info,image);
334      if (quantum_info == (QuantumInfo *) NULL)
335        ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
336      status=SetQuantumFormat(image,quantum_info,quantum_format);
337      if (status == MagickFalse)
338        ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
339      pixels=(unsigned char *) GetQuantumPixels(quantum_info);
340      if(image->columns != ipl_info.width){
341 /*
342      printf("Columns not set correctly!  Wanted: %.20g, got: %.20g\n",
343        (double) ipl_info.width, (double) image->columns);
344 */
345      }
346 
347     /*
348     Covert IPL binary to pixel packets
349      */
350 
351   if(ipl_info.colors == 1){
352       for(y = 0; y < (ssize_t) image->rows; y++){
353         (void) ReadBlob(image, length*image->depth/8, pixels);
354         q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
355         if (q == (Quantum *) NULL)
356                 break;
357         (void) ImportQuantumPixels(image,(CacheView *) NULL,quantum_info,
358           GrayQuantum,pixels,exception);
359         if (SyncAuthenticPixels(image,exception) == MagickFalse)
360           break;
361     }
362   }
363   else{
364       for(y = 0; y < (ssize_t) image->rows; y++){
365         (void) ReadBlob(image, length*image->depth/8, pixels);
366         q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
367         if (q == (Quantum *) NULL)
368                 break;
369         (void) ImportQuantumPixels(image,(CacheView *) NULL,quantum_info,
370           RedQuantum,pixels,exception);
371         if (SyncAuthenticPixels(image,exception) == MagickFalse)
372           break;
373       }
374       for(y = 0; y < (ssize_t) image->rows; y++){
375         (void) ReadBlob(image, length*image->depth/8, pixels);
376         q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
377         if (q == (Quantum *) NULL)
378           break;
379         (void) ImportQuantumPixels(image,(CacheView *) NULL,quantum_info,
380           GreenQuantum,pixels,exception);
381         if (SyncAuthenticPixels(image,exception) == MagickFalse)
382           break;
383       }
384       for(y = 0; y < (ssize_t) image->rows; y++){
385         (void) ReadBlob(image, length*image->depth/8, pixels);
386         q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
387         if (q == (Quantum *) NULL)
388           break;
389         (void) ImportQuantumPixels(image,(CacheView *) NULL,quantum_info,
390           BlueQuantum,pixels,exception);
391         if (SyncAuthenticPixels(image,exception) == MagickFalse)
392           break;
393       }
394    }
395    SetQuantumImageType(image,quantum_type);
396 
397     t_count++;
398   quantum_info = DestroyQuantumInfo(quantum_info);
399 
400     if (EOFBlob(image) != MagickFalse)
401     {
402       ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
403                  image->filename);
404       break;
405     }
406    if (t_count < ipl_info.z * ipl_info.time)
407      {
408       /*
409        Proceed to next image.
410        */
411       AcquireNextImage(image_info,image,exception);
412       if (GetNextImageInList(image) == (Image *) NULL)
413         {
414           status=MagickFalse;
415           break;
416         }
417       image=SyncNextImageInList(image);
418       status=SetImageProgress(image,LoadImagesTag,TellBlob(image),
419         GetBlobSize(image));
420       if (status == MagickFalse)
421         break;
422     }
423   } while (t_count < ipl_info.z*ipl_info.time);
424   CloseBlob(image);
425   if (status == MagickFalse)
426     return(DestroyImageList(image));
427   return(GetFirstImageInList(image));
428 }
429 
430 /*
431  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
432  %                                                                             %
433  %                                                                             %
434  %                                                                             %
435  %   R e g i s t e r I P L I m a g e                                           %
436  %                                                                             %
437  %                                                                             %
438  %                                                                             %
439  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
440  %
441  % RegisterIPLImage() add attributes for the Scanalytics IPL image format to the
442  % list of supported formats.
443  %
444  %
445  */
RegisterIPLImage(void)446 ModuleExport size_t RegisterIPLImage(void)
447 {
448   MagickInfo
449     *entry;
450 
451   entry=AcquireMagickInfo("IPL","IPL","IPL Image Sequence");
452   entry->decoder=(DecodeImageHandler *) ReadIPLImage;
453   entry->encoder=(EncodeImageHandler *) WriteIPLImage;
454   entry->magick=(IsImageFormatHandler *) IsIPL;
455   entry->flags|=CoderDecoderSeekableStreamFlag;
456   entry->flags|=CoderEndianSupportFlag;
457   (void) RegisterMagickInfo(entry);
458   return(MagickImageCoderSignature);
459 }
460 
461 /*
462  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
463  %                                                                             %
464  %                                                                             %
465  %                                                                             %
466  %   U n r e g i s t e r I P L I m a g e                                       %
467  %                                                                             %
468  %                                                                             %
469  %                                                                             %
470  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
471  %
472  %  UnregisterIPLImage() removes format registrations made by the
473  %  IPL module from the list of supported formats.
474  %
475  %  The format of the UnregisterIPLImage method is:
476  %
477  %      UnregisterIPLImage(void)
478  %
479  */
UnregisterIPLImage(void)480 ModuleExport void UnregisterIPLImage(void)
481 {
482   (void) UnregisterMagickInfo("IPL");
483 }
484 
485 /*
486 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
487 %                                                                             %
488 %                                                                             %
489 %                                                                             %
490 %   W r i t e I P L I m a g e                                                 %
491 %                                                                             %
492 %                                                                             %
493 %                                                                             %
494 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
495 %
496 %  WriteIPLImage() writes an image to a file in Scanalytics IPLabimage format.
497 %
498 %  The format of the WriteIPLImage method is:
499 %
500 %      MagickBooleanType WriteIPLImage(const ImageInfo *image_info,Image *image)
501 %       Image *image,ExceptionInfo *exception)
502 %
503 %  A description of each parameter follows.
504 %
505 %    o image_info: The image info.
506 %
507 %    o image:  The image.
508 %
509 %    o exception: return any errors or warnings in this structure.
510 %
511 */
WriteIPLImage(const ImageInfo * image_info,Image * image,ExceptionInfo * exception)512 static MagickBooleanType WriteIPLImage(const ImageInfo *image_info,Image *image,
513   ExceptionInfo *exception)
514 {
515   IPLInfo
516     ipl_info;
517 
518   MagickBooleanType
519     status;
520 
521   MagickOffsetType
522     scene;
523 
524   register const Quantum
525     *p;
526 
527   QuantumInfo
528     *quantum_info;
529 
530   size_t
531     imageListLength;
532 
533   ssize_t
534     y;
535 
536   unsigned char
537     *pixels;
538 
539    /*
540     Open output image file.
541   */
542   assert(image_info != (const ImageInfo *) NULL);
543   assert(image_info->signature == MagickCoreSignature);
544   assert(image != (Image *) NULL);
545   assert(image->signature == MagickCoreSignature);
546   if (image->debug != MagickFalse)
547     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
548   assert(exception != (ExceptionInfo *) NULL);
549   assert(exception->signature == MagickCoreSignature);
550   status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
551   if (status == MagickFalse)
552     return(status);
553   scene=0;
554 
555   quantum_info=AcquireQuantumInfo(image_info,image);
556   if (quantum_info == (QuantumInfo *) NULL)
557     ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
558   if ((quantum_info->format == UndefinedQuantumFormat) &&
559       (IsHighDynamicRangeImage(image,exception) != MagickFalse))
560     SetQuantumFormat(image,quantum_info,FloatingPointQuantumFormat);
561   switch(quantum_info->depth){
562   case 8:
563     ipl_info.byteType = 0;
564     break;
565   case 16:
566     if(quantum_info->format == SignedQuantumFormat){
567       ipl_info.byteType = 2;
568     }
569     else{
570       ipl_info.byteType = 1;
571     }
572     break;
573   case 32:
574     if(quantum_info->format == FloatingPointQuantumFormat){
575       ipl_info.byteType = 3;
576     }
577     else{
578       ipl_info.byteType = 4;
579     }
580     break;
581   case 64:
582     ipl_info.byteType = 10;
583     break;
584   default:
585     ipl_info.byteType = 2;
586     break;
587 
588   }
589   imageListLength=GetImageListLength(image);
590   ipl_info.z = (unsigned int) imageListLength;
591   /* There is no current method for detecting whether we have T or Z stacks */
592   ipl_info.time = 1;
593   ipl_info.width = (unsigned int) image->columns;
594   ipl_info.height = (unsigned int) image->rows;
595   (void) TransformImageColorspace(image,sRGBColorspace,exception);
596   if(IssRGBCompatibleColorspace(image->colorspace) != MagickFalse) { ipl_info.colors = 3; }
597   else{ ipl_info.colors = 1; }
598 
599   ipl_info.size = (unsigned int) (28 +
600     ((image->depth)/8)*ipl_info.height*ipl_info.width*ipl_info.colors*ipl_info.z);
601 
602   /* Ok!  Calculations are done.  Lets write this puppy down! */
603 
604   /*
605     Write IPL header.
606   */
607   /* Shockingly (maybe not if you have used IPLab),  IPLab itself CANNOT read MSBEndian
608   files!   The reader above can, but they cannot.  For compatability reasons, I will leave
609   the code in here, but it is all but useless if you want to use IPLab. */
610 
611   if(image_info->endian == MSBEndian)
612     (void) WriteBlob(image, 4, (const unsigned char *) "mmmm");
613   else{
614     image->endian = LSBEndian;
615     (void) WriteBlob(image, 4, (const unsigned char *) "iiii");
616   }
617   (void) WriteBlobLong(image, 4);
618   (void) WriteBlob(image, 4, (const unsigned char *) "100f");
619   (void) WriteBlob(image, 4, (const unsigned char *) "data");
620   (void) WriteBlobLong(image, ipl_info.size);
621   (void) WriteBlobLong(image, ipl_info.width);
622   (void) WriteBlobLong(image, ipl_info.height);
623   (void) WriteBlobLong(image, ipl_info.colors);
624   if(image_info->adjoin == MagickFalse)
625   (void) WriteBlobLong(image, 1);
626   else
627   (void) WriteBlobLong(image, ipl_info.z);
628   (void) WriteBlobLong(image, ipl_info.time);
629   (void) WriteBlobLong(image, ipl_info.byteType);
630 
631   do
632     {
633       /*
634   Convert MIFF to IPL raster pixels.
635       */
636       pixels=(unsigned char *) GetQuantumPixels(quantum_info);
637   if(ipl_info.colors == 1){
638   /* Red frame */
639   for(y = 0; y < (ssize_t) ipl_info.height; y++){
640     p=GetVirtualPixels(image,0,y,image->columns,1,exception);
641     if (p == (const Quantum *) NULL)
642       break;
643     (void) ExportQuantumPixels(image,(CacheView *) NULL, quantum_info,
644       GrayQuantum, pixels,exception);
645     (void) WriteBlob(image, image->columns*image->depth/8, pixels);
646   }
647 
648 }
649   if(ipl_info.colors == 3){
650   /* Red frame */
651   for(y = 0; y < (ssize_t) ipl_info.height; y++){
652     p=GetVirtualPixels(image,0,y,image->columns,1,exception);
653     if (p == (const Quantum *) NULL)
654       break;
655     (void) ExportQuantumPixels(image,(CacheView *) NULL, quantum_info,
656       RedQuantum, pixels,exception);
657     (void) WriteBlob(image, image->columns*image->depth/8, pixels);
658   }
659 
660     /* Green frame */
661     for(y = 0; y < (ssize_t) ipl_info.height; y++){
662       p=GetVirtualPixels(image,0,y,image->columns,1,exception);
663       if (p == (const Quantum *) NULL)
664         break;
665       (void) ExportQuantumPixels(image,(CacheView *) NULL, quantum_info,
666         GreenQuantum, pixels,exception);
667       (void) WriteBlob(image, image->columns*image->depth/8, pixels);
668     }
669     /* Blue frame */
670     for(y = 0; y < (ssize_t) ipl_info.height; y++){
671       p=GetVirtualPixels(image,0,y,image->columns,1,exception);
672       if (p == (const Quantum *) NULL)
673         break;
674       (void) ExportQuantumPixels(image,(CacheView *) NULL, quantum_info,
675         BlueQuantum, pixels,exception);
676       (void) WriteBlob(image, image->columns*image->depth/8, pixels);
677       if (image->previous == (Image *) NULL)
678         {
679           status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
680                 image->rows);
681           if (status == MagickFalse)
682             break;
683         }
684     }
685   }
686   quantum_info=DestroyQuantumInfo(quantum_info);
687   if (GetNextImageInList(image) == (Image *) NULL)
688     break;
689       image=SyncNextImageInList(image);
690       status=SetImageProgress(image,SaveImagesTag,scene++,imageListLength);
691       if (status == MagickFalse)
692         break;
693     }while (image_info->adjoin != MagickFalse);
694 
695   (void) WriteBlob(image, 4, (const unsigned char *) "fini");
696   (void) WriteBlobLong(image, 0);
697 
698 CloseBlob(image);
699 return(MagickTrue);
700 }
701