#include "iron_array.h" #include "iron_gpu.h" #include #include #include void *gpu_create_texture_from_bytes(void *buffer, int width, int height, int format); void console_info(char *s); buffer_t *iron_inflate(buffer_t *bytes, bool raw); // TIFF tags #define TAG_IMAGE_WIDTH 256 #define TAG_IMAGE_LENGTH 257 #define TAG_BITS_PER_SAMPLE 258 #define TAG_COMPRESSION 259 #define TAG_PHOTOMETRIC 262 #define TAG_STRIP_OFFSETS 273 #define TAG_SAMPLES_PER_PIXEL 277 #define TAG_ROWS_PER_STRIP 278 #define TAG_STRIP_BYTE_COUNTS 279 #define TAG_PLANAR_CONFIGURATION 284 #define TAG_PREDICTOR 317 #define TAG_COLOR_MAP 320 #define TAG_TILE_WIDTH 322 #define TAG_TILE_LENGTH 323 #define TAG_TILE_OFFSETS 324 #define TAG_TILE_BYTE_COUNTS 325 #define TAG_SAMPLE_FORMAT 339 // Compression types #define COMP_NONE 1 #define COMP_LZW 5 #define COMP_DEFLATE 8 #define COMP_PACKBITS 32773 #define COMP_DEFLATE2 32946 static bool tiff_le; static uint8_t *tiff_buf; static size_t tiff_size; static uint16_t r16(size_t pos) { uint8_t *p = tiff_buf + pos; return tiff_le ? (uint16_t)(p[0] | p[1] << 8) : (uint16_t)(p[0] << 8 | p[1]); } static uint32_t r32(size_t pos) { uint8_t *p = tiff_buf + pos; return tiff_le ? (uint32_t)(p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24) : (uint32_t)(p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]); } static uint32_t ifd_val(size_t vpos, uint16_t type, uint32_t idx) { if (type == 3 || type == 8) return r16(vpos + idx * 2); // SHORT / SSHORT if (type == 4 || type == 9) return r32(vpos + idx * 4); // LONG / SLONG return tiff_buf[vpos + idx]; // BYTE and others } static void packbits_decode(uint8_t *in, size_t in_len, uint8_t *out, size_t out_len) { size_t ip = 0, op = 0; while (ip < in_len && op < out_len) { int8_t n = (int8_t)in[ip++]; if (n >= 0) { size_t cnt = (size_t)(n + 1); if (op + cnt > out_len) cnt = out_len - op; memcpy(out + op, in + ip, cnt); ip += (size_t)(n + 1); op += cnt; } else if (n != -128) { size_t cnt = (size_t)(-n + 1); if (op + cnt > out_len) cnt = out_len - op; memset(out + op, in[ip++], cnt); op += cnt; } } } // TIFF LZW — MSB-first bit order static void lzw_decode(uint8_t *in, size_t in_len, uint8_t *out, size_t out_len) { uint16_t prefix[4096]; uint8_t suffix[4096]; uint8_t first[4096]; uint8_t stack[4096]; for (int i = 0; i < 256; i++) { prefix[i] = 0xFFFF; suffix[i] = first[i] = (uint8_t)i; } int bit_len = 9; int nxt = 258; size_t ipos = 0; size_t opos = 0; int bit_pos = 0; int old_code = -1; while (opos < out_len) { // Read bit_len bits, MSB first int code = 0; for (int b = 0; b < bit_len; b++) { if (ipos >= in_len) return; code = (code << 1) | ((in[ipos] >> (7 - bit_pos)) & 1); if (++bit_pos == 8) { bit_pos = 0; ipos++; } } if (code == 256) { bit_len = 9; nxt = 258; old_code = -1; continue; } if (code == 257) break; int stack_top = 0; int cur = code; if (code >= nxt) { // Special case: new code equals next table entry stack[stack_top++] = first[old_code]; cur = old_code; } while (prefix[cur] != 0xFFFF && stack_top < 4096) { stack[stack_top++] = suffix[cur]; cur = prefix[cur]; } stack[stack_top++] = suffix[cur]; // root / first char if (old_code >= 0 && nxt < 4096) { prefix[nxt] = (uint16_t)old_code; suffix[nxt] = stack[stack_top - 1]; // first char of current string first[nxt] = first[old_code]; nxt++; if (nxt == (1 << bit_len) && bit_len < 12) bit_len++; } for (int s = stack_top - 1; s >= 0 && opos < out_len; s--) out[opos++] = stack[s]; old_code = code; } } // Undo horizontal differencing predictor (predictor == 2) static void undo_predictor(uint8_t *data, uint32_t w, uint32_t h, uint16_t spp, uint16_t bps) { uint32_t row_bytes = w * spp * ((bps + 7) / 8); for (uint32_t y = 0; y < h; y++) { uint8_t *row = data + (size_t)y * row_bytes; if (bps == 16) { uint16_t *p = (uint16_t *)row; for (uint32_t x = 1; x < w; x++) for (uint16_t c = 0; c < spp; c++) p[x * spp + c] += p[(x - 1) * spp + c]; } else { // 8-bit for (uint32_t x = 1; x < w; x++) for (uint16_t c = 0; c < spp; c++) row[x * spp + c] += row[(x - 1) * spp + c]; } } } void *io_tiff_parse(uint8_t *buf, size_t buf_size) { tiff_buf = buf; tiff_size = buf_size; if (buf_size < 8) return NULL; if (buf[0] == 'I' && buf[1] == 'I') tiff_le = true; else if (buf[0] == 'M' && buf[1] == 'M') tiff_le = false; else return NULL; if (r16(2) != 42) return NULL; uint32_t ifd_offset = r32(4); uint32_t width = 0, height = 0; uint16_t bps = 8; uint16_t bps_arr[4] = {8, 8, 8, 8}; uint16_t compression = COMP_NONE; uint16_t photometric = 2; uint16_t spp = 3; uint16_t predictor = 1; uint32_t rows_per_strip = 0xFFFFFFFF; uint32_t tile_w = 0, tile_h = 0; uint16_t sample_fmt = 1; uint32_t *strip_offsets = NULL; uint32_t *strip_counts = NULL; uint32_t num_strips = 0; uint32_t *tile_offsets = NULL; uint32_t *tile_counts = NULL; uint32_t num_tiles = 0; uint16_t *color_map = NULL; uint32_t color_map_len = 0; static const size_t type_sizes[] = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8}; size_t pos = ifd_offset; uint16_t num_entries = r16(pos); pos += 2; for (uint16_t i = 0; i < num_entries; i++, pos += 12) { uint16_t tag = r16(pos); uint16_t type = r16(pos + 2); uint32_t count = r32(pos + 4); uint32_t vraw = r32(pos + 8); size_t tsz = (type < 13) ? type_sizes[type] : 1; size_t vpos = (tsz * count <= 4) ? (pos + 8) : vraw; switch (tag) { case TAG_IMAGE_WIDTH: width = ifd_val(vpos, type, 0); break; case TAG_IMAGE_LENGTH: height = ifd_val(vpos, type, 0); break; case TAG_COMPRESSION: compression = (uint16_t)ifd_val(vpos, type, 0); break; case TAG_PHOTOMETRIC: photometric = (uint16_t)ifd_val(vpos, type, 0); break; case TAG_SAMPLES_PER_PIXEL: spp = (uint16_t)ifd_val(vpos, type, 0); break; case TAG_PREDICTOR: predictor = (uint16_t)ifd_val(vpos, type, 0); break; case TAG_SAMPLE_FORMAT: sample_fmt = (uint16_t)ifd_val(vpos, type, 0); break; case TAG_ROWS_PER_STRIP: rows_per_strip = ifd_val(vpos, type, 0); break; case TAG_TILE_WIDTH: tile_w = ifd_val(vpos, type, 0); break; case TAG_TILE_LENGTH: tile_h = ifd_val(vpos, type, 0); break; case TAG_BITS_PER_SAMPLE: bps = (uint16_t)ifd_val(vpos, type, 0); for (uint32_t c = 0; c < count && c < 4; c++) bps_arr[c] = (uint16_t)ifd_val(vpos, type, c); break; case TAG_STRIP_OFFSETS: num_strips = count; strip_offsets = (uint32_t *)malloc(count * sizeof(uint32_t)); for (uint32_t s = 0; s < count; s++) strip_offsets[s] = ifd_val(vpos, type, s); break; case TAG_STRIP_BYTE_COUNTS: strip_counts = (uint32_t *)malloc(count * sizeof(uint32_t)); for (uint32_t s = 0; s < count; s++) strip_counts[s] = ifd_val(vpos, type, s); break; case TAG_TILE_OFFSETS: num_tiles = count; tile_offsets = (uint32_t *)malloc(count * sizeof(uint32_t)); for (uint32_t t = 0; t < count; t++) tile_offsets[t] = ifd_val(vpos, type, t); break; case TAG_TILE_BYTE_COUNTS: tile_counts = (uint32_t *)malloc(count * sizeof(uint32_t)); for (uint32_t t = 0; t < count; t++) tile_counts[t] = ifd_val(vpos, type, t); break; case TAG_COLOR_MAP: color_map_len = count; color_map = (uint16_t *)malloc(count * sizeof(uint16_t)); for (uint32_t c = 0; c < count; c++) color_map[c] = (uint16_t)ifd_val(vpos, 3, c); break; } } if (width == 0 || height == 0) return NULL; uint8_t *rgba = (uint8_t *)calloc((size_t)width * height * 4, 1); uint32_t bps_bytes = (bps + 7) / 8; bool tiled = (tile_w > 0 && tile_h > 0 && tile_offsets != NULL); uint32_t tiles_x = tiled ? (width + tile_w - 1) / tile_w : 1; uint32_t blk_cnt = tiled ? num_tiles : num_strips; for (uint32_t blk = 0; blk < blk_cnt; blk++) { uint32_t off = tiled ? tile_offsets[blk] : strip_offsets[blk]; uint32_t blen = tiled ? tile_counts[blk] : strip_counts[blk]; if (off + blen > buf_size) break; uint32_t blk_x, blk_y, blk_w, blk_h; if (tiled) { blk_x = (blk % tiles_x) * tile_w; blk_y = (blk / tiles_x) * tile_h; blk_w = tile_w; blk_h = tile_h; } else { blk_x = 0; blk_y = blk * rows_per_strip; blk_w = width; blk_h = rows_per_strip; if (blk_y + blk_h > height) blk_h = height - blk_y; } size_t row_stride = (size_t)blk_w * spp * bps_bytes; size_t expected_sz = (size_t)blk_h * row_stride; uint8_t *raw = NULL; bool raw_free = false; if (compression == COMP_NONE) { raw = buf + off; } else if (compression == COMP_PACKBITS) { raw = (uint8_t *)malloc(expected_sz); raw_free = true; packbits_decode(buf + off, blen, raw, expected_sz); } else if (compression == COMP_LZW) { raw = (uint8_t *)malloc(expected_sz); raw_free = true; lzw_decode(buf + off, blen, raw, expected_sz); } #ifdef WITH_COMPRESS else if (compression == COMP_DEFLATE || compression == COMP_DEFLATE2) { buffer_t compressed; compressed.buffer = buf + off; compressed.length = compressed.capacity = blen; buffer_t *decomp = iron_inflate(&compressed, false); raw = decomp->buffer; raw_free = false; } #endif else { console_info("Error: TIFF compression type not supported"); continue; } if (!raw) continue; if (predictor == 2 && compression != COMP_NONE) undo_predictor(raw, blk_w, blk_h, spp, bps); for (uint32_t row = 0; row < blk_h; row++) { uint32_t img_y = blk_y + row; if (img_y >= height) break; uint8_t *src = raw + row * row_stride; for (uint32_t col = 0; col < blk_w; col++) { uint32_t img_x = blk_x + col; if (img_x >= width) break; uint8_t *dst = rgba + ((size_t)img_y * width + img_x) * 4; uint8_t r = 0, g = 0, b = 0, a = 255; if (photometric == 3) { // Palette / indexed color uint32_t idx = 0; if (bps == 8) idx = src[col]; else if (bps == 4) idx = (col & 1) ? (src[col / 2] & 0x0F) : (src[col / 2] >> 4); else if (bps == 1) idx = (src[col / 8] >> (7 - col % 8)) & 1; uint32_t nc = color_map_len / 3; if (color_map && idx < nc) { r = color_map[idx] >> 8; g = color_map[nc + idx] >> 8; b = color_map[nc * 2 + idx] >> 8; } } else if (bps == 8) { uint8_t *p = src + col * spp; if (photometric <= 1) { uint8_t v = photometric == 0 ? 255 - p[0] : p[0]; r = g = b = v; a = spp >= 2 ? p[1] : 255; } else { r = p[0]; g = p[1]; b = p[2]; a = spp >= 4 ? p[3] : 255; } } else if (bps == 16) { uint16_t *p = (uint16_t *)(src + col * spp * 2); if (photometric <= 1) { uint8_t v = photometric == 0 ? 255 - p[0] / 257 : p[0] / 257; r = g = b = v; a = spp >= 2 ? p[1] / 257 : 255; } else { r = p[0] / 257; g = p[1] / 257; b = p[2] / 257; a = spp >= 4 ? p[3] / 257 : 255; } } else if (bps == 32 && sample_fmt == 3) { // 32-bit float float *p = (float *)(src + col * spp * 4); if (photometric <= 1) { float v = photometric == 0 ? 1.0f - p[0] : p[0]; v = v < 0.0f ? 0.0f : v > 1.0f ? 1.0f : v; r = g = b = (uint8_t)(v * 255.0f); a = spp >= 2 ? (uint8_t)((p[1] < 0.0f ? 0.0f : p[1] > 1.0f ? 1.0f : p[1]) * 255.0f) : 255; } else { float fr = p[0] < 0.0f ? 0.0f : p[0] > 1.0f ? 1.0f : p[0]; float fg = p[1] < 0.0f ? 0.0f : p[1] > 1.0f ? 1.0f : p[1]; float fb = p[2] < 0.0f ? 0.0f : p[2] > 1.0f ? 1.0f : p[2]; r = (uint8_t)(fr * 255.0f); g = (uint8_t)(fg * 255.0f); b = (uint8_t)(fb * 255.0f); a = spp >= 4 ? (uint8_t)((p[3] < 0.0f ? 0.0f : p[3] > 1.0f ? 1.0f : p[3]) * 255.0f) : 255; } } dst[0] = r; dst[1] = g; dst[2] = b; dst[3] = a; } } if (raw_free) free(raw); } free(strip_offsets); free(strip_counts); free(tile_offsets); free(tile_counts); free(color_map); buffer_t *b = (buffer_t *)malloc(sizeof(buffer_t)); b->buffer = rgba; b->length = b->capacity = (uint32_t)((size_t)width * height * 4); return gpu_create_texture_from_bytes(b, (int)width, (int)height, GPU_TEXTURE_FORMAT_RGBA32); }