diff --git a/lib/obp60task/imglib.cpp b/lib/obp60task/imglib.cpp new file mode 100644 index 0000000..80b795e --- /dev/null +++ b/lib/obp60task/imglib.cpp @@ -0,0 +1,347 @@ +/****************************************************************************** + * Image functions: + * - Convert a 1bit framebuffer in RAM to + * - GIF, compressed, based on giflib and gif_hash + * - PBM, portable bitmap, very simple copy + * - BMP, bigger with a little bit fiddling around + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +#include // needed for PROGMEM +#include +#include "GwLog.h" // needed for logger +#include "imglib.h" + +GifFilePrivateType gifprivate; + +void ClearHashTable(GifHashTableType *HashTable) { + memset(HashTable->HTable, 0xFF, HT_SIZE * sizeof(uint32_t)); +} + +GifHashTableType *InitHashTable(void) { + GifHashTableType *HashTable; + if ((HashTable = (GifHashTableType *)ps_malloc(sizeof(GifHashTableType))) == NULL) { + return NULL; + } + ClearHashTable(HashTable); + return HashTable; +} + +static int KeyItem(uint32_t Item) { + return ((Item >> 12) ^ Item) & HT_KEY_MASK; +} + +void InsertHashTable(GifHashTableType *HashTable, uint32_t Key, int Code) { + int HKey = KeyItem(Key); + uint32_t *HTable = HashTable->HTable; + while (HT_GET_KEY(HTable[HKey]) != 0xFFFFFL) { + HKey = (HKey + 1) & HT_KEY_MASK; + } + HTable[HKey] = HT_PUT_KEY(Key) | HT_PUT_CODE(Code); +} + +int ExistsHashTable(GifHashTableType *HashTable, uint32_t Key) { + int HKey = KeyItem(Key); + uint32_t *HTable = HashTable->HTable, HTKey; + while ((HTKey = HT_GET_KEY(HTable[HKey])) != 0xFFFFFL) { + if (Key == HTKey) { + return HT_GET_CODE(HTable[HKey]); + } + HKey = (HKey + 1) & HT_KEY_MASK; + } + return -1; +} + +/****************************************************************************** + Put 2 bytes (a word) into the given file in little-endian order: +******************************************************************************/ +void GifPutWord(std::vector* gifBuffer, uint16_t Word) { + /*cuint8_t c[2]; + [0] = LOBYTE(Word); + c[1] = HIBYTE(Word); + gifBuffer->push_back(c[0]); + gifBuffer->push_back(c[1]); */ + gifBuffer->push_back(LOBYTE(Word)); + gifBuffer->push_back(HIBYTE(Word)); +} + +/****************************************************************************** + This routines buffers the given characters until 255 characters are ready + to be output. If Code is equal to -1 the buffer is flushed (EOF). + The buffer is Dumped with first byte as its size, as GIF format requires. +******************************************************************************/ +void GifBufferedOutput(std::vector* gifBuffer, GifByteType *Buf, int c) { + if (c == FLUSH_OUTPUT) { + // Flush everything out. + for (int i = 0; i < Buf[0] + 1; i++) { + gifBuffer->push_back(Buf[i]); + } + // Mark end of compressed data, by an empty block (see GIF doc): + Buf[0] = 0; + gifBuffer->push_back(0); + } else { + if (Buf[0] == 255) { + // Dump out this buffer - it is full: + for (int i = 0; i < Buf[0] + 1; i++) { + gifBuffer->push_back(Buf[i]); + } + Buf[0] = 0; + } + Buf[++Buf[0]] = c; + } +} + +/****************************************************************************** + The LZ compression output routine: + This routine is responsible for the compression of the bit stream into + 8 bits (bytes) packets. +******************************************************************************/ +void GifCompressOutput(std::vector* gifBuffer, const int Code) { + + if (Code == FLUSH_OUTPUT) { + while (gifprivate.CrntShiftState > 0) { + // Get Rid of what is left in DWord, and flush it. + GifBufferedOutput(gifBuffer, gifprivate.Buf, gifprivate.CrntShiftDWord & 0xff); + gifprivate.CrntShiftDWord >>= 8; + gifprivate.CrntShiftState -= 8; + } + gifprivate.CrntShiftState = 0; // For next time. + GifBufferedOutput(gifBuffer, gifprivate.Buf, FLUSH_OUTPUT); + } else { + gifprivate.CrntShiftDWord |= ((long)Code) << gifprivate.CrntShiftState; + gifprivate.CrntShiftState += gifprivate.RunningBits; + while (gifprivate.CrntShiftState >= 8) { + // Dump out full bytes: + GifBufferedOutput(gifBuffer, gifprivate.Buf, gifprivate.CrntShiftDWord & 0xff); + gifprivate.CrntShiftDWord >>= 8; + gifprivate.CrntShiftState -= 8; + } + } + + /* If code cannt fit into RunningBits bits, must raise its size. Note */ + /* however that codes above LZ_MAX_CODE are used for special signaling. */ + if (gifprivate.RunningCode >= gifprivate.MaxCode1 && Code <= LZ_MAX_CODE) { + gifprivate.MaxCode1 = 1 << ++gifprivate.RunningBits; + } +} + +/****************************************************************************** + Setup the LZ compression for this image: +******************************************************************************/ +void GifSetupCompress(std::vector* gifBuffer) { + gifBuffer->push_back(0x02);// Bits per pixel wit minimum 2 + + gifprivate.Buf[0] = 0; // Nothing was output yet + gifprivate.BitsPerPixel = 2; // Minimum is 2 + gifprivate.ClearCode = (1 << 2); + gifprivate.EOFCode = gifprivate.ClearCode + 1; + gifprivate.RunningCode = gifprivate.EOFCode + 1; + gifprivate.RunningBits = 2 + 1; // Number of bits per code + gifprivate.MaxCode1 = 1 << gifprivate.RunningBits; // Max. code + 1 + gifprivate.CrntCode = FIRST_CODE; // Signal that this is first one! + gifprivate.CrntShiftState = 0; // No information in CrntShiftDWord + gifprivate.CrntShiftDWord = 0; + + GifCompressOutput(gifBuffer, gifprivate.ClearCode); +} + +void createGifHeader(std::vector* gifBuffer, uint16_t width, uint16_t height) { + + // SCREEN DESCRIPTOR + gifBuffer->push_back('G'); + gifBuffer->push_back('I'); + gifBuffer->push_back('F'); + gifBuffer->push_back('8'); + gifBuffer->push_back('7'); + gifBuffer->push_back('a'); + + GifPutWord(gifBuffer, width); + GifPutWord(gifBuffer, height); + + gifBuffer->push_back(0x80 | (1 << 4)); + gifBuffer->push_back(0x00); // Index into the ColorTable for background color + gifBuffer->push_back(0x00); // Pixel Aspect Ratio + + // Colormap + gifBuffer->push_back(0xff); // Color 0 + gifBuffer->push_back(0xff); + gifBuffer->push_back(0xff); + gifBuffer->push_back(0x00); // Color 1 + gifBuffer->push_back(0x00); + gifBuffer->push_back(0x00); + + // IMAGE DESCRIPTOR + gifBuffer->push_back(DESCRIPTOR_INTRODUCER); + + GifPutWord(gifBuffer, 0); + GifPutWord(gifBuffer, 0); + GifPutWord(gifBuffer, width); + GifPutWord(gifBuffer, height); + + gifBuffer->push_back(0x00); // No colormap here , we use the global one +} + +/****************************************************************************** + The LZ compression routine: + This version compresses the given buffer Line of length LineLen. + This routine can be called a few times (one per scan line, for example), in + order to complete the whole image. +******************************************************************************/ +void GifCompressLine(std::vector* gifBuffer, const GifPixelType *Line, const int LineLen) { + int i = 0, CrntCode; + GifHashTableType *HashTable; + + HashTable = gifprivate.HashTable; + + if (gifprivate.CrntCode == FIRST_CODE) { // Its first time! + CrntCode = Line[i++]; + } else { + CrntCode = + gifprivate.CrntCode; // Get last code in compression + } + while (i < LineLen) { // Decode LineLen items + GifPixelType Pixel = Line[i++]; // Get next pixel from stream. + /* Form a new unique key to search hash table for the code + * combines CrntCode as Prefix string with Pixel as postfix + * char. + */ + int NewCode; + unsigned long NewKey = (((uint32_t)CrntCode) << 8) + Pixel; + if ((NewCode = ExistsHashTable(HashTable, NewKey)) >= 0) { + /* This Key is already there, or the string is old one, + * so simple take new code as our CrntCode: + */ + CrntCode = NewCode; + } else { + /* Put it in hash table, output the prefix code, and + * make our CrntCode equal to Pixel. + */ + GifCompressOutput(gifBuffer, CrntCode); + CrntCode = Pixel; + + /* If however the HashTable if full, we send a clear + * first and Clear the hash table. + */ + if (gifprivate.RunningCode >= LZ_MAX_CODE) { + // Time to do some clearance: + GifCompressOutput(gifBuffer, gifprivate.ClearCode); + gifprivate.RunningCode = gifprivate.EOFCode + 1; + gifprivate.RunningBits = gifprivate.BitsPerPixel + 1; + gifprivate.MaxCode1 = 1 << gifprivate.RunningBits; + ClearHashTable(HashTable); + } else { + // Put this unique key with its relative Code in hash table: + InsertHashTable(HashTable, NewKey, gifprivate.RunningCode++); + } + } + } + + // Preserve the current state of the compression algorithm: + gifprivate.CrntCode = CrntCode; + + if (gifprivate.PixelCount == 0) { + // We are done - output last Code and flush output buffers: + GifCompressOutput(gifBuffer, CrntCode); + GifCompressOutput(gifBuffer, gifprivate.EOFCode); + GifCompressOutput(gifBuffer, FLUSH_OUTPUT); + } +} + +bool createBMP(uint8_t *frameBuffer, std::vector* imageBuffer, uint16_t width, uint16_t height) { + // For BMP the line size has to be a multiple of 4 bytes. + // So padding is needed. Also the lines have to be in reverded + // order compared to plain buffer + + // BMP header for black-and-white image (1 bit per pixel) + const uint8_t bmp_header[] PROGMEM = { + // BITMAPFILEHEADER (14 Bytes) + 0x42, 0x4D, // bfType: 'BM' signature + 0x2e, 0x3d, 0x00, 0x00, // bfSize: file size in bytes + 0x00, 0x00, // bfReserved1 + 0x00, 0x00, // bfReserved2 + 0x3e, 0x00, 0x00, 0x00, // bfOffBits: offset in bytes to pixeldata + // BITMAPINFOHEADER (40 Bytes) + 0x28, 0x00, 0x00, 0x00, // biSize: DIB header size + (uint8_t)LOBYTE(width), (uint8_t)HIBYTE(width), 0x00, 0x00, // biWidth + (uint8_t)LOBYTE(height), (uint8_t)HIBYTE(height), 0x00, 0x00, // biHeight + 0x01, 0x00, // biPlanes: Number of color planes (1) + 0x01, 0x00, // biBitCount: Color depth (1 bit per pixel) + 0x00, 0x00, 0x00, 0x00, // biCompression: Compression (none) + 0xf0, 0x3c, 0x00, 0x00, // biSizeImage: Image data size (calculate) + 0x13, 0x0b, 0x00, 0x00, // biXPelsPerMeter: Horizontal resolution (2835 pixels/meter) + 0x13, 0x0b, 0x00, 0x00, // biYPelsPerMeter: Vertical resolution (2835 pixels/meter) + 0x02, 0x00, 0x00, 0x00, // biClrUsed: Colors in color palette (2) + 0x00, 0x00, 0x00, 0x00, // biClrImportant: Important colors (all) + // PALETTE: COLORTRIPLES of RGBQUAD (n * 4 Bytes) + 0x00, 0x00, 0x00, 0x00, // Color palette: Black + 0xff, 0xff, 0xff, 0x00 // Color palette: White + }; + size_t bmp_headerSize = sizeof(bmp_header); + + size_t lineSize = (width / 8); + size_t paddingSize = 0; + if (lineSize % 4 != 0) { + paddingSize = 4 - lineSize % 4; + } + size_t imageSize = bmp_headerSize + (lineSize + paddingSize) * height; + + imageBuffer->resize(imageSize); + memcpy(imageBuffer->data(), bmp_header, bmp_headerSize); + for (int y = 0; y < height; y++) { + uint8_t* srcRow = frameBuffer + (y * lineSize); + uint8_t* destRow = imageBuffer->data() + bmp_headerSize + ((height - 1 - y) * (lineSize + paddingSize)); + memcpy(destRow, srcRow, lineSize); + for (int j = 0; j < paddingSize; j++) { + destRow[lineSize + j] = 0x00; + } + } + return true; +} + +bool createPBM(uint8_t *frameBuffer, std::vector* imageBuffer, uint16_t width, uint16_t height) { + // creates binary PBM image inside imagebuffer + // returns bytesize of created image + const char pbm_header[] PROGMEM = "P4\n#Created by OBP60\n400 300\n"; + size_t pbm_headerSize = sizeof(pbm_header) - 1; // We don't want trailing zero + size_t imageSize = pbm_headerSize + width / 8 * height; + imageBuffer->resize(imageSize); + memcpy(imageBuffer->data(), pbm_header, pbm_headerSize); + memcpy(imageBuffer->data() + pbm_headerSize, frameBuffer, width / 8 * height); + return true; +} + +bool createGIF(uint8_t *framebuffer, std::vector* gifBuffer, uint16_t width, uint16_t height) { + + size_t imageSize = 0; + uint16_t bufOffset = 0; // Offset into imageBuffer for next write access + + gifprivate.HashTable = InitHashTable(); + if (gifprivate.HashTable == NULL) { + return false; + } + gifprivate.PixelCount = width * height; + + createGifHeader(gifBuffer, width, height); + + // Reset compress algorithm parameters. + GifSetupCompress(gifBuffer); + + gifBuffer->reserve(4096); // to avoid lots of alloactions + GifPixelType line[width]; + for (int y = 0; y < height; y++) { + // convert uint8_t pixels to single pixels + for (int x = 0; x < width; x++) { + int byteIndex = (y * width + x) / 8; + uint8_t bitIndex = 7 - ((y * width + x) % 8); + line[x] = (framebuffer[byteIndex] & (uint8_t)(1 << bitIndex)) == 0; + } + gifprivate.PixelCount -= width; + GifCompressLine(gifBuffer, line, width); + } + + gifBuffer->push_back(TERMINATOR_INTRODUCER); + free((GifHashTableType *)gifprivate.HashTable); + + return true; +} diff --git a/lib/obp60task/imglib.h b/lib/obp60task/imglib.h new file mode 100644 index 0000000..1299a7b --- /dev/null +++ b/lib/obp60task/imglib.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * + * imglib.h + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +#ifndef _IMGLIB_H_ +#define _IMGLIB_H_ 1 + +// extract bytes from an unsigned word +#define LOBYTE(x) ((x)&0xff) +#define HIBYTE(x) (((x) >> 8) & 0xff) + +// GIF encoding constants +#define DESCRIPTOR_INTRODUCER 0x2c +#define TERMINATOR_INTRODUCER 0x3b + +#define LZ_MAX_CODE 4095 // Biggest code possible in 12 bits +#define LZ_BITS 12 + +#define FLUSH_OUTPUT 4096 // Impossible code, to signal flush +#define FIRST_CODE 4097 // Impossible code, to signal first +#define NO_SUCH_CODE 4098 // Impossible code, to signal empty + +// magfic constants and declarations for GIF LZW + +#define HT_SIZE 8192 /* 12bits = 4096 or twice as big! */ +#define HT_KEY_MASK 0x1FFF /* 13bits keys */ +#define HT_KEY_NUM_BITS 13 /* 13bits keys */ +#define HT_MAX_KEY 8191 /* 13bits - 1, maximal code possible */ +#define HT_MAX_CODE 4095 /* Biggest code possible in 12 bits. */ + +/* The 32 bits of the long are divided into two parts for the key & code: */ +/* 1. The code is 12 bits as our compression algorithm is limited to 12bits */ +/* 2. The key is 12 bits Prefix code + 8 bit new char or 20 bits. */ +/* The key is the upper 20 bits. The code is the lower 12. */ +#define HT_GET_KEY(l) (l >> 12) +#define HT_GET_CODE(l) (l & 0x0FFF) +#define HT_PUT_KEY(l) (l << 12) +#define HT_PUT_CODE(l) (l & 0x0FFF) + +typedef unsigned char GifPixelType; +typedef unsigned char GifByteType; + +typedef struct GifHashTableType { + uint32_t HTable[HT_SIZE]; +} GifHashTableType; + +typedef struct GifFilePrivateType { + uint8_t BitsPerPixel; // Bits per pixel (Codes uses at least this + 1) + uint16_t ClearCode; // The CLEAR LZ code + uint16_t EOFCode; // The EOF LZ code + uint16_t RunningCode; // The next code algorithm can generate + uint16_t RunningBits; // The number of bits required to represent RunningCode + uint16_t MaxCode1; // 1 bigger than max. possible code, in RunningBits bits + uint16_t LastCode; // The code before the current code. + uint16_t CrntCode; // Current algorithm code + uint16_t CrntShiftState; // Number of bits in CrntShiftDWord + uint32_t CrntShiftDWord; // For bytes decomposition into codes + uint32_t PixelCount; // Number of pixels in image + GifByteType Buf[256]; // Compressed input is buffered here + GifHashTableType *HashTable; +} GifFilePrivateType; + +bool createGIF(uint8_t *framebuffer, std::vector* imageBuffer, uint16_t width, uint16_t height); +bool createBMP(uint8_t *framebuffer, std::vector* imageBuffer, uint16_t width, uint16_t height); +bool createPBM(uint8_t *framebuffer, std::vector* gifBuffer, uint16_t width, uint16_t height); + +#endif /* _IMGLIB_H */