plugins: add io_tiff

This commit is contained in:
luboslenco
2026-03-23 23:08:49 +01:00
parent 77b89e6f5e
commit 3ece2266cd
3 changed files with 461 additions and 44 deletions
@@ -1,23 +0,0 @@
MIT License
Copyright (c) 2017 Photopea
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
https://github.com/photopea/UTIF.js
File diff suppressed because one or more lines are too long
+461
View File
@@ -0,0 +1,461 @@
#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);
}