From: Dirk Lemstra Date: Wed, 6 Sep 2017 19:24:30 +0000 (+0200) Subject: Added support for writing layered tiff files with -define tiff:write-layers=true. X-Git-Tag: 7.0.7-1~16 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b40ea40a35b8b5d011b4543bcfb8f8adfc9bb581;p=imagemagick Added support for writing layered tiff files with -define tiff:write-layers=true. --- diff --git a/ChangeLog b/ChangeLog index 52c62e6f4..6b0591a2e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,8 @@ -2017-09-27 7.0.7-0 Cristy +2017-09-05 7.0.7-1 Dirk Lemstra + * Added -define tiff:write-layers=true to add support for writing layered + tiff files. + +2017-09-03 7.0.7-0 Cristy * Release ImageMagick version 7.0.7-0, GIT revision 20996:2f8ac2203:20170903. 2017-08-28 7.0.7-0 Cristy diff --git a/MagickCore/blob-private.h b/MagickCore/blob-private.h index 285e0a754..b5f04d3d5 100644 --- a/MagickCore/blob-private.h +++ b/MagickCore/blob-private.h @@ -110,15 +110,15 @@ extern MagickExport ssize_t WriteBlobByte(Image *,const unsigned char), WriteBlobFloat(Image *,const float), WriteBlobLong(Image *,const unsigned int), + WriteBlobLongLong(Image *,const MagickSizeType), WriteBlobShort(Image *,const unsigned short), + WriteBlobSignedLong(Image *,const signed int), WriteBlobLSBLong(Image *,const unsigned int), WriteBlobLSBShort(Image *,const unsigned short), WriteBlobLSBSignedLong(Image *,const signed int), WriteBlobLSBSignedShort(Image *,const signed short), WriteBlobMSBLong(Image *,const unsigned int), - WriteBlobMSBLongLong(Image *,const MagickSizeType), WriteBlobMSBShort(Image *,const unsigned short), - WriteBlobMSBSignedLong(Image *,const signed int), WriteBlobMSBSignedShort(Image *,const signed short), WriteBlobString(Image *,const char *); @@ -134,6 +134,7 @@ extern MagickExport unsigned short extern MagickExport void AttachBlob(BlobInfo *,const void *,const size_t), + AttachCustomStream(BlobInfo *,CustomStreamInfo *), *DetachBlob(BlobInfo *), DisassociateBlob(Image *), GetBlobInfo(BlobInfo *), diff --git a/MagickCore/blob.c b/MagickCore/blob.c index a2481d566..ef09452a6 100644 --- a/MagickCore/blob.c +++ b/MagickCore/blob.c @@ -275,6 +275,43 @@ MagickExport void AttachBlob(BlobInfo *blob_info,const void *blob, % % % % % % ++ A t t a c h C u s t o m S t r e a m % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% AttachCustomStream() attaches a CustomStreamInfo to the BlobInfo structure. +% +% The format of the AttachCustomStream method is: +% +% void AttachCustomStream(BlobInfo *blob_info, +% CustomStreamInfo *custom_stream) +% +% A description of each parameter follows: +% +% o blob_info: specifies a pointer to a BlobInfo structure. +% +% o custom_stream: the custom stream info. +% +*/ +MagickExport void AttachCustomStream(BlobInfo *blob_info, + CustomStreamInfo *custom_stream) +{ + assert(blob_info != (BlobInfo *) NULL); + assert(custom_stream != (CustomStreamInfo *) NULL); + assert(custom_stream->signature == MagickCoreSignature); + if (blob_info->debug != MagickFalse) + (void) LogMagickEvent(TraceEvent,GetMagickModule(),"..."); + blob_info->type=CustomStream; + blob_info->custom_stream=custom_stream; +} + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % + B l o b T o F i l e % % % % % @@ -5466,6 +5503,61 @@ MagickExport ssize_t WriteBlobLong(Image *image,const unsigned int value) % % % % % % ++ W r i t e B l o b L o n g L o n g % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% WriteBlobMSBLongLong() writes a long long value as a 64-bit quantity in the +% byte-order specified by the endian member of the image structure. +% +% The format of the WriteBlobLongLong method is: +% +% ssize_t WriteBlobLongLong(Image *image,const MagickSizeType value) +% +% A description of each parameter follows. +% +% o value: Specifies the value to write. +% +% o image: the image. +% +*/ +MagickExport ssize_t WriteBlobLongLong(Image *image,const MagickSizeType value) +{ + unsigned char + buffer[8]; + + assert(image != (Image *) NULL); + assert(image->signature == MagickCoreSignature); + if (image->endian == LSBEndian) + { + buffer[0]=(unsigned char) value; + buffer[1]=(unsigned char) (value >> 8); + buffer[2]=(unsigned char) (value >> 16); + buffer[3]=(unsigned char) (value >> 24); + buffer[4]=(unsigned char) (value >> 32); + buffer[5]=(unsigned char) (value >> 40); + buffer[6]=(unsigned char) (value >> 48); + buffer[7]=(unsigned char) (value >> 56); + return(WriteBlobStream(image,8,buffer)); + } + buffer[0]=(unsigned char) (value >> 56); + buffer[1]=(unsigned char) (value >> 48); + buffer[2]=(unsigned char) (value >> 40); + buffer[3]=(unsigned char) (value >> 32); + buffer[4]=(unsigned char) (value >> 24); + buffer[5]=(unsigned char) (value >> 16); + buffer[6]=(unsigned char) (value >> 8); + buffer[7]=(unsigned char) value; + return(WriteBlobStream(image,8,buffer)); +} + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % + W r i t e B l o b S h o r t % % % % % @@ -5509,6 +5601,63 @@ MagickExport ssize_t WriteBlobShort(Image *image,const unsigned short value) % % % % % % ++ W r i t e B l o b S i g n e d L o n g % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% WriteBlobSignedLong() writes a signed value as a 32-bit quantity in the +% byte-order specified by the endian member of the image structure. +% +% The format of the WriteBlobSignedLong method is: +% +% ssize_t WriteBlobSignedLong(Image *image,const signed int value) +% +% A description of each parameter follows. +% +% o image: the image. +% +% o value: Specifies the value to write. +% +*/ +MagickExport ssize_t WriteBlobSignedLong(Image *image,const signed int value) +{ + union + { + unsigned int + unsigned_value; + + signed int + signed_value; + } quantum; + + unsigned char + buffer[4]; + + assert(image != (Image *) NULL); + assert(image->signature == MagickCoreSignature); + quantum.signed_value=value; + if (image->endian == LSBEndian) + { + buffer[0]=(unsigned char) quantum.unsigned_value; + buffer[1]=(unsigned char) (quantum.unsigned_value >> 8); + buffer[2]=(unsigned char) (quantum.unsigned_value >> 16); + buffer[3]=(unsigned char) (quantum.unsigned_value >> 24); + return(WriteBlobStream(image,4,buffer)); + } + buffer[0]=(unsigned char) (quantum.unsigned_value >> 24); + buffer[1]=(unsigned char) (quantum.unsigned_value >> 16); + buffer[2]=(unsigned char) (quantum.unsigned_value >> 8); + buffer[3]=(unsigned char) quantum.unsigned_value; + return(WriteBlobStream(image,4,buffer)); +} + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % + W r i t e B l o b L S B L o n g % % % % % @@ -5721,99 +5870,6 @@ MagickExport ssize_t WriteBlobMSBLong(Image *image,const unsigned int value) % % % % % % -+ W r i t e B l o b M S B L o n g L o n g % -% % -% % -% % -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% WriteBlobMSBLongLong() writes a long long value as a 64-bit quantity in -% most-significant byte first order. -% -% The format of the WriteBlobMSBLongLong method is: -% -% ssize_t WriteBlobMSBLongLong(Image *image,const MagickSizeType value) -% -% A description of each parameter follows. -% -% o value: Specifies the value to write. -% -% o image: the image. -% -*/ -MagickExport ssize_t WriteBlobMSBLongLong(Image *image, - const MagickSizeType value) -{ - unsigned char - buffer[8]; - - assert(image != (Image *) NULL); - assert(image->signature == MagickCoreSignature); - buffer[0]=(unsigned char) (value >> 56); - buffer[1]=(unsigned char) (value >> 48); - buffer[2]=(unsigned char) (value >> 40); - buffer[3]=(unsigned char) (value >> 32); - buffer[4]=(unsigned char) (value >> 24); - buffer[5]=(unsigned char) (value >> 16); - buffer[6]=(unsigned char) (value >> 8); - buffer[7]=(unsigned char) value; - return(WriteBlobStream(image,8,buffer)); -} - -/* -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% % -% % -% % -+ W r i t e B l o b M S B S i g n e d L o n g % -% % -% % -% % -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% WriteBlobMSBSignedLong() writes a signed value as a 32-bit quantity in -% most-significant byte first order. -% -% The format of the WriteBlobMSBSignedLong method is: -% -% ssize_t WriteBlobMSBSignedLong(Image *image,const signed int value) -% -% A description of each parameter follows. -% -% o image: the image. -% -% o value: Specifies the value to write. -% -*/ -MagickExport ssize_t WriteBlobMSBSignedLong(Image *image,const signed int value) -{ - union - { - unsigned int - unsigned_value; - - signed int - signed_value; - } quantum; - - unsigned char - buffer[4]; - - assert(image != (Image *) NULL); - assert(image->signature == MagickCoreSignature); - quantum.signed_value=value; - buffer[0]=(unsigned char) (quantum.unsigned_value >> 24); - buffer[1]=(unsigned char) (quantum.unsigned_value >> 16); - buffer[2]=(unsigned char) (quantum.unsigned_value >> 8); - buffer[3]=(unsigned char) quantum.unsigned_value; - return(WriteBlobStream(image,4,buffer)); -} - -/* -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% % -% % -% % + W r i t e B l o b M S B S i g n e d S h o r t % % % % % diff --git a/coders/psd-private.h b/coders/psd-private.h index 64169d037..a69ba4182 100644 --- a/coders/psd-private.h +++ b/coders/psd-private.h @@ -43,7 +43,9 @@ typedef struct _PSDInfo extern ModuleExport MagickBooleanType ReadPSDLayers(Image *,const ImageInfo *,const PSDInfo *, - const MagickBooleanType,ExceptionInfo *); + ExceptionInfo *), + WritePSDLayers(Image *,const ImageInfo *,const PSDInfo *, + ExceptionInfo *); #if defined(__cplusplus) || defined(c_plusplus) } diff --git a/coders/psd.c b/coders/psd.c index 88a63e577..ac557e0b2 100644 --- a/coders/psd.c +++ b/coders/psd.c @@ -240,39 +240,58 @@ static MagickBooleanType IsPSD(const unsigned char *magick,const size_t length) % */ -static const char *CompositeOperatorToPSDBlendMode(CompositeOperator op) +static const char *CompositeOperatorToPSDBlendMode(Image *image) { - const char - *blend_mode; - - switch (op) + switch (image->compose) { - case ColorBurnCompositeOp: blend_mode = "idiv"; break; - case ColorDodgeCompositeOp: blend_mode = "div "; break; - case ColorizeCompositeOp: blend_mode = "colr"; break; - case DarkenCompositeOp: blend_mode = "dark"; break; - case DifferenceCompositeOp: blend_mode = "diff"; break; - case DissolveCompositeOp: blend_mode = "diss"; break; - case ExclusionCompositeOp: blend_mode = "smud"; break; - case HardLightCompositeOp: blend_mode = "hLit"; break; - case HardMixCompositeOp: blend_mode = "hMix"; break; - case HueCompositeOp: blend_mode = "hue "; break; - case LightenCompositeOp: blend_mode = "lite"; break; - case LinearBurnCompositeOp: blend_mode = "lbrn"; break; - case LinearDodgeCompositeOp:blend_mode = "lddg"; break; - case LinearLightCompositeOp:blend_mode = "lLit"; break; - case LuminizeCompositeOp: blend_mode = "lum "; break; - case MultiplyCompositeOp: blend_mode = "mul "; break; - case OverCompositeOp: blend_mode = "norm"; break; - case OverlayCompositeOp: blend_mode = "over"; break; - case PinLightCompositeOp: blend_mode = "pLit"; break; - case SaturateCompositeOp: blend_mode = "sat "; break; - case ScreenCompositeOp: blend_mode = "scrn"; break; - case SoftLightCompositeOp: blend_mode = "sLit"; break; - case VividLightCompositeOp: blend_mode = "vLit"; break; - default: blend_mode = "norm"; + case ColorBurnCompositeOp: + return(image->endian == LSBEndian ? "vidi" : "idiv"); + case ColorDodgeCompositeOp: + return(image->endian == LSBEndian ? " vid" : "div "); + case ColorizeCompositeOp: + return(image->endian == LSBEndian ? "rloc" : "colr"); + case DarkenCompositeOp: + return(image->endian == LSBEndian ? "krad" : "dark"); + case DifferenceCompositeOp: + return(image->endian == LSBEndian ? "ffid" : "diff"); + case DissolveCompositeOp: + return(image->endian == LSBEndian ? "ssid" : "diss"); + case ExclusionCompositeOp: + return(image->endian == LSBEndian ? "dums" : "smud"); + case HardLightCompositeOp: + return(image->endian == LSBEndian ? "tiLh" : "hLit"); + case HardMixCompositeOp: + return(image->endian == LSBEndian ? "xiMh" : "hMix"); + case HueCompositeOp: + return(image->endian == LSBEndian ? " euh" : "hue "); + case LightenCompositeOp: + return(image->endian == LSBEndian ? "etil" : "lite"); + case LinearBurnCompositeOp: + return(image->endian == LSBEndian ? "nrbl" : "lbrn"); + case LinearDodgeCompositeOp: + return(image->endian == LSBEndian ? "gddl" : "lddg"); + case LinearLightCompositeOp: + return(image->endian == LSBEndian ? "tiLl" : "lLit"); + case LuminizeCompositeOp: + return(image->endian == LSBEndian ? " mul" : "lum "); + case MultiplyCompositeOp: + return(image->endian == LSBEndian ? " lum" : "mul "); + case OverlayCompositeOp: + return(image->endian == LSBEndian ? "revo" : "over"); + case PinLightCompositeOp: + return(image->endian == LSBEndian ? "tiLp" : "pLit"); + case SaturateCompositeOp: + return(image->endian == LSBEndian ? " tas" : "sat "); + case ScreenCompositeOp: + return(image->endian == LSBEndian ? "nrcs" : "scrn"); + case SoftLightCompositeOp: + return(image->endian == LSBEndian ? "tiLs" : "sLit"); + case VividLightCompositeOp: + return(image->endian == LSBEndian ? "tiLv" : "vLit"); + case OverCompositeOp: + default: + return(image->endian == LSBEndian ? "mron" : "norm"); } - return(blend_mode); } /* @@ -1856,8 +1875,7 @@ static MagickBooleanType ReadPSDLayersInternal(Image *image, } ModuleExport MagickBooleanType ReadPSDLayers(Image *image, - const ImageInfo *image_info,const PSDInfo *psd_info, - const MagickBooleanType skip_layers,ExceptionInfo *exception) + const ImageInfo *image_info,const PSDInfo *psd_info,ExceptionInfo *exception) { PolicyDomain domain; @@ -1868,8 +1886,8 @@ ModuleExport MagickBooleanType ReadPSDLayers(Image *image, domain=CoderPolicyDomain; rights=ReadPolicyRights; if (IsRightsAuthorized(domain,rights,"PSD") == MagickFalse) - return(MagickFalse); - return(ReadPSDLayersInternal(image,image_info,psd_info,skip_layers, + return(MagickTrue); + return(ReadPSDLayersInternal(image,image_info,psd_info,MagickFalse, exception)); } @@ -2344,8 +2362,8 @@ static inline ssize_t SetPSDSize(const PSDInfo *psd_info,Image *image, const MagickSizeType size) { if (psd_info->version == 1) - return(WriteBlobMSBLong(image,(unsigned int) size)); - return(WriteBlobMSBLongLong(image,size)); + return(WriteBlobLong(image,(unsigned int) size)); + return(WriteBlobLongLong(image,size)); } static inline ssize_t WritePSDSize(const PSDInfo *psd_info,Image *image, @@ -2359,10 +2377,7 @@ static inline ssize_t WritePSDSize(const PSDInfo *psd_info,Image *image, current_offset=TellBlob(image); SeekBlob(image,offset,SEEK_SET); - if (psd_info->version == 1) - result=WriteBlobMSBLong(image,(unsigned int) size); - else - result=WriteBlobMSBLongLong(image,size); + result=SetPSDSize(psd_info, image, size); SeekBlob(image,current_offset,SEEK_SET); return(result); } @@ -2490,17 +2505,17 @@ static size_t WriteCompressionStart(const PSDInfo *psd_info,Image *image, if (next_image->compression == RLECompression) { - length=WriteBlobMSBShort(image,RLE); + length=WriteBlobShort(image,RLE); for (i=0; i < channels; i++) for (y=0; y < (ssize_t) next_image->rows; y++) length+=SetPSDOffset(psd_info,image,0); } #ifdef MAGICKCORE_ZLIB_DELEGATE else if (next_image->compression == ZipCompression) - length=WriteBlobMSBShort(image,ZipWithoutPrediction); + length=WriteBlobShort(image,ZipWithoutPrediction); #endif else - length=WriteBlobMSBShort(image,Raw); + length=WriteBlobShort(image,Raw); return(length); } @@ -2874,7 +2889,7 @@ static inline size_t WriteChannelSize(const PSDInfo *psd_info,Image *image, size_t count; - count=WriteBlobMSBSignedShort(image,channel); + count=(size_t) WriteBlobShort(image,channel); count+=SetPSDSize(psd_info,image,0); return(count); } @@ -3087,8 +3102,9 @@ static const StringInfo *GetAdditionalInformation(const ImageInfo *image_info, return(profile); } -static MagickBooleanType WritePSDImage(const ImageInfo *image_info, - Image *image,ExceptionInfo *exception) +static MagickBooleanType WritePSDLayersInternal(Image *image, + const ImageInfo *image_info,const PSDInfo *psd_info,size_t *layers_size, + ExceptionInfo *exception) { char layer_name[MagickPathExtent]; @@ -3097,7 +3113,6 @@ static MagickBooleanType WritePSDImage(const ImageInfo *image_info, *property; const StringInfo - *icc_profile, *info; Image @@ -3111,9 +3126,6 @@ static MagickBooleanType WritePSDImage(const ImageInfo *image_info, *layer_size_offsets, size_offset; - PSDInfo - psd_info; - register ssize_t i; @@ -3122,11 +3134,221 @@ static MagickBooleanType WritePSDImage(const ImageInfo *image_info, layer_index, length, name_length, - num_channels, - packet_size, rounded_size, size; + status=MagickTrue; + base_image=GetNextImageInList(image); + if (base_image == (Image *) NULL) + base_image=image; + size=0; + size_offset=TellBlob(image); + SetPSDSize(psd_info,image,0); + layer_count=0; + for (next_image=base_image; next_image != NULL; ) + { + layer_count++; + next_image=GetNextImageInList(next_image); + } + if (image->alpha_trait != UndefinedPixelTrait) + size+=WriteBlobShort(image,-(unsigned short) layer_count); + else + size+=WriteBlobShort(image,(unsigned short) layer_count); + layer_size_offsets=(MagickOffsetType *) AcquireQuantumMemory( + (size_t) layer_count,sizeof(MagickOffsetType)); + if (layer_size_offsets == (MagickOffsetType *) NULL) + ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed"); + layer_index=0; + for (next_image=base_image; next_image != NULL; ) + { + Image + *mask; + + unsigned char + default_color; + + unsigned short + channels, + total_channels; + + mask=(Image *) NULL; + property=GetImageArtifact(next_image,"psd:opacity-mask"); + default_color=0; + if (property != (const char *) NULL) + { + mask=(Image *) GetImageRegistry(ImageRegistryType,property,exception); + default_color=strlen(property) == 9 ? 255 : 0; + } + size+=WriteBlobSignedLong(image,(signed int) next_image->page.y); + size+=WriteBlobSignedLong(image,(signed int) next_image->page.x); + size+=WriteBlobSignedLong(image,(signed int) (next_image->page.y+ + next_image->rows)); + size+=WriteBlobSignedLong(image,(signed int) (next_image->page.x+ + next_image->columns)); + channels=1U; + if ((next_image->storage_class != PseudoClass) && + (IsImageGray(next_image) == MagickFalse)) + channels=next_image->colorspace == CMYKColorspace ? 4U : 3U; + total_channels=channels; + if (next_image->alpha_trait != UndefinedPixelTrait) + total_channels++; + if (mask != (Image *) NULL) + total_channels++; + size+=WriteBlobShort(image,total_channels); + layer_size_offsets[layer_index++]=TellBlob(image); + for (i=0; i < (ssize_t) channels; i++) + size+=WriteChannelSize(psd_info,image,(signed short) i); + if (next_image->alpha_trait != UndefinedPixelTrait) + size+=WriteChannelSize(psd_info,image,-1); + if (mask != (Image *) NULL) + size+=WriteChannelSize(psd_info,image,-2); + size+=WriteBlobString(image,image->endian == LSBEndian ? "MIB8" :"8BIM"); + size+=WriteBlobString(image,CompositeOperatorToPSDBlendMode(image)); + property=GetImageArtifact(next_image,"psd:layer.opacity"); + if (property != (const char *) NULL) + { + Quantum + opacity; + + opacity=(Quantum) StringToInteger(property); + size+=WriteBlobByte(image,ScaleQuantumToChar(opacity)); + (void) ApplyPSDLayerOpacity(next_image,opacity,MagickTrue,exception); + } + else + size+=WriteBlobByte(image,255); + size+=WriteBlobByte(image,0); + size+=WriteBlobByte(image,next_image->compose==NoCompositeOp ? + 1 << 0x02 : 1); /* layer properties - visible, etc. */ + size+=WriteBlobByte(image,0); + info=GetAdditionalInformation(image_info,next_image,exception); + property=(const char *) GetImageProperty(next_image,"label",exception); + if (property == (const char *) NULL) + { + (void) FormatLocaleString(layer_name,MagickPathExtent,"L%.20g", + (double) layer_index); + property=layer_name; + } + name_length=strlen(property)+1; + if ((name_length % 4) != 0) + name_length+=(4-(name_length % 4)); + if (info != (const StringInfo *) NULL) + name_length+=GetStringInfoLength(info); + name_length+=8; + if (mask != (Image *) NULL) + name_length+=20; + size+=WriteBlobLong(image,(unsigned int) name_length); + if (mask == (Image *) NULL) + size+=WriteBlobLong(image,0); + else + { + if (mask->compose != NoCompositeOp) + (void) ApplyPSDOpacityMask(next_image,mask,ScaleCharToQuantum( + default_color),MagickTrue,exception); + mask->page.y+=image->page.y; + mask->page.x+=image->page.x; + size+=WriteBlobLong(image,20); + size+=WriteBlobSignedLong(image,mask->page.y); + size+=WriteBlobSignedLong(image,mask->page.x); + size+=WriteBlobSignedLong(image,(const signed int) mask->rows+ + mask->page.y); + size+=WriteBlobSignedLong(image,(const signed int) mask->columns+ + mask->page.x); + size+=WriteBlobByte(image,default_color); + size+=WriteBlobByte(image,mask->compose == NoCompositeOp ? 2 : 0); + size+=WriteBlobMSBShort(image,0); + } + size+=WriteBlobLong(image,0); + size+=WritePascalString(image,property,4); + if (info != (const StringInfo *) NULL) + size+=WriteBlob(image,GetStringInfoLength(info), + GetStringInfoDatum(info)); + next_image=GetNextImageInList(next_image); + } + /* + Now the image data! + */ + next_image=base_image; + layer_index=0; + while (next_image != NULL) + { + length=WritePSDChannels(psd_info,image_info,image,next_image, + layer_size_offsets[layer_index++],MagickTrue,exception); + if (length == 0) + { + status=MagickFalse; + break; + } + size+=length; + next_image=GetNextImageInList(next_image); + } + /* + Write the total size + */ + if (layers_size != (size_t*) NULL) + *layers_size=size; + if ((size/2) != ((size+1)/2)) + rounded_size=size+1; + else + rounded_size=size; + (void) WritePSDSize(psd_info,image,rounded_size,size_offset); + layer_size_offsets=(MagickOffsetType *) RelinquishMagickMemory( + layer_size_offsets); + /* + Remove the opacity mask from the registry + */ + next_image=base_image; + while (next_image != (Image *) NULL) + { + property=GetImageArtifact(next_image,"psd:opacity-mask"); + if (property != (const char *) NULL) + DeleteImageRegistry(property); + next_image=GetNextImageInList(next_image); + } + + return(status); +} + +ModuleExport MagickBooleanType WritePSDLayers(Image * image, + const ImageInfo *image_info,const PSDInfo *psd_info,ExceptionInfo *exception) +{ + PolicyDomain + domain; + + PolicyRights + rights; + + domain=CoderPolicyDomain; + rights=WritePolicyRights; + if (IsRightsAuthorized(domain,rights,"PSD") == MagickFalse) + return(MagickTrue); + return WritePSDLayersInternal(image,image_info,psd_info,(size_t*) NULL, + exception); +} + +static MagickBooleanType WritePSDImage(const ImageInfo *image_info, + Image *image,ExceptionInfo *exception) +{ + const StringInfo + *icc_profile; + + Image + *base_image, + *next_image; + + MagickBooleanType + status; + + PSDInfo + psd_info; + + register ssize_t + i; + + size_t + length, + num_channels, + packet_size; + StringInfo *bim_profile; @@ -3270,175 +3492,22 @@ static MagickBooleanType WritePSDImage(const ImageInfo *image_info, PSDQuantum(GetStringInfoLength(icc_profile))) (void) WriteBlobByte(image,0); } - base_image=GetNextImageInList(image); - if (base_image == (Image *) NULL) - base_image=image; - size=0; - size_offset=TellBlob(image); - SetPSDSize(&psd_info,image,0); - SetPSDSize(&psd_info,image,0); - layer_count=0; - for (next_image=base_image; next_image != NULL; ) - { - layer_count++; - next_image=GetNextImageInList(next_image); - } - if (image->alpha_trait != UndefinedPixelTrait) - size+=WriteBlobMSBShort(image,-(unsigned short) layer_count); - else - size+=WriteBlobMSBShort(image,(unsigned short) layer_count); - layer_size_offsets=(MagickOffsetType *) AcquireQuantumMemory( - (size_t) layer_count,sizeof(MagickOffsetType)); - if (layer_size_offsets == (MagickOffsetType *) NULL) - ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed"); - layer_index=0; - for (next_image=base_image; next_image != NULL; ) - { - Image - *mask; - - unsigned char - default_color; + if (status != MagickFalse) + { + MagickOffsetType + size_offset; - unsigned short - channels, - total_channels; + size_t + size; - mask=(Image *) NULL; - property=GetImageArtifact(next_image,"psd:opacity-mask"); - default_color=0; - if (property != (const char *) NULL) - { - mask=(Image *) GetImageRegistry(ImageRegistryType,property,exception); - default_color=strlen(property) == 9 ? 255 : 0; - } - size+=WriteBlobMSBLong(image,(unsigned int) next_image->page.y); - size+=WriteBlobMSBLong(image,(unsigned int) next_image->page.x); - size+=WriteBlobMSBLong(image,(unsigned int) (next_image->page.y+ - next_image->rows)); - size+=WriteBlobMSBLong(image,(unsigned int) (next_image->page.x+ - next_image->columns)); - channels=1U; - if ((next_image->storage_class != PseudoClass) && - (IsImageGray(next_image) == MagickFalse)) - channels=next_image->colorspace == CMYKColorspace ? 4U : 3U; - total_channels=channels; - if (next_image->alpha_trait != UndefinedPixelTrait) - total_channels++; - if (mask != (Image *) NULL) - total_channels++; - size+=WriteBlobMSBShort(image,total_channels); - layer_size_offsets[layer_index++]=TellBlob(image); - for (i=0; i < (ssize_t) channels; i++) - size+=WriteChannelSize(&psd_info,image,(signed short) i); - if (next_image->alpha_trait != UndefinedPixelTrait) - size+=WriteChannelSize(&psd_info,image,-1); - if (mask != (Image *) NULL) - size+=WriteChannelSize(&psd_info,image,-2); - size+=WriteBlob(image,4,(const unsigned char *) "8BIM"); - size+=WriteBlob(image,4,(const unsigned char *) - CompositeOperatorToPSDBlendMode(next_image->compose)); - property=GetImageArtifact(next_image,"psd:layer.opacity"); - if (property != (const char *) NULL) - { - Quantum - opacity; - - opacity=(Quantum) StringToInteger(property); - size+=WriteBlobByte(image,ScaleQuantumToChar(opacity)); - (void) ApplyPSDLayerOpacity(next_image,opacity,MagickTrue,exception); - } - else - size+=WriteBlobByte(image,255); - size+=WriteBlobByte(image,0); - size+=WriteBlobByte(image,next_image->compose==NoCompositeOp ? - 1 << 0x02 : 1); /* layer properties - visible, etc. */ - size+=WriteBlobByte(image,0); - info=GetAdditionalInformation(image_info,next_image,exception); - property=(const char *) GetImageProperty(next_image,"label",exception); - if (property == (const char *) NULL) - { - (void) FormatLocaleString(layer_name,MagickPathExtent,"L%.20g", - (double) layer_index); - property=layer_name; - } - name_length=strlen(property)+1; - if ((name_length % 4) != 0) - name_length+=(4-(name_length % 4)); - if (info != (const StringInfo *) NULL) - name_length+=GetStringInfoLength(info); - name_length+=8; - if (mask != (Image *) NULL) - name_length+=20; - size+=WriteBlobMSBLong(image,(unsigned int) name_length); - if (mask == (Image *) NULL) - size+=WriteBlobMSBLong(image,0); - else - { - if (mask->compose != NoCompositeOp) - (void) ApplyPSDOpacityMask(next_image,mask,ScaleCharToQuantum( - default_color),MagickTrue,exception); - mask->page.y+=image->page.y; - mask->page.x+=image->page.x; - size+=WriteBlobMSBLong(image,20); - size+=WriteBlobMSBSignedLong(image,mask->page.y); - size+=WriteBlobMSBSignedLong(image,mask->page.x); - size+=WriteBlobMSBSignedLong(image,(const signed int) mask->rows+ - mask->page.y); - size+=WriteBlobMSBSignedLong(image,(const signed int) mask->columns+ - mask->page.x); - size+=WriteBlobByte(image,default_color); - size+=WriteBlobByte(image,mask->compose == NoCompositeOp ? 2 : 0); - size+=WriteBlobMSBShort(image,0); - } - size+=WriteBlobMSBLong(image,0); - size+=WritePascalString(image,property,4); - if (info != (const StringInfo *) NULL) - size+=WriteBlob(image,GetStringInfoLength(info), - GetStringInfoDatum(info)); - next_image=GetNextImageInList(next_image); - } - /* - Now the image data! - */ - next_image=base_image; - layer_index=0; - while (next_image != NULL) - { - length=WritePSDChannels(&psd_info,image_info,image,next_image, - layer_size_offsets[layer_index++],MagickTrue,exception); - if (length == 0) - { - status=MagickFalse; - break; - } - size+=length; - next_image=GetNextImageInList(next_image); - } + size_offset=TellBlob(image); + SetPSDSize(&psd_info,image,0); + status=WritePSDLayersInternal(image,image_info,&psd_info,&size, + exception); + size_offset+=WritePSDSize(&psd_info,image,size+ + (psd_info.version == 1 ? 8 : 16),size_offset); + } (void) WriteBlobMSBLong(image,0); /* user mask data */ - /* - Write the total size - */ - size_offset+=WritePSDSize(&psd_info,image,size+ - (psd_info.version == 1 ? 8 : 16),size_offset); - if ((size/2) != ((size+1)/2)) - rounded_size=size+1; - else - rounded_size=size; - (void) WritePSDSize(&psd_info,image,rounded_size,size_offset); - layer_size_offsets=(MagickOffsetType *) RelinquishMagickMemory( - layer_size_offsets); - /* - Remove the opacity mask from the registry - */ - next_image=base_image; - while (next_image != (Image *) NULL) - { - property=GetImageArtifact(next_image,"psd:opacity-mask"); - if (property != (const char *) NULL) - DeleteImageRegistry(property); - next_image=GetNextImageInList(next_image); - } /* Write composite image. */ diff --git a/coders/tiff.c b/coders/tiff.c index 77512c668..ca34bc684 100644 --- a/coders/tiff.c +++ b/coders/tiff.c @@ -121,6 +121,20 @@ typedef enum ReadGenericMethod } TIFFMethodType; +typedef struct _PhotoshopProfile +{ + StringInfo + *data; + + MagickOffsetType + offset; + + size_t + length, + extent, + quantum; +} PhotoshopProfile; + #if defined(MAGICKCORE_HAVE_TIFFREADEXIFDIRECTORY) typedef struct _ExifInfo { @@ -222,6 +236,65 @@ static MagickBooleanType WriteGROUP4Image(const ImageInfo *,Image *,ExceptionInfo *), WritePTIFImage(const ImageInfo *,Image *,ExceptionInfo *), WriteTIFFImage(const ImageInfo *,Image *,ExceptionInfo *); + +static MagickOffsetType TIFFSeekCustomStream(const MagickOffsetType offset, + const int whence,void *user_data) +{ + PhotoshopProfile + *profile; + + profile=(PhotoshopProfile *) user_data; + switch (whence) + { + case SEEK_SET: + default: + { + if (offset < 0) + return(-1); + profile->offset=offset; + break; + } + case SEEK_CUR: + { + if ((profile->offset+offset) < 0) + return(-1); + profile->offset+=offset; + break; + } + case SEEK_END: + { + if (((MagickOffsetType) profile->length+offset) < 0) + return(-1); + profile->offset=profile->length+offset; + break; + } + } + + return(profile->offset); +} + +static MagickOffsetType TIFFTellCustomStream(void *user_data) +{ + PhotoshopProfile + *profile; + + profile=(PhotoshopProfile *) user_data; + return(profile->offset); +} + +static void InitPSDInfo(const Image *image, PSDInfo *info) +{ + info->version=1; + info->columns=image->columns; + info->rows=image->rows; + /* Setting the mode to a value that won't change the colorspace */ + info->mode=10; + info->channels=1U; + if (image->storage_class == PseudoClass) + info->mode=2; // indexed mode + else + info->channels=(unsigned short) image->number_channels; +} #endif /* @@ -1008,6 +1081,45 @@ static TIFFMethodType GetJPEGMethod(Image* image,TIFF *tiff,uint16 photometric, return(method); } +static ssize_t TIFFReadCustomStream(unsigned char *data,const size_t count, + void *user_data) +{ + PhotoshopProfile + *profile; + + size_t + total; + + ssize_t + remaining; + + if (count == 0) + return(0); + profile=(PhotoshopProfile *) user_data; + remaining=(MagickOffsetType) profile->length-profile->offset; + if (remaining <= 0) + return(-1); + total=MagickMin(count, (size_t) remaining); + (void) memcpy(data,profile->data->datum+profile->offset,total); + return(total); +} + +static CustomStreamInfo *TIFFAcquireCustomStreamForReading( + PhotoshopProfile *profile,ExceptionInfo *exception) +{ + CustomStreamInfo + *custom_stream; + + custom_stream=AcquireCustomStreamInfo(exception); + if (custom_stream == (CustomStreamInfo *) NULL) + return(custom_stream); + SetCustomStreamData(custom_stream,(void *) profile); + SetCustomStreamReader(custom_stream,TIFFReadCustomStream); + SetCustomStreamSeeker(custom_stream,TIFFSeekCustomStream); + SetCustomStreamTeller(custom_stream,TIFFTellCustomStream); + return(custom_stream); +} + static void TIFFReadPhotoshopLayers(Image* image,const ImageInfo *image_info, ExceptionInfo *exception) { @@ -1015,11 +1127,17 @@ static void TIFFReadPhotoshopLayers(Image* image,const ImageInfo *image_info, *option; const StringInfo - *layer_info; + *profile; + + CustomStreamInfo + *custom_stream; Image *layers; + PhotoshopProfile + photoshop_profile; + PSDInfo info; @@ -1033,41 +1151,44 @@ static void TIFFReadPhotoshopLayers(Image* image,const ImageInfo *image_info, option=GetImageOption(image_info,"tiff:ignore-layers"); if (option != (const char * ) NULL) return; - layer_info=GetImageProfile(image,"tiff:37724"); - if (layer_info == (const StringInfo *) NULL) + profile=GetImageProfile(image,"tiff:37724"); + if (profile == (const StringInfo *) NULL) return; - for (i=0; i < (ssize_t) layer_info->length-8; i++) + for (i=0; i < (ssize_t) profile->length-8; i++) { - if (LocaleNCompare((const char *) (layer_info->datum+i), + if (LocaleNCompare((const char *) (profile->datum+i), image->endian == MSBEndian ? "8BIM" : "MIB8",4) != 0) continue; i+=4; - if ((LocaleNCompare((const char *) (layer_info->datum+i), + if ((LocaleNCompare((const char *) (profile->datum+i), image->endian == MSBEndian ? "Layr" : "ryaL",4) == 0) || - (LocaleNCompare((const char *) (layer_info->datum+i), + (LocaleNCompare((const char *) (profile->datum+i), image->endian == MSBEndian ? "LMsk" : "ksML",4) == 0) || - (LocaleNCompare((const char *) (layer_info->datum+i), + (LocaleNCompare((const char *) (profile->datum+i), image->endian == MSBEndian ? "Lr16" : "61rL",4) == 0) || - (LocaleNCompare((const char *) (layer_info->datum+i), + (LocaleNCompare((const char *) (profile->datum+i), image->endian == MSBEndian ? "Lr32" : "23rL",4) == 0)) break; } i+=4; - if (i >= (ssize_t) (layer_info->length-8)) + if (i >= (ssize_t) (profile->length-8)) + return; + photoshop_profile.data=(StringInfo *) profile; + photoshop_profile.length=profile->length; + custom_stream=TIFFAcquireCustomStreamForReading(&photoshop_profile,exception); + if (custom_stream == (CustomStreamInfo *) NULL) return; layers=CloneImage(image,image->columns,image->rows,MagickTrue,exception); + if (layers == (Image *) NULL) + { + custom_stream=DestroyCustomStreamInfo(custom_stream); + return; + } (void) DeleteImageProfile(layers,"tiff:37724"); - AttachBlob(layers->blob,layer_info->datum,layer_info->length); + AttachCustomStream(layers->blob,custom_stream); SeekBlob(layers,(MagickOffsetType) i,SEEK_SET); - info.version=1; - info.columns=layers->columns; - info.rows=layers->rows; - info.channels=(unsigned short) layers->number_channels; - /* Setting the mode to a value that won't change the colorspace */ - info.mode=10; - ReadPSDLayers(layers,image_info,&info,MagickFalse,exception); - /* we need to set the datum in case a realloc happened */ - ((StringInfo *) layer_info)->datum=GetBlobStreamData(layers); + InitPSDInfo(layers,&info); + (void) ReadPSDLayers(layers,image_info,&info,exception); DeleteImageFromList(&layers); if (layers != (Image *) NULL) { @@ -1080,6 +1201,7 @@ static void TIFFReadPhotoshopLayers(Image* image,const ImageInfo *image_info, layers=GetNextImageInList(layers); } } + custom_stream=DestroyCustomStreamInfo(custom_stream); } #if defined(__cplusplus) || defined(c_plusplus) @@ -2931,6 +3053,137 @@ static int32 TIFFWritePixels(TIFF *tiff,TIFFInfo *tiff_info,ssize_t row, return(status); } +static ssize_t TIFFWriteCustomStream(unsigned char *data,const size_t count, + void *user_data) +{ + PhotoshopProfile + *profile; + + if (count == 0) + return(0); + profile=(PhotoshopProfile *) user_data; + if ((profile->offset+(MagickOffsetType) count) >= + (MagickOffsetType) profile->extent) + { + profile->extent+=count+profile->quantum; + profile->quantum<<=1; + SetStringInfoLength(profile->data,profile->extent); + } + (void) memcpy(profile->data->datum+profile->offset,data,count); + profile->offset+=count; + return(count); +} + +static CustomStreamInfo *TIFFAcquireCustomStreamForWriting( + PhotoshopProfile *profile,ExceptionInfo *exception) +{ + CustomStreamInfo + *custom_stream; + + custom_stream=AcquireCustomStreamInfo(exception); + if (custom_stream == (CustomStreamInfo *) NULL) + return(custom_stream); + SetCustomStreamData(custom_stream,(void *) profile); + SetCustomStreamWriter(custom_stream,TIFFWriteCustomStream); + SetCustomStreamSeeker(custom_stream,TIFFSeekCustomStream); + SetCustomStreamTeller(custom_stream,TIFFTellCustomStream); + return(custom_stream); +} + +static MagickBooleanType TIFFWritePhotoshopLayers(Image* image, + const ImageInfo *image_info,ExceptionInfo *exception) +{ + BlobInfo + *blob; + + CustomStreamInfo + *custom_stream; + + Image + *next; + + ImageInfo + *clone_info; + + MagickBooleanType + status; + + PhotoshopProfile + profile; + + PSDInfo + info; + + size_t + length; + + StringInfo + *layers; + + next=image->next; + if (next == (Image *) NULL) + return(MagickTrue); + clone_info=CloneImageInfo(image_info); + if (clone_info == (ImageInfo *) NULL) + ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", + image->filename); + profile.offset=0; + profile.quantum=MagickMinBlobExtent; + layers=AcquireStringInfo(profile.quantum); + if (layers == (StringInfo *) NULL) + { + clone_info=DestroyImageInfo(clone_info); + ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", + image->filename); + } + profile.data=layers; + profile.extent=layers->length; + custom_stream=TIFFAcquireCustomStreamForWriting(&profile,exception); + if (custom_stream == (CustomStreamInfo *) NULL) + { + clone_info=DestroyImageInfo(clone_info); + layers=DestroyStringInfo(layers); + ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", + image->filename); + } + blob=CloneBlobInfo((BlobInfo *) NULL); + if (blob == (BlobInfo *) NULL) + { + clone_info=DestroyImageInfo(clone_info); + layers=DestroyStringInfo(layers); + custom_stream=DestroyCustomStreamInfo(custom_stream); + ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", + image->filename); + } + DestroyBlob(next); + next->blob=blob; + while (next != (Image *) NULL) + next=SyncNextImageInList(next); + next=image->next; + AttachCustomStream(next->blob,custom_stream); + InitPSDInfo(image,&info); + if (next->endian == UndefinedEndian) + next->endian=(HOST_FILLORDER == FILLORDER_LSB2MSB) ? LSBEndian : MSBEndian; + WriteBlobString(next,"Adobe Photoshop Document Data Block"); + WriteBlobByte(next,0); + WriteBlobString(next,next->endian == LSBEndian ? "MIB8ryaL" : "8BIMLayr"); + status=WritePSDLayers(next,clone_info,&info,exception); + if (status != MagickFalse) + { + SetStringInfoLength(layers,(size_t) profile.offset); + status=SetImageProfile(image,"tiff:37724",layers,exception); + } + while (next != (Image *) NULL) + { + CloseBlob(next); + next=next->next; + } + layers=DestroyStringInfo(layers); + clone_info=DestroyImageInfo(clone_info); + custom_stream=DestroyCustomStreamInfo(custom_stream); + return(status); +} + static void TIFFSetProfiles(TIFF *tiff,Image *image) { const char @@ -2994,7 +3247,7 @@ static void TIFFSetProfiles(TIFF *tiff,Image *image) } } -static void TIFFSetProperties(TIFF *tiff,const ImageInfo *image_info, +static void TIFFSetProperties(TIFF *tiff,const MagickBooleanType adjoin, Image *image,ExceptionInfo *exception) { const char @@ -3056,7 +3309,7 @@ static void TIFFSetProperties(TIFF *tiff,const ImageInfo *image_info, page=(uint16) image->scene; pages=(uint16) GetImageListLength(image); - if ((image_info->adjoin != MagickFalse) && (pages > 1)) + if ((adjoin != MagickFalse) && (pages > 1)) (void) TIFFSetField(tiff,TIFFTAG_SUBFILETYPE,FILETYPE_PAGE); (void) TIFFSetField(tiff,TIFFTAG_PAGENUMBER,page,pages); } @@ -3145,6 +3398,7 @@ static MagickBooleanType WriteTIFFImage(const ImageInfo *image_info, endian_type; MagickBooleanType + adjoin, debug, status; @@ -3228,6 +3482,7 @@ static MagickBooleanType WriteTIFFImage(const ImageInfo *image_info, scene=0; debug=IsEventLogging(); (void) debug; + adjoin=image_info->adjoin; do { /* @@ -3682,8 +3937,14 @@ static MagickBooleanType WriteTIFFImage(const ImageInfo *image_info, chromaticity[1]=(float) image->chromaticity.white_point.y; (void) TIFFSetField(tiff,TIFFTAG_WHITEPOINT,chromaticity); } + option=GetImageOption(image_info,"tiff:write-layers"); + if (IsStringTrue(option) != MagickFalse) + { + (void) TIFFWritePhotoshopLayers(image,image_info,exception); + adjoin=MagickFalse; + } if ((LocaleCompare(image_info->magick,"PTIF") != 0) && - (image_info->adjoin != MagickFalse) && (GetImageListLength(image) > 1)) + (adjoin != MagickFalse) && (GetImageListLength(image) > 1)) { (void) TIFFSetField(tiff,TIFFTAG_SUBFILETYPE,FILETYPE_PAGE); if (image->scene != 0) @@ -3692,7 +3953,7 @@ static MagickBooleanType WriteTIFFImage(const ImageInfo *image_info, } if (image->orientation != UndefinedOrientation) (void) TIFFSetField(tiff,TIFFTAG_ORIENTATION,(uint16) image->orientation); - (void) TIFFSetProfiles(tiff,image); + TIFFSetProfiles(tiff,image); { uint16 page, @@ -3701,11 +3962,11 @@ static MagickBooleanType WriteTIFFImage(const ImageInfo *image_info, page=(uint16) scene; pages=(uint16) GetImageListLength(image); if ((LocaleCompare(image_info->magick,"PTIF") != 0) && - (image_info->adjoin != MagickFalse) && (pages > 1)) + (adjoin != MagickFalse) && (pages > 1)) (void) TIFFSetField(tiff,TIFFTAG_SUBFILETYPE,FILETYPE_PAGE); (void) TIFFSetField(tiff,TIFFTAG_PAGENUMBER,page,pages); } - (void) TIFFSetProperties(tiff,image_info,image,exception); + (void) TIFFSetProperties(tiff,adjoin,image,exception); DisableMSCWarning(4127) if (0) RestoreMSCWarning @@ -3966,7 +4227,7 @@ RestoreMSCWarning GetImageListLength(image)); if (status == MagickFalse) break; - } while (image_info->adjoin != MagickFalse); + } while (adjoin != MagickFalse); TIFFClose(tiff); return(MagickTrue); }