diff options
author | jan.dudek | 2008-12-14 21:58:11 +0000 |
---|---|---|
committer | jan.dudek | 2008-12-14 21:58:11 +0000 |
commit | de21a479786b7733dbcadb422e62b447a87fe7d1 (patch) | |
tree | 267aa58a01e437b4fc79125cdb3a2956bbb25cd7 | |
parent | a6f8b9b9eb1f46998d5159daa25381a4b36e9ac6 (diff) | |
download | gdk-pixbuf-psd-de21a479786b7733dbcadb422e62b447a87fe7d1.tar.gz gdk-pixbuf-psd-de21a479786b7733dbcadb422e62b447a87fe7d1.tar.bz2 |
Progressive loading is working
git-svn-id: http://gdk-pixbuf-psd.googlecode.com/svn/trunk@3 c5539ac3-5556-0410-9a1f-7faf0b045682
-rw-r--r-- | io-psd.c | 472 |
1 files changed, 459 insertions, 13 deletions
@@ -20,8 +20,17 @@ * Boston, MA 02111-1307, USA. */ +/* + * TODO + * - use http://library.gnome.org/devel/glib/unstable/glib-Byte-Order-Macros.html + * - report errors from parse_psd_header + * - other color modes + * - ... + */ + #include <stdlib.h> #include <stdio.h> +#include <string.h> #include <gdk-pixbuf/gdk-pixbuf-io.h> #include <glib/gstdio.h> @@ -31,13 +40,15 @@ typedef struct guchar signature[4]; /* file ID, always "8BPS" */ guint16 version; /* version number, always 1 */ guchar reserved[6]; - guint8 channels; /* number of color channels (1-24) */ + guint16 channels; /* number of color channels (1-24) */ guint32 rows; /* height of image in pixels (1-30000) */ guint32 columns; /* width of image in pixels (1-30000) */ guint16 depth; /* number of bits per channel (1, 8, and 16) */ guint16 color_mode; /* color mode as defined below */ } PsdHeader; +#define PSD_HEADER_SIZE 26 + typedef enum { PSD_MODE_MONO = 0, @@ -56,6 +67,49 @@ typedef enum PSD_COMPRESSION_RLE = 1 } PsdCompressionType; +typedef enum +{ + PSD_STATE_HEADER, + PSD_STATE_COLOR_MODE_BLOCK, + PSD_STATE_RESOURCES_BLOCK, + PSD_STATE_LAYERS_BLOCK, + PSD_STATE_COMPRESSION, + PSD_STATE_LINES_LENGTHS, + PSD_STATE_CHANNEL_DATA, + PSD_STATE_DONE +} PsdReadState; + +typedef struct +{ + PsdReadState state; + + GdkPixbuf* pixbuf; + + GdkPixbufModuleSizeFunc size_func; + GdkPixbufModuleUpdatedFunc updated_func; + GdkPixbufModulePreparedFunc prepared_func; + gpointer user_data; + + guchar* buffer; + guint bytes_read; + guint32 bytes_to_skip; + gboolean bytes_to_skip_known; + + //PsdHeader hd; + guint32 width; /* width of image in pixels (1-30000) */ + guint32 height; /* height of image in pixels (1-30000) */ + guint16 channels; /* number of color channels (1-24) */ + guint16 depth; /* number of bits per channel (1, 8, and 16) */ + PsdColorMode color_mode; + PsdCompressionType compression; + + guchar** channels_buffers; + guint current_channel; + guint current_row; + guint position; // ? redundant? + guint16* lines_lengths; +} PsdContext; + static guint16 read_uint16 (FILE *fp) { @@ -76,6 +130,19 @@ read_uint32 (FILE *fp) return t; } +static guint16 +parse_uint16 (guchar* buf) +{ + return (buf[0] << 8) | buf[1]; +} + +static guint32 +parse_uint32 (guchar* buf) +{ + return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; +} + + static PsdHeader psd_read_header (FILE *fp) { @@ -106,6 +173,28 @@ psd_read_header (FILE *fp) return hd; } +/* + * Parse Psdheader from buffer + * str is expected to be at least PSD_HEADER_SIZE long + */ +static PsdHeader +psd_parse_header (guchar* str) +{ + PsdHeader hd; + + memcpy(hd.signature, str, 4); + hd.version = parse_uint16(str + 4); + hd.channels = parse_uint16(str + 12); + hd.rows = parse_uint32(str + 14); + hd.columns = parse_uint32(str + 18); + hd.depth = parse_uint16(str + 22); + hd.color_mode = parse_uint16(str + 24); + + return hd; +} + +// -- non-progressive loading -------------------------------------------------- + static GdkPixbuf* gdk_pixbuf__psd_image_load (FILE *fp, GError **error) @@ -117,7 +206,7 @@ gdk_pixbuf__psd_image_load (FILE *fp, guchar **buffers; PsdHeader hd = psd_read_header(fp); - pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, hd.columns, hd.rows); + pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, hd.columns, hd.rows); if (pixbuf == NULL) { g_set_error (error, GDK_PIXBUF_ERROR, @@ -167,7 +256,7 @@ gdk_pixbuf__psd_image_load (FILE *fp, gchar byte = fgetc(fp); ++bytes_read; - if (byte == -128) { + if (byte == -128) { continue; } else if (byte > -1) { gint count = byte + 1; @@ -199,9 +288,9 @@ gdk_pixbuf__psd_image_load (FILE *fp, if (hd.color_mode == PSD_MODE_RGB) { for (int i = 0; i < hd.rows; ++i) { for (int j = 0; j < hd.columns; ++j) { - pixels[i * rowstride + 3 * j + 0] = buffers[0][i * hd.columns + j]; - pixels[i * rowstride + 3 * j + 1] = buffers[1][i * hd.columns + j]; - pixels[i * rowstride + 3 * j + 2] = buffers[2][i * hd.columns + j]; + pixels[i*rowstride + 3*j + 0] = buffers[0][i*hd.columns + j]; + pixels[i*rowstride + 3*j + 1] = buffers[1][i*hd.columns + j]; + pixels[i*rowstride + 3*j + 2] = buffers[2][i*hd.columns + j]; } } } @@ -210,16 +299,373 @@ gdk_pixbuf__psd_image_load (FILE *fp, return pixbuf; } + +// --- progressive loading ----------------------------------------------------- + +// Attempts to read bytes_needed bytes from data and stores them +// in buffer. +// Returns true if there were enough bytes and false otherwise +// (which means we need to call feed_buffer again) + +static gboolean +feed_buffer (guchar* buffer, + guint* bytes_read, + const guchar** data, + guint* size, + guint bytes_needed) +{ + gint how_many = bytes_needed - *bytes_read; + if (how_many > *size) { + how_many = *size; + } + memcpy(buffer + *bytes_read, *data, how_many); + *bytes_read += how_many; + *data += how_many; + *size -= how_many; + return (*bytes_read == bytes_needed); +} + +// Attempts to read size of the block and then skip this block. +// Returns true when finishes consuming block data, otherwise false +// (false means we must call skip_block once again) + +static gboolean +skip_block (PsdContext* context, const guchar** data, guint* size) +{ + static guint counter; + + if (!context->bytes_to_skip_known) { + context->bytes_read = 0; + if (feed_buffer(context->buffer, &context->bytes_read, data, size, 4)) { + context->bytes_to_skip = parse_uint32(context->buffer); + context->bytes_to_skip_known = TRUE; + counter = 0; + } else { + return FALSE; + } + } + if (*size < context->bytes_to_skip) { + *data += *size; + context->bytes_to_skip -= *size; + counter += *size; + *size = 0; + return FALSE; + } else { + counter += context->bytes_to_skip; + *size -= context->bytes_to_skip; + *data += context->bytes_to_skip; + return TRUE; + } +} + +// Decodes RLE-compressed data +static void +decompress_line(const guchar* src, guint line_length, guchar* dest) +{ + guint16 bytes_read = 0; + while (bytes_read < line_length) { + gchar byte = src[bytes_read]; + ++bytes_read; + + if (byte == -128) { + continue; + } else if (byte > -1) { + gint count = byte + 1; + + // copy next count bytes + for (int k = 0; k < count; ++k) { + *dest = src[bytes_read]; + ++dest; + ++bytes_read; + } + } else { + gint count = -byte + 1; + + // copy next byte count times + guchar next_byte = src[bytes_read]; + ++bytes_read; + for (int k = 0; k < count; ++k) { + *dest = next_byte; + ++dest; + } + } + } +} + +static void +reset_context(PsdContext* ctx) +{ + ctx->bytes_read = 0; + ctx->bytes_to_skip = 0; + ctx->bytes_to_skip_known = FALSE; +} + +static gpointer +gdk_pixbuf__psd_image_begin_load (GdkPixbufModuleSizeFunc size_func, + GdkPixbufModulePreparedFunc prepared_func, + GdkPixbufModuleUpdatedFunc updated_func, + gpointer user_data, + GError **error) +{ + PsdContext* context = g_malloc(sizeof(PsdContext)); + if (context == NULL) { + g_set_error ( + error, + GDK_PIXBUF_ERROR, + GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, + ("Not enough memory")); + return NULL; + } + context->size_func = size_func; + context->prepared_func = prepared_func; + context->updated_func = updated_func; + context->user_data = user_data; + + context->state = PSD_STATE_HEADER; + + // we'll allocate larger buffer once we know image size + context->buffer = g_malloc(256); + reset_context(context); + + //context->bytes_read = 0; + //context->bytes_to_skip = 0; + //context->bytes_to_skip_known = FALSE; + + context->channels_buffers = NULL; + context->current_channel = 0; + context->current_row = 0; + context->position = 0; + context->lines_lengths = NULL; + + return (gpointer) context; +} + +static gboolean +gdk_pixbuf__psd_image_stop_load (gpointer context_ptr, GError **error) +{ + PsdContext *ctx = (PsdContext *) context_ptr; + gboolean retval = TRUE; + + if (ctx->state != PSD_STATE_DONE) { + g_set_error ( + error, + GDK_PIXBUF_ERROR, + GDK_PIXBUF_ERROR_CORRUPT_IMAGE, + ("PSD file was corrupted or incomplete.")); + retval = FALSE; + } else { + // convert or copy channel buffers to our GdkPixbuf + + //for (int i = 0; i < ctx->channels; i++) { + guchar* pixels = gdk_pixbuf_get_pixels(ctx->pixbuf); + for (int i = 0; i < ctx->height; i++) { + for (int j = 0; j < ctx->width; j++) { + pixels[3*j+0] = 0x00; + pixels[3*j+1] = 0x00; + pixels[3*j+2] = 0x00; + } + pixels += gdk_pixbuf_get_rowstride(ctx->pixbuf); + } + + for (int i = 0; i < 3; i++) { + guchar* pixels = gdk_pixbuf_get_pixels(ctx->pixbuf); + for (int j = 0; j < ctx->height; j++) { + guchar* src_buf = &ctx->channels_buffers[i][ctx->width * j]; + for (int k = 0; k < ctx->width; k++) { + pixels[3 * k + i] = src_buf[k]; + } + pixels += gdk_pixbuf_get_rowstride(ctx->pixbuf); + } + } + } + + g_free (ctx->buffer); // TODO a few more buffers need freeing + g_free (ctx); + + return retval; +} + + +static gboolean +gdk_pixbuf__psd_image_load_increment (gpointer context_ptr, + const guchar *data, + guint size, + GError **error) +{ + + PsdContext* context = (PsdContext*) context_ptr; + PsdContext* ctx = context; + + while (size > 0) { + switch (context->state) { + case PSD_STATE_HEADER: + if (feed_buffer( + context->buffer, &context->bytes_read, + &data, &size, PSD_HEADER_SIZE)) + { + PsdHeader hd = psd_parse_header(ctx->buffer); + + ctx->width = hd.columns; + ctx->height = hd.rows; + ctx->channels = hd.channels; + ctx->depth = hd.depth; + ctx->color_mode = hd.color_mode; + + if (ctx->size_func) { + gint w = ctx->width; + gint h = ctx->height; + ctx->size_func(&w, &h, ctx->user_data); + if (w == 0 || h == 0) { + return FALSE; + } + } + + // we need buffer that can contain one channel data of one + // row in RLE compressed format. 2*width should be enough + g_free(ctx->buffer); + ctx->buffer = g_malloc(ctx->width * 2); + + // this will be needed for RLE decompression + ctx->lines_lengths = + g_malloc(2 * ctx->channels * ctx->height); + + ctx->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, + 8, ctx->width, ctx->height); + + if (ctx->lines_lengths == NULL || ctx->buffer == NULL || + ctx->pixbuf == NULL) + { + g_set_error (error, GDK_PIXBUF_ERROR, + GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, + ("Insufficient memory to load PSD image file")); + return FALSE; + } + + // create separate buffers for each channel + context->channels_buffers = + g_malloc(sizeof(guchar*) * ctx->channels); + for (int i = 0; i < ctx->channels; i++) { + ctx->channels_buffers[i] = + g_malloc(ctx->width * ctx->height); + + if (ctx->channels_buffers[i] == NULL) { + g_set_error (error, GDK_PIXBUF_ERROR, + GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, + ("Insufficient memory to load PSD image file")); + return FALSE; + } + } + + ctx->prepared_func(ctx->pixbuf, NULL, ctx->user_data); + + ctx->state = PSD_STATE_COLOR_MODE_BLOCK; + reset_context(ctx); + } + break; + case PSD_STATE_COLOR_MODE_BLOCK: + if (skip_block(ctx, &data, &size)) { + ctx->state = PSD_STATE_RESOURCES_BLOCK; + reset_context(ctx); + } + break; + case PSD_STATE_RESOURCES_BLOCK: + if (skip_block(ctx, &data, &size)) { + ctx->state = PSD_STATE_LAYERS_BLOCK; + reset_context(ctx); + } + break; + case PSD_STATE_LAYERS_BLOCK: + if (skip_block(ctx, &data, &size)) { + ctx->state = PSD_STATE_COMPRESSION; + reset_context(ctx); + } + break; + case PSD_STATE_COMPRESSION: + if (feed_buffer(ctx->buffer, &ctx->bytes_read, &data, &size, 2)) + { + ctx->compression = parse_uint16(ctx->buffer); + + if (ctx->compression == PSD_COMPRESSION_RLE) { + ctx->state = PSD_STATE_LINES_LENGTHS; + reset_context(ctx); + } else if (ctx->compression == PSD_COMPRESSION_NONE) { + ctx->state = PSD_STATE_CHANNEL_DATA; + } else { + g_set_error (error, GDK_PIXBUF_ERROR, + GDK_PIXBUF_ERROR_UNKNOWN_TYPE, + ("Unsupported compression type")); + return FALSE; + } + } + break; + case PSD_STATE_LINES_LENGTHS: + if (feed_buffer( + (guchar*) ctx->lines_lengths, &ctx->bytes_read, &data, + &size, 2 * ctx->height * ctx->channels)) + { + // convert from different endianness + for (int i = 0; i < ctx->height * ctx->channels; i++) { + ctx->lines_lengths[i] = parse_uint16( + (guchar*) &ctx->lines_lengths[i]); + } + ctx->state = PSD_STATE_CHANNEL_DATA; + reset_context(ctx); + } + break; + case PSD_STATE_CHANNEL_DATA: + if (context->compression == PSD_COMPRESSION_RLE) + { + guint line_length = ctx->lines_lengths[ + ctx->current_channel * ctx->height + ctx->current_row]; + if (feed_buffer(ctx->buffer, &ctx->bytes_read, &data, + &size, line_length)) + { + ctx->bytes_read = 0; + decompress_line( + ctx->buffer, + line_length, + ctx->channels_buffers[ctx->current_channel] + + ctx->position + ); + context->position += context->width; + ++context->current_row; + + if (ctx->current_row >= ctx->height) { + ++ctx->current_channel; + ctx->current_row = 0; + ctx->position = 0; + if (ctx->current_channel >= ctx->channels) { + ctx->state = PSD_STATE_DONE; + } + } + } + } else { + if (feed_buffer( + context->buffer, &context->bytes_read, + &data, &size, context->width)) + { + //memcpy(dest, context->buffer, context->hd.columns); + // TODO + } + } + break; + case PSD_STATE_DONE: + default: + size = 0; + break; + } + } + return TRUE; +} + + void fill_vtable (GdkPixbufModule* module) { -/* module->load = gdk_pixbuf__xbm_image_load; - module->begin_load = gdk_pixbuf__xbm_image_begin_load; - module->stop_load = gdk_pixbuf__xbm_image_stop_loads - module->load_increment = gdk_pixbuf__xbm_image_load_increment;*/ - // TODO progressive loading - - module->load = gdk_pixbuf__psd_image_load; + //module->load = gdk_pixbuf__psd_image_load; + module->begin_load = gdk_pixbuf__psd_image_begin_load; + module->stop_load = gdk_pixbuf__psd_image_stop_load; + module->load_increment = gdk_pixbuf__psd_image_load_increment; } void |