/**************************************************************************** * 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 #endif #include "./readbmp.h" #include #include #include #include /* ------------------------------------------------------------------------------------------------ * 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; xbuffer); 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; x0) { 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:"", rv); GWEN_Buffer_free(buf); return NULL; } return buf; }