diff --git a/Graphics/gl2gif.cpp b/Graphics/gl2gif.cpp index a83d130b6b2aada30301c5483394c86b276b990a..c17cecbb3fadb78655ac9acbabdba61f0a6dcacd 100644 --- a/Graphics/gl2gif.cpp +++ b/Graphics/gl2gif.cpp @@ -1,1066 +1,1408 @@ -/* - This file is a big hack from - - The GIFLIB distribution is Copyright (c) 1997 Eric S. Raymond - +/* $Id: gl2gif.cpp,v 1.5 2000-12-28 18:57:05 geuzaine Exp $ */ +/* + * gl2gif: an OpenGL to GIF printing library + * + * Warning: This code is really a dirty hack. It SHOULD be cleaned + * (and most of all, all the static variables should be removed) + * + * + * Based on + * + * . libppm3.c - ppm utility library part 3 + * Copyright (C) 1989, 1991 by Jef Poskanzer. + * + * . ppmtogif.c - read a portable pixmap and produce a GIF file + * Copyright (C) 1989 by Jef Poskanzer. + * + * . GIFCOMPR.C + * Lempel-Ziv compression based on 'compress.c'. + * File compression ala IEEE Computer, June 1984. + * By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + * Jim McKie (decvax!mcvax!jim) + * Steve Davies (decvax!vax135!petsd!peora!srd) + * Ken Turkowski (decvax!decwrl!turtlevax!ken) + * James A. Woods (decvax!ihnp4!ames!jaw) + * Joe Orost (decvax!vax135!petsd!joe) + * GIF modifications by David Rowley (mgardi@watdcsu.waterloo.edu) + * + * . ppmquant.c - quantize the colors in a pixmap down to a specified + * number + * Copyright (C) 1989, 1991 by Jef Poskanzer. Based on Paul + * Heckbert's paper "Color Image Quantization for Frame Buffer + * Display", SIGGRAPH '82 Proceedings, page 297. + * */ -#include <stdio.h> -#include <stdarg.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <ctype.h> -#include <unistd.h> - -#include <GL/gl.h> +#include "Gmsh.h" +#include "GmshUI.h" #include "gl2gif.h" -static int _GifError = 0; -static int ExpNumOfColors = 8, ColorMapSize = 256; +/* ------------------------------------------------------------------ + PPM colormap routines + ------------------------------------------------------------------ */ +#define HASH_SIZE 20023 +#define ppm_hashpixel(p) ( ( (int) (p) & 0xfffffffful ) % HASH_SIZE ) -/****************************************************************************** -* Initialize HashTable - allocate the memory needed and clear it. * -******************************************************************************/ -GifHashTableType *_InitHashTable(void) -{ - GifHashTableType *HashTable; +static int static_red[MAX_GIFCOLORS]; +static int static_green[MAX_GIFCOLORS]; +static int static_blue[MAX_GIFCOLORS]; +static int static_perm[MAX_GIFCOLORS], static_permi[MAX_GIFCOLORS]; +static int static_nbcolors; +static pixel** static_pixels; +static colorhash_table static_cht; - if ((HashTable = (GifHashTableType *) malloc(sizeof(GifHashTableType))) - == NULL) - return NULL; +colorhash_table ppm_alloccolorhash( ){ + colorhash_table cht; + int i; + + cht = (colorhash_table) Malloc( HASH_SIZE * sizeof(colorhist_list) ); - _ClearHashTable(HashTable); + for ( i = 0; i < HASH_SIZE; ++i ) + cht[i] = (colorhist_list) 0; + + return cht; +} - return HashTable; +void ppm_freecolorhash( colorhash_table cht ){ + int i; + colorhist_list chl, chlnext; + + for ( i = 0; i < HASH_SIZE; ++i ) + for ( chl = cht[i]; chl != (colorhist_list) 0; chl = chlnext ){ + chlnext = chl->next; + Free( (char*) chl ); + } + Free( (char*) cht ); } -/****************************************************************************** -* Routine to clear the HashTable to an empty state. * -* This part is a little machine depended. Use the commented part otherwise. * -******************************************************************************/ -void _ClearHashTable(GifHashTableType *HashTable) -{ - int index = HT_SIZE; - unsigned long* HTable = HashTable->HTable; - while (--index>=0) - HTable[index] = 0xfffffffful; +colorhash_table ppm_computecolorhash( pixel ** const pixels, + const int cols, const int rows, + const int maxcolors, int * const colorsP ){ + colorhash_table cht; + const pixel* pP; + colorhist_list chl; + int col, row, hash; + + cht = ppm_alloccolorhash( ); + *colorsP = 0; + + /* Go through the entire image, building a hash table of colors. */ + for ( row = 0; row < rows; ++row ) + for ( col = 0, pP = pixels[row]; col < cols; ++col, ++pP ){ + hash = ppm_hashpixel( *pP ); + for ( chl = cht[hash]; chl != (colorhist_list) 0; chl = chl->next ) + if ( PPM_EQUAL( chl->ch.color, *pP ) ) + break; + if ( chl != (colorhist_list) 0 ) + ++(chl->ch.value); + else{ + if ( ++(*colorsP) > maxcolors ){ + ppm_freecolorhash( cht ); + return (colorhash_table) 0; + } + chl = (colorhist_list) Malloc( sizeof(struct colorhist_list_item) ); + chl->ch.color = *pP; + chl->ch.value = 1; + chl->next = cht[hash]; + cht[hash] = chl; + } + } + + return cht; } -/****************************************************************************** -* Routine to generate an HKey for the hashtable out of the given unique key. * -* The given Key is assumed to be 20 bits as follows: lower 8 bits are the * -* new postfix character, while the upper 12 bits are the prefix code. * -* Because the average hit ratio is only 2 (2 hash references per entry), * -* evaluating more complex keys (such as twin prime keys) does not worth it! * -******************************************************************************/ -static int KeyItem(unsigned long Item) -{ - return ((Item >> 12) ^ Item) & HT_KEY_MASK; +int ppm_addtocolorhash( colorhash_table cht, const pixel * const colorP, + const int value ){ + register int hash; + register colorhist_list chl; + + chl = (colorhist_list) Malloc( sizeof(struct colorhist_list_item) ); + hash = ppm_hashpixel( *colorP ); + chl->ch.color = *colorP; + chl->ch.value = value; + chl->next = cht[hash]; + cht[hash] = chl; + return 0; } -/****************************************************************************** -* Routine to insert a new Item into the HashTable. The data is assumed to be * -* new one. * -******************************************************************************/ -void _InsertHashTable(GifHashTableType *HashTable, unsigned long Key, int Code) -{ - int HKey = KeyItem(Key); - unsigned long *HTable = HashTable -> HTable; - - while (HT_GET_KEY(HTable[HKey]) != 0xFFFFFL) { - HKey = (HKey + 1) & HT_KEY_MASK; +colorhist_vector ppm_colorhashtocolorhist( const colorhash_table cht, + const int maxcolors ){ + colorhist_vector chv; + colorhist_list chl; + int i, j; + + /* Now collate the hash table into a simple colorhist array. */ + chv = (colorhist_vector) Malloc( maxcolors * sizeof(struct colorhist_item) ); + /* Loop through the hash table. */ + j = 0; + for ( i = 0; i < HASH_SIZE; ++i ) + for ( chl = cht[i]; chl != (colorhist_list) 0; chl = chl->next ){ + /* Add the new entry. */ + chv[j] = chl->ch; + ++j; } - HTable[HKey] = HT_PUT_KEY(Key) | HT_PUT_CODE(Code); + + /* All done. */ + return chv; } -/****************************************************************************** -* Routine to test if given Key exists in HashTable and if so returns its code * -* Returns the Code if key was found, -1 if not. * -******************************************************************************/ -int _ExistsHashTable(GifHashTableType *HashTable, unsigned long Key) -{ - int HKey = KeyItem(Key); - unsigned long *HTable = HashTable -> HTable, HTKey; - - while ((HTKey = HT_GET_KEY(HTable[HKey])) != 0xFFFFFL) { - if (Key == HTKey) return HT_GET_CODE(HTable[HKey]); - HKey = (HKey + 1) & HT_KEY_MASK; - } +colorhash_table ppm_colorhisttocolorhash( const colorhist_vector chv, + const int colors ){ + colorhash_table cht; + int i, hash; + pixel color; + colorhist_list chl; + + cht = ppm_alloccolorhash( ); /* Initializes to NULLs */ + + for ( i = 0; i < colors; ++i ){ + color = chv[i].color; + hash = ppm_hashpixel( color ); + for ( chl = cht[hash]; chl != (colorhist_list) 0; chl = chl->next ) + if ( PPM_EQUAL( chl->ch.color, color ) ) + Msg(ERROR, "GIF: same color found twice - %d %d %d", PPM_GETR(color), + PPM_GETG(color), PPM_GETB(color) ); + chl = (colorhist_list) Malloc( sizeof(struct colorhist_list_item) ); + chl->ch.color = color; + chl->ch.value = i; + chl->next = cht[hash]; + cht[hash] = chl; + } + + return cht; +} - return -1; +colorhist_vector ppm_computecolorhist( pixel ** const pixels, + const int cols, const int rows, + const int maxcolors, + int * const colorsP ){ + colorhash_table cht; + colorhist_vector chv; + + cht = ppm_computecolorhash( pixels, cols, rows, maxcolors, colorsP ); + if ( cht == (colorhash_table) 0 ) + return (colorhist_vector) 0; + chv = ppm_colorhashtocolorhist( cht, maxcolors ); + ppm_freecolorhash( cht ); + return chv; } -/****************************************************************************** -* Miscellaneous utility functions * -******************************************************************************/ -int BitSize(int n) -/* return smallest bitfield size n will fit in */ -{ - register int i; +int ppm_lookupcolor( const colorhash_table cht, const pixel * const colorP ){ + int hash; + colorhist_list chl; + + hash = ppm_hashpixel( *colorP ); + for ( chl = cht[hash]; chl != (colorhist_list) 0; chl = chl->next ) + if ( PPM_EQUAL( chl->ch.color, *colorP ) ) + return chl->ch.value; + + return -1; +} - for (i = 1; i <= 8; i++) - if ((1 << i) >= n) - break; - return(i); +void ppm_freecolorhist( colorhist_vector chv ){ + Free( (char*) chv ); } +static int colorstobpp( int colors ){ + int bpp; + + if ( colors <= 2 ) + bpp = 1; + else if ( colors <= 4 ) + bpp = 2; + else if ( colors <= 8 ) + bpp = 3; + else if ( colors <= 16 ) + bpp = 4; + else if ( colors <= 32 ) + bpp = 5; + else if ( colors <= 64 ) + bpp = 6; + else if ( colors <= 128 ) + bpp = 7; + else if ( colors <= 256 ) + bpp = 8; + else{ + Msg(ERROR, "GIF: can't happen: too many colors" ); + bpp = 8 ; + } -/****************************************************************************** -* Color map object functions * -******************************************************************************/ + return bpp; +} -ColorMapObject *MakeMapObject(int ColorCount, GifColorType *ColorMap) -{ - ColorMapObject *Object; - if (ColorCount != (1 << BitSize(ColorCount))){ - printf("Arrrrrgggg\n"); - return((ColorMapObject *)NULL); - } +static int sqr(int x){ + return x*x; +} - Object = (ColorMapObject *)malloc(sizeof(ColorMapObject)); - if (Object == (ColorMapObject *)NULL) - return((ColorMapObject *)NULL); +static int closestcolor(pixel color){ + int i,r,g,b,d,imin,dmin; + + r=(int)PPM_GETR(color); + g=(int)PPM_GETG(color); + b=(int)PPM_GETB(color); + + dmin=1000000; + for (i=0 ; i<static_nbcolors ; i++) { + d = sqr(r-static_red[i]) + sqr(g-static_green[i]) + sqr(b-static_blue[i]); + if (d<dmin) { + dmin=d; + imin=i; + } + } + ppm_addtocolorhash(static_cht,&color,static_permi[imin]); + return imin; +} - Object->Colors = (GifColorType *)calloc(ColorCount, sizeof(GifColorType)); - if (Object->Colors == (GifColorType *)NULL) - return((ColorMapObject *)NULL); - Object->ColorCount = ColorCount; - Object->BitsPerPixel = BitSize(ColorCount); +static int GetPixel( int x, int y ){ + int color; + + color = ppm_lookupcolor( static_cht, &static_pixels[y][x] ); + if (color == -1) + color = closestcolor(static_pixels[y][x]); + else + color = static_perm[color]; + return color; +} - if (ColorMap) - memcpy((char *)Object->Colors, - (char *)ColorMap, ColorCount * sizeof(GifColorType)); - return(Object); -} +/*------------------------------------------------------------------ + GIF compression routines + ------------------------------------------------------------------*/ -void FreeMapObject(ColorMapObject *Object) -{ - free(Object->Colors); - free(Object); -} +#define BITS 12 +#define HSIZE 5003 /* 80% occupancy */ +#define TRUE 1 +#define FALSE 0 -/***************************************************************************** -* Print the last GIF error to stderr. * -*****************************************************************************/ -void PrintGifError(void) -{ - char *Err; - - switch(_GifError) { - case E_GIF_ERR_OPEN_FAILED: - Err = "Failed to open given file"; - break; - case E_GIF_ERR_WRITE_FAILED: - Err = "Failed to Write to given file"; - break; - case E_GIF_ERR_HAS_SCRN_DSCR: - Err = "Screen Descriptor already been set"; - break; - case E_GIF_ERR_HAS_IMAG_DSCR: - Err = "Image Descriptor is still active"; - break; - case E_GIF_ERR_NO_COLOR_MAP: - Err = "Neither Global Nor Local color map"; - break; - case E_GIF_ERR_DATA_TOO_BIG: - Err = "#Pixels bigger than Width * Height"; - break; - case E_GIF_ERR_NOT_ENOUGH_MEM: - Err = "Fail to allocate required memory"; - break; - case E_GIF_ERR_DISK_IS_FULL: - Err = "Write failed (disk full?)"; - break; - case E_GIF_ERR_CLOSE_FAILED: - Err = "Failed to close given file"; - break; - case E_GIF_ERR_NOT_WRITEABLE: - Err = "Given file was not opened for write"; - break; - default: - Err = NULL; - break; - } - if (Err != NULL) - fprintf(stderr, "Error: %s\n", Err); - else - fprintf(stderr, "Error: GIF undefined error %d\n", _GifError); -} +typedef unsigned char char_type; +typedef int (* ifunptr)(int, int); -#define ABS(x) ((x) > 0 ? (x) : (-(x))) - -#define COLOR_ARRAY_SIZE 32768 -#define BITS_PER_PRIM_COLOR 5 -#define MAX_PRIM_COLOR 0x1f - -static int SortRGBAxis; - -typedef struct QuantizedColorType { - GifByteType RGB[3]; - GifByteType NewColorIndex; - long Count; - struct QuantizedColorType *Pnext; -} QuantizedColorType; - -typedef struct NewColorMapType { - GifByteType RGBMin[3], RGBWidth[3]; - int NumEntries; /* # of QuantizedColorType in linked list below. */ - long Count; /* Total number of pixels in all the entries. */ - QuantizedColorType *QuantizedColors; -} NewColorMapType; - -static int SubdivColorMap(NewColorMapType *NewColorSubdiv, - int ColorMapSize, - int *NewColorMapSize); -static int SortCmpRtn(const VoidPtr Entry1, const VoidPtr Entry2); - -/****************************************************************************** -* Quantize high resolution image into lower one. Input image consists of a * -* 2D array for each of the RGB colors with size Width by Height. There is no * -* Color map for the input. Output is a quantized image with 2D array of * -* indexes into the output color map. * -* Note input image can be 24 bits at the most (8 for red/green/blue) and * -* the output has 256 colors at the most (256 entries in the color map.). * -* ColorMapSize specifies size of color map up to 256 and will be updated to * -* real size before returning. * -* Also non of the parameter are allocated by this routine. * -* This function returns GIF_OK if succesfull, GIF_ERROR otherwise. * -******************************************************************************/ -int QuantizeBuffer(int Width, int Height, int *ColorMapSize, - GifByteType *RedInput, GifByteType *GreenInput, GifByteType *BlueInput, - GifByteType *OutputBuffer, GifColorType *OutputColorMap) -{ - int Index, NumOfEntries; - int i, j, MaxRGBError[3]; - int NewColorMapSize; - long Red, Green, Blue; - NewColorMapType NewColorSubdiv[256]; - QuantizedColorType *ColorArrayEntries, *QuantizedColor; - - if ((ColorArrayEntries = (QuantizedColorType *) - malloc(sizeof(QuantizedColorType) * COLOR_ARRAY_SIZE)) == NULL) { - _GifError = E_GIF_ERR_NOT_ENOUGH_MEM; - return GIF_ERROR; - } +static int g_init_bits; +static FILE* g_outfile; +static int Width, Height; +static int curx, cury; +static long CountDown; +static int Pass = 0; +static int Interlace; - for (i = 0; i < COLOR_ARRAY_SIZE; i++) { - ColorArrayEntries[i].RGB[0]= i >> (2 * BITS_PER_PRIM_COLOR); - ColorArrayEntries[i].RGB[1] = (i >> BITS_PER_PRIM_COLOR) & - MAX_PRIM_COLOR; - ColorArrayEntries[i].RGB[2] = i & MAX_PRIM_COLOR; - ColorArrayEntries[i].Count = 0; - } +#include <ctype.h> - /* Sample the colors and their distribution: */ - for (i = 0; i < (int)(Width * Height); i++) { - Index = ((RedInput[i] >> (8 - BITS_PER_PRIM_COLOR)) - << (2 * BITS_PER_PRIM_COLOR)) + - ((GreenInput[i] >> (8 - BITS_PER_PRIM_COLOR)) - << BITS_PER_PRIM_COLOR) + - (BlueInput[i] >> (8 - BITS_PER_PRIM_COLOR)); - ColorArrayEntries[Index].Count++; - } +#define ARGVAL() (*++(*argv) || (--argc && *++argv)) - /* Put all the colors in the first entry of the color map, and call the */ - /* recursive subdivision process. */ - for (i = 0; i < 256; i++) { - NewColorSubdiv[i].QuantizedColors = NULL; - NewColorSubdiv[i].Count = NewColorSubdiv[i].NumEntries = 0; - for (j = 0; j < 3; j++) { - NewColorSubdiv[i].RGBMin[j] = 0; - NewColorSubdiv[i].RGBWidth[j] = 255; - } - } +static int n_bits; /* number of bits/code */ +static int maxbits = BITS; /* user settable max # bits/code */ +static code_int maxcode; /* maximum code, given n_bits */ +static code_int maxmaxcode = (code_int)1 << BITS; + /* should NEVER generate this code */ - /* Find the non empty entries in the color table and chain them: */ - for (i = 0; i < COLOR_ARRAY_SIZE; i++) - if (ColorArrayEntries[i].Count > 0) break; - QuantizedColor = NewColorSubdiv[0].QuantizedColors = &ColorArrayEntries[i]; - NumOfEntries = 1; - while (++i < COLOR_ARRAY_SIZE) - if (ColorArrayEntries[i].Count > 0) { - QuantizedColor -> Pnext = &ColorArrayEntries[i]; - QuantizedColor = &ColorArrayEntries[i]; - NumOfEntries++; - } - QuantizedColor -> Pnext = NULL; - - NewColorSubdiv[0].NumEntries = NumOfEntries;/* Different sampled colors. */ - NewColorSubdiv[0].Count = ((long) Width) * Height; /* Pixels. */ - NewColorMapSize = 1; - if (SubdivColorMap(NewColorSubdiv, *ColorMapSize, &NewColorMapSize) != - GIF_OK) { - free((char *) ColorArrayEntries); - return GIF_ERROR; - } - if (NewColorMapSize < *ColorMapSize) { - /* And clear rest of color map: */ - for (i = NewColorMapSize; i < *ColorMapSize; i++) - OutputColorMap[i].Red = - OutputColorMap[i].Green = - OutputColorMap[i].Blue = 0; - } +#define MAXCODE(n_bits) (((code_int) 1 << (n_bits)) - 1) - /* Average the colors in each entry to be the color to be used in the */ - /* output color map, and plug it into the output color map itself. */ - for (i = 0; i < NewColorMapSize; i++) { - if ((j = NewColorSubdiv[i].NumEntries) > 0) { - QuantizedColor = NewColorSubdiv[i].QuantizedColors; - Red = Green = Blue = 0; - while (QuantizedColor) { - QuantizedColor -> NewColorIndex = i; - Red += QuantizedColor -> RGB[0]; - Green += QuantizedColor -> RGB[1]; - Blue += QuantizedColor -> RGB[2]; - QuantizedColor = QuantizedColor -> Pnext; - } - OutputColorMap[i].Red = (Red << (8 - BITS_PER_PRIM_COLOR)) / j; - OutputColorMap[i].Green = (Green << (8 - BITS_PER_PRIM_COLOR)) / j; - OutputColorMap[i].Blue= (Blue << (8 - BITS_PER_PRIM_COLOR)) / j; - } - else - fprintf(stderr, "Warning, Null entry in quantized color map - that's weird\n"); - } +static count_int htab [HSIZE]; +static unsigned short codetab [HSIZE]; +#define HashTabOf(i) htab[i] +#define CodeTabOf(i) codetab[i] - /* Finally scan the input buffer again and put the mapped index in the */ - /* output buffer. */ - MaxRGBError[0] = MaxRGBError[1] = MaxRGBError[2] = 0; - for (i = 0; i < (int)(Width * Height); i++) { - Index = ((RedInput[i] >> (8 - BITS_PER_PRIM_COLOR)) - << (2 * BITS_PER_PRIM_COLOR)) + - ((GreenInput[i] >> (8 - BITS_PER_PRIM_COLOR)) - << BITS_PER_PRIM_COLOR) + - (BlueInput[i] >> (8 - BITS_PER_PRIM_COLOR)); - Index = ColorArrayEntries[Index].NewColorIndex; - OutputBuffer[i] = Index; - if (MaxRGBError[0] < ABS(OutputColorMap[Index].Red - RedInput[i])) - MaxRGBError[0] = ABS(OutputColorMap[Index].Red - RedInput[i]); - if (MaxRGBError[1] < ABS(OutputColorMap[Index].Green - GreenInput[i])) - MaxRGBError[1] = ABS(OutputColorMap[Index].Green - GreenInput[i]); - if (MaxRGBError[2] < ABS(OutputColorMap[Index].Blue - BlueInput[i])) - MaxRGBError[2] = ABS(OutputColorMap[Index].Blue - BlueInput[i]); - } +static code_int hsize = HSIZE; /* for dynamic table sizing */ - free((char *) ColorArrayEntries); +/* + * To save much memory, we overlay the table used by compress() with those + * used by decompress(). The tab_prefix table is the same size and type + * as the codetab. The tab_suffix table needs 2**BITS characters. We + * get this from the beginning of htab. The output stack uses the rest + * of htab, and contains characters. There is plenty of room for any + * possible stack (stack used to be 8000 characters). + */ - *ColorMapSize = NewColorMapSize; +#define tab_prefixof(i) CodeTabOf(i) +#define tab_suffixof(i) ((char_type*)(htab))[i] +#define de_stack ((char_type*)&tab_suffixof((code_int)1<<BITS)) - return GIF_OK; -} +static code_int free_ent = 0; /* first unused entry */ -/****************************************************************************** -* Routine to subdivide the RGB space recursively using median cut in each * -* axes alternatingly until ColorMapSize different cubes exists. * -* The biggest cube in one dimension is subdivide unless it has only one entry.* -* Returns GIF_ERROR if failed, otherwise GIF_OK. * -******************************************************************************/ -static int SubdivColorMap(NewColorMapType *NewColorSubdiv, - int ColorMapSize, - int *NewColorMapSize) -{ - int MaxSize; - int i, j, Index = 0, NumEntries, MinColor, MaxColor; - long Sum, Count; - QuantizedColorType *QuantizedColor, **SortArray; - - while (ColorMapSize > *NewColorMapSize) { - /* Find candidate for subdivision: */ - MaxSize = -1; - for (i = 0; i < *NewColorMapSize; i++) { - for (j = 0; j < 3; j++) { - if (((int) NewColorSubdiv[i].RGBWidth[j]) > MaxSize && - NewColorSubdiv[i].NumEntries > 1) { - MaxSize = NewColorSubdiv[i].RGBWidth[j]; - Index = i; - SortRGBAxis = j; - } - } - } - - if (MaxSize == -1) - return GIF_OK; - - /* Split the entry Index into two along the axis SortRGBAxis: */ - - /* Sort all elements in that entry along the given axis and split at */ - /* the median. */ - if ((SortArray = (QuantizedColorType **) - malloc(sizeof(QuantizedColorType *) * - NewColorSubdiv[Index].NumEntries)) == NULL) - return GIF_ERROR; - for (j = 0, QuantizedColor = NewColorSubdiv[Index].QuantizedColors; - j < NewColorSubdiv[Index].NumEntries && QuantizedColor != NULL; - j++, QuantizedColor = QuantizedColor -> Pnext) - SortArray[j] = QuantizedColor; - qsort(SortArray, NewColorSubdiv[Index].NumEntries, - sizeof(QuantizedColorType *), SortCmpRtn); - - /* Relink the sorted list into one: */ - for (j = 0; j < NewColorSubdiv[Index].NumEntries - 1; j++) - SortArray[j] -> Pnext = SortArray[j + 1]; - SortArray[NewColorSubdiv[Index].NumEntries - 1] -> Pnext = NULL; - NewColorSubdiv[Index].QuantizedColors = QuantizedColor = SortArray[0]; - free((char *) SortArray); - - /* Now simply add the Counts until we have half of the Count: */ - Sum = NewColorSubdiv[Index].Count / 2 - QuantizedColor -> Count; - NumEntries = 1; - Count = QuantizedColor -> Count; - while ((Sum -= QuantizedColor -> Pnext -> Count) >= 0 && - QuantizedColor -> Pnext != NULL && - QuantizedColor -> Pnext -> Pnext != NULL) { - QuantizedColor = QuantizedColor -> Pnext; - NumEntries++; - Count += QuantizedColor -> Count; - } - /* Save the values of the last color of the first half, and first */ - /* of the second half so we can update the Bounding Boxes later. */ - /* Also as the colors are quantized and the BBoxes are full 0..255, */ - /* they need to be rescaled. */ - MaxColor = QuantizedColor -> RGB[SortRGBAxis];/* Max. of first half. */ - MinColor = QuantizedColor -> Pnext -> RGB[SortRGBAxis];/* of second. */ - MaxColor <<= (8 - BITS_PER_PRIM_COLOR); - MinColor <<= (8 - BITS_PER_PRIM_COLOR); - - /* Partition right here: */ - NewColorSubdiv[*NewColorMapSize].QuantizedColors = - QuantizedColor -> Pnext; - QuantizedColor -> Pnext = NULL; - NewColorSubdiv[*NewColorMapSize].Count = Count; - NewColorSubdiv[Index].Count -= Count; - NewColorSubdiv[*NewColorMapSize].NumEntries = - NewColorSubdiv[Index].NumEntries - NumEntries; - NewColorSubdiv[Index].NumEntries = NumEntries; - for (j = 0; j < 3; j++) { - NewColorSubdiv[*NewColorMapSize].RGBMin[j] = - NewColorSubdiv[Index].RGBMin[j]; - NewColorSubdiv[*NewColorMapSize].RGBWidth[j] = - NewColorSubdiv[Index].RGBWidth[j]; - } - NewColorSubdiv[*NewColorMapSize].RGBWidth[SortRGBAxis] = - NewColorSubdiv[*NewColorMapSize].RGBMin[SortRGBAxis] + - NewColorSubdiv[*NewColorMapSize].RGBWidth[SortRGBAxis] - - MinColor; - NewColorSubdiv[*NewColorMapSize].RGBMin[SortRGBAxis] = MinColor; - - NewColorSubdiv[Index].RGBWidth[SortRGBAxis] = - MaxColor - NewColorSubdiv[Index].RGBMin[SortRGBAxis]; - - (*NewColorMapSize)++; - } +/* + * block compression parameters -- after all codes are used up, + * and compression rate changes, start over. + */ +static int clear_flg = 0; - return GIF_OK; +static int offset; +static long int in_count = 1; /* length of input */ +static long int out_count = 0; /* # of codes output (for debugging) */ +static int ClearCode; +static int EOFCode; + +/* + * Number of characters so far in this 'packet' + */ +static int a_count; + +/* + * Set up the 'byte output' routine + */ +static void char_init(){ + a_count = 0; } -/****************************************************************************** -* Routine called by qsort to compare to entries. * -******************************************************************************/ -static int SortCmpRtn(const VoidPtr Entry1, const VoidPtr Entry2) -{ - return (* ((QuantizedColorType **) Entry1)) -> RGB[SortRGBAxis] - - (* ((QuantizedColorType **) Entry2)) -> RGB[SortRGBAxis]; +/* + * Define the storage for the packet accumulator + */ +static char accum[ 256 ]; + + +/* + * Flush the packet to disk, and reset the accumulator + */ +static void flush_char(){ + if( a_count > 0 ) { + fputc( a_count, g_outfile ); + fwrite( accum, 1, a_count, g_outfile ); + a_count = 0; + } } -#define GIF87_STAMP "GIF87a" /* First chars in file - GIF stamp. */ -#define GIF89_STAMP "GIF89a" /* First chars in file - GIF stamp. */ +/* + * Add a character to the end of the current packet, and if it is 254 + * characters, flush the packet to disk. + */ +static void char_out( int c){ + accum[ a_count++ ] = c; + if( a_count >= 254 ) + flush_char(); +} -/* Masks given codes to BitsPerPixel, to make sure all codes are in range: */ -static GifPixelType CodeMask[] = { - 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff -}; +/* + * Bump the 'curx' and 'cury' to point to the next pixel + */ -static char *GifVersionPrefix = GIF87_STAMP; - -#define WRITE(_gif,_buf,_len) \ - fwrite(_buf, 1, _len, ((GifFilePrivateType*)_gif->Private)->File) - -static int EGifPutWord(int Word, GifFileType *GifFile); -static int EGifSetupCompress(GifFileType *GifFile); -static int EGifCompressLine(GifFileType *GifFile, GifPixelType *Line, - int LineLen); -static int EGifCompressOutput(GifFileType *GifFile, int Code); -static int EGifBufferedOutput(GifFileType *GifFile, GifByteType *Buf, int c); - -/****************************************************************************** -* Update a new gif file, given its file handle, which must be opened for * -* write in binary mode. * -* Returns GifFileType pointer dynamically allocated which serves as the gif * -* info record. _GifError is cleared if succesfull. * -******************************************************************************/ -GifFileType *EGifOpenFileHandle(FILE *f, int FileHandle) -{ - GifFileType *GifFile; - GifFilePrivateType *Private; - - if ((GifFile = (GifFileType *) malloc(sizeof(GifFileType))) == NULL) { - _GifError = E_GIF_ERR_NOT_ENOUGH_MEM; - return NULL; +static void BumpPixel(){ + /* + * Bump the current X position + */ + ++curx; + + /* + * If we are at the end of a scan line, set curx back to the beginning + * If we are interlaced, bump the cury to the appropriate spot, + * otherwise, just increment it. + */ + if( curx == Width ) { + curx = 0; + + if( !Interlace ) + ++cury; + else { + switch( Pass ) { + + case 0: + cury += 8; + if( cury >= Height ) { + ++Pass; + cury = 4; + } + break; + + case 1: + cury += 8; + if( cury >= Height ) { + ++Pass; + cury = 2; + } + break; + + case 2: + cury += 4; + if( cury >= Height ) { + ++Pass; + cury = 1; + } + break; + + case 3: + cury += 2; + break; + } } + } +} + + +/* + * Return the next pixel from the image + */ +static int GIFNextPixel( ifunptr getpixel){ + int r; + + if( CountDown == 0 ) + return EOF; + + --CountDown; + + r = ( * getpixel )( curx, cury ); + + BumpPixel(); + + return r; +} + + +/* + * Output the given code. + * Inputs: + * code: A n_bits-bit integer. If == -1, then EOF. This assumes + * that n_bits =< (long)wordsize - 1. + * Outputs: + * Outputs code to the file. + * Assumptions: + * Chars are 8 bits long. + * Algorithm: + * Maintain a BITS character long buffer (so that 8 codes will + * fit in it exactly). Use the VAX insv instruction to insert each + * code in turn. When the buffer fills up empty it and start over. + */ - memset(GifFile, '\0', sizeof(GifFileType)); +static unsigned long cur_accum = 0; +static int cur_bits = 0; - if ((Private = (GifFilePrivateType *) - malloc(sizeof(GifFilePrivateType))) == NULL) { - free(GifFile); - _GifError = E_GIF_ERR_NOT_ENOUGH_MEM; - return NULL; +static unsigned long masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, + 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, + 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +static void output( code_int code){ + cur_accum &= masks[ cur_bits ]; + + if( cur_bits > 0 ) + cur_accum |= ((long)code << cur_bits); + else + cur_accum = code; + + cur_bits += n_bits; + + while( cur_bits >= 8 ) { + char_out( (unsigned int)(cur_accum & 0xff) ); + cur_accum >>= 8; + cur_bits -= 8; + } + + /* + * If the next entry is going to be too big for the code size, + * then increase it, if possible. + */ + if ( free_ent > maxcode || clear_flg ) { + + if( clear_flg ) { + maxcode = MAXCODE (n_bits = g_init_bits); + clear_flg = 0; + } + else { + ++n_bits; + if ( n_bits == maxbits ) + maxcode = maxmaxcode; + else + maxcode = MAXCODE(n_bits); } - if ((Private->HashTable = _InitHashTable()) == NULL) { - free(GifFile); - free(Private); - _GifError = E_GIF_ERR_NOT_ENOUGH_MEM; - return NULL; + } + + if( code == EOFCode ) { + /* + * At EOF, write the rest of the buffer. + */ + while( cur_bits > 0 ) { + char_out( (unsigned int)(cur_accum & 0xff) ); + cur_accum >>= 8; + cur_bits -= 8; } - - GifFile->Private = (VoidPtr) Private; - Private->FileHandle = FileHandle; - Private->File = f; - Private->FileState = FILE_STATE_WRITE; - _GifError = 0; + flush_char(); + + fflush( g_outfile ); + + if( ferror( g_outfile ) ) + Msg(ERROR, "GIF: Error writing output file"); + } +} + + +/* + * compress + * + * Algorithm: use open addressing double hashing (no chaining) on the + * prefix code / next character combination. We do a variant of Knuth's + * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + * secondary probe. Here, the modular division first probe is gives way + * to a faster exclusive-or manipulation. Also do block compression with + * an adaptive reset, whereby the code table is cleared when the compression + * ratio decreases, but after the table fills. The variable-length output + * codes are re-sized at this point, and a special CLEAR code is generated + * for the decompressor. Late addition: construct the table according to + * file size for noticeable speed improvement on small files. Please direct + * questions about this implementation to ames!jaw. + */ - return GifFile; +static void cl_hash(register count_int hsize){ /* reset code table */ + register count_int *htab_p = htab+hsize; + + register long i; + register long m1 = -1; + + i = hsize - 16; + do { /* might use Sys V memset(3) here */ + *(htab_p-16) = m1; + *(htab_p-15) = m1; + *(htab_p-14) = m1; + *(htab_p-13) = m1; + *(htab_p-12) = m1; + *(htab_p-11) = m1; + *(htab_p-10) = m1; + *(htab_p-9) = m1; + *(htab_p-8) = m1; + *(htab_p-7) = m1; + *(htab_p-6) = m1; + *(htab_p-5) = m1; + *(htab_p-4) = m1; + *(htab_p-3) = m1; + *(htab_p-2) = m1; + *(htab_p-1) = m1; + htab_p -= 16; + } while ((i -= 16) >= 0); + + for ( i += 16; i > 0; --i ) + *--htab_p = m1; } -/****************************************************************************** -* Routine to set current GIF version. All files open for write will be * -* using this version until next call to this routine. Version consists of * -* 3 characters as "87a" or "89a". No test is made to validate the version. * -******************************************************************************/ -void EGifSetGifVersion(char *Version) -{ - strncpy(&GifVersionPrefix[3], Version, 3); + +/* + * Clear out the hash table + */ +static void cl_block (){ /* table clear for block compress */ + + cl_hash ( (count_int) hsize ); + free_ent = ClearCode + 2; + clear_flg = 1; + + output( (code_int)ClearCode ); } -/****************************************************************************** -* This routine should be called before any other EGif calls, immediately * -* follows the GIF file openning. * -******************************************************************************/ -int EGifPutScreenDesc(GifFileType *GifFile, - int Width, int Height, int ColorRes, int BackGround, - ColorMapObject *ColorMap) -{ - int i; - GifByteType Buf[3]; - GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; - - if (Private->FileState & FILE_STATE_SCREEN) { - /* If already has screen descriptor - something is wrong! */ - _GifError = E_GIF_ERR_HAS_SCRN_DSCR; - return GIF_ERROR; - } - if (!IS_WRITEABLE(Private)) { - /* This file was NOT open for writing: */ - _GifError = E_GIF_ERR_NOT_WRITEABLE; - return GIF_ERROR; +static void compress( int init_bits, FILE* outfile, ifunptr ReadValue){ + register long fcode; + register code_int i /* = 0 */; + register int c; + register code_int ent; + register code_int disp; + register code_int hsize_reg; + register int hshift; + + /* + * Set up the globals: g_init_bits - initial number of bits + * g_outfile - pointer to output file + */ + g_init_bits = init_bits; + g_outfile = outfile; + + /* + * Set up the necessary values + */ + offset = 0; + out_count = 0; + clear_flg = 0; + in_count = 1; + maxcode = MAXCODE(n_bits = g_init_bits); + + ClearCode = (1 << (init_bits - 1)); + EOFCode = ClearCode + 1; + free_ent = ClearCode + 2; + + char_init(); + + ent = GIFNextPixel( ReadValue ); + + hshift = 0; + for ( fcode = (long) hsize; fcode < 65536L; fcode *= 2L ) + ++hshift; + hshift = 8 - hshift; /* set hash code range bound */ + + hsize_reg = hsize; + cl_hash( (count_int) hsize_reg); /* clear hash table */ + + output( (code_int)ClearCode ); + + while ( (c = GIFNextPixel( ReadValue )) != EOF ) { + + ++in_count; + + fcode = (long) (((long) c << maxbits) + ent); + i = (((code_int)c << hshift) ^ ent); /* xor hashing */ + + if ( HashTabOf (i) == fcode ) { + ent = CodeTabOf (i); + continue; + } else if ( (long)HashTabOf (i) < 0 ) /* empty slot */ + goto nomatch; + disp = hsize_reg - i; /* secondary hash (after G. Knott) */ + if ( i == 0 ) + disp = 1; +probe: + if ( (i -= disp) < 0 ) + i += hsize_reg; + + if ( HashTabOf (i) == fcode ) { + ent = CodeTabOf (i); + continue; } + if ( (long)HashTabOf (i) > 0 ) + goto probe; +nomatch: + output ( (code_int) ent ); + ++out_count; + ent = c; + if ( free_ent < maxmaxcode ) { + CodeTabOf (i) = free_ent++; /* code -> hashtable */ + HashTabOf (i) = fcode; + } else + cl_block(); + } + /* + * Put out the final code. + */ + output( (code_int)ent ); + ++out_count; + output( (code_int) EOFCode ); +} - /* First write the version prefix into the file. */ - if (WRITE(GifFile, GifVersionPrefix, strlen(GifVersionPrefix)) != strlen(GifVersionPrefix)) { - _GifError = E_GIF_ERR_WRITE_FAILED; - return GIF_ERROR; - } - GifFile->SWidth = Width; - GifFile->SHeight = Height; - GifFile->SColorResolution = ColorRes; - GifFile->SBackGroundColor = BackGround; - if(ColorMap) - GifFile->SColorMap=MakeMapObject(ColorMap->ColorCount,ColorMap->Colors); - else - GifFile->SColorMap=NULL; - - /* Put the screen descriptor into the file: */ - EGifPutWord(Width, GifFile); - EGifPutWord(Height, GifFile); - Buf[0] = (ColorMap ? 0x80 : 0x00) | - ((ColorRes - 1) << 4) | - (ColorMap->BitsPerPixel - 1); - Buf[1] = BackGround; - Buf[2] = 0; - WRITE(GifFile, Buf, 3); - - /* If we have Global color map - dump it also: */ - if (ColorMap != NULL) - for (i = 0; i < ColorMap->ColorCount; i++) { - /* Put the ColorMap out also: */ - Buf[0] = ColorMap->Colors[i].Red; - Buf[1] = ColorMap->Colors[i].Green; - Buf[2] = ColorMap->Colors[i].Blue; - if (WRITE(GifFile, Buf, 3) != 3) { - _GifError = E_GIF_ERR_WRITE_FAILED; - return GIF_ERROR; - } - } - - /* Mark this file as has screen descriptor, and no pixel written yet: */ - Private->FileState |= FILE_STATE_SCREEN; - - return GIF_OK; +/* + * Write out a word to the GIF file + */ +static void Putword( int w, FILE* fp){ + fputc( w & 0xff, fp ); + fputc( (w / 256) & 0xff, fp ); } -/****************************************************************************** -* This routine should be called before any attemp to dump an image - any * -* call to any of the pixel dump routines. * -******************************************************************************/ -int EGifPutImageDesc(GifFileType *GifFile, - int Left, int Top, int Width, int Height, int Interlace, - ColorMapObject *ColorMap) -{ - int i; - GifByteType Buf[3]; - GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; - - if (Private->FileState & FILE_STATE_IMAGE && -#if defined(__GNUC__) - Private->PixelCount > 0xffff0000UL) { -#else - Private->PixelCount > 0xffff0000) { -#endif - /* If already has active image descriptor - something is wrong! */ - _GifError = E_GIF_ERR_HAS_IMAG_DSCR; - return GIF_ERROR; - } - if (!IS_WRITEABLE(Private)) { - /* This file was NOT open for writing: */ - _GifError = E_GIF_ERR_NOT_WRITEABLE; - return GIF_ERROR; - } - GifFile->Image.Left = Left; - GifFile->Image.Top = Top; - GifFile->Image.Width = Width; - GifFile->Image.Height = Height; - GifFile->Image.Interlace = Interlace; - if(ColorMap) - GifFile->Image.ColorMap =MakeMapObject(ColorMap->ColorCount,ColorMap->Colors); - else - GifFile->Image.ColorMap = NULL; - - /* Put the image descriptor into the file: */ - Buf[0] = ','; /* Image seperator character. */ - WRITE(GifFile, Buf, 1); - EGifPutWord(Left, GifFile); - EGifPutWord(Top, GifFile); - EGifPutWord(Width, GifFile); - EGifPutWord(Height, GifFile); - Buf[0] = (ColorMap ? 0x80 : 0x00) | - (Interlace ? 0x40 : 0x00) | - (ColorMap ? ColorMap->BitsPerPixel - 1 : 0); - WRITE(GifFile, Buf, 1); - - /* If we have Global color map - dump it also: */ - if (ColorMap != NULL) - for (i = 0; i < ColorMap->ColorCount; i++) { - /* Put the ColorMap out also: */ - Buf[0] = ColorMap->Colors[i].Red; - Buf[1] = ColorMap->Colors[i].Green; - Buf[2] = ColorMap->Colors[i].Blue; - if (WRITE(GifFile, Buf, 3) != 3) { - _GifError = E_GIF_ERR_WRITE_FAILED; - return GIF_ERROR; - } - } - if (GifFile->SColorMap == NULL && GifFile->Image.ColorMap == NULL) - { - _GifError = E_GIF_ERR_NO_COLOR_MAP; - return GIF_ERROR; - } - /* Mark this file as has screen descriptor: */ - Private->FileState |= FILE_STATE_IMAGE; - Private->PixelCount = (long) Width * (long) Height; +static void GIFEncode( FILE* fp, + int GWidth, int GHeight, + int GInterlace, int Background, int Transparent, + int BitsPerPixel, int Red[], int Green[], int Blue[], + ifunptr GetPixel){ + int B; + int RWidth, RHeight; + int LeftOfs, TopOfs; + int Resolution; + int ColorMapSize; + int InitCodeSize; + int i; - EGifSetupCompress(GifFile); /* Reset compress algorithm parameters. */ + /* reset stuff for output */ + cur_accum = 0; + cur_bits = 0; + free_ent = 0; - return GIF_OK; + Interlace = GInterlace; + + ColorMapSize = 1 << BitsPerPixel; + + RWidth = Width = GWidth; + RHeight = Height = GHeight; + LeftOfs = TopOfs = 0; + + Resolution = BitsPerPixel; + + /* + * Calculate number of bits we are expecting + */ + CountDown = (long)Width * (long)Height; + + /* + * Indicate which pass we are on (if interlace) + */ + Pass = 0; + + /* + * The initial code size + */ + if( BitsPerPixel <= 1 ) + InitCodeSize = 2; + else + InitCodeSize = BitsPerPixel; + + /* + * Set up the current x and y position + */ + curx = cury = 0; + + /* + * Write the Magic header + */ + fwrite( Transparent < 0 ? "GIF87a" : "GIF89a", 1, 6, fp ); + + /* + * Write out the screen width and height + */ + Putword( RWidth, fp ); + Putword( RHeight, fp ); + + /* + * Indicate that there is a global colour map + */ + B = 0x80; /* Yes, there is a color map */ + + /* + * OR in the resolution + */ + B |= (Resolution - 1) << 5; + + /* + * OR in the Bits per Pixel + */ + B |= (BitsPerPixel - 1); + + /* + * Write it out + */ + fputc( B, fp ); + + /* + * Write out the Background colour + */ + fputc( Background, fp ); + + /* + * Byte of 0's (future expansion) + */ + fputc( 0, fp ); + + /* + * Write out the Global Colour Map + */ + for( i=0; i<ColorMapSize; ++i ) { + fputc( Red[i], fp ); + fputc( Green[i], fp ); + fputc( Blue[i], fp ); + } + + /* + * Write out extension for transparent colour index, if necessary. + */ + if ( Transparent >= 0 ) { + fputc( '!', fp ); + fputc( 0xf9, fp ); + fputc( 4, fp ); + fputc( 1, fp ); + fputc( 0, fp ); + fputc( 0, fp ); + fputc( Transparent, fp ); + fputc( 0, fp ); + } + + /* + * Write an Image separator + */ + fputc( ',', fp ); + + /* + * Write the Image header + */ + + Putword( LeftOfs, fp ); + Putword( TopOfs, fp ); + Putword( Width, fp ); + Putword( Height, fp ); + + /* + * Write out whether or not the image is interlaced + */ + if( Interlace ) + fputc( 0x40, fp ); + else + fputc( 0x00, fp ); + + /* + * Write out the initial code size + */ + fputc( InitCodeSize, fp ); + + /* + * Go and actually compress the data + */ + compress( InitCodeSize+1, fp, GetPixel ); + + /* + * Write out a Zero-length packet (to end the series) + */ + fputc( 0, fp ); + + /* + * Write the GIF file terminator + */ + fputc( ';', fp ); + } -/****************************************************************************** -* Put one full scanned line (Line) of length LineLen into GIF file. * -******************************************************************************/ -int EGifPutLine(GifFileType *GifFile, GifPixelType *Line, int LineLen) -{ - int i; - GifPixelType Mask; - GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; - - if (!IS_WRITEABLE(Private)) { - /* This file was NOT open for writing: */ - _GifError = E_GIF_ERR_NOT_WRITEABLE; - return GIF_ERROR; - } - - if (!LineLen) - LineLen = GifFile->Image.Width; - if (Private->PixelCount < (unsigned)LineLen) { - _GifError = E_GIF_ERR_DATA_TOO_BIG; - return GIF_ERROR; - } - Private->PixelCount -= LineLen; - /* Make sure the codes are not out of bit range, as we might generate */ - /* wrong code (because of overflow when we combine them) in this case: */ - Mask = CodeMask[Private->BitsPerPixel]; - for (i = 0; i < LineLen; i++) Line[i] &= Mask; +/* ------------------------------------------------------------------ + PPM quantization + ------------------------------------------------------------------ */ - return EGifCompressLine(GifFile, Line, LineLen); -} +/* #define LARGE_NORM */ +#define LARGE_LUM -/****************************************************************************** -* This routine should be called last, to close GIF file. * -******************************************************************************/ -int EGifCloseFile(GifFileType *GifFile) -{ - GifByteType Buf; - GifFilePrivateType *Private; - - if (GifFile == NULL) return GIF_ERROR; - - Private = (GifFilePrivateType *) GifFile->Private; - if (!IS_WRITEABLE(Private)) { - /* This file was NOT open for writing: */ - _GifError = E_GIF_ERR_NOT_WRITEABLE; - return GIF_ERROR; - } +/* #define REP_CENTER_BOX */ +/* #define REP_AVERAGE_COLORS */ +#define REP_AVERAGE_PIXELS - Buf = ';'; - WRITE(GifFile, &Buf, 1); +typedef struct box* box_vector; +struct box{ + int ind; + int colors; + int sum; +}; - if (GifFile->Image.ColorMap) - FreeMapObject(GifFile->Image.ColorMap); - if (GifFile->SColorMap) - FreeMapObject(GifFile->SColorMap); - if (Private) { - if (Private->HashTable) free((char *) Private->HashTable); - free((char *) Private); - } - free(GifFile); - return GIF_OK; +static int redcompare( const void *ch1, const void *ch2 ){ + return (int) PPM_GETR( ((colorhist_vector)ch1)->color ) - + (int) PPM_GETR( ((colorhist_vector)ch2)->color ); } -/****************************************************************************** -* Put 2 bytes (word) into the given file: * -******************************************************************************/ -static int EGifPutWord(int Word, GifFileType *GifFile) -{ - char c[2]; - - c[0] = Word & 0xff; - c[1] = (Word >> 8) & 0xff; - if (WRITE(GifFile, c, 2) == 2) - return GIF_OK; - else - return GIF_ERROR; +static int greencompare( const void *ch1, const void *ch2 ) { + return (int) PPM_GETG(((colorhist_vector) ch1)->color ) - + (int) PPM_GETG( ((colorhist_vector)ch2)->color ); } -/****************************************************************************** -* Setup the LZ compression for this image: * -******************************************************************************/ -static int EGifSetupCompress(GifFileType *GifFile) -{ - int BitsPerPixel; - GifByteType Buf; - GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; - - /* Test and see what color map to use, and from it # bits per pixel: */ - if (GifFile->Image.ColorMap) - BitsPerPixel = GifFile->Image.ColorMap->BitsPerPixel; - else if (GifFile->SColorMap) - BitsPerPixel = GifFile->SColorMap->BitsPerPixel; - else { - _GifError = E_GIF_ERR_NO_COLOR_MAP; - return GIF_ERROR; - } +static int bluecompare(const void *ch1, const void *ch2 ) { + return (int) PPM_GETB(((colorhist_vector)ch1)->color ) - + (int) PPM_GETB(((colorhist_vector)ch2)->color ); +} - Buf = BitsPerPixel = (BitsPerPixel < 2 ? 2 : BitsPerPixel); - WRITE(GifFile, &Buf, 1); /* Write the Code size to file. */ - - Private->Buf[0] = 0; /* Nothing was output yet. */ - Private->BitsPerPixel = BitsPerPixel; - Private->ClearCode = (1 << BitsPerPixel); - Private->EOFCode = Private->ClearCode + 1; - Private->RunningCode = Private->EOFCode + 1; - Private->RunningBits = BitsPerPixel + 1; /* Number of bits per code. */ - Private->MaxCode1 = 1 << Private->RunningBits; /* Max. code + 1. */ - Private->CrntCode = FIRST_CODE; /* Signal that this is first one! */ - Private->CrntShiftState = 0; /* No information in CrntShiftDWord. */ - Private->CrntShiftDWord = 0; - - /* Clear hash table and send Clear to make sure the decoder do the same. */ - _ClearHashTable(Private->HashTable); - - if (EGifCompressOutput(GifFile, Private->ClearCode) == GIF_ERROR) { - _GifError = E_GIF_ERR_DISK_IS_FULL; - return GIF_ERROR; - } - return GIF_OK; +static int sumcompare(const void *b1, const void *b2 ) { + return(((box_vector)b2)->sum - ((box_vector)b1)->sum); } -/****************************************************************************** -* The LZ compression routine: * -* This version compress the given buffer Line of length LineLen. * -* This routine can be called few times (one per scan line, for example), in * -* order the complete the whole image. * -******************************************************************************/ -static int EGifCompressLine(GifFileType *GifFile, GifPixelType *Line, - int LineLen) -{ - int i = 0, CrntCode, NewCode; - unsigned long NewKey; - GifPixelType Pixel; - GifHashTableType *HashTable; - GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; - - HashTable = Private->HashTable; - - if (Private->CrntCode == FIRST_CODE) /* Its first time! */ - CrntCode = Line[i++]; +/* + * Here is the fun part, the median-cut colormap generator. This is based + * on Paul Heckbert's paper "Color Image Quantization for Frame Buffer + * Display", SIGGRAPH '82 Proceedings, page 297. + */ + +static colorhist_vector mediancut( colorhist_vector chv, int colors, + int sum, pixval maxval, int newcolors ){ + colorhist_vector colormap; + box_vector bv; + register int bi, i; + int boxes; + + bv = (box_vector) malloc( sizeof(struct box) * newcolors ); + colormap = + (colorhist_vector) malloc( sizeof(struct colorhist_item) * newcolors ); + if ( bv == (box_vector) 0 || colormap == (colorhist_vector) 0 ) + Msg(ERROR, "GIF: out of memory" ); + for ( i = 0; i < newcolors; ++i ) + PPM_ASSIGN( colormap[i].color, 0, 0, 0 ); + + /* + * Set up the initial box. + */ + bv[0].ind = 0; + bv[0].colors = colors; + bv[0].sum = sum; + boxes = 1; + + /* + * Main loop: split boxes until we have enough. + */ + while ( boxes < newcolors ){ + register int indx, clrs; + int sm; + register int minr, maxr, ming, maxg, minb, maxb, v; + int halfsum, lowersum; + + /* + * Find the first splittable box. + */ + for ( bi = 0; bi < boxes; ++bi ) + if ( bv[bi].colors >= 2 ) + break; + if ( bi == boxes ) + break; /* ran out of colors! */ + indx = bv[bi].ind; + clrs = bv[bi].colors; + sm = bv[bi].sum; + + /* + * Go through the box finding the minimum and maximum of each + * component - the boundaries of the box. + */ + minr = maxr = PPM_GETR( chv[indx].color ); + ming = maxg = PPM_GETG( chv[indx].color ); + minb = maxb = PPM_GETB( chv[indx].color ); + for ( i = 1; i < clrs; ++i ){ + v = PPM_GETR( chv[indx + i].color ); + if ( v < minr ) minr = v; + if ( v > maxr ) maxr = v; + v = PPM_GETG( chv[indx + i].color ); + if ( v < ming ) ming = v; + if ( v > maxg ) maxg = v; + v = PPM_GETB( chv[indx + i].color ); + if ( v < minb ) minb = v; + if ( v > maxb ) maxb = v; + } + + /* + * Find the largest dimension, and sort by that component. I have + * included two methods for determining the "largest" dimension; + * first by simply comparing the range in RGB space, and second + * by transforming into luminosities before the comparison. You + * can switch which method is used by switching the commenting on + * the LARGE_ defines at the beginning of this source file. + */ +#ifdef LARGE_NORM + if ( maxr - minr >= maxg - ming && maxr - minr >= maxb - minb ) + qsort( (char*) &(chv[indx]), clrs, sizeof(struct colorhist_item), + redcompare ); + else if ( maxg - ming >= maxb - minb ) + qsort( (char*) &(chv[indx]), clrs, sizeof(struct colorhist_item), + greencompare ); + else + qsort( (char*) &(chv[indx]), clrs, sizeof(struct colorhist_item), + bluecompare ); +#endif + +#ifdef LARGE_LUM + pixel p; + float rl, gl, bl; + + PPM_ASSIGN(p, maxr - minr, 0, 0); + rl = PPM_LUMIN(p); + PPM_ASSIGN(p, 0, maxg - ming, 0); + gl = PPM_LUMIN(p); + PPM_ASSIGN(p, 0, 0, maxb - minb); + bl = PPM_LUMIN(p); + + if ( rl >= gl && rl >= bl ) + qsort( (char*) &(chv[indx]), clrs, sizeof(struct colorhist_item), + &redcompare ); + else if ( gl >= bl ) + qsort( (char*) &(chv[indx]), clrs, sizeof(struct colorhist_item), + &greencompare ); else - CrntCode = Private->CrntCode; /* Get last code in compression. */ - - while (i < LineLen) { /* Decode LineLen items. */ - Pixel = Line[i++]; /* Get next pixel from stream. */ - /* Form a new unique key to search hash table for the code combines */ - /* CrntCode as Prefix string with Pixel as postfix char. */ - NewKey = (((unsigned long) CrntCode) << 8) + Pixel; - if ((NewCode = _ExistsHashTable(HashTable, NewKey)) >= 0) { - /* This Key is already there, or the string is old one, so */ - /* simple take new code as our CrntCode: */ - CrntCode = NewCode; - } - else { - /* Put it in hash table, output the prefix code, and make our */ - /* CrntCode equal to Pixel. */ - if (EGifCompressOutput(GifFile, CrntCode) - == GIF_ERROR) { - _GifError = E_GIF_ERR_DISK_IS_FULL; - return GIF_ERROR; - } - CrntCode = Pixel; - - /* If however the HashTable if full, we send a clear first and */ - /* Clear the hash table. */ - if (Private->RunningCode >= LZ_MAX_CODE) { - /* Time to do some clearance: */ - if (EGifCompressOutput(GifFile, Private->ClearCode) - == GIF_ERROR) { - _GifError = E_GIF_ERR_DISK_IS_FULL; - return GIF_ERROR; - } - Private->RunningCode = Private->EOFCode + 1; - Private->RunningBits = Private->BitsPerPixel + 1; - Private->MaxCode1 = 1 << Private->RunningBits; - _ClearHashTable(HashTable); - } - else { - /* Put this unique key with its relative Code in hash table: */ - _InsertHashTable(HashTable, NewKey, Private->RunningCode++); - } - } + qsort( (char*) &(chv[indx]), clrs, sizeof(struct colorhist_item), + &bluecompare ); +#endif + + /* + * Now find the median based on the counts, so that about half the + * pixels (not colors, pixels) are in each subdivision. + */ + lowersum = chv[indx].value; + halfsum = sm / 2; + for ( i = 1; i < clrs - 1; ++i ){ + if ( lowersum >= halfsum ) + break; + lowersum += chv[indx + i].value; + } + + /* + * Split the box, and sort to bring the biggest boxes to the top. + */ + bv[bi].colors = i; + bv[bi].sum = lowersum; + bv[boxes].ind = indx + i; + bv[boxes].colors = clrs - i; + bv[boxes].sum = sm - lowersum; + ++boxes; + qsort( (char*) bv, boxes, sizeof(struct box), sumcompare ); + } + /* + * Ok, we've got enough boxes. Now choose a representative color for + * each box. There are a number of possible ways to make this choice. + * One would be to choose the center of the box; this ignores any structure + * within the boxes. Another method would be to average all the colors in + * the box - this is the method specified in Heckbert's paper. A third + * method is to average all the pixels in the box. You can switch which + * method is used by switching the commenting on the REP_ defines at + * the beginning of this source file. + */ + for ( bi = 0; bi < boxes; ++bi ){ + +#ifdef REP_CENTER_BOX + register int indx = bv[bi].ind; + register int clrs = bv[bi].colors; + register int minr, maxr, ming, maxg, minb, maxb, v; + + minr = maxr = PPM_GETR( chv[indx].color ); + ming = maxg = PPM_GETG( chv[indx].color ); + minb = maxb = PPM_GETB( chv[indx].color ); + for ( i = 1; i < clrs; ++i ){ + v = PPM_GETR( chv[indx + i].color ); + minr = min( minr, v ); + maxr = max( maxr, v ); + v = PPM_GETG( chv[indx + i].color ); + ming = min( ming, v ); + maxg = max( maxg, v ); + v = PPM_GETB( chv[indx + i].color ); + minb = min( minb, v ); + maxb = max( maxb, v ); } + PPM_ASSIGN( colormap[bi].color, ( minr + maxr ) / 2, ( ming + maxg ) / 2, + ( minb + maxb ) / 2 ); +#endif - /* Preserve the current state of the compression algorithm: */ - Private->CrntCode = CrntCode; - - if (Private->PixelCount == 0) - { - /* We are done - output last Code and flush output buffers: */ - if (EGifCompressOutput(GifFile, CrntCode) - == GIF_ERROR) { - _GifError = E_GIF_ERR_DISK_IS_FULL; - return GIF_ERROR; - } - if (EGifCompressOutput(GifFile, Private->EOFCode) - == GIF_ERROR) { - _GifError = E_GIF_ERR_DISK_IS_FULL; - return GIF_ERROR; - } - if (EGifCompressOutput(GifFile, FLUSH_OUTPUT) == GIF_ERROR) { - _GifError = E_GIF_ERR_DISK_IS_FULL; - return GIF_ERROR; - } +#ifdef REP_AVERAGE_COLORS + register int indx = bv[bi].ind; + register int clrs = bv[bi].colors; + register long r = 0, g = 0, b = 0; + + for ( i = 0; i < clrs; ++i ){ + r += PPM_GETR( chv[indx + i].color ); + g += PPM_GETG( chv[indx + i].color ); + b += PPM_GETB( chv[indx + i].color ); } + r = r / clrs; + g = g / clrs; + b = b / clrs; + PPM_ASSIGN( colormap[bi].color, r, g, b ); +#endif - return GIF_OK; +#ifdef REP_AVERAGE_PIXELS + register int indx = bv[bi].ind; + register int clrs = bv[bi].colors; + register long r = 0, g = 0, b = 0, sum = 0; + + for ( i = 0; i < clrs; ++i ){ + r += PPM_GETR( chv[indx + i].color ) * chv[indx + i].value; + g += PPM_GETG( chv[indx + i].color ) * chv[indx + i].value; + b += PPM_GETB( chv[indx + i].color ) * chv[indx + i].value; + sum += chv[indx + i].value; + } + r = r / sum; + if ( r > (long)maxval ) r = maxval; /* avoid math errors */ + g = g / sum; + if ( g > (long)maxval ) g = maxval; + b = b / sum; + if ( b > (long)maxval ) b = maxval; + PPM_ASSIGN( colormap[bi].color, r, g, b ); +#endif + } + + /* + * All done. + */ + return colormap; } -/****************************************************************************** -* The LZ compression output routine: * -* This routine is responsible for the compression of the bit stream into * -* 8 bits (bytes) packets. * -* Returns GIF_OK if written succesfully. * -******************************************************************************/ -static int EGifCompressOutput(GifFileType *GifFile, int Code) -{ - GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; - int retval = GIF_OK; - - if (Code == FLUSH_OUTPUT) { - while (Private->CrntShiftState > 0) { - /* Get Rid of what is left in DWord, and flush it. */ - if (EGifBufferedOutput(GifFile, Private->Buf, - Private->CrntShiftDWord & 0xff) == GIF_ERROR) - retval = GIF_ERROR; - Private->CrntShiftDWord >>= 8; - Private->CrntShiftState -= 8; - } - Private->CrntShiftState = 0; /* For next time. */ - if (EGifBufferedOutput(GifFile, Private->Buf, - FLUSH_OUTPUT) == GIF_ERROR) - retval = GIF_ERROR; - } - else { - Private->CrntShiftDWord |= ((long) Code) << Private->CrntShiftState; - Private->CrntShiftState += Private->RunningBits; - while (Private->CrntShiftState >= 8) { - /* Dump out full bytes: */ - if (EGifBufferedOutput(GifFile, Private->Buf, - Private->CrntShiftDWord & 0xff) == GIF_ERROR) - retval = GIF_ERROR; - Private->CrntShiftDWord >>= 8; - Private->CrntShiftState -= 8; - } - } - /* If code cannt fit into RunningBits bits, must raise its size. Note */ - /* however that codes above 4095 are used for special signaling. */ - if (Private->RunningCode >= Private->MaxCode1 && Code <= 4095) { - Private->MaxCode1 = 1 << ++Private->RunningBits; - } - return retval; -} +/* ------------------------------------------------------------------ + GL2GIF public routine + ------------------------------------------------------------------ */ + +#define FS_SCALE 1024 +#define MAXCOL2 32767 + +void create_gif(FILE *outfile, int width, int height, + int dither, int sort, int interlace, + int transparency, int bg_r, int bg_g, int bg_b){ + + int i,j,k,transparent,rows,cols; + pixel transcolor; + colorhist_vector chv, colormap; + int BitsPerPixel, usehash; + unsigned char *RedBuffer, *GreenBuffer, *BlueBuffer; + pixval maxval=MAXCOL2, newmaxval; + colorhash_table cht; + register pixel* pP; + register int col, row, limitcol, ind; + int newcolors=255; + long *thisrerr, *nextrerr, *thisgerr, *nextgerr; + long *thisberr, *nextberr, *temperr; + register long sr=0, sg=0, sb=0, err=0; + int fs_direction; + + /* This is stupid, but I couldn't figure out how to pack the data + directly from OpenGL frame buffer into the approproate format into + unsigned long pixel[][]. */ + + glPixelStorei(GL_PACK_ALIGNMENT,1); + glPixelStorei(GL_UNPACK_ALIGNMENT,1); + RedBuffer = (unsigned char *)Malloc(height*width*sizeof(unsigned char)); + GreenBuffer = (unsigned char *)Malloc(height*width*sizeof(unsigned char)); + BlueBuffer = (unsigned char *)Malloc(height*width*sizeof(unsigned char)); + glReadPixels(0,0,width,height,GL_RED,GL_UNSIGNED_BYTE,RedBuffer); + glReadPixels(0,0,width,height,GL_GREEN,GL_UNSIGNED_BYTE,GreenBuffer); + glReadPixels(0,0,width,height,GL_BLUE,GL_UNSIGNED_BYTE,BlueBuffer); -/****************************************************************************** -* This routines buffers the given characters until 255 characters are ready * -* to be output. If Code is equal to -1 the buffer is flushed (EOF). * -* The buffer is Dumped with first byte as its size, as GIF format requires. * -* Returns GIF_OK if written succesfully. * -******************************************************************************/ -static int EGifBufferedOutput(GifFileType *GifFile, GifByteType *Buf, int c) -{ - if (c == FLUSH_OUTPUT) { - /* Flush everything out. */ - if (Buf[0] != 0 && WRITE(GifFile, Buf, Buf[0]+1) != (unsigned)(Buf[0] + 1)) - { - _GifError = E_GIF_ERR_WRITE_FAILED; - return GIF_ERROR; - } - /* Mark end of compressed data, by an empty block (see GIF doc): */ - Buf[0] = 0; - if (WRITE(GifFile, Buf, 1) != 1) - { - _GifError = E_GIF_ERR_WRITE_FAILED; - return GIF_ERROR; - } - } - else { - if (Buf[0] == 255) { - /* Dump out this buffer - it is full: */ - if (WRITE(GifFile, Buf, Buf[0] + 1) != (unsigned)(Buf[0] + 1)) - { - _GifError = E_GIF_ERR_WRITE_FAILED; - return GIF_ERROR; - } - Buf[0] = 0; - } - Buf[++Buf[0]] = c; - } + static_pixels = (pixel**)Malloc(height*sizeof(pixel*)); + for(i = 0 ; i<height ; i++) + static_pixels[i] = (pixel*)Malloc(3*width*sizeof(pixel)); - return GIF_OK; -} + for(i = 0 ; i<height ; i++) + for(j = 0 ; j<width ; j++) + PPM_ASSIGN(static_pixels[height-1-i][j], + RedBuffer[i*width+j], + GreenBuffer[i*width+j], + BlueBuffer[i*width+j]); -static void QuitGifError(GifFileType *GifFile) -{ - PrintGifError(); - if (GifFile != NULL) EGifCloseFile(GifFile); -} + Free(RedBuffer); + Free(GreenBuffer); + Free(BlueBuffer); -static void SaveGif(FILE *fp, - GifByteType *OutputBuffer, - ColorMapObject *OutputColorMap, - int ExpColorMapSize, int Width, int Height){ - int i; - GifFileType *GifFile; - GifByteType *Ptr; + /* Try to compute color histogram */ - if ((GifFile = EGifOpenFileHandle(fp, 1)) == NULL){ - QuitGifError(GifFile); - return; - } + chv = ppm_computecolorhist( static_pixels, width, height, MAX_GIFCOLORS, + &static_nbcolors ); - if (EGifPutScreenDesc(GifFile, Width, Height, ExpColorMapSize, 0, OutputColorMap) == GIF_ERROR){ - QuitGifError(GifFile); - return; - } - if (EGifPutImageDesc(GifFile, 0, 0, Width, Height, FALSE, NULL) == GIF_ERROR){ - QuitGifError(GifFile); - return; - } + /* Fuck, there are more than 256 colors in the picture: we need to quantize */ - Ptr = &OutputBuffer[(Height-1)*Width] ; - for (i = 0; i < Height; i++) { - if (EGifPutLine(GifFile, Ptr, Width) == GIF_ERROR){ - QuitGifError(GifFile); - return; + if ( chv == (colorhist_vector) 0 ){ + + Msg(DEBUG, "GIF: Too many colors in image"); + + rows = height ; + cols = width ; + + while(1){ + Msg(DEBUG, "GIF: making histogram..." ); + chv = ppm_computecolorhist(static_pixels, width, height, MAXCOL2, + &static_nbcolors ); + if ( chv != (colorhist_vector) 0 ) + break; + Msg(DEBUG, "GIF: still too many colors!" ); + newmaxval = maxval / 2; + Msg(DEBUG, "GIF: scaling colors from maxval=%d to maxval=%d to improve clustering...", + maxval, newmaxval ); + for ( row = 0; row < rows; ++row ) + for ( col = 0, pP = static_pixels[row]; col < cols; ++col, ++pP ) + PPM_DEPTH( *pP, *pP, maxval, newmaxval ); + maxval = newmaxval; } - Ptr -= Width; - } + Msg(DEBUG, "GIF: %d colors found", static_nbcolors ); + Msg(DEBUG, "GIF: choosing %d colors...", newcolors ); + colormap = mediancut( chv, static_nbcolors, rows * cols, maxval, newcolors ); + + cht = ppm_alloccolorhash( ); + + ppm_freecolorhist( chv ); + + /* map the colors in the image to their closest match in the new colormap */ + Msg(DEBUG, "GIF: mapping image to new colors..." ); + usehash = 1; + + if ( dither ){ + Msg(DEBUG, "GIF: Floyd-Steinberg dithering is selected..." ); + /* Initialize Floyd-Steinberg error vectors. */ + thisrerr = (long*) Malloc( (cols + 2)* sizeof(long) ); + nextrerr = (long*) Malloc( (cols + 2)* sizeof(long) ); + thisgerr = (long*) Malloc( (cols + 2)* sizeof(long) ); + nextgerr = (long*) Malloc( (cols + 2)* sizeof(long) ); + thisberr = (long*) Malloc( (cols + 2)* sizeof(long) ); + nextberr = (long*) Malloc( (cols + 2)* sizeof(long) ); + /* srand( (int) ( time( 0 ) ^ getpid( ) ) ); */ + for ( col = 0; col < cols + 2; ++col ){ + thisrerr[col] = rand( ) % ( FS_SCALE * 2 ) - FS_SCALE; + thisgerr[col] = rand( ) % ( FS_SCALE * 2 ) - FS_SCALE; + thisberr[col] = rand( ) % ( FS_SCALE * 2 ) - FS_SCALE; + /* (random errors in [-1 .. 1]) */ + } + fs_direction = 1; + } + for ( row = 0; row < rows; ++row ){ + + if ( dither ) + for ( col = 0; col < cols + 2; ++col ) + nextrerr[col] = nextgerr[col] = nextberr[col] = 0; + + if ( ( ! dither ) || fs_direction ){ + col = 0; + limitcol = cols; + pP = static_pixels[row]; + } + else{ + col = cols - 1; + limitcol = -1; + pP = &(static_pixels[row][col]); + } + + do{ + + if ( dither ){ + /* Use Floyd-Steinberg errors to adjust actual color. */ + sr = PPM_GETR(*pP) + thisrerr[col + 1] / FS_SCALE; + sg = PPM_GETG(*pP) + thisgerr[col + 1] / FS_SCALE; + sb = PPM_GETB(*pP) + thisberr[col + 1] / FS_SCALE; + if ( sr < 0 ) sr = 0; + else if ( sr > (long)maxval ) sr = maxval; + if ( sg < 0 ) sg = 0; + else if ( sg > (long)maxval ) sg = maxval; + if ( sb < 0 ) sb = 0; + else if ( sb > (long)maxval ) sb = maxval; + PPM_ASSIGN( *pP, sr, sg, sb ); + } + + /* Check hash table to see if we have already matched this color. */ + ind = ppm_lookupcolor( cht, pP ); + if ( ind == -1 ){ /* No; search colormap for closest match. */ + register int i, r1, g1, b1, r2, g2, b2; + register long dist, newdist; + r1 = PPM_GETR( *pP ); + g1 = PPM_GETG( *pP ); + b1 = PPM_GETB( *pP ); + dist = 2000000000; + for ( i = 0; i < newcolors; ++i ){ + r2 = PPM_GETR( colormap[i].color ); + g2 = PPM_GETG( colormap[i].color ); + b2 = PPM_GETB( colormap[i].color ); + newdist = + ( r1 - r2 ) * ( r1 - r2 ) + + ( g1 - g2 ) * ( g1 - g2 ) + + ( b1 - b2 ) * ( b1 - b2 ); + if ( newdist < dist ){ + ind = i; + dist = newdist; + } + } + if ( usehash ){ + if ( ppm_addtocolorhash( cht, pP, ind ) < 0 ){ + Msg(WARNING, "GIF: Out of memory adding to hash table, proceeding without it"); + usehash = 0; + } + } + } + + if ( dither ){ + /* Propagate Floyd-Steinberg error terms. */ + if ( fs_direction ){ + err = ( sr - (long) PPM_GETR( colormap[ind].color ) ) * FS_SCALE; + thisrerr[col + 2] += ( err * 7 ) / 16; + nextrerr[col ] += ( err * 3 ) / 16; + nextrerr[col + 1] += ( err * 5 ) / 16; + nextrerr[col + 2] += ( err ) / 16; + err = ( sg - (long) PPM_GETG( colormap[ind].color ) ) * FS_SCALE; + thisgerr[col + 2] += ( err * 7 ) / 16; + nextgerr[col ] += ( err * 3 ) / 16; + nextgerr[col + 1] += ( err * 5 ) / 16; + nextgerr[col + 2] += ( err ) / 16; + err = ( sb - (long) PPM_GETB( colormap[ind].color ) ) * FS_SCALE; + thisberr[col + 2] += ( err * 7 ) / 16; + nextberr[col ] += ( err * 3 ) / 16; + nextberr[col + 1] += ( err * 5 ) / 16; + nextberr[col + 2] += ( err ) / 16; + } + else{ + err = ( sr - (long) PPM_GETR( colormap[ind].color ) ) * FS_SCALE; + thisrerr[col ] += ( err * 7 ) / 16; + nextrerr[col + 2] += ( err * 3 ) / 16; + nextrerr[col + 1] += ( err * 5 ) / 16; + nextrerr[col ] += ( err ) / 16; + err = ( sg - (long) PPM_GETG( colormap[ind].color ) ) * FS_SCALE; + thisgerr[col ] += ( err * 7 ) / 16; + nextgerr[col + 2] += ( err * 3 ) / 16; + nextgerr[col + 1] += ( err * 5 ) / 16; + nextgerr[col ] += ( err ) / 16; + err = ( sb - (long) PPM_GETB( colormap[ind].color ) ) * FS_SCALE; + thisberr[col ] += ( err * 7 ) / 16; + nextberr[col + 2] += ( err * 3 ) / 16; + nextberr[col + 1] += ( err * 5 ) / 16; + nextberr[col ] += ( err ) / 16; + } + } + + *pP = colormap[ind].color; + + if ( ( ! dither ) || fs_direction ){ + ++col; + ++pP; + } + else{ + --col; + --pP; + } + } + while ( col != limitcol ); + + if ( dither ){ + temperr = thisrerr; + thisrerr = nextrerr; + nextrerr = temperr; + temperr = thisgerr; + thisgerr = nextgerr; + nextgerr = temperr; + temperr = thisberr; + thisberr = nextberr; + nextberr = temperr; + fs_direction = ! fs_direction; + } - if (EGifCloseFile(GifFile) == GIF_ERROR) - QuitGifError(GifFile); -} + } -void create_gif(FILE *fp, int width, int height){ - GifByteType *RedBuffer, *GreenBuffer, *BlueBuffer, *OutputBuffer = NULL; - ColorMapObject *OutputColorMap = NULL; + if(cht) ppm_freecolorhash(cht); + if(dither){ + Free(thisrerr); + Free(nextrerr); + Free(thisgerr); + Free(nextgerr); + Free(thisberr); + Free(nextberr); + } + chv = ppm_computecolorhist( static_pixels, width, height, MAX_GIFCOLORS, + &static_nbcolors ); - _GifError = 0; - ExpNumOfColors = 8; - ColorMapSize = 256; - - RedBuffer = (unsigned char *)malloc(height*width*sizeof(unsigned char)); - GreenBuffer = (unsigned char *)malloc(height*width*sizeof(unsigned char)); - BlueBuffer = (unsigned char *)malloc(height*width*sizeof(unsigned char)); + } + + /* We now have a colormap of maximum 256 colors */ - if(!RedBuffer || !GreenBuffer || !BlueBuffer){ - fprintf(stderr, "Error: Failed to allocate memory for RGB Buffers\n"); - return; + for ( i = 0; i < static_nbcolors; ++i ){ + static_red[i] = PPM_GETR( chv[i].color ); + static_green[i] = PPM_GETG( chv[i].color ); + static_blue[i] = PPM_GETB( chv[i].color ); } - glReadPixels(0,0,width,height,GL_RED,GL_UNSIGNED_BYTE,RedBuffer); - glReadPixels(0,0,width,height,GL_GREEN,GL_UNSIGNED_BYTE,GreenBuffer); - glReadPixels(0,0,width,height,GL_BLUE,GL_UNSIGNED_BYTE,BlueBuffer); - - if (!(OutputColorMap = MakeMapObject(ColorMapSize, NULL))){ - fprintf(stderr, "Warning: Failed to allocate memory for ColorMap\n"); - return; + /* Sort the colormap */ + for (i=0 ; i<static_nbcolors ; i++) + static_permi[i] = i; + if (sort) { + Msg(DEBUG, "GIF: sorting colormap"); + for (i=0 ; i<static_nbcolors ; i++) + for (j=i+1 ; j<static_nbcolors ; j++) + if (((static_red[i]*MAX_GIFCOLORS)+static_green[i])*MAX_GIFCOLORS+static_blue[i] > + ((static_red[j]*MAX_GIFCOLORS)+static_green[j])*MAX_GIFCOLORS+static_blue[j]) { + k = static_permi[i]; static_permi[i] = static_permi[j]; static_permi[j] = k; + k = static_red[i]; static_red[i] = static_red[j]; static_red[j] = k; + k = static_green[i]; static_green[i] = static_green[j]; static_green[j] = k; + k = static_blue[i]; static_blue[i] = static_blue[j]; static_blue[j] = k; + } } + for (i=0 ; i<static_nbcolors ; i++) + static_perm[static_permi[i]]=i; + + BitsPerPixel = colorstobpp( static_nbcolors ); - if(!(OutputBuffer = (GifByteType *) malloc(width * height * sizeof(GifByteType)))){ - fprintf(stderr, "Error: Failed to allocate memory for Output Buffer\n"); - return; + /* And make a hash table for fast lookup. */ + static_cht = ppm_colorhisttocolorhash( chv, static_nbcolors ); + ppm_freecolorhist( chv ); + + /* figure out the transparent colour index */ + if (transparency) { + PPM_ASSIGN(transcolor, bg_r, bg_g, bg_b); + transparent = ppm_lookupcolor( static_cht, &transcolor ); + if (transparent == -1) + transparent = closestcolor( transcolor ); + else + transparent = static_perm[transparent]; } + else + transparent = -1 ; - if (QuantizeBuffer(width, height, &ColorMapSize, - RedBuffer, GreenBuffer, BlueBuffer, - OutputBuffer, OutputColorMap->Colors) == GIF_ERROR) - fprintf(stderr, "Warning: Quantize Buffer Failed\n"); + /* All set, let's do it. */ + GIFEncode(outfile, width, height, interlace, 0, transparent, BitsPerPixel, + static_red, static_green, static_blue, GetPixel ); + + for(i = 0 ; i<height ; i++) + Free(static_pixels[i]); + Free(static_pixels); - free(RedBuffer); - free(GreenBuffer); - free(BlueBuffer); - - SaveGif(fp, OutputBuffer, OutputColorMap, ExpNumOfColors, width, height); } diff --git a/Graphics/gl2gif.h b/Graphics/gl2gif.h index bbad10d3e3b8ec77e33a1a2f30aedd80629b7d71..73284befb1949cca7372cb8d926b8aceab19a557 100644 --- a/Graphics/gl2gif.h +++ b/Graphics/gl2gif.h @@ -1,150 +1,58 @@ #ifndef _GL2GIF_H_ #define _GL2GIF_H_ -#define GIF_ERROR 0 -#define GIF_OK 1 +#define MAX_GIFCOLORS 256 -#ifndef TRUE -#define TRUE 1 -#define FALSE 0 -#endif +/* New types */ + +typedef unsigned int pixval; +typedef unsigned long pixel; +typedef int code_int; +typedef long int count_int; + +/* PPM handling */ + +#define PPM_MAXMAXVAL 1023 + +#define PPM_GETR(p) (((p) & 0x3ff00000) >> 20) +#define PPM_GETG(p) (((p) & 0xffc00) >> 10) +#define PPM_GETB(p) ((p) & 0x3ff) +#define PPM_EQUAL(p,q) ((p) == (q)) + +#define PPM_ASSIGN(p,red,grn,blu) \ + (p) = ((pixel) (red) << 20) | ((pixel) (grn) << 10) | (pixel) (blu) + +#define PPM_LUMIN(p) ( 0.299 * PPM_GETR(p) + 0.587 * PPM_GETG(p) + 0.114 * PPM_GETB(p) ) + +#define PPM_DEPTH(newp,p,oldmaxval,newmaxval) \ + PPM_ASSIGN( (newp), \ + ( (int) PPM_GETR(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval), \ + ( (int) PPM_GETG(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval), \ + ( (int) PPM_GETB(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval) ) + +/* Color histogram stuff */ + +typedef struct colorhist_item* colorhist_vector; +struct colorhist_item { + pixel color; + int value; +}; + +typedef struct colorhist_list_item* colorhist_list; +struct colorhist_list_item { + struct colorhist_item ch; + colorhist_list next; +}; + +/* Color hash table stuff */ + +typedef colorhist_list* colorhash_table; + +/* Public function */ + +void create_gif(FILE *outfile, int width, int height, + int dither, int sort, int interlace, + int transparency, int r, int g, int b); -#define VoidPtr void * - -typedef unsigned char GifPixelType; -typedef unsigned char GifByteType; - -typedef struct GifColorType { - GifByteType Red, Green, Blue; -} GifColorType; - -typedef struct ColorMapObject{ - int ColorCount; - int BitsPerPixel; - GifColorType *Colors; /* on malloc(3) heap */ -} ColorMapObject; - -typedef struct GifImageDesc { - int Left, Top, Width, Height ; /* Current image dimensions. */ - int Interlace; /* Sequential/Interlaced lines. */ - ColorMapObject *ColorMap; /* The local color map */ -} GifImageDesc; - -typedef struct GifFileType { - int SWidth, SHeight; /* Screen dimensions. */ - int SColorResolution; /* How many colors can we generate? */ - int SBackGroundColor; /* I hope you understand this one... */ - ColorMapObject *SColorMap; /* NULL if not exists. */ - int ImageCount; /* Number of current image */ - GifImageDesc Image; /* Block describing current image */ - VoidPtr Private; /* Don't mess with this! */ -} GifFileType; - -typedef enum { - UNDEFINED_RECORD_TYPE, - SCREEN_DESC_RECORD_TYPE, - IMAGE_DESC_RECORD_TYPE, /* Begin with ',' */ - TERMINATE_RECORD_TYPE /* Begin with ';' */ -} GifRecordType; - -#define HT_SIZE 8192 /* 12bits = 4096 or twice as big! */ -#define HT_KEY_MASK 0x1FFF /* 13bits keys */ -#define HT_KEY_NUM_BITS 13 /* 13bits keys */ -#define HT_MAX_KEY 8191 /* 13bits - 1, maximal code possible */ -#define HT_MAX_CODE 4095 /* Biggest code possible in 12 bits. */ - -/* The 32 bits of the long are divided into two parts for the key & code: */ -/* 1. The code is 12 bits as our compression algorithm is limited to 12bits */ -/* 2. The key is 12 bits Prefix code + 8 bit new char or 20 bits. */ -#define HT_GET_KEY(l) (l >> 12) -#define HT_GET_CODE(l) (l & 0x0FFF) -#define HT_PUT_KEY(l) (l << 12) -#define HT_PUT_CODE(l) (l & 0x0FFF) - -typedef struct GifHashTableType { - unsigned long HTable[HT_SIZE]; -} GifHashTableType; - -#define LZ_MAX_CODE 4095 /* Biggest code possible in 12 bits. */ -#define LZ_BITS 12 - -#define FLUSH_OUTPUT 4096 /* Impossible code, to signal flush. */ -#define FIRST_CODE 4097 /* Impossible code, to signal first. */ -#define NO_SUCH_CODE 4098 /* Impossible code, to signal empty. */ - -#define FILE_STATE_WRITE 0x01 -#define FILE_STATE_SCREEN 0x02 -#define FILE_STATE_IMAGE 0x04 -#define FILE_STATE_READ 0x08 - -#define IS_READABLE(Private) (Private->FileState & FILE_STATE_READ) -#define IS_WRITEABLE(Private) (Private->FileState & FILE_STATE_WRITE) - -typedef struct GifFilePrivateType { - int FileState; - int FileHandle; /* Where all this data goes to! */ - int BitsPerPixel; /* Bits per pixel (Codes uses at least this + 1). */ - int ClearCode; /* The CLEAR LZ code. */ - int EOFCode; /* The EOF LZ code. */ - int RunningCode; /* The next code algorithm can generate. */ - int RunningBits;/* The number of bits required to represent RunningCode. */ - int MaxCode1; /* 1 bigger than max. possible code, in RunningBits bits. */ - int LastCode; /* The code before the current code. */ - int CrntCode; /* Current algorithm code. */ - int StackPtr; /* For character stack (see below). */ - int CrntShiftState; /* Number of bits in CrntShiftDWord. */ - unsigned long CrntShiftDWord; /* For bytes decomposition into codes. */ - unsigned long PixelCount; /* Number of pixels in image. */ - FILE *File; /* File as stream. */ - GifByteType Buf[256]; /* Compressed input is buffered here. */ - GifByteType Stack[LZ_MAX_CODE]; /* Decoded pixels are stacked here. */ - GifByteType Suffix[LZ_MAX_CODE+1]; /* So we can trace the codes. */ - int Prefix[LZ_MAX_CODE+1]; - GifHashTableType *HashTable; -} GifFilePrivateType; - - -#define E_GIF_ERR_OPEN_FAILED 1 /* And EGif possible errors. */ -#define E_GIF_ERR_WRITE_FAILED 2 -#define E_GIF_ERR_HAS_SCRN_DSCR 3 -#define E_GIF_ERR_HAS_IMAG_DSCR 4 -#define E_GIF_ERR_NO_COLOR_MAP 5 -#define E_GIF_ERR_DATA_TOO_BIG 6 -#define E_GIF_ERR_NOT_ENOUGH_MEM 7 -#define E_GIF_ERR_DISK_IS_FULL 8 -#define E_GIF_ERR_CLOSE_FAILED 9 -#define E_GIF_ERR_NOT_WRITEABLE 10 - -/* Provate functions */ - -GifHashTableType *_InitHashTable(void); -void _ClearHashTable(GifHashTableType *HashTable); -void _InsertHashTable(GifHashTableType *HashTable, unsigned long Key, int Code); -int _ExistsHashTable(GifHashTableType *HashTable, unsigned long Key); - -GifFileType *EGifOpenFileHandle(int GifFileHandle); -void EGifSetGifVersion(char *Version); -int EGifPutScreenDesc(GifFileType *GifFile, - int GifWidth, int GifHeight, int GifColorRes, int GifBackGround, - ColorMapObject *GifColorMap); -int EGifPutImageDesc(GifFileType *GifFile, - int GifLeft, int GifTop, int Width, int GifHeight, int GifInterlace, - ColorMapObject *GifColorMap); -int EGifPutLine(GifFileType *GifFile, GifPixelType *GifLine, int GifLineLen); -int EGifCloseFile(GifFileType *GifFile); - -int QuantizeBuffer(int Width, int Height, int *ColorMapSize, - GifByteType *RedInput, GifByteType *GreenInput, GifByteType *BlueInput, - GifByteType *OutputBuffer, GifColorType *OutputColorMap); - -extern void PrintGifError(void); -extern int GifLastError(void); -extern ColorMapObject *MakeMapObject(int ColorCount, GifColorType *ColorMap); -extern void FreeMapObject(ColorMapObject *Object); -extern int BitSize(int n); - -/* Public functions */ - -void create_gif(FILE *fp, int width, int height); #endif