/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % X X CCCC FFFFF % % X X C F % % X C FFF % % X X C F % % X X CCCC F % % % % % % Read GIMP XCF Image Format % % % % Software Design % % Leonard Rosenthol % % November 2001 % % % % % % Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization % % dedicated to making software imaging solutions freely available. % % % % You may not use this file except in compliance with the License. You may % % obtain a copy of the License at % % % % https://imagemagick.org/script/license.php % % % % Unless required by applicable law or agreed to in writing, software % % distributed under the License is distributed on an "AS IS" BASIS, % % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % % See the License for the specific language governing permissions and % % limitations under the License. % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % */ /* Include declarations. */ #include "MagickCore/studio.h" #include "MagickCore/blob.h" #include "MagickCore/blob-private.h" #include "MagickCore/cache.h" #include "MagickCore/color.h" #include "MagickCore/composite.h" #include "MagickCore/exception.h" #include "MagickCore/exception-private.h" #include "MagickCore/image.h" #include "MagickCore/image-private.h" #include "MagickCore/list.h" #include "MagickCore/magick.h" #include "MagickCore/memory_.h" #include "MagickCore/pixel.h" #include "MagickCore/pixel-accessor.h" #include "MagickCore/property.h" #include "MagickCore/quantize.h" #include "MagickCore/quantum-private.h" #include "MagickCore/resource_.h" #include "MagickCore/static.h" #include "MagickCore/string_.h" #include "MagickCore/string-private.h" #include "MagickCore/module.h" /* Typedef declarations. */ typedef enum { GIMP_RGB, GIMP_GRAY, GIMP_INDEXED } GimpImageBaseType; typedef enum { PROP_END = 0, PROP_COLORMAP = 1, PROP_ACTIVE_LAYER = 2, PROP_ACTIVE_CHANNEL = 3, PROP_SELECTION = 4, PROP_FLOATING_SELECTION = 5, PROP_OPACITY = 6, PROP_MODE = 7, PROP_VISIBLE = 8, PROP_LINKED = 9, PROP_PRESERVE_TRANSPARENCY = 10, PROP_APPLY_MASK = 11, PROP_EDIT_MASK = 12, PROP_SHOW_MASK = 13, PROP_SHOW_MASKED = 14, PROP_OFFSETS = 15, PROP_COLOR = 16, PROP_COMPRESSION = 17, PROP_GUIDES = 18, PROP_RESOLUTION = 19, PROP_TATTOO = 20, PROP_PARASITES = 21, PROP_UNIT = 22, PROP_PATHS = 23, PROP_USER_UNIT = 24 } PropType; typedef enum { COMPRESS_NONE = 0, COMPRESS_RLE = 1, COMPRESS_ZLIB = 2, /* unused */ COMPRESS_FRACTAL = 3 /* unused */ } XcfCompressionType; typedef struct { size_t version, width, height, image_type, bytes_per_pixel; int compression; size_t file_size; size_t number_layers; } XCFDocInfo; typedef struct { char name[1024]; unsigned int active; size_t width, height, type, alpha, visible, linked, preserve_trans, apply_mask, show_mask, edit_mask, floating_offset; ssize_t offset_x, offset_y; size_t mode, tattoo; Image *image; } XCFLayerInfo; #define TILE_WIDTH 64 #define TILE_HEIGHT 64 typedef struct { unsigned char red, green, blue, alpha; } XCFPixelInfo; /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s X C F % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsXCF() returns MagickTrue if the image format type, identified by the % magick string, is XCF (GIMP native format). % % The format of the IsXCF method is: % % MagickBooleanType IsXCF(const unsigned char *magick,const size_t length) % % A description of each parameter follows: % % o magick: compare image format pattern against these bytes. % % o length: Specifies the length of the magick string. % % */ static MagickBooleanType IsXCF(const unsigned char *magick,const size_t length) { if (length < 8) return(MagickFalse); if (LocaleNCompare((char *) magick,"gimp xcf",8) == 0) return(MagickTrue); return(MagickFalse); } typedef enum { GIMP_LAYER_MODE_NORMAL_LEGACY, GIMP_LAYER_MODE_DISSOLVE, GIMP_LAYER_MODE_BEHIND_LEGACY, GIMP_LAYER_MODE_MULTIPLY_LEGACY, GIMP_LAYER_MODE_SCREEN_LEGACY, GIMP_LAYER_MODE_OVERLAY_LEGACY, GIMP_LAYER_MODE_DIFFERENCE_LEGACY, GIMP_LAYER_MODE_ADDITION_LEGACY, GIMP_LAYER_MODE_SUBTRACT_LEGACY, GIMP_LAYER_MODE_DARKEN_ONLY_LEGACY, GIMP_LAYER_MODE_LIGHTEN_ONLY_LEGACY, GIMP_LAYER_MODE_HSV_HUE_LEGACY, GIMP_LAYER_MODE_HSV_SATURATION_LEGACY, GIMP_LAYER_MODE_HSL_COLOR_LEGACY, GIMP_LAYER_MODE_HSV_VALUE_LEGACY, GIMP_LAYER_MODE_DIVIDE_LEGACY, GIMP_LAYER_MODE_DODGE_LEGACY, GIMP_LAYER_MODE_BURN_LEGACY, GIMP_LAYER_MODE_HARDLIGHT_LEGACY, GIMP_LAYER_MODE_SOFTLIGHT_LEGACY, GIMP_LAYER_MODE_GRAIN_EXTRACT_LEGACY, GIMP_LAYER_MODE_GRAIN_MERGE_LEGACY, GIMP_LAYER_MODE_COLOR_ERASE_LEGACY, GIMP_LAYER_MODE_OVERLAY, GIMP_LAYER_MODE_LCH_HUE, GIMP_LAYER_MODE_LCH_CHROMA, GIMP_LAYER_MODE_LCH_COLOR, GIMP_LAYER_MODE_LCH_LIGHTNESS, GIMP_LAYER_MODE_NORMAL, GIMP_LAYER_MODE_BEHIND, GIMP_LAYER_MODE_MULTIPLY, GIMP_LAYER_MODE_SCREEN, GIMP_LAYER_MODE_DIFFERENCE, GIMP_LAYER_MODE_ADDITION, GIMP_LAYER_MODE_SUBTRACT, GIMP_LAYER_MODE_DARKEN_ONLY, GIMP_LAYER_MODE_LIGHTEN_ONLY, GIMP_LAYER_MODE_HSV_HUE, GIMP_LAYER_MODE_HSV_SATURATION, GIMP_LAYER_MODE_HSL_COLOR, GIMP_LAYER_MODE_HSV_VALUE, GIMP_LAYER_MODE_DIVIDE, GIMP_LAYER_MODE_DODGE, GIMP_LAYER_MODE_BURN, GIMP_LAYER_MODE_HARDLIGHT, GIMP_LAYER_MODE_SOFTLIGHT, GIMP_LAYER_MODE_GRAIN_EXTRACT, GIMP_LAYER_MODE_GRAIN_MERGE, GIMP_LAYER_MODE_VIVID_LIGHT, GIMP_LAYER_MODE_PIN_LIGHT, GIMP_LAYER_MODE_LINEAR_LIGHT, GIMP_LAYER_MODE_HARD_MIX, GIMP_LAYER_MODE_EXCLUSION, GIMP_LAYER_MODE_LINEAR_BURN, GIMP_LAYER_MODE_LUMA_DARKEN_ONLY, GIMP_LAYER_MODE_LUMA_LIGHTEN_ONLY, GIMP_LAYER_MODE_LUMINANCE, GIMP_LAYER_MODE_COLOR_ERASE, GIMP_LAYER_MODE_ERASE, GIMP_LAYER_MODE_MERGE, GIMP_LAYER_MODE_SPLIT, GIMP_LAYER_MODE_PASS_THROUGH, } GimpLayerMode; /* Simple utility routine to convert between PSD blending modes and ImageMagick compositing operators */ static CompositeOperator GIMPBlendModeToCompositeOperator( size_t blendMode) { switch ( blendMode ) { case GIMP_LAYER_MODE_NORMAL_LEGACY: case GIMP_LAYER_MODE_NORMAL: return(OverCompositeOp); case GIMP_LAYER_MODE_DISSOLVE: return(DissolveCompositeOp); case GIMP_LAYER_MODE_MULTIPLY_LEGACY: case GIMP_LAYER_MODE_MULTIPLY: return(MultiplyCompositeOp); case GIMP_LAYER_MODE_SCREEN_LEGACY: case GIMP_LAYER_MODE_SCREEN: return(ScreenCompositeOp); case GIMP_LAYER_MODE_OVERLAY_LEGACY: case GIMP_LAYER_MODE_OVERLAY: return(OverlayCompositeOp); case GIMP_LAYER_MODE_DIFFERENCE_LEGACY: case GIMP_LAYER_MODE_DIFFERENCE: return(DifferenceCompositeOp); case GIMP_LAYER_MODE_ADDITION_LEGACY: case GIMP_LAYER_MODE_ADDITION: return(ModulusAddCompositeOp); case GIMP_LAYER_MODE_SUBTRACT_LEGACY: case GIMP_LAYER_MODE_SUBTRACT: return(ModulusSubtractCompositeOp); case GIMP_LAYER_MODE_DARKEN_ONLY_LEGACY: case GIMP_LAYER_MODE_DARKEN_ONLY: case GIMP_LAYER_MODE_LUMA_DARKEN_ONLY: return(DarkenCompositeOp); case GIMP_LAYER_MODE_LIGHTEN_ONLY_LEGACY: case GIMP_LAYER_MODE_LIGHTEN_ONLY: return(LightenCompositeOp); case GIMP_LAYER_MODE_LUMA_LIGHTEN_ONLY: return(LightenCompositeOp); case GIMP_LAYER_MODE_HSV_HUE_LEGACY: case GIMP_LAYER_MODE_HSV_HUE: return(HueCompositeOp); case GIMP_LAYER_MODE_HSV_SATURATION_LEGACY: case GIMP_LAYER_MODE_HSV_SATURATION: return(SaturateCompositeOp); case GIMP_LAYER_MODE_HSL_COLOR_LEGACY: case GIMP_LAYER_MODE_HSL_COLOR: return(ColorizeCompositeOp); case GIMP_LAYER_MODE_DODGE_LEGACY: case GIMP_LAYER_MODE_DODGE: return(ColorDodgeCompositeOp); case GIMP_LAYER_MODE_BURN_LEGACY: case GIMP_LAYER_MODE_BURN: return(ColorBurnCompositeOp); case GIMP_LAYER_MODE_HARDLIGHT_LEGACY: case GIMP_LAYER_MODE_HARDLIGHT: return(HardLightCompositeOp); case GIMP_LAYER_MODE_DIVIDE_LEGACY: case GIMP_LAYER_MODE_DIVIDE: return(DivideDstCompositeOp); /* these are the ones we don't support...yet */ default: return(OverCompositeOp); } } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + R e a d B l o b S t r i n g W i t h L o n g S i z e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ReadBlobStringWithLongSize reads characters from a blob or file % starting with a ssize_t length byte and then characters to that length % % The format of the ReadBlobStringWithLongSize method is: % % char *ReadBlobStringWithLongSize(Image *image,char *string, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image: the image. % % o string: the address of a character buffer. % % o exception: return any errors or warnings in this structure. % */ static char *ReadBlobStringWithLongSize(Image *image,char *string,size_t max, ExceptionInfo *exception) { int c; MagickOffsetType offset; ssize_t i; size_t length; assert(image != (Image *) NULL); assert(image->signature == MagickCoreSignature); assert(max != 0); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); length=ReadBlobMSBLong(image); for (i=0; i < (ssize_t) MagickMin(length,max-1); i++) { c=ReadBlobByte(image); if (c == EOF) return((char *) NULL); string[i]=(char) c; } string[i]='\0'; offset=SeekBlob(image,(MagickOffsetType) (length-i),SEEK_CUR); if (offset < 0) (void) ThrowMagickException(exception,GetMagickModule(), CorruptImageError,"ImproperImageHeader","`%s'",image->filename); return(string); } static MagickOffsetType GetXCFOffset(Image *image, XCFDocInfo *inDocInfo) { if (inDocInfo->version >= 4) return (MagickOffsetType) ReadBlobMSBLongLong(image); else return (MagickOffsetType) ReadBlobMSBLong(image); } static MagickBooleanType load_tile(Image *image,Image *tile_image, XCFDocInfo *inDocInfo,XCFLayerInfo *inLayerInfo,size_t data_length, ExceptionInfo *exception) { ssize_t y; ssize_t x; Quantum *q; size_t extent; ssize_t count; unsigned char *graydata; XCFPixelInfo *xcfdata, *xcfodata; extent=0; if (inDocInfo->image_type == GIMP_GRAY) extent=tile_image->columns*tile_image->rows*sizeof(*graydata); else if (inDocInfo->image_type == GIMP_RGB) extent=tile_image->columns*tile_image->rows*sizeof(*xcfdata); if (extent > data_length) ThrowBinaryException(CorruptImageError,"NotEnoughPixelData", image->filename); xcfdata=(XCFPixelInfo *) AcquireQuantumMemory(MagickMax(data_length, tile_image->columns*tile_image->rows),sizeof(*xcfdata)); if (xcfdata == (XCFPixelInfo *) NULL) ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", image->filename); xcfodata=xcfdata; graydata=(unsigned char *) xcfdata; /* used by gray and indexed */ count=ReadBlob(image,data_length,(unsigned char *) xcfdata); if (count != (ssize_t) data_length) { xcfodata=(XCFPixelInfo *) RelinquishMagickMemory(xcfodata); ThrowBinaryException(CorruptImageError,"NotEnoughPixelData", image->filename); } for (y=0; y < (ssize_t) tile_image->rows; y++) { q=GetAuthenticPixels(tile_image,0,y,tile_image->columns,1,exception); if (q == (Quantum *) NULL) break; if (inDocInfo->image_type == GIMP_GRAY) { for (x=0; x < (ssize_t) tile_image->columns; x++) { SetPixelGray(tile_image,ScaleCharToQuantum(*graydata),q); SetPixelAlpha(tile_image,ScaleCharToQuantum((unsigned char) inLayerInfo->alpha),q); graydata++; q+=GetPixelChannels(tile_image); } } else if (inDocInfo->image_type == GIMP_RGB) { for (x=0; x < (ssize_t) tile_image->columns; x++) { SetPixelRed(tile_image,ScaleCharToQuantum(xcfdata->red),q); SetPixelGreen(tile_image,ScaleCharToQuantum(xcfdata->green),q); SetPixelBlue(tile_image,ScaleCharToQuantum(xcfdata->blue),q); SetPixelAlpha(tile_image,xcfdata->alpha == 255U ? TransparentAlpha : ScaleCharToQuantum((unsigned char) inLayerInfo->alpha),q); xcfdata++; q+=GetPixelChannels(tile_image); } } if (SyncAuthenticPixels(tile_image,exception) == MagickFalse) break; } xcfodata=(XCFPixelInfo *) RelinquishMagickMemory(xcfodata); return MagickTrue; } static MagickBooleanType load_tile_rle(Image *image,Image *tile_image, XCFDocInfo *inDocInfo,XCFLayerInfo *inLayerInfo,size_t data_length, ExceptionInfo *exception) { MagickOffsetType size; Quantum alpha, data; Quantum *q; size_t length; ssize_t bytes_per_pixel, count, i, j; unsigned char pixel, *xcfdata, *xcfodata, *xcfdatalimit; bytes_per_pixel=(ssize_t) inDocInfo->bytes_per_pixel; xcfdata=(unsigned char *) AcquireQuantumMemory(data_length,sizeof(*xcfdata)); if (xcfdata == (unsigned char *) NULL) ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", image->filename); xcfodata=xcfdata; count=ReadBlob(image, (size_t) data_length, xcfdata); xcfdatalimit = xcfodata+count-1; alpha=ScaleCharToQuantum((unsigned char) inLayerInfo->alpha); for (i=0; i < bytes_per_pixel; i++) { q=GetAuthenticPixels(tile_image,0,0,tile_image->columns,tile_image->rows, exception); if (q == (Quantum *) NULL) continue; size=(MagickOffsetType) tile_image->rows*tile_image->columns; while (size > 0) { if (xcfdata > xcfdatalimit) goto bogus_rle; pixel=(*xcfdata++); length=(size_t) pixel; if (length >= 128) { length=255-(length-1); if (length == 128) { if (xcfdata >= xcfdatalimit) goto bogus_rle; length=(size_t) ((*xcfdata << 8) + xcfdata[1]); xcfdata+=2; } size-=length; if (size < 0) goto bogus_rle; if (&xcfdata[length-1] > xcfdatalimit) goto bogus_rle; while (length-- > 0) { data=ScaleCharToQuantum(*xcfdata++); switch (i) { case 0: { if (inDocInfo->image_type == GIMP_GRAY) SetPixelGray(tile_image,data,q); else { SetPixelRed(tile_image,data,q); SetPixelGreen(tile_image,data,q); SetPixelBlue(tile_image,data,q); } SetPixelAlpha(tile_image,alpha,q); break; } case 1: { if (inDocInfo->image_type == GIMP_GRAY) SetPixelAlpha(tile_image,data,q); else SetPixelGreen(tile_image,data,q); break; } case 2: { SetPixelBlue(tile_image,data,q); break; } case 3: { SetPixelAlpha(tile_image,data,q); break; } } q+=GetPixelChannels(tile_image); } } else { length+=1; if (length == 128) { if (xcfdata >= xcfdatalimit) goto bogus_rle; length=(size_t) ((*xcfdata << 8) + xcfdata[1]); xcfdata+=2; } size-=length; if (size < 0) goto bogus_rle; if (xcfdata > xcfdatalimit) goto bogus_rle; pixel=(*xcfdata++); for (j=0; j < (ssize_t) length; j++) { data=ScaleCharToQuantum(pixel); switch (i) { case 0: { if (inDocInfo->image_type == GIMP_GRAY) SetPixelGray(tile_image,data,q); else { SetPixelRed(tile_image,data,q); SetPixelGreen(tile_image,data,q); SetPixelBlue(tile_image,data,q); } SetPixelAlpha(tile_image,alpha,q); break; } case 1: { if (inDocInfo->image_type == GIMP_GRAY) SetPixelAlpha(tile_image,data,q); else SetPixelGreen(tile_image,data,q); break; } case 2: { SetPixelBlue(tile_image,data,q); break; } case 3: { SetPixelAlpha(tile_image,data,q); break; } } q+=GetPixelChannels(tile_image); } } } if (SyncAuthenticPixels(tile_image,exception) == MagickFalse) break; } xcfodata=(unsigned char *) RelinquishMagickMemory(xcfodata); return(MagickTrue); bogus_rle: if (xcfodata != (unsigned char *) NULL) xcfodata=(unsigned char *) RelinquishMagickMemory(xcfodata); return(MagickFalse); } static MagickBooleanType load_level(Image *image,XCFDocInfo *inDocInfo, XCFLayerInfo *inLayerInfo,ExceptionInfo *exception) { int destLeft = 0, destTop = 0; Image* tile_image; MagickBooleanType status; MagickOffsetType saved_pos, offset, offset2; ssize_t i; size_t width, height, ntiles, ntile_rows, ntile_cols, tile_image_width, tile_image_height; /* start reading the data */ width=ReadBlobMSBLong(image); height=ReadBlobMSBLong(image); /* Read in the first tile offset. If it is '0', then this tile level is empty and we can simply return. */ offset=GetXCFOffset(image,inDocInfo); if (EOFBlob(image) != MagickFalse) ThrowBinaryException(CorruptImageError,"UnexpectedEndOfFile", image->filename); if (offset == 0) { (void) SetImageBackgroundColor(image,exception); return(MagickTrue); } /* Initialise the reference for the in-memory tile-compression */ ntile_rows=(height+TILE_HEIGHT-1)/TILE_HEIGHT; ntile_cols=(width+TILE_WIDTH-1)/TILE_WIDTH; ntiles=ntile_rows*ntile_cols; for (i = 0; i < (ssize_t) ntiles; i++) { status=MagickFalse; if (offset == 0) ThrowBinaryException(CorruptImageError,"NotEnoughTiles",image->filename); /* Save the current position as it is where the next tile offset is stored. */ saved_pos=TellBlob(image); /* read in the offset of the next tile so we can calculate the amount of data needed for this tile*/ offset2=GetXCFOffset(image,inDocInfo); if ((MagickSizeType) offset2 >= inDocInfo->file_size) ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile", image->filename); /* if the offset is 0 then we need to read in the maximum possible allowing for negative compression */ if (offset2 == 0) offset2=(MagickOffsetType) (offset + TILE_WIDTH * TILE_WIDTH * 4* 1.5); /* seek to the tile offset */ if ((offset > offset2) || (SeekBlob(image, offset, SEEK_SET) != offset)) ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile", image->filename); /* Allocate the image for the tile. NOTE: the last tile in a row or column may not be a full tile! */ tile_image_width=(size_t) (destLeft == (int) ntile_cols-1 ? (int) width % TILE_WIDTH : TILE_WIDTH); if (tile_image_width == 0) tile_image_width=TILE_WIDTH; tile_image_height = (size_t) (destTop == (int) ntile_rows-1 ? (int) height % TILE_HEIGHT : TILE_HEIGHT); if (tile_image_height == 0) tile_image_height=TILE_HEIGHT; tile_image=CloneImage(inLayerInfo->image,tile_image_width, tile_image_height,MagickTrue,exception); if (tile_image == (Image *) NULL) ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", image->filename); (void) SetImageBackgroundColor(tile_image,exception); /* read in the tile */ switch (inDocInfo->compression) { case COMPRESS_NONE: status=load_tile(image,tile_image,inDocInfo,inLayerInfo,(size_t) (offset2-offset),exception); break; case COMPRESS_RLE: status=load_tile_rle(image,tile_image,inDocInfo,inLayerInfo,(size_t) (offset2-offset),exception); break; case COMPRESS_ZLIB: tile_image=DestroyImage(tile_image); ThrowBinaryException(CoderError,"ZipCompressNotSupported", image->filename) case COMPRESS_FRACTAL: tile_image=DestroyImage(tile_image); ThrowBinaryException(CoderError,"FractalCompressNotSupported", image->filename) } /* composite the tile onto the layer's image, and then destroy it */ if (status != MagickFalse) (void) CompositeImage(inLayerInfo->image,tile_image,CopyCompositeOp, MagickTrue,destLeft * TILE_WIDTH,destTop*TILE_HEIGHT,exception); tile_image=DestroyImage(tile_image); if (status == MagickFalse) return(MagickFalse); /* adjust tile position */ destLeft++; if (destLeft >= (int) ntile_cols) { destLeft = 0; destTop++; } /* restore the saved position so we'll be ready to * read the next offset. */ offset=SeekBlob(image, saved_pos, SEEK_SET); /* read in the offset of the next tile */ offset=GetXCFOffset(image,inDocInfo); } if (offset != 0) ThrowBinaryException(CorruptImageError,"CorruptImage",image->filename) return(MagickTrue); } static MagickBooleanType load_hierarchy(Image *image,XCFDocInfo *inDocInfo, XCFLayerInfo *inLayer, ExceptionInfo *exception) { MagickOffsetType saved_pos, offset, junk; (void) ReadBlobMSBLong(image); /* width */ (void) ReadBlobMSBLong(image); /* height */ inDocInfo->bytes_per_pixel=ReadBlobMSBLong(image); /* load in the levels...we make sure that the number of levels * calculated when the TileManager was created is the same * as the number of levels found in the file. */ offset=GetXCFOffset(image,inDocInfo); /* top level */ if ((MagickSizeType) offset >= GetBlobSize(image)) ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile", image->filename); /* discard offsets for layers below first, if any. */ do { junk=(MagickOffsetType) ReadBlobMSBLong(image); } while (junk != 0); /* save the current position as it is where the * next level offset is stored. */ saved_pos=TellBlob(image); /* seek to the level offset */ if (SeekBlob(image, offset, SEEK_SET) != offset) ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile", image->filename); /* read in the level */ if (load_level (image, inDocInfo, inLayer, exception) == 0) return(MagickFalse); /* restore the saved position so we'll be ready to * read the next offset. */ offset=SeekBlob(image, saved_pos, SEEK_SET); return(MagickTrue); } static void InitXCFImage(XCFLayerInfo *outLayer,ExceptionInfo *exception) { outLayer->image->page.x=outLayer->offset_x; outLayer->image->page.y=outLayer->offset_y; outLayer->image->page.width=outLayer->width; outLayer->image->page.height=outLayer->height; (void) SetImageProperty(outLayer->image,"label",(char *) outLayer->name, exception); } static MagickBooleanType ReadOneLayer(const ImageInfo *image_info,Image* image, XCFDocInfo* inDocInfo,XCFLayerInfo *outLayer,const ssize_t layer, ExceptionInfo *exception) { MagickBooleanType status; MagickOffsetType offset; unsigned int foundPropEnd = 0; MagickOffsetType hierarchy_offset, layer_mask_offset; /* clear the block! */ (void) memset( outLayer, 0, sizeof( XCFLayerInfo ) ); /* read in the layer width, height, type and name */ outLayer->width = ReadBlobMSBLong(image); outLayer->height = ReadBlobMSBLong(image); outLayer->type = ReadBlobMSBLong(image); (void) ReadBlobStringWithLongSize(image, outLayer->name, sizeof(outLayer->name),exception); if (EOFBlob(image) != MagickFalse) ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile", image->filename); if ((outLayer->width == 0) || (outLayer->height == 0)) ThrowBinaryException(CorruptImageError,"ImproperImageHeader", image->filename); /* read the layer properties! */ foundPropEnd = 0; while ( (foundPropEnd == MagickFalse) && (EOFBlob(image) == MagickFalse) ) { PropType prop_type = (PropType) ReadBlobMSBLong(image); size_t prop_size = ReadBlobMSBLong(image); switch (prop_type) { case PROP_END: foundPropEnd = 1; break; case PROP_ACTIVE_LAYER: outLayer->active = 1; break; case PROP_FLOATING_SELECTION: outLayer->floating_offset = ReadBlobMSBLong(image); break; case PROP_OPACITY: outLayer->alpha = ReadBlobMSBLong(image); break; case PROP_VISIBLE: outLayer->visible = ReadBlobMSBLong(image); break; case PROP_LINKED: outLayer->linked = ReadBlobMSBLong(image); break; case PROP_PRESERVE_TRANSPARENCY: outLayer->preserve_trans = ReadBlobMSBLong(image); break; case PROP_APPLY_MASK: outLayer->apply_mask = ReadBlobMSBLong(image); break; case PROP_EDIT_MASK: outLayer->edit_mask = ReadBlobMSBLong(image); break; case PROP_SHOW_MASK: outLayer->show_mask = ReadBlobMSBLong(image); break; case PROP_OFFSETS: outLayer->offset_x = ReadBlobMSBSignedLong(image); outLayer->offset_y = ReadBlobMSBSignedLong(image); break; case PROP_MODE: outLayer->mode = ReadBlobMSBLong(image); break; case PROP_TATTOO: outLayer->preserve_trans = ReadBlobMSBLong(image); break; case PROP_PARASITES: { if (DiscardBlobBytes(image,prop_size) == MagickFalse) ThrowFileException(exception,CorruptImageError, "UnexpectedEndOfFile",image->filename); /* ssize_t base = info->cp; GimpParasite *p; while (info->cp - base < prop_size) { p = xcf_load_parasite(info); gimp_drawable_parasite_attach(GIMP_DRAWABLE(layer), p); gimp_parasite_free(p); } if (info->cp - base != prop_size) g_message ("Error detected while loading a layer's parasites"); */ } break; default: /* g_message ("unexpected/unknown layer property: %d (skipping)", prop_type); */ { int buf[16]; ssize_t amount; /* read over it... */ while ((prop_size > 0) && (EOFBlob(image) == MagickFalse)) { amount = (ssize_t) MagickMin(16, prop_size); amount = ReadBlob(image, (size_t) amount, (unsigned char *) &buf); if (!amount) ThrowBinaryException(CorruptImageError,"CorruptImage", image->filename); prop_size -= (size_t) MagickMin(16, (size_t) amount); } } break; } } if (EOFBlob(image) != MagickFalse) ThrowBinaryException(CorruptImageError,"UnexpectedEndOfFile", image->filename); if (foundPropEnd == MagickFalse) return(MagickFalse); /* allocate the image for this layer */ if (image_info->number_scenes != 0) { ssize_t scene; scene=inDocInfo->number_layers-layer-1; if (scene > (ssize_t) (image_info->scene+image_info->number_scenes-1)) { outLayer->image=CloneImage(image,0,0,MagickTrue,exception); if (outLayer->image == (Image *) NULL) return(MagickFalse); InitXCFImage(outLayer,exception); return(MagickTrue); } } outLayer->image=CloneImage(image,outLayer->width, outLayer->height,MagickTrue, exception); if (outLayer->image == (Image *) NULL) return(MagickFalse); outLayer->width=outLayer->image->columns; status=SetImageExtent(outLayer->image,outLayer->image->columns, outLayer->image->rows,exception); if (status != MagickFalse) status=ResetImagePixels(outLayer->image,exception); if (status == MagickFalse) { outLayer->image=DestroyImageList(outLayer->image); return(MagickFalse); } /* clear the image based on the layer opacity */ outLayer->image->background_color.alpha= ScaleCharToQuantum((unsigned char) outLayer->alpha); if (outLayer->alpha != 255U) { outLayer->image->background_color.alpha_trait=BlendPixelTrait; outLayer->image->alpha_trait=BlendPixelTrait; (void) SetImageBackgroundColor(outLayer->image,exception); } InitXCFImage(outLayer,exception); /* set the compositing mode */ outLayer->image->compose = GIMPBlendModeToCompositeOperator( outLayer->mode ); if ( outLayer->visible == MagickFalse ) { /* BOGUS: should really be separate member var! */ outLayer->image->compose = NoCompositeOp; } /* read the hierarchy and layer mask offsets */ hierarchy_offset = GetXCFOffset(image,inDocInfo); layer_mask_offset = GetXCFOffset(image,inDocInfo); /* read in the hierarchy */ offset=SeekBlob(image, hierarchy_offset, SEEK_SET); if (offset != hierarchy_offset) ThrowBinaryException(CorruptImageError,"InvalidImageHeader", image->filename); if (load_hierarchy (image, inDocInfo, outLayer, exception) == 0) return(MagickFalse); /* read in the layer mask */ if (layer_mask_offset != 0) { offset=SeekBlob(image, (MagickOffsetType) layer_mask_offset, SEEK_SET); #if 0 /* BOGUS: support layer masks! */ layer_mask = xcf_load_layer_mask (info, gimage); if (layer_mask == 0) goto error; /* set the offsets of the layer_mask */ GIMP_DRAWABLE (layer_mask)->offset_x = GIMP_DRAWABLE (layer)->offset_x; GIMP_DRAWABLE (layer_mask)->offset_y = GIMP_DRAWABLE (layer)->offset_y; gimp_layer_add_mask (layer, layer_mask, MagickFalse); layer->mask->apply_mask = apply_mask; layer->mask->edit_mask = edit_mask; layer->mask->show_mask = show_mask; #endif } /* attach the floating selection... */ #if 0 /* BOGUS: we may need to read this, even if we don't support it! */ if (add_floating_sel) { GimpLayer *floating_sel; floating_sel = info->floating_sel; floating_sel_attach (floating_sel, GIMP_DRAWABLE (layer)); } #endif return MagickTrue; } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e a d X C F I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ReadXCFImage() reads a GIMP (GNU Image Manipulation Program) image % file and returns it. It allocates the memory necessary for the new Image % structure and returns a pointer to the new image. % % The format of the ReadXCFImage method is: % % image=ReadXCFImage(image_info) % % A description of each parameter follows: % % o image_info: the image info. % % o exception: return any errors or warnings in this structure. % */ static Image *ReadXCFImage(const ImageInfo *image_info,ExceptionInfo *exception) { char magick[14]; Image *image; int foundPropEnd = 0; MagickBooleanType status; MagickOffsetType offset; ssize_t i; size_t image_type, precision, length; ssize_t count; XCFDocInfo doc_info; /* Open image file. */ assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickCoreSignature); if (image_info->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s", image_info->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickCoreSignature); image=AcquireImage(image_info,exception); status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); if (status == MagickFalse) { image=DestroyImageList(image); return((Image *) NULL); } count=ReadBlob(image,sizeof(magick),(unsigned char *) magick); if ((count != sizeof(magick)) || (LocaleNCompare((char *) magick,"gimp xcf",8) != 0)) ThrowReaderException(CorruptImageError,"ImproperImageHeader"); (void) memset(&doc_info,0,sizeof(XCFDocInfo)); doc_info.version=StringToUnsignedLong(magick+10); doc_info.width=ReadBlobMSBLong(image); doc_info.height=ReadBlobMSBLong(image); if ((doc_info.width > 262144) || (doc_info.height > 262144)) ThrowReaderException(CorruptImageError,"ImproperImageHeader"); doc_info.image_type=ReadBlobMSBLong(image); precision=150; if (doc_info.version >= 4) { precision=ReadBlobMSBLong(image); if (precision == 0) precision=150; if (precision != 150) ThrowReaderException(CoderError,"DataStorageTypeIsNotSupported"); } /* Initialize image attributes. */ image->columns=doc_info.width; image->rows=doc_info.height; image_type=doc_info.image_type; doc_info.file_size=GetBlobSize(image); image->compression=NoCompression; image->depth=8; status=SetImageExtent(image,image->columns,image->rows,exception); if (status == MagickFalse) return(DestroyImageList(image)); if (status != MagickFalse) status=ResetImagePixels(image,exception); if (image_type == GIMP_INDEXED) ThrowReaderException(CoderError,"ColormapTypeNotSupported"); if (image_type == GIMP_RGB) SetImageColorspace(image,sRGBColorspace,exception); else if (image_type == GIMP_GRAY) SetImageColorspace(image,GRAYColorspace,exception); else ThrowReaderException(CorruptImageError,"ImproperImageHeader"); (void) SetImageBackgroundColor(image,exception); (void) SetImageAlpha(image,OpaqueAlpha,exception); /* Read properties. */ while ((foundPropEnd == MagickFalse) && (EOFBlob(image) == MagickFalse)) { PropType prop_type = (PropType) ReadBlobMSBLong(image); size_t prop_size = ReadBlobMSBLong(image); switch (prop_type) { case PROP_END: foundPropEnd=1; break; case PROP_COLORMAP: { /* Cannot rely on prop_size here--the value is set incorrectly by some Gimp versions. */ size_t num_colors = ReadBlobMSBLong(image); if (DiscardBlobBytes(image,3*num_colors) == MagickFalse) ThrowFileException(exception,CorruptImageError, "UnexpectedEndOfFile",image->filename); /* if (info->file_version == 0) { gint i; g_message (_("XCF warning: version 0 of XCF file format\n" "did not save indexed colormaps correctly.\n" "Substituting grayscale map.")); info->cp += xcf_read_int32 (info->fp, (guint32*) &gimage->num_cols, 1); gimage->cmap = g_new (guchar, gimage->num_cols*3); xcf_seek_pos (info, info->cp + gimage->num_cols); for (i = 0; inum_cols; i++) { gimage->cmap[i*3+0] = i; gimage->cmap[i*3+1] = i; gimage->cmap[i*3+2] = i; } } else { info->cp += xcf_read_int32 (info->fp, (guint32*) &gimage->num_cols, 1); gimage->cmap = g_new (guchar, gimage->num_cols*3); info->cp += xcf_read_int8 (info->fp, (guint8*) gimage->cmap, gimage->num_cols*3); } */ break; } case PROP_COMPRESSION: { doc_info.compression = ReadBlobByte(image); if ((doc_info.compression != COMPRESS_NONE) && (doc_info.compression != COMPRESS_RLE) && (doc_info.compression != COMPRESS_ZLIB) && (doc_info.compression != COMPRESS_FRACTAL)) ThrowReaderException(CorruptImageError,"UnrecognizedImageCompression"); } break; case PROP_GUIDES: { /* just skip it - we don't care about guides */ if (DiscardBlobBytes(image,prop_size) == MagickFalse) ThrowFileException(exception,CorruptImageError, "UnexpectedEndOfFile",image->filename); } break; case PROP_RESOLUTION: { /* float xres = (float) */ (void) ReadBlobMSBLong(image); /* float yres = (float) */ (void) ReadBlobMSBLong(image); /* if (xres < GIMP_MIN_RESOLUTION || xres > GIMP_MAX_RESOLUTION || yres < GIMP_MIN_RESOLUTION || yres > GIMP_MAX_RESOLUTION) { g_message ("Warning, resolution out of range in XCF file"); xres = gimage->gimp->config->default_xresolution; yres = gimage->gimp->config->default_yresolution; } */ /* BOGUS: we don't write these yet because we aren't reading them properly yet :( image->resolution.x = xres; image->resolution.y = yres; */ } break; case PROP_TATTOO: { /* we need to read it, even if we ignore it */ /*size_t tattoo_state = */ (void) ReadBlobMSBLong(image); } break; case PROP_PARASITES: { /* BOGUS: we may need these for IPTC stuff */ if (DiscardBlobBytes(image,prop_size) == MagickFalse) ThrowFileException(exception,CorruptImageError, "UnexpectedEndOfFile",image->filename); /* gssize_t base = info->cp; GimpParasite *p; while (info->cp - base < prop_size) { p = xcf_load_parasite (info); gimp_image_parasite_attach (gimage, p); gimp_parasite_free (p); } if (info->cp - base != prop_size) g_message ("Error detected while loading an image's parasites"); */ } break; case PROP_UNIT: { /* BOGUS: ignore for now... */ /*size_t unit = */ (void) ReadBlobMSBLong(image); } break; case PROP_PATHS: { /* BOGUS: just skip it for now */ if (DiscardBlobBytes(image,prop_size) == MagickFalse) ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile", image->filename); /* PathList *paths = xcf_load_bzpaths (gimage, info); gimp_image_set_paths (gimage, paths); */ } break; case PROP_USER_UNIT: { char unit_string[1000]; /*BOGUS: ignored for now */ /*float factor = (float) */ (void) ReadBlobMSBLong(image); /* size_t digits = */ (void) ReadBlobMSBLong(image); for (i=0; i<5; i++) (void) ReadBlobStringWithLongSize(image, unit_string, sizeof(unit_string),exception); } break; default: { int buf[16]; ssize_t amount; /* read over it... */ while ((prop_size > 0) && (EOFBlob(image) == MagickFalse)) { amount=(ssize_t) MagickMin(16, prop_size); amount=(ssize_t) ReadBlob(image,(size_t) amount,(unsigned char *) &buf); if (!amount) ThrowReaderException(CorruptImageError,"CorruptImage"); prop_size -= (size_t) MagickMin(16,(size_t) amount); } } break; } } if (foundPropEnd == MagickFalse) ThrowReaderException(CorruptImageError,"ImproperImageHeader"); if ((image_info->ping != MagickFalse) && (image_info->number_scenes != 0)) { ; /* do nothing, were just pinging! */ } else { int current_layer = 0, foundAllLayers = MagickFalse, number_layers = 0; MagickOffsetType oldPos=TellBlob(image); XCFLayerInfo *layer_info; /* The read pointer. */ do { offset=GetXCFOffset(image,&doc_info); if (offset == 0) foundAllLayers=MagickTrue; else number_layers++; if (EOFBlob(image) != MagickFalse) { ThrowFileException(exception,CorruptImageError, "UnexpectedEndOfFile",image->filename); break; } } while (foundAllLayers == MagickFalse); if (AcquireMagickResource(ListLengthResource,number_layers) == MagickFalse) ThrowReaderException(ResourceLimitError,"ListLengthExceedsLimit"); doc_info.number_layers=number_layers; offset=SeekBlob(image,oldPos,SEEK_SET); /* restore the position! */ if (offset < 0) ThrowReaderException(CorruptImageError,"ImproperImageHeader"); /* allocate our array of layer info blocks */ length=(size_t) number_layers; layer_info=(XCFLayerInfo *) AcquireQuantumMemory(length, sizeof(*layer_info)); if (layer_info == (XCFLayerInfo *) NULL) ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed"); (void) memset(layer_info,0,number_layers*sizeof(XCFLayerInfo)); for ( ; ; ) { MagickBooleanType layer_ok; MagickOffsetType saved_pos; /* read in the offset of the next layer */ offset=GetXCFOffset(image,&doc_info); /* if the offset is 0 then we are at the end * of the layer list. */ if (offset == 0) break; /* save the current position as it is where the * next layer offset is stored. */ saved_pos=TellBlob(image); /* seek to the layer offset */ layer_ok=MagickFalse; if (SeekBlob(image,offset,SEEK_SET) == offset) { /* read in the layer */ layer_ok=ReadOneLayer(image_info,image,&doc_info, &layer_info[current_layer],current_layer,exception); } if (layer_ok == MagickFalse) { ssize_t j; for (j=0; j <= current_layer; j++) if (layer_info[j].image != (Image *) NULL) layer_info[j].image=DestroyImage(layer_info[j].image); layer_info=(XCFLayerInfo *) RelinquishMagickMemory(layer_info); ThrowReaderException(CorruptImageError,"NotEnoughPixelData"); } /* restore the saved position so we'll be ready to * read the next offset. */ offset=SeekBlob(image, saved_pos, SEEK_SET); current_layer++; } #if 0 { /* NOTE: XCF layers are REVERSED from composite order! */ signed int j; for (j=number_layers-1; j>=0; j--) { /* BOGUS: need to consider layer blending modes!! */ if ( layer_info[j].visible ) { /* only visible ones, please! */ CompositeImage(image, OverCompositeOp, layer_info[j].image, layer_info[j].offset_x, layer_info[j].offset_y ); layer_info[j].image =DestroyImage( layer_info[j].image ); /* If we do this, we'll get REAL gray images! */ if ( image_type == GIMP_GRAY ) { QuantizeInfo qi; GetQuantizeInfo(&qi); qi.colorspace = GRAYColorspace; QuantizeImage( &qi, layer_info[j].image ); } } } } #else { /* NOTE: XCF layers are REVERSED from composite order! */ ssize_t j; /* now reverse the order of the layers as they are put into subimages */ for (j=(ssize_t) number_layers-1; j >= 0; j--) AppendImageToList(&image,layer_info[j].image); } #endif layer_info=(XCFLayerInfo *) RelinquishMagickMemory(layer_info); #if 0 /* BOGUS: do we need the channels?? */ while (MagickTrue) { /* read in the offset of the next channel */ info->cp += xcf_read_int32 (info->fp, &offset, 1); /* if the offset is 0 then we are at the end * of the channel list. */ if (offset == 0) break; /* save the current position as it is where the * next channel offset is stored. */ saved_pos = info->cp; /* seek to the channel offset */ xcf_seek_pos (info, offset); /* read in the layer */ channel = xcf_load_channel (info, gimage); if (channel == 0) goto error; num_successful_elements++; /* add the channel to the image if its not the selection */ if (channel != gimage->selection_mask) gimp_image_add_channel (gimage, channel, -1); /* restore the saved position so we'll be ready to * read the next offset. */ xcf_seek_pos (info, saved_pos); } #endif } (void) CloseBlob(image); if (GetNextImageInList(image) != (Image *) NULL) DestroyImage(RemoveFirstImageFromList(&image)); if (image_type == GIMP_GRAY) image->type=GrayscaleType; return(GetFirstImageInList(image)); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e g i s t e r X C F I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % RegisterXCFImage() adds attributes for the XCF image format to % the list of supported formats. The attributes include the image format % tag, a method to read and/or write the format, whether the format % supports the saving of more than one frame to the same file or blob, % whether the format supports native in-memory I/O, and a brief % description of the format. % % The format of the RegisterXCFImage method is: % % size_t RegisterXCFImage(void) % */ ModuleExport size_t RegisterXCFImage(void) { MagickInfo *entry; entry=AcquireMagickInfo("XCF","XCF","GIMP image"); entry->decoder=(DecodeImageHandler *) ReadXCFImage; entry->magick=(IsImageFormatHandler *) IsXCF; entry->flags|=CoderDecoderSeekableStreamFlag; (void) RegisterMagickInfo(entry); return(MagickImageCoderSignature); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % U n r e g i s t e r X C F I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % UnregisterXCFImage() removes format registrations made by the % XCF module from the list of supported formats. % % The format of the UnregisterXCFImage method is: % % UnregisterXCFImage(void) % */ ModuleExport void UnregisterXCFImage(void) { (void) UnregisterMagickInfo("XCF"); }