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