1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <draw.h>
5 #include <event.h>
6 #include "imagefile.h"
7
8 int cflag = 0;
9 int dflag = 0;
10 int eflag = 0;
11 int nineflag = 0;
12 int threeflag = 0;
13 int output = 0;
14 ulong outchan = CMAP8;
15 Image **allims;
16 Image **allmasks;
17 Rawimage **allimages;
18 int which;
19 int defaultcolor = 1;
20
21 enum{
22 Border = 2,
23 Edge = 5
24 };
25
26 char *show(int, char*);
27
28 Rectangle
imager(void)29 imager(void)
30 {
31 Rectangle r;
32
33 if(allims==nil || allims[0]==nil)
34 return screen->r;
35 r = insetrect(screen->clipr, Edge+Border);
36 r.max.x = r.min.x+Dx(allims[0]->r);
37 r.max.y = r.min.y+Dy(allims[0]->r);
38 return r;
39 }
40
41 void
eresized(int new)42 eresized(int new)
43 {
44 Rectangle r;
45
46 if(new && getwindow(display, Refnone) < 0){
47 fprint(2, "gif: can't reattach to window\n");
48 exits("resize");
49 }
50 if(allims==nil || allims[which]==nil)
51 return;
52 r = imager();
53 border(screen, r, -Border, nil, ZP);
54 r.min.x += allims[which]->r.min.x - allims[0]->r.min.x;
55 r.min.y += allims[which]->r.min.y - allims[0]->r.min.y;
56 if(which > 0 && allimages[which]->gifflags & TRANSP)
57 drawop(screen, r, allims[which], allmasks[which],
58 allims[which]->r.min, SoverD);
59 else
60 drawop(screen, r, allims[which], allmasks[which],
61 allims[which]->r.min, S);
62 flushimage(display, 1);
63 }
64
65 void
main(int argc,char * argv[])66 main(int argc, char *argv[])
67 {
68 int fd, i;
69 char *err;
70
71 ARGBEGIN{
72 case '3': /* produce encoded, compressed, three-color bitmap file; no display by default */
73 threeflag++;
74 /* fall through */
75 case 't': /* produce encoded, compressed, true-color bitmap file; no display by default */
76 cflag++;
77 dflag++;
78 output++;
79 defaultcolor = 0;
80 outchan = RGB24;
81 break;
82 case 'c': /* produce encoded, compressed, bitmap file; no display by default */
83 cflag++;
84 dflag++;
85 output++;
86 if(defaultcolor)
87 outchan = CMAP8;
88 break;
89 case 'd': /* suppress display of image */
90 dflag++;
91 break;
92 case 'e': /* disable floyd-steinberg error diffusion */
93 eflag++;
94 break;
95 case 'k': /* force black and white */
96 defaultcolor = 0;
97 outchan = GREY8;
98 break;
99 case 'v': /* force RGBV */
100 defaultcolor = 0;
101 outchan = CMAP8;
102 break;
103 case '9': /* produce plan 9, uncompressed, bitmap file; no display by default */
104 nineflag++;
105 dflag++;
106 output++;
107 if(defaultcolor)
108 outchan = CMAP8;
109 break;
110 default:
111 fprint(2, "usage: gif -39cdektv [file.gif ...]\n");
112 exits("usage");
113 }ARGEND;
114
115 err = nil;
116 if(argc == 0)
117 err = show(0, "<stdin>");
118 else{
119 for(i=0; i<argc; i++){
120 fd = open(argv[i], OREAD);
121 if(fd < 0){
122 fprint(2, "gif: can't open %s: %r\n", argv[i]);
123 err = "open";
124 }else{
125 err = show(fd, argv[i]);
126 close(fd);
127 }
128 if(output && argc>1 && err==nil){
129 fprint(2, "gif: exiting after one file\n");
130 break;
131 }
132 }
133 }
134 exits(err);
135 }
136
137 Image*
transparency(Rawimage * r,char * name)138 transparency(Rawimage *r, char *name)
139 {
140 Image *i;
141 int j, index;
142 uchar *pic, *mpic, *mask;
143
144 if((r->gifflags&TRANSP) == 0)
145 return nil;
146 i = allocimage(display, r->r, GREY8, 0, 0);
147 if(i == nil){
148 fprint(2, "gif: allocimage for mask of %s failed: %r\n", name);
149 return nil;
150 }
151 pic = r->chans[0];
152 mask = malloc(r->chanlen);
153 if(mask == nil){
154 fprint(2, "gif: malloc for mask of %s failed: %r\n", name);
155 freeimage(i);
156 return nil;
157 }
158 index = r->giftrindex;
159 mpic = mask;
160 for(j=0; j<r->chanlen; j++)
161 if(*pic++ == index)
162 *mpic++ = 0;
163 else
164 *mpic++ = 0xFF;
165 if(loadimage(i, i->r, mask, r->chanlen) < 0){
166 fprint(2, "gif: loadimage for mask of %s failed: %r\n", name);
167 free(mask);
168 freeimage(i);
169 return nil;
170 }
171 free(mask);
172 return i;
173 }
174
175 /* interleave alpha values of 0xFF in data stream. alpha value comes first, then b g r */
176 uchar*
expand(uchar * u,int chanlen,int nchan)177 expand(uchar *u, int chanlen, int nchan)
178 {
179 int j, k;
180 uchar *v, *up, *vp;
181
182 v = malloc(chanlen*(nchan+1));
183 if(v == nil){
184 fprint(2, "gif: malloc fails: %r\n");
185 exits("malloc");
186 }
187 up = u;
188 vp = v;
189 for(j=0; j<chanlen; j++){
190 *vp++ = 0xFF;
191 for(k=0; k<nchan; k++)
192 *vp++ = *up++;
193 }
194 return v;
195 }
196
197 void
addalpha(Rawimage * i)198 addalpha(Rawimage *i)
199 {
200 char buf[32];
201
202 switch(outchan){
203 case CMAP8:
204 i->chans[0] = expand(i->chans[0], i->chanlen/1, 1);
205 i->chanlen = 2*(i->chanlen/1);
206 i->chandesc = CRGBVA16;
207 outchan = CHAN2(CMap, 8, CAlpha, 8);
208 break;
209
210 case GREY8:
211 i->chans[0] = expand(i->chans[0], i->chanlen/1, 1);
212 i->chanlen = 2*(i->chanlen/1);
213 i->chandesc = CYA16;
214 outchan = CHAN2(CGrey, 8, CAlpha, 8);
215 break;
216
217 case RGB24:
218 i->chans[0] = expand(i->chans[0], i->chanlen/3, 3);
219 i->chanlen = 4*(i->chanlen/3);
220 i->chandesc = CRGBA32;
221 outchan = RGBA32;
222 break;
223
224 default:
225 chantostr(buf, outchan);
226 fprint(2, "gif: can't add alpha to type %s\n", buf);
227 exits("err");
228 }
229 }
230
231 /*
232 * Called only when writing output. If the output is RGBA32,
233 * we must write four bytes per pixel instead of two.
234 * There's always at least two: data plus alpha.
235 * r is used only for reference; the image is already in c.
236 */
237 void
blackout(Rawimage * r,Rawimage * c)238 blackout(Rawimage *r, Rawimage *c)
239 {
240 int i, trindex;
241 uchar *rp, *cp;
242
243 rp = r->chans[0];
244 cp = c->chans[0];
245 trindex = r->giftrindex;
246 if(outchan == RGBA32)
247 for(i=0; i<r->chanlen; i++){
248 if(*rp == trindex){
249 *cp++ = 0x00;
250 *cp++ = 0x00;
251 *cp++ = 0x00;
252 *cp++ = 0x00;
253 }else{
254 *cp++ = 0xFF;
255 cp += 3;
256 }
257 rp++;
258 }
259 else
260 for(i=0; i<r->chanlen; i++){
261 if(*rp == trindex){
262 *cp++ = 0x00;
263 *cp++ = 0x00;
264 }else{
265 *cp++ = 0xFF;
266 cp++;
267 }
268 rp++;
269 }
270 }
271
272 int
init(void)273 init(void)
274 {
275 static int inited;
276
277 if(inited == 0){
278 if(initdraw(0, 0, 0) < 0){
279 fprint(2, "gif: initdraw failed: %r\n");
280 return -1;
281 }
282 einit(Ekeyboard|Emouse);
283 inited++;
284 }
285 return 1;
286 }
287
288 char*
show(int fd,char * name)289 show(int fd, char *name)
290 {
291 Rawimage **images, **rgbv;
292 Image **ims, **masks;
293 int j, k, n, ch, nloop, loopcount, dt;
294 char *err;
295 char buf[32];
296
297 err = nil;
298 images = readgif(fd, CRGB);
299 if(images == nil){
300 fprint(2, "gif: decode %s failed: %r\n", name);
301 return "decode";
302 }
303 for(n=0; images[n]; n++)
304 ;
305 ims = malloc((n+1)*sizeof(Image*));
306 masks = malloc((n+1)*sizeof(Image*));
307 rgbv = malloc((n+1)*sizeof(Rawimage*));
308 if(masks==nil || rgbv==nil || ims==nil){
309 fprint(2, "gif: malloc of masks for %s failed: %r\n", name);
310 err = "malloc";
311 goto Return;
312 }
313 memset(masks, 0, (n+1)*sizeof(Image*));
314 memset(ims, 0, (n+1)*sizeof(Image*));
315 memset(rgbv, 0, (n+1)*sizeof(Rawimage*));
316 if(!dflag){
317 if(init() < 0){
318 err = "initdraw";
319 goto Return;
320 }
321 if(defaultcolor && screen->depth>8)
322 outchan = RGB24;
323 }
324
325 for(k=0; k<n; k++){
326 if(outchan == CMAP8)
327 rgbv[k] = torgbv(images[k], !eflag);
328 else{
329 if(outchan==GREY8 || (images[k]->chandesc==CY && threeflag==0))
330 rgbv[k] = totruecolor(images[k], CY);
331 else
332 rgbv[k] = totruecolor(images[k], CRGB24);
333 }
334 if(rgbv[k] == nil){
335 fprint(2, "gif: converting %s to local format failed: %r\n", name);
336 err = "torgbv";
337 goto Return;
338 }
339 if(!dflag){
340 masks[k] = transparency(images[k], name);
341 if(rgbv[k]->chandesc == CY)
342 ims[k] = allocimage(display, rgbv[k]->r, GREY8, 0, 0);
343 else
344 ims[k] = allocimage(display, rgbv[k]->r, outchan, 0, 0);
345 if(ims[k] == nil){
346 fprint(2, "gif: allocimage %s failed: %r\n", name);
347 err = "allocimage";
348 goto Return;
349 }
350 if(loadimage(ims[k], ims[k]->r, rgbv[k]->chans[0], rgbv[k]->chanlen) < 0){
351 fprint(2, "gif: loadimage %s failed: %r\n", name);
352 err = "loadimage";
353 goto Return;
354 }
355 }
356 }
357
358 allimages = images;
359 allims = ims;
360 allmasks = masks;
361 loopcount = images[0]->gifloopcount;
362 if(!dflag){
363 nloop = 0;
364 do{
365 for(k=0; k<n; k++){
366 which = k;
367 eresized(0);
368 dt = images[k]->gifdelay*10;
369 if(dt < 50)
370 dt = 50;
371 while(n==1 || ecankbd()){
372 if((ch=ekbd())=='q' || ch==0x7F || ch==0x04) /* an odd, democratic list */
373 exits(nil);
374 if(ch == '\n')
375 goto Out;
376 }sleep(dt);
377 }
378 /* loopcount -1 means no loop (this code's rule), loopcount 0 means loop forever (netscape's rule)*/
379 }while(loopcount==0 || ++nloop<loopcount);
380 /* loop count has run out */
381 ekbd();
382 Out:
383 drawop(screen, screen->clipr, display->white, nil, ZP, S);
384 }
385 if(n>1 && output)
386 fprint(2, "gif: warning: only writing first image in %d-image GIF %s\n", n, name);
387 if(nineflag){
388 if(images[0]->gifflags&TRANSP){
389 addalpha(rgbv[0]);
390 blackout(images[0], rgbv[0]);
391 }
392 chantostr(buf, outchan);
393 print("%11s %11d %11d %11d %11d ", buf,
394 rgbv[0]->r.min.x, rgbv[0]->r.min.y, rgbv[0]->r.max.x, rgbv[0]->r.max.y);
395 if(write(1, rgbv[0]->chans[0], rgbv[0]->chanlen) != rgbv[0]->chanlen){
396 fprint(2, "gif: %s: write error %r\n", name);
397 return "write";
398 }
399 }else if(cflag){
400 if(images[0]->gifflags&TRANSP){
401 addalpha(rgbv[0]);
402 blackout(images[0], rgbv[0]);
403 }
404 if(writerawimage(1, rgbv[0]) < 0){
405 fprint(2, "gif: %s: write error: %r\n", name);
406 return "write";
407 }
408 }
409
410 Return:
411 allims = nil;
412 allmasks = nil;
413 allimages = nil;
414 for(k=0; images[k]; k++){
415 for(j=0; j<images[k]->nchans; j++)
416 free(images[k]->chans[j]);
417 free(images[k]->cmap);
418 if(rgbv[k])
419 free(rgbv[k]->chans[0]);
420 freeimage(ims[k]);
421 freeimage(masks[k]);
422 free(images[k]);
423 free(rgbv[k]);
424 }
425 free(images);
426 free(masks);
427 free(ims);
428 return err;
429 }
430