306 lines
8.1 KiB
C++
306 lines
8.1 KiB
C++
/*
|
|
* Bitmap Format Graphics Implementation
|
|
* Nana C++ Library(http://www.nanapro.org)
|
|
* Copyright(C) 2003-2020 Jinhao(cnjinhao@hotmail.com)
|
|
*
|
|
* Distributed under the Boost Software License, Version 1.0.
|
|
* (See accompanying file LICENSE_1_0.txt or copy at
|
|
* http://www.boost.org/LICENSE_1_0.txt)
|
|
*
|
|
* @file: nana/paint/detail/image_bmp.hpp
|
|
* @contributors: Ryan Gonzalez
|
|
*/
|
|
#ifndef NANA_PAINT_DETAIL_IMAGE_BMP_HPP
|
|
#define NANA_PAINT_DETAIL_IMAGE_BMP_HPP
|
|
|
|
#include <memory>
|
|
#include <cstring>
|
|
#include "image_pixbuf.hpp"
|
|
|
|
namespace nana{ namespace paint
|
|
{
|
|
namespace detail
|
|
{
|
|
#ifndef NANA_WINDOWS
|
|
struct bitmap_file_header
|
|
{
|
|
unsigned short bfType;
|
|
unsigned bfSize;
|
|
unsigned short bfReserved1;
|
|
unsigned short bfReserved2;
|
|
unsigned bfOffBits;
|
|
} __attribute__((packed));
|
|
|
|
struct bitmap_core_header
|
|
{
|
|
unsigned biSize;
|
|
unsigned short biWidth;
|
|
unsigned short biHeight;
|
|
unsigned short biPlanes;
|
|
unsigned short biBitCount;
|
|
} __attribute__((packed));
|
|
|
|
struct bitmap_info_header {
|
|
unsigned biSize;
|
|
int biWidth;
|
|
int biHeight;
|
|
unsigned short biPlanes;
|
|
unsigned short biBitCount;
|
|
unsigned biCompression;
|
|
unsigned biSizeImage;
|
|
int biXPelsPerMeter;
|
|
int biYPelsPerMeter;
|
|
unsigned biClrUsed;
|
|
unsigned biClrImportant;
|
|
}__attribute__((packed));
|
|
|
|
struct rgb_quad
|
|
{
|
|
unsigned char rgbBlue;
|
|
unsigned char rgbGreen;
|
|
unsigned char rgbRed;
|
|
unsigned char rgbReserved;
|
|
};
|
|
#else
|
|
typedef BITMAPFILEHEADER bitmap_file_header;
|
|
typedef BITMAPCOREHEADER bitmap_core_header;
|
|
typedef BITMAPINFOHEADER bitmap_info_header;
|
|
typedef RGBQUAD rgb_quad;
|
|
#endif
|
|
|
|
class image_bmp
|
|
:public basic_image_pixbuf
|
|
{
|
|
public:
|
|
~image_bmp()
|
|
{
|
|
this->close();
|
|
}
|
|
|
|
bool open(const void* file_data, std::size_t bytes) override
|
|
{
|
|
auto bmp_file = reinterpret_cast<const bitmap_file_header*>(file_data);
|
|
if ((bmp_file->bfType != 0x4D42) || (bmp_file->bfSize != bytes))
|
|
return false;
|
|
|
|
auto const header_bytes = *reinterpret_cast<const unsigned long*>(bmp_file + 1);
|
|
|
|
//There are two kind of base headers. Determinate it by size of header(The first ulong of header).
|
|
//Only Windows Bitmap(BITMAPINFOHEADER) is supported.
|
|
if (sizeof(bitmap_core_header) == header_bytes)
|
|
{
|
|
//The OS/2 BITMAPCOREHEADER is not supported.
|
|
throw std::invalid_argument("BMP with OS/2 BITMAPCOREHEADER is not supported now.");
|
|
}
|
|
|
|
auto header = reinterpret_cast<const bitmap_info_header *>(bmp_file + 1);
|
|
|
|
const std::size_t bmp_height = std::abs(header->biHeight);
|
|
|
|
//Bitmap file is 4byte-aligned for each line.
|
|
auto bytes_per_line = (((header->biWidth * header->biBitCount + 31) & ~31) >> 3);
|
|
|
|
pixbuf_.open(header->biWidth, bmp_height);
|
|
|
|
auto bits = reinterpret_cast<const unsigned char*>(reinterpret_cast<const char*>(file_data) + bmp_file->bfOffBits);
|
|
|
|
if (16 <= header->biBitCount)
|
|
pixbuf_.put(bits, header->biWidth, bmp_height, header->biBitCount, bytes_per_line, (header->biHeight < 0));
|
|
else
|
|
_m_put_with_palette(header, bits, bmp_file->bfSize - bmp_file->bfOffBits, bytes_per_line);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool open(const std::filesystem::path& filename) override
|
|
{
|
|
std::ifstream ifs(filename.string(), std::ios::binary);
|
|
|
|
auto const bytes = static_cast<unsigned>(std::filesystem::file_size(filename));
|
|
if (ifs && (bytes > static_cast<int>(sizeof(bitmap_file_header))))
|
|
{
|
|
std::unique_ptr<char[]> buffer{ new char[bytes] };
|
|
|
|
ifs.read(buffer.get(), bytes);
|
|
if (bytes == static_cast<std::size_t>(ifs.gcount()))
|
|
return open(buffer.get(), bytes);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool alpha_channel() const override
|
|
{
|
|
return false;
|
|
}
|
|
private:
|
|
std::unique_ptr<unsigned char[]> _m_decompress_rle8(const bitmap_info_header* header, const unsigned char* data, std::size_t data_size)
|
|
{
|
|
std::size_t const lines = std::abs(header->biHeight);
|
|
unsigned char* const indexes = new unsigned char[header->biWidth * lines];
|
|
auto p = indexes;
|
|
auto p_line = p;
|
|
auto const p_end = indexes + header->biWidth * lines;
|
|
|
|
std::size_t line_pos = 0;
|
|
auto end = data + data_size;
|
|
while (data != end && p < p_end)
|
|
{
|
|
if (0 == data[0])
|
|
{
|
|
//escape
|
|
if (0 == data[1])
|
|
{
|
|
//eol
|
|
data += 2;
|
|
++line_pos;
|
|
}
|
|
else if (1 == data[1])
|
|
{
|
|
//eof
|
|
data += 2;
|
|
break;
|
|
}
|
|
else if (2 == data[1])
|
|
{
|
|
//delta
|
|
auto x = data[2];
|
|
auto y = data[3];
|
|
|
|
// Check if the delta is available
|
|
if ((p + x < p_line + header->biWidth) && (line_pos + y < lines))
|
|
{
|
|
p += y * header->biWidth + x;
|
|
line_pos += y;
|
|
}
|
|
else
|
|
break;
|
|
|
|
data += 4;
|
|
}
|
|
else
|
|
{
|
|
//absolute
|
|
std::memcpy(p, data + 2, data[1]);
|
|
p += data[1];
|
|
|
|
data += ((data[1] + 1) & 0xFFE) + 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::memset(p, data[1], data[0]);
|
|
p += data[0];
|
|
|
|
data += 2;
|
|
}
|
|
}
|
|
|
|
return std::unique_ptr<unsigned char[]>{ indexes };
|
|
}
|
|
|
|
void _m_put_with_palette(const bitmap_info_header* header, const unsigned char* bits, std::size_t length, unsigned line_bytes)
|
|
{
|
|
auto const image_height = std::abs(header->biHeight);
|
|
const std::size_t total_pixels = header->biWidth * static_cast<std::size_t>(image_height);
|
|
|
|
auto const color_table = reinterpret_cast<const rgb_quad*>(reinterpret_cast<const unsigned char*>(header) + header->biSize);
|
|
|
|
auto dst_px = pixbuf_.raw_ptr(0);
|
|
auto const end_dst_px = dst_px + total_pixels;
|
|
|
|
int line_pos = image_height - 1;
|
|
int delta = -1;
|
|
if (header->biHeight < 0)
|
|
{
|
|
line_pos = 0;
|
|
delta = 1;
|
|
}
|
|
|
|
if (8 == header->biBitCount)
|
|
{
|
|
//decompressed indexes
|
|
std::unique_ptr<unsigned char[]> indexes;
|
|
|
|
if (1 == header->biCompression)
|
|
{
|
|
indexes = _m_decompress_rle8(header, bits, length);
|
|
line_bytes = header->biWidth;
|
|
bits = indexes.get();
|
|
}
|
|
|
|
while (dst_px < end_dst_px)
|
|
{
|
|
auto px_indexes = bits + line_bytes * line_pos;
|
|
auto const line_end_dst_px = dst_px + header->biWidth;
|
|
while (dst_px != line_end_dst_px)
|
|
{
|
|
auto & rgb = color_table[*px_indexes++];
|
|
dst_px->element.red = rgb.rgbRed;
|
|
dst_px->element.green = rgb.rgbGreen;
|
|
dst_px->element.blue = rgb.rgbBlue;
|
|
dst_px->element.alpha_channel = rgb.rgbReserved;
|
|
|
|
++dst_px;
|
|
}
|
|
line_pos += delta;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (dst_px < end_dst_px)
|
|
{
|
|
auto px_indexes = bits + line_bytes * line_pos;
|
|
auto const line_end_dst_px = dst_px + header->biWidth;
|
|
std::size_t pos = 0;
|
|
switch (header->biBitCount)
|
|
{
|
|
case 4:
|
|
while (dst_px != line_end_dst_px)
|
|
{
|
|
auto & rgb = color_table[((pos & 1) ? px_indexes[pos >> 1] : (px_indexes[pos >> 1] >> 4)) & 0xF];
|
|
dst_px->element.red = rgb.rgbRed;
|
|
dst_px->element.green = rgb.rgbGreen;
|
|
dst_px->element.blue = rgb.rgbBlue;
|
|
dst_px->element.alpha_channel = rgb.rgbReserved;
|
|
++dst_px;
|
|
++pos;
|
|
}
|
|
break;
|
|
case 2:
|
|
while (dst_px != line_end_dst_px)
|
|
{
|
|
//auto const shift = ((3 - (pos & 0x3)) << 1); // (index % 4) * 2
|
|
auto& rgb = color_table[(px_indexes[pos >> 2] >> ((3 - (pos & 0x3)) << 1)) & 0x3];
|
|
dst_px->element.red = rgb.rgbRed;
|
|
dst_px->element.green = rgb.rgbGreen;
|
|
dst_px->element.blue = rgb.rgbBlue;
|
|
dst_px->element.alpha_channel = rgb.rgbReserved;
|
|
++dst_px;
|
|
++pos;
|
|
}
|
|
break;
|
|
case 1:
|
|
while (dst_px != line_end_dst_px)
|
|
{
|
|
auto & rgb = color_table[(px_indexes[pos >> 3] >> (7 - (pos & 7))) & 1];
|
|
|
|
dst_px->element.red = rgb.rgbRed;
|
|
dst_px->element.green = rgb.rgbGreen;
|
|
dst_px->element.blue = rgb.rgbBlue;
|
|
dst_px->element.alpha_channel = rgb.rgbReserved;
|
|
++dst_px;
|
|
++pos;
|
|
}
|
|
break;
|
|
}
|
|
line_pos += delta;
|
|
}
|
|
}
|
|
}
|
|
};//end class bmpfile
|
|
}//end namespace detail
|
|
}//end namespace paint
|
|
}//end namespace nana
|
|
|
|
#endif
|