| #ifdef _MSC_VER |
| #pragma warning(disable : 4996) |
| #endif |
| |
| #include <memory> |
| |
| #include "tiio_bmp.h" |
| |
| #include "tpixel.h" |
| #include "tpixelgr.h" |
| #include "tproperty.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); |
| } |
| |
| |
| |
| } |
| |
| |
| |
| 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) { |
| |
| 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); |
| |
| |
| 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); |
| |
| |
| 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); |
| |
| |
| 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 |
| { |
| m_header.biWidth = getshort(m_chan); |
| m_header.biHeight = getshort(m_chan); |
| m_header.biPlanes = getshort(m_chan); |
| m_header.biBitCount = getshort(m_chan); |
| |
| |
| 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; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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) |
| m_readLineMethod = &BmpReader::read16m555Line; |
| else if (m_header.biCompression == 3) |
| { |
| 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; |
| |
| |
| 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]; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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++; |
| |
| } |
| 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); |
| 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; |
| }; |
| |
| |
| |
| 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 |
| + 40 |
| + cmapSize * 4 |
| + bytePerLine * ly |
| ; |
| int offset = 14 + 40 + cmapSize * 4; |
| int compression = 0; |
| int imageSize = bytePerLine * ly; |
| |
| putc('B', m_chan); |
| putc('M', m_chan); |
| |
| putint(m_chan, fileSize); |
| putshort(m_chan, 0); |
| putshort(m_chan, 0); |
| |
| putint(m_chan, offset); |
| |
| putint(m_chan, 40); |
| putint(m_chan, m_info.m_lx); |
| putint(m_chan, m_info.m_ly); |
| putshort(m_chan, 1); |
| putshort(m_chan, m_bitPerPixel); |
| putint(m_chan, compression); |
| putint(m_chan, imageSize); |
| putint(m_chan, 0); |
| putint(m_chan, 0); |
| putint(m_chan, cmapSize); |
| putint(m_chan, 0); |
| |
| 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++) |
| { |
| 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); |
| } |