xref: /plan9/sys/src/cmd/jpg/gif.c (revision 42860f2175826b5ad335dcbba3e4af6c135d20a2)
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