blob: d62ba437f4e69b6d29ceaadc0c1369b6d9f40026 [file] [log] [blame]
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% GGGG IIIII FFFFF %
% G I F %
% G GG I FFF %
% G G I F %
% GGG IIIII F %
% %
% %
% Read/Write Compuserv Graphics Interchange Format %
% %
% Software Design %
% Cristy %
% July 1992 %
% %
% %
% Copyright 1999-2019 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/attribute.h"
#include "MagickCore/blob.h"
#include "MagickCore/blob-private.h"
#include "MagickCore/cache.h"
#include "MagickCore/color.h"
#include "MagickCore/color-private.h"
#include "MagickCore/colormap.h"
#include "MagickCore/colormap-private.h"
#include "MagickCore/colorspace.h"
#include "MagickCore/colorspace-private.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/profile.h"
#include "MagickCore/magick.h"
#include "MagickCore/memory_.h"
#include "MagickCore/monitor.h"
#include "MagickCore/monitor-private.h"
#include "MagickCore/option.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"
/*
Define declarations.
*/
#define MaximumLZWBits 12
#define MaximumLZWCode (1UL << MaximumLZWBits)
/*
Typdef declarations.
*/
typedef struct _LZWCodeInfo
{
unsigned char
buffer[280];
size_t
count,
bit;
MagickBooleanType
eof;
} LZWCodeInfo;
typedef struct _LZWStack
{
size_t
*codes,
*index,
*top;
} LZWStack;
typedef struct _LZWInfo
{
Image
*image;
LZWStack
*stack;
MagickBooleanType
genesis;
size_t
data_size,
maximum_data_value,
clear_code,
end_code,
bits,
first_code,
last_code,
maximum_code,
slot,
*table[2];
LZWCodeInfo
code_info;
} LZWInfo;
/*
Forward declarations.
*/
static inline int
GetNextLZWCode(LZWInfo *,const size_t);
static MagickBooleanType
WriteGIFImage(const ImageInfo *,Image *,ExceptionInfo *);
static ssize_t
ReadBlobBlock(Image *,unsigned char *);
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% D e c o d e I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% DecodeImage uncompresses an image via GIF-coding.
%
% The format of the DecodeImage method is:
%
% MagickBooleanType DecodeImage(Image *image,const ssize_t opacity)
%
% A description of each parameter follows:
%
% o image: the address of a structure of type Image.
%
% o opacity: The colormap index associated with the transparent color.
%
*/
static LZWInfo *RelinquishLZWInfo(LZWInfo *lzw_info)
{
if (lzw_info->table[0] != (size_t *) NULL)
lzw_info->table[0]=(size_t *) RelinquishMagickMemory(
lzw_info->table[0]);
if (lzw_info->table[1] != (size_t *) NULL)
lzw_info->table[1]=(size_t *) RelinquishMagickMemory(
lzw_info->table[1]);
if (lzw_info->stack != (LZWStack *) NULL)
{
if (lzw_info->stack->codes != (size_t *) NULL)
lzw_info->stack->codes=(size_t *) RelinquishMagickMemory(
lzw_info->stack->codes);
lzw_info->stack=(LZWStack *) RelinquishMagickMemory(lzw_info->stack);
}
lzw_info=(LZWInfo *) RelinquishMagickMemory(lzw_info);
return((LZWInfo *) NULL);
}
static inline void ResetLZWInfo(LZWInfo *lzw_info)
{
size_t
one;
lzw_info->bits=lzw_info->data_size+1;
one=1;
lzw_info->maximum_code=one << lzw_info->bits;
lzw_info->slot=lzw_info->maximum_data_value+3;
lzw_info->genesis=MagickTrue;
}
static LZWInfo *AcquireLZWInfo(Image *image,const size_t data_size)
{
LZWInfo
*lzw_info;
register ssize_t
i;
size_t
one;
lzw_info=(LZWInfo *) AcquireMagickMemory(sizeof(*lzw_info));
if (lzw_info == (LZWInfo *) NULL)
return((LZWInfo *) NULL);
(void) memset(lzw_info,0,sizeof(*lzw_info));
lzw_info->image=image;
lzw_info->data_size=data_size;
one=1;
lzw_info->maximum_data_value=(one << data_size)-1;
lzw_info->clear_code=lzw_info->maximum_data_value+1;
lzw_info->end_code=lzw_info->maximum_data_value+2;
lzw_info->table[0]=(size_t *) AcquireQuantumMemory(MaximumLZWCode,
sizeof(**lzw_info->table));
lzw_info->table[1]=(size_t *) AcquireQuantumMemory(MaximumLZWCode,
sizeof(**lzw_info->table));
if ((lzw_info->table[0] == (size_t *) NULL) ||
(lzw_info->table[1] == (size_t *) NULL))
{
lzw_info=RelinquishLZWInfo(lzw_info);
return((LZWInfo *) NULL);
}
(void) memset(lzw_info->table[0],0,MaximumLZWCode*
sizeof(**lzw_info->table));
(void) memset(lzw_info->table[1],0,MaximumLZWCode*
sizeof(**lzw_info->table));
for (i=0; i <= (ssize_t) lzw_info->maximum_data_value; i++)
{
lzw_info->table[0][i]=0;
lzw_info->table[1][i]=(size_t) i;
}
ResetLZWInfo(lzw_info);
lzw_info->code_info.buffer[0]='\0';
lzw_info->code_info.buffer[1]='\0';
lzw_info->code_info.count=2;
lzw_info->code_info.bit=8*lzw_info->code_info.count;
lzw_info->code_info.eof=MagickFalse;
lzw_info->genesis=MagickTrue;
lzw_info->stack=(LZWStack *) AcquireMagickMemory(sizeof(*lzw_info->stack));
if (lzw_info->stack == (LZWStack *) NULL)
{
lzw_info=RelinquishLZWInfo(lzw_info);
return((LZWInfo *) NULL);
}
lzw_info->stack->codes=(size_t *) AcquireQuantumMemory(2UL*
MaximumLZWCode,sizeof(*lzw_info->stack->codes));
if (lzw_info->stack->codes == (size_t *) NULL)
{
lzw_info=RelinquishLZWInfo(lzw_info);
return((LZWInfo *) NULL);
}
lzw_info->stack->index=lzw_info->stack->codes;
lzw_info->stack->top=lzw_info->stack->codes+2*MaximumLZWCode;
return(lzw_info);
}
static inline int GetNextLZWCode(LZWInfo *lzw_info,const size_t bits)
{
int
code;
register ssize_t
i;
size_t
one;
while (((lzw_info->code_info.bit+bits) > (8*lzw_info->code_info.count)) &&
(lzw_info->code_info.eof == MagickFalse))
{
ssize_t
count;
lzw_info->code_info.buffer[0]=lzw_info->code_info.buffer[
lzw_info->code_info.count-2];
lzw_info->code_info.buffer[1]=lzw_info->code_info.buffer[
lzw_info->code_info.count-1];
lzw_info->code_info.bit-=8*(lzw_info->code_info.count-2);
lzw_info->code_info.count=2;
count=ReadBlobBlock(lzw_info->image,&lzw_info->code_info.buffer[
lzw_info->code_info.count]);
if (count > 0)
lzw_info->code_info.count+=count;
else
lzw_info->code_info.eof=MagickTrue;
}
if ((lzw_info->code_info.bit+bits) > (8*lzw_info->code_info.count))
return(-1);
code=0;
one=1;
for (i=0; i < (ssize_t) bits; i++)
{
code|=((lzw_info->code_info.buffer[lzw_info->code_info.bit/8] &
(one << (lzw_info->code_info.bit % 8))) != 0) << i;
lzw_info->code_info.bit++;
}
return(code);
}
static inline int PopLZWStack(LZWStack *stack_info)
{
if (stack_info->index <= stack_info->codes)
return(-1);
stack_info->index--;
return((int) *stack_info->index);
}
static inline void PushLZWStack(LZWStack *stack_info,const size_t value)
{
if (stack_info->index >= stack_info->top)
return;
*stack_info->index=value;
stack_info->index++;
}
static int ReadBlobLZWByte(LZWInfo *lzw_info)
{
int
code;
size_t
one,
value;
ssize_t
count;
if (lzw_info->stack->index != lzw_info->stack->codes)
return(PopLZWStack(lzw_info->stack));
if (lzw_info->genesis != MagickFalse)
{
lzw_info->genesis=MagickFalse;
do
{
lzw_info->first_code=(size_t) GetNextLZWCode(lzw_info,lzw_info->bits);
lzw_info->last_code=lzw_info->first_code;
} while (lzw_info->first_code == lzw_info->clear_code);
return((int) lzw_info->first_code);
}
code=GetNextLZWCode(lzw_info,lzw_info->bits);
if (code < 0)
return(code);
if ((size_t) code == lzw_info->clear_code)
{
ResetLZWInfo(lzw_info);
return(ReadBlobLZWByte(lzw_info));
}
if ((size_t) code == lzw_info->end_code)
return(-1);
if ((size_t) code < lzw_info->slot)
value=(size_t) code;
else
{
PushLZWStack(lzw_info->stack,lzw_info->first_code);
value=lzw_info->last_code;
}
count=0;
while (value > lzw_info->maximum_data_value)
{
if ((size_t) count > MaximumLZWCode)
return(-1);
count++;
if ((size_t) value > MaximumLZWCode)
return(-1);
PushLZWStack(lzw_info->stack,lzw_info->table[1][value]);
value=lzw_info->table[0][value];
}
lzw_info->first_code=lzw_info->table[1][value];
PushLZWStack(lzw_info->stack,lzw_info->first_code);
one=1;
if (lzw_info->slot < MaximumLZWCode)
{
lzw_info->table[0][lzw_info->slot]=lzw_info->last_code;
lzw_info->table[1][lzw_info->slot]=lzw_info->first_code;
lzw_info->slot++;
if ((lzw_info->slot >= lzw_info->maximum_code) &&
(lzw_info->bits < MaximumLZWBits))
{
lzw_info->bits++;
lzw_info->maximum_code=one << lzw_info->bits;
}
}
lzw_info->last_code=(size_t) code;
return(PopLZWStack(lzw_info->stack));
}
static MagickBooleanType DecodeImage(Image *image,const ssize_t opacity,
ExceptionInfo *exception)
{
int
c;
LZWInfo
*lzw_info;
size_t
pass;
ssize_t
index,
offset,
y;
unsigned char
data_size;
/*
Allocate decoder tables.
*/
assert(image != (Image *) NULL);
assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
data_size=(unsigned char) ReadBlobByte(image);
if (data_size > MaximumLZWBits)
ThrowBinaryException(CorruptImageError,"CorruptImage",image->filename);
lzw_info=AcquireLZWInfo(image,data_size);
if (lzw_info == (LZWInfo *) NULL)
ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
image->filename);
pass=0;
offset=0;
for (y=0; y < (ssize_t) image->rows; y++)
{
register ssize_t
x;
register Quantum
*magick_restrict q;
q=QueueAuthenticPixels(image,0,offset,image->columns,1,exception);
if (q == (Quantum *) NULL)
break;
for (x=0; x < (ssize_t) image->columns; )
{
c=ReadBlobLZWByte(lzw_info);
if (c < 0)
break;
index=ConstrainColormapIndex(image,(ssize_t) c,exception);
SetPixelIndex(image,(Quantum) index,q);
SetPixelViaPixelInfo(image,image->colormap+index,q);
SetPixelAlpha(image,index == opacity ? TransparentAlpha : OpaqueAlpha,q);
x++;
q+=GetPixelChannels(image);
}
if (SyncAuthenticPixels(image,exception) == MagickFalse)
break;
if (x < (ssize_t) image->columns)
break;
if (image->interlace == NoInterlace)
offset++;
else
{
switch (pass)
{
case 0:
default:
{
offset+=8;
break;
}
case 1:
{
offset+=8;
break;
}
case 2:
{
offset+=4;
break;
}
case 3:
{
offset+=2;
break;
}
}
if ((pass == 0) && (offset >= (ssize_t) image->rows))
{
pass++;
offset=4;
}
if ((pass == 1) && (offset >= (ssize_t) image->rows))
{
pass++;
offset=2;
}
if ((pass == 2) && (offset >= (ssize_t) image->rows))
{
pass++;
offset=1;
}
}
}
lzw_info=RelinquishLZWInfo(lzw_info);
if (y < (ssize_t) image->rows)
ThrowBinaryException(CorruptImageError,"CorruptImage",image->filename);
return(MagickTrue);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% E n c o d e I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% EncodeImage compresses an image via GIF-coding.
%
% The format of the EncodeImage method is:
%
% MagickBooleanType EncodeImage(const ImageInfo *image_info,Image *image,
% const size_t data_size)
%
% A description of each parameter follows:
%
% o image_info: the image info.
%
% o image: the address of a structure of type Image.
%
% o data_size: The number of bits in the compressed packet.
%
*/
static MagickBooleanType EncodeImage(const ImageInfo *image_info,Image *image,
const size_t data_size,ExceptionInfo *exception)
{
#define MaxCode(number_bits) ((one << (number_bits))-1)
#define MaxHashTable 5003
#define MaxGIFBits 12UL
#define MaxGIFTable (1UL << MaxGIFBits)
#define GIFOutputCode(code) \
{ \
/* \
Emit a code. \
*/ \
if (bits > 0) \
datum|=(size_t) (code) << bits; \
else \
datum=(size_t) (code); \
bits+=number_bits; \
while (bits >= 8) \
{ \
/* \
Add a character to current packet. \
*/ \
packet[length++]=(unsigned char) (datum & 0xff); \
if (length >= 254) \
{ \
(void) WriteBlobByte(image,(unsigned char) length); \
(void) WriteBlob(image,length,packet); \
length=0; \
} \
datum>>=8; \
bits-=8; \
} \
if (free_code > max_code) \
{ \
number_bits++; \
if (number_bits == MaxGIFBits) \
max_code=MaxGIFTable; \
else \
max_code=MaxCode(number_bits); \
} \
}
Quantum
index;
short
*hash_code,
*hash_prefix,
waiting_code;
size_t
bits,
clear_code,
datum,
end_of_information_code,
free_code,
length,
max_code,
next_pixel,
number_bits,
one,
pass;
ssize_t
displacement,
offset,
k,
y;
unsigned char
*packet,
*hash_suffix;
/*
Allocate encoder tables.
*/
assert(image != (Image *) NULL);
one=1;
packet=(unsigned char *) AcquireQuantumMemory(256,sizeof(*packet));
hash_code=(short *) AcquireQuantumMemory(MaxHashTable,sizeof(*hash_code));
hash_prefix=(short *) AcquireQuantumMemory(MaxHashTable,sizeof(*hash_prefix));
hash_suffix=(unsigned char *) AcquireQuantumMemory(MaxHashTable,
sizeof(*hash_suffix));
if ((packet == (unsigned char *) NULL) || (hash_code == (short *) NULL) ||
(hash_prefix == (short *) NULL) ||
(hash_suffix == (unsigned char *) NULL))
{
if (packet != (unsigned char *) NULL)
packet=(unsigned char *) RelinquishMagickMemory(packet);
if (hash_code != (short *) NULL)
hash_code=(short *) RelinquishMagickMemory(hash_code);
if (hash_prefix != (short *) NULL)
hash_prefix=(short *) RelinquishMagickMemory(hash_prefix);
if (hash_suffix != (unsigned char *) NULL)
hash_suffix=(unsigned char *) RelinquishMagickMemory(hash_suffix);
return(MagickFalse);
}
/*
Initialize GIF encoder.
*/
(void) memset(packet,0,256*sizeof(*packet));
(void) memset(hash_code,0,MaxHashTable*sizeof(*hash_code));
(void) memset(hash_prefix,0,MaxHashTable*sizeof(*hash_prefix));
(void) memset(hash_suffix,0,MaxHashTable*sizeof(*hash_suffix));
number_bits=data_size;
max_code=MaxCode(number_bits);
clear_code=((short) one << (data_size-1));
end_of_information_code=clear_code+1;
free_code=clear_code+2;
length=0;
datum=0;
bits=0;
GIFOutputCode(clear_code);
/*
Encode pixels.
*/
offset=0;
pass=0;
waiting_code=0;
for (y=0; y < (ssize_t) image->rows; y++)
{
register const Quantum
*magick_restrict p;
register ssize_t
x;
p=GetVirtualPixels(image,0,offset,image->columns,1,exception);
if (p == (const Quantum *) NULL)
break;
if (y == 0)
{
waiting_code=(short) GetPixelIndex(image,p);
p+=GetPixelChannels(image);
}
for (x=(ssize_t) (y == 0 ? 1 : 0); x < (ssize_t) image->columns; x++)
{
/*
Probe hash table.
*/
index=(Quantum) ((size_t) GetPixelIndex(image,p) & 0xff);
p+=GetPixelChannels(image);
k=(ssize_t) (((size_t) index << (MaxGIFBits-8))+waiting_code);
if (k >= MaxHashTable)
k-=MaxHashTable;
next_pixel=MagickFalse;
displacement=1;
if (hash_code[k] > 0)
{
if ((hash_prefix[k] == waiting_code) &&
(hash_suffix[k] == (unsigned char) index))
{
waiting_code=hash_code[k];
continue;
}
if (k != 0)
displacement=MaxHashTable-k;
for ( ; ; )
{
k-=displacement;
if (k < 0)
k+=MaxHashTable;
if (hash_code[k] == 0)
break;
if ((hash_prefix[k] == waiting_code) &&
(hash_suffix[k] == (unsigned char) index))
{
waiting_code=hash_code[k];
next_pixel=MagickTrue;
break;
}
}
if (next_pixel != MagickFalse)
continue;
}
GIFOutputCode(waiting_code);
if (free_code < MaxGIFTable)
{
hash_code[k]=(short) free_code++;
hash_prefix[k]=waiting_code;
hash_suffix[k]=(unsigned char) index;
}
else
{
/*
Fill the hash table with empty entries.
*/
for (k=0; k < MaxHashTable; k++)
hash_code[k]=0;
/*
Reset compressor and issue a clear code.
*/
free_code=clear_code+2;
GIFOutputCode(clear_code);
number_bits=data_size;
max_code=MaxCode(number_bits);
}
waiting_code=(short) index;
}
if (image_info->interlace == NoInterlace)
offset++;
else
switch (pass)
{
case 0:
default:
{
offset+=8;
if (offset >= (ssize_t) image->rows)
{
pass++;
offset=4;
}
break;
}
case 1:
{
offset+=8;
if (offset >= (ssize_t) image->rows)
{
pass++;
offset=2;
}
break;
}
case 2:
{
offset+=4;
if (offset >= (ssize_t) image->rows)
{
pass++;
offset=1;
}
break;
}
case 3:
{
offset+=2;
break;
}
}
}
/*
Flush out the buffered code.
*/
GIFOutputCode(waiting_code);
GIFOutputCode(end_of_information_code);
if (bits > 0)
{
/*
Add a character to current packet.
*/
packet[length++]=(unsigned char) (datum & 0xff);
if (length >= 254)
{
(void) WriteBlobByte(image,(unsigned char) length);
(void) WriteBlob(image,length,packet);
length=0;
}
}
/*
Flush accumulated data.
*/
if (length > 0)
{
(void) WriteBlobByte(image,(unsigned char) length);
(void) WriteBlob(image,length,packet);
}
/*
Free encoder memory.
*/
hash_suffix=(unsigned char *) RelinquishMagickMemory(hash_suffix);
hash_prefix=(short *) RelinquishMagickMemory(hash_prefix);
hash_code=(short *) RelinquishMagickMemory(hash_code);
packet=(unsigned char *) RelinquishMagickMemory(packet);
return(MagickTrue);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% I s G I F %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% IsGIF() returns MagickTrue if the image format type, identified by the
% magick string, is GIF.
%
% The format of the IsGIF method is:
%
% MagickBooleanType IsGIF(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 IsGIF(const unsigned char *magick,const size_t length)
{
if (length < 4)
return(MagickFalse);
if (LocaleNCompare((char *) magick,"GIF8",4) == 0)
return(MagickTrue);
return(MagickFalse);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
+ R e a d B l o b B l o c k %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% ReadBlobBlock() reads data from the image file and returns it. The
% amount of data is determined by first reading a count byte. The number
% of bytes read is returned.
%
% The format of the ReadBlobBlock method is:
%
% ssize_t ReadBlobBlock(Image *image,unsigned char *data)
%
% A description of each parameter follows:
%
% o image: the image.
%
% o data: Specifies an area to place the information requested from
% the file.
%
*/
static ssize_t ReadBlobBlock(Image *image,unsigned char *data)
{
ssize_t
count;
unsigned char
block_count;
assert(image != (Image *) NULL);
assert(image->signature == MagickCoreSignature);
assert(data != (unsigned char *) NULL);
count=ReadBlob(image,1,&block_count);
if (count != 1)
return(0);
count=ReadBlob(image,(size_t) block_count,data);
if (count != (ssize_t) block_count)
return(0);
return(count);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% R e a d G I F I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% ReadGIFImage() reads a Compuserve Graphics 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 ReadGIFImage method is:
%
% Image *ReadGIFImage(const ImageInfo *image_info,ExceptionInfo *exception)
%
% A description of each parameter follows:
%
% o image_info: the image info.
%
% o exception: return any errors or warnings in this structure.
%
*/
static void *DestroyGIFProfile(void *profile)
{
return((void *) DestroyStringInfo((StringInfo *) profile));
}
static MagickBooleanType PingGIFImage(Image *image,ExceptionInfo *exception)
{
unsigned char
buffer[256],
length,
data_size;
assert(image != (Image *) NULL);
assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (ReadBlob(image,1,&data_size) != 1)
ThrowBinaryException(CorruptImageError,"CorruptImage",image->filename);
if (data_size > MaximumLZWBits)
ThrowBinaryException(CorruptImageError,"CorruptImage",image->filename);
if (ReadBlob(image,1,&length) != 1)
ThrowBinaryException(CorruptImageError,"CorruptImage",image->filename);
while (length != 0)
{
if (ReadBlob(image,length,buffer) != (ssize_t) length)
ThrowBinaryException(CorruptImageError,"CorruptImage",image->filename);
if (ReadBlob(image,1,&length) != 1)
ThrowBinaryException(CorruptImageError,"CorruptImage",image->filename);
}
return(MagickTrue);
}
static Image *ReadGIFImage(const ImageInfo *image_info,ExceptionInfo *exception)
{
#define BitSet(byte,bit) (((byte) & (bit)) == (bit))
#define LSBFirstOrder(x,y) (((y) << 8) | (x))
#define ThrowGIFException(exception,message) \
{ \
if (profiles != (LinkedListInfo *) NULL) \
profiles=DestroyLinkedList(profiles,DestroyGIFProfile); \
if (global_colormap != (unsigned char *) NULL) \
global_colormap=(unsigned char *) RelinquishMagickMemory(global_colormap); \
if (meta_image != (Image *) NULL) \
meta_image=DestroyImage(meta_image); \
ThrowReaderException((exception),(message)); \
}
Image
*image,
*meta_image;
LinkedListInfo
*profiles;
MagickBooleanType
status;
register ssize_t
i;
register unsigned char
*p;
size_t
duration,
global_colors,
image_count,
local_colors,
one;
ssize_t
count,
opacity;
unsigned char
background,
buffer[257],
c,
flag,
*global_colormap;
/*
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);
}
/*
Determine if this a GIF file.
*/
count=ReadBlob(image,6,buffer);
if ((count != 6) || ((LocaleNCompare((char *) buffer,"GIF87",5) != 0) &&
(LocaleNCompare((char *) buffer,"GIF89",5) != 0)))
ThrowReaderException(CorruptImageError,"ImproperImageHeader");
(void) memset(buffer,0,sizeof(buffer));
meta_image=AcquireImage(image_info,exception); /* metadata container */
meta_image->page.width=ReadBlobLSBShort(image);
meta_image->page.height=ReadBlobLSBShort(image);
flag=(unsigned char) ReadBlobByte(image);
profiles=(LinkedListInfo *) NULL;
background=(unsigned char) ReadBlobByte(image);
c=(unsigned char) ReadBlobByte(image); /* reserved */
one=1;
global_colors=one << (((size_t) flag & 0x07)+1);
global_colormap=(unsigned char *) AcquireQuantumMemory((size_t)
MagickMax(global_colors,256),3UL*sizeof(*global_colormap));
if (global_colormap == (unsigned char *) NULL)
ThrowGIFException(ResourceLimitError,"MemoryAllocationFailed");
(void) memset(global_colormap,0,3*MagickMax(global_colors,256)*
sizeof(*global_colormap));
if (BitSet((int) flag,0x80) != 0)
{
count=ReadBlob(image,(size_t) (3*global_colors),global_colormap);
if (count != (ssize_t) (3*global_colors))
ThrowGIFException(CorruptImageError,"InsufficientImageDataInFile");
}
duration=0;
opacity=(-1);
image_count=0;
for ( ; ; )
{
count=ReadBlob(image,1,&c);
if (count != 1)
break;
if (c == (unsigned char) ';')
break; /* terminator */
if (c == (unsigned char) '!')
{
/*
GIF Extension block.
*/
(void) memset(buffer,0,sizeof(buffer));
count=ReadBlob(image,1,&c);
if (count != 1)
ThrowGIFException(CorruptImageError,"UnableToReadExtensionBlock");
switch (c)
{
case 0xf9:
{
/*
Read graphics control extension.
*/
while (ReadBlobBlock(image,buffer) != 0) ;
meta_image->dispose=(DisposeType) ((buffer[0] >> 2) & 0x07);
meta_image->delay=((size_t) buffer[2] << 8) | buffer[1];
if ((ssize_t) (buffer[0] & 0x01) == 0x01)
opacity=(ssize_t) buffer[3];
break;
}
case 0xfe:
{
char
*comments;
size_t
extent,
offset;
comments=AcquireString((char *) NULL);
extent=MagickPathExtent;
for (offset=0; ; offset+=count)
{
count=ReadBlobBlock(image,buffer);
if (count == 0)
break;
buffer[count]='\0';
if (((ssize_t) count+offset+MagickPathExtent) >= (ssize_t) extent)
{
extent<<=1;
comments=(char *) ResizeQuantumMemory(comments,extent+
MagickPathExtent,sizeof(*comments));
if (comments == (char *) NULL)
ThrowGIFException(ResourceLimitError,
"MemoryAllocationFailed");
}
(void) CopyMagickString(&comments[offset],(char *) buffer,extent-
offset);
}
(void) SetImageProperty(meta_image,"comment",comments,exception);
comments=DestroyString(comments);
break;
}
case 0xff:
{
MagickBooleanType
loop;
/*
Read Netscape Loop extension.
*/
loop=MagickFalse;
if (ReadBlobBlock(image,buffer) != 0)
loop=LocaleNCompare((char *) buffer,"NETSCAPE2.0",11) == 0 ?
MagickTrue : MagickFalse;
if (loop != MagickFalse)
while (ReadBlobBlock(image,buffer) != 0)
{
meta_image->iterations=((size_t) buffer[2] << 8) | buffer[1];
if (meta_image->iterations != 0)
meta_image->iterations++;
}
else
{
char
name[MagickPathExtent];
int
block_length,
info_length,
reserved_length;
MagickBooleanType
i8bim,
icc,
iptc,
magick;
StringInfo
*profile;
unsigned char
*info;
/*
Store GIF application extension as a generic profile.
*/
icc=LocaleNCompare((char *) buffer,"ICCRGBG1012",11) == 0 ?
MagickTrue : MagickFalse;
magick=LocaleNCompare((char *) buffer,"ImageMagick",11) == 0 ?
MagickTrue : MagickFalse;
i8bim=LocaleNCompare((char *) buffer,"MGK8BIM0000",11) == 0 ?
MagickTrue : MagickFalse;
iptc=LocaleNCompare((char *) buffer,"MGKIPTC0000",11) == 0 ?
MagickTrue : MagickFalse;
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Reading GIF application extension");
info=(unsigned char *) AcquireQuantumMemory(255UL,
sizeof(*info));
if (info == (unsigned char *) NULL)
ThrowGIFException(ResourceLimitError,
"MemoryAllocationFailed");
(void) memset(info,0,255UL*sizeof(*info));
reserved_length=255;
for (info_length=0; ; )
{
block_length=(int) ReadBlobBlock(image,&info[info_length]);
if (block_length == 0)
break;
info_length+=block_length;
if (info_length > (reserved_length-255))
{
reserved_length+=4096;
info=(unsigned char *) ResizeQuantumMemory(info,(size_t)
reserved_length,sizeof(*info));
if (info == (unsigned char *) NULL)
{
info=(unsigned char *) RelinquishMagickMemory(info);
ThrowGIFException(ResourceLimitError,
"MemoryAllocationFailed");
}
}
}
profile=BlobToStringInfo(info,(size_t) info_length);
if (profile == (StringInfo *) NULL)
{
info=(unsigned char *) RelinquishMagickMemory(info);
ThrowGIFException(ResourceLimitError,
"MemoryAllocationFailed");
}
if (i8bim != MagickFalse)
(void) CopyMagickString(name,"8bim",sizeof(name));
else if (icc != MagickFalse)
(void) CopyMagickString(name,"icc",sizeof(name));
else if (iptc != MagickFalse)
(void) CopyMagickString(name,"iptc",sizeof(name));
else if (magick != MagickFalse)
{
(void) CopyMagickString(name,"magick",sizeof(name));
meta_image->gamma=StringToDouble((char *) info+6,
(char **) NULL);
}
else
(void) FormatLocaleString(name,sizeof(name),"gif:%.11s",
buffer);
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" profile name=%s",name);
info=(unsigned char *) RelinquishMagickMemory(info);
if (magick != MagickFalse)
profile=DestroyStringInfo(profile);
else
{
if (profiles == (LinkedListInfo *) NULL)
profiles=NewLinkedList(0);
SetStringInfoName(profile,name);
(void) AppendValueToLinkedList(profiles,profile);
}
}
break;
}
default:
{
while (ReadBlobBlock(image,buffer) != 0) ;
break;
}
}
}
if (c != (unsigned char) ',')
continue;
image_count++;
if (image_count != 1)
{
/*
Allocate next image structure.
*/
AcquireNextImage(image_info,image,exception);
if (GetNextImageInList(image) == (Image *) NULL)
{
status=MagickFalse;
break;
}
image=SyncNextImageInList(image);
}
/*
Read image attributes.
*/
meta_image->page.x=(ssize_t) ReadBlobLSBShort(image);
meta_image->page.y=(ssize_t) ReadBlobLSBShort(image);
meta_image->scene=image->scene;
(void) CloneImageProperties(image,meta_image);
DestroyImageProperties(meta_image);
image->storage_class=PseudoClass;
image->compression=LZWCompression;
image->columns=ReadBlobLSBShort(image);
image->rows=ReadBlobLSBShort(image);
image->depth=8;
flag=(unsigned char) ReadBlobByte(image);
image->interlace=BitSet((int) flag,0x40) != 0 ? GIFInterlace : NoInterlace;
local_colors=BitSet((int) flag,0x80) == 0 ? global_colors : one <<
((size_t) (flag & 0x07)+1);
image->colors=local_colors;
if (opacity >= (ssize_t) image->colors)
opacity=(-1);
image->ticks_per_second=100;
image->alpha_trait=opacity >= 0 ? BlendPixelTrait : UndefinedPixelTrait;
if ((image->columns == 0) || (image->rows == 0))
ThrowGIFException(CorruptImageError,"NegativeOrZeroImageSize");
/*
Inititialize colormap.
*/
if (AcquireImageColormap(image,image->colors,exception) == MagickFalse)
ThrowGIFException(ResourceLimitError,"MemoryAllocationFailed");
if (BitSet((int) flag,0x80) == 0)
{
/*
Use global colormap.
*/
p=global_colormap;
for (i=0; i < (ssize_t) image->colors; i++)
{
image->colormap[i].red=(double) ScaleCharToQuantum(*p++);
image->colormap[i].green=(double) ScaleCharToQuantum(*p++);
image->colormap[i].blue=(double) ScaleCharToQuantum(*p++);
if (i == opacity)
{
image->colormap[i].alpha=(double) TransparentAlpha;
image->transparent_color=image->colormap[opacity];
}
}
image->background_color=image->colormap[MagickMin((ssize_t) background,
(ssize_t) image->colors-1)];
}
else
{
unsigned char
*colormap;
/*
Read local colormap.
*/
colormap=(unsigned char *) AcquireQuantumMemory((size_t)
MagickMax(local_colors,256),3UL*sizeof(*colormap));
if (colormap == (unsigned char *) NULL)
ThrowGIFException(ResourceLimitError,"MemoryAllocationFailed");
(void) memset(colormap,0,3*MagickMax(local_colors,256)*
sizeof(*colormap));
count=ReadBlob(image,(3*local_colors)*sizeof(*colormap),colormap);
if (count != (ssize_t) (3*local_colors))
{
colormap=(unsigned char *) RelinquishMagickMemory(colormap);
ThrowGIFException(CorruptImageError,"InsufficientImageDataInFile");
}
p=colormap;
for (i=0; i < (ssize_t) image->colors; i++)
{
image->colormap[i].red=(double) ScaleCharToQuantum(*p++);
image->colormap[i].green=(double) ScaleCharToQuantum(*p++);
image->colormap[i].blue=(double) ScaleCharToQuantum(*p++);
if (i == opacity)
image->colormap[i].alpha=(double) TransparentAlpha;
}
colormap=(unsigned char *) RelinquishMagickMemory(colormap);
}
if (image->gamma == 1.0)
{
for (i=0; i < (ssize_t) image->colors; i++)
if (IsPixelInfoGray(image->colormap+i) == MagickFalse)
break;
(void) SetImageColorspace(image,i == (ssize_t) image->colors ?
GRAYColorspace : RGBColorspace,exception);
}
if ((image_info->ping != MagickFalse) && (image_info->number_scenes != 0))
if (image->scene >= (image_info->scene+image_info->number_scenes-1))
break;
status=SetImageExtent(image,image->columns,image->rows,exception);
if (status == MagickFalse)
{
if (profiles != (LinkedListInfo *) NULL)
profiles=DestroyLinkedList(profiles,DestroyGIFProfile);
global_colormap=(unsigned char *) RelinquishMagickMemory(
global_colormap);
meta_image=DestroyImage(meta_image);
return(DestroyImageList(image));
}
/*
Decode image.
*/
if (image_info->ping != MagickFalse)
status=PingGIFImage(image,exception);
else
status=DecodeImage(image,opacity,exception);
if ((image_info->ping == MagickFalse) && (status == MagickFalse))
ThrowGIFException(CorruptImageError,"CorruptImage");
if (profiles != (LinkedListInfo *) NULL)
{
StringInfo
*profile;
/*
Set image profiles.
*/
ResetLinkedListIterator(profiles);
profile=(StringInfo *) GetNextValueInLinkedList(profiles);
while (profile != (StringInfo *) NULL)
{
(void) SetImageProfile(image,GetStringInfoName(profile),profile,
exception);
profile=(StringInfo *) GetNextValueInLinkedList(profiles);
}
profiles=DestroyLinkedList(profiles,DestroyGIFProfile);
}
duration+=image->delay*image->iterations;
if (image_info->number_scenes != 0)
if (image->scene >= (image_info->scene+image_info->number_scenes-1))
break;
opacity=(-1);
status=SetImageProgress(image,LoadImageTag,(MagickOffsetType)
image->scene-1,image->scene);
if (status == MagickFalse)
break;
}
image->duration=duration;
if (profiles != (LinkedListInfo *) NULL)
profiles=DestroyLinkedList(profiles,DestroyGIFProfile);
meta_image=DestroyImage(meta_image);
global_colormap=(unsigned char *) RelinquishMagickMemory(global_colormap);
if ((image->columns == 0) || (image->rows == 0))
ThrowReaderException(CorruptImageError,"NegativeOrZeroImageSize");
(void) CloseBlob(image);
if (status == MagickFalse)
return(DestroyImageList(image));
return(GetFirstImageInList(image));
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% R e g i s t e r G I F I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% RegisterGIFImage() adds properties for the GIF image format to
% the list of supported formats. The properties 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 RegisterGIFImage method is:
%
% size_t RegisterGIFImage(void)
%
*/
ModuleExport size_t RegisterGIFImage(void)
{
MagickInfo
*entry;
entry=AcquireMagickInfo("GIF","GIF",
"CompuServe graphics interchange format");
entry->decoder=(DecodeImageHandler *) ReadGIFImage;
entry->encoder=(EncodeImageHandler *) WriteGIFImage;
entry->magick=(IsImageFormatHandler *) IsGIF;
entry->mime_type=ConstantString("image/gif");
(void) RegisterMagickInfo(entry);
entry=AcquireMagickInfo("GIF","GIF87",
"CompuServe graphics interchange format");
entry->decoder=(DecodeImageHandler *) ReadGIFImage;
entry->encoder=(EncodeImageHandler *) WriteGIFImage;
entry->magick=(IsImageFormatHandler *) IsGIF;
entry->flags^=CoderAdjoinFlag;
entry->version=ConstantString("version 87a");
entry->mime_type=ConstantString("image/gif");
(void) RegisterMagickInfo(entry);
return(MagickImageCoderSignature);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% U n r e g i s t e r G I F I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% UnregisterGIFImage() removes format registrations made by the
% GIF module from the list of supported formats.
%
% The format of the UnregisterGIFImage method is:
%
% UnregisterGIFImage(void)
%
*/
ModuleExport void UnregisterGIFImage(void)
{
(void) UnregisterMagickInfo("GIF");
(void) UnregisterMagickInfo("GIF87");
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% W r i t e G I F I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% WriteGIFImage() writes an image to a file in the Compuserve Graphics
% image format.
%
% The format of the WriteGIFImage method is:
%
% MagickBooleanType WriteGIFImage(const ImageInfo *image_info,
% Image *image,ExceptionInfo *exception)
%
% A description of each parameter follows.
%
% o image_info: the image info.
%
% o image: The image.
%
% o exception: return any errors or warnings in this structure.
%
*/
static MagickBooleanType WriteGIFImage(const ImageInfo *image_info,Image *image,
ExceptionInfo *exception)
{
int
c;
ImageInfo
*write_info;
MagickBooleanType
status;
MagickOffsetType
scene;
RectangleInfo
page;
register ssize_t
i;
register unsigned char
*q;
size_t
bits_per_pixel,
delay,
imageListLength,
length,
one;
ssize_t
j,
opacity;
unsigned char
*colormap,
*global_colormap;
/*
Open output image file.
*/
assert(image_info != (const ImageInfo *) NULL);
assert(image_info->signature == MagickCoreSignature);
assert(image != (Image *) NULL);
assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickCoreSignature);
status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
if (status == MagickFalse)
return(status);
/*
Allocate colormap.
*/
global_colormap=(unsigned char *) AcquireQuantumMemory(768UL,
sizeof(*global_colormap));
colormap=(unsigned char *) AcquireQuantumMemory(768UL,sizeof(*colormap));
if ((global_colormap == (unsigned char *) NULL) ||
(colormap == (unsigned char *) NULL))
{
if (global_colormap != (unsigned char *) NULL)
global_colormap=(unsigned char *) RelinquishMagickMemory(
global_colormap);
if (colormap != (unsigned char *) NULL)
colormap=(unsigned char *) RelinquishMagickMemory(colormap);
ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
}
for (i=0; i < 768; i++)
colormap[i]=(unsigned char) 0;
/*
Write GIF header.
*/
write_info=CloneImageInfo(image_info);
if (LocaleCompare(write_info->magick,"GIF87") != 0)
(void) WriteBlob(image,6,(unsigned char *) "GIF89a");
else
{
(void) WriteBlob(image,6,(unsigned char *) "GIF87a");
write_info->adjoin=MagickFalse;
}
/*
Determine image bounding box.
*/
page.width=image->columns;
if (image->page.width > page.width)
page.width=image->page.width;
page.height=image->rows;
if (image->page.height > page.height)
page.height=image->page.height;
page.x=image->page.x;
page.y=image->page.y;
(void) WriteBlobLSBShort(image,(unsigned short) page.width);
(void) WriteBlobLSBShort(image,(unsigned short) page.height);
/*
Write images to file.
*/
if ((write_info->adjoin != MagickFalse) &&
(GetNextImageInList(image) != (Image *) NULL))
write_info->interlace=NoInterlace;
scene=0;
one=1;
imageListLength=GetImageListLength(image);
do
{
(void) TransformImageColorspace(image,sRGBColorspace,exception);
opacity=(-1);
if (IsImageOpaque(image,exception) != MagickFalse)
{
if ((image->storage_class == DirectClass) || (image->colors > 256))
(void) SetImageType(image,PaletteType,exception);
}
else
{
double
alpha,
beta;
/*
Identify transparent colormap index.
*/
if ((image->storage_class == DirectClass) || (image->colors > 256))
(void) SetImageType(image,PaletteBilevelAlphaType,exception);
for (i=0; i < (ssize_t) image->colors; i++)
if (image->colormap[i].alpha != OpaqueAlpha)
{
if (opacity < 0)
{
opacity=i;
continue;
}
alpha=fabs(image->colormap[i].alpha-TransparentAlpha);
beta=fabs(image->colormap[opacity].alpha-TransparentAlpha);
if (alpha < beta)
opacity=i;
}
if (opacity == -1)
{
(void) SetImageType(image,PaletteBilevelAlphaType,exception);
for (i=0; i < (ssize_t) image->colors; i++)
if (image->colormap[i].alpha != OpaqueAlpha)
{
if (opacity < 0)
{
opacity=i;
continue;
}
alpha=fabs(image->colormap[i].alpha-TransparentAlpha);
beta=fabs(image->colormap[opacity].alpha-TransparentAlpha);
if (alpha < beta)
opacity=i;
}
}
if (opacity >= 0)
{
image->colormap[opacity].red=image->transparent_color.red;
image->colormap[opacity].green=image->transparent_color.green;
image->colormap[opacity].blue=image->transparent_color.blue;
}
}
if ((image->storage_class == DirectClass) || (image->colors > 256))
ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
for (bits_per_pixel=1; bits_per_pixel < 8; bits_per_pixel++)
if ((one << bits_per_pixel) >= image->colors)
break;
q=colormap;
for (i=0; i < (ssize_t) image->colors; i++)
{
*q++=ScaleQuantumToChar(ClampToQuantum(image->colormap[i].red));
*q++=ScaleQuantumToChar(ClampToQuantum(image->colormap[i].green));
*q++=ScaleQuantumToChar(ClampToQuantum(image->colormap[i].blue));
}
for ( ; i < (ssize_t) (one << bits_per_pixel); i++)
{
*q++=(unsigned char) 0x0;
*q++=(unsigned char) 0x0;
*q++=(unsigned char) 0x0;
}
if ((GetPreviousImageInList(image) == (Image *) NULL) ||
(write_info->adjoin == MagickFalse))
{
/*
Write global colormap.
*/
c=0x80;
c|=(8-1) << 4; /* color resolution */
c|=(bits_per_pixel-1); /* size of global colormap */
(void) WriteBlobByte(image,(unsigned char) c);
for (j=0; j < (ssize_t) image->colors; j++)
if (IsPixelInfoEquivalent(&image->background_color,image->colormap+j))
break;
(void) WriteBlobByte(image,(unsigned char)
(j == (ssize_t) image->colors ? 0 : j)); /* background color */
(void) WriteBlobByte(image,(unsigned char) 0x00); /* reserved */
length=(size_t) (3*(one << bits_per_pixel));
(void) WriteBlob(image,length,colormap);
for (j=0; j < 768; j++)
global_colormap[j]=colormap[j];
}
if (LocaleCompare(write_info->magick,"GIF87") != 0)
{
const char
*value;
/*
Write graphics control extension.
*/
(void) WriteBlobByte(image,(unsigned char) 0x21);
(void) WriteBlobByte(image,(unsigned char) 0xf9);
(void) WriteBlobByte(image,(unsigned char) 0x04);
c=image->dispose << 2;
if (opacity >= 0)
c|=0x01;
(void) WriteBlobByte(image,(unsigned char) c);
delay=(size_t) (100*image->delay/MagickMax((size_t)
image->ticks_per_second,1));
(void) WriteBlobLSBShort(image,(unsigned short) delay);
(void) WriteBlobByte(image,(unsigned char) (opacity >= 0 ? opacity :
0));
(void) WriteBlobByte(image,(unsigned char) 0x00);
value=GetImageProperty(image,"comment",exception);
if (value != (const char *) NULL)
{
register const char
*p;
size_t
count;
/*
Write comment extension.
*/
(void) WriteBlobByte(image,(unsigned char) 0x21);
(void) WriteBlobByte(image,(unsigned char) 0xfe);
for (p=value; *p != '\0'; )
{
count=MagickMin(strlen(p),255);
(void) WriteBlobByte(image,(unsigned char) count);
for (i=0; i < (ssize_t) count; i++)
(void) WriteBlobByte(image,(unsigned char) *p++);
}
(void) WriteBlobByte(image,(unsigned char) 0x00);
}
if ((GetPreviousImageInList(image) == (Image *) NULL) &&
(GetNextImageInList(image) != (Image *) NULL) &&
(image->iterations != 1))
{
/*
Write Netscape Loop extension.
*/
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Writing GIF Extension %s","NETSCAPE2.0");
(void) WriteBlobByte(image,(unsigned char) 0x21);
(void) WriteBlobByte(image,(unsigned char) 0xff);
(void) WriteBlobByte(image,(unsigned char) 0x0b);
(void) WriteBlob(image,11,(unsigned char *) "NETSCAPE2.0");
(void) WriteBlobByte(image,(unsigned char) 0x03);
(void) WriteBlobByte(image,(unsigned char) 0x01);
(void) WriteBlobLSBShort(image,(unsigned short) (image->iterations ?
image->iterations-1 : 0));
(void) WriteBlobByte(image,(unsigned char) 0x00);
}
if ((image->gamma != 1.0f/2.2f))
{
char
attributes[MagickPathExtent];
ssize_t
count;
/*
Write ImageMagick extension.
*/
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Writing GIF Extension %s","ImageMagick");
(void) WriteBlobByte(image,(unsigned char) 0x21);
(void) WriteBlobByte(image,(unsigned char) 0xff);
(void) WriteBlobByte(image,(unsigned char) 0x0b);
(void) WriteBlob(image,11,(unsigned char *) "ImageMagick");
count=FormatLocaleString(attributes,MagickPathExtent,"gamma=%g",
image->gamma);
(void) WriteBlobByte(image,(unsigned char) count);
(void) WriteBlob(image,(size_t) count,(unsigned char *) attributes);
(void) WriteBlobByte(image,(unsigned char) 0x00);
}
ResetImageProfileIterator(image);
for ( ; ; )
{
char
*name;
const StringInfo
*profile;
name=GetNextImageProfile(image);
if (name == (const char *) NULL)
break;
profile=GetImageProfile(image,name);
if (profile != (StringInfo *) NULL)
{
if ((LocaleCompare(name,"ICC") == 0) ||
(LocaleCompare(name,"ICM") == 0) ||
(LocaleCompare(name,"IPTC") == 0) ||
(LocaleCompare(name,"8BIM") == 0) ||
(LocaleNCompare(name,"gif:",4) == 0))
{
ssize_t
offset;
unsigned char
*datum;
datum=GetStringInfoDatum(profile);
length=GetStringInfoLength(profile);
(void) WriteBlobByte(image,(unsigned char) 0x21);
(void) WriteBlobByte(image,(unsigned char) 0xff);
(void) WriteBlobByte(image,(unsigned char) 0x0b);
if ((LocaleCompare(name,"ICC") == 0) ||
(LocaleCompare(name,"ICM") == 0))
{
/*
Write ICC extension.
*/
(void) WriteBlob(image,11,(unsigned char *) "ICCRGBG1012");
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Writing GIF Extension %s","ICCRGBG1012");
}
else
if ((LocaleCompare(name,"IPTC") == 0))
{
/*
Write IPTC extension.
*/
(void) WriteBlob(image,11,(unsigned char *) "MGKIPTC0000");
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Writing GIF Extension %s","MGKIPTC0000");
}
else
if ((LocaleCompare(name,"8BIM") == 0))
{
/*
Write 8BIM extension.
*/
(void) WriteBlob(image,11,(unsigned char *)
"MGK8BIM0000");
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Writing GIF Extension %s","MGK8BIM0000");
}
else
{
char
extension[MagickPathExtent];
/*
Write generic extension.
*/
(void) CopyMagickString(extension,name+4,
sizeof(extension));
(void) WriteBlob(image,11,(unsigned char *) extension);
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Writing GIF Extension %s",name);
}
offset=0;
while ((ssize_t) length > offset)
{
size_t
block_length;
if ((length-offset) < 255)
block_length=length-offset;
else
block_length=255;
(void) WriteBlobByte(image,(unsigned char) block_length);
(void) WriteBlob(image,(size_t) block_length,datum+offset);
offset+=(ssize_t) block_length;
}
(void) WriteBlobByte(image,(unsigned char) 0x00);
}
}
}
}
(void) WriteBlobByte(image,','); /* image separator */
/*
Write the image header.
*/
page.x=image->page.x;
page.y=image->page.y;
if ((image->page.width != 0) && (image->page.height != 0))
page=image->page;
(void) WriteBlobLSBShort(image,(unsigned short) (page.x < 0 ? 0 : page.x));
(void) WriteBlobLSBShort(image,(unsigned short) (page.y < 0 ? 0 : page.y));
(void) WriteBlobLSBShort(image,(unsigned short) image->columns);
(void) WriteBlobLSBShort(image,(unsigned short) image->rows);
c=0x00;
if (write_info->interlace != NoInterlace)
c|=0x40; /* pixel data is interlaced */
for (j=0; j < (ssize_t) (3*image->colors); j++)
if (colormap[j] != global_colormap[j])
break;
if (j == (ssize_t) (3*image->colors))
(void) WriteBlobByte(image,(unsigned char) c);
else
{
c|=0x80;
c|=(bits_per_pixel-1); /* size of local colormap */
(void) WriteBlobByte(image,(unsigned char) c);
length=(size_t) (3*(one << bits_per_pixel));
(void) WriteBlob(image,length,colormap);
}
/*
Write the image data.
*/
c=(int) MagickMax(bits_per_pixel,2);
(void) WriteBlobByte(image,(unsigned char) c);
status=EncodeImage(write_info,image,(size_t) MagickMax(bits_per_pixel,2)+1,
exception);
if (status == MagickFalse)
{
global_colormap=(unsigned char *) RelinquishMagickMemory(
global_colormap);
colormap=(unsigned char *) RelinquishMagickMemory(colormap);
write_info=DestroyImageInfo(write_info);
ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
}
(void) WriteBlobByte(image,(unsigned char) 0x00);
if (GetNextImageInList(image) == (Image *) NULL)
break;
image=SyncNextImageInList(image);
scene++;
status=SetImageProgress(image,SaveImagesTag,scene,imageListLength);
if (status == MagickFalse)
break;
} while (write_info->adjoin != MagickFalse);
(void) WriteBlobByte(image,';'); /* terminator */
global_colormap=(unsigned char *) RelinquishMagickMemory(global_colormap);
colormap=(unsigned char *) RelinquishMagickMemory(colormap);
write_info=DestroyImageInfo(write_info);
(void) CloseBlob(image);
return(MagickTrue);
}