/* * This is a plug-in for the GIMP. * * Copyright (C) 1996 Torsten Martinsen * Portions Copyright (C) 1995 Peter Mattis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id: psd.c,v 1.11 1996/08/16 09:46:40 torsten Exp $ * * @(GIMP) = * @(GIMP_DEP) = * @(GIMP_OBJ) = * @(GIMP_LIB) = * @(GIMP_AUTHOR) = * @(GIMP_EMAIL) = * @(GIMP_DESC) = * @(GIMP_VERSION) = <$Revision: 1.11 $> * @(GIMP_URL) = */ /* * TODO: * Do CMYK -> RGB conversion * Better progress indicators * Load BITMAP mode * Implement layers stuff * Handle images with > 5 channels (how?) */ #include #include #include #include #include #include "gimp.h" /* * Change these to suit your system, otherwise the assert() * macros in main() will fail. */ typedef short WORD; typedef long DWORD; typedef unsigned short UWORD; typedef long unsigned UDWORD; typedef unsigned char BYTE; static struct { BYTE signature[4]; UWORD version; BYTE reserved[6]; UWORD channels; UDWORD rows; UDWORD columns; UWORD bpp; UWORD mode; UDWORD colmaplen; void * colmapdata; UDWORD imgreslen; void * imgresdata; UDWORD miscsizelen; void * miscsizedata; UWORD compression; UWORD * rowlength; long imgdatalen; } PSDheader; static char * modename[] = { "Bitmap", "Grayscale", "Indexed Colour", "RGB Colour", "CMYK Colour", "", "", "Multichannel", "Duotone", "Lab Colour", "" }; static char *prog_name; static void readPSD(char *name); static void decode(long clen, long uclen, char *src, char *dst, int step); static void packbitsdecode(long *clenp, long uclen, char *src, char *dst, int step); static void cmyk2rgb(unsigned char *src, unsigned char *destp, long width, long height, int alpha); static void cmykp2rgb(unsigned char *src, unsigned char *destp, long width, long height, int alpha); static void cmyk_to_rgb(int *c, int *m, int *y, int *k); static void writePSD(char *name); static WORD getWord(FILE *fd, char *why); static UDWORD getDWord(FILE *fd, char *why); static void xfread(FILE *fd, void *buf, long len, char *why); static void *xmalloc(size_t n); static void readheader(FILE *fd); int main(int argc, char **argv) { prog_name = argv[0]; assert(sizeof(WORD) == 2); assert(sizeof(DWORD) == 4); if (gimp_init(argc, argv)) { gimp_install_load_save_handlers(readPSD, writePSD); gimp_main_loop(); } return 0; } static void readPSD(char *name) { FILE *fd; char *name_buf, *cmykbuf; static int number = 1; unsigned char *dest, *temp; long rowstride, channels, nbytes; int imagetype, cmyk = 0, step; Image image; name_buf = xmalloc(strlen(name) + 11); if (number > 1) sprintf (name_buf, "%s-%d", name, number); else sprintf (name_buf, "%s", name); sprintf(name_buf, "Loading %s:", name); gimp_init_progress(name_buf); fd = fopen(name, "rb"); if (!fd) { printf("%s: can't open \"%s\"\n", prog_name, name); gimp_quit(); } readheader(fd); printf("Image data %ld bytes\n", PSDheader.imgdatalen); imagetype = UNKNOWN_IMAGE; switch (PSDheader.mode) { case 0: /* Bitmap */ break; case 1: /* Grayscale */ switch (PSDheader.channels) { case 1: step = 1; imagetype = GRAY_IMAGE; break; case 2: step = 2; imagetype = GRAYA_IMAGE; break; default: break; } break; case 2: /* Indexed Colour */ imagetype = INDEXED_IMAGE; step = 1; break; case 3: /* RGB Colour */ switch (PSDheader.channels) { case 3: step = 3; imagetype = RGB_IMAGE; break; case 4: step = 4; imagetype = RGBA_IMAGE; break; default: break; } break; case 4: /* CMYK Colour */ cmyk = 1; switch (PSDheader.channels) { case 4: step = 4; imagetype = RGB_IMAGE; break; case 5: step = 5; imagetype = RGBA_IMAGE; break; default: printf("%s: cannot handle CMYK with more than 5 channels\n", prog_name); gimp_quit(); break; } break; case 7: /* Multichannel (?) */ case 8: /* Duotone */ case 9: /* Lab Colour */ default: break; } if (imagetype == UNKNOWN_IMAGE) { printf("%s: Image type %d (%s) is not supported\n", prog_name, PSDheader.mode, modename[PSDheader.mode]); gimp_quit(); } if (PSDheader.bpp != 8) { printf("%s: The GIMP only supports 8-bit depth images\n", prog_name); gimp_quit(); } image = gimp_new_image (name_buf, PSDheader.columns, PSDheader.rows, imagetype); free(name_buf); dest = gimp_image_data (image); channels = gimp_image_channels (image); rowstride = gimp_image_width (image) * channels; gimp_do_progress (5, 100); if (PSDheader.compression) { nbytes = PSDheader.columns * PSDheader.rows; temp = xmalloc(PSDheader.imgdatalen); xfread(fd, temp, PSDheader.imgdatalen, "image data"); if (!cmyk) { gimp_do_progress (50, 100); decode(PSDheader.imgdatalen, nbytes, temp, dest, step); } else { gimp_do_progress (25, 100); cmykbuf = xmalloc(step * nbytes); decode(PSDheader.imgdatalen, nbytes, temp, cmykbuf, step); gimp_do_progress (50, 100); cmyk2rgb(cmykbuf, dest, PSDheader.columns, PSDheader.rows, step > 4); free(cmykbuf); } free(temp); } else if (!cmyk) { gimp_do_progress (50, 100); xfread(fd, dest, PSDheader.imgdatalen, "image data"); } else { gimp_do_progress (25, 100); cmykbuf = xmalloc(PSDheader.imgdatalen); xfread(fd, cmykbuf, PSDheader.imgdatalen, "image data"); gimp_do_progress (50, 100); cmykp2rgb(cmykbuf, dest, PSDheader.columns, PSDheader.rows, step > 4); free(cmykbuf); } gimp_do_progress(100, 100); gimp_display_image (image); gimp_update_image (image); gimp_free_image (image); if (PSDheader.imgreslen > 0) free(PSDheader.imgresdata); if (PSDheader.miscsizedata > 0) free(PSDheader.miscsizedata); if (PSDheader.colmaplen > 0) free(PSDheader.colmapdata); gimp_quit(); } static void decode(long clen, long uclen, char * src, char * dst, int step) { int i, j; long l; UWORD * w; l = clen; for (i = 0; i < PSDheader.rows*PSDheader.channels; ++i) l -= PSDheader.rowlength[i]; if (l) printf("*** %ld should be zero\n", l); w = PSDheader.rowlength; packbitsdecode(&clen, uclen, src, dst++, step); for (j = 0; j < step-1; ++j) { for (i = 0; i < PSDheader.rows; ++i) src += *w++; packbitsdecode(&clen, uclen, src, dst++, step); } printf("clen %ld\n", clen); } /* * Decode a PackBits data stream. */ static void packbitsdecode(long * clenp, long uclen, char * src, char * dst, int step) { int n, b; long clen = *clenp; while ((clen > 0) && (uclen > 0)) { n = (int) *src++; if (n >= 128) n -= 256; if (n < 0) { /* replicate next byte -n+1 times */ clen -= 2; if (n == -128) /* nop */ continue; n = -n + 1; uclen -= n; for (b = *src++; n > 0; --n) { *dst = b; dst += step; } } else { /* copy next n+1 bytes literally */ for (b = ++n; b > 0; --b) { *dst = *src++; dst += step; } uclen -= n; clen -= n+1; } } if (uclen > 0) { printf("%s: unexpected EOF while reading image data\n", prog_name); gimp_quit(); } *clenp = clen; } static void cmyk2rgb(unsigned char * src, unsigned char * dst, long width, long height, int alpha) { int r, g, b, k; int i, j; for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { r = *src++; g = *src++; b = *src++; k = *src++; cmyk_to_rgb (&r, &g, &b, &k); *dst++ = r; *dst++ = g; *dst++ = b; if (alpha) *dst++ = *src++; } if ((i % 5) == 0) gimp_do_progress (height+i, 2*height); } } /* * Decode planar CMYK(A) to RGB(A). */ static void cmykp2rgb(unsigned char * src, unsigned char * dst, long width, long height, int alpha) { int r, g, b, k; int i, j; long n; char *rp, *gp, *bp, *kp, *ap; n = width * height; rp = src; gp = rp + n; bp = gp + n; kp = bp + n; ap = kp + n; for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { r = *rp++; g = *gp++; b = *bp++; k = *kp++; cmyk_to_rgb (&r, &g, &b, &k); *dst++ = r; *dst++ = g; *dst++ = b; if (alpha) *dst++ = *ap++; } if ((i % 5) == 0) gimp_do_progress (height+i, 2*height); } } static void cmyk_to_rgb(int *c, int *m, int *y, int *k) { #if 0 int cyan, magenta, yellow, black; cyan = *c; magenta = *m; yellow = *y; black = *k; if (black > 0) { cyan -= black; magenta -= black; yellow -= black; } *c = 255 - cyan; *m = 255 - magenta; *y = 255 - yellow; if (*c < 0) *c = 0; else if (*c > 255) *c = 255; if (*m < 0) *m = 0; else if (*m > 255) *m = 255; if (*y < 0) *y = 0; else if (*y > 255) *y = 255; #endif } static void writePSD(char *name) { printf("%s: save not yet implemented\n", prog_name); gimp_quit(); } static WORD getWord(FILE *fd, char * why) { #if BYTE_ORDER == LITTLE_ENDIAN union { struct { unsigned char b1, b2; } b; WORD w; } s; #endif UWORD w; BYTE b; xfread(fd, &w, 2, why); #if (BYTE_ORDER == BIG_ENDIAN) return (WORD) w; #else s.w = w; b = s.b.b2; s.b.b2 = s.b.b1; s.b.b1 = b; return s.w; #endif } static UDWORD getDWord(FILE *fd, char * why) { #if BYTE_ORDER == LITTLE_ENDIAN union { struct { unsigned char b1, b2, b3, b4; } b; UDWORD w; } s; unsigned char s1, s2, s3, s4; #endif UDWORD w; xfread(fd, &w, 4, why); #if (BYTE_ORDER == BIG_ENDIAN) return (DWORD) w; #else s.w = w; s1 = s.b.b1; s2 = s.b.b2; s3 = s.b.b3; s4 = s.b.b4; s.b.b3 = s2; s.b.b4 = s1; s.b.b1 = s4; s.b.b2 = s3; return s.w; #endif } static void xfread(FILE * fd, void * buf, long len, char * why) { if (fread(buf, len, 1, fd) == 0) { printf("%s: unexpected EOF while reading '%s' field\n", prog_name, why); gimp_quit(); } } static void * xmalloc(size_t n) { void *p; if ((p = malloc(n)) != NULL) return p; printf("%s: out of memory\n", prog_name); exit(1); } static void readheader(FILE * fd) { UWORD w; UDWORD dw; long pos; char dummy[6]; int i; xfread(fd, &PSDheader.signature, 4, "signature"); PSDheader.version = getWord(fd, "version"); xfread(fd, &dummy, 6, "reserved"); PSDheader.channels = getWord(fd, "channels"); PSDheader.rows = getDWord(fd, "rows"); PSDheader.columns = getDWord(fd, "columns"); PSDheader.bpp = getWord(fd, "depth"); PSDheader.mode = getWord(fd, "mode"); PSDheader.colmaplen = getDWord(fd, "color data length"); if (PSDheader.colmaplen > 0) { PSDheader.colmapdata = xmalloc(PSDheader.colmaplen); xfread(fd, PSDheader.colmapdata, PSDheader.colmaplen, "colormap"); } dw = PSDheader.imgreslen = getDWord(fd, "image resource length"); if (dw > 0) { PSDheader.imgresdata = xmalloc(dw); xfread(fd, PSDheader.imgresdata, dw, "image resources"); } dw = PSDheader.miscsizelen = getDWord(fd, "misc size data length"); if (dw > 0) { PSDheader.miscsizedata = xmalloc(dw); xfread(fd, PSDheader.miscsizedata, dw, "misc size data"); } PSDheader.compression = getWord(fd, "compression"); if (PSDheader.compression) { PSDheader.rowlength = xmalloc(PSDheader.rows * PSDheader.channels * sizeof(UWORD)); for (i = 0; i < PSDheader.rows*PSDheader.channels; ++i) PSDheader.rowlength[i] = getWord(fd, "x"); } pos = ftell(fd); fseek(fd, 0, SEEK_END); PSDheader.imgdatalen = ftell(fd)-pos; fseek(fd, pos, SEEK_SET); if (strncmp(PSDheader.signature, "8BPS", 4) != 0) { printf("%s: not an Adobe Photoshop PSD file\n", prog_name); gimp_quit(); } if (PSDheader.version != 1) { printf("%s: bad version number '%d', not 1\n", prog_name, PSDheader.version); gimp_quit(); } w = PSDheader.mode; printf("--------------------------------\n" "Channels %d\nRows %ld\nColumns %ld\nDepth %d\nMode %d (%s)\n" "Colour data %ld bytes\n", PSDheader.channels, PSDheader.rows, PSDheader.columns, PSDheader.bpp, w, modename[w < 10 ? w : 10], PSDheader.colmaplen); printf("Image resource length: %lu\n", PSDheader.imgreslen); printf("Misc Size Data length: %lu\n", PSDheader.miscsizelen); w = PSDheader.compression; printf("Compression %d (%s)\n", w, w ? "RLE" : "raw"); }