Files
aqhomecontrol/apps/aqhome-tool/image/readbmp.c
2026-01-16 16:43:14 +01:00

653 lines
18 KiB
C

/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2025 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./readbmp.h"
#include <gwenhywfar/i18n.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/args.h>
/* ------------------------------------------------------------------------------------------------
* defs
* ------------------------------------------------------------------------------------------------
*/
#define I18S(msg) msg
#define I18N(msg) GWEN_I18N_Translate(PACKAGE, msg)
#define A_ARG GWEN_ARGS_FLAGS_HAS_ARGUMENT
#define A_END (GWEN_ARGS_FLAGS_HELP | GWEN_ARGS_FLAGS_LAST)
#define A_CHAR GWEN_ArgsType_Char
#define A_INT GWEN_ArgsType_Int
#define BMP_FILE_OFFS_FILEHEADER 0
#define BMP_FILE_OFFS_IMAGEHEADER 14
#define BMP_FILEHEADER_OFFS_TYPE 0 /* 2 bytes ("BM") */
#define BMP_FILEHEADER_OFFS_FSIZE 2 /* 4 bytes */
#define BMP_FILEHEADER_OFFS_RESERVED1 6 /* 2 bytes */
#define BMP_FILEHEADER_OFFS_RESERVED2 8 /* 2 bytes */
#define BMP_FILEHEADER_OFFS_PIXELOFFS 10 /* 4 bytes offset to begin of pixel data */
#define BMP_FILEHEADER_SIZE 14
#define BMP_IMAGEHEADER_OFFS_HSIZE 0 /* 4 bytes header size */
#define BMP_IMAGEHEADER_OFFS_WIDTH 4 /* 4 bytes */
#define BMP_IMAGEHEADER_OFFS_HEIGHT 8 /* 4 bytes */
#define BMP_IMAGEHEADER_OFFS_PLANES 12 /* 2 bytes (1) */
#define BMP_IMAGEHEADER_OFFS_BPP 14 /* 2 bytes bit per pixel */
#define BMP_IMAGEHEADER_OFFS_COMPR 16 /* 4 bytes (0=uncompressed) */
#define BMP_IMAGEHEADER_OFFS_IMGSIZE 20 /* 4 bytes (may be 0 for uncompressed data) */
#define BMP_IMAGEHEADER_OFFS_XPPM 24 /* 4 bytes X pixel per meter */
#define BMP_IMAGEHEADER_OFFS_YPPM 28 /* 4 bytes Y pixel per meter */
#define BMP_IMAGEHEADER_OFFS_COLORMAPENTRIES 32 /* 4 bytes number of color map entries actually used */
#define BMP_IMAGEHEADER_OFFS_IMPORTANT 36 /* 4 bytes number of significant colors */
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static int AQH_Tool_ReadAndDumpBmpFile(const char *fname);
static int AQH_Tool_ExportBmpFile(const char *fname);
static int _exportBmp_1bpp(const BMP_FILE *bf);
static int _exportBmp_gray8bpp(const BMP_FILE *bf);
static GWEN_BUFFER *_extractPixels_gray8bpp(const uint8_t *ptrPixels, int imageWidth, int imageHeight);
static void _printBytes_ASM(const char *sName, const uint8_t *ptrPixels, int lenPixels, int imageWidth, int imageHeight);
static GWEN_BUFFER *_rleEncode(const uint8_t *ptrPixels, int lenPixels);
static int _countRepeats(const uint8_t *ptrPixels, int lenPixels);
static BMP_FILEHEADER *_fileHeader_new();
static void _fileHeader_free(BMP_FILEHEADER *fh);
static BMP_IMAGEHEADER *_imageHeader_new();
static void _imageHeader_free(BMP_IMAGEHEADER *ih);
static void _dumpBmpFileHeader(const BMP_FILEHEADER *fh);
static void _dumpBmpImageHeader(const BMP_IMAGEHEADER *ih);
static BMP_FILEHEADER *_readFileHeaderAt(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset);
static BMP_IMAGEHEADER *_readImageHeaderAt(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset);
static uint16_t _readUint16At(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset, uint16_t defaultValue);
static uint32_t _readUint32At(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset, uint32_t defaultValue);
static int32_t _readInt32At(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset, int32_t defaultValue);
static GWEN_BUFFER *_readFileIntoBuffer(const char *fname);
/* ------------------------------------------------------------------------------------------------
* code
* ------------------------------------------------------------------------------------------------
*/
int AQH_Tool_DumpBmpFile(GWEN_DB_NODE *dbGlobalArgs, int argc, char **argv)
{
GWEN_DB_NODE *dbLocalArgs;
int rv;
const char *sFilename;
const GWEN_ARGS args[]= {
/* flags type name min max s long short_descr, long_descr */
{ A_ARG, A_CHAR, "bmpFile", 1, 1, "i", "bmpFile", I18S("BMP file to read"), NULL},
{ A_END, A_INT, "help", 0, 0, "h", "help", I18S("Show this help screen"), NULL}
};
dbLocalArgs=GWEN_DB_GetGroup(dbGlobalArgs, GWEN_DB_FLAGS_DEFAULT, "local");
rv=GWEN_Args_Check(argc, argv, 1, GWEN_ARGS_MODE_ALLOW_FREEPARAM, args, dbLocalArgs);
if (rv==GWEN_ARGS_RESULT_ERROR) {
fprintf(stderr, "ERROR: Could not parse arguments\n");
return 1;
}
else if (rv==GWEN_ARGS_RESULT_HELP) {
GWEN_BUFFER *ubuf;
ubuf=GWEN_Buffer_new(0, 1024, 0, 1);
if (GWEN_Args_Usage(args, ubuf, GWEN_ArgsOutType_Txt)) {
fprintf(stderr, "ERROR: Could not create help string\n");
return 1;
}
fprintf(stderr, "%s\n", GWEN_Buffer_GetStart(ubuf));
GWEN_Buffer_free(ubuf);
return 1;
}
sFilename=GWEN_DB_GetCharValue(dbLocalArgs, "bmpFile", 0, NULL);
if (!(sFilename && *sFilename)) {
fprintf(stderr, "Missing file name\n");
return 1;
}
//return AQH_Tool_ReadAndDumpBmpFile(sFilename);
return AQH_Tool_ExportBmpFile(sFilename);
}
int AQH_Tool_ReadAndDumpBmpFile(const char *fname)
{
BMP_FILE *bf;
bf=BMP_File_fromFile(fname);
if (bf) {
_dumpBmpFileHeader(bf->fileHeader);
_dumpBmpImageHeader(bf->imageHeader);
BMP_File_free(bf);
}
return 0;
}
int AQH_Tool_ExportBmpFile(const char *fname)
{
BMP_FILE *bf;
bf=BMP_File_fromFile(fname);
if (bf) {
int rv;
_dumpBmpFileHeader(bf->fileHeader);
_dumpBmpImageHeader(bf->imageHeader);
if (bf->imageHeader->bitsPerPixel==1)
rv=_exportBmp_1bpp(bf);
else if (bf->imageHeader->bitsPerPixel==8)
rv=_exportBmp_gray8bpp(bf);
else {
fprintf(stderr, "Invalid bits per pixel (%d)", bf->imageHeader->bitsPerPixel);
rv=2;
}
BMP_File_free(bf);
return rv;
}
return 2;;
}
int _exportBmp_1bpp(const BMP_FILE *bf)
{
const uint8_t *ptrBuffer;
uint32_t offsPixels;
const uint8_t *ptrPixels;
int imageWidth;
int imageHeight;
int rowWidthInBytes;
int columns;
int y;
ptrBuffer=(const uint8_t *) GWEN_Buffer_GetStart(bf->buffer);
offsPixels=bf->fileHeader->offsPixels;
ptrPixels=ptrBuffer+offsPixels;
imageWidth=bf->imageHeader->imgWidth;
imageHeight=bf->imageHeader->imgHeight;
columns=imageWidth/8;
rowWidthInBytes=4*((columns+3)/4); /* BMPs have multiple of 4 bytes per row! */
fprintf(stdout, "imgData: \n");
fprintf(stdout, " .dw %d, %d\n", imageWidth, imageHeight);
for (y=imageHeight-1; y>=0; y--) {
const uint8_t *rowPtr;
int x;
fprintf(stdout, " .db ");
rowPtr=ptrPixels+(y*rowWidthInBytes);
for (x=0; x<columns; x++) {
if (x)
fprintf(stdout, ", ");
fprintf(stdout, "0x%02x", rowPtr[x]^0xff);
}
fprintf(stdout, "\n");
}
return 0;
}
int _exportBmp_gray8bpp(const BMP_FILE *bf)
{
const uint8_t *ptrBuffer;
uint32_t offsPixels;
const uint8_t *ptrPixels;
int imageWidth;
int imageHeight;
GWEN_BUFFER *pixelBuf;
GWEN_BUFFER *asmBuf;
ptrBuffer=(const uint8_t *) GWEN_Buffer_GetStart(bf->buffer);
offsPixels=bf->fileHeader->offsPixels;
ptrPixels=ptrBuffer+offsPixels;
imageWidth=bf->imageHeader->imgWidth;
imageHeight=bf->imageHeader->imgHeight;
pixelBuf=_extractPixels_gray8bpp(ptrPixels, imageWidth, imageHeight);
asmBuf=_rleEncode((const uint8_t*) GWEN_Buffer_GetStart(pixelBuf), GWEN_Buffer_GetUsedBytes(pixelBuf));
_printBytes_ASM("imageData",
(const uint8_t*) GWEN_Buffer_GetStart(asmBuf), GWEN_Buffer_GetUsedBytes(asmBuf),
imageWidth, imageHeight);
fprintf(stderr, "Compression: %d -> %d bytes\n", GWEN_Buffer_GetUsedBytes(pixelBuf), GWEN_Buffer_GetUsedBytes(asmBuf));
GWEN_Buffer_free(asmBuf);
GWEN_Buffer_free(pixelBuf);
return 0;
}
GWEN_BUFFER *_extractPixels_gray8bpp(const uint8_t *ptrPixels, int imageWidth, int imageHeight)
{
int rowWidthInBytes;
int columns;
int y;
GWEN_BUFFER *destBuf;
destBuf=GWEN_Buffer_new(0, 256, 0, 1);
columns=imageWidth;
rowWidthInBytes=4*((columns+3)/4); /* BMPs have multiple of 4 bytes per row! */
for (y=imageHeight-1; y>=0; y--) {
const uint8_t *rowPtr;
int x;
uint8_t currentByte=0;
int packedPixels=0;
rowPtr=ptrPixels+(y*rowWidthInBytes);
for (x=0; x<columns; x++) {
uint8_t pixel;
uint8_t newPix;
pixel=rowPtr[x];
switch(pixel) {
case 0x00: newPix=0b01; break; /* outline color */
case 0x60: newPix=0b10; break; /* color 1 */
case 0xc0: newPix=0b11; break; /* color 2 */
case 0xff: newPix=0b00; break; /* background color */
default: newPix=0b00; break; /* background color */
}
currentByte<<=2;
currentByte|=newPix;
packedPixels++;
if (packedPixels==4) {
GWEN_Buffer_AppendByte(destBuf, currentByte);
packedPixels=0;
currentByte=0;
}
}
}
if (GWEN_Buffer_GetUsedBytes(destBuf)==0) {
GWEN_Buffer_free(destBuf);
return NULL;
}
return destBuf;
}
void _printBytes_ASM(const char *sName, const uint8_t *ptrPixels, int lenPixels, int imageWidth, int imageHeight)
{
int i;
fprintf(stdout, "%s: \n", sName);
fprintf(stdout, " .dw %d, %d ; width, height\n", imageWidth, imageHeight);
for (i=0; i<lenPixels; i++) {
uint8_t currentByte;
currentByte=ptrPixels[i];
if ((i & 15)==0) {
if (i)
fprintf(stdout, "\n");
fprintf(stdout, " .db 0x%02x", currentByte);
}
else {
fprintf(stdout, ", 0x%02x", currentByte);
}
}
fprintf(stdout, "\n");
}
GWEN_BUFFER *_rleEncode(const uint8_t *ptrPixels, int lenPixels)
{
GWEN_BUFFER *resultBuf;
GWEN_BUFFER *currentBuf;
int numCurrentBuf;
resultBuf=GWEN_Buffer_new(0, 256, 0, 1);
currentBuf=GWEN_Buffer_new(0, 128, 0, 1);
while(ptrPixels && lenPixels>0) {
int countRepeats;
countRepeats=_countRepeats(ptrPixels, lenPixels);
if (countRepeats<4) {
GWEN_Buffer_AppendByte(currentBuf, *ptrPixels);
numCurrentBuf=GWEN_Buffer_GetUsedBytes(currentBuf);
if (numCurrentBuf==127) {
GWEN_Buffer_AppendByte(resultBuf, numCurrentBuf); /* bit 7 =0 */
GWEN_Buffer_AppendBytes(resultBuf, GWEN_Buffer_GetStart(currentBuf), numCurrentBuf);
GWEN_Buffer_Reset(currentBuf);
}
ptrPixels++;
lenPixels--;
}
else {
numCurrentBuf=GWEN_Buffer_GetUsedBytes(currentBuf);
if (numCurrentBuf>0) {
GWEN_Buffer_AppendByte(resultBuf, numCurrentBuf); /* bit 7 =0 */
GWEN_Buffer_AppendBytes(resultBuf, GWEN_Buffer_GetStart(currentBuf), numCurrentBuf);
GWEN_Buffer_Reset(currentBuf);
}
GWEN_Buffer_AppendByte(resultBuf, countRepeats | 128);
GWEN_Buffer_AppendByte(resultBuf, *ptrPixels);
ptrPixels+=countRepeats;
lenPixels-=countRepeats;
}
}
numCurrentBuf=GWEN_Buffer_GetUsedBytes(currentBuf);
if (numCurrentBuf>0) {
GWEN_Buffer_AppendByte(resultBuf, numCurrentBuf); /* bit 7 =0 */
GWEN_Buffer_AppendBytes(resultBuf, GWEN_Buffer_GetStart(currentBuf), numCurrentBuf);
}
GWEN_Buffer_free(currentBuf);
if (GWEN_Buffer_GetUsedBytes(resultBuf)==0) {
GWEN_Buffer_free(resultBuf);
return NULL;
}
return resultBuf;
}
int _countRepeats(const uint8_t *ptrPixels, int lenPixels)
{
if (ptrPixels && lenPixels) {
int currentByte;
int count=1;
currentByte=*(ptrPixels++);
lenPixels--;
while(ptrPixels && lenPixels) {
if (*ptrPixels==currentByte) {
count++;
if (count==127)
return count;
}
else
return count;
ptrPixels++;
lenPixels--;
}
return count;
}
return 0;
}
BMP_FILE *BMP_File_new(const char *fname)
{
BMP_FILE *bf;
GWEN_NEW_OBJECT(BMP_FILE, bf);
bf->filename=fname?strdup(fname):NULL;
return bf;
}
void BMP_File_free(BMP_FILE *bf)
{
if (bf) {
free(bf->filename);
_imageHeader_free(bf->imageHeader);
_fileHeader_free(bf->fileHeader);
GWEN_Buffer_free(bf->buffer);
GWEN_FREE_OBJECT(bf);
}
}
BMP_FILE *BMP_File_fromFile(const char *fname)
{
BMP_FILE *bf;
const uint8_t *ptrBuffer;
uint32_t lenBuffer;
bf=BMP_File_new(fname);
bf->buffer=_readFileIntoBuffer(fname);
if (bf->buffer==NULL) {
fprintf(stderr, "Error reading bmp file \"%s\"\n", fname);
BMP_File_free(bf);
return NULL;
}
ptrBuffer=(const uint8_t *) GWEN_Buffer_GetStart(bf->buffer);
lenBuffer=GWEN_Buffer_GetUsedBytes(bf->buffer);
bf->fileHeader=_readFileHeaderAt(ptrBuffer, lenBuffer, 0);
if (bf->fileHeader==NULL) {
fprintf(stderr, "Error reading bmp file header from \"%s\"\n", fname);
BMP_File_free(bf);
return NULL;
}
bf->imageHeader=_readImageHeaderAt(ptrBuffer, lenBuffer, BMP_FILE_OFFS_IMAGEHEADER);
if (bf->imageHeader==NULL) {
fprintf(stderr, "Error reading bmp image header from \"%s\"\n", fname);
BMP_File_free(bf);
return NULL;
}
return bf;
}
BMP_FILEHEADER *_fileHeader_new()
{
BMP_FILEHEADER *fh;
GWEN_NEW_OBJECT(BMP_FILEHEADER, fh);
return fh;
}
void _fileHeader_free(BMP_FILEHEADER *fh)
{
if (fh) {
GWEN_FREE_OBJECT(fh);
}
}
BMP_IMAGEHEADER *_imageHeader_new()
{
BMP_IMAGEHEADER *ih;
GWEN_NEW_OBJECT(BMP_IMAGEHEADER, ih);
return ih;
}
void _imageHeader_free(BMP_IMAGEHEADER *ih)
{
if (ih) {
GWEN_FREE_OBJECT(ih);
}
}
void _dumpBmpFileHeader(const BMP_FILEHEADER *fh)
{
fprintf(stderr, "BMP File Header:\n");
fprintf(stderr, "- file type: %d\n", fh->fileType);
fprintf(stderr, "- file size: %d\n", fh->fileSize);
fprintf(stderr, "- Pixels at: %08x\n", fh->offsPixels);
}
void _dumpBmpImageHeader(const BMP_IMAGEHEADER *ih)
{
fprintf(stderr, "BMP Image Header:\n");
fprintf(stderr, "- dims : %d x %d (%d planes, %d bpp)\n", ih->imgWidth, ih->imgHeight, ih->imgPlanes, ih->bitsPerPixel);
fprintf(stderr, "- compression : %d\n", ih->compression);
fprintf(stderr, "- image size : %d\n", ih->imgSize);
fprintf(stderr, "- resolution : %d x %d per meter\n", ih->imgXPixelsPerMeter, ih->imgYPixelsPerMeter);
fprintf(stderr, "- used colormap: %d entries (%d important)\n", ih->colorMapUsedEntries, ih->colorMapImportantColors);
}
BMP_FILEHEADER *_readFileHeaderAt(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset)
{
BMP_FILEHEADER *fh;
if (lenBuffer<(offset+BMP_FILEHEADER_SIZE)) {
DBG_ERROR(NULL, "Offset 0x%08x out of boundary", offset);
return NULL;
}
fh=_fileHeader_new();
fh->fileType=_readUint16At(ptrBuffer, lenBuffer, offset+BMP_FILEHEADER_OFFS_TYPE, 0);
fh->fileSize=_readUint32At(ptrBuffer, lenBuffer, offset+BMP_FILEHEADER_OFFS_FSIZE, 0);
fh->offsPixels=_readUint32At(ptrBuffer, lenBuffer, offset+BMP_FILEHEADER_OFFS_PIXELOFFS, 0xffffffff);
return fh;
}
BMP_IMAGEHEADER *_readImageHeaderAt(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset)
{
BMP_IMAGEHEADER *ih;
uint32_t hsize;
hsize=_readUint32At(ptrBuffer, lenBuffer, BMP_FILE_OFFS_IMAGEHEADER, 0xffffffff);
if (lenBuffer<(offset+hsize)) {
DBG_ERROR(NULL, "Offset 0x%08x out of boundary", offset);
return NULL;
}
ih=_imageHeader_new();
ih->imgWidth=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_WIDTH, 0);
ih->imgHeight=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_HEIGHT, 0);
ih->imgPlanes=_readUint16At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_PLANES, 0);
ih->bitsPerPixel=_readUint16At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_BPP, 0);
ih->compression=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_COMPR, 0);
ih->imgSize=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_IMGSIZE, 0);
ih->imgXPixelsPerMeter=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_XPPM, 0);
ih->imgYPixelsPerMeter=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_YPPM, 0);
ih->colorMapUsedEntries=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_COLORMAPENTRIES, 0);
ih->colorMapImportantColors=_readInt32At(ptrBuffer, lenBuffer, offset+BMP_IMAGEHEADER_OFFS_IMPORTANT, 0);
return ih;
}
uint16_t _readUint16At(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset, uint16_t defaultValue)
{
uint16_t v;
if (lenBuffer<(offset+2)) {
DBG_ERROR(NULL, "Offset 0x%08x out of boundary", offset);
return defaultValue;
}
v=ptrBuffer[offset]+(ptrBuffer[offset+1]<<8);
return v;
}
uint32_t _readUint32At(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset, uint32_t defaultValue)
{
uint32_t v;
if (lenBuffer<(offset+4)) {
DBG_ERROR(NULL, "Offset 0x%08x out of boundary", offset);
return defaultValue;
}
v=ptrBuffer[offset]+(ptrBuffer[offset+1]<<8)+(ptrBuffer[offset+2]<<16)+(ptrBuffer[offset+2]<<16);
return v;
}
int32_t _readInt32At(const uint8_t *ptrBuffer, uint32_t lenBuffer, uint32_t offset, int32_t defaultValue)
{
uint32_t v;
if (lenBuffer<(offset+4)) {
DBG_ERROR(NULL, "Offset 0x%08x out of boundary", offset);
return defaultValue;
}
v=ptrBuffer[offset]+(ptrBuffer[offset+1]<<8)+(ptrBuffer[offset+2]<<16)+(ptrBuffer[offset+2]<<16);
return (int32_t) v;
}
GWEN_BUFFER *_readFileIntoBuffer(const char *fname)
{
GWEN_BUFFER *buf;
int rv;
buf=GWEN_Buffer_new(0, 1024, 0, 1);
rv=GWEN_SyncIo_Helper_ReadFile(fname, buf);
if (rv<0) {
DBG_ERROR(NULL, "Error reading file \"%s\": %d", fname?fname:"<empty>", rv);
GWEN_Buffer_free(buf);
return NULL;
}
return buf;
}