Files
armorpaint/paint/plugins/io_tiff/io_tiff.c
T
2026-03-23 23:08:49 +01:00

462 lines
12 KiB
C

#include "iron_array.h"
#include "iron_gpu.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
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);
}