2025-12-19 08:45:14 +11:00
|
|
|
#pragma once
|
|
|
|
|
|
2026-02-08 21:29:14 +01:00
|
|
|
#include <HalStorage.h>
|
2025-12-19 08:45:14 +11:00
|
|
|
|
2026-01-12 12:36:19 +01:00
|
|
|
#include <cstdint>
|
|
|
|
|
|
|
|
|
|
#include "BitmapHelpers.h"
|
|
|
|
|
|
2026-02-22 04:22:32 +00:00
|
|
|
#pragma pack(push, 1)
|
|
|
|
|
struct BmpHeader {
|
|
|
|
|
struct {
|
|
|
|
|
uint16_t bfType;
|
|
|
|
|
uint32_t bfSize;
|
|
|
|
|
uint16_t bfReserved1;
|
|
|
|
|
uint16_t bfReserved2;
|
|
|
|
|
uint32_t bfOffBits;
|
|
|
|
|
} fileHeader;
|
|
|
|
|
struct {
|
|
|
|
|
uint32_t biSize;
|
|
|
|
|
int32_t biWidth;
|
|
|
|
|
int32_t biHeight;
|
|
|
|
|
uint16_t biPlanes;
|
|
|
|
|
uint16_t biBitCount;
|
|
|
|
|
uint32_t biCompression;
|
|
|
|
|
uint32_t biSizeImage;
|
|
|
|
|
int32_t biXPelsPerMeter;
|
|
|
|
|
int32_t biYPelsPerMeter;
|
|
|
|
|
uint32_t biClrUsed;
|
|
|
|
|
uint32_t biClrImportant;
|
|
|
|
|
} infoHeader;
|
|
|
|
|
struct RgbQuad {
|
|
|
|
|
uint8_t rgbBlue;
|
|
|
|
|
uint8_t rgbGreen;
|
|
|
|
|
uint8_t rgbRed;
|
|
|
|
|
uint8_t rgbReserved;
|
|
|
|
|
};
|
|
|
|
|
RgbQuad colors[2];
|
|
|
|
|
};
|
|
|
|
|
#pragma pack(pop)
|
|
|
|
|
|
2025-12-19 08:45:14 +11:00
|
|
|
enum class BmpReaderError : uint8_t {
|
|
|
|
|
Ok = 0,
|
|
|
|
|
FileInvalid,
|
|
|
|
|
SeekStartFailed,
|
|
|
|
|
|
|
|
|
|
NotBMP,
|
|
|
|
|
DIBTooSmall,
|
|
|
|
|
|
|
|
|
|
BadPlanes,
|
|
|
|
|
UnsupportedBpp,
|
|
|
|
|
UnsupportedCompression,
|
|
|
|
|
|
|
|
|
|
BadDimensions,
|
2025-12-28 08:38:14 +09:00
|
|
|
ImageTooLarge,
|
2025-12-19 08:45:14 +11:00
|
|
|
PaletteTooLarge,
|
|
|
|
|
|
|
|
|
|
SeekPixelDataFailed,
|
|
|
|
|
BufferTooSmall,
|
|
|
|
|
OomRowBuffer,
|
|
|
|
|
ShortReadRow,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class Bitmap {
|
|
|
|
|
public:
|
|
|
|
|
static const char* errorToString(BmpReaderError err);
|
|
|
|
|
|
2026-01-12 12:36:19 +01:00
|
|
|
explicit Bitmap(FsFile& file, bool dithering = false) : file(file), dithering(dithering) {}
|
2025-12-28 08:38:14 +09:00
|
|
|
~Bitmap();
|
2025-12-19 08:45:14 +11:00
|
|
|
BmpReaderError parseHeaders();
|
2026-01-05 11:07:27 +01:00
|
|
|
BmpReaderError readNextRow(uint8_t* data, uint8_t* rowBuffer) const;
|
2025-12-19 08:45:14 +11:00
|
|
|
BmpReaderError rewindToData() const;
|
|
|
|
|
int getWidth() const { return width; }
|
|
|
|
|
int getHeight() const { return height; }
|
|
|
|
|
bool isTopDown() const { return topDown; }
|
|
|
|
|
bool hasGreyscale() const { return bpp > 1; }
|
|
|
|
|
int getRowBytes() const { return rowBytes; }
|
2026-01-14 19:24:02 +09:00
|
|
|
bool is1Bit() const { return bpp == 1; }
|
|
|
|
|
uint16_t getBpp() const { return bpp; }
|
2025-12-19 08:45:14 +11:00
|
|
|
|
|
|
|
|
private:
|
2025-12-30 15:09:30 +10:00
|
|
|
static uint16_t readLE16(FsFile& f);
|
|
|
|
|
static uint32_t readLE32(FsFile& f);
|
2025-12-19 08:45:14 +11:00
|
|
|
|
2025-12-30 15:09:30 +10:00
|
|
|
FsFile& file;
|
2026-01-12 12:36:19 +01:00
|
|
|
bool dithering = false;
|
2025-12-19 08:45:14 +11:00
|
|
|
int width = 0;
|
|
|
|
|
int height = 0;
|
|
|
|
|
bool topDown = false;
|
|
|
|
|
uint32_t bfOffBits = 0;
|
|
|
|
|
uint16_t bpp = 0;
|
2026-02-19 12:33:29 +01:00
|
|
|
uint32_t colorsUsed = 0;
|
|
|
|
|
bool nativePalette = false; // true if all palette entries map to native gray levels
|
2025-12-19 08:45:14 +11:00
|
|
|
int rowBytes = 0;
|
|
|
|
|
uint8_t paletteLum[256] = {};
|
2025-12-28 08:38:14 +09:00
|
|
|
|
2026-02-19 12:33:29 +01:00
|
|
|
// Dithering state (mutable for const methods)
|
2025-12-28 08:38:14 +09:00
|
|
|
mutable int16_t* errorCurRow = nullptr;
|
|
|
|
|
mutable int16_t* errorNextRow = nullptr;
|
2026-01-05 11:07:27 +01:00
|
|
|
mutable int prevRowY = -1; // Track row progression for error propagation
|
2026-01-12 12:36:19 +01:00
|
|
|
|
|
|
|
|
mutable AtkinsonDitherer* atkinsonDitherer = nullptr;
|
|
|
|
|
mutable FloydSteinbergDitherer* fsDitherer = nullptr;
|
2025-12-19 08:45:14 +11:00
|
|
|
};
|