ppmtogif-noLZW.c v4.9
Archive-name: ppmtogif-noLZW.c
Version: $Revision: 4.9 $
Last-Modified: $Date: 2004-01-08 17:41:43+09 $
かべ。
とりあえず現状放出。
・ppmを食って
・ランレングスGIFを吐く
Cプログラムです。
前回(1.28)からの変更点:
・高速化
・外部カラーマップ (-map ppmfile)
・動画GIF出力対応(!)
・ランレングスに加え、無圧縮出力もサポート
最新ブラウザだけ相手にする&&ベタ塗り絵ならPNGのほうが
ずっと優れてますが、ビンテージブラウザを常用してると
そうもいきませんで…
参考:他のツールの状況
・無圧縮 (1ピクセル1コード)
ImageMagick
djpeg
netpbm の ppmtogif -nolzw (コードはdjpegと同じ)
・ランレングス
これ
gifsicle (アルゴリズム的にちょっとLZWとかぶっている?)
・独自
whirlgif (作者曰くB-Treeを使っており特許問題なし)
なんと、無圧縮とランレングスの両方吐けるツールは
このppmtogif-noLZWが初めてのようです(ほんとかよ)
ランレングスにするとだいたいLZWの4倍くらいの大きさにふくれます。
ブラウザが対応してるならBMPや生PPMをgzipしたほうが小さいです。
GIF出力をさらに圧縮する場合は、コード長を4bitか8bitにしたほうがいいので、
入力は8色か128色に減色したほうがよいでしょう。
--
kabe
^L
#if shell
: ${CDEBUGFLAGS:=-g -O -Wall -Wstrict-prototypes}
set -x
${CC:=gcc} ${CFLAGS:=${CDEBUGFLAGS}} $0
exit
#endif
/*
* Archive-name: ppmtogif-noLZW.c
* Version: $Revision: 4.9 $
* Last-Modified: $Date: 2004-01-08 17:41:43+09 $
*
* non-LZW,runlength GIF encoder
*
* eats PPM, outs GIF to stdout
*
* Author: kabe$sra-tohoku.co.jp
*
* Changes from 1.20:
* faster (cache colormap lookup)
* terser (use -v to get diagnostic)
* can build animated GIF
* -map refcolors.ppm now works
* add "-norle" to enable nocompress mode
* don't rely on EOF for pixel reads
*
* TODO:
* -codelen #
* netpbm-ppmtogif commandline compatibility
* canonicalize/clear "pixel" "index" (esp.IRT netpbm)
* immediate error on invalid colorspec
* "-sort" sort colormap
* (use "struct pixel" IRT libppm)(no benefit?)
* use local colormap? (esp anim)
* builtin 6x6x6+16=226 cmap cube
* output diff pixels for anim inter-frame
* do something with peek_v
*
* redef initcodesize +1
* build runlength on read?
* (win, as full image don't have to be read into memory)
*
* debug options:
* -DNO_TWIRL
* -DDUMP_CMAP
* -DPROFILE_CMAP_HASH=1
*
* License:
* * Modification and redistribution is allowed.
* * No gurantee and no liability by author for anything
* including but not limited to
* infringing patents; use at your own risk.
*/
static const char _rcsid[] = "$Id: ppmtogif-noLZW.c,v 4.9 2004-01-08 17:41:43+09 kabe Exp kabe $";
#include <stdio.h>
#include <string.h> /*strcmp, memset*/
#include <ctype.h> /*toupper*/
#include <stdlib.h> /*malloc, abs, atoi*/
#include <errno.h> /*errno, EINVAL*/
#include <stdarg.h> /*va_list*/
/* bbstream.h { */
typedef struct {
FILE *fd;
unsigned int dangle; /* dangling bits not enough for an octet; LSB filled */
int danglebits; /* current data bits in dangle */
int buflen; /* current data length of buf */
unsigned char buf[260]; /* 256+4(possible overpush) */
} bbstream_t;
bbstream_t *bbstream_open(FILE *outfd);
int bbstream_out(bbstream_t *bb, int data, int bits);
void bbstream_close(bbstream_t *bb);
/* } bbstream.h */
/*****************************************************************/
/* libppm-ish routines { */
/*static*/ struct {
char *progname;
int verbose;
} G = {
.progname = "ppmtogif",
.verbose = 0
};
void
pm_init( int *argcP, char *argv[] )
{
/* init progname */
G.progname = strrchr( argv[0], '/' );
if (!G.progname) {
G.progname = argv[0];
} else {
G.progname++; /* skip '/' */
}
#if defined(O_BINARY) && HAVE_SETMODE
/* DOS binary mode; this only matters for cygwin.
* cf. netpbm pbm/libpbm1.c:pm_init() */
if (!isatty(fileno(stdin))) setmode(fileno(stdin) ,O_BINARY);
if (!isatty(fileno(stdout))) setmode(fileno(stdout),O_BINARY);
#endif
}
/* printf to stderr with program name prefixed */
int
pm_message(const char fmt[], ... )
{
int ret;
va_list va;
va_start(va, fmt);
fputs(G.progname, stderr); fputs(": ", stderr);
ret = vfprintf(stderr, fmt, va);
va_end(va);
return ret;
}
/*
* void ppm_readppminit( FILE* fp, int* colsP, int* rowsP, pixval* maxvalP, int* formatP )
*formatP is ('P'<<8) + char
*/
void
ppm_readppminit( FILE* ppmfd, int* colsP, int* rowsP, int* maxvalP, int* formatP)
{
if ((*formatP = fgetc(ppmfd)) != 'P') {
pm_message("input not PNM\n"); exit(1);
}
*formatP = (*formatP << 8) + fgetc(ppmfd); /* '3' '6' */
fscanf(ppmfd, "%d %d %d", colsP, rowsP, maxvalP);
fgetc(ppmfd); /* discard terminating newline */
}
/* xtol("fedc",3) returns 0xfed */
static unsigned long
xtol( hex, len )
const char *hex; /* string to parse */
size_t len; /* length to parse in */
{
static const char * const x = "0123456789ABCDEF";
unsigned long ret = 0;
while (*hex && len) {
char *p = strchr(x, toupper(*hex));
if (!p) { errno = EINVAL; return 0; }
ret = (ret<<4) + p-x;
hex++;
len--;
}
return ret;
}
/*
* pixel ppm_parsecolor( char* colorname, pixval maxval )
* colorname:
* "#rgb" "#rrggbb" "#rrrgggbbb" "#rrrrggggbbbb"
* maxval: full-scale pixel value to normalize the return RGB values
*/
void
ppm_parsecolor(const char *cname, unsigned int maxval, unsigned *rP,unsigned *gP,unsigned *bP)
{
unsigned parsedmaxval;
unsigned long r,g,b;
if (cname[0]=='#') {
int s;
switch(s=strlen(cname+1)) {
int cstep;
case 3: /* #rgb */
case 6: /* #rrggbb */
case 9: /* #rrrgggbbb */
case 12: /* #rrrrggggbbbb */
cstep=s/3; /* 1,2,3,4 */
parsedmaxval = (1<<(cstep*4))-1; /*0xf,0xff,0xfff,0xffff*/
r = xtol(cname+1 , cstep);
g = xtol(cname+1+cstep , cstep);
b = xtol(cname+1+cstep*2, cstep);
break;
default: goto parsefail;
}
}/*transrgb="#rgb"*/ else {
goto parsefail;
}
/* redepth */
if (parsedmaxval == maxval) {
/* match; do nothing */
} else if (parsedmaxval < maxval) {
/* extend */
int /*double*/ ratio = maxval / parsedmaxval;
r = r * ratio;
g = g * ratio;
b = b * ratio;
} else if (parsedmaxval > maxval) {
/* shrink */
int /*double*/ ratio = parsedmaxval / maxval;
r = r / ratio;
g = g / ratio;
b = b / ratio;
}
*rP = r;
*gP = g;
*bP = b;
return;
parsefail:
pm_message("cannot parse colorname <%s>\n", cname);
exit(1);
}
/*
* pnm_readpixel
* no libpnm equivalent
* read a single pixel from pnm file, after ppm_readppminit()
* pnmfd: FILE* for read
* pnmtype: 'P3' 'P6'
* maxval: of the pnm file; 255 || 65535
* rP,gP,bP: returned pixel value
* return: 0 if success, EOF if eof
*/
int
pnm_readpixel(pnmfd, pnmtype, maxval, rP,gP,bP)
register FILE * const pnmfd;
const int pnmtype;
const unsigned maxval;
register unsigned * const rP, * const gP, * const bP;
{
/* read one pixel */
switch (pnmtype) {
case ('P'<<8)+'3':
/* ascii PPM# */
if (feof(pnmfd)) return EOF;
if ( 3 == fscanf(pnmfd, "%d %d %d", rP,gP,bP)) {
break;
}
return EOF;
case ('P'<<8)+'6':
/* raw PPM */
/* read fullscale value here */
if (maxval < 256) {
/* 8 bit */
*rP = getc(pnmfd) & 255;
*gP = getc(pnmfd) & 255;
*bP = getc(pnmfd) & 255;
} else {
register unsigned m;
/* 16 bit (MSByte first) */
m = getc(pnmfd); *rP = ((m<<8)+getc(pnmfd))&65535;
m = getc(pnmfd); *gP = ((m<<8)+getc(pnmfd))&65535;
m = getc(pnmfd); *bP = ((m<<8)+getc(pnmfd))&65535;
}
if (feof(pnmfd)) return EOF;
break;
default:
pm_message("PNM type <%c%c> not supported now\n", pnmtype>>8,pnmtype&255);
/* should bail immediately, not neat */
exit(1);
return EOF;
}/*esac pnmtype*/
return 0;
}
/* } libppm-ish routines */
/*****************************************************************/
/* cmap.c { */
struct cmap_t {
int cmaplen; /*valid cmap[] entries*/
struct {
unsigned /*char*/ r,g,b; /* 8 bit is enough */
} cmap[256];
/* used for fast rgb->index translation in cmap_findent()*/
struct cmap_hent_t {
int i;
unsigned r,g,b;
} hash[256]; /* 256 entries == 4kB */
#if PROFILE_CMAP_HASH
int finds, hits, clashes;
#endif
};
#define HASHENT(r,g,b) (((r)+(g)+(b))&255)
/* clear the cmap */
void
cmap_init(struct cmap_t * const cmap)
{
int i;
cmap->cmaplen = 0;
/* for (i=0; i<256; i++) cmap->cmap[i].r=cmap->cmap[i].g=cmap->cmap[i].b=0; */
memset(cmap->cmap, 0, sizeof(cmap->cmap));
for (i=0;i<256;i++) cmap->hash[i].i = -1;
#if PROFILE_CMAP_HASH
cmap->finds = 0; cmap->hits = 0; cmap->clashes = 0;
#endif
}
/* return index of cmap.cmap[], -1 if not found */
/* this routine is the bottleneck */
int
cmap_findent(cmap, r,g,b, matchmode)
register struct cmap_t * const cmap;
const unsigned r,g,b;
const int matchmode; /* '=':exact '~':best */
{
register int i;
int best_i;
struct cmap_hent_t *hent;
long best_d = 0x7fffffff; /*LONG_MAX*/
#if PROFILE_CMAP_HASH
cmap->finds++;
#endif
/* simple optimize: use 256 entry hash for recent queries */
hent = &cmap->hash[HASHENT(r,g,b)];
if (hent->i>=0 && hent->r==r && hent->g==g && hent->b==b) {
#if PROFILE_CMAP_HASH
cmap->hits++;
#endif
i = hent->i;
return i;
}
/* TODO: linear search, which is VERY slow */
/* TODO: pack RGB into single integer for fast compare */
best_i = -1;
for (i=0; i<cmap->cmaplen; i++) {
if (cmap->cmap[i].r==r && cmap->cmap[i].g==g && cmap->cmap[i].b==b) {
/* found */
hent->i = i; hent->r = r; hent->g = g; hent->b = b;
return i;
}
if (matchmode == '~') {
#if USE_EUCLID_DISTANCE
long x1,x2,x3,d;
x1 = (long)cmap->cmap[i].r - (long)r;
x2 = (long)cmap->cmap[i].g - (long)g;
x3 = (long)cmap->cmap[i].b - (long)b;
d = (x1*x1)+(x2*x2)+(x3*x3);
#else
long d;
/* use manhattan distance */
d = labs((long)cmap->cmap[i].r - (long)r) +
labs((long)cmap->cmap[i].g - (long)g) +
labs((long)cmap->cmap[i].b - (long)b);
#endif
if (d < best_d) {
best_i = i;
best_d = d;
}
}
}/*next i*/
/* not found. pickup best */
i = best_i; /* always -1 if matchmode='=' */
if (i>=0) {
#if PROFILE_CMAP_HASH
if (hent->i >=0) cmap->clashes++;
#endif
hent->i = i; hent->r = r; hent->g = g; hent->b = b;
}
return i;
}
/* return index of added color into cmap.cmap[], -1 for error */
/* NOTE: doesn't check for duplicate entry */
int
cmap_add(cmap, r,g,b)
register /*nonconst*/ struct cmap_t * const cmap;
const unsigned r,g,b;
{
int i;
if (cmap->cmaplen==256) {
/* already full */
/* pm_message("Color Palette overflow, use ppmquant 256 to reduce colors\n"); */
return -1;
}
i = cmap->cmaplen;
cmap->cmap[i].r=r; cmap->cmap[i].g=g; cmap->cmap[i].b=b;
cmap->cmaplen++;
return i;
}
/* cmap.c } */
#define fput_LEs(x,fd) fputc((x)&255,(fd));fputc(((x)>>8)&255,(fd))
typedef unsigned char gifPixel; /* GIF pixel index */
/*
* return: pixel index for transrgb parameter; -1 if not found
* transrgb: transparency spec
* NULL | "none"
* "upperleft"
* "background" | "bg" most used color
* "#rrggbb" RGB spec
*/
int
get_transparentindex(gifPixel raster[], size_t rasterlen, const struct cmap_t * const cmap, const char * const transrgb)
{
int transindex = -1;
int i;
if (transrgb == NULL || transrgb[0]=='\0')
return -1;
if (!strcmp(transrgb, "none")) {
/* do nothing */
;
} else
if (!strncmp(transrgb, "background", 1)) {
/* count the pixel freq and select the most-used-color */
unsigned int xfreq[256]; /* pixel frequency counter */
size_t x;
memset(xfreq, 0, sizeof(xfreq));
for (x=0; x<rasterlen; x++) xfreq[raster[x]&255]++; /* XXX potential long process here */
transindex = 0; /* default first in cmap */
for (i=0; i<cmap->cmaplen; i++) {
if (xfreq[transindex] < xfreq[i]) {
transindex = i;
}
}
} else
if (!strncmp(transrgb, "upperleft", 1)) {
transindex = raster[0];
} else {
unsigned r,g,b;
ppm_parsecolor(transrgb, 255, &r,&g,&b);
i = cmap_findent(cmap, r,g,b, '~'); /* find best match */
if (i >= 0) {
transindex = i;
} else {
pm_message("Warning: color {%d %d %d} not found, no transparency set\n", r,g,b);
/*transindex = -1;*/
}
}/*transrgb*/
/* now, transindex is set if transparency is there */
return transindex;
}
/*
* transindex: pixel index to use for transparency, -1 if undefined
*/
void
gifout_GCE( FILE *outfd, int transindex, int delay )
{
int i;
/* graphic control extension (transparency,delay) */
if (transindex < 0 && delay == 0)
return; /* do nothing */
/* output GCE */
fputc('!', outfd); fputc(249, outfd);
fputc(4, outfd); /* sizeof(this block) */
i = (0 /*:3 reserved*/ <<5) |
(0 /*:3 disposal method*/ <<2) | /* none,keep,bg,prev */
(0 /*:1 user input flag*/<<1) |
((transindex >= 0)&1) /*:1 transparent*/;
fputc(i, outfd);
fput_LEs(delay, outfd); /* delay 1/100s */
if (transindex<0) transindex=0; /* clamp if no spec */
fputc(transindex, outfd); /*transparency index*/
fputc(0, outfd); /*terminator*/
}
/*
* Read a PPM file from head, and
* build a indexed raster and cmap
*
* Calling convention:
* FILE *ppmfd;
* int width, height;
* gifPixel *raster;
* size_t rasterlen;
* struct cmap_t cmap;
*
* ppmfd = fopen("file.ppm", "rb");
* cmap_init(&cmap);
* ppm_readraster(ppmfd, &raster,&rasterlen, &width,&height, &cmap, '+');
*
* * raster will be allocated; free(raster) afterwards
* * rasterlen, width, height will be assigned a scalar value
* * cmap is updated to contain new colors if cmap_mode=='+'
*
* Passing NULL for raster will just aquire the cmap
*
* pnm_readpnm() is similar, but has vast different call convention
*/
int
ppm_readraster(register FILE *ppmfd,
gifPixel *raster_r[], /* allocated raster, must be free()ed afterward */
size_t *rasterlen_r, /* will be width*height */
int *width_r, int *height_r, /* return */
struct cmap_t *cmap_r, /* pass in cmap_init()ed cmap */
int cmap_mode /* '+':append '.':don't modify cmap;use best match */
)
{
int width, height, depth, pnmtype;
int i;
size_t rasterlen;
size_t pixct; /* for loop */
gifPixel *raster = NULL;
ppm_readppminit(ppmfd, &width, &height, &depth, &pnmtype);
if (G.verbose >= 1) {
pm_message("PNM type: %c%c\n", (pnmtype>>8),pnmtype&255);
pm_message("size: %d x %d = %lu\n", width, height, (unsigned long)width*height);
pm_message("depth: %d\n", depth);
}
if (width_r) *width_r = width;
if (height_r) *height_r = height;
/* read PNM body */
/** normalize to 0-255, create colormap, create indexed raster stream */
if (G.verbose >= 1) {
pm_message("Reading PPM");
fprintf(stderr, ", normalize");
if (cmap_mode!='.') fprintf(stderr, ", getting colormap");
if (cmap_mode=='.' && raster_r) fprintf(stderr, ", selecting nearest color");
if (raster_r) fprintf(stderr, ", build index array");
fprintf(stderr, "...\n");
}
/* preallocate raster array, as we already know the total size */
rasterlen = width * height;
if (raster_r == NULL) {
/* caller just wants to update colormap */
raster = NULL;
;
} else {
raster = malloc(rasterlen*sizeof(raster[0]));
if (!raster) {
fprintf(stderr, "malloc() failed for gifRaster.raster!!\n");
exit(1);
}
}
/* pixel counts to read are known beforehand; don't rely on EOF */
for (pixct=0; pixct < rasterlen; pixct++) {
unsigned int r,g,b;
/* read one pixel */
if (EOF == pnm_readpixel(ppmfd, pnmtype, depth, &r,&g,&b)) {
fprintf(stderr, "Warning: short read %lu/%lu\n", (unsigned long)pixct, (unsigned long)rasterlen);
break; /*goto ppmeof*/
}
/* normalize to 0-255 */
if (depth == 255) {
/* do nothing */
;
} else if (depth == 65535) {
/* shift 8 bits */
r >>= 8; /* r /= 257 is more accurate */
g >>= 8;
b >>= 8;
} else {
/* calculate it */
r = r*255/depth;
g = g*255/depth;
b = b*255/depth;
}
/* set x255 [list $r $g $b]*/
/* If cmap is to be kept (cmap_mode='.'),
* find for approximate color ('~'),
* otherwise find exact color ('=') and add it if not found.
*/
if (cmap_mode == '.' && !raster) {
/* skip cmap_findent if not needed */
} else {
i = cmap_findent(cmap_r, r,g,b, (cmap_mode=='.')?'~':'=');
if (i < 0) {
/* not found. add it*/
i = cmap_add(cmap_r, r,g,b);
if (i < 0) {
/* already full */
pm_message("Color Palette overflow, use \"ppmquant 256\" to reduce colors\n");
return 1;
}
}
/* now cmap.cmap[i] points to the pixel */
/* add the new pixel index to gifRaster */
/*expand shouldn't happen, as we already allocated it*/
if (raster) raster[pixct] = i;
}
#ifndef NO_TWIRL
/* twirl counter */
if (G.verbose >= 0 && (pixct & 0x1fff) == 0) {
/* puts -nonewline stderr "." */
fprintf(stderr, "\rRead %d%% %c\010",
pixct*100 / rasterlen,
"-\\|/"[(pixct/0x2000)%4]
); /*CT*/
}
#endif
}/*next pixct*/
/*ppmeof: ;*/
#ifndef NO_TWIRL
if (G.verbose>=0) fprintf(stderr, "\rRead 100%% \n"); /*CT*/
#endif
if (raster_r) { *raster_r = raster; raster = NULL; /* memory ownership to caller */ }
if (rasterlen_r) *rasterlen_r = pixct;
if (G.verbose>=1) if (cmap_mode == '+') pm_message("Got %d colormap entries\n", cmap_r->cmaplen);
#if PROFILE_CMAP_HASH
pm_message("cmap profile: hash hits %d clash %d / %d\n", cmap_r->hits, cmap_r->clashes, cmap_r->finds);
#endif
return 0;
}
/* add signature of this program */
void
gifout_addprogsig(FILE *outfd)
{
static const char rev[] = "$Revision: 4.9 $";
char comment[80];
strcpy(comment, "$" "Rem: non-LZW, runlength GIF Encoder (impl C ver ");
strcat(comment, rev+11);
strcpy(comment+strlen(comment)-2, ") $");
fputc('!', outfd); fputc(254, outfd);
fputc(strlen(comment), outfd);
fputs(comment, outfd);
fputc('\0', outfd);
}
/*
*
* ppmtogif: Convert PPM stream to GIF stream
*
* ppmfd: filehandle for PPM stream input
* outfd: filehandle for GIF stream output
* given_cmap: -map colormap to use. If NULL, build a new one from input.
* transrgb: transparency spec
* "none"
* "upperleft"
* "background" most used color
* "#rrggbb" RGB spec
* compressmode: 'r':RLE 'n':raw (no compression)
*
* Most straightforward invocation is
* ppmtogif(stdin, stdout, NULL, NULL, 'r');
*
*/
int
ppmtogif(FILE * const ppmfd, FILE * const outfd, struct cmap_t *given_cmap, const char *transrgb, int compressmode)
{
int width,height;
struct {
int theLen;
gifPixel *raster;
} gifRaster = {0,NULL};
struct cmap_t *cmap;
int pixbits; /* bits needed for this GIF */
int i;
int gifraster_runlength_dc(FILE *outfd, gifPixel indexraster[],size_t indexrasterlen, int pixbits);
int gifraster_nocompress(FILE *, gifPixel [], size_t, int);
/* select colormap */
if (given_cmap == NULL) {
cmap = malloc(sizeof(struct cmap_t));
cmap_init(cmap);
} else {
cmap = given_cmap;
if (G.verbose>=1) pm_message("Using external cmap with %d entries\n", given_cmap->cmaplen);
// for (i=0;i<cmap->cmaplen;i++) { fprintf(stderr, " %3d: %3d %3d %3d\n", i,cmap->cmap[i].r,cmap->cmap[i].g,cmap->cmap[i].b); }
#if PROFILE_CMAP_HASH
cmap->hits=cmap->finds=cmap->clashes = 0;
#endif
}
/* read PNM */
i = ppm_readraster(ppmfd, &gifRaster.raster,&gifRaster.theLen, &width, &height, cmap, (given_cmap==NULL)?'+':'.');
if (i!=0) return i;
/*
## values set so far:
## width,height
## gifRaster.raster[] {pixel pixel ...}
## cmap.cmap[].{r,g,b} {r g b} {r g b} ...
*/
/* TODO: -sort colormap (what is it used for?)*/
#if DUMP_CMAP
fprintf(stderr, "Colormap:\n");
for (i=0; i<cmap->cmaplen; i++) {fprintf(stderr, "%3d:{%d %d %d}\n", i, cmap->cmap[i].r, cmap->cmap[i].g, cmap->cmap[i].b);}
#endif
/* get bitlength needed for this pixel values */
for (pixbits=1; (1<<pixbits) < cmap->cmaplen; pixbits++)
;
if (G.verbose>=1) pm_message("Needed Pixel bits: %d\n", pixbits);
if (pixbits > 8) {
pm_message("Too many colors (%d bits)\n", pixbits);
return 1;
}
/* output GIF header */
fprintf(outfd, transrgb?"GIF89a":"GIF87a"); /*XXX should be "89a" if using transparency*/
/* logical screen descriptor */
fput_LEs(width, outfd); fput_LEs(height, outfd);
i = (1 /*:1 Global colormap follows*/<<7) |
(7 /*:3 bits per color intensity*/ <<4) |
(0 /*:1 Global colormap is sorted*/ <<3) |
(pixbits-1 /*:3 (bits/pixel)-1 */);
fputc(i, outfd);
fputc(0, outfd); /* background */
fputc(0, outfd); /* aspect ratio */
/* Global color map */
for (i=0; i<cmap->cmaplen; i++) {
fputc(cmap->cmap[i].r, outfd);
fputc(cmap->cmap[i].g, outfd);
fputc(cmap->cmap[i].b, outfd);
}
/* pad colormap to 2**pixbits */
for ((void) i; i<(1<<pixbits); i++) {
fputc(0, outfd); fputc(0, outfd); fputc(0, outfd);
}
/* ====== { */
/* calculate transparency */
i = get_transparentindex(gifRaster.raster,gifRaster.theLen, cmap, transrgb);
if (G.verbose>=1) if (i>=0) pm_message("Using index %d (%d %d %d) for transparency\n", i, cmap->cmap[i].r,cmap->cmap[i].g,cmap->cmap[i].b);
/* Graphic Control Extention: transparency, delay */
gifout_GCE(outfd, i, 0/*delay*/);
/* Image Descriptor */
fputc(',', outfd);
fput_LEs(0, outfd); fput_LEs(0, outfd); /*left,top*/
fput_LEs(width, outfd); fput_LEs(height, outfd);
i = (0 /*:1 0:use global colormap 1:has local colormap*/ <<7) |
(0 /*:1 0:sequential 1:interlace*/ <<6) |
(0 /*:1 sorted cmap*/ << 5) |
(0 /*:2 reserved*/ <<3) |
0 /*3: (local cmap bits/pixel)-1*/;
fputc(i, outfd);
/*
### select algorithm
## These should output from <codelen> to last null block
#gifraster_nocompress $outfd $gifRaster $pixbits
#gifraster_runlength $outfd $gifRaster $pixbits
#gifraster_runlength_vc $outfd $gifRaster $pixbits
*/
switch(compressmode) {
default:
case 'r':
gifraster_runlength_dc(outfd, gifRaster.raster,gifRaster.theLen, pixbits);
break;
case 'n':
gifraster_nocompress(outfd, gifRaster.raster,gifRaster.theLen, pixbits);
break;
}
/* ====== } */
/* include comment in trailer */
gifout_addprogsig(outfd);
/* Image terminator */
fputc(';', outfd);
free(gifRaster.raster);
if (given_cmap == NULL) free(cmap);
return 0;
}
/* no compression (reset on every table overflow) */
int
gifraster_nocompress(FILE *outfd, gifPixel indexraster[],size_t indexrasterlen, int pixbits)
{
int initcodesize; /* initial code length after clear */
int codesize; /* current code bit length */
int clearcode;
int inittablevacants; /* empty "table" slots after clear */
int tablevacants; /* current empty "table" slots */
bbstream_t *bbid; /* output byteblock stream */
size_t theindex; /* indexraster[theindex] */
initcodesize = pixbits + 1;
/* Minimal 3bit basecode required to
* accomodate Clear and Stop signal */
if (initcodesize <= 2) initcodesize = 3;
/* TODO: initcodesize = 4 for efficient post-compress */
clearcode = (1<<(initcodesize-1));
/* ### non-compressing GIF stream
## Insert clear code before "table" cause a codesize extend.
## Initial code length will be 2**initcodesize
## Initial ("clear"ed) table will be filled with
##
## 0-(2**(initcodesize-1) -1) raw pixels
## (2**(initcodesize-1) +0,+1) clear,stop
## (2**(initcodesize-1) +2 ... 2**(initcodesize)-1) table vacant
##
## so we can push on by (2**(initcodesize)) - (2**(initcodesize-1) +2)
## entries == (2**(initcodesize-1) - 2).
## (actually 1 less, if not to extend codelen)
##
## ex. initcodesize=4 (pixbits=3)
## "table" = 01234567CExxxxxx
## <----> tablevacants = 2**(4-1)-2 = 6
##
## initcodesize is the "code size" value +1 in the file.
## codesize is the current code size
*/
codesize = initcodesize;
/* How much can we push on before table overflow (code extends)? */
inittablevacants = (1<<(initcodesize-1)) - 2;
tablevacants = inittablevacants;
/* output initial code size */
fputc(initcodesize-1, outfd);
bbid = bbstream_open(outfd);
#ifdef CLEAR
#undef CLEAR
#endif
#define CLEAR() \
bbstream_out(bbid, clearcode, codesize); \
tablevacants = inittablevacants; \
codesize = initcodesize
/* clear code first */
CLEAR();
for (theindex=0; theindex < indexrasterlen; theindex++) {
bbstream_out(bbid, indexraster[theindex], codesize);
/* initial output after clear doesn't extend the "table",
* but to push for 1 less to not extend codelen,
* leave this to decrement even for initial out */
tablevacants--;
if (tablevacants == 0) {
/* "table" is full-1. clear */
/* does not extend codelen now */
CLEAR();
}
}
/* end */
bbstream_out(bbid, clearcode + 1 /*end code*/, codesize);
bbstream_close(bbid);
return 0;
}
#if 0 /*{*/
////# fixed codelen runlength
//proc gifraster_runlength {outfd gifRaster pixbits} \
//{
// set initcodesize $pixbits
// if {$initcodesize <= 1} {set initcodesize 2} ;# min 2bit basecode
//
// # make <codesize> the real width == initial initcodesize+1
// set codesize [expr $initcodesize + 1]
// set clearcode [expr (1 << $initcodesize)]
//
// ### Runlength-coding
// ## 1. Reset on every pixel different from previous.
// ## 2. For length-running pixels, fill the "vacant" table
// ## with same pixels.
// ## Do not extend the code size; just repeat the
// ## last "table entry".
//
// set inittablevacants [expr (1 << $initcodesize) - 2]
//
// puts -nonewline $outfd [binary format {c} $initcodesize]
// dputs "Initial codesize: $initcodesize"
//
// set bbid [bbstream_open $outfd]
//
// while {[llength $gifRaster]} {
//
// set theindex [lindex $gifRaster 0]
// ## retrieve the runlength
// for {set r 1} {[lindex $gifRaster $r] == $theindex} {incr r} {}
//dputs "runlength $theindex x $r"
// set gifRaster [lrange $gifRaster $r end] ;# heavy
//
// while {$r} {
// ## reset on new pixel
// bbstream_out $bbid $clearcode $codesize
// set tabextent 0 ;# valid "table" entries past clear/end
// set codesize [expr $initcodesize + 1] ;# (redundant)
//
// ## initial 1
// bbstream_out $bbid $theindex $codesize
// incr r -1
//
// ## output by runlength-like.
// ## Progress with initial vacant "table",
// ## then fill the "table" with "aa" "aaa" ... entries.
// ##
// ## for codelen=3,
// ## "0" 4(clr), 0 ("table" = 0,1,2,3,C,E)
// ## "00" 4,0,0 (0,1,2,3,C,E,00)
// ## "000" 4,0,6 (0,1,2,3,C,E,00)
// ## "0000" 4,0,6,0 (0,1,2,3,C,E,00,000)(full,codelen bump)
// ## you have to "clear" for any further pixels,
// ## as "table" is always growing.
// ## Actually, you can't fill the table in full because
// ## once the table is full the codelen is bumped.
// ##
// ## codelen=4 (inittablevacants=6)
// ## "0" 8,0 (0,1,2,3,4,5,6,7,C,E)
// ## "00" 8,0,0 (0,1,2,3,4,5,6,7,C,E,00)
// ## "000" 8,0,10 (0,1,2,3,4,5,6,7,C,E,00)
// ## "0000" 8,0,10,0 (0,1,2,3,4,5,6,7,C,E,00,000)
// ## "00000" 8,0,10,10 (0,1,2,3,4,5,6,7,C,E,00,000,0000)
// ## "000000" 8,0,10,11 (0,1,2,3,4,5,6,7,C,E,00,000,0000,00000)
// ## "0000000" 8,0,10,12 (0,1,2,3,4,5,6,7,C,E,00,000,0000,00000,000000)
// ## "00000000" 8,0,10,13 (0,1,2,3,4,5,6,7,C,E,00,000,0000,00000,000000,0000000)(full,codelen bump)
//
// ## max code able to output is $clearcode + 2 + $tabextent
// ## max length solvable is $tabextent + 2
//
////#puts stderr "tabextent=$tabextent / $inittablevacants"
// while {$tabextent < [expr $inittablevacants - 1]} {
// ## Pack it until the table is almost (inittablevacants-1)
// ## filled. You can't fill up the table; the
// ## codelen will be bumped if so.
// if {$r == 0} {
// # next iterate
// } elseif {$r == 1} {
// bbstream_out $bbid $theindex $codesize
// incr r -1
// } elseif {$r <= [expr $tabextent + 2]} {
// ## solvable
// ## r==2: out=$clearcode + 2
// ## r==3: out=$clearcode + 3
// bbstream_out $bbid [expr $clearcode + $r] $codesize
// incr r -$r
// } else {
// ## not yet solvable. out the longest
// bbstream_out $bbid [expr $clearcode + 2 + $tabextent] $codesize
// incr r -2; incr r -$tabextent
// }
// incr tabextent
// } ;# while tabextent
//
// # table is full; reset
//
// } ;#while $r
// } ;# while gifRaster
//
// # end
// bbstream_out $bbid [expr $clearcode + 1] $codesize
//
// bbstream_close $bbid
//}
//
////## variable codelength runlength
////## this algoritm seems most feasible
//proc gifraster_runlength_vc {outfd gifRaster pixbits} \
//{
// set initcodesize $pixbits
// if {$initcodesize <= 1} {set initcodesize 2} ;# min 2bit basecode
//
// # make <codesize> the real width == initial initcodesize+1
// set codesize [expr $initcodesize + 1]
// set clearcode [expr (1 << $initcodesize)]
//
// ### Runlength-coding with variable codelen
// ## 1. Reset on every pixel different from previous.
// ## 2. For length-running pixels, fill the "vacant" table
// ## with same pixels.
// ## Extend the codelen as needed.
//
// ## Things that doesn't change:
// ## initcodesize
// ## clearcode
// ## inittablevacants (tablevacants reset to this only on clear)
// ## Things to change on clear:
// ## tabextent
// ## tablevacants
// ## codesize
// ## Things to change on codelen change:
// ## codesize
// ## tablevacants
//
// set inittablevacants [expr (1 << $initcodesize) - 2]
//
// puts -nonewline $outfd [binary format {c} $initcodesize]
// dputs "Initial codesize: $initcodesize"
//
// set bbid [bbstream_open $outfd]
//
// while {[llength $gifRaster]} {
//
// set theindex [lindex $gifRaster 0]
// ## retrieve the runlength
// for {set r 1} {[lindex $gifRaster $r] == $theindex} {incr r} {}
//dputs "runlength $theindex x $r"
// set gifRaster [lrange $gifRaster $r end] ;# heavy
//
// while {$r} {
// ## reset on new pixel
// bbstream_out $bbid $clearcode $codesize
// set tabextent 0
// set codesize [expr $initcodesize + 1]
// set tablevacants $inittablevacants
//
// ## initial 1
// bbstream_out $bbid $theindex $codesize
// incr r -1
//
// ## max code able to output is $clearcode + 2 + $tabextent
// ## max length solvable is $tabextent + 2
//
// while {$r} {
//dputs "tabextent=$tabextent / $tablevacants"
// if {$r == 0} {
// error "NOTREACHED" ;# because of while
// } elseif {$r == 1} {
// bbstream_out $bbid $theindex $codesize
// incr r -1
// } elseif {$r <= [expr $tabextent + 2]} {
// ## solvable
// ## r==2: out=$clearcode + 2
// ## r==3: out=$clearcode + 3
// bbstream_out $bbid [expr $clearcode + $r] $codesize
// incr r -$r
// } else {
// ## not yet solvable. out the longest
// bbstream_out $bbid [expr $clearcode + 2 + $tabextent] $codesize
// incr r -2; incr r -$tabextent
// }
// incr tabextent
//
// ## expand the "table" as needed.
// if {$tabextent == $tablevacants} {
// ## The decoder has already bumped the codesize
// ## (thus tablesize) now.
// ##
// ## New table:
// ## 0-2**(initcodesize)-1 pixel
// ## 2**(initcodesize)+0,+1 clear,stop
// ## 2**(initcodesize)+2 - 2**(codesize)-1 enlarged
// ## New <tablevacants> is thus
// ## 2**(codesize) - (2**(initcodesize)+2)
// ## == 2**(codesize) - 2**(initcodesize) - 2
// incr tablevacants [expr (1 << $codesize)]
// incr codesize
//
// if {$codesize > 12} {
// #table overflow; reset!
// break ;# tabextent
// }
// }
//
// } ;# while tabextent $r
//
// # end runlength or table full
//
// } ;#while $r
// } ;# while gifRaster
//
// # end
// bbstream_out $bbid [expr $clearcode + 1] $codesize
//
// bbstream_close $bbid
//}
#endif /*}*/
/* runlength, adaptive codelen, ondemand clear */
int
gifraster_runlength_dc(FILE *outfd, gifPixel indexraster[],size_t indexrasterlen, int pixbits)
{
int codesize; /*current (real) codesize */
int clearcode; /*constant determined from initial code size*/
int inittableremain; /*vacant slots in the "table" after clear*/
int tableremain; /*current vacant slots; decremented on output, reset to inittableremain on clear*/
int runindex; /*current vacant top of the "table"*/
int runhead; /*index into "table" of the second pixel of current runlength */
/*(first pixel is in range 0 - ((pixbits**2)-1) )*/
bbstream_t *bbid; /* output byteblock stream */
/* memmove() is really heavy, so optimize! */
int theraster; /* current(next) runlength start as indexraster[theraster] */
/* non-changing values */
int initcodesize = pixbits + 1; /* +1 to accomodate CLEAR,STOP */
if (initcodesize <= 2) initcodesize=3; /* min 3bit basecode */
/*(initcodesize now refers to real codesize for this routine)*/
codesize = initcodesize;
clearcode = 1 << (initcodesize - 1);
/* {init,}codesize is the actual bit width
* (<codesize> written in the file is 1 less) */
/*
## Initial "table" size is 2**initcodesize, filled with
## 0 -- 2**(initcodesize-1==usually pixbits)-1 raw pixel values
## 2**(initcodesize-1)+0,+1 clear,stop
## 2**(initcodesize-1)+2 -- 2**(initcodesize)-1 vacant
## ex. for initcodesize=4 (depth 3bit)
## table={01234567CS------}
##
## The vacant "table" size is
## (2**initcodesize)-(2**(initcodesize-1)+2)
## == (2**initcodesize) - (clearcode + 2)
## We can push this far(actually 1 less) without
## extending the codelen
*/
inittableremain = (1<<initcodesize) - clearcode - 2; /*const*/
tableremain = inittableremain; /*decr, reset on clear*/
/*always calculatable by (2**codesize) - runindex ? */
runindex = clearcode + 2; /*current "table" top*/
/*runindex+tableremain == current table size*/
/* tableremain and runindex is always in sync, thus
* tableremain--,runindex++ ; TODO either one eliminatable? */
fputc(initcodesize-1, outfd); /* GIF spec demands -1 for data */
bbid = bbstream_open(outfd);
/*
## clear:
## bbstream_out $bbid $clearcode $codesize
## set codesize $initcodesize
## set tableremain $inittableremain
## set runindex [expr $clearcode + 2]
## #set runhead $runindex
## out:
## bbstream_out $bbid <something> $codesize
## incr r -1
## incr tableremain -1
## incr runindex
## ## (if tableremain==0 the code size has extended)
##dothisbefore?
## if {!$tableremain} {
## incr tableremain [expr 1 << $codesize]
## incr codesize
## if {$codesize > 12} {
## set codesize 12
## ##codesize too long; reset now!
## error
## }
## }
*/
#ifdef CLEAR
#undef CLEAR
#endif
#define CLEAR() \
bbstream_out(bbid, clearcode, codesize); \
codesize = initcodesize; \
tableremain = inittableremain; \
runindex = clearcode + 2 \
/*set runhead $runindex*/
theraster = 0;
/* initial clear. */
#ifndef MAKE_XLI_BARF
CLEAR();
#endif
/* main pixels loop */
while (theraster < indexrasterlen) {
/* find runlength */
int theindex; /* one pixel value of indexraster[] */
int r; /* runlength counter */
theindex = indexraster[theraster];
for (r=1; theraster+r < indexrasterlen && indexraster[theraster+r]==theindex; r++)
;
theraster += r;
/*
* Clear code emission timing:
*
* - We always want the first pixel in the shortest code
* if (codesize != initcodesize)
*
* - Also calculate if clear is better for current r, that is
* if codelen expands beyond initcodesize during the run
* {111...CSxxxx------------}
* 23456789ABCD will-be-filled "table" runlength
* <tableremain>
* thus, maximum runlength accomodable by current tableremain
* will be 1(initial) + 2+3+4+...+D
* == 1+((3+tableremain)*tableremain)/2
* thus
* if (r > 1+((3+tableremain)*tableremain)/2)
*
* Deployment result:
* ... output shinks a bit, but doesn't gain much
* compared to additional calculation for each cycle
*/
firstpixel:
if (codesize != initcodesize || r > 1+((3+tableremain)*tableremain)/2) {
CLEAR();
}
/*## output the first pixel. {*/
bbstream_out(bbid, theindex, codesize);
r--;
runhead = runindex; /* mark "table" index for 2pixel */
if (0==tableremain) { /* runindex == (1<<codesize) */
/* table is full; bump codesize */
tableremain += (1<<codesize);
codesize++;
/* codesize can't overflow for 1st pixel */
}
tableremain--; /* XXX should not after clear? */
runindex++; /* bump top of the "table" */
/*
## we may should always clear beforehand
## for tableremain <=1...
* }*/
while (r) {
if (r == 0) {
/*## nothing.*/
/*NOTREACHED*/
} else if (r == 1) {
/* double pixel or pixel remaining at tail */
bbstream_out(bbid, theindex, codesize);
r--;
/*
## The "table" is currently filled from runhead with
## 2,3,4... pixel chains until index runindex-1.
## So the maximum solvable runlength by a single code is
## (runindex-1 - runhead + 2).
## (last one--runindex is filled AFTER output.)
*/
} else if (r <= runindex-1 - runhead + 2) {
/* all remaining is solvable.*/
bbstream_out(bbid, runhead + r-2, codesize);
r -= r;
} else {
/* it's not solvable; output the longest chain possible */
if (runindex == runhead) {
/*1st pixel??*/
fprintf(stderr,"1stpix\n"); exit(1);
}
bbstream_out(bbid, runindex - 1, codesize);
r -= (runindex-1 - runhead + 2);
}/*endif*/
/*
## As something should have been out,
## do the common things on single output
*/
if (0==tableremain) {
/* codesize extend */
tableremain += (1 << codesize);
codesize++;
if (codesize > 12) {
/* 12bit overflow; clear immediately */
codesize=12;
CLEAR();
runhead = runindex;
// continue; /* skip runindex update */
/*
XXX: MUST goto generating a first pixel.
*/
goto firstpixel;
}
}
tableremain--;
runindex++;
}/*wend r (runlength)*/
}/*wend indexrasterlen*/
bbstream_out(bbid, clearcode + 1/*end*/, codesize);
bbstream_close(bbid);
return 0;
}
/* bbstream.c {*/
/* ByteBlock Stream routines
*
* bbstream: {bytecount(1-254):8 { variable_length_bits_data ... }}[] 0:8
*
* data bytes are LSB packed, thus (0123)(4567)(89ab) will be
* 45670123 xxxx89ab
*/
bbstream_t *
bbstream_open(FILE *outfd)
{
bbstream_t *bb = malloc(sizeof(bbstream_t));
bb->fd = outfd;
bb->dangle = 0;
bb->danglebits = 0;
bb->buflen = 0;
return bb;
}
int
bbstream_out(bbstream_t *bb, int data, int bits)
{
/*dputs "out: $data:$bits"*/
/* stack the data onto MSB */
bb->dangle = (data<<bb->danglebits) | bb->dangle;
bb->danglebits += bits;
/* chop off dangle from LSB byte to buffer */
while (bb->danglebits >= 8) {
bb->buf[bb->buflen++] = bb->dangle & 255;
bb->danglebits -= 8;
bb->dangle >>= 8;
if (bb->buflen>260) {fprintf(stderr,"ERR\n"); exit(1);}
}
/* put it out in 254 bytes block */
while (bb->buflen >= 254) {
fputc(254, bb->fd);
fwrite(bb->buf, 1, 254, bb->fd);
bb->buflen -= 254;
memmove(bb->buf, bb->buf+254, bb->buflen);
/* TODO: memmove can cost, so ring buffering may be better (low priority) */
}
return 0;
}
void
bbstream_close(bbstream_t *bb)
{
/* flush remaining dangle */
bb->buf[bb->buflen++] = bb->dangle;
while (bb->buflen) {
size_t thislen = (bb->buflen > 254) ? 254 : bb->buflen;
fputc(thislen, bb->fd);
fwrite(bb->buf, 1, thislen, bb->fd);
bb->buflen -= thislen;
memmove(bb->buf, bb->buf+thislen, bb->buflen);
}
fputc('\0', bb->fd); /* terminator count=0 */
free(bb);
}
/* bbstream.c }*/
/* return stdin for "-" */
FILE *
fopen_(const char *filename, const char *mode)
{
if (filename[0]=='-' && filename[1]=='\0') {
return stdin;
} else {
return fopen(filename, mode);
}
/* NOTREACHED */
}
/*
* Considerations for animated gif:
*
* - Always use local colormap, or try to collect global colormap?
* - Render full frame, or output only different pixels from previous frame?
* (needs holding full-color previous frame)
*
* TODO:
* do something with implicit stdin logic flow
* cleanup indent
* local cmap switch ("animate" defaults to local, "+map" to aggregate)
*
* "animate" ImageMagick tool thinks the initial frame is the whole size
* (not the size in the initial header); so it needs the biggest
* image for initial frame... (Netscape properly renders it)
*
*/
int
anim_main(int argc, char *argv[], FILE *outfd)
{
int optind;
int force_filename = 0; /* bool; 1 if "--" */
struct cmap_t _global_cmap;
struct cmap_t *cmap = &_global_cmap;
int cmap_given = 0; /* bool; 1 if -map */
int max_width = 0, max_height = 0;
int filecount = 0;
int pixbits;
int i;
char *transrgb = NULL;
int compressmode = 'r';
int delay = 0;
int add_loop_ext = 0; /*bool*/
int loops = 0;
int gifheader_done = 0; /*bool*/
int pass;
int peek_v = 0; /* G.verbose level floor */
cmap_init(cmap);
/* sneak peek -v option */
if (!strcmp("-v", argv[1])) {
peek_v++;
for (i=1; i<argc; i++) argv[i]=argv[i+1];
argc--;
}
for (pass=1; pass <= 2; pass++) {
G.verbose = peek_v;
switch (pass) {
case 1:
if (G.verbose>=1) fprintf(stderr,"=== PASS1: collect colormap and maximum size\n"); /*always supressed...*/
break;
case 2:
if (G.verbose>=1) {
fprintf(stderr,"=== PASS2: output data\n");
fprintf(stderr, "max width x height = %d x %d\n", max_width, max_height);
}
break;
}/*esac pass*/
filecount = 0;
for (optind = 1; optind < argc; optind++) {
FILE *ppmfile;
int status;
int width, height;
gifPixel *raster = NULL;
size_t raster_len;
if (!force_filename && argv[optind][0]=='-' && argv[optind][1] != '\0') {
/* -option */
char *opt = argv[optind];
if (!strcmp("-v", opt)) {
G.verbose++;
continue;
}
else if (!strcmp("+v", opt) || !strcmp("-q", opt)) {
G.verbose--;
continue;
}
else if (!strncmp("-map", opt, 2)) {
optind++;
if (pass == 1) {
FILE *mapf;
/* is there a need to read mapfile from stdin? */
mapf = fopen_(argv[optind], "rb");
if (mapf == NULL) {
pm_message("Can't open file %s\n", argv[optind]);
return 1;
}
if (G.verbose>=0) pm_message("Reading colormap sample from <%s>\n", (mapf==stdin)?"(stdin)":argv[optind]);
ppm_readraster(mapf, NULL,NULL, &width,&height, cmap, '+');
if (mapf!=stdin) fclose(mapf);
cmap_given = 1;
}/*pass1*/
continue;
}/*-map*/
else if (!strncmp("-loop", opt, 3)) {
add_loop_ext = 1;
loops = atoi(argv[++optind]);
continue;
}
else if (!strncmp("-delay", opt, 2)) {
delay = atoi(argv[++optind]);
continue;
}
else if (!strncmp("-transparent", opt, 3)) {
transrgb = argv[++optind];
continue;
}
else if (!strncmp("-rle", opt, 3)
|| !strncmp(opt, "-nolzw", 4) /*ppmtogif opt*/ ) {
compressmode = 'r'; /*default RLE*/
continue;
}
else if (!strncmp("-raw", opt, 3) ||
!strncmp(opt, "-norle", 5)) {
compressmode = 'n'; /*no compression*/
continue;
}
else if (!strcmp("--", opt)) {
force_filename = 1;
continue;
}
else {
/* other options */
goto usage;
continue;
}
usage:
fprintf(stderr, "usage: %s [-v][-q] [-map colormap.ppm] [-transparent <colorspec>] [-loop loopcount] [-delay delay-in-10msecs] [-norle] [ppmfile ...]\n", G.progname);
return 1;
}/* fi options */
force_filename = 0;
/* now argv[optind] is filename */
/* non-animation, single-input shortcut; XXX messy */
if (filecount==0 && pass==1 &&
optind == argc-1 /* single filename */ ) {
if (G.verbose>=1) fprintf(stderr,"=== single input, shortcut to PASS2\n");
pass = 2;
}
ppmfile = fopen_(argv[optind], "rb");
if (0) {
read_stdin: /* TOTAL MESS: implicit stdin mode */
ppmfile = stdin;
}
if (ppmfile == NULL) {
pm_message("Can't open file %s\n", argv[optind]);
return 1;
}
if (G.verbose>=0) pm_message("<%s>\n", (ppmfile==stdin)?"(stdin)":argv[optind]);
switch (pass) {
case 1:
/* collect size and (optionally) cmap */
status = ppm_readraster(ppmfile, NULL,NULL, &width, &height, cmap, cmap_given?'.':'+');
break;
case 2:
/* collect image */
status = ppm_readraster(ppmfile, &raster,&raster_len, &width, &height, cmap, cmap_given?'.':'+');
break;
}
if (status) return status; /*error*/
if (width > max_width) max_width = width;
if (height > max_height) max_height = height;
if (ppmfile != stdin) fclose(ppmfile);
if (pass==2 && !gifheader_done) {
/* delay GIF header output until here
* (not at looptop of pass2)
* to enable 1-pass shortcut on non-animated image
*/
/* get bitlength needed for this pixel values */
for (pixbits=1; (1<<pixbits) < cmap->cmaplen; pixbits++)
;
if (G.verbose>=1) pm_message("Needed Pixel bits: %d\n", pixbits);
if (pixbits > 8) {
fprintf(stderr, "Too many colors (%d bits)\n", pixbits);
return 1;
}
/* output GIF header */
fprintf(outfd, "GIF89a"); /* comment extension needs 89a anyway */
/* logical screen descriptor */
fput_LEs(max_width, outfd); fput_LEs(max_height, outfd);
i = (1 /*:1 Global colormap follows*/<<7) |
(7 /*:3 bits per color intensity*/ <<4) |
(0 /*:1 Global colormap is sorted*/ <<3) |
(pixbits-1 /*:3 (bits/pixel)-1 */);
fputc(i, outfd);
fputc(0, outfd); /* background */
fputc(0, outfd); /* aspect ratio */
/* Global color map */
for (i=0; i<cmap->cmaplen; i++) {
fputc(cmap->cmap[i].r, outfd);
fputc(cmap->cmap[i].g, outfd);
fputc(cmap->cmap[i].b, outfd);
}
/* pad colormap to 2**pixbits */
for ((void) i; i<(1<<pixbits); i++) {
fputc(0, outfd); fputc(0, outfd); fputc(0, outfd);
}
if (add_loop_ext) {
fputc('!', outfd);
fputc(0xff, outfd); /* app extension */
fputc(11, outfd); /* label len */
fputs("NETSCAPE2.0", outfd);
fputc(3, outfd); /* len of subblock */
fputc(1, outfd); /* type? */
fput_LEs(loops, outfd);
fputc(0, outfd); /* terminator */
}
gifheader_done = 1;
}/*GIF header*/
if (pass == 2) {
/*==== per-image Graphic Block */
/* calculate transparency */
i = get_transparentindex(raster,raster_len, cmap, transrgb);
if (G.verbose>=1) if (i>=0) pm_message("Using index %d (%d %d %d) for transparency\n", i, cmap->cmap[i].r,cmap->cmap[i].g,cmap->cmap[i].b);
/* Graphic Control Extention: transparency, delay */
gifout_GCE(outfd, i, delay);
/* Image Descriptor */
fputc(',', outfd);
fput_LEs(0, outfd); fput_LEs(0, outfd); /*left,top*/
fput_LEs(width, outfd); fput_LEs(height, outfd);
i = (0 /*:1 0:use global colormap 1:has local colormap*/ <<7) |
(0 /*:1 0:sequential 1:interlace*/ <<6) |
(0 /*:1 sorted cmap*/ << 5) |
(0 /*:2 reserved*/ <<3) |
0 /*3: (local cmap bits/pixel)-1*/;
fputc(i, outfd);
/* local cmap here if needed */
/* data stream */
switch(compressmode) {
default:
case 'r':
gifraster_runlength_dc(outfd, raster,raster_len, pixbits);
break;
case 'n':
gifraster_nocompress(outfd, raster,raster_len, pixbits);
break;
}
}/*Graphic Block pass2*/
filecount++;
if (raster) { free(raster); raster = NULL; }
}/* wend optind */
/* argv exausted */
/* check for implicit stdin mode */
if (filecount == 0) {
/* no explicit files; force use stdin */
if (G.verbose>=1) fprintf(stderr,"=== use stdin, redo as PASS2\n");
pass = 2;
goto read_stdin;
/* list-based languages could forcibly
* insert "-" at the end of the argv and continue,
* but this is C; logic is messy here
*/
}
}/* wend pass */
/* include comment */
gifout_addprogsig(outfd);
/* Image Trailer */
fputc(';', outfd);
return 0;
}
int
main(int argc, char *argv[])
{
pm_init(&argc, argv);
return anim_main(argc, argv, stdout);
}
Fnews-brouse 1.9(20180406) -- by Mizuno, MWE <mwe@ccsf.jp>
GnuPG Key ID = ECC8A735
GnuPG Key fingerprint = 9BE6 B9E9 55A5 A499 CD51 946E 9BDC 7870 ECC8 A735