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