diff --git a/apps/aqhome-tool/0BUILD b/apps/aqhome-tool/0BUILD index 3247e7c..0baffb6 100644 --- a/apps/aqhome-tool/0BUILD +++ b/apps/aqhome-tool/0BUILD @@ -51,6 +51,7 @@ aqhome aqhtool_nodes aqhtool_data + aqhtool_image @@ -61,6 +62,7 @@ nodes data + image diff --git a/apps/aqhome-tool/image/0BUILD b/apps/aqhome-tool/image/0BUILD new file mode 100644 index 0000000..05d83d6 --- /dev/null +++ b/apps/aqhome-tool/image/0BUILD @@ -0,0 +1,62 @@ + + + + + + + + $(gwenhywfar_cflags) + -I$(topsrcdir) + -I$(topbuilddir) + + + + --include=$(builddir) + --include=$(srcdir) + + + $(visibility_cflags) + + + + + + + + + + + + + + + + + + readbmp.h + + + + $(local/typefiles) + + readbmp.c + + + + + + + + + + + + + + + + + + + + diff --git a/apps/aqhome-tool/image/readbmp.c b/apps/aqhome-tool/image/readbmp.c new file mode 100644 index 0000000..18de34d --- /dev/null +++ b/apps/aqhome-tool/image/readbmp.c @@ -0,0 +1,457 @@ +/**************************************************************************** + * 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 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 { + 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 lenBuffer; + 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); + lenBuffer=GWEN_Buffer_GetUsedBytes(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; xfilename=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; +} + + + + + diff --git a/apps/aqhome-tool/image/readbmp.h b/apps/aqhome-tool/image/readbmp.h new file mode 100644 index 0000000..9a68a63 --- /dev/null +++ b/apps/aqhome-tool/image/readbmp.h @@ -0,0 +1,69 @@ +/**************************************************************************** + * This file is part of the project AqHome. + * AqHome (c) by 2023 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. + ****************************************************************************/ + +#ifndef AQHOME_TOOL_READBMP_H +#define AQHOME_TOOL_READBMP_H + + +#include +#include + +#include +#include + +#include +#include +#include + + + +typedef struct BMP_FILEHEADER BMP_FILEHEADER; +struct BMP_FILEHEADER { + uint16_t fileType; + uint32_t fileSize; + uint32_t offsPixels; +}; + + +typedef struct BMP_IMAGEHEADER BMP_IMAGEHEADER; +struct BMP_IMAGEHEADER { + int32_t imgWidth; + int32_t imgHeight; + uint16_t imgPlanes; /* 1 */ + uint16_t bitsPerPixel; + uint32_t compression; + uint32_t imgSize; + uint32_t imgXPixelsPerMeter; + uint32_t imgYPixelsPerMeter; + uint32_t colorMapUsedEntries; + uint32_t colorMapImportantColors; +}; + + + +typedef struct BMP_FILE BMP_FILE; +struct BMP_FILE { + char *filename; + BMP_FILEHEADER *fileHeader; + BMP_IMAGEHEADER *imageHeader; + + GWEN_BUFFER *buffer; +}; + + +int AQH_Tool_DumpBmpFile(GWEN_DB_NODE *dbGlobalArgs, int argc, char **argv); + + +BMP_FILE *BMP_File_new(const char *fname); +void BMP_File_free(BMP_FILE *bf); +BMP_FILE *BMP_File_fromFile(const char *fname); + + + +#endif + diff --git a/apps/aqhome-tool/main.c b/apps/aqhome-tool/main.c index ef92099..eb7107a 100644 --- a/apps/aqhome-tool/main.c +++ b/apps/aqhome-tool/main.c @@ -25,6 +25,7 @@ #include "./data/watch.h" #include "./data/devicestate.h" #include "./data/imgperioddata.h" +#include "./image/readbmp.h" #include #include @@ -105,6 +106,7 @@ int main(int argc, char **argv) GWEN_FE_DAH("watch", AQH_Tool_Watch, I18N("Watch and print changes of values on the data server")), GWEN_FE_DAH("devicestate", AQH_Tool_DeviceState, I18N("Show state of devices")), GWEN_FE_DAH("imgperioddata", AQH_Tool_ImgPeriodData, I18N("Create diagram of datapoints from a date range")), + GWEN_FE_DAH("dumpbmp", AQH_Tool_DumpBmpFile, I18N("Dump headers of BMP file")), GWEN_FE_END(), }; const GWEN_FUNCS *func;