decoder_PNG.c (15009B)
1 #include "decoder_PNG.h" 2 #include "compression/zlib.h" 3 #include <stdlib.h> 4 #include <string.h> 5 #include <lauxlib.h> 6 7 /* Note: This is a simplified PNG decoder. 8 * Full PNG support requires zlib decompression. 9 * For production use, consider using stb_image.h or miniz. 10 * 11 * This implementation provides: 12 * - PNG signature validation 13 * - Chunk parsing 14 * - Basic structure for future zlib integration 15 */ 16 17 /* Read big-endian values (PNG uses big-endian) */ 18 static inline uint32_t read_be32(const uint8_t* data) { 19 return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; 20 } 21 22 /* CRC32 calculation (simplified) */ 23 static uint32_t crc32_table[256]; 24 static int crc32_table_computed = 0; 25 26 static void make_crc32_table(void) { 27 for (uint32_t n = 0; n < 256; n++) { 28 uint32_t c = n; 29 for (int k = 0; k < 8; k++) { 30 if (c & 1) 31 c = 0xEDB88320L ^ (c >> 1); 32 else 33 c = c >> 1; 34 } 35 crc32_table[n] = c; 36 } 37 crc32_table_computed = 1; 38 } 39 40 static uint32_t update_crc32(uint32_t crc, const uint8_t* buf, uint32_t len) { 41 if (!crc32_table_computed) 42 make_crc32_table(); 43 44 uint32_t c = crc ^ 0xFFFFFFFFL; 45 for (uint32_t n = 0; n < len; n++) { 46 c = crc32_table[(c ^ buf[n]) & 0xFF] ^ (c >> 8); 47 } 48 return c ^ 0xFFFFFFFFL; 49 } 50 51 /* Paeth predictor for PNG filtering */ 52 static inline uint8_t paeth_predictor(uint8_t a, uint8_t b, uint8_t c) { 53 int p = (int)a + (int)b - (int)c; 54 int pa = abs(p - (int)a); 55 int pb = abs(p - (int)b); 56 int pc = abs(p - (int)c); 57 if (pa <= pb && pa <= pc) return a; 58 if (pb <= pc) return b; 59 return c; 60 } 61 62 /* Parse PNG chunk */ 63 static int png_read_chunk(const uint8_t* data, uint32_t offset, uint32_t data_size, png_chunk_t* chunk) { 64 if (offset + 12 > data_size) return -1; // Need at least 12 bytes for chunk 65 66 chunk->length = read_be32(data + offset); 67 chunk->type = read_be32(data + offset + 4); 68 69 if (offset + 12 + chunk->length > data_size) return -1; // Not enough data 70 71 chunk->data = (uint8_t*)(data + offset + 8); 72 chunk->crc = read_be32(data + offset + 8 + chunk->length); 73 74 return offset + 12 + chunk->length; // Return next offset 75 } 76 77 /* Parse IHDR chunk */ 78 static int png_parse_ihdr(png_context_t* ctx, const uint8_t* data, uint32_t length) { 79 if (length < 13) return -1; 80 81 ctx->ihdr.width = read_be32(data); 82 ctx->ihdr.height = read_be32(data + 4); 83 ctx->ihdr.bit_depth = data[8]; 84 ctx->ihdr.color_type = data[9]; 85 ctx->ihdr.compression_method = data[10]; 86 ctx->ihdr.filter_method = data[11]; 87 ctx->ihdr.interlace_method = data[12]; 88 89 /* Validate */ 90 if (ctx->ihdr.width == 0 || ctx->ihdr.height == 0) return -1; 91 if (ctx->ihdr.compression_method != 0) return -1; // Only deflate supported 92 if (ctx->ihdr.filter_method != 0) return -1; 93 94 return 0; 95 } 96 97 /* Decode PNG from memory */ 98 image_t* png_decode(const uint8_t* data, uint32_t data_size) { 99 if (!data || data_size < PNG_SIGNATURE_SIZE + 12) return NULL; 100 101 /* Check PNG signature */ 102 if (memcmp(data, PNG_SIGNATURE, PNG_SIGNATURE_SIZE) != 0) { 103 return NULL; 104 } 105 106 /* Initialize context */ 107 png_context_t ctx; 108 memset(&ctx, 0, sizeof(png_context_t)); 109 110 /* Parse chunks */ 111 uint32_t offset = PNG_SIGNATURE_SIZE; 112 int found_ihdr = 0; 113 int found_iend = 0; 114 115 while (offset < data_size && !found_iend) { 116 png_chunk_t chunk; 117 int next_offset = png_read_chunk(data, offset, data_size, &chunk); 118 if (next_offset < 0) break; 119 120 switch (chunk.type) { 121 case PNG_CHUNK_IHDR: 122 if (png_parse_ihdr(&ctx, chunk.data, chunk.length) == 0) { 123 found_ihdr = 1; 124 } 125 break; 126 127 case PNG_CHUNK_PLTE: 128 /* Store palette for indexed color images */ 129 ctx.palette = chunk.data; 130 ctx.palette_size = chunk.length; 131 break; 132 133 case PNG_CHUNK_IDAT: 134 /* Accumulate compressed image data (may be split across multiple chunks) */ 135 if (chunk.length > 0) { 136 uint32_t new_capacity = ctx.image_data_size + chunk.length; 137 if (new_capacity > ctx.image_data_capacity) { 138 ctx.image_data_capacity = new_capacity * 2; 139 uint8_t* new_data = (uint8_t*)realloc(ctx.image_data, ctx.image_data_capacity); 140 if (!new_data) { 141 if (ctx.image_data) free(ctx.image_data); 142 return NULL; 143 } 144 ctx.image_data = new_data; 145 } 146 memcpy(ctx.image_data + ctx.image_data_size, chunk.data, chunk.length); 147 ctx.image_data_size += chunk.length; 148 } 149 break; 150 151 case PNG_CHUNK_IEND: 152 found_iend = 1; 153 break; 154 155 default: 156 /* Skip unknown chunks */ 157 break; 158 } 159 160 offset = next_offset; 161 } 162 163 if (!found_ihdr || !found_iend) { 164 if (ctx.image_data) free(ctx.image_data); 165 return NULL; 166 } 167 168 /* Check if we have IDAT data */ 169 if (!ctx.image_data || ctx.image_data_size == 0) { 170 if (ctx.image_data) free(ctx.image_data); 171 return NULL; 172 } 173 174 /* Decompress the IDAT data using zlib */ 175 /* First, estimate decompressed size (width * height * bytes_per_pixel + height for filter bytes) */ 176 uint32_t estimated_size = ctx.ihdr.width * ctx.ihdr.height * 4 + ctx.ihdr.height; 177 uint8_t* decompressed_data = (uint8_t*)malloc(estimated_size); 178 if (!decompressed_data) { 179 free(ctx.image_data); 180 return NULL; 181 } 182 183 uint32_t decompressed_size = estimated_size; 184 int result = uncompress(decompressed_data, &decompressed_size, ctx.image_data, ctx.image_data_size); 185 free(ctx.image_data); // Don't need compressed data anymore 186 187 if (result != Z_OK) { 188 free(decompressed_data); 189 return NULL; 190 } 191 192 /* Calculate expected size */ 193 uint32_t bytes_per_pixel; 194 switch (ctx.ihdr.color_type) { 195 case PNG_COLOR_GRAYSCALE: 196 bytes_per_pixel = 1; 197 break; 198 case PNG_COLOR_RGB: 199 bytes_per_pixel = 3; 200 break; 201 case PNG_COLOR_PALETTE: 202 bytes_per_pixel = 1; 203 break; 204 case PNG_COLOR_GRAYSCALE_ALPHA: 205 bytes_per_pixel = 2; 206 break; 207 case PNG_COLOR_RGBA: 208 bytes_per_pixel = 4; 209 break; 210 default: 211 { 212 extern void terminal_writestring(const char*); 213 terminal_writestring("PNG: Unknown color type\n"); 214 free(decompressed_data); 215 return NULL; 216 } 217 } 218 219 /* Account for bit depth */ 220 if (ctx.ihdr.bit_depth != 8) { 221 /* Only 8-bit depth supported for now */ 222 extern void terminal_writestring(const char*); 223 terminal_writestring("PNG: Only 8-bit depth supported\n"); 224 free(decompressed_data); 225 return NULL; 226 } 227 228 /* Each scanline has: filter_type (1 byte) + pixel_data */ 229 uint32_t stride = ctx.ihdr.width * bytes_per_pixel; 230 uint32_t expected_size = ctx.ihdr.height * (1 + stride); 231 232 if (decompressed_size < expected_size) { 233 extern void terminal_writestring(const char*); 234 char buf[128]; 235 extern int snprintf(char*, size_t, const char*, ...); 236 snprintf(buf, sizeof(buf), "PNG: Decompressed size mismatch: got %u, expected %u\n", 237 decompressed_size, expected_size); 238 terminal_writestring(buf); 239 free(decompressed_data); 240 return NULL; 241 } 242 243 /* Create output image */ 244 image_t* img = image_create(ctx.ihdr.width, ctx.ihdr.height, 245 (bytes_per_pixel == 4 || bytes_per_pixel == 2) ? 32 : 24); 246 if (!img) { 247 free(decompressed_data); 248 return NULL; 249 } 250 251 /* Allocate buffer for current scanline (we'll reverse filters in-place) */ 252 uint8_t* scanline_buffer = (uint8_t*)malloc(stride); 253 uint8_t* prev_scanline = (uint8_t*)malloc(stride); 254 if (!scanline_buffer || !prev_scanline) { 255 if (scanline_buffer) free(scanline_buffer); 256 if (prev_scanline) free(prev_scanline); 257 free(decompressed_data); 258 image_destroy(img); 259 return NULL; 260 } 261 memset(prev_scanline, 0, stride); 262 263 /* Apply PNG filters and convert to RGB/RGBA */ 264 uint8_t* src = decompressed_data; 265 for (uint32_t y = 0; y < ctx.ihdr.height; y++) { 266 uint8_t filter_type = *src++; 267 memcpy(scanline_buffer, src, stride); 268 269 /* Reverse the filter for this scanline */ 270 switch (filter_type) { 271 case PNG_FILTER_NONE: 272 /* No filtering */ 273 break; 274 275 case PNG_FILTER_SUB: 276 /* Each byte is the difference from the byte to its left */ 277 for (uint32_t i = bytes_per_pixel; i < stride; i++) { 278 scanline_buffer[i] = (scanline_buffer[i] + scanline_buffer[i - bytes_per_pixel]) & 0xFF; 279 } 280 break; 281 282 case PNG_FILTER_UP: 283 /* Each byte is the difference from the byte above it */ 284 for (uint32_t i = 0; i < stride; i++) { 285 scanline_buffer[i] = (scanline_buffer[i] + prev_scanline[i]) & 0xFF; 286 } 287 break; 288 289 case PNG_FILTER_AVERAGE: 290 /* Each byte is the difference from the average of left and above */ 291 for (uint32_t i = 0; i < stride; i++) { 292 uint8_t left = (i >= bytes_per_pixel) ? scanline_buffer[i - bytes_per_pixel] : 0; 293 uint8_t above = prev_scanline[i]; 294 scanline_buffer[i] = (scanline_buffer[i] + ((left + above) / 2)) & 0xFF; 295 } 296 break; 297 298 case PNG_FILTER_PAETH: 299 /* Each byte is the difference from Paeth predictor of left, above, upper-left */ 300 for (uint32_t i = 0; i < stride; i++) { 301 uint8_t left = (i >= bytes_per_pixel) ? scanline_buffer[i - bytes_per_pixel] : 0; 302 uint8_t above = prev_scanline[i]; 303 uint8_t upper_left = (i >= bytes_per_pixel) ? prev_scanline[i - bytes_per_pixel] : 0; 304 scanline_buffer[i] = (scanline_buffer[i] + paeth_predictor(left, above, upper_left)) & 0xFF; 305 } 306 break; 307 308 default: 309 /* Unknown filter type - treat as no filter */ 310 break; 311 } 312 313 /* Now convert the filtered scanline to RGB/RGBA and write to image */ 314 for (uint32_t x = 0; x < ctx.ihdr.width; x++) { 315 uint8_t r, g, b, a = 255; 316 317 /* Read pixel based on color type */ 318 switch (ctx.ihdr.color_type) { 319 case PNG_COLOR_GRAYSCALE: 320 r = g = b = scanline_buffer[x]; 321 break; 322 323 case PNG_COLOR_RGB: 324 r = scanline_buffer[x * 3]; 325 g = scanline_buffer[x * 3 + 1]; 326 b = scanline_buffer[x * 3 + 2]; 327 break; 328 329 case PNG_COLOR_RGBA: 330 r = scanline_buffer[x * 4]; 331 g = scanline_buffer[x * 4 + 1]; 332 b = scanline_buffer[x * 4 + 2]; 333 a = scanline_buffer[x * 4 + 3]; 334 break; 335 336 case PNG_COLOR_GRAYSCALE_ALPHA: 337 r = g = b = scanline_buffer[x * 2]; 338 a = scanline_buffer[x * 2 + 1]; 339 break; 340 341 case PNG_COLOR_PALETTE: 342 /* Lookup in palette if available */ 343 if (ctx.palette && (uint32_t)(scanline_buffer[x] * 3 + 2) < ctx.palette_size) { 344 r = ctx.palette[scanline_buffer[x] * 3]; 345 g = ctx.palette[scanline_buffer[x] * 3 + 1]; 346 b = ctx.palette[scanline_buffer[x] * 3 + 2]; 347 } else { 348 r = g = b = scanline_buffer[x]; 349 } 350 break; 351 352 default: 353 r = g = b = 0; 354 break; 355 } 356 357 image_set_pixel(img, x, y, r, g, b, a); 358 } 359 360 /* Copy current scanline to prev_scanline for next iteration */ 361 memcpy(prev_scanline, scanline_buffer, stride); 362 src += stride; 363 } 364 365 free(scanline_buffer); 366 free(prev_scanline); 367 368 free(decompressed_data); 369 return img; 370 } 371 372 /* Load PNG from file (stub - would need filesystem) */ 373 image_t* png_load_file(const char* filename) { 374 (void)filename; /* Unused parameter */ 375 /* TODO: Implement file loading once filesystem is available */ 376 return NULL; 377 } 378 379 /* Encode image to PNG format (stub) */ 380 int png_encode(image_t* img, uint8_t** out_data, uint32_t* out_size) { 381 (void)img; /* Unused parameter */ 382 (void)out_data; /* Unused parameter */ 383 (void)out_size; /* Unused parameter */ 384 /* TODO: Implement PNG encoding with zlib compression */ 385 /* Recommended: Use stb_image_write.h */ 386 return -1; 387 } 388 389 /* Save PNG to file (stub - would need filesystem) */ 390 int png_save_file(image_t* img, const char* filename) { 391 (void)img; /* Unused parameter */ 392 (void)filename; /* Unused parameter */ 393 /* TODO: Implement file saving once filesystem is available */ 394 return -1; 395 } 396 397 /* Lua binding: Load PNG from memory */ 398 int lua_png_load(lua_State* L) { 399 size_t data_size; 400 const uint8_t* data = (const uint8_t*)luaL_checklstring(L, 1, &data_size); 401 402 image_t* img = png_decode(data, data_size); 403 404 if (img) { 405 lua_pushlightuserdata(L, img); 406 return 1; 407 } else { 408 lua_pushnil(L); 409 lua_pushstring(L, "PNG decoding failed - check image format and compression"); 410 return 2; 411 } 412 } 413 414 /* Lua binding: Save image as PNG 415 int lua_png_save(lua_State* L) { 416 lua_pushnil(L); 417 lua_pushstring(L, "PNG encoding not implemented - use BMP format or integrate stb_image_write.h"); 418 return 2; 419 } 420 */ 421 /* 422 * INTEGRATION NOTES: 423 * 424 * To add full PNG support, you have several options: 425 * 426 * 1. STB Image (Recommended - easiest): 427 * - Download stb_image.h from https://github.com/nothings/stb 428 * - Single header file, public domain 429 * - Add: #define STB_IMAGE_IMPLEMENTATION 430 * #include "stb_image.h" 431 * - Replace png_decode() with: 432 * int w, h, channels; 433 * uint8_t* pixels = stbi_load_from_memory(data, data_size, &w, &h, &channels, 0); 434 * 435 * 2. Miniz: 436 * - Lightweight zlib replacement 437 * - Add miniz.c to project 438 * - Use for IDAT decompression 439 * 440 * 3. Full zlib: 441 * - More complete but larger 442 * - Cross-compile for i686 443 * - Link against libz 444 */