luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

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  */