#ifdef _MSC_VER
#pragma warning(disable : 4996)
#endif
#include <memory>
#include "tiio_bmp.h"
// #include "bmp/filebmp.h"
#include "tpixel.h"
#include "tpixelgr.h"
#include "tproperty.h"
//#include "tconvert.h"
#include <stdio.h>
//=========================================================
namespace {
//=========================================================
typedef struct {
UINT bfSize;
UINT bfOffBits;
UINT biSize;
UINT biWidth;
UINT biHeight;
UINT biPlanes;
UINT biBitCount;
UINT biCompression;
UINT biSizeImage;
UINT biXPelsPerMeter;
UINT biYPelsPerMeter;
UINT biClrUsed;
UINT biClrImportant;
UINT biFilesize;
UINT biPad;
} BMP_HEADER;
const int BMP_WIN_SIZE = 40;
const int BMP_OS2_SIZE = 64;
const int BMP_BI_RGB = 0;
const int BMP_BI_RLE8 = 1;
const int BMP_BI_RLE4 = 2;
//=========================================================
UINT getshort(FILE *fp) {
int c = getc(fp), c1 = getc(fp);
return ((UINT)c) + (((UINT)c1) << 8);
}
//---------------------------------------------------------
UINT getint(FILE *fp) {
int c = getc(fp), c1 = getc(fp), c2 = getc(fp), c3 = getc(fp);
return (((UINT)c) << 0) + (((UINT)c1) << 8) + (((UINT)c2) << 16) +
(((UINT)c3) << 24);
}
//---------------------------------------------------------
void putshort(FILE *fp, int i) {
int c = (((UINT)i)) & 0xff, c1 = (((UINT)i) >> 8) & 0xff;
putc(c, fp);
putc(c1, fp);
}
//---------------------------------------------------------
void putint(FILE *fp, int i) {
int c = ((UINT)i) & 0xff, c1 = (((UINT)i) >> 8) & 0xff,
c2 = (((UINT)i) >> 16) & 0xff, c3 = (((UINT)i) >> 24) & 0xff;
putc(c, fp);
putc(c1, fp);
putc(c2, fp);
putc(c3, fp);
}
//=========================================================
} // namespace
//=========================================================
class BmpReader final : public Tiio::Reader {
FILE *m_chan;
BMP_HEADER m_header;
char *m_line;
int m_lineSize;
std::unique_ptr<TPixel[]> m_cmap;
bool m_corrupted;
typedef int (BmpReader::*ReadLineMethod)(char *buffer, int x0, int x1,
int shrink);
ReadLineMethod m_readLineMethod;
public:
BmpReader();
~BmpReader();
void open(FILE *file) override;
int readNoLine(char *buffer, int x0, int x1, int shrink);
void skipBytes(int count) {
// fseek(m_chan, count, SEEK_CUR); //inefficiente se count รจ piccolo
for (int i = 0; i < count; i++) {
getc(m_chan);
}
}
int read1Line(char *buffer, int x0, int x1, int shrink);
int read4Line(char *buffer, int x0, int x1, int shrink);
int read8Line(char *buffer, int x0, int x1, int shrink);
int read8LineRle(char *buffer, int x0, int x1, int shrink);
int read16m555Line(char *buffer, int x0, int x1, int shrink);
int read16m565Line(char *buffer, int x0, int x1, int shrink);
int read24Line(char *buffer, int x0, int x1, int shrink);
int read32Line(char *buffer, int x0, int x1, int shrink);
void readLine(char *buffer, int x0, int x1, int shrink) override;
int skipLines(int lineCount) override {
fseek(m_chan, lineCount * m_lineSize, SEEK_CUR);
/* for(int i=0;i<lineCount;i++)
skipBytes(m_lineSize);*/
return lineCount;
}
private:
void readHeader();
};
//---------------------------------------------------------
BmpReader::BmpReader()
: m_chan(0), m_corrupted(false), m_readLineMethod(&BmpReader::readNoLine) {
memset(&m_header, 0, sizeof m_header);
}
//---------------------------------------------------------
BmpReader::~BmpReader() {}
//---------------------------------------------------------
void BmpReader::open(FILE *file) {
m_chan = file;
readHeader();
}
//---------------------------------------------------------
void BmpReader::readHeader() {
if (!m_chan) return;
fseek(m_chan, 0L, SEEK_END);
m_header.biFilesize = ftell(m_chan);
fseek(m_chan, 0L, 0);
/* read the file type (first two bytes) */
char signature[2];
signature[0] = fgetc(m_chan);
signature[1] = fgetc(m_chan);
if (signature[0] != 'B' || signature[1] != 'M') {
m_corrupted = true;
return;
}
m_header.bfSize = getint(m_chan);
/* reserved and ignored */
getshort(m_chan);
getshort(m_chan);
m_header.bfOffBits = getint(m_chan);
m_header.biSize = getint(m_chan);
if ((int)m_header.biSize == BMP_WIN_SIZE ||
(int)m_header.biSize == BMP_OS2_SIZE) {
m_header.biWidth = getint(m_chan);
m_header.biHeight = getint(m_chan);
m_header.biPlanes = getshort(m_chan);
m_header.biBitCount = getshort(m_chan);
m_header.biCompression = getint(m_chan);
m_header.biSizeImage = getint(m_chan);
m_header.biXPelsPerMeter = getint(m_chan);
m_header.biYPelsPerMeter = getint(m_chan);
m_header.biClrUsed = getint(m_chan);
m_header.biClrImportant = getint(m_chan);
} else // old bitmap format
{
m_header.biWidth = getshort(m_chan);
m_header.biHeight = getshort(m_chan);
m_header.biPlanes = getshort(m_chan);
m_header.biBitCount = getshort(m_chan);
/* Not in old versions so have to compute them */
m_header.biSizeImage =
(((m_header.biPlanes * m_header.biBitCount * m_header.biWidth) + 31) /
32) *
4 * m_header.biHeight;
m_header.biCompression = BMP_BI_RGB;
m_header.biXPelsPerMeter = 0;
m_header.biYPelsPerMeter = 0;
m_header.biClrUsed = 0;
m_header.biClrImportant = 0;
}
m_header.biPad = 0;
m_info.m_lx = m_header.biWidth;
m_info.m_ly = m_header.biHeight;
/*
BMP_HEADER *hd = m_header;
if ((int)hd->biSize != BMP_WIN_OS2_OLD)
{
// skip ahead to colormap, using biSize
int c = hd->biSize - 40; // 40 bytes read from biSize to biClrImportant
for (int i=0; i<c; i++) getc(m_chan);
hd->biPad = hd->bfOffBits - (hd->biSize + 14);
}
else
hd->biPad = 0;
*/
// load up colormap, if any
if (m_header.biBitCount < 16) {
int cmaplen =
(m_header.biClrUsed) ? m_header.biClrUsed : 1 << m_header.biBitCount;
assert(cmaplen <= 256);
m_cmap.reset(new TPixel[256]);
TPixel32 *pix = m_cmap.get();
for (int i = 0; i < cmaplen; i++) {
pix->b = getc(m_chan);
pix->g = getc(m_chan);
pix->r = getc(m_chan);
pix->m = 255;
getc(m_chan);
++pix;
}
}
/*
if (hd->biSize != BMP_WIN_OS2_OLD)
{
// Waste any unused bytes between the colour map (if present)
// and the start of the actual bitmap data.
while (hd->biPad > 0)
{
(void) getc(m_chan);
hd->biPad--;
}
}
*/
// get information about the portion of the image to load
// get_info_region(&info, x1, y1, x2, y2, scale,
// (int)hd->biWidth, (int)hd->biHeight, TNZ_BOTLEFT);
// skip_bmp_lines(fp, hd->biWidth, (unsigned int)(info.startScanRow-1),
// (unsigned int)SEEK_CUR, subtype);
int lx = m_info.m_lx;
switch (m_header.biBitCount) {
case 1:
m_info.m_samplePerPixel = 1;
m_readLineMethod = &BmpReader::read1Line;
m_lineSize = (lx + 7) / 8;
break;
case 4:
m_info.m_samplePerPixel = 1;
if (m_header.biCompression == 0) m_readLineMethod = &BmpReader::read4Line;
m_lineSize = (lx + 1) / 2;
break;
case 8:
m_info.m_samplePerPixel = 1;
if (m_header.biCompression == 0)
m_readLineMethod = &BmpReader::read8Line;
else if (m_header.biCompression == 1)
m_readLineMethod = &BmpReader::read8LineRle;
m_lineSize = lx;
break;
case 16:
m_info.m_samplePerPixel = 3;
if (m_header.biCompression == 0) // BI_RGB
m_readLineMethod = &BmpReader::read16m555Line;
else if (m_header.biCompression == 3) // BI_BITFIELDS
{
unsigned int rmask = 0, gmask = 0, bmask = 0;
rmask = getint(m_chan);
gmask = getint(m_chan);
bmask = getint(m_chan);
if (gmask == 0x7E0)
m_readLineMethod = &BmpReader::read16m565Line;
else
m_readLineMethod = &BmpReader::read16m555Line;
}
m_lineSize = lx * 2;
break;
case 24:
m_info.m_samplePerPixel = 3;
m_readLineMethod = &BmpReader::read24Line;
m_lineSize = lx * 3;
break;
case 32:
m_info.m_samplePerPixel = 3;
m_readLineMethod = &BmpReader::read32Line;
m_lineSize = lx * 4;
break;
}
m_lineSize += 3 - ((m_lineSize + 3) & 3);
fseek(m_chan, m_header.bfOffBits, SEEK_SET);
}
//---------------------------------------------------------
void BmpReader::readLine(char *buffer, int x0, int x1, int shrink) {
(this->*m_readLineMethod)(buffer, x0, x1, shrink);
}
//---------------------------------------------------------
int BmpReader::readNoLine(char *buffer, int x0, int x1, int shrink) {
skipBytes(m_lineSize);
return 0;
}
//---------------------------------------------------------
int BmpReader::read1Line(char *buffer, int x0, int x1, int shrink) {
TPixel32 *pix = (TPixel32 *)buffer;
// pix += x0;
if (x0 > 0) skipBytes(x0 / 8);
TPixel32 *endPix = pix + x1 + 1;
int value = 0;
int index = x0;
if (x0 % 8 != 0) {
value = getc(m_chan);
for (index = x0; index < x0 + 8 - (x0 % 8); index += shrink) {
pix[index] = m_cmap[(value >> (7 - ((index) % 8))) & 1];
}
}
value = getc(m_chan);
int prevBlock = index / 8;
for (int j = index; pix + j < endPix; j += shrink) {
if (j / 8 > prevBlock) value = getc(m_chan);
prevBlock = j / 8;
pix[j] = m_cmap[(value >> (7 - (j % 8))) & 1];
}
/*
while(pix+8<=endPix)
{
value = getc(m_chan);
pix[0] = m_cmap[(value>>7)&1];
pix[1] = m_cmap[(value>>6)&1];
pix[2] = m_cmap[(value>>5)&1];
pix[3] = m_cmap[(value>>4)&1];
pix[4] = m_cmap[(value>>3)&1];
pix[5] = m_cmap[(value>>2)&1];
pix[6] = m_cmap[(value>>1)&1];
pix[7] = m_cmap[(value>>0)&1];
pix +=8*shrink;
if(shrink>1) value = getc(m_chan);
//pix+=8*shrink;
//if(pix+8<endPix && shrink>1) skipBytes(shrink-1);
}
if(pix<endPix)
{
value = getc(m_chan);
assert(pix+8>=endPix);
for(int j=0; pix+j<endPix; j++)
pix[j] = m_cmap[(value>>(7-j))&1];
} */
if ((m_info.m_lx - x1) / 8 > 0) {
skipBytes((m_info.m_lx - x1) / 8);
}
int bytes = (m_info.m_lx + 7) / 8;
if (m_lineSize - bytes > 0) skipBytes(m_lineSize - bytes);
return 0;
}
//---------------------------------------------------------
int BmpReader::read4Line(char *buffer, int x0, int x1, int shrink) {
TPixel32 *pix = (TPixel32 *)buffer;
pix += 2 * x0;
if (x0 > 0) skipBytes(x0 / 2);
TPixel32 *endPix = pix + x1 - x0 + 1;
int value;
while (pix + 2 <= endPix) {
value = getc(m_chan);
pix[0] = m_cmap[value & 0xF];
pix[1] = m_cmap[(value >> 4) & 0xF];
// pix+=2*shrink;
pix++;
// if(pix+2<=endPix && shrink>1) skipBytes(shrink-1);
}
if (pix < endPix) {
value = getc(m_chan);
pix[0] = m_cmap[value & 0xF];
}
if ((m_info.m_lx - x1) / 2 > 0) {
skipBytes((m_info.m_lx - x1) / 2);
}
int bytes = (m_info.m_lx + 1) / 2;
if (m_lineSize - bytes) skipBytes(m_lineSize - bytes);
return 0;
}
//---------------------------------------------------------
int BmpReader::read8Line(char *buffer, int x0, int x1, int shrink) {
TPixel32 *pix = (TPixel32 *)buffer;
if (x0 > 0) skipBytes(x0);
pix += x0;
TPixel32 *endPix = pix + x1 - x0 + 1;
while (pix < endPix) {
int value = getc(m_chan);
*pix++ = m_cmap[value];
if (pix < endPix && shrink > 1) {
skipBytes(shrink - 1);
pix += (shrink - 1);
}
}
if (m_info.m_lx - x1 - 1 > 0) {
skipBytes(m_info.m_lx - x1 - 1);
}
if (m_lineSize - m_info.m_lx > 0) skipBytes(m_lineSize - m_info.m_lx);
return 0;
}
//---------------------------------------------------------
int BmpReader::read8LineRle(char *buffer, int x0, int x1, int shrink) {
TPixel32 *pix = (TPixel32 *)buffer;
if (x0 > 0) skipBytes(x0);
pix += x0;
TPixel32 *endPix = pix + (x1 - x0 + 1);
while (pix < endPix) {
int i, count = getc(m_chan);
assert(count >= 0);
if (count == 0) {
int pixels = getc(m_chan);
assert(pixels >= 0 && pixels != 2);
if (pixels < 3) return 0;
for (i = 0; i < pixels; i++) *pix++ = m_cmap[getc(m_chan)];
if ((1 + 1 + pixels) & 0x1) getc(m_chan);
} else {
int value = getc(m_chan);
assert(value >= 0);
for (i = 0; i < count; i++) *pix++ = m_cmap[value];
}
if (pix < endPix && shrink > 1) {
skipBytes(shrink - 1);
pix += (shrink - 1);
}
}
if (m_info.m_lx - x1 - 1 > 0) {
skipBytes(m_info.m_lx - x1 - 1);
}
if (m_lineSize - m_info.m_lx > 0) skipBytes(m_lineSize - m_info.m_lx);
int val = getc(m_chan);
assert(val == 0); // end scanline or end bmp
val = getc(m_chan);
assert(val == 0 || val == 1);
return 0;
}
//---------------------------------------------------------
int BmpReader::read16m555Line(char *buffer, int x0, int x1, int shrink) {
TPixel32 *pix = (TPixel32 *)buffer;
if (x0 > 0) skipBytes(2 * x0);
pix += x0;
TPixel32 *endPix = pix + x1 - x0 + 1;
while (pix < endPix) {
int v = getshort(m_chan);
int r = (v >> 10) & 0x1F;
int g = (v >> 5) & 0x1F;
int b = v & 0x1F;
pix->r = r << 3 | r >> 2;
pix->g = g << 3 | g >> 2;
pix->b = b << 3 | b >> 2;
pix->m = 255;
pix += shrink;
if (pix < endPix && shrink > 1) skipBytes(2 * (shrink - 1));
}
if (m_info.m_lx - x1 - 1 > 0) skipBytes(2 * (m_info.m_lx - x1 - 1));
if (m_lineSize - 2 * m_info.m_lx > 0) skipBytes(m_lineSize - 2 * m_info.m_lx);
return 0;
}
//---------------------------------------------------------
int BmpReader::read16m565Line(char *buffer, int x0, int x1, int shrink) {
TPixel32 *pix = (TPixel32 *)buffer;
if (x0 > 0) skipBytes(2 * x0);
pix += x0;
TPixel32 *endPix = pix + x1 - x0 + 1;
while (pix < endPix) {
int v = getshort(m_chan);
int r = (v >> 11) & 0x1F;
int g = (v >> 5) & 0x3F;
int b = v & 0x1F;
pix->r = r << 3 | r >> 2;
pix->g = g << 2 | g >> 4;
pix->b = b << 3 | b >> 2;
pix->m = 255;
pix += shrink;
if (pix < endPix && shrink > 1) skipBytes(2 * (shrink - 1));
}
if (m_info.m_lx - x1 - 1 > 0) skipBytes(2 * (m_info.m_lx - x1 - 1));
if (m_lineSize - 2 * m_info.m_lx > 0) skipBytes(m_lineSize - 2 * m_info.m_lx);
return 0;
}
//---------------------------------------------------------
int BmpReader::read24Line(char *buffer, int x0, int x1, int shrink) {
TPixel32 *pix = (TPixel32 *)buffer;
if (x0 > 0) skipBytes(3 * x0);
pix += x0;
TPixel32 *endPix = pix + x1 - x0 + 1;
while (pix < endPix) {
pix->b = getc(m_chan);
pix->g = getc(m_chan);
pix->r = getc(m_chan);
pix->m = 255;
pix += shrink;
if (pix < endPix && shrink > 1) skipBytes(3 * (shrink - 1));
}
if (m_info.m_lx - x1 - 1 > 0) skipBytes(3 * (m_info.m_lx - x1 - 1));
if (m_lineSize - 3 * m_info.m_lx > 0) skipBytes(m_lineSize - 3 * m_info.m_lx);
return 0;
}
//---------------------------------------------------------
int BmpReader::read32Line(char *buffer, int x0, int x1, int shrink) {
TPixel32 *pix = (TPixel32 *)buffer;
if (x0 > 0) skipBytes(4 * x0);
pix += x0;
TPixel32 *endPix = pix + x1 - x0 + 1;
while (pix < endPix) {
pix->b = getc(m_chan);
pix->g = getc(m_chan);
pix->r = getc(m_chan);
pix->m = getc(m_chan);
pix += shrink;
if (pix < endPix && shrink > 1) skipBytes(4 * (shrink - 1));
}
if (m_info.m_lx - x1 - 1 > 0) skipBytes(4 * (m_info.m_lx - x1 - 1));
if (m_lineSize - 4 * m_info.m_lx > 0) skipBytes(m_lineSize - 4 * m_info.m_lx);
return 0;
}
//=========================================================
class BmpWriter final : public Tiio::Writer {
FILE *m_chan;
int m_bitPerPixel;
int m_compression;
public:
BmpWriter();
~BmpWriter();
void open(FILE *file, const TImageInfo &info) override;
void flush() override { fflush(m_chan); }
void writeLine(char *buffer) override;
// for now opentoonz does not support bmp format with alpha channel
bool writeAlphaSupported() const override { return false; }
};
//---------------------------------------------------------
BmpWriter::BmpWriter() : m_chan(0) {}
//---------------------------------------------------------
BmpWriter::~BmpWriter() { delete m_properties; }
//---------------------------------------------------------
void BmpWriter::open(FILE *file, const TImageInfo &info) {
m_chan = file;
m_info = info;
int lx = m_info.m_lx;
int ly = m_info.m_ly;
if (!m_properties) m_properties = new Tiio::BmpWriterProperties();
TEnumProperty *p =
(TEnumProperty *)(m_properties->getProperty("Bits Per Pixel"));
assert(p);
std::string str = ::to_string(p->getValue());
m_bitPerPixel = atoi(str.c_str());
int cmapSize = 0;
std::vector<TPixel> *colormap = 0;
if (m_bitPerPixel == 8) {
TPointerProperty *colormapProp =
(TPointerProperty *)(m_properties->getProperty("Colormap"));
if (colormapProp) {
colormap = (std::vector<TPixel> *)colormapProp->getValue();
cmapSize = colormap->size();
} else
cmapSize = 256;
}
assert(m_bitPerPixel == 8 || m_bitPerPixel == 24);
int bytePerLine =
((lx * m_bitPerPixel + 31) / 32) * (m_bitPerPixel == 8 ? 1 : 4);
int fileSize = 14 // file header
+ 40 // info header
+ cmapSize * 4 // colormap size
+ bytePerLine * ly // image size
;
int offset = 14 + 40 + cmapSize * 4;
int compression = 0;
int imageSize = bytePerLine * ly;
putc('B', m_chan);
putc('M', m_chan); // BMP file magic number
putint(m_chan, fileSize);
putshort(m_chan, 0); // reserved1
putshort(m_chan, 0); // reserved2
putint(m_chan, offset); // offset from BOfile to BObitmap
putint(m_chan, 40); // size of bitmap info header
putint(m_chan, m_info.m_lx); // width
putint(m_chan, m_info.m_ly); // height
putshort(m_chan, 1); // must be '1'
putshort(m_chan, m_bitPerPixel); // 1,4,8, or 24
putint(m_chan, compression); // BMP_BI_RGB, BMP_BI_RLE8 or BMP_BI_RLE4
putint(m_chan, imageSize); // size of raw image data
putint(m_chan, 0); // dpi * 39" per meter
putint(m_chan, 0); // dpi * 39" per meter
putint(m_chan, cmapSize); // colors used in cmap
putint(m_chan, 0); // same as above
if (colormap)
for (int i = 0; i < (int)colormap->size(); i++) {
putc((*colormap)[i].b, m_chan);
putc((*colormap)[i].g, m_chan);
putc((*colormap)[i].r, m_chan);
putc(0, m_chan);
}
else
for (int i = 0; i < cmapSize; i++) // palette!!
{
putc(i, m_chan);
putc(i, m_chan);
putc(i, m_chan);
putc(0, m_chan);
}
}
//---------------------------------------------------------
void BmpWriter::writeLine(char *buffer) {
int lx = m_info.m_lx;
int j;
switch (m_bitPerPixel) {
case 24: {
TPixel32 *pix = (TPixel32 *)buffer;
for (j = 0; j < lx; j++) {
putc(pix->b, m_chan);
putc(pix->g, m_chan);
putc(pix->r, m_chan);
++pix;
}
int bytes = lx * 3;
while (bytes & 3) {
putc(0, m_chan);
bytes++;
}
break;
}
case 8: {
TPixelGR8 *pix = (TPixelGR8 *)buffer;
for (j = 0; j < lx; j++) {
putc(pix->value, m_chan);
++pix;
}
while (lx & 3) {
putc(0, m_chan);
lx++;
}
break;
}
default:
assert(false);
}
ftell(m_chan);
}
//=========================================================
Tiio::Reader *Tiio::makeBmpReader() { return new BmpReader(); }
//=========================================================
Tiio::Writer *Tiio::makeBmpWriter() { return new BmpWriter(); }
//=========================================================
Tiio::BmpWriterProperties::BmpWriterProperties()
: m_pixelSize("Bits Per Pixel") {
m_pixelSize.addValue(L"24 bits");
m_pixelSize.addValue(L"8 bits (Greyscale)");
bind(m_pixelSize);
}
void Tiio::BmpWriterProperties::updateTranslation() {
m_pixelSize.setQStringName(tr("Bits Per Pixel"));
m_pixelSize.setItemUIName(L"24 bits", tr("24 bits"));
m_pixelSize.setItemUIName(L"8 bits (Greyscale)", tr("8 bits (Greyscale)"));
}