mirror of https://github.com/lianthony/NT4.0
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.
1152 lines
29 KiB
1152 lines
29 KiB
/*
|
|
Enhanced NCSA Mosaic from Spyglass
|
|
"Guitar"
|
|
|
|
Copyright 1994 Spyglass, Inc.
|
|
All Rights Reserved
|
|
|
|
*/
|
|
|
|
/* +-------------------------------------------------------------------+ */
|
|
/* | Copyright 1990, David Koblas. | */
|
|
/* | Permission to use, copy, modify, and distribute this software | */
|
|
/* | and its documentation for any purpose and without fee is hereby | */
|
|
/* | granted, provided that the above copyright notice appear in all | */
|
|
/* | copies and that both that copyright notice and this permission | */
|
|
/* | notice appear in supporting documentation. This software is | */
|
|
/* | provided "as is" without express or implied warranty. | */
|
|
/* +-------------------------------------------------------------------+ */
|
|
|
|
#include "all.h"
|
|
#ifdef FEATURE_IMG_THREADS
|
|
#include "safestrm.h"
|
|
#include "decoder.h"
|
|
#endif
|
|
|
|
|
|
#define NUM_IMPORTANT_COLORS 256
|
|
|
|
#define MAXCOLORMAPSIZE 256
|
|
|
|
#define TRUE 1
|
|
#define FALSE 0
|
|
|
|
#define CM_RED 0
|
|
#define CM_GREEN 1
|
|
#define CM_BLUE 2
|
|
|
|
#define MAX_LWZ_BITS 12
|
|
|
|
#define INTERLACE 0x40
|
|
#define LOCALCOLORMAP 0x80
|
|
#define BitSet(byte, bit) (((byte) & (bit)) == (bit))
|
|
|
|
#define LM_to_uint(a,b) ((((unsigned int) b)<<8)|((unsigned int)a))
|
|
|
|
typedef struct _GIFSCREEN
|
|
{
|
|
unsigned long Width;
|
|
unsigned long Height;
|
|
unsigned char ColorMap[3][MAXCOLORMAPSIZE];
|
|
unsigned long BitPixel;
|
|
unsigned long ColorResolution;
|
|
unsigned long Background;
|
|
unsigned long AspectRatio;
|
|
}
|
|
GIFSCREEN;
|
|
|
|
typedef struct _GIF89
|
|
{
|
|
long transparent;
|
|
long delayTime;
|
|
long inputFlag;
|
|
long disposal;
|
|
}
|
|
GIF89;
|
|
|
|
|
|
typedef struct _GIFINFO
|
|
{
|
|
#ifdef FEATURE_IMG_THREADS
|
|
PDECODER pdecoder;
|
|
PSAFESTREAM pSSInput;
|
|
#endif
|
|
unsigned char *src;
|
|
GIF89 Gif89;
|
|
long lGifLoc;
|
|
long ZeroDataBlock;
|
|
|
|
/*
|
|
** Pulled out of nextCode
|
|
*/
|
|
long curbit, lastbit, get_done;
|
|
long last_byte;
|
|
long return_clear;
|
|
/*
|
|
** Out of nextLWZ
|
|
*/
|
|
long stack[(1 << (MAX_LWZ_BITS)) * 2], *sp;
|
|
long code_size, set_code_size;
|
|
long max_code, max_code_size;
|
|
long clear_code, end_code;
|
|
|
|
/*
|
|
* Were statics in procedures
|
|
*/
|
|
unsigned char buf[280];
|
|
long table[2][(1 << MAX_LWZ_BITS)];
|
|
long firstcode, oldcode;
|
|
|
|
} GIFINFO,*PGIFINFO;
|
|
|
|
static long ReadColorMap(PGIFINFO pGifInfo, long number, unsigned char buffer[3][MAXCOLORMAPSIZE]);
|
|
static long DoExtension(PGIFINFO pGifInfo, long label);
|
|
static long GetDataBlock(PGIFINFO pGifInfo, unsigned char *buf);
|
|
static unsigned char *ReadImage(PGIFINFO pGifInfo, long len, long height, PALETTEENTRY * colrs, long cmapSize, unsigned char cmap[3][MAXCOLORMAPSIZE], long interlace, long ignore);
|
|
static BOOL ReadOK(PGIFINFO pGifInfo, unsigned char *buffer, long len);
|
|
static char *FlipAndPadBitmap(WORD wWidth, WORD wHeight, char *data);
|
|
static WORD FlipBitmapVertical(WORD width, WORD height, char *flipmap);
|
|
|
|
|
|
|
|
#ifdef FEATURE_IMG_THREADS
|
|
static BOOL ReadOK(PGIFINFO pGifInfo, unsigned char *buffer, long len)
|
|
{
|
|
if (pGifInfo->src == NULL)
|
|
return cbSS_Read(pGifInfo->pSSInput,buffer,len) == len;
|
|
|
|
memcpy(buffer, pGifInfo->src + pGifInfo->lGifLoc, (int) len);
|
|
pGifInfo->lGifLoc += len;
|
|
return (TRUE);
|
|
}
|
|
#else
|
|
static BOOL ReadOK(PGIFINFO pGifInfo, unsigned char *buffer, long len)
|
|
{
|
|
memcpy(buffer, pGifInfo->src + pGifInfo->lGifLoc, (int) len);
|
|
pGifInfo->lGifLoc += len;
|
|
return (TRUE);
|
|
}
|
|
#endif
|
|
|
|
#ifdef FEATURE_IMG_THREADS
|
|
unsigned char *ReadGIFMaster(void *pdecoderObject,unsigned char *pMem, long *w, long *h, PALETTEENTRY * colrs, long *bg);
|
|
|
|
unsigned char *ReadGIFObject(void *pdecoderObject, long *w, long *h, PALETTEENTRY * colrs, long *bg)
|
|
{
|
|
return ReadGIFMaster(pdecoderObject,NULL,w,h,colrs,bg);
|
|
}
|
|
unsigned char *ReadGIFData(unsigned char *pMem, long *w, long *h, PALETTEENTRY * colrs, long *bg)\
|
|
{
|
|
return ReadGIFMaster(NULL,pMem,w,h,colrs,bg);
|
|
}
|
|
|
|
unsigned char *ReadGIFMaster(void *pdecoderObject, unsigned char *pMem, long *w, long *h, PALETTEENTRY * colrs, long *bg)
|
|
#else
|
|
unsigned char *ReadGIF(unsigned char *pMem, long *w, long *h, PALETTEENTRY * colrs, long *bg)
|
|
#endif
|
|
{
|
|
unsigned char buf[16];
|
|
unsigned char c;
|
|
unsigned char localColorMap[3][MAXCOLORMAPSIZE];
|
|
long useGlobalColormap;
|
|
long imageCount = 0;
|
|
char version[4];
|
|
long imageNumber = 1;
|
|
unsigned char *image = NULL;
|
|
unsigned long i;
|
|
PGIFINFO pGifInfo = NULL;
|
|
GIFSCREEN GifScreen;
|
|
long bitPixel;
|
|
|
|
pGifInfo = (PGIFINFO) GTR_CALLOC(1,sizeof(GIFINFO));
|
|
if (pGifInfo == NULL)
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("Cannot allocate space for GifInfo block\n"));
|
|
goto exitPoint;
|
|
}
|
|
pGifInfo->ZeroDataBlock = 0;
|
|
#ifdef FEATURE_IMG_THREADS
|
|
if (pdecoderObject)
|
|
{
|
|
pGifInfo->src = NULL;
|
|
pGifInfo->pdecoder = pdecoderObject;
|
|
pGifInfo->pSSInput = pDC_GetStream(pdecoderObject);
|
|
} else
|
|
pGifInfo->src = pMem;
|
|
#else
|
|
pGifInfo->src = pMem;
|
|
#endif
|
|
/*
|
|
* Initialize GIF89 extensions
|
|
*/
|
|
pGifInfo->Gif89.transparent = -1;
|
|
pGifInfo->Gif89.delayTime = -1;
|
|
pGifInfo->Gif89.inputFlag = -1;
|
|
pGifInfo->Gif89.disposal = 0;
|
|
pGifInfo->lGifLoc = 0;
|
|
|
|
if (!ReadOK(pGifInfo, buf, 6))
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("GIF: error reading magic number\n"));
|
|
goto exitPoint;
|
|
}
|
|
|
|
if (strncmp((char *) buf, "GIF", 3) != 0)
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("GIF: not a GIF file\n"));
|
|
goto exitPoint;
|
|
}
|
|
|
|
version[0] = *(buf + 3);
|
|
version[1] = *(buf + 4);
|
|
version[2] = *(buf + 5);
|
|
//lstrncpy(version, buf + 3, 3);
|
|
version[3] = '\0';
|
|
|
|
if ((lstrcmp(version, "87a") != 0) && (lstrcmp(version, "89a") != 0))
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("GIF: bad version number, not 87a or 89a\n"));
|
|
goto exitPoint;
|
|
}
|
|
|
|
if (!ReadOK(pGifInfo, buf, 7))
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("GIF: failed to read screen descriptor\n"));
|
|
goto exitPoint;
|
|
}
|
|
|
|
GifScreen.Width = LM_to_uint(buf[0], buf[1]);
|
|
GifScreen.Height = LM_to_uint(buf[2], buf[3]);
|
|
GifScreen.BitPixel = 2 << (buf[4] & 0x07);
|
|
GifScreen.ColorResolution = (((buf[4] & 0x70) >> 3) + 1);
|
|
GifScreen.Background = buf[5];
|
|
GifScreen.AspectRatio = buf[6];
|
|
|
|
if (BitSet(buf[4], LOCALCOLORMAP))
|
|
{ /* Global Colormap */
|
|
int scale = 65536 / MAXCOLORMAPSIZE;
|
|
|
|
if (ReadColorMap(pGifInfo, GifScreen.BitPixel, GifScreen.ColorMap))
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("error reading global colormap\n"));
|
|
goto exitPoint;
|
|
}
|
|
for (i = 0; i < GifScreen.BitPixel; i++)
|
|
{
|
|
int tmp;
|
|
|
|
tmp = (BYTE) (GifScreen.ColorMap[0][i]);
|
|
colrs[i].peRed = (BYTE) (GifScreen.ColorMap[0][i]);
|
|
colrs[i].peGreen = (BYTE) (GifScreen.ColorMap[1][i]);
|
|
colrs[i].peBlue = (BYTE) (GifScreen.ColorMap[2][i]);
|
|
colrs[i].peFlags = (BYTE) 0;
|
|
}
|
|
for (i = GifScreen.BitPixel; i < MAXCOLORMAPSIZE; i++)
|
|
{
|
|
colrs[i].peRed = (BYTE) 0;
|
|
colrs[i].peGreen = (BYTE) 0;
|
|
colrs[i].peBlue = (BYTE) 0;
|
|
colrs[i].peFlags = (BYTE) 0;
|
|
}
|
|
|
|
}
|
|
|
|
if (GifScreen.AspectRatio != 0 && GifScreen.AspectRatio != 49)
|
|
{
|
|
float r;
|
|
r = ((float) (GifScreen.AspectRatio) + (float) 15.0) / (float) 64.0;
|
|
XX_DMsg(DBG_IMAGE, ("Warning: non-square pixels!\n"));
|
|
}
|
|
|
|
while (image == NULL)
|
|
{
|
|
if (!ReadOK(pGifInfo, &c, 1))
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("EOF / read error on image data\n"));
|
|
goto exitPoint;
|
|
}
|
|
|
|
if (c == ';')
|
|
{ /* GIF terminator */
|
|
if (imageCount < imageNumber)
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("No images found in file\n"));
|
|
goto exitPoint;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (c == '!')
|
|
{ /* Extension */
|
|
if (!ReadOK(pGifInfo, &c, 1))
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("EOF / read error on extension function code\n"));
|
|
goto exitPoint;
|
|
}
|
|
DoExtension(pGifInfo, c);
|
|
continue;
|
|
}
|
|
|
|
if (c != ',')
|
|
{ /* Not a valid start character */
|
|
break;
|
|
}
|
|
|
|
++imageCount;
|
|
|
|
if (!ReadOK(pGifInfo, buf, 9))
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("couldn't read left/top/width/height\n"));
|
|
goto exitPoint;
|
|
}
|
|
|
|
useGlobalColormap = !BitSet(buf[8], LOCALCOLORMAP);
|
|
|
|
bitPixel = 1 << ((buf[8] & 0x07) + 1);
|
|
|
|
/*
|
|
* We only want to set width and height for the imageNumber
|
|
* we are requesting.
|
|
*/
|
|
if (imageCount == imageNumber)
|
|
{
|
|
*w = LM_to_uint(buf[4], buf[5]);
|
|
*h = LM_to_uint(buf[6], buf[7]);
|
|
*bg = pGifInfo->Gif89.transparent;
|
|
#ifdef FEATURE_IMG_THREADS
|
|
if (pGifInfo->pdecoder) DC_PostStatus(pGifInfo->pdecoder,DC_WHKnown);
|
|
#endif
|
|
}
|
|
|
|
if (!useGlobalColormap)
|
|
{
|
|
if (ReadColorMap(pGifInfo, bitPixel, localColorMap))
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("error reading local colormap\n"));
|
|
goto exitPoint;
|
|
}
|
|
if (imageCount == imageNumber)
|
|
{
|
|
image = ReadImage(pGifInfo, LM_to_uint(buf[4], buf[5]),
|
|
LM_to_uint(buf[6], buf[7]), colrs, bitPixel, localColorMap,
|
|
BitSet(buf[8], INTERLACE), imageCount != imageNumber);
|
|
}
|
|
else
|
|
{
|
|
unsigned char *tdata;
|
|
|
|
tdata = ReadImage(pGifInfo, LM_to_uint(buf[4], buf[5]),
|
|
LM_to_uint(buf[6], buf[7]), colrs, bitPixel, localColorMap,
|
|
BitSet(buf[8], INTERLACE), imageCount != imageNumber);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* We only want to set the data for the
|
|
* imageNumber we are requesting.
|
|
*/
|
|
if (imageCount == imageNumber)
|
|
{
|
|
image = ReadImage(pGifInfo, LM_to_uint(buf[4], buf[5]),
|
|
LM_to_uint(buf[6], buf[7]), colrs, GifScreen.BitPixel, GifScreen.ColorMap,
|
|
BitSet(buf[8], INTERLACE), imageCount != imageNumber);
|
|
}
|
|
else
|
|
{
|
|
unsigned char *tdata;
|
|
|
|
tdata = ReadImage(pGifInfo, LM_to_uint(buf[4], buf[5]),
|
|
LM_to_uint(buf[6], buf[7]), colrs, GifScreen.BitPixel, GifScreen.ColorMap,
|
|
BitSet(buf[8], INTERLACE), imageCount != imageNumber);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
exitPoint:
|
|
if (pGifInfo) GTR_FREE(pGifInfo);
|
|
return image;
|
|
}
|
|
|
|
static long ReadColorMap(PGIFINFO pGifInfo, long number, unsigned char buffer[3][MAXCOLORMAPSIZE])
|
|
{
|
|
long i;
|
|
unsigned char rgb[3];
|
|
|
|
for (i = 0; i < number; ++i)
|
|
{
|
|
if (!ReadOK(pGifInfo, rgb, sizeof(rgb)))
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("bad colormap\n"));
|
|
return (TRUE);
|
|
}
|
|
buffer[CM_RED][i] = rgb[0];
|
|
buffer[CM_GREEN][i] = rgb[1];
|
|
buffer[CM_BLUE][i] = rgb[2];
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static long DoExtension(PGIFINFO pGifInfo, long label)
|
|
{
|
|
unsigned char buf[256];
|
|
int count;
|
|
// CMF /1/25/95 removed dead code
|
|
// char *str;
|
|
|
|
switch (label)
|
|
{
|
|
case 0x01: /* Plain Text Extension */
|
|
// CMF str = "Plain Text Extension";
|
|
break;
|
|
case 0xff: /* Application Extension */
|
|
// CMF str = "Application Extension";
|
|
break;
|
|
case 0xfe: /* Comment Extension */
|
|
// CMF str = "Comment Extension";
|
|
while (GetDataBlock(pGifInfo, (unsigned char *) buf) > 0)
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("GIF comment: %s\n", buf));
|
|
}
|
|
return FALSE;
|
|
case 0xf9: /* Graphic Control Extension */
|
|
// CMF str = "Graphic Control Extension";
|
|
count = GetDataBlock(pGifInfo, (unsigned char *) buf);
|
|
if (count >= 3)
|
|
{
|
|
pGifInfo->Gif89.disposal = (buf[0] >> 2) & 0x7;
|
|
pGifInfo->Gif89.inputFlag = (buf[0] >> 1) & 0x1;
|
|
pGifInfo->Gif89.delayTime = LM_to_uint(buf[1], buf[2]);
|
|
if ((buf[0] & 0x1) != 0)
|
|
pGifInfo->Gif89.transparent = buf[3];
|
|
}
|
|
while (GetDataBlock(pGifInfo, (unsigned char *) buf) > 0)
|
|
;
|
|
return FALSE;
|
|
default:
|
|
// CMF str = buf;
|
|
sprintf(buf, "UNKNOWN (0x%02x)", label);
|
|
break;
|
|
}
|
|
|
|
while (GetDataBlock(pGifInfo, (unsigned char *) buf) > 0)
|
|
;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static long
|
|
GetDataBlock(PGIFINFO pGifInfo, unsigned char *buf)
|
|
{
|
|
unsigned char count;
|
|
|
|
count = 0;
|
|
if (!ReadOK(pGifInfo, &count, 1))
|
|
{
|
|
return -1;
|
|
}
|
|
pGifInfo->ZeroDataBlock = count == 0;
|
|
|
|
if ((count != 0) && (!ReadOK(pGifInfo, buf, count)))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return ((long) count);
|
|
}
|
|
|
|
|
|
static void initLWZ(PGIFINFO pGifInfo,long input_code_size)
|
|
{
|
|
|
|
pGifInfo->set_code_size = input_code_size;
|
|
pGifInfo->code_size = pGifInfo->set_code_size + 1;
|
|
pGifInfo->clear_code = 1 << pGifInfo->set_code_size;
|
|
pGifInfo->end_code = pGifInfo->clear_code + 1;
|
|
pGifInfo->max_code_size = 2 * pGifInfo->clear_code;
|
|
pGifInfo->max_code = pGifInfo->clear_code + 2;
|
|
|
|
pGifInfo->curbit = pGifInfo->lastbit = 0;
|
|
pGifInfo->last_byte = 2;
|
|
pGifInfo->get_done = FALSE;
|
|
|
|
pGifInfo->return_clear = TRUE;
|
|
|
|
pGifInfo->sp = pGifInfo->stack;
|
|
}
|
|
|
|
static long nextCode(PGIFINFO pGifInfo, long code_size)
|
|
{
|
|
static const long maskTbl[16] =
|
|
{
|
|
0x0000, 0x0001, 0x0003, 0x0007,
|
|
0x000f, 0x001f, 0x003f, 0x007f,
|
|
0x00ff, 0x01ff, 0x03ff, 0x07ff,
|
|
0x0fff, 0x1fff, 0x3fff, 0x7fff,
|
|
};
|
|
long i, j, ret, end;
|
|
unsigned char *buf = &pGifInfo->buf[0];
|
|
|
|
if (pGifInfo->return_clear)
|
|
{
|
|
pGifInfo->return_clear = FALSE;
|
|
return pGifInfo->clear_code;
|
|
}
|
|
|
|
end = pGifInfo->curbit + code_size;
|
|
|
|
if (end >= pGifInfo->lastbit)
|
|
{
|
|
long count;
|
|
|
|
if (pGifInfo->get_done)
|
|
{
|
|
return -1;
|
|
}
|
|
buf[0] = buf[pGifInfo->last_byte - 2];
|
|
buf[1] = buf[pGifInfo->last_byte - 1];
|
|
|
|
if ((count = GetDataBlock(pGifInfo, &buf[2])) == 0)
|
|
pGifInfo->get_done = TRUE;
|
|
if (count < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
pGifInfo->last_byte = 2 + count;
|
|
pGifInfo->curbit = (pGifInfo->curbit - pGifInfo->lastbit) + 16;
|
|
pGifInfo->lastbit = (2 + count) * 8;
|
|
|
|
end = pGifInfo->curbit + code_size;
|
|
}
|
|
|
|
j = end / 8;
|
|
i = pGifInfo->curbit / 8;
|
|
|
|
if (i == j)
|
|
ret = buf[i];
|
|
else if (i + 1 == j)
|
|
ret = buf[i] | (((long) buf[i + 1]) << 8);
|
|
else
|
|
ret = buf[i] | (((long) buf[i + 1]) << 8) | (((long) buf[i + 2]) << 16);
|
|
|
|
ret = (ret >> (pGifInfo->curbit % 8)) & maskTbl[code_size];
|
|
|
|
pGifInfo->curbit += code_size;
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define readLWZ(pGifInfo) ((pGifInfo->sp > pGifInfo->stack) ? *--(pGifInfo->sp) : nextLWZ(pGifInfo))
|
|
|
|
|
|
static long nextLWZ(PGIFINFO pGifInfo)
|
|
{
|
|
long code, incode;
|
|
long i;
|
|
long *table0 = &(pGifInfo->table[0][0]);
|
|
long *table1 = &(pGifInfo->table[1][1]);
|
|
long *pstacktop = &(pGifInfo->stack[(1 << (MAX_LWZ_BITS)) * 2]);
|
|
|
|
while ((code = nextCode(pGifInfo, pGifInfo->code_size)) >= 0)
|
|
{
|
|
if (code == pGifInfo->clear_code)
|
|
{
|
|
|
|
/* corrupt GIFs can make this happen */
|
|
if (pGifInfo->clear_code >= (1 << MAX_LWZ_BITS))
|
|
{
|
|
return -2;
|
|
}
|
|
/* CMF following old code replaced by memset and memcpy
|
|
for (i = 0; i < pGifInfo->clear_code; ++i)
|
|
{
|
|
table0[i] = 0;
|
|
table1[i] = i;
|
|
}
|
|
for (; i < (1 << MAX_LWZ_BITS); ++i)
|
|
table0[i] = table1[i] = 0;
|
|
*/
|
|
for (i = 0; i < pGifInfo->clear_code; ++i)
|
|
{
|
|
table1[i] = i;
|
|
}
|
|
memset(table0,0,sizeof(long)*(1 << MAX_LWZ_BITS));
|
|
memset(&table1[pGifInfo->clear_code],0,sizeof(long)*((1 << MAX_LWZ_BITS)-pGifInfo->clear_code));
|
|
pGifInfo->code_size = pGifInfo->set_code_size + 1;
|
|
pGifInfo->max_code_size = 2 * pGifInfo->clear_code;
|
|
pGifInfo->max_code = pGifInfo->clear_code + 2;
|
|
pGifInfo->sp = pGifInfo->stack;
|
|
do
|
|
{
|
|
pGifInfo->firstcode = pGifInfo->oldcode = nextCode(pGifInfo, pGifInfo->code_size);
|
|
}
|
|
while (pGifInfo->firstcode == pGifInfo->clear_code);
|
|
|
|
return pGifInfo->firstcode;
|
|
}
|
|
if (code == pGifInfo->end_code)
|
|
{
|
|
long count;
|
|
unsigned char buf[260];
|
|
|
|
if (pGifInfo->ZeroDataBlock)
|
|
{
|
|
return -2;
|
|
}
|
|
|
|
while ((count = GetDataBlock(pGifInfo, buf)) > 0)
|
|
;
|
|
|
|
if (count != 0)
|
|
return -2;
|
|
}
|
|
|
|
incode = code;
|
|
|
|
if (code >= pGifInfo->max_code)
|
|
{
|
|
if (pGifInfo->sp >= pstacktop) return -2;
|
|
*(pGifInfo->sp)++ = pGifInfo->firstcode;
|
|
code = pGifInfo->oldcode;
|
|
}
|
|
|
|
while (code >= pGifInfo->clear_code)
|
|
{
|
|
if (pGifInfo->sp >= pstacktop) return -2;
|
|
*(pGifInfo->sp)++ = table1[code];
|
|
if (code == table0[code])
|
|
{
|
|
return (code);
|
|
}
|
|
code = table0[code];
|
|
}
|
|
|
|
if (pGifInfo->sp >= pstacktop) return -2;
|
|
*(pGifInfo->sp)++ = pGifInfo->firstcode = table1[code];
|
|
|
|
if ((code = pGifInfo->max_code) < (1 << MAX_LWZ_BITS))
|
|
{
|
|
table0[code] = pGifInfo->oldcode;
|
|
table1[code] = pGifInfo->firstcode;
|
|
++pGifInfo->max_code;
|
|
if ((pGifInfo->max_code >= pGifInfo->max_code_size) && (pGifInfo->max_code_size < (1 << MAX_LWZ_BITS)))
|
|
{
|
|
pGifInfo->max_code_size *= 2;
|
|
++pGifInfo->code_size;
|
|
}
|
|
}
|
|
|
|
pGifInfo->oldcode = incode;
|
|
|
|
if (pGifInfo->sp > pGifInfo->stack)
|
|
return (*--(pGifInfo->sp));
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static unsigned char *ReadImage(PGIFINFO pGifInfo, long len, long height, PALETTEENTRY * colrs, long cmapSize,
|
|
unsigned char cmap[3][MAXCOLORMAPSIZE], long interlace, long ignore)
|
|
{
|
|
unsigned char *dp, c;
|
|
long v;
|
|
long xpos = 0, ypos = 0, pass = 0;
|
|
unsigned char *image;
|
|
long padlen = ((len + 3) / 4) * 4;
|
|
#ifdef FEATURE_IMG_THREADS
|
|
PIMGCBINFO pImgCBInfo = NULL;
|
|
#endif
|
|
|
|
#ifdef FEATURE_IMG_THREADS
|
|
if (pGifInfo->pdecoder)
|
|
pImgCBInfo = pDC_GetOutput(pGifInfo->pdecoder);
|
|
#endif
|
|
|
|
|
|
cmap = cmap;
|
|
/*
|
|
** Initialize the Compression routines
|
|
*/
|
|
if (!ReadOK(pGifInfo, &c, 1))
|
|
{
|
|
return (NULL);
|
|
}
|
|
|
|
initLWZ(pGifInfo,c);
|
|
|
|
|
|
/*
|
|
** If this is an "uninteresting picture" ignore it.
|
|
*/
|
|
if (ignore)
|
|
{
|
|
while (readLWZ(pGifInfo) >= 0)
|
|
;
|
|
return (NULL);
|
|
}
|
|
|
|
image = (unsigned char *) GTR_MALLOC(padlen * height * sizeof(char));
|
|
if (image == NULL)
|
|
{
|
|
XX_DMsg(DBG_IMAGE, ("Cannot allocate space for image data\n"));
|
|
return (NULL);
|
|
}
|
|
|
|
for (v = 0; v < MAXCOLORMAPSIZE; v++)
|
|
{
|
|
colrs[v].peRed = colrs[v].peGreen = colrs[v].peBlue = colrs[v].peFlags = (BYTE) 0;
|
|
}
|
|
for (v = 0; v < cmapSize; v++)
|
|
{
|
|
colrs[v].peRed = cmap[CM_RED][v] * 0x101;
|
|
colrs[v].peGreen = cmap[CM_GREEN][v] * 0x101;
|
|
colrs[v].peBlue = cmap[CM_BLUE][v] * 0x101;
|
|
}
|
|
|
|
#ifdef FEATURE_IMG_THREADS
|
|
if (pImgCBInfo) pImgCBInfo->data = image;
|
|
#endif
|
|
|
|
if (interlace)
|
|
{
|
|
long i;
|
|
long pass = 0, step = 8;
|
|
|
|
#ifdef FEATURE_IMG_THREADS
|
|
if (pImgCBInfo)
|
|
{
|
|
if (height > 4) pImgCBInfo->flags |= IMG_INTERLEAVED;
|
|
else pImgCBInfo->flags &= ~IMG_INTERLEAVED;
|
|
}
|
|
#endif
|
|
|
|
for (i = 0; i < height; i++)
|
|
{
|
|
// XX_DMsg(DBG_IMAGE, ("readimage, logical=%d, offset=%d\n", i, padlen * ((height-1) - ypos)));
|
|
dp = &image[padlen * ((height-1) - ypos)];
|
|
for (xpos = 0; xpos < len; xpos++)
|
|
{
|
|
if ((v = readLWZ(pGifInfo)) < 0)
|
|
goto fini;
|
|
|
|
*dp++ = (unsigned char) v;
|
|
}
|
|
ypos += step;
|
|
while (ypos >= height)
|
|
{
|
|
if (pass++ > 0)
|
|
step /= 2;
|
|
ypos = step / 2;
|
|
if (pImgCBInfo && pass == 1) DC_SetCoversImg(pGifInfo->pdecoder);
|
|
}
|
|
#ifdef FEATURE_IMG_THREADS
|
|
if (pImgCBInfo && height > 4)
|
|
{
|
|
pImgCBInfo->logicalRow = i;
|
|
if(pImgCBInfo->bProgSeen)
|
|
{
|
|
pImgCBInfo->bProgSeen = FALSE;
|
|
DC_PostStatus(pGifInfo->pdecoder,DC_ProgDraw);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef FEATURE_IMG_THREADS
|
|
if (pImgCBInfo && height <= 4)
|
|
{
|
|
pImgCBInfo->logicalRow = height-1;
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
|
|
#ifdef FEATURE_IMG_THREADS
|
|
if (pImgCBInfo)
|
|
{
|
|
pImgCBInfo->flags &= ~IMG_INTERLEAVED;
|
|
pImgCBInfo->logicalRow = -1;
|
|
}
|
|
#endif
|
|
|
|
for (ypos = height-1; ypos >= 0; ypos--)
|
|
{
|
|
dp = &image[padlen * ypos];
|
|
for (xpos = 0; xpos < len; xpos++)
|
|
{
|
|
if ((v = readLWZ(pGifInfo)) < 0)
|
|
goto fini;
|
|
|
|
*dp++ = (unsigned char) v;
|
|
}
|
|
#ifdef FEATURE_IMG_THREADS
|
|
if (pImgCBInfo)
|
|
{
|
|
pImgCBInfo->logicalRow++;
|
|
// XX_DMsg(DBG_IMAGE, ("readimage, logical=%d, offset=%d\n", pImgCBInfo->logicalRow, padlen * ypos));
|
|
if(pImgCBInfo->bProgSeen)
|
|
{
|
|
pImgCBInfo->bProgSeen = FALSE;
|
|
DC_PostStatus(pGifInfo->pdecoder,DC_ProgDraw);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
fini:
|
|
return (image);
|
|
}
|
|
|
|
#ifdef FEATURE_IMG_THREADS
|
|
|
|
void getPassInfo(int logicalRowX,int height,int *pPassX,int *pRowX, int *pBandX)
|
|
{
|
|
int passLow,passHigh,passBand;
|
|
int pass = 0;
|
|
int step = 8;
|
|
int ypos = 0;
|
|
|
|
if (logicalRowX >= height) logicalRowX = height-1;
|
|
passBand = 8;
|
|
passLow = 0;
|
|
while (step > 1)
|
|
{
|
|
if (pass == 3) passHigh = height-1;
|
|
else passHigh = (height-1-ypos) / step + passLow;
|
|
if (logicalRowX >= passLow && logicalRowX <= passHigh)
|
|
{
|
|
*pPassX = pass;
|
|
*pRowX = ypos + (logicalRowX - passLow) * step;
|
|
*pBandX = passBand;
|
|
return;
|
|
}
|
|
if (pass++ > 0)
|
|
step /= 2;
|
|
ypos = step / 2;
|
|
passBand /= 2;
|
|
passLow = passHigh+1;
|
|
}
|
|
}
|
|
|
|
|
|
// Performs a StretchDIBits for progressive draw (deals with
|
|
// only some of the data being available etc
|
|
int GifStretchDIBits(
|
|
PDECODER pdecoder,
|
|
HDC hdc, // handle of device context
|
|
int XDest, // x-coordinate of upper-left corner of dest. rect.
|
|
int YDest, // y-coordinate of upper-left corner of dest. rect.
|
|
int nDestWidth, // width of destination rectangle
|
|
int nDestHeight, // height of destination rectangle
|
|
int XSrc, // x-coordinate of upper-left corner of source rect.
|
|
int YSrc, // y-coordinate of upper-left corner of source rect.
|
|
int nSrcWidth, // width of source rectangle
|
|
int nSrcHeight, // height of source rectangle
|
|
UINT iUsage, // usage
|
|
DWORD dwRop, // raster operation code
|
|
PDIBENV pdibenv // DIBENV for draw
|
|
)
|
|
{
|
|
PIMGCBINFO pImgCBInfo = pDC_GetOutput(pdecoder);
|
|
int logicalRow = pImgCBInfo->logicalRow;
|
|
int logicalFill = pImgCBInfo->logicalFill;
|
|
BOOL bitbltNeeded = TRUE;
|
|
int i;
|
|
int padXSize = ((pImgCBInfo->width + 3) / 4) * 4;
|
|
int err;
|
|
int row = logicalRow;
|
|
int pass;
|
|
int band;
|
|
int band2;
|
|
int nDestBand;
|
|
int offset;
|
|
int passFill;
|
|
int rowFill;
|
|
int bandFill;
|
|
int step;
|
|
int j;
|
|
|
|
if (nSrcHeight == 0) return 0;
|
|
if (pImgCBInfo->pbmi == NULL)
|
|
{
|
|
pImgCBInfo->ditherRow = 0;
|
|
|
|
if (pImgCBInfo->ditherData == NULL)
|
|
{
|
|
pImgCBInfo->ditherData = pCreateDitherData(pImgCBInfo->width);
|
|
if (pImgCBInfo->ditherData == NULL) return 0;
|
|
}
|
|
|
|
|
|
if ((IMG_INTERLEAVED & pImgCBInfo->flags) && pImgCBInfo->pRow == NULL)
|
|
{
|
|
pImgCBInfo->pRow = GTR_MALLOC(padXSize*pImgCBInfo->height);
|
|
if (pImgCBInfo->pRow == NULL) return 0;
|
|
}
|
|
|
|
if (pImgCBInfo->hPalette == NULL)
|
|
{
|
|
LOGPALETTE *lp;
|
|
|
|
lp = (LOGPALETTE *) GTR_MALLOC(sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * 256);
|
|
if (lp == NULL) return 0;
|
|
|
|
lp->palVersion = 0x300;
|
|
lp->palNumEntries = 256;
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
lp->palPalEntry[i] = pImgCBInfo->colors[i];
|
|
}
|
|
pImgCBInfo->hPalette = CreatePalette(lp);
|
|
GTR_FREE(lp);
|
|
}
|
|
|
|
if (wg.eColorMode == 8)
|
|
{
|
|
pImgCBInfo->pbmi = BIT_Make_DIB_PAL_Header(pImgCBInfo->width, pImgCBInfo->height,
|
|
NULL, pImgCBInfo->hPalette, pImgCBInfo->transparent);
|
|
}
|
|
else
|
|
{
|
|
pImgCBInfo->pbmi = BIT_Make_DIB_RGB_Header_Screen(pImgCBInfo->width, pImgCBInfo->height,
|
|
NULL, pImgCBInfo->hPalette, pImgCBInfo->transparent, pImgCBInfo->flags);
|
|
}
|
|
if (pImgCBInfo->pbmi == NULL) return 0;
|
|
}
|
|
|
|
pdibenv->transparent = pImgCBInfo->transparent;
|
|
if (IMG_INTERLEAVED & pImgCBInfo->flags)
|
|
{
|
|
getPassInfo(logicalFill,pImgCBInfo->height,&passFill,&rowFill,&bandFill);
|
|
getPassInfo(logicalRow,pImgCBInfo->height,&pass,&row,&band);
|
|
step = passFill == 0 ? 8 : bandFill*2;
|
|
// XX_DMsg(DBG_IMAGE, ("logicalFill=%d logicalRow=%d\n", logicalFill, logicalRow));
|
|
// XX_DMsg(DBG_IMAGE, ("rowFill=%d passFill=%d row=%d step=%d band=%d\n", rowFill, passFill, row, step,band));
|
|
for (i = logicalFill; i <= logicalRow;i++)
|
|
{
|
|
offset = padXSize*(pImgCBInfo->height - rowFill - 1) ; /* the DIB is stored upside down */
|
|
band2 = rowFill <= row ? band : band*2;
|
|
if (band2 != 1)
|
|
{
|
|
// XX_DMsg(DBG_IMAGE, ("copy row=%d..%d, offset=%d\n", rowFill,rowFill+band2-1,offset));
|
|
if (wg.eColorMode == 8)
|
|
x_ColorConstrain(pImgCBInfo->data+offset,pImgCBInfo->pRow+offset,pImgCBInfo->colors,pImgCBInfo->width,pImgCBInfo->transparent);
|
|
else
|
|
memcpy(pImgCBInfo->pRow+offset,pImgCBInfo->data+offset,padXSize);
|
|
if (rowFill+band2 > pImgCBInfo->height) band2 = pImgCBInfo->height-rowFill;
|
|
for (j = 1; j < band2; j++)
|
|
memcpy(pImgCBInfo->pRow+(offset-j*padXSize),pImgCBInfo->pRow+offset,padXSize);
|
|
}
|
|
if ((rowFill += step) >= pImgCBInfo->height)
|
|
{
|
|
if (passFill++ > 0)
|
|
step /= 2;
|
|
rowFill = step / 2;
|
|
}
|
|
|
|
}
|
|
pImgCBInfo->logicalFill = logicalRow+1;
|
|
switch (pass)
|
|
{
|
|
case 0:
|
|
band += row;
|
|
break;
|
|
case 3:
|
|
band = pImgCBInfo->height-row-1;
|
|
break;
|
|
default:
|
|
band = pImgCBInfo->height;
|
|
break;
|
|
}
|
|
if (band > pImgCBInfo->height) band = pImgCBInfo->height;
|
|
|
|
if (nSrcHeight != nDestHeight || nSrcWidth != nDestWidth)
|
|
{
|
|
nDestBand = (int) (((long) band * nDestHeight) / nSrcHeight);
|
|
if ( (((long) band * nDestHeight) % nSrcHeight))
|
|
nDestBand++;
|
|
}
|
|
else
|
|
{
|
|
nDestBand = band;
|
|
}
|
|
if (nDestBand > nDestHeight) nDestBand = nDestHeight;
|
|
|
|
if (band > 0)
|
|
{
|
|
pImgCBInfo->pbmi->bmiHeader.biHeight = band;
|
|
if (pass != 3)
|
|
{
|
|
// XX_DMsg(DBG_IMAGE, ("stretch %d,%d,%d,%d <- %d,%d,%d,%d\n",XDest,YDest,nDestWidth,band,0,0,pImgCBInfo->width, band));
|
|
err = MyStretchDIBits(hdc, XDest, YDest,
|
|
nDestWidth, nDestBand,
|
|
0, 0,
|
|
pImgCBInfo->width, band,
|
|
pImgCBInfo->pRow+((pImgCBInfo->height-band)*padXSize),
|
|
pImgCBInfo->pbmi,
|
|
iUsage, dwRop, pdibenv);
|
|
}
|
|
else
|
|
{
|
|
err = MyStretchDIBits(hdc, XDest, YDest + (nDestHeight-nDestBand),
|
|
nDestWidth, nDestBand,
|
|
0, 0,
|
|
pImgCBInfo->width, band, pImgCBInfo->pRow, pImgCBInfo->pbmi,
|
|
iUsage, dwRop, pdibenv);
|
|
}
|
|
bitbltNeeded = pass == 3;
|
|
}
|
|
if (bitbltNeeded)
|
|
{
|
|
if (row >= pImgCBInfo->height) row = pImgCBInfo->height - 1;
|
|
}
|
|
}
|
|
|
|
if (bitbltNeeded)
|
|
{
|
|
band = row + 1;
|
|
if (nSrcHeight != nDestHeight || nSrcWidth != nDestWidth)
|
|
{
|
|
nDestBand = (int) (((long) band * nDestHeight) / nSrcHeight);
|
|
if ( (((long) band * nDestHeight) % nSrcHeight))
|
|
nDestBand++;
|
|
}
|
|
else
|
|
{
|
|
nDestBand = band;
|
|
}
|
|
if (nDestBand > nDestHeight) nDestBand = nDestHeight;
|
|
if (wg.eColorMode == 8)
|
|
{
|
|
if (pImgCBInfo->ditherRow <= row)
|
|
{
|
|
x_DitherRelative(pImgCBInfo->data, pImgCBInfo->colors, pImgCBInfo->width, pImgCBInfo->height, pImgCBInfo->transparent, pImgCBInfo->ditherData, pImgCBInfo->ditherRow, row);
|
|
pImgCBInfo->ditherRow = band;
|
|
}
|
|
}
|
|
pImgCBInfo->pbmi->bmiHeader.biHeight = band;
|
|
// XX_DMsg(DBG_IMAGE, ("stretch %d,%d,%d,%d <- %d,%d,%d,%d\n",XDest,YDest,nDestWidth,band,0,0,pImgCBInfo->width, band));
|
|
err = MyStretchDIBits(hdc, XDest, YDest,
|
|
nDestWidth, nDestBand,
|
|
0, 0,
|
|
pImgCBInfo->width, band,
|
|
pImgCBInfo->data+((pImgCBInfo->height-band)*padXSize),
|
|
pImgCBInfo->pbmi,
|
|
iUsage, dwRop, pdibenv);
|
|
// XX_DMsg(DBG_IMAGE, ("After StretchDIBits, err=%d, GetLastError()=%d\n", err, GetLastError()));
|
|
}
|
|
}
|
|
|
|
// Performs an invalidate of rectangles changed between logicalRow0
|
|
// and logicalRowN
|
|
void GenericUpdateRect(unsigned long flags,int width,int height,struct Mwin *tw, RECT *r,int logicalRow0,int logicalRowN, BOOL bTransparent)
|
|
{
|
|
RECT updateRect;
|
|
int pass0,passN;
|
|
int row0,rowN;
|
|
int band0,bandN;
|
|
int nDestRow0, nDestRowN;
|
|
int nDestLogicalRow0, nDestLogicalRowN;
|
|
int nDestHeight = r->bottom - r->top;
|
|
int row;
|
|
|
|
if (logicalRow0 == 0)
|
|
{
|
|
InvalidateRect(tw->win, r, TRUE);
|
|
return;
|
|
}
|
|
if (logicalRow0 > logicalRowN) return;
|
|
if (height == 0) return;
|
|
|
|
|
|
// XX_DMsg(DBG_IMAGE, ("GenericUpdateRect, log0=%d, logN=%d\n", logicalRow0,logicalRowN));
|
|
if (IMG_INTERLEAVED & flags)
|
|
{
|
|
getPassInfo(logicalRow0,height,&pass0,&row0,&band0);
|
|
getPassInfo(logicalRowN,height,&passN,&rowN,&bandN);
|
|
|
|
|
|
// XX_DMsg(DBG_IMAGE, ("GenericUpdateRect, pass0=%d, row0=%d, band0=%d\n", pass0,row0,band0));
|
|
// XX_DMsg(DBG_IMAGE, ("GenericUpdateRect, passN=%d, rowN=%d, bandN=%d\n", passN,rowN,bandN));
|
|
if (passN == pass0+1)
|
|
{
|
|
row = row0 < band0 ? 0 : row0 - band0;
|
|
nDestRow0 = (int) (((long) row * nDestHeight) / height);
|
|
if (nDestRow0 != 0 && (((long) row * nDestHeight) % height))
|
|
nDestRow0--;
|
|
|
|
updateRect.left = r->left;
|
|
updateRect.top = r->top + nDestRow0;
|
|
updateRect.right = r->right;
|
|
updateRect.bottom = r->top + nDestHeight;
|
|
InvalidateRect(tw->win, &updateRect, FALSE /*bTransparent*/);
|
|
#ifdef DBG_UPDATEIMG
|
|
XX_DMsg(DBG_IMAGE, ("GenericUpdateRect, [left,top = %d,%d] rect[1]=(%d,%d,%d,%d)\n", r->left,r->top,updateRect.left,updateRect.top,updateRect.right,updateRect.bottom));
|
|
#endif
|
|
row0 = 0;
|
|
}
|
|
else if (passN > pass0+1)
|
|
{
|
|
row0 = 0;
|
|
rowN = height - 1;
|
|
bandN = 1;
|
|
}
|
|
|
|
row = (row0 < bandN ? 0 : row0 - bandN);
|
|
nDestRow0 = (int) (((long) row * nDestHeight) / height);
|
|
if (nDestRow0 != 0 && (((long) row * nDestHeight) % height))
|
|
nDestRow0--;
|
|
row = rowN + bandN;
|
|
nDestRowN = (int) (((long) row * nDestHeight) / height);
|
|
if ((((long) row * nDestHeight) % height))
|
|
nDestRowN++;
|
|
|
|
updateRect.left = r->left;
|
|
updateRect.top = r->top + nDestRow0;
|
|
updateRect.right = r->right;
|
|
updateRect.bottom = r->top + nDestRowN;
|
|
// We must special case the last row of last pass to deal with dithering
|
|
// the possibly even numbered last row
|
|
if (logicalRowN == height - 1) updateRect.bottom = r->bottom;
|
|
}
|
|
else
|
|
{
|
|
nDestLogicalRow0 = (int) (((long) logicalRow0 * nDestHeight) / height);
|
|
nDestLogicalRowN = (int) (((long) logicalRowN * nDestHeight) / height);
|
|
if (nDestLogicalRow0 != 0 && (((long) logicalRow0 * nDestHeight) % height) )
|
|
nDestLogicalRow0--;
|
|
if ( (((long) logicalRowN * nDestHeight) % height) )
|
|
nDestLogicalRowN++;
|
|
updateRect.left = r->left;
|
|
updateRect.top = r->top + nDestLogicalRow0;
|
|
updateRect.right = r->right;
|
|
updateRect.bottom = r->top + nDestLogicalRowN + 1;
|
|
}
|
|
if (updateRect.bottom > r->bottom) updateRect.bottom = r->bottom;
|
|
#ifdef DBG_UPDATEIMG
|
|
XX_DMsg(DBG_IMAGE, ("GifUpdateRect, [left,top = %d,%d] rect[1]=(%d,%d,%d,%d)\n", r->left,r->top,updateRect.left,updateRect.top,updateRect.right,updateRect.bottom));
|
|
#endif
|
|
InvalidateRect(tw->win, &updateRect, FALSE/*bTransparent*/);
|
|
tw->bErase = TRUE;
|
|
}
|
|
|
|
// Performs an invalidate of rectangles changed between logicalRow0
|
|
// and logicalRowN
|
|
void GifUpdateRect(PDECODER pdecoder,struct Mwin *tw, RECT *r,int logicalRow0,int logicalRowN)
|
|
{
|
|
PIMGCBINFO pImgCBInfo = pDC_GetOutput(pdecoder);
|
|
|
|
GenericUpdateRect(pImgCBInfo->flags,pImgCBInfo->width,pImgCBInfo->height,tw,r,logicalRow0,logicalRowN,pImgCBInfo->transparent >= 0);
|
|
}
|
|
|
|
|
|
// Performs an invalidate of rectangles changed between logicalRow0
|
|
// and logicalRowN
|
|
void GifImgUpdateRect(struct ImageInfo *pImg,struct Mwin *tw, RECT *r,int logicalRow0,int logicalRowN)
|
|
{
|
|
GenericUpdateRect(pImg->flags,pImg->width,pImg->height,tw,r,logicalRow0,logicalRowN,pImg->transparent >= 0);
|
|
}
|
|
|
|
#endif
|