paint: import psd layers
This commit is contained in:
@@ -0,0 +1,489 @@
|
||||
#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);
|
||||
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;
|
||||
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 + 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));
|
||||
|
||||
for (uint16_t c = 0; c < layers[i].num_channels; c++) {
|
||||
// Skip layer mask channels (-2, -3)
|
||||
if (layers[i].chan_ids[c] < -1) {
|
||||
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)
|
||||
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);
|
||||
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);
|
||||
|
||||
if (num_layers_found == 0) {
|
||||
result = tex;
|
||||
}
|
||||
else {
|
||||
char *layer_name = layers[i].name[0] != 0 ? layers[i].name : "layer";
|
||||
io_psd_import_layer((char *)file_name, layer_name, tex);
|
||||
}
|
||||
num_layers_found++;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user