Files
armorpaint/paint/plugins/io_psd/io_psd.c
T
2026-04-16 21:17:19 +02:00

563 lines
17 KiB
C

#include "iron_array.h"
#include "iron_gpu.h"
#include <stdint.h>
#include <stdio.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);
void io_psd_import_layer(char *file_name, char *layer_name, void *tex);
static uint16_t psd_r16(uint8_t *buf, size_t pos) {
return (uint16_t)(buf[pos] << 8 | buf[pos + 1]);
}
static uint32_t psd_r32(uint8_t *buf, size_t pos) {
return (uint32_t)(buf[pos] << 24 | buf[pos + 1] << 16 | buf[pos + 2] << 8 | buf[pos + 3]);
}
static uint64_t psd_r64(uint8_t *buf, size_t pos) {
return ((uint64_t)psd_r32(buf, pos) << 32) | psd_r32(buf, pos + 4);
}
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;
}
}
}
// Assemble one pixel from planar channel buffers into RGBA8
// bufs[c] is the plane for channel c; r/g/b/a_c are channel indices (-1 = missing)
static void psd_pixel(uint8_t *dst, uint8_t **bufs, int r_c, int g_c, int b_c, int a_c, size_t src_i, uint16_t depth, uint16_t color_mode) {
uint8_t r = 0, g = 0, b = 0, a = 255;
#define CHAN8(c) ((c) >= 0 && bufs[c] ? bufs[c][src_i] : 0)
#define CHAN16(c) ((c) >= 0 && bufs[c] ? (uint8_t)(psd_r16(bufs[c], src_i) / 257) : 0)
#define CHANF(c) ((c) >= 0 && bufs[c] ? *(float *)(bufs[c] + src_i) : 0.0f)
#define CLAMP01(v) ((v) < 0.0f ? 0.0f : (v) > 1.0f ? 1.0f : (v))
if (color_mode == 1) { // Grayscale
uint8_t v = (depth == 16) ? (uint8_t)(psd_r16(bufs[0], src_i) / 257) : bufs[0][src_i];
if (depth == 32) {
float fv = *(float *)(bufs[0] + src_i);
v = (uint8_t)(CLAMP01(fv) * 255.0f);
}
r = g = b = v;
if (a_c >= 0 && bufs[a_c]) {
if (depth == 8)
a = bufs[a_c][src_i];
else if (depth == 16)
a = (uint8_t)(psd_r16(bufs[a_c], src_i) / 257);
else if (depth == 32)
a = (uint8_t)(CLAMP01(*(float *)(bufs[a_c] + src_i)) * 255.0f);
}
}
else {
if (depth == 8) {
r = CHAN8(r_c);
g = CHAN8(g_c);
b = CHAN8(b_c);
a = (a_c >= 0 && bufs[a_c]) ? bufs[a_c][src_i] : 255;
}
else if (depth == 16) {
r = CHAN16(r_c);
g = CHAN16(g_c);
b = CHAN16(b_c);
a = (a_c >= 0 && bufs[a_c]) ? (uint8_t)(psd_r16(bufs[a_c], src_i) / 257) : 255;
}
else if (depth == 32) {
r = (uint8_t)(CLAMP01(CHANF(r_c)) * 255.0f);
g = (uint8_t)(CLAMP01(CHANF(g_c)) * 255.0f);
b = (uint8_t)(CLAMP01(CHANF(b_c)) * 255.0f);
a = (a_c >= 0 && bufs[a_c]) ? (uint8_t)(CLAMP01(*(float *)(bufs[a_c] + src_i)) * 255.0f) : 255;
}
}
dst[0] = r;
dst[1] = g;
dst[2] = b;
dst[3] = a;
#undef CHAN8
#undef CHAN16
#undef CHANF
#undef CLAMP01
}
// Decode all channels for one layer/strip and assemble into an RGBA8 buffer
// bufs/chan_ids/num_chans describe the decoded planar data
static uint8_t *psd_assemble_rgba(uint32_t w, uint32_t h, uint32_t depth_bytes, uint16_t depth, uint16_t color_mode, uint8_t **bufs, int16_t *chan_ids,
uint16_t num_chans) {
int r_c = -1, g_c = -1, b_c = -1, a_c = -1;
for (uint16_t c = 0; c < num_chans; c++) {
if (chan_ids[c] == 0)
r_c = c;
else if (chan_ids[c] == 1)
g_c = c;
else if (chan_ids[c] == 2)
b_c = c;
else if (chan_ids[c] == -1)
a_c = c;
}
// Grayscale: treat channel 0 as luminance when no explicit R/G/B
if (color_mode == 1 && r_c < 0 && num_chans > 0)
r_c = 0;
size_t row_bytes = (size_t)w * depth_bytes;
uint8_t *rgba = (uint8_t *)malloc((size_t)w * h * 4);
if (!rgba)
return NULL;
for (uint32_t y = 0; y < h; y++) {
for (uint32_t x = 0; x < w; x++) {
size_t src_i = (size_t)y * row_bytes + (size_t)x * depth_bytes;
psd_pixel(rgba + ((size_t)y * w + x) * 4, bufs, r_c, g_c, b_c, a_c, src_i, depth, color_mode);
}
}
return rgba;
}
// Decode one channel plane from the current file position
static uint8_t *psd_decode_channel(uint8_t *buf, size_t buf_size, size_t *pos, uint64_t chan_len, uint32_t rows, size_t row_bytes, uint16_t version) {
if (*pos + chan_len > buf_size || chan_len < 2)
return NULL;
uint16_t comp = psd_r16(buf, *pos);
size_t data_off = *pos + 2;
size_t plane_size = rows * row_bytes;
*pos += chan_len;
uint8_t *plane = (uint8_t *)calloc(plane_size, 1);
if (!plane)
return NULL;
if (comp == 0) {
// Raw
size_t copy_len = chan_len - 2;
if (copy_len > plane_size)
copy_len = plane_size;
memcpy(plane, buf + data_off, copy_len);
}
else if (comp == 1) {
// PackBits RLE — row byte counts then data
size_t cnt_bytes = (version == 2) ? 4 : 2;
size_t rdata = data_off + (size_t)rows * cnt_bytes;
for (uint32_t row = 0; row < rows; row++) {
size_t count_pos = data_off + (size_t)row * cnt_bytes;
uint32_t rlen = (cnt_bytes == 4) ? psd_r32(buf, count_pos) : psd_r16(buf, count_pos);
if (rdata + rlen <= buf_size)
packbits_decode(buf + rdata, rlen, plane + (size_t)row * row_bytes, row_bytes);
rdata += rlen;
}
}
else {
free(plane);
return NULL; // Unsupported per-channel compression
}
return plane;
}
#define PSD_MAX_CHAN 6
typedef struct {
int32_t top, left, bottom, right;
int32_t mask_top, mask_left, mask_bottom, mask_right;
uint8_t mask_default_color;
int has_mask;
uint16_t num_channels;
int16_t chan_ids[PSD_MAX_CHAN];
uint64_t chan_lengths[PSD_MAX_CHAN]; // Includes 2-byte compression header
char name[256];
} psd_layer_t;
void *io_psd_parse(uint8_t *buf, size_t buf_size, const char *file_name) {
if (buf_size < 26)
return NULL;
if (buf[0] != '8' || buf[1] != 'B' || buf[2] != 'P' || buf[3] != 'S')
return NULL;
uint16_t version = psd_r16(buf, 4);
if (version != 1 && version != 2)
return NULL;
uint16_t channels = psd_r16(buf, 12);
uint32_t height = psd_r32(buf, 14);
uint32_t width = psd_r32(buf, 18);
uint16_t depth = psd_r16(buf, 22);
uint16_t color_mode = psd_r16(buf, 24);
if (width == 0 || height == 0)
return NULL;
size_t pos = 26;
// Section 2: Color mode data
if (pos + 4 > buf_size)
return NULL;
pos += 4 + psd_r32(buf, pos);
// Section 3: Image resources
if (pos + 4 > buf_size)
return NULL;
pos += 4 + psd_r32(buf, pos);
// Section 4: Layer and mask information
size_t section4_start = pos;
uint64_t section4_len = 0;
if (version == 2) {
if (pos + 8 > buf_size)
return NULL;
section4_len = psd_r64(buf, pos);
pos += 8;
}
else {
if (pos + 4 > buf_size)
return NULL;
section4_len = psd_r32(buf, pos);
pos += 4;
}
size_t section4_end = (version == 2) ? section4_start + 8 + section4_len : section4_start + 4 + section4_len;
uint32_t depth_bytes = (depth + 7) / 8;
// Parse layers
void *result = NULL;
int num_layers_found = 0;
if (section4_len > 0) {
// Layer info sub-section
uint64_t layer_info_len = 0;
if (version == 2) {
if (pos + 8 > buf_size)
goto fallback;
layer_info_len = psd_r64(buf, pos);
pos += 8;
}
else {
if (pos + 4 > buf_size)
goto fallback;
layer_info_len = psd_r32(buf, pos);
pos += 4;
}
if (layer_info_len == 0)
goto fallback;
size_t layer_info_end = pos + layer_info_len;
// Layer count (signed: negative means first alpha = merged transparency)
if (pos + 2 > buf_size)
goto fallback;
int16_t layer_count_raw = (int16_t)psd_r16(buf, pos);
uint16_t layer_count = (uint16_t)(layer_count_raw < 0 ? -layer_count_raw : layer_count_raw);
pos += 2;
if (layer_count == 0)
goto fallback;
psd_layer_t *layers = (psd_layer_t *)calloc(layer_count, sizeof(psd_layer_t));
if (!layers)
goto fallback;
// Pass 1: read layer records
for (uint16_t i = 0; i < layer_count; i++) {
if (pos + 18 > buf_size)
break;
layers[i].top = (int32_t)psd_r32(buf, pos);
pos += 4;
layers[i].left = (int32_t)psd_r32(buf, pos);
pos += 4;
layers[i].bottom = (int32_t)psd_r32(buf, pos);
pos += 4;
layers[i].right = (int32_t)psd_r32(buf, pos);
pos += 4;
layers[i].num_channels = psd_r16(buf, pos);
pos += 2;
if (layers[i].num_channels > PSD_MAX_CHAN)
layers[i].num_channels = PSD_MAX_CHAN;
for (uint16_t c = 0; c < layers[i].num_channels; c++) {
layers[i].chan_ids[c] = (int16_t)psd_r16(buf, pos);
pos += 2;
if (version == 2) {
layers[i].chan_lengths[c] = psd_r64(buf, pos);
pos += 8;
}
else {
layers[i].chan_lengths[c] = psd_r32(buf, pos);
pos += 4;
}
}
pos += 4; // blend mode signature "8BIM"
pos += 4; // blend mode key
pos += 1; // opacity
pos += 1; // clipping
pos += 1; // flags
pos += 1; // filler
uint32_t extra_len = psd_r32(buf, pos);
pos += 4;
size_t extra_end = pos + extra_len;
// Layer mask data
if (pos + 4 <= buf_size) {
uint32_t mask_len = psd_r32(buf, pos);
pos += 4;
if (mask_len >= 17 && pos + 17 <= buf_size) {
layers[i].mask_top = (int32_t)psd_r32(buf, pos);
layers[i].mask_left = (int32_t)psd_r32(buf, pos + 4);
layers[i].mask_bottom = (int32_t)psd_r32(buf, pos + 8);
layers[i].mask_right = (int32_t)psd_r32(buf, pos + 12);
layers[i].mask_default_color = buf[pos + 16];
layers[i].has_mask = 1;
}
pos += mask_len;
}
// Layer blending ranges
if (pos + 4 <= buf_size) {
uint32_t blend_len = psd_r32(buf, pos);
pos += 4 + blend_len;
}
// Layer name (pascal string, padded to 4-byte boundary)
if (pos < extra_end && pos < buf_size) {
uint8_t name_len = buf[pos++];
if (name_len > 255)
name_len = 255;
if (pos + name_len <= buf_size)
memcpy(layers[i].name, buf + pos, name_len);
layers[i].name[name_len] = 0;
pos += name_len;
// pad to 4-byte boundary counting from the length byte
uint32_t padded = ((uint32_t)(name_len + 1) + 3) & ~3u;
pos += padded - (name_len + 1);
}
pos = extra_end; // skip any remaining extra data
}
// Pass 2: read channel image data (immediately follows all records)
for (uint16_t i = 0; i < layer_count; i++) {
int32_t lw = layers[i].right - layers[i].left;
int32_t lh = layers[i].bottom - layers[i].top;
if (lw <= 0 || lh <= 0) {
// Skip channel data for empty/invisible layers
for (uint16_t c = 0; c < layers[i].num_channels; c++)
pos += (size_t)layers[i].chan_lengths[c];
continue;
}
size_t row_bytes = (size_t)lw * depth_bytes;
uint8_t *bufs[PSD_MAX_CHAN];
memset(bufs, 0, sizeof(bufs));
uint8_t *mask_buf = NULL;
int32_t mw = layers[i].has_mask ? (layers[i].mask_right - layers[i].mask_left) : lw;
int32_t mh = layers[i].has_mask ? (layers[i].mask_bottom - layers[i].mask_top) : lh;
int32_t mox = layers[i].has_mask ? layers[i].mask_left : layers[i].left;
int32_t moy = layers[i].has_mask ? layers[i].mask_top : layers[i].top;
size_t mask_row_bytes = (mw > 0 && mh > 0) ? (size_t)mw * depth_bytes : 0;
for (uint16_t c = 0; c < layers[i].num_channels; c++) {
if (layers[i].chan_ids[c] < -1) {
// Decode first mask channel using mask bounds, skip additional ones
if (mask_buf == NULL && mask_row_bytes > 0)
mask_buf = psd_decode_channel(buf, buf_size, &pos, layers[i].chan_lengths[c], (uint32_t)mh, mask_row_bytes, version);
else
pos += (size_t)layers[i].chan_lengths[c];
continue;
}
bufs[c] = psd_decode_channel(buf, buf_size, &pos, layers[i].chan_lengths[c], (uint32_t)lh, row_bytes, version);
}
uint8_t *layer_rgba =
psd_assemble_rgba((uint32_t)lw, (uint32_t)lh, depth_bytes, depth, color_mode, bufs, layers[i].chan_ids, layers[i].num_channels);
for (uint16_t c = 0; c < layers[i].num_channels; c++)
free(bufs[c]);
if (!layer_rgba) {
free(mask_buf);
continue;
}
// Place the layer into a full-image canvas at its (left, top) offset
uint8_t *rgba = (uint8_t *)calloc((size_t)width * height * 4, 1);
if (!rgba) {
free(layer_rgba);
free(mask_buf);
continue;
}
int32_t ox = layers[i].left;
int32_t oy = layers[i].top;
for (int32_t y = 0; y < lh; y++) {
int32_t dy = oy + y;
if (dy < 0 || dy >= (int32_t)height)
continue;
for (int32_t x = 0; x < lw; x++) {
int32_t dx = ox + x;
if (dx < 0 || dx >= (int32_t)width)
continue;
uint8_t *src = layer_rgba + ((size_t)y * lw + x) * 4;
uint8_t *dst = rgba + ((size_t)dy * width + dx) * 4;
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
}
}
free(layer_rgba);
buffer_t *b = (buffer_t *)malloc(sizeof(buffer_t));
b->buffer = rgba;
b->length = b->capacity = (uint32_t)((size_t)width * height * 4);
void *tex = gpu_create_texture_from_bytes(b, (int)width, (int)height, GPU_TEXTURE_FORMAT_RGBA32);
char *layer_name = layers[i].name[0] != 0 ? layers[i].name : "layer";
if (num_layers_found == 0) {
result = tex;
}
else {
io_psd_import_layer((char *)file_name, layer_name, tex);
}
num_layers_found++;
// Import mask as a separate grayscale texture
if (mask_buf != NULL) {
uint8_t *mask_rgba = (uint8_t *)calloc((size_t)width * height * 4, 1);
if (mask_rgba) {
for (int32_t y = 0; y < mh; y++) {
int32_t dy = moy + y;
if (dy < 0 || dy >= (int32_t)height)
continue;
for (int32_t x = 0; x < mw; x++) {
int32_t dx = mox + x;
if (dx < 0 || dx >= (int32_t)width)
continue;
size_t src_i = ((size_t)y * mw + x) * depth_bytes;
uint8_t v;
if (depth == 8)
v = mask_buf[src_i];
else if (depth == 16)
v = (uint8_t)(psd_r16(mask_buf, src_i) / 257);
else {
float fv = *(float *)(mask_buf + src_i);
v = (uint8_t)((fv < 0.0f ? 0.0f : fv > 1.0f ? 1.0f : fv) * 255.0f);
}
uint8_t *dst = mask_rgba + ((size_t)dy * width + dx) * 4;
if (layers[i].mask_default_color == 255) {
// White background, dark stroke: invert so stroke is opaque, bg transparent
dst[0] = dst[1] = dst[2] = 0;
dst[3] = (uint8_t)(255 - v);
}
else {
// Black background, light stroke
dst[0] = dst[1] = dst[2] = 255;
dst[3] = v;
}
}
}
buffer_t *mb = (buffer_t *)malloc(sizeof(buffer_t));
mb->buffer = mask_rgba;
mb->length = mb->capacity = (uint32_t)((size_t)width * height * 4);
void *mask_tex = gpu_create_texture_from_bytes(mb, (int)width, (int)height, GPU_TEXTURE_FORMAT_RGBA32);
char mask_name[256 + 5];
snprintf(mask_name, sizeof(mask_name), "%s_mask", layer_name);
io_psd_import_layer((char *)file_name, mask_name, mask_tex);
}
free(mask_buf);
}
}
free(layers);
if (result != NULL)
return result;
}
fallback:
// No layers (or all empty) — decode the merged composite image in section 5
pos = section4_end;
if (pos + 2 > buf_size)
return NULL;
uint16_t compression = psd_r16(buf, pos);
pos += 2;
if (compression != 0 && compression != 1) {
console_info("Error: This psd compression type is not yet implemented");
return NULL;
}
size_t row_bytes = (size_t)width * depth_bytes;
size_t plane_size = (size_t)height * row_bytes;
uint16_t read_chans = channels > 4 ? 4 : channels;
uint8_t *planes = (uint8_t *)calloc((size_t)read_chans * plane_size, 1);
if (!planes)
return NULL;
// Build synthetic chan_ids for the composite (RGB order: 0, 1, 2; alpha: -1)
int16_t comp_chan_ids[4] = {0, 1, 2, -1};
if (compression == 0) {
for (uint16_t c = 0; c < channels; c++) {
if (pos + plane_size > buf_size) {
free(planes);
return NULL;
}
if (c < read_chans)
memcpy(planes + (size_t)c * plane_size, buf + pos, plane_size);
pos += plane_size;
}
}
else {
size_t cnt_bytes = (version == 2) ? 4 : 2;
size_t rle_counts_pos = pos;
size_t rle_data_pos = pos + (size_t)channels * height * cnt_bytes;
for (uint16_t c = 0; c < channels; c++) {
for (uint32_t row = 0; row < height; row++) {
size_t count_pos = rle_counts_pos + ((size_t)c * height + row) * cnt_bytes;
uint32_t rlen = (cnt_bytes == 4) ? psd_r32(buf, count_pos) : psd_r16(buf, count_pos);
if (c < read_chans && rle_data_pos + rlen <= buf_size)
packbits_decode(buf + rle_data_pos, rlen, planes + (size_t)c * plane_size + (size_t)row * row_bytes, row_bytes);
rle_data_pos += rlen;
}
}
}
uint8_t *planes_ptrs[4] = {NULL, NULL, NULL, NULL};
for (uint16_t c = 0; c < read_chans; c++)
planes_ptrs[c] = planes + (size_t)c * plane_size;
uint8_t *rgba = psd_assemble_rgba(width, height, depth_bytes, depth, color_mode, planes_ptrs, comp_chan_ids, read_chans);
free(planes);
if (!rgba)
return NULL;
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);
}