| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % 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-2009 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 % |
| % % |
| % http://www.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 "magick/studio.h" |
| #include "magick/blob.h" |
| #include "magick/blob-private.h" |
| #include "magick/cache.h" |
| #include "magick/color.h" |
| #include "magick/composite.h" |
| #include "magick/exception.h" |
| #include "magick/exception-private.h" |
| #include "magick/image.h" |
| #include "magick/image-private.h" |
| #include "magick/list.h" |
| #include "magick/magick.h" |
| #include "magick/memory_.h" |
| #include "magick/quantize.h" |
| #include "magick/quantum-private.h" |
| #include "magick/static.h" |
| #include "magick/string_.h" |
| #include "magick/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 |
| { |
| unsigned long |
| width, |
| height, |
| image_type, |
| bytes_per_pixel; |
| |
| int |
| compression; |
| |
| size_t |
| file_size; |
| |
| ExceptionInfo |
| *exception; |
| } XCFDocInfo; |
| |
| typedef struct |
| { |
| char |
| name[1024]; |
| |
| unsigned int |
| active; |
| |
| unsigned long |
| width, |
| height, |
| type, |
| opacity, |
| visible, |
| linked, |
| preserve_trans, |
| apply_mask, |
| show_mask, |
| edit_mask, |
| floating_offset; |
| |
| ssize_t |
| offset_x, |
| offset_y; |
| |
| unsigned long |
| mode, |
| tattoo; |
| |
| Image |
| *image; |
| } XCFLayerInfo; |
| |
| #define TILE_WIDTH 64 |
| #define TILE_HEIGHT 64 |
| |
| typedef struct |
| { |
| unsigned char |
| red, |
| green, |
| blue, |
| opacity; |
| } XCFPixelPacket; |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % 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_NORMAL_MODE, |
| GIMP_DISSOLVE_MODE, |
| GIMP_BEHIND_MODE, |
| GIMP_MULTIPLY_MODE, |
| GIMP_SCREEN_MODE, |
| GIMP_OVERLAY_MODE, |
| GIMP_DIFFERENCE_MODE, |
| GIMP_ADDITION_MODE, |
| GIMP_SUBTRACT_MODE, |
| GIMP_DARKEN_ONLY_MODE, |
| GIMP_LIGHTEN_ONLY_MODE, |
| GIMP_HUE_MODE, |
| GIMP_SATURATION_MODE, |
| GIMP_COLOR_MODE, |
| GIMP_VALUE_MODE, |
| GIMP_DIVIDE_MODE, |
| GIMP_DODGE_MODE, |
| GIMP_BURN_MODE, |
| GIMP_HARDLIGHT_MODE |
| } GimpLayerModeEffects; |
| |
| /* |
| Simple utility routine to convert between PSD blending modes and |
| ImageMagick compositing operators |
| */ |
| static CompositeOperator GIMPBlendModeToCompositeOperator( |
| unsigned long blendMode) |
| { |
| switch ( blendMode ) |
| { |
| case GIMP_NORMAL_MODE: return( OverCompositeOp ); |
| case GIMP_DISSOLVE_MODE: return( DissolveCompositeOp ); |
| case GIMP_MULTIPLY_MODE: return( MultiplyCompositeOp ); |
| case GIMP_SCREEN_MODE: return( ScreenCompositeOp ); |
| case GIMP_OVERLAY_MODE: return( OverlayCompositeOp ); |
| case GIMP_DIFFERENCE_MODE: return( DifferenceCompositeOp ); |
| case GIMP_ADDITION_MODE: return( AddCompositeOp ); |
| case GIMP_SUBTRACT_MODE: return( SubtractCompositeOp ); |
| case GIMP_DARKEN_ONLY_MODE: return( DarkenCompositeOp ); |
| case GIMP_LIGHTEN_ONLY_MODE:return( LightenCompositeOp ); |
| case GIMP_HUE_MODE: return( HueCompositeOp ); |
| case GIMP_SATURATION_MODE: return( SaturateCompositeOp ); |
| case GIMP_COLOR_MODE: return( ColorizeCompositeOp ); |
| case GIMP_DODGE_MODE: return( ColorDodgeCompositeOp ); |
| case GIMP_BURN_MODE: return( ColorBurnCompositeOp ); |
| case GIMP_HARDLIGHT_MODE: return( HardLightCompositeOp ); |
| case GIMP_DIVIDE_MODE: return( DivideCompositeOp ); |
| /* these are the ones we don't support...yet */ |
| case GIMP_BEHIND_MODE: return( OverCompositeOp ); |
| case GIMP_VALUE_MODE: return( OverCompositeOp ); |
| 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 long length byte and then characters to that length |
| % |
| % The format of the ReadBlobStringWithLongSize method is: |
| % |
| % char *ReadBlobStringWithLongSize(Image *image,char *string) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o string: the address of a character buffer. |
| % |
| */ |
| |
| static inline size_t MagickMin(const size_t x,const size_t y) |
| { |
| if (x < y) |
| return(x); |
| return(y); |
| } |
| |
| static char *ReadBlobStringWithLongSize(Image *image,char *string,size_t max) |
| { |
| int |
| c; |
| |
| MagickOffsetType |
| offset; |
| |
| register long |
| i; |
| |
| unsigned long |
| length; |
| |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickSignature); |
| assert(max != 0); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| length=ReadBlobMSBLong(image); |
| for (i=0; i < (long) 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(&image->exception,GetMagickModule(), |
| CorruptImageError,"ImproperImageHeader","`%s'",image->filename); |
| return(string); |
| } |
| |
| static MagickBooleanType load_tile(Image *image,Image *tile_image, |
| XCFDocInfo *inDocInfo,XCFLayerInfo *inLayerInfo,size_t data_length) |
| { |
| ExceptionInfo |
| *exception; |
| |
| long |
| y; |
| |
| register long |
| x; |
| |
| register PixelPacket |
| *q; |
| |
| ssize_t |
| count; |
| |
| unsigned char |
| *graydata; |
| |
| XCFPixelPacket |
| *xcfdata, |
| *xcfodata; |
| |
| xcfdata=(XCFPixelPacket *) AcquireQuantumMemory(data_length,sizeof(*xcfdata)); |
| if (xcfdata == (XCFPixelPacket *) 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) |
| ThrowBinaryException(CorruptImageError,"NotEnoughPixelData", |
| image->filename); |
| exception=(&image->exception); |
| for (y=0; y < (long) tile_image->rows; y++) |
| { |
| q=QueueAuthenticPixels(tile_image,0,y,tile_image->columns,1,exception); |
| if (q == (PixelPacket *) NULL) |
| break; |
| if (inDocInfo->image_type == GIMP_GRAY) |
| { |
| for (x=0; x < (long) tile_image->columns; x++) |
| { |
| q->red=ScaleCharToQuantum(*graydata); |
| q->green=q->red; |
| q->blue=q->red; |
| q->opacity=ScaleCharToQuantum(255-inLayerInfo->opacity); |
| graydata++; |
| q++; |
| } |
| } |
| else |
| if (inDocInfo->image_type == GIMP_RGB) |
| { |
| for (x=0; x < (long) tile_image->columns; x++) |
| { |
| q->red=ScaleCharToQuantum(xcfdata->red); |
| q->green=ScaleCharToQuantum(xcfdata->green); |
| q->blue=ScaleCharToQuantum(xcfdata->blue); |
| q->opacity=(Quantum) (xcfdata->opacity == 0U ? TransparentOpacity : |
| ScaleCharToQuantum(255-inLayerInfo->opacity)); |
| xcfdata++; |
| q++; |
| } |
| } |
| if (SyncAuthenticPixels(tile_image,exception) == MagickFalse) |
| break; |
| } |
| xcfodata=(XCFPixelPacket *) RelinquishMagickMemory(xcfodata); |
| return MagickTrue; |
| } |
| |
| static MagickBooleanType load_tile_rle(Image *image,Image *tile_image, |
| XCFDocInfo *inDocInfo,XCFLayerInfo *inLayerInfo,size_t data_length) |
| { |
| ExceptionInfo |
| *exception; |
| |
| long |
| i, |
| j; |
| |
| MagickOffsetType |
| size; |
| |
| register PixelPacket |
| *q; |
| |
| ssize_t |
| bytes_per_pixel, |
| count; |
| |
| size_t |
| length; |
| |
| unsigned char |
| data, |
| 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; |
| exception=(&image->exception); |
| for (i=0; i < (long) bytes_per_pixel; i++) |
| { |
| q=GetAuthenticPixels(tile_image,0,0,tile_image->columns,tile_image->rows,exception); |
| 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=(*xcfdata++); |
| switch (i) |
| { |
| case 0: |
| { |
| q->red=ScaleCharToQuantum(data); |
| if (inDocInfo->image_type == GIMP_GRAY) |
| { |
| q->green=ScaleCharToQuantum(data); |
| q->blue=ScaleCharToQuantum(data); |
| q->opacity=ScaleCharToQuantum(255-inLayerInfo->opacity); |
| } |
| else |
| { |
| q->green= q->red; |
| q->blue= q->red; |
| q->opacity=ScaleCharToQuantum(255-inLayerInfo->opacity); |
| } |
| break; |
| } |
| case 1: |
| { |
| q->green=ScaleCharToQuantum(data); |
| break; |
| } |
| case 2: |
| { |
| q->blue=ScaleCharToQuantum(data); |
| break; |
| } |
| case 3: |
| { |
| q->opacity=(Quantum) (data == 0 ? TransparentOpacity : |
| ScaleCharToQuantum(255-inLayerInfo->opacity)); |
| break; |
| } |
| } |
| q++; |
| } |
| } |
| 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 < (long) length; j++) |
| { |
| data=pixel; |
| switch (i) |
| { |
| case 0: |
| { |
| q->red=ScaleCharToQuantum(data); |
| if (inDocInfo->image_type == GIMP_GRAY) |
| { |
| q->green=ScaleCharToQuantum(data); |
| q->blue=ScaleCharToQuantum(data); |
| q->opacity=ScaleCharToQuantum(255-inLayerInfo->opacity); |
| } |
| else |
| { |
| q->green=q->red; |
| q->blue=q->red; |
| q->opacity=ScaleCharToQuantum(255-inLayerInfo->opacity); |
| } |
| break; |
| } |
| case 1: |
| { |
| q->green=ScaleCharToQuantum(data); |
| break; |
| } |
| case 2: |
| { |
| q->blue=ScaleCharToQuantum(data); |
| break; |
| } |
| case 3: |
| { |
| q->opacity=(Quantum) (data == 0 ? TransparentOpacity : |
| ScaleCharToQuantum(255-inLayerInfo->opacity)); |
| break; |
| } |
| } |
| q++; |
| } |
| } |
| } |
| 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; |
| |
| register long |
| i; |
| |
| unsigned long |
| width, |
| height, |
| ntiles, |
| ntile_rows, |
| ntile_cols, |
| tile_image_width, |
| tile_image_height; |
| |
| /* start reading the data */ |
| exception=inDocInfo->exception; |
| 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=(MagickOffsetType) ReadBlobMSBLong(image); |
| if (offset == 0) |
| 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 < (long) 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=(MagickOffsetType)ReadBlobMSBLong(image); |
| /* 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 */ |
| offset=SeekBlob(image, offset, SEEK_SET); |
| |
| /* allocate the image for the tile |
| NOTE: the last tile in a row or column may not be a full tile! |
| */ |
| tile_image_width=(unsigned long) (destLeft == (int) ntile_cols-1 ? |
| (int) width % TILE_WIDTH : TILE_WIDTH); |
| if (tile_image_width == 0) tile_image_width=TILE_WIDTH; |
| tile_image_height = (unsigned long) (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); |
| |
| /* read in the tile */ |
| switch (inDocInfo->compression) |
| { |
| case COMPRESS_NONE: |
| if (load_tile(image,tile_image,inDocInfo,inLayerInfo,(size_t) (offset2-offset)) == 0) |
| status=MagickTrue; |
| break; |
| case COMPRESS_RLE: |
| if (load_tile_rle (image,tile_image,inDocInfo,inLayerInfo, |
| (int) (offset2-offset)) == 0) |
| status=MagickTrue; |
| break; |
| case COMPRESS_ZLIB: |
| ThrowBinaryException(CoderError,"ZipCompressNotSupported", |
| image->filename) |
| case COMPRESS_FRACTAL: |
| ThrowBinaryException(CoderError,"FractalCompressNotSupported", |
| image->filename) |
| } |
| |
| /* composite the tile onto the layer's image, and then destroy it */ |
| (void) CompositeImage(inLayerInfo->image,CopyCompositeOp,tile_image, |
| destLeft * TILE_WIDTH,destTop*TILE_HEIGHT); |
| tile_image=DestroyImage(tile_image); |
| |
| /* adjust tile position */ |
| destLeft++; |
| if (destLeft >= (int) ntile_cols) |
| { |
| destLeft = 0; |
| destTop++; |
| } |
| if (status != MagickFalse) |
| return(MagickFalse); |
| /* 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=(MagickOffsetType) ReadBlobMSBLong(image); |
| } |
| if (offset != 0) |
| ThrowBinaryException(CorruptImageError,"CorruptImage",image->filename) |
| return(MagickTrue); |
| } |
| |
| static MagickBooleanType load_hierarchy(Image *image,XCFDocInfo *inDocInfo, |
| XCFLayerInfo *inLayer) |
| { |
| MagickOffsetType |
| saved_pos, |
| offset, |
| junk; |
| |
| unsigned long |
| width, |
| height, |
| bytes_per_pixel; |
| |
| width=ReadBlobMSBLong(image); |
| height=ReadBlobMSBLong(image); |
| bytes_per_pixel=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=(MagickOffsetType) ReadBlobMSBLong(image); /* top level */ |
| |
| /* 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 */ |
| offset=SeekBlob(image, offset, SEEK_SET); |
| |
| /* read in the level */ |
| if (load_level (image, inDocInfo, inLayer) == 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 MagickBooleanType ReadOneLayer(Image* image,XCFDocInfo* inDocInfo, |
| XCFLayerInfo *outLayer ) |
| { |
| long |
| i; |
| |
| MagickOffsetType |
| offset; |
| |
| unsigned int |
| foundPropEnd = 0; |
| |
| unsigned long |
| hierarchy_offset, |
| layer_mask_offset; |
| |
| /* clear the block! */ |
| (void) ResetMagickMemory( 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)); |
| /* allocate the image for this layer */ |
| outLayer->image=CloneImage(image,outLayer->width, outLayer->height,MagickTrue, |
| &image->exception); |
| if (outLayer->image == (Image *) NULL) |
| return MagickFalse; |
| /* read the layer properties! */ |
| foundPropEnd = 0; |
| while ( (foundPropEnd == MagickFalse) && (EOFBlob(image) == MagickFalse) ) { |
| PropType prop_type = (PropType) ReadBlobMSBLong(image); |
| unsigned long 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->opacity = 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 = (long) ReadBlobMSBLong(image); |
| outLayer->offset_y = (long) ReadBlobMSBLong(image); |
| break; |
| case PROP_MODE: |
| outLayer->mode = ReadBlobMSBLong(image); |
| break; |
| case PROP_TATTOO: |
| outLayer->preserve_trans = ReadBlobMSBLong(image); |
| break; |
| case PROP_PARASITES: |
| { |
| for (i=0; i < (long) prop_size; i++ ) |
| (void) ReadBlobByte(image); |
| |
| /* |
| long 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 -= (unsigned long) MagickMin(16, (size_t) amount); |
| } |
| } |
| break; |
| } |
| } |
| |
| if (foundPropEnd == MagickFalse) |
| return(MagickFalse); |
| /* clear the image based on the layer opacity */ |
| outLayer->image->background_color.opacity= |
| ScaleCharToQuantum((unsigned char) (255-outLayer->opacity)); |
| (void) SetImageBackgroundColor(outLayer->image); |
| |
| /* 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 = ReadBlobMSBLong(image); |
| layer_mask_offset = ReadBlobMSBLong(image); |
| |
| /* read in the hierarchy */ |
| offset=SeekBlob(image, (MagickOffsetType) hierarchy_offset, SEEK_SET); |
| if (offset < 0) |
| (void) ThrowMagickException(&image->exception,GetMagickModule(), |
| CorruptImageError,"InvalidImageHeader","`%s'",image->filename); |
| if (load_hierarchy (image, inDocInfo, outLayer) == 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; |
| |
| register long |
| i; |
| |
| size_t |
| length; |
| |
| ssize_t |
| count; |
| |
| unsigned long |
| image_type; |
| |
| XCFDocInfo |
| doc_info; |
| |
| /* |
| Open image file. |
| */ |
| assert(image_info != (const ImageInfo *) NULL); |
| assert(image_info->signature == MagickSignature); |
| if (image_info->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s", |
| image_info->filename); |
| assert(exception != (ExceptionInfo *) NULL); |
| assert(exception->signature == MagickSignature); |
| image=AcquireImage(image_info); |
| status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); |
| if (status == MagickFalse) |
| { |
| image=DestroyImageList(image); |
| return((Image *) NULL); |
| } |
| count=ReadBlob(image,14,(unsigned char *) magick); |
| if ((count == 0) || |
| (LocaleNCompare((char *) magick,"gimp xcf",8) != 0)) |
| ThrowReaderException(CorruptImageError,"ImproperImageHeader"); |
| (void) ResetMagickMemory(&doc_info,0,sizeof(XCFDocInfo)); |
| doc_info.exception=exception; |
| 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); |
| /* |
| 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; |
| if (image_type == GIMP_RGB) |
| image->colorspace=RGBColorspace; |
| else |
| if (image_type == GIMP_GRAY) |
| image->colorspace=GRAYColorspace; |
| else |
| if (image_type == GIMP_INDEXED) |
| ThrowReaderException(CoderError,"ColormapTypeNotSupported"); |
| (void) SetImageBackgroundColor(image); |
| image->matte=MagickTrue; |
| /* |
| Read properties. |
| */ |
| while ((foundPropEnd == MagickFalse) && (EOFBlob(image) == MagickFalse)) |
| { |
| PropType prop_type = (PropType) ReadBlobMSBLong(image); |
| unsigned long 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. |
| */ |
| unsigned long num_colours = ReadBlobMSBLong(image); |
| for (i=0; i < (long) (3L*num_colours); i++ ) |
| (void) ReadBlobByte(image); |
| /* |
| 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; i<gimage->num_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 */ |
| for (i=0; i < (long) prop_size; i++ ) |
| if (ReadBlobByte(image) == EOF) |
| 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->x_resolution = xres; |
| image->y_resolution = yres; |
| */ |
| } |
| break; |
| |
| case PROP_TATTOO: |
| { |
| /* we need to read it, even if we ignore it */ |
| /*unsigned long tattoo_state = */ (void) ReadBlobMSBLong(image); |
| } |
| break; |
| |
| case PROP_PARASITES: |
| { |
| /* BOGUS: we may need these for IPTC stuff */ |
| for (i=0; i < (long) prop_size; i++ ) |
| if (ReadBlobByte(image) == EOF) |
| ThrowFileException(exception,CorruptImageError, |
| "UnexpectedEndOfFile",image->filename); |
| |
| /* |
| glong 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... */ |
| /*unsigned long unit = */ (void) ReadBlobMSBLong(image); |
| } |
| break; |
| |
| case PROP_PATHS: |
| { |
| /* BOGUS: just skip it for now */ |
| for (i=0; i< (long) prop_size; i++ ) |
| if (ReadBlobByte(image) == EOF) |
| 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); |
| /* unsigned long digits = */ (void) ReadBlobMSBLong(image); |
| for (i=0; i<5; i++) |
| (void) ReadBlobStringWithLongSize(image, unit_string, |
| sizeof(unit_string)); |
| } |
| break; |
| |
| default: |
| { |
| int buf[16]; |
| long amount; |
| |
| /* read over it... */ |
| while ((prop_size > 0) && (EOFBlob(image) == MagickFalse)) |
| { |
| amount=(long) MagickMin(16, prop_size); |
| amount=(long) ReadBlob(image,(size_t) amount,(unsigned char *) &buf); |
| if (!amount) |
| ThrowReaderException(CorruptImageError,"CorruptImage"); |
| prop_size -= (unsigned long) 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 |
| { |
| long offset = (long) ReadBlobMSBLong(image); |
| if (offset == 0) |
| foundAllLayers=MagickTrue; |
| else |
| number_layers++; |
| if (EOFBlob(image) != MagickFalse) |
| { |
| ThrowFileException(exception,CorruptImageError, |
| "UnexpectedEndOfFile",image->filename); |
| break; |
| } |
| } while (foundAllLayers == MagickFalse); |
| 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) ResetMagickMemory(layer_info,0,number_layers*sizeof(XCFLayerInfo)); |
| for ( ; ; ) |
| { |
| MagickBooleanType |
| layer_ok; |
| |
| MagickOffsetType |
| offset, |
| saved_pos; |
| |
| /* read in the offset of the next layer */ |
| offset=(MagickOffsetType) ReadBlobMSBLong(image); |
| /* 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 */ |
| offset=SeekBlob(image,offset,SEEK_SET); |
| /* read in the layer */ |
| layer_ok=ReadOneLayer(image,&doc_info,&layer_info[current_layer]); |
| if (layer_ok == MagickFalse) |
| { |
| int j; |
| |
| for (j=0; j < current_layer; j++) |
| layer_info[j].image=DestroyImage(layer_info[j].image); |
| ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed"); |
| } |
| /* restore the saved position so we'll be ready to |
| * read the next offset. |
| */ |
| offset=SeekBlob(image, saved_pos, SEEK_SET); |
| current_layer++; |
| } |
| if (number_layers == 1) |
| { |
| /* |
| Composite the layer data onto the main image, dispose the layer. |
| */ |
| (void) CompositeImage(image,OverCompositeOp,layer_info[0].image, |
| layer_info[0].offset_x,layer_info[0].offset_y); |
| layer_info[0].image =DestroyImage( layer_info[0].image); |
| } |
| else |
| { |
| #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 ); |
| |
| /* Bob says that 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! */ |
| signed int j; |
| |
| /* first we copy the last layer on top of the main image */ |
| (void) CompositeImage(image,CopyCompositeOp, |
| layer_info[number_layers-1].image, |
| layer_info[number_layers-1].offset_x, |
| layer_info[number_layers-1].offset_y); |
| layer_info[number_layers-1].image=DestroyImage( |
| layer_info[number_layers-1].image); |
| |
| /* now reverse the order of the layers as they are put |
| into subimages |
| */ |
| j=number_layers-2; |
| image->next=layer_info[j].image; |
| layer_info[j].image->previous=image; |
| layer_info[j].image->page.x=layer_info[j].offset_x; |
| layer_info[j].image->page.y=layer_info[j].offset_y; |
| layer_info[j].image->page.width=layer_info[j].width; |
| layer_info[j].image->page.height=layer_info[j].height; |
| for (j=number_layers-3; j>=0; j--) |
| { |
| if (j > 0) |
| layer_info[j].image->next=layer_info[j-1].image; |
| if (j < (number_layers-1)) |
| layer_info[j].image->previous=layer_info[j+1].image; |
| layer_info[j].image->page.x=layer_info[j].offset_x; |
| layer_info[j].image->page.y=layer_info[j].offset_y; |
| layer_info[j].image->page.width=layer_info[j].width; |
| layer_info[j].image->page.height=layer_info[j].height; |
| } |
| } |
| #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 (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: |
| % |
| % unsigned long RegisterXCFImage(void) |
| % |
| */ |
| ModuleExport unsigned long RegisterXCFImage(void) |
| { |
| MagickInfo |
| *entry; |
| |
| entry=SetMagickInfo("XCF"); |
| entry->decoder=(DecodeImageHandler *) ReadXCFImage; |
| entry->magick=(IsImageFormatHandler *) IsXCF; |
| entry->description=ConstantString("GIMP image"); |
| entry->module=ConstantString("XCF"); |
| entry->seekable_stream=MagickTrue; |
| (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"); |
| } |