/* * pngxrbmp.c - libpng external I/O: BMP reader. * Copyright (C) 2003-2016 Cosmin Truta and the Contributing Authors. * * This code was derived from "bmp2png.c" by MIYASAKA Masaru, and * is distributed under the same copyright and warranty terms as libpng. */ #include "pngxtern.h" #include "pngxutil.h" #include #include #define PNGX_INTERNAL #include "pngxpriv.h" /*****************************************************************************/ /* BMP file header macros */ /*****************************************************************************/ /* BMP file signature */ #define BMP_SIGNATURE 0x4d42 /* "BM" */ #define BMP_SIG_BYTES 2 /* BITMAPFILEHEADER */ #define BFH_WTYPE 0 /* WORD bfType; */ #define BFH_DSIZE 2 /* DWORD bfSize; */ #define BFH_WRESERVED1 6 /* WORD bfReserved1; */ #define BFH_WRESERVED2 8 /* WORD bfReserved2; */ #define BFH_DOFFBITS 10 /* DWORD bfOffBits; */ #define BFH_DBIHSIZE 14 /* DWORD biSize; */ #define FILEHED_SIZE 14 /* sizeof(BITMAPFILEHEADER) */ #define BIHSIZE_SIZE 4 /* sizeof(biSize) */ /* BITMAPINFOHEADER */ #define BIH_DSIZE 0 /* DWORD biSize; */ #define BIH_LWIDTH 4 /* LONG biWidth; */ #define BIH_LHEIGHT 8 /* LONG biHeight; */ #define BIH_WPLANES 12 /* WORD biPlanes; */ #define BIH_WBITCOUNT 14 /* WORD biBitCount; */ #define BIH_DCOMPRESSION 16 /* DWORD biCompression; */ #define BIH_DSIZEIMAGE 20 /* DWORD biSizeImage; */ #define BIH_LXPELSPERMETER 24 /* LONG biXPelsPerMeter; */ #define BIH_LYPELSPERMETER 28 /* LONG biYPelsPerMeter; */ #define BIH_DCLRUSED 32 /* DWORD biClrUsed; */ #define BIH_DCLRIMPORTANT 36 /* DWORD biClrImportant; */ #define B4H_DREDMASK 40 /* DWORD bV4RedMask; */ #define B4H_DGREENMASK 44 /* DWORD bV4GreenMask; */ #define B4H_DBLUEMASK 48 /* DWORD bV4BlueMask; */ #define B4H_DALPHAMASK 52 /* DWORD bV4AlphaMask; */ #define B4H_DCSTYPE 56 /* DWORD bV4CSType; */ #define B4H_XENDPOINTS 60 /* CIEXYZTRIPLE bV4Endpoints; */ #define B4H_DGAMMARED 96 /* DWORD bV4GammaRed; */ #define B4H_DGAMMAGREEN 100 /* DWORD bV4GammaGreen; */ #define B4H_DGAMMABLUE 104 /* DWORD bV4GammaBlue; */ #define B5H_DINTENT 108 /* DWORD bV5Intent; */ #define B5H_DPROFILEDATA 112 /* DWORD bV5ProfileData; */ #define B5H_DPROFILESIZE 116 /* DWORD bV5ProfileSize; */ #define B5H_DRESERVED 120 /* DWORD bV5Reserved; */ #define INFOHED_SIZE 40 /* sizeof(BITMAPINFOHEADER) */ #define BMPV4HED_SIZE 108 /* sizeof(BITMAPV4HEADER) */ #define BMPV5HED_SIZE 124 /* sizeof(BITMAPV5HEADER) */ /* BITMAPCOREHEADER */ #define BCH_DSIZE 0 /* DWORD bcSize; */ #define BCH_WWIDTH 4 /* WORD bcWidth; */ #define BCH_WHEIGHT 6 /* WORD bcHeight; */ #define BCH_WPLANES 8 /* WORD bcPlanes; */ #define BCH_WBITCOUNT 10 /* WORD bcBitCount; */ #define COREHED_SIZE 12 /* sizeof(BITMAPCOREHEADER) */ /* RGB */ #define RGB_BLUE 0 /* BYTE rgbBlue; */ #define RGB_GREEN 1 /* BYTE rgbGreen; */ #define RGB_RED 2 /* BYTE rgbRed; */ #define RGB_RESERVED 3 /* BYTE rgbReserved; */ #define RGBTRIPLE_SIZE 3 /* sizeof(RGBTRIPLE) */ #define RGBQUAD_SIZE 4 /* sizeof(RGBQUAD) */ /* Constants for the biCompression field */ #ifndef BI_RGB #define BI_RGB 0 /* Uncompressed format */ #define BI_RLE8 1 /* RLE format (8 bits/pixel) */ #define BI_RLE4 2 /* RLE format (4 bits/pixel) */ #define BI_BITFIELDS 3 /* Bitfield format */ #define BI_JPEG 4 /* JPEG format */ #define BI_PNG 5 /* PNG format */ #endif /*****************************************************************************/ /* BMP memory utilities */ /*****************************************************************************/ static unsigned int bmp_get_word(png_bytep ptr) { return (unsigned int)ptr[0] + ((unsigned int)ptr[1] << 8); } static png_uint_32 bmp_get_dword(png_bytep ptr) { return ((png_uint_32)ptr[0]) + ((png_uint_32)ptr[1] << 8) + ((png_uint_32)ptr[2] << 16) + ((png_uint_32)ptr[3] << 24); } /*****************************************************************************/ /* BMP helpers */ /*****************************************************************************/ static void bmp_memset_bytes(png_bytep ptr, size_t offset, int ch, size_t len) { memset(ptr + offset, ch, len); } static void bmp_memset_halfbytes(png_bytep ptr, size_t offset, int ch, size_t len) { if (len == 0) return; ptr += offset / 2; if (offset & 1) /* use half-byte operations at odd offset */ { *ptr = (png_byte)((*ptr & 0xf0) | (ch & 0x0f)); ch = ((ch & 0xf0) >> 4) | ((ch & 0x0f) << 4); ++ptr; --len; } memset(ptr, ch, len / 2); if (len & 1) ptr[len / 2] = (png_byte)(ch & 0xf0); } static size_t bmp_fread_bytes(png_bytep ptr, size_t offset, size_t len, FILE *stream) { size_t result; result = fread(ptr + offset, 1, len, stream); if (len & 1) getc(stream); /* skip padding */ return result; } static size_t bmp_fread_halfbytes(png_bytep ptr, size_t offset, size_t len, FILE *stream) { size_t result; int ch; if (len == 0) return 0; ptr += offset / 2; if (offset & 1) /* use half-byte operations at odd offset */ { for (result = 0; result < len - 1; result += 2) { ch = getc(stream); if (ch == EOF) break; *ptr = (png_byte)((*ptr & 0xf0) | ((ch & 0xf0) >> 4)); ++ptr; *ptr = (png_byte)((ch & 0x0f) << 4); } } else { result = fread(ptr, 1, (len + 1) / 2, stream) * 2; } if (len & 2) getc(stream); /* skip padding */ return (result <= len) ? result : len; } /*****************************************************************************/ /* BMP packbit (BITFIELDS) helpers */ /*****************************************************************************/ static void bmp_process_mask(png_uint_32 bmp_mask, png_bytep sig_bit, png_bytep shift_bit) { *sig_bit = *shift_bit = (png_byte)0; if (bmp_mask == 0) return; while ((bmp_mask & 1) == 0) { bmp_mask >>= 1; ++*shift_bit; } while (bmp_mask != 0) { if ((bmp_mask & 1) == 0 || *sig_bit >= 8) { *sig_bit = (png_byte)0; return; } bmp_mask >>= 1; ++*sig_bit; } } /*****************************************************************************/ /* BMP I/O utilities */ /*****************************************************************************/ static size_t bmp_read_rows(png_bytepp begin_row, png_bytepp end_row, size_t row_size, unsigned int compression, FILE *stream) { size_t result; png_bytepp crt_row; int inc; size_t crtn, dcrtn, endn; unsigned int len, b1, b2; int ch; void (*bmp_memset_fn)(png_bytep, size_t, int, size_t); size_t (*bmp_fread_fn)(png_bytep, size_t, size_t, FILE *); if (row_size == 0) return 0; /* this should not happen */ inc = (begin_row <= end_row) ? 1 : -1; crtn = 0; result = 0; if (compression == BI_RLE4) { endn = row_size * 2; if (endn <= row_size) return 0; /* overflow */ bmp_memset_fn = bmp_memset_halfbytes; bmp_fread_fn = bmp_fread_halfbytes; } else { endn = row_size; bmp_memset_fn = bmp_memset_bytes; bmp_fread_fn = bmp_fread_bytes; } if (compression == BI_RGB || compression == BI_BITFIELDS) { /* Read uncompressed bitmap. */ for (crt_row = begin_row; crt_row != end_row; crt_row += inc) { crtn = bmp_fread_fn(*crt_row, 0, endn, stream); if (crtn != endn) break; ++result; } } else if (compression == BI_RLE8 || compression == BI_RLE4) { /* Read RLE-compressed bitmap. */ if (compression == BI_RLE8) { endn = row_size; } else /* BI_RLE4 */ { endn = row_size * 2; if (endn <= row_size) return 0; /* overflow */ } for (crt_row = begin_row; crt_row != end_row; ) { ch = getc(stream); b1 = (unsigned int)ch; ch = getc(stream); b2 = (unsigned int)ch; if (ch == EOF) break; if (b1 == 0) /* escape */ { if (b2 == 0) /* end of line */ { bmp_memset_fn(*crt_row, crtn, 0, endn - crtn); crt_row += inc; crtn = 0; ++result; if (crt_row == end_row) /* all rows are read */ { ch = getc(stream); /* check for the end of bitmap */ if (ch != EOF && ch != 0) { ungetc(ch, stream); /* forget about the end of bitmap */ break; } getc(stream); /* expect 1, but break the loop anyway */ break; } } else if (b2 == 1) /* end of bitmap */ { bmp_memset_fn(*crt_row, crtn, 0, endn - crtn); crt_row += inc; crtn = 0; result = (begin_row <= end_row) ? (end_row - begin_row) : (begin_row - end_row); break; /* the rest is wiped out at the end */ } else if (b2 == 2) /* delta */ { ch = getc(stream); b1 = (unsigned int)ch; /* horiz. offset */ ch = getc(stream); b2 = (unsigned int)ch; /* vert. offset */ if (ch == EOF) break; dcrtn = (b1 < endn - crtn) ? (crtn + b1) : endn; for ( ; b2 > 0; --b2) { bmp_memset_fn(*crt_row, crtn, 0, endn - crtn); crt_row += inc; crtn = 0; ++result; if (crt_row == end_row) break; } if (crt_row != end_row) bmp_memset_fn(*crt_row, crtn, 0, dcrtn - crtn); } else /* b2 >= 3 bytes in absolute mode */ { len = (b2 <= endn - crtn) ? b2 : (unsigned int)(endn - crtn); if (bmp_fread_fn(*crt_row, crtn, len, stream) != len) break; crtn += len; } } else /* b1 > 0 bytes in run-length encoded mode */ { len = (b1 <= endn - crtn) ? b1 : (unsigned int)(endn - crtn); bmp_memset_fn(*crt_row, crtn, (int)b2, len); crtn += len; } } } else return 0; /* error: compression method not applicable. */ /* Wipe out the portion left unread. */ for ( ; crt_row != end_row; crt_row += inc) { bmp_memset_fn(*crt_row, crtn, 0, endn - crtn); crtn = 0; } return result; } /*****************************************************************************/ /* BMP-to-PNG sample conversion */ /*****************************************************************************/ static void bmp_to_png_rows(png_bytepp row_pointers, png_uint_32 width, png_uint_32 height, unsigned int pixdepth, png_bytep rgba_sig, png_bytep rgba_shift) { png_bytep src_ptr, dest_ptr; unsigned int rgba_mask[4]; unsigned int num_samples, sample, mask; unsigned int wpix; png_uint_32 dwpix; png_uint_32 x, y; unsigned int i; if (pixdepth == 24) /* BGR -> RGB */ { for (y = 0; y < height; ++y) { src_ptr = row_pointers[y]; for (x = 0; x < width; ++x, src_ptr += 3) { png_byte tmp = src_ptr[0]; src_ptr[0] = src_ptr[2]; src_ptr[2] = tmp; } } return; } num_samples = (rgba_sig[3] != 0) ? 4 : 3; for (i = 0; i < num_samples; ++i) rgba_mask[i] = (1U << rgba_sig[i]) - 1; if (pixdepth == 16) { for (y = 0; y < height; ++y) { src_ptr = row_pointers[y] + (width - 1) * 2; dest_ptr = row_pointers[y] + (width - 1) * num_samples; for (x = 0; x < width; ++x, src_ptr -= 2, dest_ptr -= num_samples) { /* Inline bmp_get_word() for performance reasons. */ wpix = (unsigned int)src_ptr[0] + ((unsigned int)src_ptr[1] << 8); for (i = 0; i < num_samples; ++i) { mask = rgba_mask[i]; sample = (wpix >> rgba_shift[i]) & mask; dest_ptr[i] = (png_byte)((sample * 255 + mask / 2) / mask); } } } } else if (pixdepth == 32) { for (y = 0; y < height; ++y) { src_ptr = dest_ptr = row_pointers[y]; for (x = 0; x < width; ++x, src_ptr += 4, dest_ptr += num_samples) { /* Inline bmp_get_dword() for performance reasons. */ dwpix = (png_uint_32)src_ptr[0] + ((png_uint_32)src_ptr[1] << 8) + ((png_uint_32)src_ptr[2] << 16) + ((png_uint_32)src_ptr[3] << 24); for (i = 0; i < num_samples; ++i) { mask = rgba_mask[i]; sample = (dwpix >> rgba_shift[i]) & mask; dest_ptr[i] = (png_byte)((sample * 255 + mask / 2) / mask); } } } } /* else do nothing */ } /*****************************************************************************/ /* BMP read support for pngxtern */ /*****************************************************************************/ int /* PRIVATE */ pngx_sig_is_bmp(png_bytep sig, size_t sig_size, png_const_charpp fmt_name_ptr, png_const_charpp fmt_long_name_ptr) { static const char bmp_fmt_name[] = "BMP"; static const char os2bmp_fmt_long_name[] = "OS/2 Bitmap"; static const char winbmp_fmt_long_name[] = "Windows Bitmap"; png_uint_32 bihsize; /* Require at least the bitmap file header and the subsequent 4 bytes. */ if (sig_size < FILEHED_SIZE + 4) return -1; /* insufficient data */ if (bmp_get_word(sig) != BMP_SIGNATURE) return 0; /* not BMP */ /* Avoid using bfhsize because it is not reliable. */ bihsize = bmp_get_dword(sig + FILEHED_SIZE); if ((bihsize > PNG_UINT_31_MAX) || (bihsize != COREHED_SIZE && bihsize < INFOHED_SIZE)) return 0; /* garbage in bihsize, this cannot be BMP */ /* Store the format name. */ if (fmt_name_ptr != NULL) *fmt_name_ptr = bmp_fmt_name; if (fmt_long_name_ptr != NULL) { if (bihsize == COREHED_SIZE) *fmt_long_name_ptr = os2bmp_fmt_long_name; else *fmt_long_name_ptr = winbmp_fmt_long_name; } return 1; /* BMP */ } int /* PRIVATE */ pngx_read_bmp(png_structp png_ptr, png_infop info_ptr, FILE *stream) { png_byte bfh[FILEHED_SIZE + BMPV5HED_SIZE]; png_bytep const bih = bfh + FILEHED_SIZE; png_byte rgbq[RGBQUAD_SIZE]; png_uint_32 offbits, bihsize, skip; png_uint_32 width, height, rowsize; int topdown; unsigned int pixdepth; png_uint_32 compression; unsigned int palsize, palnum; png_uint_32 rgba_mask[4]; png_byte rgba_sig[4], rgba_shift[4]; int bit_depth, color_type; png_color palette[256]; png_color_8 sig_bit; png_bytepp row_pointers, begin_row, end_row; unsigned int i; size_t y; /* Find the BMP header. */ for (i = 0; ; ++i) /* skip macbinary header */ { if (fread(bfh, FILEHED_SIZE + BIHSIZE_SIZE, 1, stream) != 1) ++i; else if (bmp_get_word(bfh + BFH_WTYPE) == BMP_SIGNATURE) break; if (fread(bfh, 128 - FILEHED_SIZE - BIHSIZE_SIZE, 1, stream) != 1) ++i; if (i > 0) return 0; /* not a BMP file */ } /* Read the BMP header. */ offbits = bmp_get_dword(bfh + BFH_DOFFBITS); bihsize = bmp_get_dword(bfh + BFH_DBIHSIZE); if ((offbits > PNG_UINT_31_MAX) || (bihsize > PNG_UINT_31_MAX) || (offbits < bihsize + FILEHED_SIZE) || (bihsize != COREHED_SIZE && bihsize < INFOHED_SIZE)) return 0; /* not a BMP file, just a file with a matching signature */ if (bihsize > BMPV5HED_SIZE) { skip = bihsize - BMPV5HED_SIZE; bihsize = BMPV5HED_SIZE; } else skip = 0; if (fread(bih + BIHSIZE_SIZE, bihsize - BIHSIZE_SIZE, 1, stream) != 1) return 0; if (skip > 0) if (fseek(stream, (long)skip, SEEK_CUR) != 0) return 0; skip = offbits - bihsize - FILEHED_SIZE; /* new skip */ topdown = 0; if (bihsize < INFOHED_SIZE) /* OS/2 BMP */ { width = bmp_get_word(bih + BCH_WWIDTH); height = bmp_get_word(bih + BCH_WHEIGHT); pixdepth = bmp_get_word(bih + BCH_WBITCOUNT); compression = BI_RGB; palsize = RGBTRIPLE_SIZE; } else /* Windows BMP */ { width = bmp_get_dword(bih + BIH_LWIDTH); height = bmp_get_dword(bih + BIH_LHEIGHT); pixdepth = bmp_get_word(bih + BIH_WBITCOUNT); compression = bmp_get_dword(bih + BIH_DCOMPRESSION); palsize = RGBQUAD_SIZE; if (height > PNG_UINT_31_MAX) /* top-down BMP */ { height = PNG_UINT_32_MAX - height + 1; topdown = 1; } if (bihsize == INFOHED_SIZE && compression == BI_BITFIELDS) { /* Read the RGB[A] mask. */ i = (skip <= 16) ? (unsigned int)skip : 16; if (fread(bih + B4H_DREDMASK, i, 1, stream) != 1) return 0; bihsize += i; skip -= i; } } memset(rgba_mask, 0, sizeof(rgba_mask)); if (pixdepth > 8) { if (compression == BI_RGB) { if (pixdepth == 16) { compression = BI_BITFIELDS; rgba_mask[0] = 0x7c00; rgba_mask[1] = 0x03e0; rgba_mask[2] = 0x001f; } else /* pixdepth == 24 || pixdepth == 32 */ { rgba_mask[0] = (png_uint_32)0x00ff0000L; rgba_mask[1] = (png_uint_32)0x0000ff00L; rgba_mask[2] = (png_uint_32)0x000000ffL; } } else if (compression == BI_BITFIELDS) { if (bihsize >= INFOHED_SIZE + 12) { rgba_mask[0] = bmp_get_dword(bih + B4H_DREDMASK); rgba_mask[1] = bmp_get_dword(bih + B4H_DGREENMASK); rgba_mask[2] = bmp_get_dword(bih + B4H_DBLUEMASK); } else png_error(png_ptr, "Missing color mask in BMP file"); } if (bihsize >= INFOHED_SIZE + 16) rgba_mask[3] = bmp_get_dword(bih + B4H_DALPHAMASK); } switch (compression) { case BI_RGB: /* Allow pixel depth values 1, 2, 4, 8, 16, 24, 32. * (Although the BMP spec does not mention pixel depth = 2, * it is supported for robustness reasons, at no extra cost.) */ if (pixdepth > 0 && 32 % pixdepth != 0 && pixdepth != 24) pixdepth = 0; break; case BI_RLE8: if (pixdepth != 8) pixdepth = 0; break; case BI_RLE4: if (pixdepth != 4) pixdepth = 0; break; case BI_BITFIELDS: if (pixdepth != 16 && pixdepth != 32) pixdepth = 0; break; case BI_JPEG: png_error(png_ptr, "JPEG-compressed BMP files are not supported"); /* NOTREACHED */ break; case BI_PNG: if (ungetc(getc(stream), stream) == 0) /* IHDR is likely to follow */ png_set_sig_bytes(png_ptr, 8); png_set_read_fn(png_ptr, stream, NULL); png_read_png(png_ptr, info_ptr, 0, NULL); /* TODO: Check for mismatches between the BMP and PNG info. */ return 1; default: png_error(png_ptr, "Unsupported compression method in BMP file"); } /* Check the BMP image parameters. */ if (width == 0 || width > PNG_UINT_31_MAX || height == 0) png_error(png_ptr, "Invalid image dimensions in BMP file"); if (pixdepth == 0) png_error(png_ptr, "Invalid pixel depth in BMP file"); /* Compute the PNG image parameters. */ if (pixdepth <= 8) { palnum = skip / palsize; if (palnum > 256) palnum = 256; skip -= palsize * palnum; rowsize = (width + (32 / pixdepth) - 1) / (32 / pixdepth) * 4; /* rowsize becomes 0 on overflow. */ bit_depth = pixdepth; color_type = (palnum > 0) ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_GRAY; } else { palnum = 0; bit_depth = 8; switch (pixdepth) { case 16: rowsize = (width * 2 + 3) & (~3); break; case 24: rowsize = (width * 3 + 3) & (~3); break; case 32: rowsize = width * 4; break; default: /* never get here */ bit_depth = 0; rowsize = 0; } if (rowsize / width < pixdepth / 8) rowsize = 0; /* overflow */ color_type = (rgba_mask[3] != 0) ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB; } if (rowsize == 0) png_error(png_ptr, "Can't handle exceedingly large BMP dimensions"); /* Set the PNG image type. */ png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); if (pixdepth > 8) { for (i = 0; i < 4; ++i) bmp_process_mask(rgba_mask[i], &rgba_sig[i], &rgba_shift[i]); if (rgba_sig[0] == 0 || rgba_sig[1] == 0 || rgba_sig[2] == 0) png_error(png_ptr, "Invalid color mask in BMP file"); if (rgba_sig[0] != 8 || rgba_sig[1] != 8 || rgba_sig[2] != 8 || (rgba_sig[3] != 0 && rgba_sig[3] != 8)) { sig_bit.red = rgba_sig[0]; sig_bit.green = rgba_sig[1]; sig_bit.blue = rgba_sig[2]; sig_bit.alpha = rgba_sig[3]; png_set_sBIT(png_ptr, info_ptr, &sig_bit); } } /* Read the color palette (if applicable). */ if (palnum > 0) { for (i = 0; i < palnum; ++i) { if (fread(rgbq, palsize, 1, stream) != 1) break; palette[i].red = rgbq[RGB_RED]; palette[i].green = rgbq[RGB_GREEN]; palette[i].blue = rgbq[RGB_BLUE]; } png_set_PLTE(png_ptr, info_ptr, palette, i); if (i != palnum) png_error(png_ptr, "Error reading color palette in BMP file"); } /* Allocate memory and read the image data. */ row_pointers = pngx_malloc_rows_extended(png_ptr, info_ptr, rowsize, -1); if (topdown) { begin_row = row_pointers; end_row = row_pointers + height; } else { begin_row = row_pointers + height - 1; end_row = row_pointers - 1; } if (skip > 0) fseek(stream, (long)skip, SEEK_CUR); y = bmp_read_rows(begin_row, end_row, rowsize, compression, stream); /* Postprocess the image data, even if it has not been read entirely. */ if (pixdepth > 8) bmp_to_png_rows(row_pointers, width, height, pixdepth, rgba_sig, rgba_shift); /* Check the result. */ if (y != (size_t)height) png_error(png_ptr, "Error reading BMP file"); return 1; /* one image has been successfully read */ }