From bdcabdedf3e5f575a5b7a3532f119ec1a08d75aa Mon Sep 17 00:00:00 2001 From: Gaspar Capello Date: Tue, 30 Aug 2022 13:34:32 -0300 Subject: [PATCH] Add extended support to BMP files (fix #3277, fix #1495) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this addition, Aseprite supported following BMP formats: - Size = 12 (OS/2 BMP 1.0) - Size = 40 (Windows BMP v3) Now extended support has been added to the BMP extension: - Load BMP files with BMP header size: - Size = 12 (OS/2 BMP 1.0) - Size = 16 (OS/2 BMP 2.0 - size 16) - Size = 24 (OS/2 BMP 2.0 - size 24) - Size = 40 (Windows BMP v3) - Size = 52 (BITMAPV2INFOHEADER) - Size = 56 (BITMAPV3INFOHEADER) - Size = 60 (OS/2 BMP 2.0 - size 60) - Size = 64 (OS/2 BMP 2.0 - size 64) - Size = 108 (BITMAPV4INFOHEADER) at the moment colorimetry isn’t supported. - Size = 124 (BITMAPV5INFOHEADER) at the moment ICC profiles not supported. - Load BMP with alpha channel. - Load BMP 2bit color depth. - Export BMP files with alpha channel (not palette based). --- src/app/file/bmp_format.cpp | 620 ++++++++++++++++++++++++++++++------ src/doc/color_scales.h | 14 +- 2 files changed, 544 insertions(+), 90 deletions(-) diff --git a/src/app/file/bmp_format.cpp b/src/app/file/bmp_format.cpp index b2e34c43e..b2bf636e9 100644 --- a/src/app/file/bmp_format.cpp +++ b/src/app/file/bmp_format.cpp @@ -6,6 +6,11 @@ // the End-User License Agreement for Aseprite. // // bmp.c - Based on the code of Seymour Shlien and Jonas Petersen. +// +// Info about BMP format: +// https://en.wikipedia.org/wiki/BMP_file_format +// http://justsolve.archiveteam.org/wiki/BMP +// https://docs.microsoft.com/en-us/windows/win32/gdi/bitmap-header-types #ifdef HAVE_CONFIG_H #include "config.h" @@ -46,6 +51,7 @@ class BmpFormat : public FileFormat { uint32_t red_mask; // Mask for red channel. uint32_t green_mask; // Mask for green channel. uint32_t blue_mask; // Mask for blue channel. + uint32_t alpha_mask; // Mask for alpha channel. }; const char* onGetName() const override { @@ -65,6 +71,7 @@ class BmpFormat : public FileFormat { FILE_SUPPORT_LOAD | FILE_SUPPORT_SAVE | FILE_SUPPORT_RGB | + FILE_SUPPORT_RGBA | FILE_SUPPORT_GRAY | FILE_SUPPORT_INDEXED | FILE_SUPPORT_SEQUENCES | @@ -82,34 +89,63 @@ FileFormat* CreateBmpFormat() return new BmpFormat; } -#define BI_RGB 0 -#define BI_RLE8 1 -#define BI_RLE4 2 -#define BI_BITFIELDS 3 +#define BI_RGB 0 +#define BI_RLE8 1 +#define BI_RLE4 2 +#define BI_BITFIELDS 3 +#define BI_ALPHABITFIELDS 6 -#define OS2INFOHEADERSIZE 12 -#define WININFOHEADERSIZE 40 +#define OS2FILEHEADERSIZE 14 -typedef struct BITMAPFILEHEADER +#define OS2INFOHEADERSIZE 12 +#define OS22INFOHEADERSIZE16 16 +#define OS22INFOHEADERSIZE24 24 +#define WININFOHEADERSIZE 40 +#define BV2INFOHEADERSIZE 52 +#define BV3INFOHEADERSIZE 56 +#define OS22INFOHEADERSIZE60 60 +#define OS22INFOHEADERSIZE64 64 +#define BV4INFOHEADERSIZE 108 +#define BV5INFOHEADERSIZE 124 + +struct BITMAPFILEHEADER { uint32_t bfType; uint32_t bfSize; uint16_t bfReserved1; uint16_t bfReserved2; uint32_t bfOffBits; -} BITMAPFILEHEADER; +}; -// Used for both OS/2 and Windows BMP. +// Used for all Info Header Sizes. // Contains only the parameters needed to load the image. -typedef struct BITMAPINFOHEADER +struct BITMAPINFOHEADER { + uint32_t biSize; uint32_t biWidth; uint32_t biHeight; uint16_t biBitCount; uint32_t biCompression; -} BITMAPINFOHEADER; + uint32_t biClrUsed; + uint32_t rMask; + uint32_t gMask; + uint32_t bMask; + uint32_t aMask; -typedef struct WINBMPINFOHEADER // Size: 40. + bool isRGBMasks() const + { + return biSize >= BV2INFOHEADERSIZE || + biCompression == BI_BITFIELDS || + biCompression == BI_ALPHABITFIELDS; + }; + bool isAlphaMask() const + { + return biSize >= BV3INFOHEADERSIZE || + biCompression == BI_ALPHABITFIELDS; + }; +}; + +struct WINBMPINFOHEADER // Size: 16 to 64 { uint32_t biWidth; uint32_t biHeight; @@ -121,15 +157,92 @@ typedef struct WINBMPINFOHEADER // Size: 40. uint32_t biYPelsPerMeter; uint32_t biClrUsed; uint32_t biClrImportant; -} WINBMPINFOHEADER; -typedef struct OS2BMPINFOHEADER // Size: 12. + uint32_t redMask; + uint32_t greenMask; + uint32_t blueMask; + uint32_t alphaMask; +}; + +struct OS2BMPINFOHEADER // Size: 12. { uint16_t biWidth; uint16_t biHeight; uint16_t biPlanes; uint16_t biBitCount; -} OS2BMPINFOHEADER; +}; + +// TO DO: support ICC profiles and colorimetry +struct CIEXYZ +{ + uint32_t ciexyzX; // Fix Point: 2 bits integer part, 30 bits to fractional part + uint32_t ciexyzY; + uint32_t ciexyzZ; +}; + +struct CIEXYZTRIPLE +{ + CIEXYZ ciexyzRed; + CIEXYZ ciexyzGreen; + CIEXYZ ciexyzBlue; +}; + +struct BMPV4HEADER // Size: 108. +{ + uint32_t bV4Width; + uint32_t bV4Height; + uint16_t bV4Planes; + uint16_t bV4BitCount; + uint32_t bV4Compression; + uint32_t bV4SizeImage; + uint32_t bV4XPelsPerMeter; + uint32_t bV4YPelsPerMeter; + uint32_t bV4ClrUsed; + uint32_t bV4ClrImportant; + + uint32_t bV4RedMask; + uint32_t bV4GreenMask; + uint32_t bV4BlueMask; + uint32_t bV4AlphaMask; +// TO DO: support ICC profiles and colorimetry + uint32_t bV4CSType; + CIEXYZTRIPLE bV4Endpoints; + uint32_t bV4GammaRed; + uint32_t bV4GammaGreen; + uint32_t bV4GammaBlue; +}; + +struct BMPV5HEADER // Size: 124. +{ + uint32_t bV5Width; + uint32_t bV5Height; + uint16_t bV5Planes; + uint16_t bV5BitCount; + uint32_t bV5Compression; + + uint32_t bV5SizeImage; + uint32_t bV5XPelsPerMeter; + uint32_t bV5YPelsPerMeter; + uint32_t bV5ClrUsed; + uint32_t bV5ClrImportant; + + uint32_t bV5RedMask; + uint32_t bV5GreenMask; + uint32_t bV5BlueMask; + uint32_t bV5AlphaMask; +// TO DO: support ICC profiles and colorimetry + uint32_t bV5CSType; + CIEXYZTRIPLE bV5Endpoints; + + uint32_t bV5GammaRed; + uint32_t bV5GammaGreen; + uint32_t bV5GammaBlue; + + uint32_t bV5Intent; + uint32_t bV5ProfileData; + uint32_t bV5ProfileSize; + uint32_t bV5Reserved; +}; /* read_bmfileheader: * Reads a BMP file header and check that it has the BMP magic number. @@ -155,21 +268,68 @@ static int read_win_bminfoheader(FILE *f, BITMAPINFOHEADER *infoheader) { WINBMPINFOHEADER win_infoheader; + int biSize = infoheader->biSize; + + if (biSize != OS22INFOHEADERSIZE16 && + biSize != OS22INFOHEADERSIZE24 && + biSize != WININFOHEADERSIZE && + biSize != BV2INFOHEADERSIZE && + biSize != BV3INFOHEADERSIZE && + biSize != OS22INFOHEADERSIZE60 && + biSize != OS22INFOHEADERSIZE64) + return -1; + win_infoheader.biWidth = fgetl(f); win_infoheader.biHeight = fgetl(f); win_infoheader.biPlanes = fgetw(f); - win_infoheader.biBitCount = fgetw(f); - win_infoheader.biCompression = fgetl(f); - win_infoheader.biSizeImage = fgetl(f); - win_infoheader.biXPelsPerMeter = fgetl(f); - win_infoheader.biYPelsPerMeter = fgetl(f); - win_infoheader.biClrUsed = fgetl(f); - win_infoheader.biClrImportant = fgetl(f); + win_infoheader.biBitCount = fgetw(f); // = 16 bytes + + win_infoheader.redMask = 0; + win_infoheader.greenMask = 0; + win_infoheader.blueMask = 0; + win_infoheader.alphaMask = 0; + if (biSize == OS22INFOHEADERSIZE16) + win_infoheader.biCompression = BI_RGB; // = 16 bytes + else { + ASSERT(biSize >= OS22INFOHEADERSIZE24); + win_infoheader.biCompression = fgetl(f); + win_infoheader.biSizeImage = fgetl(f); // = 24 bytes + + if (biSize >= WININFOHEADERSIZE) { + win_infoheader.biXPelsPerMeter = fgetl(f); + win_infoheader.biYPelsPerMeter = fgetl(f); + win_infoheader.biClrUsed = fgetl(f); + win_infoheader.biClrImportant = fgetl(f); // = 40 bytes (WININFOHEADERSIZE) + + // 'biCompression' is needed to execute + // infoheader->isRGBMasks() and infoheader->isAlphaMask() + infoheader->biCompression = win_infoheader.biCompression; + + if (infoheader->isRGBMasks()) { + win_infoheader.redMask = fgetl(f); + win_infoheader.greenMask = fgetl(f); + win_infoheader.blueMask = fgetl(f); // = 52 bytes (BV2INFOHEADERSIZE) + if (infoheader->isAlphaMask()) { + win_infoheader.alphaMask = fgetl(f); // = 56 bytes (BV3INFOHEADERSIZE) + if (biSize >= OS22INFOHEADERSIZE60) { + fgetl(f); // <--discarded // = 60 bytes + if (biSize == OS22INFOHEADERSIZE64) + fgetl(f); // <--discarded // = 64 bytes + } + } + } + } + } infoheader->biWidth = win_infoheader.biWidth; infoheader->biHeight = win_infoheader.biHeight; infoheader->biBitCount = win_infoheader.biBitCount; infoheader->biCompression = win_infoheader.biCompression; + infoheader->biClrUsed = win_infoheader.biClrUsed; + infoheader->rMask = win_infoheader.redMask; + infoheader->gMask = win_infoheader.greenMask; + infoheader->bMask = win_infoheader.blueMask; + infoheader->aMask = win_infoheader.alphaMask; return 0; } @@ -190,6 +350,130 @@ static int read_os2_bminfoheader(FILE *f, BITMAPINFOHEADER *infoheader) infoheader->biHeight = os2_infoheader.biHeight; infoheader->biBitCount = os2_infoheader.biBitCount; infoheader->biCompression = 0; + infoheader->biClrUsed = -1; // Not defined in this format + + return 0; +} + +/* read_v4_bminfoheader: + * Reads information from an V4 format BMP file header. + */ +static int read_v4_bminfoheader(FILE *f, BITMAPINFOHEADER *infoheader) +{ + BMPV4HEADER v4_infoheader; + + v4_infoheader.bV4Width = fgetl(f); + v4_infoheader.bV4Height = fgetl(f); + v4_infoheader.bV4Planes = fgetw(f); + v4_infoheader.bV4BitCount = fgetw(f); + v4_infoheader.bV4Compression = fgetl(f); + + v4_infoheader.bV4SizeImage = fgetl(f); + v4_infoheader.bV4XPelsPerMeter = fgetl(f); + v4_infoheader.bV4YPelsPerMeter = fgetl(f); + v4_infoheader.bV4ClrUsed = fgetl(f); + v4_infoheader.bV4ClrImportant = fgetl(f); + + v4_infoheader.bV4RedMask = fgetl(f); + v4_infoheader.bV4GreenMask = fgetl(f); + v4_infoheader.bV4BlueMask = fgetl(f); + v4_infoheader.bV4AlphaMask = fgetl(f); + + // TO DO: support ICC profiles and colorimetry + v4_infoheader.bV4CSType = fgetl(f); + + // CIEXYZTRIPLE { + v4_infoheader.bV4Endpoints.ciexyzRed.ciexyzX = fgetl(f); + v4_infoheader.bV4Endpoints.ciexyzRed.ciexyzY = fgetl(f); + v4_infoheader.bV4Endpoints.ciexyzRed.ciexyzZ = fgetl(f); + + v4_infoheader.bV4Endpoints.ciexyzGreen.ciexyzX = fgetl(f); + v4_infoheader.bV4Endpoints.ciexyzGreen.ciexyzY = fgetl(f); + v4_infoheader.bV4Endpoints.ciexyzGreen.ciexyzZ = fgetl(f); + + v4_infoheader.bV4Endpoints.ciexyzBlue.ciexyzX = fgetl(f); + v4_infoheader.bV4Endpoints.ciexyzBlue.ciexyzY = fgetl(f); + v4_infoheader.bV4Endpoints.ciexyzBlue.ciexyzZ = fgetl(f); + // } CIEXYZTRIPLE + + v4_infoheader.bV4GammaRed = fgetl(f); + v4_infoheader.bV4GammaGreen = fgetl(f); + v4_infoheader.bV4GammaBlue = fgetl(f); + + infoheader->biWidth = v4_infoheader.bV4Width; + infoheader->biHeight = v4_infoheader.bV4Height; + infoheader->biBitCount = v4_infoheader.bV4BitCount; + infoheader->biCompression = v4_infoheader.bV4Compression; + infoheader->biClrUsed = v4_infoheader.bV4ClrUsed; + + infoheader->rMask = v4_infoheader.bV4RedMask; + infoheader->gMask = v4_infoheader.bV4GreenMask; + infoheader->bMask = v4_infoheader.bV4BlueMask; + infoheader->aMask = v4_infoheader.bV4AlphaMask; + + return 0; +} + +/* read_v5_bminfoheader: + * Reads information from an V5 format BMP file header. + */ +static int read_v5_bminfoheader(FILE *f, BITMAPINFOHEADER *infoheader) +{ + BMPV5HEADER v5_infoheader; + + v5_infoheader.bV5Width = fgetl(f); + v5_infoheader.bV5Height = fgetl(f); + v5_infoheader.bV5Planes = fgetw(f); + v5_infoheader.bV5BitCount = fgetw(f); + v5_infoheader.bV5Compression = fgetl(f); + + v5_infoheader.bV5SizeImage = fgetl(f); + v5_infoheader.bV5XPelsPerMeter = fgetl(f); + v5_infoheader.bV5YPelsPerMeter = fgetl(f); + v5_infoheader.bV5ClrUsed = fgetl(f); + v5_infoheader.bV5ClrImportant = fgetl(f); + + v5_infoheader.bV5RedMask = fgetl(f); + v5_infoheader.bV5GreenMask = fgetl(f); + v5_infoheader.bV5BlueMask = fgetl(f); + v5_infoheader.bV5AlphaMask = fgetl(f); + + // TO DO: support ICC profiles and colorimetry + v5_infoheader.bV5CSType = fgetl(f); + + // CIEXYZTRIPLE { + v5_infoheader.bV5Endpoints.ciexyzRed.ciexyzX = fgetl(f); + v5_infoheader.bV5Endpoints.ciexyzRed.ciexyzY = fgetl(f); + v5_infoheader.bV5Endpoints.ciexyzRed.ciexyzZ = fgetl(f); + + v5_infoheader.bV5Endpoints.ciexyzGreen.ciexyzX = fgetl(f); + v5_infoheader.bV5Endpoints.ciexyzGreen.ciexyzY = fgetl(f); + v5_infoheader.bV5Endpoints.ciexyzGreen.ciexyzZ = fgetl(f); + + v5_infoheader.bV5Endpoints.ciexyzBlue.ciexyzX = fgetl(f); + v5_infoheader.bV5Endpoints.ciexyzBlue.ciexyzY = fgetl(f); + v5_infoheader.bV5Endpoints.ciexyzBlue.ciexyzZ = fgetl(f); + // } CIEXYZTRIPLE + + v5_infoheader.bV5GammaRed = fgetl(f); + v5_infoheader.bV5GammaGreen = fgetl(f); + v5_infoheader.bV5GammaBlue = fgetl(f); + + v5_infoheader.bV5Intent = fgetl(f); + v5_infoheader.bV5ProfileData = fgetl(f); + v5_infoheader.bV5ProfileSize = fgetl(f); + fgetl(f); // <-- Reserved DWORD + + infoheader->biWidth = v5_infoheader.bV5Width; + infoheader->biHeight = v5_infoheader.bV5Height; + infoheader->biBitCount = v5_infoheader.bV5BitCount; + infoheader->biCompression = v5_infoheader.bV5Compression; + infoheader->biClrUsed = v5_infoheader.bV5ClrUsed; + + infoheader->rMask = v5_infoheader.bV5RedMask; + infoheader->gMask = v5_infoheader.bV5GreenMask; + infoheader->bMask = v5_infoheader.bV5BlueMask; + infoheader->aMask = v5_infoheader.bV5AlphaMask; return 0; } @@ -253,6 +537,38 @@ static void read_1bit_line(int length, FILE *f, Image *image, int line) } } +/* read_2bit_line (not standard): + * Support function for reading the 2 bit bitmap file format. + */ +static void read_2bit_line(int length, FILE *f, Image *image, int line) +{ + unsigned char b[16]; + unsigned long n; + int i, j, k; + int temp; + int pix; + + for (i=0; i> 2; + b[k*4+2] = temp & 3; + temp = temp >> 2; + b[k*4+1] = temp & 3; + temp = temp >> 2; + b[k*4] = temp & 3; + n = n >> 8; + } + } + pix = b[j]; + put_pixel(image, i, line, pix); + } +} + /* read_4bit_line: * Support function for reading the 4 bit bitmap file format. */ @@ -305,9 +621,9 @@ static void read_8bit_line(int length, FILE *f, Image *image, int line) } } -static void read_16bit_line(int length, FILE *f, Image *image, int line) +static void read_16bit_line(int length, FILE *f, Image *image, int line, bool& withAlpha) { - int i, r, g, b, word; + int i, r, g, b, a, word; for (i=0; i> 10) & 0x1f; g = (word >> 5) & 0x1f; b = (word) & 0x1f; - + a = (word & 0x8000 ? 255 : 0); + if (a) + withAlpha = true; put_pixel(image, i, line, rgba(scale_5bits_to_8bits(r), scale_5bits_to_8bits(g), - scale_5bits_to_8bits(b), 255)); + scale_5bits_to_8bits(b), a)); } i = (2*i) % 4; @@ -345,23 +663,26 @@ static void read_24bit_line(int length, FILE *f, Image *image, int line) fgetc(f); } -static void read_32bit_line(int length, FILE *f, Image *image, int line) +static void read_32bit_line(int length, FILE *f, Image *image, int line, + bool& withAlpha) { - int i, r, g, b; + int i, r, g, b, a; for (i=0; ibiBitCount) { case 1: read_1bit_line(infoheader->biWidth, f, image, line); break; + case 2: read_2bit_line(infoheader->biWidth, f, image, line); break; case 4: read_4bit_line(infoheader->biWidth, f, image, line); break; case 8: read_8bit_line(infoheader->biWidth, f, image, line); break; - case 16: read_16bit_line(infoheader->biWidth, f, image, line); break; + case 16: read_16bit_line(infoheader->biWidth, f, image, line, withAlpha); break; case 24: read_24bit_line(infoheader->biWidth, f, image, line); break; - case 32: read_32bit_line(infoheader->biWidth, f, image, line); break; + case 32: read_32bit_line(infoheader->biWidth, f, image, line, withAlpha); break; } fop->setProgress((float)(i+1) / (float)(height)); if (fop->isStop()) break; } + + if ((infoheader->biBitCount == 32 || + infoheader->biBitCount == 16 ) && !withAlpha) { + LockImageBits imageBits(image, image->bounds()); + auto imgIt = imageBits.begin(), imgEnd = imageBits.end(); + for (; imgIt != imgEnd; ++imgIt) + *imgIt |= 0xff000000; + } } /* read_rle8_compressed_image: @@ -545,27 +875,33 @@ static void read_rle4_compressed_image(FILE *f, Image *image, const BITMAPINFOHE } static int read_bitfields_image(FILE *f, Image *image, BITMAPINFOHEADER *infoheader, - unsigned long rmask, unsigned long gmask, unsigned long bmask) + uint32_t rmask, uint32_t gmask, uint32_t bmask, + uint32_t amask, bool& withAlpha) { -#define CALC_SHIFT(c) \ - mask = ~c##mask; \ - c##shift = 0; \ - while (mask & 1) { \ - ++c##shift; \ - mask >>= 1; \ - } \ - if ((c##mask >> c##shift) == 0x1f) \ - c##scale = scale_5bits_to_8bits; \ - else if ((c##mask >> c##shift) == 0x3f) \ - c##scale = scale_6bits_to_8bits; \ - else \ - c##scale = NULL; +#define CALC_SHIFT(c) \ + c##shift = 0; \ + if (c##mask) { \ + mask = ~c##mask; \ + while (mask & 1) { \ + ++c##shift; \ + mask >>= 1; \ + } \ + if (mask) { \ + mask = ~mask; \ + while (mask & 1) { \ + c##bits++; \ + mask >>= 1; \ + } \ + } \ + else \ + c##bits = 32 - c##shift; \ + } \ + else \ + c##bits = 8; \ - unsigned long buffer, mask, rshift, gshift, bshift; - int i, j, k, line, height, dir, r, g, b; - int (*rscale)(int); - int (*gscale)(int); - int (*bscale)(int); + uint32_t buffer, mask, rshift, gshift, bshift, ashift; + int rbits = 0, gbits = 0, bbits = 0, abits = 0; + int i, j, k, line, height, dir, r, g, b, a; int bits_per_pixel; int bytes_per_pixel; @@ -578,6 +914,7 @@ static int read_bitfields_image(FILE *f, Image *image, BITMAPINFOHEADER *infohea CALC_SHIFT(r); CALC_SHIFT(g); CALC_SHIFT(b); + CALC_SHIFT(a); /* calculate bits-per-pixel and bytes-per-pixel */ bits_per_pixel = infoheader->biBitCount; @@ -594,12 +931,16 @@ static int read_bitfields_image(FILE *f, Image *image, BITMAPINFOHEADER *infohea r = (buffer & rmask) >> rshift; g = (buffer & gmask) >> gshift; b = (buffer & bmask) >> bshift; + a = (buffer & amask) >> ashift; - r = rscale ? rscale(r): r; - g = gscale ? gscale(g): g; - b = bscale ? bscale(b): b; + r = (rbits == 8 ? r : scale_xxbits_to_8bits(rbits, r) ); + g = (gbits == 8 ? g : scale_xxbits_to_8bits(gbits, g) ); + b = (bbits == 8 ? b : scale_xxbits_to_8bits(bbits, b) ); + a = (abits == 8 ? a : scale_xxbits_to_8bits(abits, a) ); - put_pixel_fast(image, j, line, rgba(r, g, b, 255)); + if (a) + withAlpha = true; + put_pixel_fast(image, j, line, rgba(r, g, b, a)); } j = (bytes_per_pixel*j) % 4; @@ -608,12 +949,19 @@ static int read_bitfields_image(FILE *f, Image *image, BITMAPINFOHEADER *infohea fgetc(f); } + if (!withAlpha) { + LockImageBits imageBits(image, image->bounds()); + auto imgIt = imageBits.begin(), imgEnd = imageBits.end(); + for (; imgIt != imgEnd; ++imgIt) + *imgIt |= 0xff000000; + } + return 0; } bool BmpFormat::onLoad(FileOp *fop) { - unsigned long rmask, gmask, bmask; + uint32_t rmask, gmask, bmask, amask; BITMAPFILEHEADER fileheader; BITMAPINFOHEADER infoheader; unsigned long biSize; @@ -626,31 +974,79 @@ bool BmpFormat::onLoad(FileOp *fop) if (read_bmfileheader(f, &fileheader) != 0) return false; - biSize = fgetl(f); + infoheader.biSize = fgetl(f); - if (biSize == WININFOHEADERSIZE) { + if (infoheader.biSize >= 16 && infoheader.biSize <= 64) { format = BMP_OPTIONS_FORMAT_WINDOWS; if (read_win_bminfoheader(f, &infoheader) != 0) { return false; } - if (infoheader.biCompression != BI_BITFIELDS) - read_bmicolors(fop, fileheader.bfOffBits - 54, f, true); + if (infoheader.biCompression != BI_BITFIELDS && + infoheader.biCompression != BI_ALPHABITFIELDS) + read_bmicolors(fop, + fileheader.bfOffBits - infoheader.biSize - OS2FILEHEADERSIZE, + f, true); + else if (infoheader.biBitCount <= 8) + return false; } - else if (biSize == OS2INFOHEADERSIZE) { + else if (infoheader.biSize == OS2INFOHEADERSIZE) { format = BMP_OPTIONS_FORMAT_OS2; if (read_os2_bminfoheader(f, &infoheader) != 0) { return false; } /* compute number of colors recorded */ - if (infoheader.biCompression != BI_BITFIELDS) - read_bmicolors(fop, fileheader.bfOffBits - 26, f, false); + if (infoheader.biCompression != BI_BITFIELDS && + infoheader.biCompression != BI_ALPHABITFIELDS) + read_bmicolors(fop, + fileheader.bfOffBits - infoheader.biSize - OS2FILEHEADERSIZE, + f, false); + else if (infoheader.biBitCount <= 8) + return false; + } + else if (infoheader.biSize == BV4INFOHEADERSIZE) { + format = BMP_OPTIONS_FORMAT_WINDOWS; + + if (read_v4_bminfoheader(f, &infoheader) != 0) { + return false; + } + /* compute number of colors recorded */ + if (infoheader.biCompression != BI_BITFIELDS && + infoheader.biCompression != BI_ALPHABITFIELDS) + read_bmicolors(fop, + fileheader.bfOffBits - infoheader.biSize - OS2FILEHEADERSIZE, + f, true); + else if (infoheader.biBitCount <= 8) + return false; + } + else if (infoheader.biSize == BV5INFOHEADERSIZE) { + format = BMP_OPTIONS_FORMAT_WINDOWS; + + if (read_v5_bminfoheader(f, &infoheader) != 0) { + return false; + } + /* compute number of colors recorded */ + if (infoheader.biCompression != BI_BITFIELDS && + infoheader.biCompression != BI_ALPHABITFIELDS) + read_bmicolors(fop, + fileheader.bfOffBits - infoheader.biSize - OS2FILEHEADERSIZE, + f, true); + else if (infoheader.biBitCount <= 8) + return false; } else { return false; } + // Check compatible Compression + if (infoheader.biCompression == 4 || + infoheader.biCompression == 5 || + infoheader.biCompression > 6) { + fop->setError("Unsupported BMP compression.\n"); + return false; + } + // Check image size is valid { if (int(infoheader.biWidth) < 1 || @@ -681,13 +1077,14 @@ bool BmpFormat::onLoad(FileOp *fop) pixelFormat = IMAGE_INDEXED; /* bitfields have the 'mask' for each component */ - if (infoheader.biCompression == BI_BITFIELDS) { - rmask = fgetl(f); - gmask = fgetl(f); - bmask = fgetl(f); + if (infoheader.isRGBMasks()) { + rmask = infoheader.rMask; + gmask = infoheader.gMask; + bmask = infoheader.bMask; + amask = (infoheader.isAlphaMask() ? infoheader.aMask : 0); } else - rmask = gmask = bmask = 0; + rmask = gmask = bmask = amask = 0; ImageRef image = fop->sequenceImage(pixelFormat, infoheader.biWidth, @@ -697,14 +1094,21 @@ bool BmpFormat::onLoad(FileOp *fop) } if (pixelFormat == IMAGE_RGB) - clear_image(image.get(), rgba(0, 0, 0, 255)); + clear_image(image.get(), + rgba(0, 0, 0, (infoheader.isAlphaMask() ? 0 : 255))); else clear_image(image.get(), 0); + // We indirectly calculate 'on the fly' if the BMP file + // has all its pixels with alpha value equal to 0 (i.e. BMP + // without alpha channel) or if there is at least one pixel + // with non 0 alpha (i.e. BMP works with alpha channel). + // The result of this analysis will be stored in the boolean 'withAlpha'. + bool withAlpha = false; switch (infoheader.biCompression) { case BI_RGB: - read_image(f, image.get(), &infoheader, fop); + read_image(f, image.get(), &infoheader, fop, withAlpha); break; case BI_RLE8: @@ -716,7 +1120,9 @@ bool BmpFormat::onLoad(FileOp *fop) break; case BI_BITFIELDS: - if (read_bitfields_image(f, image.get(), &infoheader, rmask, gmask, bmask) < 0) { + case BI_ALPHABITFIELDS: + if (read_bitfields_image(f, image.get(), &infoheader, + rmask, gmask, bmask, amask, withAlpha) < 0) { fop->setError("Unsupported bitfields in the BMP file.\n"); return false; } @@ -742,7 +1148,12 @@ bool BmpFormat::onLoad(FileOp *fop) bmp_options->red_mask = rmask; bmp_options->green_mask = gmask; bmp_options->blue_mask = bmask; - + if (withAlpha) { + bmp_options->alpha_mask = amask; + fop->sequenceSetHasAlpha(true); + } + else + bmp_options->alpha_mask = 0; fop->setLoadedFormatOptions(bmp_options); } @@ -760,9 +1171,14 @@ bool BmpFormat::onSave(FileOp *fop) int biSizeImage; int ncolors = fop->sequenceGetNColors(); int bpp = 0; + bool withAlpha = img->needAlpha(); + switch (spec.colorMode()) { case ColorMode::RGB: - bpp = 24; + if (withAlpha) + bpp = 32; + else + bpp = 24; break; case ColorMode::GRAYSCALE: bpp = 8; @@ -789,13 +1205,18 @@ bool BmpFormat::onSave(FileOp *fop) if (bpp <= 8) { biSizeImage = (w + filler)*bpp/8 * h; - bfSize = (54 // header - + ncolors*4 // palette - + biSizeImage); // image data + bfSize = (WININFOHEADERSIZE + OS2FILEHEADERSIZE // header + + ncolors*4 // palette + + biSizeImage); // image data } else { biSizeImage = (w*3 + filler) * h; - bfSize = 54 + biSizeImage; // header + image data + if (withAlpha) + bfSize = BV3INFOHEADERSIZE + + OS2FILEHEADERSIZE + biSizeImage; // header + image data + else + bfSize = WININFOHEADERSIZE + + OS2FILEHEADERSIZE + biSizeImage; // header + image data } FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb")); @@ -807,25 +1228,38 @@ bool BmpFormat::onSave(FileOp *fop) fputw(0, f); /* bfReserved1 */ fputw(0, f); /* bfReserved2 */ - if (bpp <= 8) /* bfOffBits */ - fputl(54+ncolors*4, f); - else - fputl(54, f); + if (bpp <= 8) { + fputl(WININFOHEADERSIZE + OS2FILEHEADERSIZE + + ncolors * 4, f); /* bfOffBits */ + /* info_header */ + fputl(WININFOHEADERSIZE, f); /* biSize */ + } + else if (withAlpha) { + fputl(BV3INFOHEADERSIZE + OS2FILEHEADERSIZE, f); /* bfOffBits -taking account RBGA masks- */ + /* info_header */ + fputl(BV3INFOHEADERSIZE, f); /* biSize */ + } + else { + fputl(WININFOHEADERSIZE + OS2FILEHEADERSIZE, f); /* bfOffBits */ + /* info_header */ + fputl(WININFOHEADERSIZE, f); /* biSize */ + } - /* info_header */ - fputl(40, f); /* biSize */ fputl(w, f); /* biWidth */ fputl(h, f); /* biHeight */ fputw(1, f); /* biPlanes */ fputw(bpp, f); /* biBitCount */ - fputl(0, f); /* biCompression */ + if (withAlpha) /* biCompression */ + fputl(BI_BITFIELDS, f); + else + fputl(BI_RGB, f); fputl(biSizeImage, f); /* biSizeImage */ fputl(0xB12, f); /* biXPelsPerMeter (0xB12 = 72 dpi) */ fputl(0xB12, f); /* biYPelsPerMeter */ if (bpp <= 8) { - fputl(ncolors, f); /* biClrUsed */ - fputl(ncolors, f); /* biClrImportant */ + fputl(ncolors, f); /* biClrUsed */ + fputl(ncolors, f); /* biClrImportant */ // Save the palette for (i=0; i> 4); } + inline int scale_xxbits_to_8bits(const int xx, const int v) { + switch (xx) { + case 3: + return scale_3bits_to_8bits(v); + case 5: + return scale_5bits_to_8bits(v); + case 6: + return scale_6bits_to_8bits(v); + } + return (int)(255.0 / (double(1<