mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-25 15:15:40 +00:00
406 lines
13 KiB
C++
406 lines
13 KiB
C++
#ifndef INCLUDED_BMPFILE_HPP
|
|
#define INCLUDED_BMPFILE_HPP
|
|
|
|
#include "OSD/Logger.h"
|
|
#include <memory>
|
|
#include <string>
|
|
#include <cstdio>
|
|
#include <cstdint>
|
|
|
|
namespace Util
|
|
{
|
|
namespace detail
|
|
{
|
|
#pragma pack(push, 1)
|
|
|
|
struct BMPHeader
|
|
{
|
|
uint8_t id[2];
|
|
uint32_t file_size;
|
|
uint16_t reserved1;
|
|
uint16_t reserved2;
|
|
uint32_t bitmap_offset;
|
|
BMPHeader(uint32_t _file_size, uint32_t _bitmap_offset)
|
|
: file_size(_file_size),
|
|
reserved1(0),
|
|
reserved2(0),
|
|
bitmap_offset(_bitmap_offset)
|
|
{
|
|
id[0] = 'B';
|
|
id[1] = 'M';
|
|
}
|
|
};
|
|
|
|
// BITMAPV4HEADER
|
|
struct BMPInfoHeader
|
|
{
|
|
uint32_t size;
|
|
int32_t width;
|
|
int32_t height;
|
|
uint16_t num_planes;
|
|
uint16_t bits_per_pixel;
|
|
uint32_t compression_method;
|
|
uint32_t bitmap_size;
|
|
int32_t horizontal_resolution;
|
|
int32_t vertical_resolution;
|
|
uint32_t num_palette_colors;
|
|
uint32_t num_important_colors;
|
|
uint32_t red_mask;
|
|
uint32_t green_mask;
|
|
uint32_t blue_mask;
|
|
uint32_t alpha_mask;
|
|
uint32_t color_space;
|
|
uint32_t endpoints_red_x;
|
|
uint32_t endpoints_red_y;
|
|
uint32_t endpoints_red_z;
|
|
uint32_t endpoints_green_x;
|
|
uint32_t endpoints_green_y;
|
|
uint32_t endpoints_green_z;
|
|
uint32_t endpoints_blue_x;
|
|
uint32_t endpoints_blue_y;
|
|
uint32_t endpoints_blue_z;
|
|
uint32_t gamma_red;
|
|
uint32_t gamma_green;
|
|
uint32_t gamma_blue;
|
|
BMPInfoHeader(int32_t _width, int32_t _height)
|
|
: size(sizeof(BMPInfoHeader)),
|
|
width(_width),
|
|
height(_height),
|
|
num_planes(1),
|
|
bits_per_pixel(32),
|
|
compression_method(3), // BI_BITFIELDS
|
|
bitmap_size(_width*_height*4),
|
|
horizontal_resolution(2835), // 72 dpi
|
|
vertical_resolution(2835),
|
|
num_palette_colors(0),
|
|
num_important_colors(0),
|
|
red_mask(0x00ff0000),
|
|
green_mask(0x0000ff00),
|
|
blue_mask(0x000000ff),
|
|
alpha_mask(0xff000000),
|
|
color_space(1), // LCS_DEVICE_RGB
|
|
endpoints_red_x(0),
|
|
endpoints_red_y(0),
|
|
endpoints_red_z(0),
|
|
endpoints_green_x(0),
|
|
endpoints_green_y(0),
|
|
endpoints_green_z(0),
|
|
endpoints_blue_x(0),
|
|
endpoints_blue_y(0),
|
|
endpoints_blue_z(0),
|
|
gamma_red(0),
|
|
gamma_green(0),
|
|
gamma_blue(0)
|
|
{}
|
|
};
|
|
|
|
struct FileHeader
|
|
{
|
|
BMPHeader bmp_header;
|
|
BMPInfoHeader bmp_info_header;
|
|
FileHeader(int32_t width, int32_t height)
|
|
: bmp_header(sizeof(FileHeader) + width*height*3, sizeof(FileHeader)),
|
|
bmp_info_header(width, height)
|
|
{}
|
|
};
|
|
|
|
#pragma pack(pop)
|
|
}
|
|
|
|
struct RGBA8
|
|
{
|
|
static const unsigned bytes_per_pixel = 4;
|
|
static inline uint8_t GetRed(const uint8_t *pixel)
|
|
{
|
|
return pixel[0];
|
|
}
|
|
static inline uint8_t GetGreen(const uint8_t *pixel)
|
|
{
|
|
return pixel[1];
|
|
}
|
|
static inline uint8_t GetBlue(const uint8_t *pixel)
|
|
{
|
|
return pixel[2];
|
|
}
|
|
static inline uint8_t GetAlpha(const uint8_t *pixel)
|
|
{
|
|
return pixel[3];
|
|
}
|
|
};
|
|
|
|
// Texture format 0: TRRR RRGG GGGB BBBB, T = contour bit
|
|
template <bool EnableContour>
|
|
struct T1RGB5
|
|
{
|
|
static const unsigned bytes_per_pixel = 2;
|
|
static inline uint8_t GetRed(const uint8_t *pixel)
|
|
{
|
|
return uint8_t((255.0f / 31.0f) * float((*reinterpret_cast<const uint16_t *>(pixel) >> 10) & 0x1f));
|
|
}
|
|
static inline uint8_t GetGreen(const uint8_t *pixel)
|
|
{
|
|
return uint8_t((255.0f / 31.0f) * float((*reinterpret_cast<const uint16_t *>(pixel) >> 5) & 0x1f));
|
|
}
|
|
static inline uint8_t GetBlue(const uint8_t *pixel)
|
|
{
|
|
return uint8_t((255.0f / 31.0f) * float((*reinterpret_cast<const uint16_t *>(pixel) >> 0) & 0x1f));
|
|
}
|
|
static inline uint8_t GetAlpha(const uint8_t *pixel)
|
|
{
|
|
if (EnableContour)
|
|
{
|
|
bool t = (*reinterpret_cast<const uint16_t*>(pixel) >> 15) & 0x1;
|
|
return t ? uint8_t(0x00) : uint8_t(0xff); // T-bit indicates transparency
|
|
}
|
|
else
|
|
{
|
|
return 0xff; // force opaque
|
|
}
|
|
}
|
|
};
|
|
|
|
using T1RGB5ContourEnabled = T1RGB5<true>;
|
|
using T1RGB5ContourIgnored = T1RGB5<false>;
|
|
|
|
// Texture format 1: xxxx xxxx AAAA LLLL
|
|
struct A4L4Low
|
|
{
|
|
static const unsigned bytes_per_pixel = 2;
|
|
static inline uint8_t GetRed(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 0) & 0xf));
|
|
}
|
|
static inline uint8_t GetGreen(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 0) & 0xf));
|
|
}
|
|
static inline uint8_t GetBlue(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 0) & 0xf));
|
|
}
|
|
static inline uint8_t GetAlpha(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 4) & 0xf));
|
|
}
|
|
};
|
|
|
|
// Texture format 2: xxxx xxxx LLLL AAAA
|
|
struct L4A4Low
|
|
{
|
|
static const unsigned bytes_per_pixel = 2;
|
|
static inline uint8_t GetRed(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 4) & 0xf));
|
|
}
|
|
static inline uint8_t GetGreen(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 4) & 0xf));
|
|
}
|
|
static inline uint8_t GetBlue(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 4) & 0xf));
|
|
}
|
|
static inline uint8_t GetAlpha(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 0) & 0xf));
|
|
}
|
|
};
|
|
|
|
// Texture format 3: AAAA LLLL xxxx xxxx
|
|
struct A4L4High
|
|
{
|
|
static const unsigned bytes_per_pixel = 2;
|
|
static inline uint8_t GetRed(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 8) & 0xf));
|
|
}
|
|
static inline uint8_t GetGreen(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 8) & 0xf));
|
|
}
|
|
static inline uint8_t GetBlue(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 8) & 0xf));
|
|
}
|
|
static inline uint8_t GetAlpha(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 12) & 0xf));
|
|
}
|
|
};
|
|
|
|
// Texture format 4: LLLL AAAA xxxx xxxx
|
|
struct L4A4High
|
|
{
|
|
static const unsigned bytes_per_pixel = 2;
|
|
static inline uint8_t GetRed(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 12) & 0xf));
|
|
}
|
|
static inline uint8_t GetGreen(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 12) & 0xf));
|
|
}
|
|
static inline uint8_t GetBlue(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 12) & 0xf));
|
|
}
|
|
static inline uint8_t GetAlpha(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> 8) & 0xf));
|
|
}
|
|
};
|
|
|
|
// Texture format 5: xxxx xxxx LLLL LLLL, where L=0xff is transparent and L!=0xff is opaque
|
|
struct L8Low
|
|
{
|
|
static const unsigned bytes_per_pixel = 2;
|
|
static inline uint8_t GetRed(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((*reinterpret_cast<const uint16_t*>(pixel) >> 0) & 0xff);
|
|
}
|
|
static inline uint8_t GetGreen(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((*reinterpret_cast<const uint16_t*>(pixel) >> 0) & 0xff);
|
|
}
|
|
static inline uint8_t GetBlue(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((*reinterpret_cast<const uint16_t*>(pixel) >> 0) & 0xff);
|
|
}
|
|
static inline uint8_t GetAlpha(const uint8_t* pixel)
|
|
{
|
|
uint8_t l = uint8_t((*reinterpret_cast<const uint16_t*>(pixel) >> 0) & 0xff);
|
|
return l == 0xff ? 0 : 0xff;
|
|
}
|
|
};
|
|
|
|
// Texture format 6: LLLL LLLL xxxx xxxx, where L=0xff is transparent and L!=0xff is opaque
|
|
struct L8High
|
|
{
|
|
static const unsigned bytes_per_pixel = 2;
|
|
static inline uint8_t GetRed(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((*reinterpret_cast<const uint16_t*>(pixel) >> 8) & 0xff);
|
|
}
|
|
static inline uint8_t GetGreen(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((*reinterpret_cast<const uint16_t*>(pixel) >> 8) & 0xff);
|
|
}
|
|
static inline uint8_t GetBlue(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((*reinterpret_cast<const uint16_t*>(pixel) >> 8) & 0xff);
|
|
}
|
|
static inline uint8_t GetAlpha(const uint8_t* pixel)
|
|
{
|
|
uint8_t l = uint8_t((*reinterpret_cast<const uint16_t*>(pixel) >> 8) & 0xff);
|
|
return l == 0xff ? 0 : 0xff;
|
|
}
|
|
};
|
|
|
|
// Texture format 7: RRRR GGGG BBBB AAAA
|
|
struct RGBA4
|
|
{
|
|
static const unsigned bytes_per_pixel = 2;
|
|
static inline uint8_t GetRed(const uint8_t *pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t *>(pixel) >> 12) & 0xf));
|
|
}
|
|
static inline uint8_t GetGreen(const uint8_t *pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t *>(pixel) >> 8) & 0xf));
|
|
}
|
|
static inline uint8_t GetBlue(const uint8_t *pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t *>(pixel) >> 4) & 0xf));
|
|
}
|
|
static inline uint8_t GetAlpha(const uint8_t *pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t *>(pixel) >> 0) & 0xf));
|
|
}
|
|
};
|
|
|
|
// Texture format 8: xxxx xxxx xxxx LLLL
|
|
// Texture format 9: xxxx xxxx LLLL xxxx
|
|
// Texture format 10: xxxx LLLL xxxx xxxx
|
|
// Texture format 11: LLLL xxxx xxxx xxxx
|
|
template <int Channel>
|
|
struct L4
|
|
{
|
|
static const unsigned bytes_per_pixel = 2;
|
|
static inline uint8_t GetRed(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> (Channel * 4)) & 0xf));
|
|
}
|
|
static inline uint8_t GetGreen(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> (Channel * 4)) & 0xf));
|
|
}
|
|
static inline uint8_t GetBlue(const uint8_t* pixel)
|
|
{
|
|
return uint8_t((255.0f / 15.0f) * float((*reinterpret_cast<const uint16_t*>(pixel) >> (Channel * 4)) & 0xf));
|
|
}
|
|
static inline uint8_t GetAlpha(const uint8_t* pixel)
|
|
{
|
|
uint8_t l = (*reinterpret_cast<const uint16_t*>(pixel) >> (Channel * 4)) & 0xf;
|
|
return l == 0x0f ? 0x00 : 0xff;
|
|
}
|
|
};
|
|
|
|
using L4Channel0 = L4<0>;
|
|
using L4Channel1 = L4<1>;
|
|
using L4Channel2 = L4<2>;
|
|
using L4Channel3 = L4<3>;
|
|
|
|
template <class SurfaceFormat>
|
|
static bool WriteSurfaceToBMP(const std::string &file_name, const uint8_t *pixels, int32_t width, int32_t height, bool flip_vertical)
|
|
{
|
|
using namespace detail;
|
|
size_t file_size = sizeof(FileHeader) + width*height*4;
|
|
std::shared_ptr<uint8_t> file(new uint8_t[file_size], std::default_delete<uint8_t[]>());
|
|
FileHeader *header = new (file.get()) FileHeader(width, height);
|
|
uint8_t *bmp = file.get() + sizeof(*header);
|
|
if (!flip_vertical)
|
|
{
|
|
for (int32_t y = height - 1; y >= 0; y--)
|
|
{
|
|
const uint8_t *src = &pixels[y * width * SurfaceFormat::bytes_per_pixel];
|
|
for (int32_t x = 0; x < width; x++)
|
|
{
|
|
*bmp++ = SurfaceFormat::GetBlue(src);
|
|
*bmp++ = SurfaceFormat::GetGreen(src);
|
|
*bmp++ = SurfaceFormat::GetRed(src);
|
|
*bmp++ = SurfaceFormat::GetAlpha(src);
|
|
src += SurfaceFormat::bytes_per_pixel;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int32_t y = 0; y < height; y++)
|
|
{
|
|
const uint8_t *src = &pixels[y * width * SurfaceFormat::bytes_per_pixel];
|
|
for (int32_t x = 0; x < width; x++)
|
|
{
|
|
*bmp++ = SurfaceFormat::GetBlue(src);
|
|
*bmp++ = SurfaceFormat::GetGreen(src);
|
|
*bmp++ = SurfaceFormat::GetRed(src);
|
|
*bmp++ = SurfaceFormat::GetAlpha(src);
|
|
src += SurfaceFormat::bytes_per_pixel;
|
|
}
|
|
}
|
|
}
|
|
FILE *fp = fopen(file_name.c_str(), "wb");
|
|
if (fp)
|
|
{
|
|
fwrite(file.get(), sizeof(uint8_t), file_size, fp);
|
|
fclose(fp);
|
|
}
|
|
else
|
|
{
|
|
ErrorLog("Unable to open '%s' for writing.", file_name.c_str());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
} // Util
|
|
|
|
#endif // INCLUDED_BMPFILE_HPP
|