You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1637 lines
50 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% 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; 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 */
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");
}