]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/jpg/readbmp.c
merge
[plan9front.git] / sys / src / cmd / jpg / readbmp.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <draw.h>
5 #include "imagefile.h"
6 #include "bmp.h"
7
8 /*
9  MS-BMP file reader
10  (c) 2003, I.P.Keller
11
12  aims to decode *all* valid bitmap formats, although some of the
13  flavours couldn't be verified due to lack of suitable test-files.
14  the following flavours are supported:
15
16         Bit/Pix Orientation     Compression     Tested?
17           1     top->bottom     n/a             yes
18           1     bottom->top     n/a             yes
19           4     top->bottom     no              yes
20           4     bottom->top     no              yes
21           4     top->bottom     RLE4            yes, but not with displacement
22           8     top->bottom     no              yes
23           8     bottom->top     no              yes
24           8     top->bottom     RLE8            yes, but not with displacement
25          16     top->bottom     no              no
26          16     bottom->top     no              no
27          16     top->bottom     BITMASK         no
28          16     bottom->top     BITMASK         no
29          24     top->bottom     n/a             yes
30          24     bottom->top     n/a             yes
31          32     top->bottom     no              no
32          32     bottom->top     no              no
33          32     top->bottom     BITMASK         no
34          32     bottom->top     BITMASK         no
35
36  OS/2 1.x bmp files are recognised as well, but testing was very limited.
37
38  verifying was done with a number of test files, generated by
39  different tools. nevertheless, the tests were in no way exhaustive
40  enough to guarantee bug-free decoding. caveat emptor!
41 */
42
43 static short
44 r16(Biobuf*b)
45 {
46         short s;
47
48         s = Bgetc(b);
49         s |= ((short)Bgetc(b)) << 8;
50         return s;
51 }
52
53
54 static long
55 r32(Biobuf*b)
56 {
57         long l;
58
59         l = Bgetc(b);
60         l |= ((long)Bgetc(b)) << 8;
61         l |= ((long)Bgetc(b)) << 16;
62         l |= ((long)Bgetc(b)) << 24;
63         return l;
64 }
65
66
67 /* get highest bit set */
68 static int
69 msb(ulong x)
70 {
71         int i;
72         for(i = 32; i; i--, x <<= 1)
73                 if(x & 0x80000000L)
74                         return i;
75         return 0;
76 }
77
78 /* Load a 1-Bit encoded BMP file (uncompressed) */
79 static int
80 load_1T(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
81 {
82         long ix, iy, i = 0, step_up = 0, padded_width = ((width + 31) / 32) * 32;
83         int val = 0, n;
84
85         if(height > 0) {        /* bottom-up */
86                 i = (height - 1) * width;
87                 step_up = -2 * width;
88         } else
89                 height = -height;
90
91         for(iy = height; iy; iy--, i += step_up)
92                 for(ix = 0, n = 0; ix < padded_width; ix++, n--) {
93                         if(!n) {
94                                 val = Bgetc(b);
95                                 n = 8;
96                         }
97                         if(ix < width) {
98                                 buf[i] = clut[val & 0x80 ? 1 : 0];
99                                 i++;
100                         }
101                         val <<= 1;
102                 }
103         return 0;
104 }
105
106 /* Load a 4-Bit encoded BMP file (uncompressed) */
107 static int
108 load_4T(Biobuf* b, long width, long height, Rgb* buf, Rgb* clut)
109 {
110         long ix, iy, i = 0, step_up = 0, skip = (4 - (((width % 8) + 1) / 2)) & 3;
111         uint valH, valL;
112
113         if(height > 0) {        /* bottom-up */
114                 i = (height - 1) * width;
115                 step_up = -2 * width;
116         } else
117                 height = -height;
118
119         for(iy = height; iy; iy--, i += step_up) {
120                 for(ix = 0; ix < width; ) {
121                         valH = valL = Bgetc(b) & 0xff;
122                         valH >>= 4;
123
124                         buf[i] = clut[valH];
125                         i++; ix++;
126
127                         if(ix < width) {
128                                 valL &= 0xf;
129                                 buf[i] = clut[valL];
130                                 i++; ix++;
131                         }
132                 }
133                 Bseek(b, skip, 1);
134         }
135         return 0;
136 }
137
138 /* Load a 4-Bit encoded BMP file (RLE4-compressed) */
139 static int
140 load_4C(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
141 {
142         long ix, iy = height -1;
143         uint val, valS, skip;
144         Rgb* p;
145
146         while(iy >= 0) {
147                 ix = 0;
148                 while(ix < width) {
149                         val = (uint)Bgetc(b);
150
151                         if(0 != val) {
152                                 valS = (uint)Bgetc(b);
153                                 p = &buf[ix + iy * width];
154                                 while(val--) {
155                                         *p = clut[0xf & (valS >> 4)];
156                                         p++;
157                                         ix++;
158                                         if(val != 0) {
159                                                 *p = clut[0xf & valS];
160                                                 p++;
161                                                 ix++;
162                                                 val--;
163                                         }
164                                 }
165                         } else {
166                                 /* Special modes... */
167                                 val = Bgetc(b);
168                                 switch(val) {
169                                         case 0: /* End-Of-Line detected */
170                                                 ix = width;
171                                                 iy--;
172                                                 break;
173                                         case 1: /* End-Of-Picture detected -->> abort */
174                                                 ix = width;
175                                                 iy = -1;
176                                                 break;
177                                         case 2: /* Position change detected */
178                                                 val = (uint)Bgetc(b);
179                                                 ix += val;
180                                                 val = (uint)Bgetc(b);
181                                                 iy -= val;
182                                                 break;
183
184                                         default:/* Transparent data sequence detected */
185                                                 p = &buf[ix + iy * width];
186                                                 if((1 == (val & 3)) || (2 == (val & 3)))
187                                                         skip = 1;
188                                                 else 
189                                                         skip = 0;
190
191                                                 while(val--) {
192                                                         valS = (uint)Bgetc(b);
193                                                         *p = clut[0xf & (valS >> 4)];
194                                                         p++;
195                                                         ix++;
196                                                         if(val != 0) {
197                                                                 *p = clut[0xf & valS];
198                                                                 p++;
199                                                                 ix++;
200                                                                 val--;
201                                                         }
202                                                 }
203                                                 if(skip)
204                                                         Bgetc(b);
205                                                 break;
206                                 }
207                         }
208                 }
209         }
210         return 0;
211 }
212
213 /* Load a 8-Bit encoded BMP file (uncompressed) */
214 static int
215 load_8T(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
216 {
217         long ix, iy, i = 0, step_up = 0, skip = (4 - (width % 4)) & 3;
218
219         if(height > 0) {        /* bottom-up */
220                 i = (height - 1) * width;
221                 step_up = -2 * width;
222         } else
223                 height = -height;
224
225         for(iy = height; iy; iy--, i += step_up) {
226                 for(ix = 0; ix < width; ix++, i++)
227                         buf[i] = clut[Bgetc(b) & 0xff];
228                 Bseek(b, skip, 1);
229         }
230         return 0;
231 }
232
233 /* Load a 8-Bit encoded BMP file (RLE8-compressed) */
234 static int
235 load_8C(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
236 {
237         long ix, iy = height -1;
238         int val, valS, skip;
239         Rgb* p;
240
241         while(iy >= 0) {
242                 ix = 0;
243                 while(ix < width) {
244                         val = Bgetc(b);
245
246                         if(0 != val) {
247                                 valS = Bgetc(b);
248                                 p = &buf[ix + iy * width];
249                                 while(val--) {
250                                         *p = clut[valS];
251                                         p++;
252                                         ix++;
253                                 }
254                         } else {
255                                 /* Special modes... */
256                                 val = Bgetc(b);
257                                 switch(val) {
258                                         case 0: /* End-Of-Line detected */
259                                                 ix = width;
260                                                 iy--;
261                                                 break;
262                                         case 1: /* End-Of-Picture detected */
263                                                 ix = width;
264                                                 iy = -1;
265                                                 break;
266                                         case 2: /* Position change detected */
267                                                 val = Bgetc(b);
268                                                 ix += val;
269                                                 val = Bgetc(b);
270                                                 iy -= val;
271                                                 break;
272                                         default: /* Transparent (not compressed) sequence detected */
273                                                 p = &buf[ix + iy * width];
274                                                 if(val & 1)
275                                                         skip = 1;
276                                                 else 
277                                                         skip = 0;
278
279                                                 while(val--) {
280                                                         valS = Bgetc(b);
281                                                         *p = clut[valS];
282                                                         p++;
283                                                         ix++;
284                                                 }
285                                                 if(skip)
286                                                         /* Align data stream */
287                                                         Bgetc(b);
288                                                 break;
289                                 }
290                         }
291                 }
292         }
293         return 0;
294 }
295
296 /* Load a 16-Bit encoded BMP file (uncompressed) */
297 static int
298 load_16(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
299 {
300         uchar c[2];
301         long ix, iy, i = 0, step_up = 0;
302
303         if(height > 0) {        /* bottom-up */
304                 i = (height - 1) * width;
305                 step_up = -2 * width;
306         } else
307                 height = -height;
308
309         if(clut) {
310                 unsigned mask_blue =  (unsigned)clut[0].blue +
311                                      ((unsigned)clut[0].green << 8);
312                 unsigned mask_green =  (unsigned)clut[1].blue +
313                                       ((unsigned)clut[1].green << 8);
314                 unsigned mask_red =  (unsigned)clut[2].blue +
315                                     ((unsigned)clut[2].green << 8);
316                 int shft_blue = msb((ulong)mask_blue) - 8;
317                 int shft_green = msb((ulong)mask_green) - 8;
318                 int shft_red = msb((ulong)mask_red) - 8;
319
320                 for(iy = height; iy; iy--, i += step_up)
321                         for(ix = 0; ix < width; ix++, i++) {
322                                 unsigned val;
323                                 Bread(b, c, sizeof(c));
324                                 val = (unsigned)c[0] + ((unsigned)c[1] << 8);
325
326                                 buf[i].alpha = 0;
327                                 if(shft_blue >= 0)
328                                         buf[i].blue = (uchar)((val & mask_blue) >> shft_blue);
329                                 else
330                                         buf[i].blue = (uchar)((val & mask_blue) << -shft_blue);
331                                 if(shft_green >= 0)
332                                         buf[i].green = (uchar)((val & mask_green) >> shft_green);
333                                 else
334                                         buf[i].green = (uchar)((val & mask_green) << -shft_green);
335                                 if(shft_red >= 0)
336                                         buf[i].red = (uchar)((val & mask_red) >> shft_red);
337                                 else
338                                         buf[i].red = (uchar)((val & mask_red) << -shft_red);
339                         }
340         } else
341                 for(iy = height; iy; iy--, i += step_up)
342                         for(ix = 0; ix < width; ix++, i++) {
343                                 Bread(b, c, sizeof(c));
344                                 buf[i].blue = (uchar)((c[0] << 3) & 0xf8);
345                                 buf[i].green = (uchar)(((((unsigned)c[1] << 6) +
346                                                         (((unsigned)c[0]) >> 2))) & 0xf8);
347                                 buf[i].red = (uchar)((c[1] << 1) & 0xf8);
348                         }
349         return 0;
350 }
351
352 /* Load a 24-Bit encoded BMP file (uncompressed) */
353 static int
354 load_24T(Biobuf* b, long width, long height, Rgb* buf)
355 {
356         long ix, iy, i = 0, step_up = 0, skip = (4 - ((width * 3) % 4)) & 3;
357
358         if(height > 0) {        /* bottom-up */
359                 i = (height - 1) * width;
360                 step_up = -2 * width;
361         } else
362                 height = -height;
363
364         for(iy = height; iy; iy--, i += step_up) {
365                 for(ix = 0; ix < width; ix++, i++) {
366                         buf[i].alpha = 0;
367                         buf[i].blue = Bgetc(b);
368                         buf[i].green = Bgetc(b);
369                         buf[i].red = Bgetc(b);
370                 }
371                 Bseek(b, skip, 1);
372         }
373         return 0;
374 }
375
376 /* Load a 32-Bit encoded BMP file (uncompressed) */
377 static int
378 load_32(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
379 {
380         uchar c[4];
381         long ix, iy, i = 0, step_up = 0;
382
383         if(height > 0) {        /* bottom-up */
384                 i = (height - 1) * width;
385                 step_up = -2 * width;
386         } else
387                 height = -height;
388
389         if(clut) {
390                 ulong mask_blue =  (ulong)clut[0].blue +
391                                           ((ulong)clut[0].green << 8) +
392                                           ((ulong)clut[0].red << 16) +
393                                           ((ulong)clut[0].alpha << 24);
394                 ulong mask_green =  (ulong)clut[1].blue +
395                                            ((ulong)clut[1].green << 8) +
396                                            ((ulong)clut[1].red << 16) +
397                                            ((ulong)clut[1].alpha << 24);
398                 ulong mask_red =  (ulong)clut[2].blue +
399                                          ((ulong)clut[2].green << 8) +
400                                          ((ulong)clut[2].red << 16) +
401                                          ((ulong)clut[2].alpha << 24);
402                 int shft_blue = msb(mask_blue) - 8;
403                 int shft_green = msb(mask_green) - 8;
404                 int shft_red = msb(mask_red) - 8;
405
406                 for(iy = height; iy; iy--, i += step_up)
407                         for(ix = 0; ix < width; ix++, i++) {
408                                 ulong val;
409                                 Bread(b, c, sizeof(c));
410                                 val =  (ulong)c[0] + ((ulong)c[1] << 8) +
411                                       ((ulong)c[2] << 16) + ((ulong)c[1] << 24);
412
413                                 buf[i].alpha = 0;
414                                 if(shft_blue >= 0)
415                                         buf[i].blue = (uchar)((val & mask_blue) >> shft_blue);
416                                 else
417                                         buf[i].blue = (uchar)((val & mask_blue) << -shft_blue);
418                                 if(shft_green >= 0)
419                                         buf[i].green = (uchar)((val & mask_green) >> shft_green);
420                                 else
421                                         buf[i].green = (uchar)((val & mask_green) << -shft_green);
422                                 if(shft_red >= 0)
423                                         buf[i].red = (uchar)((val & mask_red) >> shft_red);
424                                 else
425                                         buf[i].red = (uchar)((val & mask_red) << -shft_red);
426                         }
427         } else
428                 for(iy = height; iy; iy--, i += step_up)
429                         for(ix = 0; ix < width; ix++, i++) {
430                                 Bread(b, c, nelem(c));
431                                 buf[i].blue = c[0];
432                                 buf[i].green = c[1];
433                                 buf[i].red = c[2];
434                         }
435         return 0;
436 }
437
438
439 static Rgb*
440 ReadBMP(Biobuf *b, int *width, int *height)
441 {
442         int colours, num_coltab = 0;
443         Filehdr bmfh;
444         Infohdr bmih;
445         Rgb clut[256];
446         Rgb* buf;
447
448         bmfh.type = r16(b);
449         if(bmfh.type != 0x4d42)         /* signature must be 'BM' */
450                 sysfatal("bad magic number, not a BMP file");
451
452         bmfh.size = r32(b);
453         bmfh.reserved1 = r16(b);
454         bmfh.reserved2 = r16(b);
455         bmfh.offbits = r32(b);
456
457         memset(&bmih, 0, sizeof(bmih));
458         bmih.size = r32(b);
459
460         if(bmih.size == 0x0c) {                 /* OS/2 1.x version */
461                 bmih.width = r16(b);
462                 bmih.height = r16(b);
463                 bmih.planes = r16(b);
464                 bmih.bpp = r16(b);
465                 bmih.compression = BMP_RGB;
466         } else {                                /* Windows */
467                 bmih.width = r32(b);
468                 bmih.height = r32(b);
469                 bmih.planes = r16(b);
470                 bmih.bpp = r16(b);
471                 bmih.compression = r32(b);
472                 bmih.imagesize = r32(b);
473                 bmih.hres = r32(b);
474                 bmih.vres = r32(b);
475                 bmih.colours = r32(b);
476                 bmih.impcolours = r32(b);
477         }
478
479         if(bmih.bpp < 16) {
480                 /* load colour table */
481                 if(bmih.impcolours)
482                         num_coltab = (int)bmih.impcolours;
483                 else
484                         num_coltab = 1 << bmih.bpp;
485         } else if(bmih.compression == BMP_BITFIELDS &&
486                   (bmih.bpp == 16 || bmih.bpp == 32))
487                 /* load bitmasks */
488                 num_coltab = 3;
489
490         if(num_coltab) {
491                 int i; 
492                 Bseek(b, bmih.size + Filehdrsz, 0);
493
494                 for(i = 0; i < num_coltab; i++) {
495                         clut[i].blue  = (uchar)Bgetc(b);
496                         clut[i].green = (uchar)Bgetc(b);
497                         clut[i].red   = (uchar)Bgetc(b);
498                         clut[i].alpha = (uchar)Bgetc(b);
499                 }
500         }
501
502         *width = bmih.width;
503         *height = bmih.height;
504         colours = bmih.bpp;
505
506         Bseek(b, bmfh.offbits, 0);
507
508         if ((buf = calloc(sizeof(Rgb), *width * abs(*height))) == nil)
509                 sysfatal("no memory");
510
511         switch(colours) {
512                 case 1:
513                         load_1T(b, *width, *height, buf, clut);
514                         break;
515                 case 4:
516                         if(bmih.compression == BMP_RLE4)
517                                 load_4C(b, *width, *height, buf, clut);
518                         else
519                                 load_4T(b, *width, *height, buf, clut);
520                         break;
521                 case 8:
522                         if(bmih.compression == BMP_RLE8)
523                                 load_8C(b, *width, *height, buf, clut);
524                         else
525                                 load_8T(b, *width, *height, buf, clut);
526                         break;
527                 case 16:
528                         load_16(b, *width, *height, buf,
529                                 bmih.compression == BMP_BITFIELDS ? clut : nil);
530                         break;
531                 case 24:
532                         load_24T(b, *width, *height, buf);
533                         break;
534                 case 32:
535                         load_32(b, *width, *height, buf,
536                                 bmih.compression == BMP_BITFIELDS ? clut : nil);
537                         break;
538         }
539         return buf;
540 }
541
542 Rawimage**
543 Breadbmp(Biobuf *bp, int colourspace)
544 {
545         Rawimage *a, **array;
546         int c, width, height;
547         uchar *r, *g, *b;
548         Rgb *s, *e;
549         Rgb *bmp;
550         char ebuf[128];
551
552         a = nil;
553         bmp = nil;
554         array = nil;
555         USED(a);
556         USED(bmp);
557         if (colourspace != CRGB) {
558                 errstr(ebuf, sizeof ebuf);      /* throw it away */
559                 werrstr("ReadRGB: unknown colour space %d", colourspace);
560                 return nil;
561         }
562
563         if ((bmp = ReadBMP(bp, &width, &height)) == nil)
564                 return nil;
565
566         if ((a = calloc(sizeof(Rawimage), 1)) == nil)
567                 goto Error;
568
569         for (c = 0; c  < 3; c++)
570                 if ((a->chans[c] = calloc(width, height)) == nil)
571                         goto Error;
572
573         if ((array = calloc(sizeof(Rawimage *), 2)) == nil)
574                 goto Error;
575         array[0] = a;
576         array[1] = nil;
577
578         a->nchans = 3;
579         a->chandesc = CRGB;
580         a->chanlen = width * height;
581         a->r = Rect(0, 0, width, height);
582
583         s = bmp;
584         e = s + width * height;
585         r = a->chans[0];
586         g = a->chans[1];
587         b = a->chans[2];
588
589         do {
590                 *r++ = s->red;
591                 *g++ = s->green;
592                 *b++ = s->blue;
593         }while(++s < e);
594
595         free(bmp);
596         return array;
597
598 Error:
599         if (a)
600                 for (c = 0; c < 3; c++)
601                         if (a->chans[c])
602                                 free(a->chans[c]);
603         if (a)
604                 free(a);
605         if (array)
606                 free(array);
607         if (bmp)
608                 free(bmp);
609         return nil;
610
611 }
612
613 Rawimage**
614 readbmp(int fd, int colorspace)
615 {
616         Rawimage * *a;
617         Biobuf b;
618
619         if (Binit(&b, fd, OREAD) < 0)
620                 return nil;
621         a = Breadbmp(&b, colorspace);
622         Bterm(&b);
623         return a;
624 }
625
626