438 lines
9.0 KiB
C
438 lines
9.0 KiB
C
|
#include <u.h>
|
||
|
#include <libc.h>
|
||
|
#include <bio.h>
|
||
|
#include <draw.h>
|
||
|
#include <event.h>
|
||
|
#include <keyboard.h>
|
||
|
#include "imagefile.h"
|
||
|
|
||
|
int cflag = 0;
|
||
|
int dflag = 0;
|
||
|
int eflag = 0;
|
||
|
int nineflag = 0;
|
||
|
int threeflag = 0;
|
||
|
int output = 0;
|
||
|
uint32_t outchan = CMAP8;
|
||
|
Image **allims;
|
||
|
int which;
|
||
|
int defaultcolor = 1;
|
||
|
|
||
|
enum{
|
||
|
Border = 2,
|
||
|
Edge = 5
|
||
|
};
|
||
|
|
||
|
char *show(int, char*);
|
||
|
|
||
|
Rectangle
|
||
|
imager(void)
|
||
|
{
|
||
|
Point p1, p2;
|
||
|
|
||
|
if(allims==nil || allims[0]==nil)
|
||
|
return screen->r;
|
||
|
|
||
|
p1 = addpt(divpt(subpt(allims[0]->r.max, allims[0]->r.min), 2), allims[0]->r.min);
|
||
|
p2 = addpt(divpt(subpt(screen->clipr.max, screen->clipr.min), 2), screen->clipr.min);
|
||
|
return rectaddpt(allims[0]->r, subpt(p2, p1));
|
||
|
}
|
||
|
|
||
|
void
|
||
|
eresized(int new)
|
||
|
{
|
||
|
Rectangle r;
|
||
|
|
||
|
if(new && getwindow(display, Refnone) < 0){
|
||
|
fprint(2, "gif: can't reattach to window\n");
|
||
|
exits("resize");
|
||
|
}
|
||
|
if(allims==nil || allims[which]==nil)
|
||
|
return;
|
||
|
r = imager();
|
||
|
border(screen, r, -Border, nil, ZP);
|
||
|
draw(screen, r, allims[which], nil, allims[which]->r.min);
|
||
|
flushimage(display, 1);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
main(int argc, char *argv[])
|
||
|
{
|
||
|
int fd, i;
|
||
|
char *err;
|
||
|
|
||
|
ARGBEGIN{
|
||
|
case '3': /* produce encoded, compressed, three-color bitmap file; no display by default */
|
||
|
threeflag++;
|
||
|
/* fall through */
|
||
|
case 't': /* produce encoded, compressed, true-color bitmap file; no display by default */
|
||
|
cflag++;
|
||
|
dflag++;
|
||
|
output++;
|
||
|
defaultcolor = 0;
|
||
|
outchan = RGB24;
|
||
|
break;
|
||
|
case 'c': /* produce encoded, compressed, bitmap file; no display by default */
|
||
|
cflag++;
|
||
|
dflag++;
|
||
|
output++;
|
||
|
if(defaultcolor)
|
||
|
outchan = CMAP8;
|
||
|
break;
|
||
|
case 'd': /* suppress display of image */
|
||
|
dflag++;
|
||
|
break;
|
||
|
case 'e': /* disable floyd-steinberg error diffusion */
|
||
|
eflag++;
|
||
|
break;
|
||
|
case 'k': /* force black and white */
|
||
|
defaultcolor = 0;
|
||
|
outchan = GREY8;
|
||
|
break;
|
||
|
case 'v': /* force RGBV */
|
||
|
defaultcolor = 0;
|
||
|
outchan = CMAP8;
|
||
|
break;
|
||
|
case '9': /* produce plan 9, uncompressed, bitmap file; no display by default */
|
||
|
nineflag++;
|
||
|
dflag++;
|
||
|
output++;
|
||
|
if(defaultcolor)
|
||
|
outchan = CMAP8;
|
||
|
break;
|
||
|
default:
|
||
|
fprint(2, "usage: gif -39cdektv [file.gif ...]\n");
|
||
|
exits("usage");
|
||
|
}ARGEND;
|
||
|
|
||
|
err = nil;
|
||
|
if(argc == 0)
|
||
|
err = show(0, "<stdin>");
|
||
|
else{
|
||
|
for(i=0; i<argc; i++){
|
||
|
fd = open(argv[i], OREAD);
|
||
|
if(fd < 0){
|
||
|
fprint(2, "gif: can't open %s: %r\n", argv[i]);
|
||
|
err = "open";
|
||
|
}else{
|
||
|
err = show(fd, argv[i]);
|
||
|
close(fd);
|
||
|
}
|
||
|
if(output && argc>1 && err==nil){
|
||
|
fprint(2, "gif: exiting after one file\n");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
exits(err);
|
||
|
}
|
||
|
|
||
|
Image*
|
||
|
transparency(Rawimage *r, char *name)
|
||
|
{
|
||
|
Image *i;
|
||
|
int j, index;
|
||
|
unsigned char *pic, *mpic, *mask;
|
||
|
|
||
|
if((r->gifflags&TRANSP) == 0)
|
||
|
return nil;
|
||
|
i = allocimage(display, r->r, GREY8, 0, 0);
|
||
|
if(i == nil){
|
||
|
fprint(2, "gif: allocimage for mask of %s failed: %r\n", name);
|
||
|
return nil;
|
||
|
}
|
||
|
pic = r->chans[0];
|
||
|
mask = malloc(r->chanlen);
|
||
|
if(mask == nil){
|
||
|
fprint(2, "gif: malloc for mask of %s failed: %r\n", name);
|
||
|
freeimage(i);
|
||
|
return nil;
|
||
|
}
|
||
|
index = r->giftrindex;
|
||
|
mpic = mask;
|
||
|
for(j=0; j<r->chanlen; j++)
|
||
|
if(*pic++ == index)
|
||
|
*mpic++ = 0;
|
||
|
else
|
||
|
*mpic++ = 0xFF;
|
||
|
if(loadimage(i, i->r, mask, r->chanlen) < 0){
|
||
|
fprint(2, "gif: loadimage for mask of %s failed: %r\n", name);
|
||
|
free(mask);
|
||
|
freeimage(i);
|
||
|
return nil;
|
||
|
}
|
||
|
free(mask);
|
||
|
return i;
|
||
|
}
|
||
|
|
||
|
/* interleave alpha values of 0xFF in data stream. alpha value comes first, then b g r */
|
||
|
unsigned char*
|
||
|
expand(unsigned char *u, int chanlen, int nchan)
|
||
|
{
|
||
|
int j, k;
|
||
|
unsigned char *v, *up, *vp;
|
||
|
|
||
|
v = malloc(chanlen*(nchan+1));
|
||
|
if(v == nil){
|
||
|
fprint(2, "gif: malloc fails: %r\n");
|
||
|
exits("malloc");
|
||
|
}
|
||
|
up = u;
|
||
|
vp = v;
|
||
|
for(j=0; j<chanlen; j++){
|
||
|
*vp++ = 0xFF;
|
||
|
for(k=0; k<nchan; k++)
|
||
|
*vp++ = *up++;
|
||
|
}
|
||
|
return v;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
addalpha(Rawimage *i)
|
||
|
{
|
||
|
char buf[32];
|
||
|
|
||
|
switch(outchan){
|
||
|
case CMAP8:
|
||
|
i->chans[0] = expand(i->chans[0], i->chanlen/1, 1);
|
||
|
i->chanlen = 2*(i->chanlen/1);
|
||
|
i->chandesc = CRGBVA16;
|
||
|
outchan = CHAN2(CMap, 8, CAlpha, 8);
|
||
|
break;
|
||
|
|
||
|
case GREY8:
|
||
|
i->chans[0] = expand(i->chans[0], i->chanlen/1, 1);
|
||
|
i->chanlen = 2*(i->chanlen/1);
|
||
|
i->chandesc = CYA16;
|
||
|
outchan = CHAN2(CGrey, 8, CAlpha, 8);
|
||
|
break;
|
||
|
|
||
|
case RGB24:
|
||
|
i->chans[0] = expand(i->chans[0], i->chanlen/3, 3);
|
||
|
i->chanlen = 4*(i->chanlen/3);
|
||
|
i->chandesc = CRGBA32;
|
||
|
outchan = RGBA32;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
chantostr(buf, outchan);
|
||
|
fprint(2, "gif: can't add alpha to type %s\n", buf);
|
||
|
exits("err");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Called only when writing output. If the output is RGBA32,
|
||
|
* we must write four bytes per pixel instead of two.
|
||
|
* There's always at least two: data plus alpha.
|
||
|
* r is used only for reference; the image is already in c.
|
||
|
*/
|
||
|
void
|
||
|
blackout(Rawimage *r, Rawimage *c)
|
||
|
{
|
||
|
int i, trindex;
|
||
|
unsigned char *rp, *cp;
|
||
|
|
||
|
rp = r->chans[0];
|
||
|
cp = c->chans[0];
|
||
|
trindex = r->giftrindex;
|
||
|
if(outchan == RGBA32)
|
||
|
for(i=0; i<r->chanlen; i++){
|
||
|
if(*rp == trindex){
|
||
|
*cp++ = 0x00;
|
||
|
*cp++ = 0x00;
|
||
|
*cp++ = 0x00;
|
||
|
*cp++ = 0x00;
|
||
|
}else{
|
||
|
*cp++ = 0xFF;
|
||
|
cp += 3;
|
||
|
}
|
||
|
rp++;
|
||
|
}
|
||
|
else
|
||
|
for(i=0; i<r->chanlen; i++){
|
||
|
if(*rp == trindex){
|
||
|
*cp++ = 0x00;
|
||
|
*cp++ = 0x00;
|
||
|
}else{
|
||
|
*cp++ = 0xFF;
|
||
|
cp++;
|
||
|
}
|
||
|
rp++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int
|
||
|
init(void)
|
||
|
{
|
||
|
static int inited;
|
||
|
|
||
|
if(inited == 0){
|
||
|
if(initdraw(0, 0, 0) < 0){
|
||
|
fprint(2, "gif: initdraw failed: %r\n");
|
||
|
return -1;
|
||
|
}
|
||
|
einit(Ekeyboard|Emouse);
|
||
|
inited++;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
char*
|
||
|
show(int fd, char *name)
|
||
|
{
|
||
|
Rawimage **images, **rgbv;
|
||
|
Image *tmp, *msk, *img, *dst, **ims;
|
||
|
Rectangle r;
|
||
|
int j, k, n, ch, nloop, loopcount, dt;
|
||
|
char *err;
|
||
|
char buf[32];
|
||
|
|
||
|
err = nil;
|
||
|
images = readgif(fd, CRGB, dflag);
|
||
|
if(images == nil){
|
||
|
fprint(2, "gif: decode %s failed: %r\n", name);
|
||
|
return "decode";
|
||
|
}
|
||
|
for(n=0; images[n]; n++)
|
||
|
if(n == 0)
|
||
|
r = images[n]->r;
|
||
|
else
|
||
|
combinerect(&r, images[n]->r);
|
||
|
tmp = nil;
|
||
|
ims = malloc((n+1)*sizeof(Image*));
|
||
|
rgbv = malloc((n+1)*sizeof(Rawimage*));
|
||
|
if(rgbv==nil || ims==nil){
|
||
|
fprint(2, "gif: malloc of masks for %s failed: %r\n", name);
|
||
|
err = "malloc";
|
||
|
goto Return;
|
||
|
}
|
||
|
memset(ims, 0, (n+1)*sizeof(Image*));
|
||
|
memset(rgbv, 0, (n+1)*sizeof(Rawimage*));
|
||
|
if(!dflag){
|
||
|
if(init() < 0){
|
||
|
err = "initdraw";
|
||
|
goto Return;
|
||
|
}
|
||
|
if(defaultcolor && screen->depth>8)
|
||
|
outchan = RGB24;
|
||
|
}
|
||
|
|
||
|
for(k=0; k<n; k++){
|
||
|
if(outchan == CMAP8)
|
||
|
rgbv[k] = torgbv(images[k], !eflag);
|
||
|
else{
|
||
|
if(outchan==GREY8 || (images[k]->chandesc==CY && threeflag==0))
|
||
|
rgbv[k] = totruecolor(images[k], CY);
|
||
|
else
|
||
|
rgbv[k] = totruecolor(images[k], CRGB24);
|
||
|
}
|
||
|
if(rgbv[k] == nil){
|
||
|
fprint(2, "gif: converting %s to local format failed: %r\n", name);
|
||
|
err = "torgbv";
|
||
|
goto Return;
|
||
|
}
|
||
|
if(!dflag){
|
||
|
msk = transparency(images[k], name);
|
||
|
if(rgbv[k]->chandesc == CY)
|
||
|
img = allocimage(display, rgbv[k]->r, GREY8, 0, 0);
|
||
|
else
|
||
|
img = allocimage(display, rgbv[k]->r, outchan, 0, 0);
|
||
|
if(tmp == nil)
|
||
|
tmp = allocimage(display, r, img->chan, 0, DWhite);
|
||
|
ims[k]= dst = allocimage(display, r, tmp->chan, 0, DWhite);
|
||
|
if(tmp == nil || img == nil || dst == nil){
|
||
|
fprint(2, "gif: allocimage %s failed: %r\n", name);
|
||
|
err = "allocimage";
|
||
|
goto Return;
|
||
|
}
|
||
|
if(loadimage(img, img->r, rgbv[k]->chans[0], rgbv[k]->chanlen) < 0){
|
||
|
fprint(2, "gif: loadimage %s failed: %r\n", name);
|
||
|
err = "loadimage";
|
||
|
goto Return;
|
||
|
}
|
||
|
switch((images[k]->gifflags>>2)&7){
|
||
|
default:
|
||
|
draw(tmp, img->r, img, msk, img->r.min);
|
||
|
draw(dst, tmp->r, tmp, nil, tmp->r.min);
|
||
|
break;
|
||
|
case 2:
|
||
|
draw(tmp, img->r, display->white, msk, img->r.min);
|
||
|
/* no break */
|
||
|
case 3:
|
||
|
draw(dst, tmp->r, tmp, nil, tmp->r.min);
|
||
|
draw(dst, img->r, img, msk, img->r.min);
|
||
|
break;
|
||
|
}
|
||
|
freeimage(msk);
|
||
|
freeimage(img);
|
||
|
}
|
||
|
}
|
||
|
if(tmp)
|
||
|
freeimage(tmp);
|
||
|
|
||
|
allims = ims;
|
||
|
loopcount = images[0]->gifloopcount;
|
||
|
if(!dflag){
|
||
|
nloop = 0;
|
||
|
do{
|
||
|
for(k=0; k<n; k++){
|
||
|
which = k;
|
||
|
eresized(0);
|
||
|
dt = images[k]->gifdelay*10;
|
||
|
if(dt < 50)
|
||
|
dt = 50;
|
||
|
while(n==1 || ecankbd()){
|
||
|
/* an odd, democratic list */
|
||
|
if((ch=ekbd())=='q' || ch==Kdel || ch==Keof)
|
||
|
exits(nil);
|
||
|
if(ch == '\n')
|
||
|
goto Out;
|
||
|
}sleep(dt);
|
||
|
}
|
||
|
/* loopcount -1 means no loop (this code's rule), loopcount 0 means loop forever (netscape's rule)*/
|
||
|
}while(loopcount==0 || ++nloop<loopcount);
|
||
|
/* loop count has run out */
|
||
|
ekbd();
|
||
|
Out:
|
||
|
draw(screen, screen->clipr, display->white, nil, ZP);
|
||
|
}
|
||
|
if(nineflag){
|
||
|
if(images[0]->gifflags&TRANSP){
|
||
|
addalpha(rgbv[0]);
|
||
|
blackout(images[0], rgbv[0]);
|
||
|
}
|
||
|
chantostr(buf, outchan);
|
||
|
print("%11s %11d %11d %11d %11d ", buf,
|
||
|
rgbv[0]->r.min.x, rgbv[0]->r.min.y, rgbv[0]->r.max.x, rgbv[0]->r.max.y);
|
||
|
if(write(1, rgbv[0]->chans[0], rgbv[0]->chanlen) != rgbv[0]->chanlen){
|
||
|
fprint(2, "gif: %s: write error %r\n", name);
|
||
|
return "write";
|
||
|
}
|
||
|
}else if(cflag){
|
||
|
if(images[0]->gifflags&TRANSP){
|
||
|
addalpha(rgbv[0]);
|
||
|
blackout(images[0], rgbv[0]);
|
||
|
}
|
||
|
if(writerawimage(1, rgbv[0]) < 0){
|
||
|
fprint(2, "gif: %s: write error: %r\n", name);
|
||
|
return "write";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Return:
|
||
|
allims = nil;
|
||
|
for(k=0; images[k]; k++){
|
||
|
for(j=0; j<images[k]->nchans; j++)
|
||
|
free(images[k]->chans[j]);
|
||
|
free(images[k]->cmap);
|
||
|
if(rgbv[k])
|
||
|
free(rgbv[k]->chans[0]);
|
||
|
freeimage(ims[k]);
|
||
|
free(images[k]);
|
||
|
free(rgbv[k]);
|
||
|
}
|
||
|
free(images);
|
||
|
free(ims);
|
||
|
return err;
|
||
|
}
|