xref: /inferno-os/appl/lib/readgif.b (revision 9b29ac7ea714507a9c0690620c02c8ca5ab25f90)
1implement RImagefile;
2
3include "sys.m";
4	sys: Sys;
5
6include "draw.m";
7
8include "bufio.m";
9	bufio: Bufio;
10	Iobuf: import bufio;
11
12include "imagefile.m";
13
14Header: adt
15{
16	fd: ref Iobuf;
17	buf: array of byte;
18	vers: string;
19	screenw: int;
20	screenh: int;
21	fields: int;
22	bgrnd: int;
23	aspect: int;
24	transp: int;
25	trindex: byte;
26};
27
28Entry: adt
29{
30	prefix: int;
31	exten: int;
32};
33
34tbl: array of Entry;
35
36init(iomod: Bufio)
37{
38	if(sys == nil)
39		sys = load Sys Sys->PATH;
40	bufio = iomod;
41}
42read(fd: ref Iobuf): (ref Rawimage, string)
43{
44	(a, err) := readarray(fd, 0);
45	if(a != nil)
46		return (a[0], err);
47	return (nil, err);
48}
49
50readmulti(fd: ref Iobuf): (array of ref Rawimage, string)
51{
52	return readarray(fd, 1);
53}
54
55readarray(fd: ref Iobuf, multi: int): (array of ref Rawimage, string)
56{
57	inittbl();
58
59	buf := array[3*256] of byte;
60
61	(header, err) := readheader(fd, buf);
62	if(header == nil)
63		return (nil, err);
64
65	globalcmap: array of byte;
66	if(header.fields & 16r80){
67		(globalcmap, err) = readcmap(header, (header.fields&7)+1);
68		if(globalcmap == nil)
69			return (nil, err);
70	}
71
72	images: array of ref Rawimage;
73	new: ref Rawimage;
74
75    Loop:
76	for(;;){
77		case c := fd.getb(){
78		Bufio->EOF =>
79			if(err == "")
80				err = "ReadGIF: premature EOF";
81			break Loop;
82		Bufio->ERROR =>
83			err = sys->sprint("ReadGIF: read error: %r");
84			return (nil, err);
85		16r21 =>	# Extension (ignored)
86			err = skipextension(header);
87			if(err != nil)
88				return (nil, err);
89
90		16r2C =>	# Image Descriptor
91			if(!multi && images!=nil)	# why read the rest?
92				break Loop;
93			(new, err) = readimage(header);
94			if(new == nil)
95				return (nil ,err);
96			if(new.fields & 16r80){
97				(new.cmap, err) = readcmap(header, (new.fields&7)+1);
98				if(new.cmap == nil)
99					return (nil, err);
100			}else
101				new.cmap = globalcmap;
102			(new.chans[0], err) = decode(header, new);
103			if(new.chans[0] == nil)
104				return (nil, err);
105			if(new.fields & 16r40)
106				interlace(new);
107			new.transp = header.transp;
108			new.trindex = header.trindex;
109			nimages := array[len images+1] of ref Rawimage;
110			nimages[0:] = images[0:];
111			nimages[len images] = new;
112			images = nimages;
113
114		16r3B =>	# Trailer
115			break Loop;
116
117		* =>
118			err = sys->sprint("ReadGIF: unknown block type: %x", c);
119			break Loop;
120		}
121	}
122
123	if(images==nil || images[0].chans[0] == nil){
124		if(err == nil)
125			err = "ReadGIF: no picture in file";
126		return (nil, err);
127	}
128
129	return (images, err);
130}
131
132readheader(fd: ref Iobuf, buf: array of byte): (ref Header, string)
133{
134	if(fd.read(buf, 13) != 13){
135		err := sys->sprint("ReadGIF: can't read header: %r");
136		return (nil, err);
137	}
138	h := ref Header;
139	h.vers = string buf[0:6];
140	if(h.vers!="GIF87a" && h.vers!="GIF89a"){
141		err := sys->sprint("ReadGIF: can't recognize format %s", h.vers);
142		return (nil, err);
143	}
144	h.screenw = int buf[6]+(int buf[7]<<8);
145	h.screenh = int buf[8]+(int buf[9]<<8);
146	h.fields = int buf[10];
147	h.bgrnd = int buf[11];
148	h.aspect = int buf[12];
149	h.fd = fd;
150	h.buf = buf;
151	h.transp = 0;
152	return (h, "");
153}
154
155readcmap(h: ref Header, size: int): (array of byte,string)
156{
157	size = 3*(1<<size);
158	map := array[size] of byte;
159	if(h.fd.read(map, size) != size)
160		return (nil, "ReadGIF: short read on color map");
161	return (map, "");
162}
163
164readimage(h: ref Header): (ref Rawimage, string)
165{
166	if(h.fd.read(h.buf, 9) != 9){
167		err := sys->sprint("ReadGIF: can't read image descriptor: %r");
168		return (nil, err);
169	}
170	i := ref Rawimage;
171	left := int h.buf[0]+(int h.buf[1]<<8);
172	top := int h.buf[2]+(int h.buf[3]<<8);
173	width := int h.buf[4]+(int h.buf[5]<<8);
174	height := int h.buf[6]+(int h.buf[7]<<8);
175	i.fields = int h.buf[8];
176	i.r.min.x = left;
177	i.r.min.y = top;
178	i.r.max.x = left+width;
179	i.r.max.y = top+height;
180	i.nchans = 1;
181	i.chans = array[1] of array of byte;
182	i.chandesc = CRGB1;
183	return (i, "");
184}
185
186readdata(h: ref Header, ch: chan of (array of byte, string))
187{
188	err: string;
189
190	# send nil for error, buffer of length 0 for EOF
191	for(;;){
192		nbytes := h.fd.getb();
193		if(nbytes < 0){
194			err = sys->sprint("ReadGIF: can't read data: %r");
195			ch <-= (nil, err);
196			return;
197		}
198		d := array[nbytes] of byte;
199		if(nbytes == 0){
200			ch <-= (d, "");
201			return;
202		}
203		n := h.fd.read(d, nbytes);
204		if(n != nbytes){
205			if(n > 0){
206				ch <-= (d[0:n], nil);
207				ch <-= (d[0:0], "ReadGIF: short data subblock");
208			}else
209				ch <-= (nil, sys->sprint("ReadGIF: can't read data: %r"));
210			return;
211		}
212		ch <-= (d, "");
213	}
214}
215
216readerr: con "ReadGIF: can't read extension: %r";
217
218skipextension(h: ref Header): string
219{
220	fmterr: con "ReadGIF: bad extension format";
221
222	hsize := 0;
223	hasdata := 0;
224
225	case h.fd.getb(){
226	Bufio->ERROR or Bufio->EOF =>
227		return sys->sprint(readerr);
228	16r01 =>	# Plain Text Extension
229		hsize = 13;
230		hasdata = 1;
231	16rF9 =>	# Graphic Control Extension
232		return graphiccontrol(h);
233	16rFE =>	# Comment Extension
234		hasdata = 1;
235	16rFF =>	# Application Extension
236		hsize = h.fd.getb();
237		# standard says this must be 11, but Adobe likes to put out 10-byte ones,
238		# so we pay attention to the field.
239		hasdata = 1;
240	* =>
241		return "ReadGIF: unknown extension";
242	}
243	if(hsize>0 && h.fd.read(h.buf, hsize) != hsize)
244		return sys->sprint(readerr);
245	if(!hasdata){
246		if(int h.buf[hsize-1] != 0)
247			return fmterr;
248	}else{
249		ch := chan of (array of byte, string);
250		spawn readdata(h, ch);
251		for(;;){
252			(data, err) := <-ch;
253			if(data == nil)
254				return err;
255			if(len data == 0)
256				break;
257		}
258	}
259	return "";
260}
261
262graphiccontrol(h: ref Header): string
263{
264	if(h.fd.read(h.buf, 5+1) != 5+1)
265		return sys->sprint(readerr);
266	if(int h.buf[1] & 1){
267		h.transp = 1;
268		h.trindex = h.buf[4];
269	}
270	return "";
271}
272
273inittbl()
274{
275	tbl = array[4096] of Entry;
276	for(i:=0; i<258; i++) {
277		tbl[i].prefix = -1;
278		tbl[i].exten = i;
279	}
280}
281
282decode(h: ref Header, i: ref Rawimage): (array of byte, string)
283{
284	c, incode: int;
285
286	err := "";
287	if(h.fd.read(h.buf, 1) != 1){
288		err = sys->sprint("ReadGIF: can't read data: %r");
289		return (nil, err);
290	}
291	codesize := int h.buf[0];
292	if(codesize>8 || 0>codesize){
293		err = sys->sprint("ReadGIF: can't handle codesize %d", codesize);
294		return (nil, err);
295	}
296	err1 := "";
297	if(i.cmap!=nil && len i.cmap!=3*(1<<codesize)
298	  && (codesize!=2 || len i.cmap!=3*2)) # peculiar GIF bitmap files...
299		err1 = sys->sprint("ReadGIF: codesize %d doesn't match color map 3*%d", codesize, len i.cmap/3);
300
301	ch := chan of (array of byte, string);
302
303	spawn readdata(h, ch);
304
305	CTM :=1<<codesize;
306	EOD := CTM+1;
307
308	pic := array[(i.r.max.x-i.r.min.x)*(i.r.max.y-i.r.min.y)] of byte;
309	pici := 0;
310	data := array[0] of byte;
311	datai := 0;
312
313	nbits := 0;
314	sreg := 0;
315	stack := array[4096] of byte;
316	stacki: int;
317	fc := 0;
318
319Init:
320	for(;;){
321		csize := codesize+1;
322		nentry := EOD+1;
323		maxentry := (1<<csize)-1;
324		first := 1;
325		ocode := -1;
326
327		for(;; ocode = incode) {
328			while(nbits < csize) {
329				if(datai == len data){
330					(data, err) = <-ch;
331					if(data == nil)
332						return (nil, err);
333					if(err!="" && err1=="")
334						err1 = err;
335					if(len data == 0)
336						break Init;
337					datai = 0;
338				}
339				c = int data[datai++];
340				sreg |= c<<nbits;
341				nbits += 8;
342			}
343			code := sreg & ((1<<csize) - 1);
344			sreg >>= csize;
345			nbits -= csize;
346
347			if(code == EOD){
348				(data, err) = <-ch;
349				if(len data != 0)
350					err = "ReadGIF: unexpected data past EOD";
351				if(err!="" && err1=="")
352					err1 = err;
353				break Init;
354			}
355
356			if(code == CTM)
357				continue Init;
358
359			stacki = len stack-1;
360
361			incode = code;
362
363			# special case for KwKwK
364			if(code == nentry) {
365				stack[stacki--] = byte fc;
366				code = ocode;
367			}
368
369			if(code > nentry) {
370				err = sys->sprint("ReadGIF: bad code %x %x", code, nentry);
371				return (nil, err);
372			}
373
374			for(c=code; c>=0; c=tbl[c].prefix)
375				stack[stacki--] = byte tbl[c].exten;
376
377			nb := len stack-(stacki+1);
378			if(pici+nb > len pic){
379				if(err1 == "")
380					err1 = "ReadGIF: data overflows picture";
381			}else{
382				pic[pici:] = stack[stacki+1:];
383				pici += nb;
384			}
385
386			fc = int stack[stacki+1];
387
388			if(first){
389				first = 0;
390				continue;
391			}
392			early:=0; # peculiar tiff feature here for reference
393			if(nentry == maxentry-early) {
394				if(csize >= 12)
395					continue;
396				csize++;
397				maxentry = (1<<csize);
398				if(csize < 12)
399					maxentry--;
400			}
401			tbl[nentry].prefix = ocode;
402			tbl[nentry].exten = fc;
403			nentry++;
404		}
405	}
406	return (pic, err1);
407}
408
409interlace(image: ref Rawimage)
410{
411	pic := image.chans[0];
412	r := image.r;
413	dx := r.max.x-r.min.x;
414	ipic := array[dx*(r.max.y-r.min.y)] of byte;
415
416	# Group 1: every 8th row, starting with row 0
417	yy := 0;
418	for(y:=r.min.y; y<r.max.y; y+=8){
419		ipic[y*dx:] = pic[yy*dx:(yy+1)*dx];
420		yy++;
421	}
422
423	# Group 2: every 8th row, starting with row 4
424	for(y=r.min.y+4; y<r.max.y; y+=8){
425		ipic[y*dx:] = pic[yy*dx:(yy+1)*dx];
426		yy++;
427	}
428
429	# Group 3: every 4th row, starting with row 2
430	for(y=r.min.y+2; y<r.max.y; y+=4){
431		ipic[y*dx:] = pic[yy*dx:(yy+1)*dx];
432		yy++;
433	}
434
435	# Group 4: every 2nd row, starting with row 1
436	for(y=r.min.y+1; y<r.max.y; y+=2){
437		ipic[y*dx:] = pic[yy*dx:(yy+1)*dx];
438		yy++;
439	}
440
441	image.chans[0] = ipic;
442}
443