aseprite/src/app/file/bmp_format.cpp
2022-08-30 14:44:47 -03:00

1352 lines
36 KiB
C++

// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// 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"
#endif
#include "app/file/file.h"
#include "app/file/file_format.h"
#include "app/file/format_options.h"
#include "base/cfile.h"
#include "base/file_handle.h"
#include "doc/doc.h"
#include "fmt/format.h"
namespace app {
// Max supported .bmp size (to filter out invalid image sizes)
const uint32_t kMaxBmpSize = 1024*1024*128; // 128 MB
using namespace base;
class BmpFormat : public FileFormat {
enum {
BMP_OPTIONS_FORMAT_WINDOWS = 12,
BMP_OPTIONS_FORMAT_OS2 = 40,
BMP_OPTIONS_COMPRESSION_RGB = 0,
BMP_OPTIONS_COMPRESSION_RLE8 = 1,
BMP_OPTIONS_COMPRESSION_RLE4 = 2,
BMP_OPTIONS_COMPRESSION_BITFIELDS = 3
};
// Data for BMP files
class BmpOptions : public FormatOptions
{
public:
int format; // bmp format.
int compression; // bmp compression.
int bits_per_pixel; // Bits per pixel.
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 {
return "bmp";
}
void onGetExtensions(base::paths& exts) const override {
exts.push_back("bmp");
}
dio::FileFormat onGetDioFormat() const override {
return dio::FileFormat::BMP_IMAGE;
}
int onGetFlags() const override {
return
FILE_SUPPORT_LOAD |
FILE_SUPPORT_SAVE |
FILE_SUPPORT_RGB |
FILE_SUPPORT_RGBA |
FILE_SUPPORT_GRAY |
FILE_SUPPORT_INDEXED |
FILE_SUPPORT_SEQUENCES |
FILE_ENCODE_ABSTRACT_IMAGE;
}
bool onLoad(FileOp* fop) override;
#ifdef ENABLE_SAVE
bool onSave(FileOp* fop) override;
#endif
};
FileFormat* CreateBmpFormat()
{
return new BmpFormat;
}
#define BI_RGB 0
#define BI_RLE8 1
#define BI_RLE4 2
#define BI_BITFIELDS 3
#define BI_ALPHABITFIELDS 6
#define OS2FILEHEADERSIZE 14
#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;
};
// Used for all Info Header Sizes.
// Contains only the parameters needed to load the image.
struct BITMAPINFOHEADER
{
uint32_t biSize;
uint32_t biWidth;
uint32_t biHeight;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biClrUsed;
uint32_t rMask;
uint32_t gMask;
uint32_t bMask;
uint32_t aMask;
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;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
uint32_t biXPelsPerMeter;
uint32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
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;
};
// 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.
*/
static int read_bmfileheader(FILE *f, BITMAPFILEHEADER *fileheader)
{
fileheader->bfType = fgetw(f);
fileheader->bfSize = fgetl(f);
fileheader->bfReserved1 = fgetw(f);
fileheader->bfReserved2 = fgetw(f);
fileheader->bfOffBits = fgetl(f);
if (fileheader->bfType != 19778)
return -1;
return 0;
}
/* read_win_bminfoheader:
* Reads information from a BMP file header.
*/
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); // = 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;
}
/* read_os2_bminfoheader:
* Reads information from an OS/2 format BMP file header.
*/
static int read_os2_bminfoheader(FILE *f, BITMAPINFOHEADER *infoheader)
{
OS2BMPINFOHEADER os2_infoheader;
os2_infoheader.biWidth = fgetw(f);
os2_infoheader.biHeight = fgetw(f);
os2_infoheader.biPlanes = fgetw(f);
os2_infoheader.biBitCount = fgetw(f);
infoheader->biWidth = os2_infoheader.biWidth;
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;
}
/* read_bmicolors:
* Loads the color palette for 1,4,8 bit formats.
*/
static void read_bmicolors(FileOp* fop, int bytes, FILE *f, bool win_flag)
{
int i, j, r, g, b;
for (i=j=0; i+3 <= bytes && j < 256; ) {
b = fgetc(f);
g = fgetc(f);
r = fgetc(f);
fop->sequenceSetColor(j, r, g, b);
j++;
i += 3;
if (win_flag && i < bytes) {
fgetc(f);
i++;
}
}
// Set the number of colors in the palette
fop->sequenceSetNColors(j);
for (; i<bytes; i++)
fgetc(f);
}
/* read_1bit_line:
* Support function for reading the 1 bit bitmap file format.
*/
static void read_1bit_line(int length, FILE *f, Image *image, int line)
{
unsigned char b[32];
unsigned long n;
int i, j, k;
int pix;
for (i=0; i<length; i++) {
j = i % 32;
if (j == 0) {
n = fgetl(f);
n =
((n&0x000000ff)<<24) |
((n&0x0000ff00)<< 8) |
((n&0x00ff0000)>> 8) |
((n&0xff000000)>>24);
for (k=0; k<32; k++) {
b[31-k] = (char)(n & 1);
n = n >> 1;
}
}
pix = b[j];
put_pixel(image, i, line, pix);
}
}
/* 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<length; i++) {
j = i % 16;
if (j == 0) {
n = fgetl(f);
for (k=0; k<4; k++) {
temp = n & 255;
b[k*4+3] = temp & 3;
temp = temp >> 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.
*/
static void read_4bit_line(int length, FILE *f, Image *image, int line)
{
unsigned char b[8];
unsigned long n;
int i, j, k;
int temp;
int pix;
for (i=0; i<length; i++) {
j = i % 8;
if (j == 0) {
n = fgetl(f);
for (k=0; k<4; k++) {
temp = n & 255;
b[k*2+1] = temp & 15;
temp = temp >> 4;
b[k*2] = temp & 15;
n = n >> 8;
}
}
pix = b[j];
put_pixel(image, i, line, pix);
}
}
/* read_8bit_line:
* Support function for reading the 8 bit bitmap file format.
*/
static void read_8bit_line(int length, FILE *f, Image *image, int line)
{
unsigned char b[4];
unsigned long n;
int i, j, k;
int pix;
for (i=0; i<length; i++) {
j = i % 4;
if (j == 0) {
n = fgetl(f);
for (k=0; k<4; k++) {
b[k] = (char)(n & 255);
n = n >> 8;
}
}
pix = b[j];
put_pixel(image, i, line, pix);
}
}
static void read_16bit_line(int length, FILE *f, Image *image, int line, bool& withAlpha)
{
int i, r, g, b, a, word;
for (i=0; i<length; i++) {
word = fgetw(f);
r = (word >> 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), a));
}
i = (2*i) % 4;
if (i > 0)
while (i++ < 4)
fgetc(f);
}
static void read_24bit_line(int length, FILE *f, Image *image, int line)
{
int i, r, g, b;
for (i=0; i<length; i++) {
b = fgetc(f);
g = fgetc(f);
r = fgetc(f);
put_pixel(image, i, line, rgba(r, g, b, 255));
}
i = (3*i) % 4;
if (i > 0)
while (i++ < 4)
fgetc(f);
}
static void read_32bit_line(int length, FILE *f, Image *image, int line,
bool& withAlpha)
{
int i, r, g, b, a;
for (i=0; i<length; i++) {
b = fgetc(f);
g = fgetc(f);
r = fgetc(f);
a = fgetc(f);
if (a)
withAlpha = true;
put_pixel(image, i, line, rgba(r, g, b, a));
}
}
/* read_image:
* For reading the noncompressed BMP image format.
*/
static void read_image(FILE *f, Image *image, const BITMAPINFOHEADER *infoheader, FileOp *fop, bool& withAlpha)
{
int i, line, height, dir;
height = (int)infoheader->biHeight;
line = height < 0 ? 0: height-1;
dir = height < 0 ? 1: -1;
height = ABS(height);
for (i=0; i<height; i++, line+=dir) {
switch (infoheader->biBitCount) {
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, withAlpha); break;
case 24: read_24bit_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<RgbTraits> imageBits(image, image->bounds());
auto imgIt = imageBits.begin(), imgEnd = imageBits.end();
for (; imgIt != imgEnd; ++imgIt)
*imgIt |= 0xff000000;
}
}
/* read_rle8_compressed_image:
* For reading the 8 bit RLE compressed BMP image format.
*
* @note This support compressed top-down bitmaps, the MSDN says that
* they can't exist, but Photoshop can create them.
*/
static void read_rle8_compressed_image(FILE *f, Image *image, const BITMAPINFOHEADER *infoheader)
{
unsigned char count, val, val0;
int j, pos, line, height, dir;
int eolflag, eopicflag;
eopicflag = 0;
height = (int)infoheader->biHeight;
line = height < 0 ? 0: height-1;
dir = height < 0 ? 1: -1;
height = ABS(height);
while (eopicflag == 0) {
pos = 0; /* x position in bitmap */
eolflag = 0; /* end of line flag */
while ((eolflag == 0) && (eopicflag == 0)) {
count = fgetc(f);
val = fgetc(f);
if (count > 0) { /* repeat pixel count times */
for (j=0;j<count;j++) {
put_pixel(image, pos, line, val);
pos++;
}
}
else {
switch (val) {
case 0: /* end of line flag */
eolflag=1;
break;
case 1: /* end of picture flag */
eopicflag=1;
break;
case 2: /* displace picture */
count = fgetc(f);
val = fgetc(f);
pos += count;
line += val*dir;
break;
default: /* read in absolute mode */
for (j=0; j<val; j++) {
val0 = fgetc(f);
put_pixel(image, pos, line, val0);
pos++;
}
if (j%2 == 1)
val0 = fgetc(f); /* align on word boundary */
break;
}
}
if (pos-1 > (int)infoheader->biWidth)
eolflag=1;
}
line += dir;
if (line < 0 || line >= height)
eopicflag = 1;
}
}
/* read_rle4_compressed_image:
* For reading the 4 bit RLE compressed BMP image format.
*
* @note This support compressed top-down bitmaps, the MSDN says that
* they can't exist, but Photoshop can create them.
*/
static void read_rle4_compressed_image(FILE *f, Image *image, const BITMAPINFOHEADER *infoheader)
{
unsigned char b[8];
unsigned char count;
unsigned short val0, val;
int j, k, pos, line, height, dir;
int eolflag, eopicflag;
eopicflag = 0; /* end of picture flag */
height = (int)infoheader->biHeight;
line = height < 0 ? 0: height-1;
dir = height < 0 ? 1: -1;
height = ABS(height);
while (eopicflag == 0) {
pos = 0;
eolflag = 0; /* end of line flag */
while ((eolflag == 0) && (eopicflag == 0)) {
count = fgetc(f);
val = fgetc(f);
if (count > 0) { /* repeat pixels count times */
b[1] = val & 15;
b[0] = (val >> 4) & 15;
for (j=0; j<count; j++) {
put_pixel(image, pos, line, b[j%2]);
pos++;
}
}
else {
switch (val) {
case 0: /* end of line */
eolflag=1;
break;
case 1: /* end of picture */
eopicflag=1;
break;
case 2: /* displace image */
count = fgetc(f);
val = fgetc(f);
pos += count;
line += val*dir;
break;
default: /* read in absolute mode */
for (j=0; j<val; j++) {
if ((j%4) == 0) {
val0 = fgetw(f);
for (k=0; k<2; k++) {
b[2*k+1] = val0 & 15;
val0 = val0 >> 4;
b[2*k] = val0 & 15;
val0 = val0 >> 4;
}
}
put_pixel(image, pos, line, b[j%4]);
pos++;
}
break;
}
}
if (pos-1 > (int)infoheader->biWidth)
eolflag=1;
}
line += dir;
if (line < 0 || line >= height)
eopicflag = 1;
}
}
static uint32_t calc_shift(const uint32_t channelMask, int& channelBits)
{
uint32_t channelShift = 0;
uint32_t mask = 0;
if (channelMask) {
mask = ~channelMask;
while (mask & 1) {
++channelShift;
mask >>= 1;
}
if (mask) {
mask = ~mask;
while (mask & 1) {
channelBits++;
mask >>= 1;
}
}
else
channelBits = 32 - channelShift;
}
else
channelBits = 8;
return channelShift;
}
static int read_bitfields_image(FILE *f, Image *image, BITMAPINFOHEADER *infoheader,
uint32_t rmask, uint32_t gmask, uint32_t bmask,
uint32_t amask, bool& withAlpha)
{
uint32_t buffer, 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;
height = (int)infoheader->biHeight;
line = height < 0 ? 0: height-1;
dir = height < 0 ? 1: -1;
height = ABS(height);
/* calculate shifts */
rshift = calc_shift(rmask, rbits);
gshift = calc_shift(gmask, gbits);
bshift = calc_shift(bmask, bbits);
ashift = calc_shift(amask, abits);
/* calculate bits-per-pixel and bytes-per-pixel */
bits_per_pixel = infoheader->biBitCount;
bytes_per_pixel = ((bits_per_pixel / 8) +
((bits_per_pixel % 8) > 0 ? 1: 0));
for (i=0; i<height; i++, line+=dir) {
for (j=0; j<(int)infoheader->biWidth; j++) {
/* read the DWORD, WORD or BYTE in little-endian order */
buffer = 0;
for (k=0; k<bytes_per_pixel; k++)
buffer |= fgetc(f) << (k<<3);
r = (buffer & rmask) >> rshift;
g = (buffer & gmask) >> gshift;
b = (buffer & bmask) >> bshift;
a = (buffer & amask) >> ashift;
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) );
if (a)
withAlpha = true;
put_pixel_fast<RgbTraits>(image, j, line, rgba(r, g, b, a));
}
j = (bytes_per_pixel*j) % 4;
if (j > 0)
while (j++ < 4)
fgetc(f);
}
if (!withAlpha) {
LockImageBits<RgbTraits> imageBits(image, image->bounds());
auto imgIt = imageBits.begin(), imgEnd = imageBits.end();
for (; imgIt != imgEnd; ++imgIt)
*imgIt |= 0xff000000;
}
return 0;
}
bool BmpFormat::onLoad(FileOp *fop)
{
uint32_t rmask, gmask, bmask, amask;
BITMAPFILEHEADER fileheader;
BITMAPINFOHEADER infoheader;
unsigned long biSize;
PixelFormat pixelFormat;
int format;
FileHandle handle(open_file_with_exception(fop->filename(), "rb"));
FILE* f = handle.get();
if (read_bmfileheader(f, &fileheader) != 0)
return false;
infoheader.biSize = fgetl(f);
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 &&
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 == 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 &&
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 ||
ABS(int(infoheader.biHeight)) == 0) {
fop->setError("Invalid BMP size.\n");
return false;
}
uint32_t size = infoheader.biWidth * uint32_t(ABS(int(infoheader.biHeight)));
if (infoheader.biBitCount >= 8)
size *= (infoheader.biBitCount / 8);
else if (8 / infoheader.biBitCount > 0)
size /= (8 / infoheader.biBitCount);
if (size > kMaxBmpSize) {
fop->setError(fmt::format("BMP size unsupported ({:.2f} MB > {:.2f} MB).\n",
size / 1024.0 / 1024.0,
kMaxBmpSize / 1024.0 / 1024.0).c_str());
return false;
}
}
if ((infoheader.biBitCount == 32) ||
(infoheader.biBitCount == 24) ||
(infoheader.biBitCount == 16))
pixelFormat = IMAGE_RGB;
else
pixelFormat = IMAGE_INDEXED;
/* bitfields have the 'mask' for each component */
if (infoheader.isRGBMasks()) {
rmask = infoheader.rMask;
gmask = infoheader.gMask;
bmask = infoheader.bMask;
amask = (infoheader.isAlphaMask() ? infoheader.aMask : 0);
}
else
rmask = gmask = bmask = amask = 0;
ImageRef image = fop->sequenceImage(pixelFormat,
infoheader.biWidth,
ABS((int)infoheader.biHeight));
if (!image) {
return false;
}
if (pixelFormat == IMAGE_RGB)
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, withAlpha);
break;
case BI_RLE8:
read_rle8_compressed_image(f, image.get(), &infoheader);
break;
case BI_RLE4:
read_rle4_compressed_image(f, image.get(), &infoheader);
break;
case BI_BITFIELDS:
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;
}
break;
default:
fop->setError("Unsupported BMP compression.\n");
return false;
}
if (ferror(f)) {
fop->setError("Error reading file.\n");
return false;
}
// Setup the file-data.
if (!fop->formatOptions()) {
auto bmp_options = std::make_shared<BmpOptions>();
bmp_options->format = format;
bmp_options->compression = infoheader.biCompression;
bmp_options->bits_per_pixel = infoheader.biBitCount;
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);
}
return true;
}
#ifdef ENABLE_SAVE
bool BmpFormat::onSave(FileOp *fop)
{
const FileAbstractImage* img = fop->abstractImage();
const ImageSpec spec = img->spec();
const int w = spec.width();
const int h = spec.height();
int bfSize;
int biSizeImage;
int ncolors = fop->sequenceGetNColors();
int bpp = 0;
bool withAlpha = img->needAlpha();
switch (spec.colorMode()) {
case ColorMode::RGB:
if (withAlpha)
bpp = 32;
else
bpp = 24;
break;
case ColorMode::GRAYSCALE:
bpp = 8;
break;
case ColorMode::INDEXED: {
if (ncolors > 16)
bpp = 8;
else if (ncolors > 2)
bpp = 4;
else
bpp = 1;
ncolors = (1 << bpp);
break;
}
default:
// TODO save ColorMode::BITMAP as 1bpp bmp?
// Invalid image format
fop->setError("Unsupported color mode.\n");
return false;
}
int filler = int((32 - ((w*bpp-1) & 31)-1) / 8);
int c, i, j, r, g, b;
if (bpp <= 8) {
biSizeImage = (w + filler)*bpp/8 * h;
bfSize = (WININFOHEADERSIZE + OS2FILEHEADERSIZE // header
+ ncolors*4 // palette
+ biSizeImage); // image data
}
else {
biSizeImage = (w*3 + filler) * h;
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"));
FILE* f = handle.get();
/* file_header */
fputw(0x4D42, f); /* bfType ("BM") */
fputl(bfSize, f); /* bfSize */
fputw(0, f); /* bfReserved1 */
fputw(0, f); /* bfReserved2 */
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 */
}
fputl(w, f); /* biWidth */
fputl(h, f); /* biHeight */
fputw(1, f); /* biPlanes */
fputw(bpp, f); /* biBitCount */
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 */
// Save the palette
for (i=0; i<ncolors; i++) {
fop->sequenceGetColor(i, &r, &g, &b);
fputc(b, f);
fputc(g, f);
fputc(r, f);
fputc(0, f);
}
}
else {
fputl(0, f); /* biClrUsed */
fputl(0, f); /* biClrImportant */
if (withAlpha) {
fputl(0x00ff0000, f);
fputl(0x0000ff00, f);
fputl(0x000000ff, f);
fputl(0xff000000, f);
}
}
// Only used in indexed mode
int colorsPerByte = std::max(1, 8/bpp);
int colorMask;
switch (bpp) {
case 8: colorMask = 0xFF; break;
case 4: colorMask = 0x0F; break;
case 1: colorMask = 0x01; break;
default: colorMask = 0; break;
}
// Save image pixels (from bottom to top)
for (i=h-1; i>=0; i--) {
switch (spec.colorMode()) {
case ColorMode::RGB: {
auto scanline = (const uint32_t*)img->getScanline(i);
for (j=0; j<w; ++j) {
c = scanline[j];
fputc(rgba_getb(c), f);
fputc(rgba_getg(c), f);
fputc(rgba_getr(c), f);
if (withAlpha)
fputc(rgba_geta(c), f);
}
break;
}
case ColorMode::GRAYSCALE: {
auto scanline = (const uint16_t*)img->getScanline(i);
for (j=0; j<w; ++j) {
c = scanline[j];
fputc(graya_getv(c), f);
}
break;
}
case ColorMode::INDEXED: {
auto scanline = (const uint8_t*)img->getScanline(i);
for (j=0; j<w; ) {
uint8_t value = 0;
for (int k=colorsPerByte-1; k>=0 && j<w; --k, ++j) {
c = scanline[j];
value |= (c & colorMask) << (bpp*k);
}
fputc(value, f);
}
break;
}
}
for (j=0; j<filler; j++)
fputc(0, f);
fop->setProgress((float)(h-i) / (float)h);
}
if (ferror(f)) {
fop->setError("Error writing file.\n");
return false;
}
else {
return true;
}
}
#endif
} // namespace app