tests: framework: Use more portable posix_memalign.
[gfxprim.git] / libs / loaders / GP_BMP.c
blob3ed51613f401f2b6cb88abe9cc0a4529e50cef3d
1 /*****************************************************************************
2 * This file is part of gfxprim library. *
3 * *
4 * Gfxprim is free software; you can redistribute it and/or *
5 * modify it under the terms of the GNU Lesser General Public *
6 * License as published by the Free Software Foundation; either *
7 * version 2.1 of the License, or (at your option) any later version. *
8 * *
9 * Gfxprim is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
12 * Lesser General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU Lesser General Public *
15 * License along with gfxprim; if not, write to the Free Software *
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, *
17 * Boston, MA 02110-1301 USA *
18 * *
19 * Copyright (C) 2009-2012 Cyril Hrubis <metan@ucw.cz> *
20 * *
21 *****************************************************************************/
25 BMP loader.
27 Thanks wikipedia for the format specification.
31 #include <stdint.h>
32 #include <inttypes.h>
34 #include <ctype.h>
35 #include <errno.h>
36 #include <string.h>
37 #include <stdio.h>
39 #include "core/GP_Debug.h"
40 #include "core/GP_Pixel.h"
41 #include "core/GP_GetPutPixel.h"
42 #include "GP_BMP.h"
44 #define BMP_HEADER_OFFSET 0x0a /* info header offset - 4 bytes */
46 #define BUF_TO_4(buf, off) \
47 (buf[off] + (buf[off+1]<<8) + (buf[off+2]<<16) + (buf[off+3]<<24))
49 #define BUF_TO_2(buf, off) \
50 (buf[off] + (buf[off+1]<<8))
53 struct bitmap_info_header {
55 * Offset to image data.
57 uint32_t pixel_offset;
60 * Header size (palette is on offset header_size + 14)
62 uint32_t header_size;
64 /*
65 * Image size in pixels.
66 * If h is negative image is top-down (bottom-up is default)
68 int32_t w;
69 int32_t h;
71 uint16_t bpp;
72 uint32_t compress_type;
73 /*
74 * if 0 image uses whole range (2^bpp colors)
76 uint32_t palette_colors;
79 enum bitmap_compress {
80 COMPRESS_RGB = 0, /* uncompressed */
81 COMPRESS_RLE8 = 1, /* run-length encoded bitmap */
82 COMPRESS_RLE4 = 2, /* run-length encoded bitmap */
83 COMPRESS_BITFIELDS = 3, /* bitfield for each channel */
84 COMPRESS_JPEG = 4, /* only for printers */
85 COMPRESS_PNG = 5, /* only for printers */
86 COMPRESS_ALPHABITFIELDS = 6,
87 COMPRESS_MAX = COMPRESS_ALPHABITFIELDS,
90 static const char *bitmap_compress_names[] = {
91 "RGB",
92 "RLE8",
93 "RLE4",
94 "BITFIELDS",
95 "JPEG",
96 "PNG",
97 "ALPHABITFIELDS",
100 static const char *bitmap_compress_name(uint32_t compress)
102 if (compress >= COMPRESS_MAX)
103 return "Unknown";
105 return bitmap_compress_names[compress];
108 enum bitmap_info_header_sizes {
109 BITMAPCOREHEADER = 12, /* old OS/2 format + win 3.0 */
110 BITMAPCOREHEADER2 = 64, /* OS/2 */
111 BITMAPINFOHEADER = 40, /* most common */
112 BITMAPINFOHEADER2 = 52, /* Undocummented */
113 BITMAPINFOHEADER3 = 56, /* Undocummented */
114 BITMAPINFOHEADER4 = 108, /* adds color space + gamma - win 95/NT4 */
115 BITMAPINFOHEADER5 = 124, /* adds ICC color profiles win 98+ */
118 static const char *bitmap_header_size_name(uint32_t size)
120 switch (size) {
121 case BITMAPCOREHEADER:
122 return "BitmapCoreHeader";
123 case BITMAPCOREHEADER2:
124 return "BitmapCoreHeader2";
125 case BITMAPINFOHEADER:
126 return "BitmapInfoHeader";
127 case BITMAPINFOHEADER2:
128 return "BitmapInfoHeader2";
129 case BITMAPINFOHEADER3:
130 return "BitmapInfoHeader3";
131 case BITMAPINFOHEADER4:
132 return "BitmapInfoHeader4";
133 case BITMAPINFOHEADER5:
134 return "BitmapInfoHeader5";
137 return "Unknown";
140 static uint32_t get_palette_size(struct bitmap_info_header *header)
143 if (header->palette_colors)
144 return header->palette_colors;
146 return (1 << header->bpp);
149 static int read_bitmap_info_header(FILE *f, struct bitmap_info_header *header)
151 uint8_t buf[36];
153 if (fread(buf, 1, sizeof(buf), f) != sizeof(buf)) {
154 GP_DEBUG(1, "Failed to read bitmap info header");
155 return EIO;
158 header->w = BUF_TO_4(buf, 0);
159 header->h = BUF_TO_4(buf, 4);
160 header->bpp = BUF_TO_2(buf, 10);
161 header->compress_type = BUF_TO_4(buf, 12);
162 header->palette_colors = BUF_TO_4(buf, 28);
164 uint16_t nr_planes = BUF_TO_2(buf, 8);
166 /* This must be 1 according to specs */
167 if (nr_planes != 1)
168 GP_DEBUG(1, "Number of planes is %"PRId16" should be 1",
169 nr_planes);
171 GP_DEBUG(2, "Have BMP bitmap size %"PRId32"x%"PRId32" %"PRIu16" "
172 "bpp, %"PRIu32" pallete colors, '%s' compression",
173 header->w, header->h, header->bpp,
174 get_palette_size(header),
175 bitmap_compress_name(header->compress_type));
177 return 0;
180 static int read_bitmap_core_header(FILE *f, struct bitmap_info_header *header)
182 uint8_t buf[12];
184 if (fread(buf, 1, sizeof(buf), f) != sizeof(buf)) {
185 GP_DEBUG(1, "Failed to read bitmap core header");
186 return EIO;
189 header->w = BUF_TO_2(buf, 0);
190 header->h = BUF_TO_2(buf, 2);
191 header->bpp = BUF_TO_2(buf, 6);
192 header->compress_type = COMPRESS_RGB;
193 header->palette_colors = 0;
195 uint16_t nr_planes = BUF_TO_2(buf, 4);
197 /* This must be 1 according to specs */
198 if (nr_planes != 1)
199 GP_DEBUG(1, "Number of planes is %"PRId16" should be 1",
200 nr_planes);
202 GP_DEBUG(2, "Have BMP bitmap size %"PRId32"x%"PRId32" %"PRIu16" bpp",
203 header->h, header->w, header->bpp);
205 return 0;
208 static int read_bitmap_header(FILE *f, struct bitmap_info_header *header)
210 uint8_t buf[8];
211 int err;
213 if (fseek(f, BMP_HEADER_OFFSET, SEEK_SET)) {
214 err = errno;
215 GP_DEBUG(1, "fseek(f, 0x%02x) failed: '%s'",
216 BMP_HEADER_OFFSET, strerror(errno));
217 return err;
220 /* Read info header size, header size determines header type */
221 if (fread(buf, 1, sizeof(buf), f) != sizeof(buf)) {
222 GP_DEBUG(1, "Failed to read info header size");
223 return EIO;
226 header->pixel_offset = BUF_TO_4(buf, 0);
227 header->header_size = BUF_TO_4(buf, 4);
229 GP_DEBUG(2, "BMP header type '%s'",
230 bitmap_header_size_name(header->header_size));
232 switch (header->header_size) {
233 case BITMAPCOREHEADER:
234 err = read_bitmap_core_header(f, header);
235 break;
236 case BITMAPCOREHEADER2:
237 return ENOSYS;
238 /* The bitmap core header only adds filelds to the end of the header */
239 case BITMAPINFOHEADER:
240 case BITMAPINFOHEADER2:
241 case BITMAPINFOHEADER3:
242 case BITMAPINFOHEADER4:
243 err = read_bitmap_info_header(f, header);
244 break;
245 default:
246 GP_DEBUG(1, "Unknown header type, continuing anyway");
247 err = read_bitmap_info_header(f, header);
248 break;
251 return err;
255 * Reads palette, the format is R G B X, each one byte.
257 static int read_bitmap_palette(FILE *f, struct bitmap_info_header *header,
258 GP_Pixel *palette)
260 uint32_t palette_colors = get_palette_size(header);
261 uint32_t palette_offset = header->header_size + 14;
262 uint8_t pixel_size;
263 uint32_t i;
264 int err;
266 switch (header->header_size) {
267 case BITMAPCOREHEADER:
268 pixel_size = 3;
269 break;
270 default:
271 pixel_size = 4;
272 break;
275 GP_DEBUG(2, "Offset to BMP palette is 0x%x (%ubytes) "
276 "pixel size %"PRIu8"bytes",
277 palette_offset, palette_offset, pixel_size);
279 if (fseek(f, palette_offset, SEEK_SET)) {
280 err = errno;
281 GP_DEBUG(1, "fseek(f, 0x%02x) failed: '%s'",
282 BMP_HEADER_OFFSET, strerror(errno));
283 return err;
286 for (i = 0; i < palette_colors; i++) {
287 uint8_t buf[4];
289 if (fread(buf, 1, pixel_size, f) != pixel_size) {
290 GP_DEBUG(1, "Failed to read palette %"PRIu32, i);
291 return EIO;
294 palette[i] = GP_Pixel_CREATE_RGB888(buf[2], buf[1], buf[0]);
296 GP_DEBUG(3, "Palette[%"PRIu32"] = [0x%02x, 0x%02x, 0x%02x]", i,
297 GP_Pixel_GET_R_RGB888(palette[i]),
298 GP_Pixel_GET_G_RGB888(palette[i]),
299 GP_Pixel_GET_B_RGB888(palette[i]));
302 return 0;
305 static int seek_pixels_offset(struct bitmap_info_header *header, FILE *f)
307 int err;
309 GP_DEBUG(2, "Offset to BMP pixels is 0x%x (%ubytes)",
310 header->pixel_offset, header->pixel_offset);
312 if (fseek(f, header->pixel_offset, SEEK_SET)) {
313 err = errno;
314 GP_DEBUG(1, "fseek(f, 0x%02x) failed: '%s'",
315 header->pixel_offset, strerror(errno));
316 return err;
319 return 0;
322 static GP_PixelType match_pixel_type(struct bitmap_info_header *header)
324 switch (header->bpp) {
325 case 1:
326 case 2:
327 case 4:
328 case 8:
329 case 24:
330 return GP_PIXEL_RGB888;
333 return GP_PIXEL_UNKNOWN;
337 * Returns four byte aligned row size for palette formats.
339 static uint32_t bitmap_row_size(struct bitmap_info_header *header)
341 uint32_t row_size = 0;
343 /* align width to whole bytes */
344 switch (header->bpp) {
345 case 1:
346 row_size = header->w / 8 + !!(header->w%8);
347 break;
348 case 2:
349 row_size = header->w / 4 + !!(header->w%4);
350 break;
351 case 4:
352 row_size = header->w / 2 + !!(header->w%2);
353 break;
354 case 8:
355 row_size = header->w;
356 break;
359 /* align row_size to four byte boundary */
360 switch (row_size % 4) {
361 case 1:
362 row_size++;
363 case 2:
364 row_size++;
365 case 3:
366 row_size++;
367 case 0:
368 break;
371 GP_DEBUG(2, "bpp = %"PRIu16", width = %"PRId32", row_size = %"PRIu32,
372 header->bpp, header->w, row_size);
374 return row_size;
377 static uint8_t get_idx(struct bitmap_info_header *header,
378 uint8_t row[], int32_t x)
380 switch (header->bpp) {
381 case 1:
382 return !!(row[x/8] & (1<<(7 - x%8)));
383 case 2:
384 return (row[x/4] >> (2*(3 - x%4))) & 0x03;
385 case 4:
386 return (row[x/2] >> (4*(!(x%2)))) & 0x0f;
387 break;
388 case 8:
389 return row[x];
392 return 0;
395 static int read_palette(FILE *f, struct bitmap_info_header *header,
396 GP_Context *context, GP_ProgressCallback *callback)
398 uint32_t palette_size = get_palette_size(header);
399 GP_Pixel palette[get_palette_size(header)];
400 int err;
402 if ((err = read_bitmap_palette(f, header, palette)))
403 return err;
405 if ((err = seek_pixels_offset(header, f)))
406 return err;
408 uint32_t row_size = bitmap_row_size(header);
409 int32_t y;
411 for (y = 0; y < GP_ABS(header->h); y++) {
412 int32_t x;
413 uint8_t row[row_size];
415 if (fread(row, 1, row_size, f) != row_size) {
416 GP_DEBUG(1, "Failed to read row %"PRId32, y);
417 return EIO;
420 for (x = 0; x < header->w; x++) {
421 uint8_t idx = get_idx(header, row, x);
422 GP_Pixel p;
424 if (idx >= palette_size) {
425 GP_DEBUG(1, "Index out of palette, ignoring");
426 p = 0;
427 } else {
428 p = palette[idx];
431 int32_t ry;
433 if (header->h < 0)
434 ry = y;
435 else
436 ry = GP_ABS(header->h) - 1 - y;
438 GP_PutPixel_Raw_24BPP(context, x, ry, p);
441 if (GP_ProgressCallbackReport(callback, header->h - y -1,
442 context->h, context->w)) {
443 GP_DEBUG(1, "Operation aborted");
444 return ECANCELED;
448 GP_ProgressCallbackDone(callback);
449 return 0;
452 static int read_rgb888(FILE *f, struct bitmap_info_header *header,
453 GP_Context *context, GP_ProgressCallback *callback)
455 uint32_t row_size = 3 * header->w;
456 int32_t y;
457 int err;
459 if ((err = seek_pixels_offset(header, f)))
460 return err;
462 for (y = 0; y < GP_ABS(header->h); y++) {
463 int32_t ry;
465 if (header->h < 0)
466 ry = y;
467 else
468 ry = GP_ABS(header->h) - 1 - y;
470 uint8_t *row = GP_PIXEL_ADDR(context, 0, ry);
472 if (fread(row, 1, row_size, f) != row_size) {
473 GP_DEBUG(1, "Failed to read row %"PRId32, y);
474 return EIO;
477 /* Rows are four byte aligned */
478 switch (row_size % 4) {
479 case 1:
480 fgetc(f);
481 case 2:
482 fgetc(f);
483 case 3:
484 fgetc(f);
485 case 0:
486 break;
489 if (GP_ProgressCallbackReport(callback, header->h - y -1,
490 context->h, context->w)) {
491 GP_DEBUG(1, "Operation aborted");
492 return ECANCELED;
496 GP_ProgressCallbackDone(callback);
497 return 0;
500 static int read_bitmap_pixels(FILE *f, struct bitmap_info_header *header,
501 GP_Context *context, GP_ProgressCallback *callback)
503 switch (header->bpp) {
504 case 1:
505 /* I haven't been able to locate 2bpp palette bmp file => not tested */
506 case 2:
507 case 4:
508 case 8:
509 return read_palette(f, header, context, callback);
510 case 24:
511 return read_rgb888(f, header, context, callback);
514 return ENOSYS;
517 int GP_MatchBMP(const void *buf)
519 return !memcmp(buf, "BM", 2);
522 int GP_OpenBMP(const char *src_path, FILE **f,
523 GP_Size *w, GP_Size *h, GP_PixelType *pixel_type)
525 int err;
527 *f = fopen(src_path, "rb");
529 if (*f == NULL) {
530 err = errno;
531 GP_DEBUG(1, "Failed to open '%s' : %s",
532 src_path, strerror(errno));
533 goto err2;
536 int ch1 = fgetc(*f);
537 int ch2 = fgetc(*f);
539 if (ch1 != 'B' || ch2 != 'M') {
540 GP_DEBUG(1, "Unexpected bitmap header 0x%02x (%c) 0x%02x (%c)",
541 ch1, isascii(ch1) ? ch1 : ' ',
542 ch2, isascii(ch2) ? ch2 : ' ');
543 err = EIO;
544 goto err1;
547 if (w != NULL || h != NULL || pixel_type != NULL) {
548 struct bitmap_info_header header;
550 if ((err = read_bitmap_header(*f, &header)))
551 goto err1;
553 if (w != NULL)
554 *w = header.w;
556 if (h != NULL)
557 *h = header.h;
559 if (pixel_type != NULL)
560 *pixel_type = match_pixel_type(&header);
563 return 0;
564 err1:
565 fclose(*f);
566 err2:
567 errno = err;
568 return 1;
571 GP_Context *GP_ReadBMP(FILE *f, GP_ProgressCallback *callback)
573 struct bitmap_info_header header;
574 GP_PixelType pixel_type;
575 GP_Context *context;
576 int err;
578 if ((err = read_bitmap_header(f, &header)))
579 goto err1;
581 if (header.compress_type != COMPRESS_RGB) {
582 GP_DEBUG(2, "Unknown compression type");
583 err = ENOSYS;
584 goto err1;
587 if ((pixel_type = match_pixel_type(&header)) == GP_PIXEL_UNKNOWN) {
588 GP_DEBUG(2, "Unknown pixel type");
589 err = ENOSYS;
590 goto err1;
593 context = GP_ContextAlloc(header.w, GP_ABS(header.h), pixel_type);
595 if (context == NULL) {
596 err = ENOMEM;
597 goto err1;
600 if ((err = read_bitmap_pixels(f, &header, context, callback)))
601 goto err2;
603 return context;
604 err2:
605 GP_ContextFree(context);
606 err1:
607 fclose(f);
608 errno = err;
609 return NULL;
612 GP_Context *GP_LoadBMP(const char *src_path, GP_ProgressCallback *callback)
614 FILE *f;
616 if (GP_OpenBMP(src_path, &f, NULL, NULL, NULL))
617 return NULL;
619 return GP_ReadBMP(f, callback);