return FILE_OK;
}
if( !strcmp(pref->name,"GIF") ) { // GIF file
- if( !FileGIF::check_sig(this->asset)) continue;
- file = new FileGIF(this->asset, this);
+ if( FileGIFList::check_sig(this->asset) )
+ file = new FileGIFList(this->asset, this);
+ else if( FileGIF::check_sig(this->asset) )
+ file = new FileGIF(this->asset, this);
+ else continue;
return FILE_OK;
}
#ifdef HAVE_EXR
break;
case FILE_GIF:
- case FILE_GIF_LIST:
file = new FileGIF(this->asset, this);
break;
+ case FILE_GIF_LIST:
+ file = new FileGIFList(this->asset, this);
+ break;
+
#ifdef HAVE_OPENEXR
case FILE_EXR:
case FILE_EXR_LIST:
if( !temp_frame ) {
temp_frame = new VFrame(asset->width, asset->height, supported_colormodel, 0);
+ temp_frame->clear_frame();
}
// printf("File::read_frame %d\n", __LINE__);
if( !strcasecmp(format, _(EXR_NAME)) ) return FILE_EXR;
if( !strcasecmp(format, _(EXR_LIST_NAME)) ) return FILE_EXR_LIST;
if( !strcasecmp(format, _(FLAC_NAME)) ) return FILE_FLAC;
+ if( !strcasecmp(format, _(GIF_NAME)) ) return FILE_GIF;
+ if( !strcasecmp(format, _(GIF_LIST_NAME)) ) return FILE_GIF_LIST;
if( !strcasecmp(format, _(CR2_NAME)) ) return FILE_CR2;
if( !strcasecmp(format, _(CR2_LIST_NAME)) ) return FILE_CR2_LIST;
if( !strcasecmp(format, _(MPEG_NAME)) ) return FILE_MPEG;
case FILE_CR2: return _(CR2_NAME);
case FILE_CR2_LIST: return _(CR2_LIST_NAME);
case FILE_FLAC: return _(FLAC_NAME);
+ case FILE_GIF: return _(GIF_NAME);
+ case FILE_GIF_LIST: return _(GIF_LIST_NAME);
case FILE_EXR: return _(EXR_NAME);
case FILE_EXR_LIST: return _(EXR_LIST_NAME);
#ifdef HAVE_LIBZMPEG
case FILE_CR2_LIST:
case FILE_EXR:
case FILE_EXR_LIST:
+ case FILE_GIF:
+ case FILE_GIF_LIST:
case FILE_PNG:
case FILE_PNG_LIST:
case FILE_PPM:
case FILE_RAWDV: return "dv";
case FILE_DB: return "db";
case FILE_EXR: return "exr";
- case FILE_EXR_LIST: return "exr";
+ case FILE_EXR_LIST: return "exrs";
case FILE_FLAC: return "flac";
case FILE_JPEG: return "jpg";
- case FILE_JPEG_LIST: return "jpg";
+ case FILE_JPEG_LIST: return "jpgs";
+ case FILE_GIF: return "gif";
+ case FILE_GIF_LIST: return "gifs";
case FILE_PCM: return "pcm";
case FILE_PNG: return "png";
- case FILE_PNG_LIST: return "png";
+ case FILE_PNG_LIST: return "pngs";
case FILE_PPM: return "ppm";
- case FILE_PPM_LIST: return "ppm";
+ case FILE_PPM_LIST: return "ppms";
case FILE_TGA: return "tga";
- case FILE_TGA_LIST: return "tga";
+ case FILE_TGA_LIST: return "tgas";
case FILE_TIFF: return "tif";
- case FILE_TIFF_LIST: return "tif";
+ case FILE_TIFF_LIST: return "tifs";
case FILE_VMPEG: return "m2v";
case FILE_WAV: return "wav";
case FILE_FFMPEG: return "ffmpg";
#include "mainerror.h"
#include "interlacemodes.h"
#include "vframe.h"
-#include <string.h>
-
-static int gif_err = 0;
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
FileGIF::FileGIF(Asset *asset, File *file)
- : FileList(asset, file, "GIFLIST", ".gif", FILE_GIF, FILE_GIF_LIST)
+ : FileBase(asset, file)
{
+ offset = 0;
+ err = 0;
+ gif_file = 0;
+ eof = 1;
+ row_size = 0;
+ rows = 0;
+ depth = 8;
+ fp = 0;
+ fd = -1;
+ writes = -1;
+ buffer = 0;
+ bg = 0;
+ output = 0;
}
FileGIF::~FileGIF()
{
+ close_file();
}
int FileGIF::check_sig(Asset *asset)
{
FILE *stream = fopen(asset->path, "rb");
-
- if(stream)
- {
+ if( stream ) {
char test[8];
int ret = fread(test, 1, 6, stream);
fclose(stream);
-
if( ret >= 6 &&
- test[0] == 'G' && test[1] == 'I' && test[2] == 'F' &&
- test[3] == '8' && test[4] == '7' && test[5] == 'A')
- {
- eprintf("FileGIFF: version error (87A): \"%s\".\n", asset->path);
- return 1;
- }
- }
-
- if(strlen(asset->path) > 4)
- {
- int len = strlen(asset->path);
- if(!strncasecmp(asset->path + len - 4, ".gif", 4)) return 1;
+ test[0] == 'G' && test[1] == 'I' && test[2] == 'F' &&
+ test[3] == '8' && (test[4] == '7' || test[4] == '9') &&
+ test[5] == 'a' ) return 1;
}
return 0;
}
int FileGIF::read_frame_header(char *path)
{
FILE *stream = fopen(path, "rb");
-
- if(stream)
- {
+ if( stream ) {
unsigned char test[16];
int ret = fread(test, 16, 1, stream);
fclose(stream);
if( ret < 1 ) return 1;
+ asset->format = FILE_GIF;
asset->width = test[6] | (test[7] << 8);
asset->height = test[8] | (test[9] << 8);
-//printf("FileGIF::read_frame_header %d %d %d\n", __LINE__, asset->width, asset->height);
return 0;
}
return 1;
}
-
-static int input_func(GifFileType *gif_file, GifByteType *buffer, int bytes)
+static int input_file(GifFileType *gif_file, GifByteType *buffer, int bytes)
{
FileGIF *file = (FileGIF*)gif_file->UserData;
- if( file->offset + bytes > file->size )
- bytes = file->size - file->offset;
- if( bytes > 0 ) {
- memcpy(buffer, file->data + file->offset, bytes);
- file->offset += bytes;
- }
+ fseek(file->fp, file->offset, SEEK_SET);
+ bytes = fread(buffer, 1, bytes, file->fp);
+ file->offset += bytes;
return bytes;
}
-int FileGIF::read_frame(VFrame *output, VFrame *input)
+int FileGIF::open_file(int rd, int wr)
{
- data = input->get_data();
- offset = 0;
- size = input->get_compressed_size();
+ return rd ? ropen_path(asset->path) :
+ wr ? wopen_path(asset->path) :
+ 0 ;
+}
- GifFileType *gif_file = DGifOpen(this, input_func, &gif_err);
- if( !gif_file ) {
- eprintf("FileGIF::read_frame %d: %s\n", __LINE__, GifErrorString(gif_err));
- return 1;
+int FileGIF::ropen_path(const char *path)
+{
+ fp = fopen(path, "r");
+ int result = !fp ? 1 : 0;
+ if( !result ) {
+ offset = 0; eof = 0;
+ gif_file = DGifOpen(this, input_file, &err);
+ if( !gif_file ) {
+ eprintf("FileGIF::ropen_path %d: %s\n", __LINE__, GifErrorString(err));
+ result = 1;
+ }
+ }
+ if( !result )
+ result = open_gif();
+ return result;
+}
+
+int FileGIF::wopen_path(const char *path)
+{
+ fd = open(path, O_CREAT+O_TRUNC+O_WRONLY, 0777);
+ int result = fd < 0 ? 1 : 0;
+ if( !result ) {
+ gif_file = EGifOpenFileHandle(fd, &err);
+ if( !gif_file ) {
+ eprintf("FileGIF::wopen_path %d: %s\n", __LINE__, GifErrorString(err));
+ result = 1;
+ }
+ }
+ if( !result ) {
+ writes = 0;
+ }
+ return result;
+}
+
+int FileGIF::write_frames(VFrame ***frames, int len)
+{
+ int result = !gif_file ? 1 : 0;
+ for( int i=0; i<len && !result; ++i )
+ result = write_frame(frames[0][i]);
+ return result;
+}
+
+int FileGIF::open_gif()
+{
+ file_pos.remove_all();
+ int width = asset->width;
+ int height = asset->height;
+ int result = read_frame_header(asset->path);
+ if( !result ) {
+ asset->actual_width = asset->width;
+ if( width ) asset->width = width;
+ asset->actual_height = asset->height;
+ if( height ) asset->height = height;
+ asset->layers = 1;
+ if( !asset->frame_rate )
+ asset->frame_rate = 1;
+ asset->video_data = 1;
+ row_size = gif_file->SWidth * sizeof(GifPixelType);
+ bg = (GifRowType)malloc(row_size);
+ for( int i=0; i<gif_file->SWidth; ++i )
+ bg[i] = gif_file->SBackGroundColor;
+ rows = gif_file->SHeight;
+ buffer = (GifRowType*)malloc(sizeof(GifRowType) * rows);
+ for( int i=0; i<gif_file->SHeight; ++i ) {
+ buffer[i] = (GifRowType)malloc(row_size);
+ memcpy(buffer[i], bg, row_size);
+ }
+ result = scan_gif();
+ asset->video_length = file_pos.size();
}
+ if( !result ) {
+ asset->video_data = 1;
+ if( !asset->frame_rate )
+ asset->frame_rate = 10;
+ }
+ return result;
+}
- GifRowType *gif_buffer = (GifRowType*)malloc(sizeof(GifRowType) * gif_file->SHeight);
- int row_size = gif_file->SWidth * sizeof(GifPixelType);
- gif_buffer[0] = (GifRowType)malloc(row_size);
+int FileGIF::close_file()
+{
+ if( gif_file ) {
+ EGifCloseFile(gif_file, &err);
+ gif_file = 0;
+ }
+ if( fp ) {
+ fclose(fp); fp = 0;
+ }
+ if( fd >= 0 ) {
+ close(fd); fd = -1;
+ }
+ if( bg ) { free(bg); bg = 0; }
+ if( buffer ) {
+ for( int k=0; k<rows; ++k )
+ free(buffer[k]);
+ free(buffer); buffer = 0;
+ rows = 0;
+ }
+ offset = 0;
+ row_size = 0;
+ err = 0;
+ writes = -1;
+ eof = 1;
+ output = 0;
+ FileBase::close_file();
+ return 0;
+}
- for( int i=0; i<gif_file->SWidth; ++i )
- gif_buffer[0][i] = gif_file->SBackGroundColor;
- for( int i=0; i<gif_file->SHeight; ++i ) {
- gif_buffer[i] = (GifRowType)malloc(row_size);
- memcpy(gif_buffer[i], gif_buffer[0], row_size);
+int FileGIF::scan_gif()
+{
+ int file_eof = eof;
+ int64_t file_offset = offset;
+ file_pos.remove_all();
+ int image_pos = offset, ret;
+// read all imgs, build file_pos index
+ while( (ret=read_next_image(0)) > 0 ) {
+ file_pos.append(image_pos);
+ image_pos = offset;
}
+ eof = file_eof;
+ offset = file_offset;
+ return ret;
+}
+
+int FileGIF::set_video_position(int64_t pos)
+{
+ if( !gif_file || !asset->video_length ) return 1;
+ int64_t sz = file_pos.size();
+ eof = pos < 0 || pos >= sz ? 1 : 0;
+ offset = !eof ? file_pos[pos] : 0;
+ return 0;
+}
+
+int FileGIF::read_frame(VFrame *output)
+{
+ if( !gif_file ) return 1;
+ for( int i=0; i<gif_file->SHeight; ++i )
+ memcpy(buffer[i], bg, row_size);
+ int ret = read_next_image(output) > 0 ? 0 : 1;
+ return ret;
+}
- int ret = 0, done = 0;
+// ret = -1:err, 0:eof, 1:frame
+int FileGIF::read_next_image(VFrame *output)
+{
+ int ret = 0;
GifRecordType record_type;
- while( !ret && !done ) {
+
+ while( !ret && !eof ) {
if( DGifGetRecordType(gif_file, &record_type) == GIF_ERROR ) {
- eprintf("FileGIF::read_frame %d: %s\n", __LINE__, GifErrorString(gif_err));
- ret = 1;
+ err = gif_file->Error;
+ eprintf("FileGIF::read_frame %d: %s\n", __LINE__, GifErrorString(err));
+ ret = -1;
break;
}
switch( record_type ) {
case IMAGE_DESC_RECORD_TYPE: {
if( DGifGetImageDesc(gif_file) == GIF_ERROR ) {
- eprintf("FileGIF::read_frame %d: %s\n", __LINE__, GifErrorString(gif_err));
+ err = gif_file->Error;
+ eprintf("FileGIF::read_frame %d: %s\n", __LINE__, GifErrorString(err));
break;
}
int row = gif_file->Image.Top;
int col = gif_file->Image.Left;
int width = gif_file->Image.Width;
int height = gif_file->Image.Height;
- int ret = 0;
if( gif_file->Image.Left + gif_file->Image.Width > gif_file->SWidth ||
gif_file->Image.Top + gif_file->Image.Height > gif_file->SHeight )
- ret = 1;
+ ret = -1;
if( !ret && gif_file->Image.Interlace ) {
static int InterlacedOffset[] = { 0, 4, 2, 1 };
static int InterlacedJumps[] = { 8, 8, 4, 2 };
for( int i=0; i<4; ++i ) {
int j = row + InterlacedOffset[i];
for( ; !ret && j<row + height; j+=InterlacedJumps[i] ) {
- if( DGifGetLine(gif_file, &gif_buffer[j][col], width) == GIF_ERROR )
- ret = 1;
+ if( DGifGetLine(gif_file, &buffer[j][col], width) == GIF_ERROR )
+ ret = -1;
}
}
}
else {
- for( int i=0; !ret && i<height; ++i ) {
- if (DGifGetLine(gif_file, &gif_buffer[row++][col], width) == GIF_ERROR)
- ret = 1;
+ for( int i=0; !ret && i<height; ++i ) {
+ if (DGifGetLine(gif_file, &buffer[row++][col], width) == GIF_ERROR)
+ ret = -1;
}
}
+ ret = 1;
break; }
case EXTENSION_RECORD_TYPE: {
int ExtFunction = 0;
GifByteType *ExtData = 0;
if( DGifGetExtension(gif_file, &ExtFunction, &ExtData) == GIF_ERROR )
- ret = 1;
+ ret = -1;
while( !ret && ExtData ) {
if( DGifGetExtensionNext(gif_file, &ExtData) == GIF_ERROR )
- ret = 1;
+ ret = -1;
}
break; }
case TERMINATE_RECORD_TYPE:
- done = 1;
+ eof = 1;
break;
default:
+ ret = -1;
break;
}
}
ColorMapObject *color_map = 0;
- if( !ret ) {
- //int background = gif_file->SBackGroundColor;
+ if( ret > 0 ) {
color_map = gif_file->Image.ColorMap;
if( !color_map ) color_map = gif_file->SColorMap;
- if( !color_map ) ret = 1;
+ if( !color_map ) ret = -1;
}
- if( !ret ) {
+ if( ret > 0 && output ) {
int screen_width = gif_file->SWidth;
int screen_height = gif_file->SHeight;
for( int i=0; i<screen_height; ++i ) {
- GifRowType gif_row = gif_buffer[i];
+ GifRowType row = buffer[i];
unsigned char *out_ptr = output->get_rows()[i];
for( int j=0; j<screen_width; ++j ) {
- GifColorType *color_map_entry = &color_map->Colors[gif_row[j]];
+ GifColorType *color_map_entry = &color_map->Colors[row[j]];
*out_ptr++ = color_map_entry->Red;
*out_ptr++ = color_map_entry->Green;
*out_ptr++ = color_map_entry->Blue;
}
}
}
- for( int k=0; k<gif_file->SHeight; ++k )
- free(gif_buffer[k]);
- free(gif_buffer);
- DGifCloseFile(gif_file, &gif_err);
return ret;
}
+int FileGIF::write_frame(VFrame *frame)
+{
+ int w = frame->get_w(), h = frame->get_h();
+ ColorMapObject *cmap = 0;
+ int cmap_sz = depth >= 0 ? 1 << depth : 0;
+ int64_t len = w * h * sizeof(GifByteType);
+ GifByteType *bfr = (GifByteType *) malloc(len);
+ int result = !bfr ? 1 : 0;
+ if( !result ) {
+ VFrame gbrp(w, h, BC_GBRP);
+ gbrp.transfer_from(frame);
+ if( !(cmap = GifMakeMapObject(cmap_sz, 0)) )
+ result = 1;
+ if( !result ) {
+ GifByteType *gp = (GifByteType *)gbrp.get_r();
+ GifByteType *bp = (GifByteType *)gbrp.get_g();
+ GifByteType *rp = (GifByteType *)gbrp.get_b();
+ if( GifQuantizeBuffer(w, h, &cmap_sz, rp, gp, bp,
+ bfr, cmap->Colors) == GIF_ERROR )
+ result = 1;
+ }
+ }
+ if( !result && !writes &&
+ EGifPutScreenDesc(gif_file, w, h, depth, 0, 0) == GIF_ERROR )
+ result = 1;
+ if( !result &&
+ EGifPutImageDesc(gif_file, 0, 0, w, h, 0, cmap) == GIF_ERROR )
+ result = 1;
+
+ GifByteType *bp = bfr;
+ for( int y=0; !result && y<h; ++y ) {
+ if( EGifPutLine(gif_file, bp, w) == GIF_ERROR )
+ result = 1;
+ bp += w;
+ }
+ GifFreeMapObject(cmap);
+ if( bfr ) free(bfr);
+ ++writes;
+ return result;
+}
+
+static int write_data(GifFileType *gif_file, const GifByteType *bfr, int bytes)
+{
+ FileGIF *file = (FileGIF*)gif_file->UserData;
+ VFrame *output = file->output;
+ long size = output->get_compressed_size();
+ long alloc = output->get_compressed_allocated();
+ long len = size + bytes;
+ if( len > alloc )
+ output->allocate_compressed_data(2*size + bytes);
+ unsigned char *data = output->get_data() + size;
+ memcpy(data, bfr, bytes);
+ output->set_compressed_size(len);
+ return bytes;
+}
+
+int FileGIF::wopen_data(VFrame *output)
+{
+ int result = 0;
+ gif_file = EGifOpen(this, write_data, &err);
+ if( !gif_file ) {
+ eprintf("FileGIF::wopen_data %d: %s\n", __LINE__, GifErrorString(err));
+ result = 1;
+ }
+ if( !result ) {
+ output->set_compressed_size(0);
+ this->output = output;
+ writes = 0;
+ }
+ return result;
+}
+
+
+FileGIFList::FileGIFList(Asset *asset, File *file)
+ : FileList(asset, file, "GIFLIST", ".gif", FILE_UNKNOWN, FILE_GIF_LIST)
+{
+}
+
+FileGIFList::~FileGIFList()
+{
+}
+
+int FileGIFList::check_sig(Asset *asset)
+{
+ FILE *stream = fopen(asset->path, "rb");
+ if( stream ) {
+ unsigned char test[16];
+ int ret = fread(test, 16, 1, stream);
+ fclose(stream);
+ if( ret < 1 ) return 1;
+ if( test[0] == 'G' && test[1] == 'I' && test[2] == 'F' &&
+ test[3] == 'L' && test[4] == 'I' && test[5] == 'S' && test[6] == 'T')
+ return 1;
+ }
+ return 0;
+}
+
+int FileGIFList::colormodel_supported(int colormodel) { return BC_RGB888; }
+int FileGIFList::get_best_colormodel(Asset *asset, int driver) { return BC_RGB888; }
+
+int FileGIFList::read_frame_header(char *path)
+{
+ FILE *stream = fopen(path, "rb");
+ if( stream ) {
+ unsigned char test[16];
+ int ret = fread(test, 16, 1, stream);
+ fclose(stream);
+ if( ret < 1 ) return 1;
+ asset->format = FILE_GIF_LIST;
+ asset->width = test[6] | (test[7] << 8);
+ asset->height = test[8] | (test[9] << 8);
+ return 0;
+ }
+ perror(path);
+ return 1;
+}
+
+int FileGIFList::read_frame(VFrame *output, char *path)
+{
+ Asset *asset = new Asset(path);
+ FileGIF gif(asset, file);
+ int ret = gif.ropen_path(path);
+ if( !ret )
+ ret = gif.read_frame(output);
+ asset->remove_user();
+ return ret;
+}
+
+int FileGIFList::write_frame(VFrame *frame, VFrame *data, FrameWriterUnit *unit)
+{
+ int native_cmodel = BC_RGB888;
+ if( frame->get_color_model() != native_cmodel ) {
+ GIFUnit *gif_unit = (GIFUnit *)unit;
+ if( !gif_unit->temp_frame ) gif_unit->temp_frame =
+ new VFrame(frame->get_w(), frame->get_h(), native_cmodel);
+ gif_unit->temp_frame->transfer_from(frame);
+ frame = gif_unit->temp_frame;
+ }
+
+ FileGIF gif(asset, file);
+ int ret = gif.wopen_data(data);
+ if( !ret )
+ ret = gif.write_frame(frame);
+ return ret;
+}
+
+FrameWriterUnit* FileGIFList::new_writer_unit(FrameWriter *writer)
+{
+ return new GIFUnit(this, writer);
+}
+
+GIFUnit::GIFUnit(FileGIFList *file, FrameWriter *writer)
+ : FrameWriterUnit(writer)
+{
+ this->file = file;
+ temp_frame = 0;
+}
+
+GIFUnit::~GIFUnit()
+{
+ delete temp_frame;
+}
+
--- /dev/null
+diff -ur a/quantize.c b/quantize.c
+--- a/quantize.c 2019-02-11 07:43:57.000000000 -0700
++++ b/quantize.c 2019-02-27 17:20:06.369498072 -0700
+# SortRGBAxis is static and not locked, qsort recoded
+# GAErrorToken is also static and not locked, not fixed
+@@ -11,8 +11,9 @@
+
+ ******************************************************************************/
+
+-#include <stdlib.h>
+ #include <stdio.h>
++#include <stdlib.h>
++
+ #include "gif_lib.h"
+ #include "gif_lib_private.h"
+
+@@ -22,8 +23,6 @@
+ #define BITS_PER_PRIM_COLOR 5
+ #define MAX_PRIM_COLOR 0x1f
+
+-static int SortRGBAxis;
+-
+ typedef struct QuantizedColorType {
+ GifByteType RGB[3];
+ GifByteType NewColorIndex;
+@@ -31,6 +30,40 @@
+ struct QuantizedColorType *Pnext;
+ } QuantizedColorType;
+
++static int QCmpr(QuantizedColorType *a, QuantizedColorType *b, int i)
++{
++ int i0 = i, i1 = i+1, i2 = i+2;
++ if( i1 >= 3 ) i1 -= 3;
++ if( i2 >= 3 ) i2 -= 3;
++ /* sort on all axes of the color space! */
++ int hash_a = (a->RGB[i0] << 16) | (a->RGB[i1] << 8) | (a->RGB[i2] << 0);
++ int hash_b = (b->RGB[i0] << 16) | (b->RGB[i1] << 8) | (b->RGB[i2] << 0);
++ return hash_a - hash_b;
++}
++
++static int QSplit(QuantizedColorType **q, int l, int r, int i)
++{
++ int m;
++ QuantizedColorType *t;
++ for(;;) {
++ while( QCmpr(q[r],q[l], i) >= 0 ) if( ++l == r ) return r;
++ t = q[l]; q[l] = q[r]; q[r] = t; m = l; l = r; r = m;
++ while( QCmpr(q[l],q[r], i) >= 0 ) if( r == --l ) return r;
++ t = q[l]; q[l] = q[r]; q[r] = t; m = l; l = r; r = m;
++ }
++}
++
++static void QSort(QuantizedColorType **q, int ll, int rr, int i)
++{
++ for(;;) {
++ int l = ll+1; if( l == rr ) return;
++ int r = rr-1; if( l == r ) return;
++ int m = QSplit(q, l, r, i);
++ QSort(q, ll, m, i);
++ ll = m;
++ }
++}
++
+ typedef struct NewColorMapType {
+ GifByteType RGBMin[3], RGBWidth[3];
+ unsigned int NumEntries; /* # of QuantizedColorType in linked list below */
+@@ -41,7 +74,6 @@
+ static int SubdivColorMap(NewColorMapType * NewColorSubdiv,
+ unsigned int ColorMapSize,
+ unsigned int *NewColorMapSize);
+-static int SortCmpRtn(const void *Entry1, const void *Entry2);
+
+ /******************************************************************************
+ Quantize high resolution image into lower one. Input image consists of a
+@@ -198,6 +230,7 @@
+ unsigned int ColorMapSize,
+ unsigned int *NewColorMapSize) {
+
++ int SortRGBAxis = 0;
+ unsigned int i, j, Index = 0;
+ QuantizedColorType *QuantizedColor, **SortArray;
+
+@@ -234,19 +267,7 @@
+ j++, QuantizedColor = QuantizedColor->Pnext)
+ SortArray[j] = QuantizedColor;
+
+- /*
+- * Because qsort isn't stable, this can produce differing
+- * results for the order of tuples depending on platform
+- * details of how qsort() is implemented.
+- *
+- * We mitigate this problem by sorting on all three axes rather
+- * than only the one specied by SortRGBAxis; that way the instability
+- * can only become an issue if there are multiple color indices
+- * referring to identical RGB tuples. Older versions of this
+- * sorted on only the one axis.
+- */
+- qsort(SortArray, NewColorSubdiv[Index].NumEntries,
+- sizeof(QuantizedColorType *), SortCmpRtn);
++ QSort(SortArray, -1, NewColorSubdiv[Index].NumEntries, SortRGBAxis);
+
+ /* Relink the sorted list into one: */
+ for (j = 0; j < NewColorSubdiv[Index].NumEntries - 1; j++)
+@@ -310,21 +331,4 @@
+ Routine called by qsort to compare two entries.
+ *****************************************************************************/
+
+-static int
+-SortCmpRtn(const void *Entry1,
+- const void *Entry2) {
+- QuantizedColorType *entry1 = (*((QuantizedColorType **) Entry1));
+- QuantizedColorType *entry2 = (*((QuantizedColorType **) Entry2));
+-
+- /* sort on all axes of the color space! */
+- int hash1 = entry1->RGB[SortRGBAxis] * 256 * 256
+- + entry1->RGB[(SortRGBAxis+1) % 3] * 256
+- + entry1->RGB[(SortRGBAxis+2) % 3];
+- int hash2 = entry2->RGB[SortRGBAxis] * 256 * 256
+- + entry2->RGB[(SortRGBAxis+1) % 3] * 256
+- + entry2->RGB[(SortRGBAxis+2) % 3];
+-
+- return hash1 - hash2;
+-}
+-
+ /* end */