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-2016 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 "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 */ 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 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 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 /* 200 Open Image 201 */ 202 203 assert(image_info != (const ImageInfo *) NULL); 204 assert(image_info->signature == MagickCoreSignature); 205 if ( image_info->debug != MagickFalse) 206 (void) LogMagickEvent(TraceEvent, GetMagickModule(), "%s", 207 image_info->filename); 208 assert(exception != (ExceptionInfo *) NULL); 209 assert(exception->signature == MagickCoreSignature); 210 image=AcquireImage(image_info,exception); 211 status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); 212 if (status == MagickFalse) 213 { 214 image=DestroyImageList(image); 215 return((Image *) NULL); 216 } 217 218 /* 219 Read IPL image 220 */ 221 222 /* 223 Determine endianness 224 If we get back "iiii", we have LSB,"mmmm", MSB 225 */ 226 count=ReadBlob(image,4,magick); 227 (void) count; 228 if((LocaleNCompare((char *) magick,"iiii",4) == 0)) 229 image->endian=LSBEndian; 230 else{ 231 if((LocaleNCompare((char *) magick,"mmmm",4) == 0)) 232 image->endian=MSBEndian; 233 else{ 234 ThrowReaderException(CorruptImageError, "ImproperImageHeader"); 235 } 236 } 237 /* Skip o'er the next 8 bytes (garbage) */ 238 count=ReadBlob(image, 8, magick); 239 /* 240 Excellent, now we read the header unimpeded. 241 */ 242 count=ReadBlob(image,4,magick); 243 if((LocaleNCompare((char *) magick,"data",4) != 0)) 244 ThrowReaderException(CorruptImageError, "ImproperImageHeader"); 245 ipl_info.size=ReadBlobLong(image); 246 ipl_info.width=ReadBlobLong(image); 247 ipl_info.height=ReadBlobLong(image); 248 if((ipl_info.width == 0UL) || (ipl_info.height == 0UL)) 249 ThrowReaderException(CorruptImageError,"ImproperImageHeader"); 250 ipl_info.colors=ReadBlobLong(image); 251 if(ipl_info.colors == 3){ SetImageColorspace(image,sRGBColorspace,exception);} 252 else { image->colorspace = GRAYColorspace; } 253 ipl_info.z=ReadBlobLong(image); 254 ipl_info.time=ReadBlobLong(image); 255 256 ipl_info.byteType=ReadBlobLong(image); 257 258 259 /* Initialize Quantum Info */ 260 261 switch (ipl_info.byteType) { 262 case 0: 263 ipl_info.depth=8; 264 quantum_format = UnsignedQuantumFormat; 265 break; 266 case 1: 267 ipl_info.depth=16; 268 quantum_format = SignedQuantumFormat; 269 break; 270 case 2: 271 ipl_info.depth=16; 272 quantum_format = UnsignedQuantumFormat; 273 break; 274 case 3: 275 ipl_info.depth=32; 276 quantum_format = SignedQuantumFormat; 277 break; 278 case 4: ipl_info.depth=32; 279 quantum_format = FloatingPointQuantumFormat; 280 break; 281 case 5: 282 ipl_info.depth=8; 283 quantum_format = UnsignedQuantumFormat; 284 break; 285 case 6: 286 ipl_info.depth=16; 287 quantum_format = UnsignedQuantumFormat; 288 break; 289 case 10: 290 ipl_info.depth=64; 291 quantum_format = FloatingPointQuantumFormat; 292 break; 293 default: 294 ipl_info.depth=16; 295 quantum_format = UnsignedQuantumFormat; 296 break; 297 } 298 299 /* 300 Set number of scenes of image 301 */ 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 < ipl_info.z * ipl_info.time){ 401 /* 402 Proceed to next image. 403 */ 404 AcquireNextImage(image_info,image,exception); 405 if (GetNextImageInList(image) == (Image *) NULL) 406 { 407 image=DestroyImageList(image); 408 return((Image *) NULL); 409 } 410 image=SyncNextImageInList(image); 411 status=SetImageProgress(image,LoadImagesTag,TellBlob(image), 412 GetBlobSize(image)); 413 if (status == MagickFalse) 414 break; 415 } 416 } while (t_count < ipl_info.z*ipl_info.time); 417 CloseBlob(image); 418 return(GetFirstImageInList(image)); 419 } 420 421 /* 422 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 423 % % 424 % % 425 % % 426 % R e g i s t e r I P L I m a g e % 427 % % 428 % % 429 % % 430 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 431 % 432 % RegisterIPLImage() add attributes for the Scanalytics IPL image format to the 433 % list of supported formats. 434 % 435 % 436 */ 437 ModuleExport size_t RegisterIPLImage(void) 438 { 439 MagickInfo 440 *entry; 441 442 entry=AcquireMagickInfo("IPL","IPL","IPL Image Sequence"); 443 entry->decoder=(DecodeImageHandler *) ReadIPLImage; 444 entry->encoder=(EncodeImageHandler *) WriteIPLImage; 445 entry->magick=(IsImageFormatHandler *) IsIPL; 446 entry->flags|=CoderEndianSupportFlag; 447 (void) RegisterMagickInfo(entry); 448 return(MagickImageCoderSignature); 449 } 450 451 /* 452 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 453 % % 454 % % 455 % % 456 % U n r e g i s t e r I P L I m a g e % 457 % % 458 % % 459 % % 460 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 461 % 462 % UnregisterIPLImage() removes format registrations made by the 463 % IPL module from the list of supported formats. 464 % 465 % The format of the UnregisterIPLImage method is: 466 % 467 % UnregisterIPLImage(void) 468 % 469 */ 470 ModuleExport void UnregisterIPLImage(void) 471 { 472 (void) UnregisterMagickInfo("IPL"); 473 } 474 475 /* 476 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 477 % % 478 % % 479 % % 480 % W r i t e I P L I m a g e % 481 % % 482 % % 483 % % 484 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 485 % 486 % WriteIPLImage() writes an image to a file in Scanalytics IPLabimage format. 487 % 488 % The format of the WriteIPLImage method is: 489 % 490 % MagickBooleanType WriteIPLImage(const ImageInfo *image_info,Image *image) 491 % Image *image,ExceptionInfo *exception) 492 % 493 % A description of each parameter follows. 494 % 495 % o image_info: The image info. 496 % 497 % o image: The image. 498 % 499 % o exception: return any errors or warnings in this structure. 500 % 501 */ 502 static MagickBooleanType WriteIPLImage(const ImageInfo *image_info,Image *image, 503 ExceptionInfo *exception) 504 { 505 IPLInfo 506 ipl_info; 507 508 MagickBooleanType 509 status; 510 511 MagickOffsetType 512 scene; 513 514 register const Quantum 515 *p; 516 517 QuantumInfo 518 *quantum_info; 519 520 ssize_t 521 y; 522 523 unsigned char 524 *pixels; 525 526 /* 527 Open output image file. 528 */ 529 assert(image_info != (const ImageInfo *) NULL); 530 assert(image_info->signature == MagickCoreSignature); 531 assert(image != (Image *) NULL); 532 assert(image->signature == MagickCoreSignature); 533 if (image->debug != MagickFalse) 534 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 535 assert(exception != (ExceptionInfo *) NULL); 536 assert(exception->signature == MagickCoreSignature); 537 status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception); 538 if (status == MagickFalse) 539 return(status); 540 scene=0; 541 542 543 quantum_info=AcquireQuantumInfo(image_info,image); 544 if ((quantum_info->format == UndefinedQuantumFormat) && 545 (IsHighDynamicRangeImage(image,exception) != MagickFalse)) 546 SetQuantumFormat(image,quantum_info,FloatingPointQuantumFormat); 547 switch(quantum_info->depth){ 548 case 8: 549 ipl_info.byteType = 0; 550 break; 551 case 16: 552 if(quantum_info->format == SignedQuantumFormat){ 553 ipl_info.byteType = 2; 554 } 555 else{ 556 ipl_info.byteType = 1; 557 } 558 break; 559 case 32: 560 if(quantum_info->format == FloatingPointQuantumFormat){ 561 ipl_info.byteType = 3; 562 } 563 else{ 564 ipl_info.byteType = 4; 565 } 566 break; 567 case 64: 568 ipl_info.byteType = 10; 569 break; 570 default: 571 ipl_info.byteType = 2; 572 break; 573 574 } 575 ipl_info.z = (unsigned int) GetImageListLength(image); 576 /* There is no current method for detecting whether we have T or Z stacks */ 577 ipl_info.time = 1; 578 ipl_info.width = (unsigned int) image->columns; 579 ipl_info.height = (unsigned int) image->rows; 580 (void) TransformImageColorspace(image,sRGBColorspace,exception); 581 if(IssRGBCompatibleColorspace(image->colorspace) != MagickFalse) { ipl_info.colors = 3; } 582 else{ ipl_info.colors = 1; } 583 584 ipl_info.size = (unsigned int) (28 + 585 ((image->depth)/8)*ipl_info.height*ipl_info.width*ipl_info.colors*ipl_info.z); 586 587 /* Ok! Calculations are done. Lets write this puppy down! */ 588 589 /* 590 Write IPL header. 591 */ 592 /* Shockingly (maybe not if you have used IPLab), IPLab itself CANNOT read MSBEndian 593 files! The reader above can, but they cannot. For compatability reasons, I will leave 594 the code in here, but it is all but useless if you want to use IPLab. */ 595 596 if(image_info->endian == MSBEndian) 597 (void) WriteBlob(image, 4, (const unsigned char *) "mmmm"); 598 else{ 599 image->endian = LSBEndian; 600 (void) WriteBlob(image, 4, (const unsigned char *) "iiii"); 601 } 602 (void) WriteBlobLong(image, 4); 603 (void) WriteBlob(image, 4, (const unsigned char *) "100f"); 604 (void) WriteBlob(image, 4, (const unsigned char *) "data"); 605 (void) WriteBlobLong(image, ipl_info.size); 606 (void) WriteBlobLong(image, ipl_info.width); 607 (void) WriteBlobLong(image, ipl_info.height); 608 (void) WriteBlobLong(image, ipl_info.colors); 609 if(image_info->adjoin == MagickFalse) 610 (void) WriteBlobLong(image, 1); 611 else 612 (void) WriteBlobLong(image, ipl_info.z); 613 (void) WriteBlobLong(image, ipl_info.time); 614 (void) WriteBlobLong(image, ipl_info.byteType); 615 616 do 617 { 618 /* 619 Convert MIFF to IPL raster pixels. 620 */ 621 pixels=(unsigned char *) GetQuantumPixels(quantum_info); 622 if(ipl_info.colors == 1){ 623 /* Red frame */ 624 for(y = 0; y < (ssize_t) ipl_info.height; y++){ 625 p=GetVirtualPixels(image,0,y,image->columns,1,exception); 626 if (p == (const Quantum *) NULL) 627 break; 628 (void) ExportQuantumPixels(image,(CacheView *) NULL, quantum_info, 629 GrayQuantum, pixels,exception); 630 (void) WriteBlob(image, image->columns*image->depth/8, pixels); 631 } 632 633 } 634 if(ipl_info.colors == 3){ 635 /* Red frame */ 636 for(y = 0; y < (ssize_t) ipl_info.height; y++){ 637 p=GetVirtualPixels(image,0,y,image->columns,1,exception); 638 if (p == (const Quantum *) NULL) 639 break; 640 (void) ExportQuantumPixels(image,(CacheView *) NULL, quantum_info, 641 RedQuantum, pixels,exception); 642 (void) WriteBlob(image, image->columns*image->depth/8, pixels); 643 } 644 645 /* Green frame */ 646 for(y = 0; y < (ssize_t) ipl_info.height; y++){ 647 p=GetVirtualPixels(image,0,y,image->columns,1,exception); 648 if (p == (const Quantum *) NULL) 649 break; 650 (void) ExportQuantumPixels(image,(CacheView *) NULL, quantum_info, 651 GreenQuantum, pixels,exception); 652 (void) WriteBlob(image, image->columns*image->depth/8, pixels); 653 } 654 /* Blue 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 BlueQuantum, pixels,exception); 661 (void) WriteBlob(image, image->columns*image->depth/8, pixels); 662 if (image->previous == (Image *) NULL) 663 { 664 status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y, 665 image->rows); 666 if (status == MagickFalse) 667 break; 668 } 669 } 670 } 671 quantum_info=DestroyQuantumInfo(quantum_info); 672 if (GetNextImageInList(image) == (Image *) NULL) 673 break; 674 image=SyncNextImageInList(image); 675 status=SetImageProgress(image,SaveImagesTag,scene++, 676 GetImageListLength(image)); 677 if (status == MagickFalse) 678 break; 679 }while (image_info->adjoin != MagickFalse); 680 681 (void) WriteBlob(image, 4, (const unsigned char *) "fini"); 682 (void) WriteBlobLong(image, 0); 683 684 CloseBlob(image); 685 return(MagickTrue); 686 } 687