G5ImageCache.cpp
1 #include "G5ImageCache.h" 2 3 #include <SDCardManager.h> 4 5 bool G5ImageCache::compressToFile(const uint8_t* bitmap, int width, int height, const char* path) { 6 if (!bitmap || width <= 0 || height <= 0 || !path) { 7 return false; 8 } 9 10 // Validate dimensions fit in uint16_t header fields 11 if (width > UINT16_MAX || height > UINT16_MAX) { 12 return false; 13 } 14 15 const int rowBytes = (width + 7) / 8; 16 17 // Estimate buffer size - Group5 typically achieves good compression, 18 // but we allocate for worst case 19 const size_t maxCompressedSize = estimateMaxCompressedSize(width, height); 20 21 // Allocate compression buffer 22 uint8_t* compressBuffer = new (std::nothrow) uint8_t[maxCompressedSize]; 23 if (!compressBuffer) { 24 return false; 25 } 26 27 G5ENCODER encoder; 28 int result = encoder.init(width, height, compressBuffer, maxCompressedSize); 29 if (result != G5_SUCCESS) { 30 delete[] compressBuffer; 31 return false; 32 } 33 34 // Encode all rows 35 for (int y = 0; y < height; y++) { 36 result = encoder.encodeLine(const_cast<uint8_t*>(bitmap + y * rowBytes)); 37 if (result != G5_SUCCESS && result != G5_ENCODE_COMPLETE) { 38 delete[] compressBuffer; 39 return false; 40 } 41 } 42 43 const int compressedSize = encoder.size(); 44 45 // Write to file 46 FsFile outFile; 47 if (!SdMan.openFileForWrite("G5C", path, outFile)) { 48 delete[] compressBuffer; 49 return false; 50 } 51 52 // Write header 53 G5ImageHeader header; 54 header.magic = G5_MAGIC; 55 header.width = width; 56 header.height = height; 57 header.compressedSize = compressedSize; 58 59 if (outFile.write(reinterpret_cast<uint8_t*>(&header), sizeof(header)) != sizeof(header)) { 60 outFile.close(); 61 SdMan.remove(path); 62 delete[] compressBuffer; 63 return false; 64 } 65 66 // Write compressed data 67 if (outFile.write(compressBuffer, compressedSize) != static_cast<size_t>(compressedSize)) { 68 outFile.close(); 69 SdMan.remove(path); 70 delete[] compressBuffer; 71 return false; 72 } 73 74 outFile.close(); 75 delete[] compressBuffer; 76 return true; 77 } 78 79 bool G5ImageCache::decompressFromFile(const char* path, std::function<void(const uint8_t*, int, int)> rowCallback) { 80 if (!path || !rowCallback) { 81 return false; 82 } 83 84 FsFile inFile; 85 if (!SdMan.openFileForRead("G5C", path, inFile)) { 86 return false; 87 } 88 89 // Read header 90 G5ImageHeader header; 91 if (inFile.read(reinterpret_cast<uint8_t*>(&header), sizeof(header)) != sizeof(header)) { 92 inFile.close(); 93 return false; 94 } 95 96 if (header.magic != G5_MAGIC) { 97 inFile.close(); 98 return false; 99 } 100 101 const int rowBytes = (header.width + 7) / 8; 102 103 // Allocate buffers 104 uint8_t* compressedData = new (std::nothrow) uint8_t[header.compressedSize]; 105 uint8_t* rowBuffer = new (std::nothrow) uint8_t[rowBytes]; 106 107 if (!compressedData || !rowBuffer) { 108 delete[] compressedData; 109 delete[] rowBuffer; 110 inFile.close(); 111 return false; 112 } 113 114 // Read compressed data 115 if (inFile.read(compressedData, header.compressedSize) != header.compressedSize) { 116 delete[] compressedData; 117 delete[] rowBuffer; 118 inFile.close(); 119 return false; 120 } 121 inFile.close(); 122 123 // Decode 124 G5DECODER decoder; 125 int result = decoder.init(header.width, header.height, compressedData, header.compressedSize); 126 if (result != G5_SUCCESS) { 127 delete[] compressedData; 128 delete[] rowBuffer; 129 return false; 130 } 131 132 for (int y = 0; y < header.height; y++) { 133 result = decoder.decodeLine(rowBuffer); 134 if (result != G5_SUCCESS && result != G5_DECODE_COMPLETE) { 135 delete[] compressedData; 136 delete[] rowBuffer; 137 return false; 138 } 139 rowCallback(rowBuffer, rowBytes, y); 140 } 141 142 delete[] compressedData; 143 delete[] rowBuffer; 144 return true; 145 } 146 147 bool G5ImageCache::readHeader(const char* path, G5ImageHeader& header) { 148 if (!path) { 149 return false; 150 } 151 152 FsFile inFile; 153 if (!SdMan.openFileForRead("G5C", path, inFile)) { 154 return false; 155 } 156 157 if (inFile.read(reinterpret_cast<uint8_t*>(&header), sizeof(header)) != sizeof(header)) { 158 inFile.close(); 159 return false; 160 } 161 inFile.close(); 162 163 return header.magic == G5_MAGIC; 164 } 165 166 size_t G5ImageCache::estimateMaxCompressedSize(int width, int height) { 167 // Group5 can theoretically expand data in worst case (random noise) 168 // Worst case: horizontal mode with long codes for every pair 169 // Safe estimate: raw size + 50% overhead 170 const size_t rawSize = static_cast<size_t>((width + 7) / 8) * height; 171 return rawSize + (rawSize / 2) + 1024; // Extra margin for safety 172 }