You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
562 lines
20 KiB
562 lines
20 KiB
/*
|
|
** $Id$
|
|
**
|
|
** Minimal GIF parser, for use in extracting and setting metadata.
|
|
** Modified for standalone & KDE calling by Bryce Nesbitt
|
|
**
|
|
** TODO:
|
|
** Support gif comments that span more than one comment block.
|
|
** Verify that Unicode utf-8 is fully unmolested by this code.
|
|
** Implement gif structure verifier.
|
|
**
|
|
** Based on: GIFtrans v1.12.2
|
|
** Copyright (C) 24.2.94 by Andreas Ley <ley@rz.uni-karlsruhe.de>
|
|
**
|
|
*******************************************************************************
|
|
**
|
|
** Original distribution site is
|
|
** ftp://ftp.rz.uni-karlsruhe.de/pub/net/www/tools/giftrans/giftrans.c
|
|
** A man-page by knordlun@fltxa.helsinki.fi (Kai Nordlund) is at
|
|
** ftp://ftp.rz.uni-karlsruhe.de/pub/net/www/tools/giftrans/giftrans.1
|
|
** An online version by taylor@intuitive.com (Dave Taylor) is at
|
|
** http://www.intuitive.com/coolweb/Addons/giftrans-doc.html
|
|
** To compile for MS-DOS or OS/2, you need getopt:
|
|
** ftp://ftp.rz.uni-karlsruhe.de/pub/net/www/tools/giftrans/getopt.c
|
|
** MS-DOS executable can be found at
|
|
** ftp://ftp.rz.uni-karlsruhe.de/pub/net/www/tools/giftrans/giftrans.exe
|
|
** OS/2 executable can be found at
|
|
** ftp://ftp.rz.uni-karlsruhe.de/pub/net/www/tools/giftrans/giftrans.os2.exe
|
|
** A template rgb.txt for use with the MS-DOS version can be found at
|
|
** ftp://ftp.rz.uni-karlsruhe.de/pub/net/www/tools/giftrans/rgb.txt
|
|
** Additional info can be found on
|
|
** http://melmac.corp.harris.com/transparent_images.html
|
|
**
|
|
** The GIF file format is documented in
|
|
** ftp://ftp.uu.net/doc/literary/obi/Standards/Graphics/Formats/gif89a.doc.Z
|
|
** A good quick reference is at:
|
|
** http://www.goice.co.jp/member/mo/formats/gif.html
|
|
**
|
|
*******************************************************************************
|
|
**
|
|
** This program is free software; you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation; either version 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program; if not, write to the Free Software
|
|
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
**
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#define STANDALONE_COMPILE
|
|
|
|
extern int set_gif_comment( const char * original_filename, char * comment );
|
|
extern void * get_gif_info( const char * original_filename );
|
|
extern int validate_gif_structure( const char * original_filename );
|
|
int giftrans( FILE * src, FILE * dest);
|
|
|
|
#define WARNING_GARBAGE 1 /* Original file had some unspecified content */
|
|
#define ERROR_NOT_A_JPEG 5 /* Original file not a proper jpeg (must be 1st) */
|
|
#define ERROR_TEMP_FILE 6 /* Problem writing temporay file */
|
|
#define ERROR_SCREWUP 7 /* Original file is now damaged. Ooops. */
|
|
#define ERROR_PREMATURE_EOF 8 /* Unexpected end of file */
|
|
#define ERROR_BAD_MARKER 9 /* Marker with illegal length */
|
|
#define ERROR_MARKER_ORDER 10 /* File seems to be mixed up */
|
|
|
|
/*****************************************************************************/
|
|
#ifndef FALSE
|
|
#define FALSE (0) /* This is the naked Truth */
|
|
#define TRUE (1) /* and this is the Light */
|
|
#endif
|
|
|
|
#ifdef DONT_USE_B_MODE /* define mode parameters for fopen() */
|
|
#define READ_BINARY "r"
|
|
#define WRITE_BINARY "w"
|
|
#else
|
|
#ifdef VMS /* VMS is very nonstandard */
|
|
#define READ_BINARY "rb", "ctx=stm"
|
|
#define WRITE_BINARY "wb", "ctx=stm"
|
|
#else /* standard ANSI-compliant case */
|
|
#define READ_BINARY "rb"
|
|
#define WRITE_BINARY "wb"
|
|
#endif
|
|
#endif
|
|
|
|
#define SUCCESS (0)
|
|
#define FAILURE (1)
|
|
|
|
static char skipcomment,list,verbose,output,debug;
|
|
char *global_comment;
|
|
static long int pos;
|
|
|
|
static char true[] = "True";
|
|
static char false[] = "False";
|
|
static char id[] = "$Id$";
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define readword(buffer) ((buffer)[0]+256*(buffer)[1])
|
|
#define readflag(buffer) ((buffer)?true:false)
|
|
#define hex(c) ('a'<=(c)&&(c)<='z'?(c)-'a'+10:'A'<=(c)&&(c)<='Z'?(c)-'A'+10:(c)-'0')
|
|
|
|
void dump(adr,data,len)
|
|
long int adr;
|
|
unsigned char *data;
|
|
size_t len;
|
|
{
|
|
int i;
|
|
|
|
while (len>0) {
|
|
(void)fprintf(stderr,"%08lx:%*s",adr,(int)((adr%16)*3+(adr%16>8?1:0)),"");
|
|
for (i=adr%16;i<16&&len>0;i++,adr++,data++,len--)
|
|
(void)fprintf(stderr,"%s%02x",i==8?" ":" ",*data);
|
|
(void)fprintf(stderr,"\n");
|
|
}
|
|
}
|
|
|
|
void writedata(dest,data,len)
|
|
FILE *dest;
|
|
unsigned char *data;
|
|
size_t len;
|
|
{
|
|
unsigned char size;
|
|
|
|
while (len) {
|
|
size=len<256?len:255;
|
|
(void)fwrite((void *)&size,1,1,dest);
|
|
(void)fwrite((void *)data,(size_t)size,1,dest);
|
|
data+=size;
|
|
len-=size;
|
|
}
|
|
size=0;
|
|
(void)fwrite((void *)&size,1,1,dest);
|
|
}
|
|
|
|
void skipdata(src)
|
|
FILE *src;
|
|
{
|
|
unsigned char size,buffer[256];
|
|
|
|
do {
|
|
pos=ftell(src);
|
|
(void)fread((void *)&size,1,1,src);
|
|
if (debug)
|
|
dump(pos,&size,1);
|
|
if (debug) {
|
|
pos=ftell(src);
|
|
(void)fread((void *)buffer,(size_t)size,1,src);
|
|
dump(pos,buffer,(size_t)size);
|
|
}
|
|
else
|
|
(void)fseek(src,(long int)size,SEEK_CUR);
|
|
} while (!feof(src)&&size>0);
|
|
}
|
|
|
|
void transblock(src,dest)
|
|
FILE *src;
|
|
FILE *dest;
|
|
{
|
|
unsigned char size,buffer[256];
|
|
|
|
pos=ftell(src);
|
|
(void)fread((void *)&size,1,1,src);
|
|
if (debug)
|
|
dump(pos,&size,1);
|
|
if (output)
|
|
(void)fwrite((void *)&size,1,1,dest);
|
|
pos=ftell(src);
|
|
(void)fread((void *)buffer,(size_t)size,1,src);
|
|
if (debug)
|
|
dump(pos,buffer,(size_t)size);
|
|
if (output)
|
|
(void)fwrite((void *)buffer,(size_t)size,1,dest);
|
|
}
|
|
|
|
void dumpcomment(src)
|
|
FILE *src;
|
|
{
|
|
unsigned char size,buffer[256];
|
|
size_t i;
|
|
|
|
pos=ftell(src);
|
|
(void)fread((void *)&size,1,1,src);
|
|
if (debug)
|
|
dump(pos,&size,1);
|
|
(void)fread((void *)buffer,(size_t)size,1,src);
|
|
if (debug)
|
|
dump(pos+1,buffer,(size_t)size);
|
|
for (i=0; i<(size_t)size; i++)
|
|
{
|
|
if ( buffer[i] >= 0x20 || buffer[i] == '\t' ||
|
|
buffer[i] =='\r' || buffer[i] == '\n')
|
|
(void)putc(buffer[i],stderr);
|
|
else
|
|
(void)fprintf(stderr,"\\%03o",buffer[i]);
|
|
}
|
|
(void)fseek(src,(long int)pos,SEEK_SET);
|
|
}
|
|
|
|
void transdata(src,dest)
|
|
FILE *src;
|
|
FILE *dest;
|
|
{
|
|
unsigned char size,buffer[256];
|
|
|
|
do {
|
|
pos=ftell(src);
|
|
(void)fread((void *)&size,1,1,src);
|
|
if (debug)
|
|
dump(pos,&size,1);
|
|
if (output)
|
|
(void)fwrite((void *)&size,1,1,dest);
|
|
pos=ftell(src);
|
|
(void)fread((void *)buffer,(size_t)size,1,src);
|
|
if (debug)
|
|
dump(pos,buffer,(size_t)size);
|
|
if (output)
|
|
(void)fwrite((void *)buffer,(size_t)size,1,dest);
|
|
} while (!feof(src)&&size>0);
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
int giftrans(src,dest)
|
|
FILE *src;
|
|
FILE *dest;
|
|
{
|
|
unsigned char buffer[3*256],lsd[7],gct[3*256];
|
|
unsigned int cnt,cols,size,gct_size;
|
|
|
|
/* Header */
|
|
pos=ftell(src);
|
|
(void)fread((void *)buffer,6,1,src);
|
|
if (strncmp((char *)buffer,"GIF",3)) {
|
|
(void)fprintf(stderr,"Not GIF file!\n");
|
|
return(1);
|
|
}
|
|
if (verbose && debug) {
|
|
buffer[6]='\0';
|
|
(void)fprintf(stderr,"Header: \"%s\"\n",buffer);
|
|
}
|
|
if (debug)
|
|
dump(pos,buffer,6);
|
|
if (output) {
|
|
(void)fwrite((void *)buffer,6,1,dest);
|
|
}
|
|
|
|
/* Logical Screen Descriptor */
|
|
pos=ftell(src);
|
|
(void)fread((void *)lsd,7,1,src);
|
|
if (verbose) {
|
|
//(void)fprintf(stderr,"Logical Screen Descriptor:\n");
|
|
(void)fprintf(stderr,"Size : %dx%d pixels\n",readword(lsd), readword(lsd+2));
|
|
//(void)fprintf(stderr,"Global Color Table Flag: %s\n",readflag(lsd[4]&0x80));
|
|
(void)fprintf(stderr,"Depth : %d bits\n",(lsd[4]&0x70>>4)+1);
|
|
//if (lsd[4]&0x80) {
|
|
// (void)fprintf(stderr,"\tSort Flag: %s\n",readflag(lsd[4]&0x8));
|
|
// (void)fprintf(stderr,"\tSize of Global Color Table: %d colors\n",2<<(lsd[4]&0x7));
|
|
// (void)fprintf(stderr,"\tBackground Color Index: %d\n",lsd[5]);
|
|
//}
|
|
if (lsd[6])
|
|
(void)fprintf(stderr,"Pixel Aspect Ratio: %d (Aspect Ratio %f)\n",lsd[6],((double)lsd[6]+15)/64);
|
|
}
|
|
if (debug)
|
|
dump(pos,lsd,7);
|
|
if (output)
|
|
(void)fwrite((void *)lsd,7,1,dest);
|
|
|
|
/* Global Color Table */
|
|
if (lsd[4]&0x80) {
|
|
gct_size=2<<(lsd[4]&0x7);
|
|
pos=ftell(src);
|
|
(void)fread((void *)gct,gct_size,3,src);
|
|
if (output)
|
|
(void)fwrite((void *)gct,gct_size,3,dest);
|
|
}
|
|
|
|
do {
|
|
pos=ftell(src);
|
|
(void)fread((void *)buffer,1,1,src);
|
|
switch (buffer[0]) {
|
|
case 0x2c: /* Image Descriptor */
|
|
if (verbose && debug)
|
|
(void)fprintf(stderr,"Image Descriptor:\n");
|
|
(void)fread((void *)(buffer+1),9,1,src);
|
|
if (debug)
|
|
dump(pos,buffer,10);
|
|
if (output)
|
|
(void)fwrite((void *)buffer,10,1,dest);
|
|
/* Local Color Table */
|
|
if (buffer[8]&0x80) {
|
|
size=2<<(buffer[8]&0x7);
|
|
pos=ftell(src);
|
|
(void)fread((void *)buffer,size,3,src);
|
|
if (verbose && debug) {
|
|
(void)fprintf(stderr,"Local Color Table:\n");
|
|
for(cnt=0;cnt<size;cnt++)
|
|
(void)fprintf(stderr,"\tColor %d: Red %d, Green %d, Blue %d\n",cnt,buffer[3*cnt],buffer[3*cnt+1],buffer[3*cnt+2]);
|
|
}
|
|
if (debug)
|
|
dump(pos,buffer,size*3);
|
|
if (output)
|
|
(void)fwrite((void *)buffer,size,3,dest);
|
|
}
|
|
/* Table Based Image Data */
|
|
pos=ftell(src);
|
|
(void)fread((void *)buffer,1,1,src);
|
|
if (verbose && debug) {
|
|
(void)fprintf(stderr,"Table Based Image Data:\n");
|
|
(void)fprintf(stderr,"\tLZW Minimum Code Size: 0x%02x\n",buffer[0]);
|
|
}
|
|
if (debug)
|
|
dump(pos,buffer,1);
|
|
if (output)
|
|
(void)fwrite((void *)buffer,1,1,dest);
|
|
transdata(src,dest);
|
|
break;
|
|
case 0x21: /* Extension */
|
|
(void)fread((void *)(buffer+1),1,1,src);
|
|
switch (buffer[1]) {
|
|
case 0xfe: /* Comment Extension */
|
|
if (verbose)
|
|
{
|
|
(void)fprintf(stderr,"Comment: ");
|
|
dumpcomment(src);
|
|
(void)fprintf(stderr,"\n");
|
|
}
|
|
if (debug)
|
|
dump(pos,buffer,2);
|
|
if (skipcomment)
|
|
skipdata(src);
|
|
else {
|
|
if (output)
|
|
(void)fwrite((void *)buffer,2,1,dest);
|
|
transdata(src,dest);
|
|
}
|
|
break;
|
|
case 0x01: /* Plain Text Extension */
|
|
case 0xf9: /* Graphic Control Extension */
|
|
case 0xff: /* Application Extension */
|
|
default:
|
|
if (verbose && debug)
|
|
(void)fprintf(stderr,"Extension type: 0x%02x\n",buffer[1]);
|
|
if (debug)
|
|
dump(pos,buffer,2);
|
|
if (output)
|
|
(void)fwrite((void *)buffer,2,1,dest);
|
|
transblock(src,dest);
|
|
transdata(src,dest);
|
|
break;
|
|
}
|
|
break;
|
|
case 0x3b: /* Trailer (write comment just before here) */
|
|
if (verbose && debug)
|
|
(void)fprintf(stderr,"Trailer %o\n", pos);
|
|
if (debug)
|
|
dump(pos,buffer,1);
|
|
if (global_comment && *global_comment && output) {
|
|
(void)fputs("\041\376",dest);
|
|
writedata(dest,(unsigned char *)global_comment,strlen(global_comment));
|
|
}
|
|
if (output)
|
|
(void)fwrite((void *)buffer,1,1,dest);
|
|
break;
|
|
default:
|
|
(void)fprintf(stderr,"0x%08lx: Error, unknown block 0x%02x!\n",ftell(src)-1,buffer[0]);
|
|
if (debug)
|
|
dump(pos,buffer,1);
|
|
return(1);
|
|
}
|
|
} while (buffer[0]!=0x3b&&!feof(src));
|
|
return(buffer[0]==0x3b?SUCCESS:FAILURE);
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
extern int validate_gif_structure( const char * original_filename )
|
|
{
|
|
FILE * infile;
|
|
int rc;
|
|
|
|
if ((infile = fopen(original_filename, READ_BINARY)) == NULL) {
|
|
fprintf(stderr, "can't open gif image '%s'\n", original_filename);
|
|
return( 1 );
|
|
}
|
|
output = FALSE;
|
|
verbose = FALSE;
|
|
debug = FALSE;
|
|
skipcomment = FALSE;
|
|
rc = giftrans( infile, NULL );
|
|
fclose( infile );
|
|
return( rc );
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
extern void * get_gif_info( const char * original_filename )
|
|
{
|
|
FILE * infile;
|
|
|
|
if ((infile = fopen(original_filename, READ_BINARY)) == NULL) {
|
|
fprintf(stderr, "can't open gif image '%s'\n", original_filename);
|
|
return( NULL );
|
|
}
|
|
|
|
output = FALSE;
|
|
verbose = TRUE;
|
|
debug = FALSE;
|
|
skipcomment = FALSE;
|
|
giftrans( infile, NULL );
|
|
return( NULL );
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
Modify the file in place, but be paranoid and safe about it.
|
|
It's worth a few extra CPU cycles to make sure we never
|
|
destory an original image:
|
|
|
|
1) Validate the input file.
|
|
2) Open a temporary file in same directory (filenameXX).
|
|
3) Copy the data, writing a new comment block.
|
|
4) Sync everything to disc.
|
|
5) Validate the temporary file.
|
|
6) Move the temporary file over the original.
|
|
*/
|
|
extern int set_gif_comment( const char * original_filename, char * comment )
|
|
{
|
|
int i;
|
|
int rc;
|
|
char * temp_filename;
|
|
int temp_filename_length;
|
|
struct stat statbuf;
|
|
FILE * infile;
|
|
FILE * outfile;
|
|
|
|
|
|
/*
|
|
* Make sure we're dealing with a proper input file. Safety first!
|
|
*/
|
|
if( validate_gif_structure( original_filename ) ) {
|
|
fprintf(stderr, "error validating gif image '%s'\n", original_filename);
|
|
return(ERROR_NOT_A_JPEG);
|
|
}
|
|
|
|
/* Get a unique temporary file in the same directory. Hopefully
|
|
* if things go wrong, this file will still be left for recovery purposes.
|
|
*
|
|
* NB: I hate these stupid unsafe string functions in C...
|
|
*/
|
|
outfile = NULL;
|
|
temp_filename_length = strlen( original_filename) + 4;
|
|
temp_filename = (char *)calloc( temp_filename_length, 1 );
|
|
for( i=0; i<10; i++ ) {
|
|
snprintf( temp_filename, temp_filename_length, "%s%d", original_filename, i );
|
|
if( (stat( temp_filename, &statbuf )) && (outfile = fopen(temp_filename, WRITE_BINARY)) ) {
|
|
//fprintf(stderr, "opened temporary file '%s'\n", temp_filename);
|
|
break;
|
|
}
|
|
}
|
|
if( !outfile ) {
|
|
fprintf(stderr, "failed opening temporary file '%s'\n", temp_filename);
|
|
return(ERROR_TEMP_FILE);
|
|
}
|
|
|
|
if ((infile = fopen(original_filename, READ_BINARY)) == NULL) {
|
|
fclose( outfile );
|
|
fprintf(stderr, "can't open gif image '%s'\n", original_filename);
|
|
return(ERROR_NOT_A_JPEG);
|
|
}
|
|
/* Let's do it */
|
|
output = TRUE;
|
|
verbose = FALSE;
|
|
debug = FALSE;
|
|
skipcomment = TRUE;
|
|
global_comment = comment;
|
|
rc = giftrans( infile, outfile );
|
|
fclose( infile );
|
|
fsync( fileno( outfile) ); /* Flush it to disk. IMPORTANT!! */
|
|
/* We really should also flush the directory */
|
|
|
|
/* If everything is OK, and if the new file validates, move
|
|
it over the top of the original */
|
|
if ( fclose( outfile ) || validate_gif_structure( temp_filename ) ) {
|
|
fprintf(stderr, "error in temporary file '%s'\n", temp_filename);
|
|
return(ERROR_TEMP_FILE);
|
|
}
|
|
if( rc ) {
|
|
unlink( temp_filename );
|
|
return( rc );
|
|
}
|
|
if( rename( temp_filename, original_filename ) ) {
|
|
fprintf(stderr, "error renaming '%s' to '%s'\n", temp_filename, original_filename);
|
|
return(ERROR_TEMP_FILE);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
#ifdef STANDALONE_COMPILE
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
char * progname;
|
|
char * filename;
|
|
char * comment;
|
|
FILE * fp;
|
|
int error;
|
|
|
|
/* Process command line arguments... */
|
|
progname = argv[0];
|
|
if (progname == NULL || progname[0] == 0)
|
|
progname = "gif-info"; /* in case C library doesn't provide it */
|
|
if( argc < 2 || argc > 3) {
|
|
fprintf(stderr, "Usage: %s <filename> [\"<comment>\"]\nReads gif image info or sets comment.\n", progname);
|
|
return(5);
|
|
}
|
|
filename = argv[1];
|
|
comment = argv[2];
|
|
|
|
|
|
/* Check if file is readable... */
|
|
if ((fp = fopen(filename, READ_BINARY)) == NULL) {
|
|
fprintf(stderr, "Error: Can't open file '%s'\n", filename);
|
|
return(5);
|
|
}
|
|
fclose(fp);
|
|
|
|
/* Check if we really have a commentable image file here... */
|
|
if( validate_gif_structure( filename ) ) {
|
|
fprintf(stderr, "Error: error parsing file '%s' as a gif image\n", filename);
|
|
return(5);
|
|
}
|
|
|
|
if( argc == 2 ) {
|
|
get_gif_info( filename );
|
|
}
|
|
else {
|
|
set_gif_comment( filename, comment );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
#endif
|
|
|