/* This file is part of "psdparse" Copyright (C) 2004-9 Toby Thain, toby@telegraphics.com.au 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "psdparse.h" #ifdef HAVE_ICONV_H extern iconv_t ic; #endif const char *colour_spaces[] = {"kDummySpace" /* = -1 */, "kRGBSpace", "kHSBSpace", "kCMYKSpace", "kPantoneSpace", "kFocoltoneSpace", "kTrumatchSpace", "kToyoSpace", "kLabSpace", "kGraySpace", "kWideCMYKSpace", "kHKSSpace", "kDICSpace", "kTotalInkSpace", "kMonitorRGBSpace", "kDuotoneSpace", "kOpacitySpace" }; /* 'Extra data' handling. *Work in progress* * * There's guesswork and trial-and-error in here, * due to many errors and omissions in Adobe's documentation on this (PS6 SDK). * It's amazing that they would try to describe a hierarchical format * as a flat list of fields. Reminds me of Jasc's PSP format docs, too. * One must assume they don't encourage people to try and USE the info. */ #define BITSTR(f) ((f) ? "(1)" : "(0)") void entertag(psd_file_t f, int level, int len, struct dictentry *parent, struct dictentry *d, int resetpos){ psd_bytes_t savepos = ftello(f); int oneline = d->tag[0] == '-'; char *tagname = d->tag + oneline; if(xml){ // check parent's one-line-ness, because what precedes our // belongs to our parent. fprintf(xml, "%s<%s>", parent->tag[0] == '-' ? " " : tabs(level), tagname); if(!oneline) fputc('\n', xml); } d->func(f, level+1, len, d); // parse contents of this datum if(xml){ fprintf(xml, "%s", oneline ? "" : tabs(level), tagname); // if parent's not one-line, then we can safely newline after our tag. fputc(parent->tag[0] == '-' ? ' ' : '\n', xml); } if(resetpos) fseeko(f, savepos, SEEK_SET); } // This uses a dumb linear search. But it's efficient enough in practice. struct dictentry *findbykey(psd_file_t f, int level, struct dictentry *parent, char *key, int len, int resetpos){ struct dictentry *d; for(d = parent; d->key; ++d) if(KEYMATCH(key, d->key)){ char *tagname = d->tag + (d->tag[0] == '-'); //fprintf(stderr, "matched tag %s\n", d->tag); if(d->func) entertag(f, level, len, parent, d, resetpos); else{ // there is no function to parse this block. // because tag is empty in this case, we only need to consider // parent's one-line-ness. if(xml){ if(parent->tag[0] == '-') fprintf(xml, " <%s /> ", tagname); else fprintf(xml, "%s<%s /> \n", tabs(level), tagname); } } return d; } return NULL; } /* not 'static'; these are referenced by scavenge.c */ struct dictentry bmdict[] = { {0, "norm", "NORMAL", "normal", NULL}, {0, "dark", "DARKEN", "darken", NULL}, {0, "lite", "LIGHTEN", "lighten", NULL}, {0, "hue ", "HUE", "hue", NULL}, {0, "sat ", "SATURATION", "saturation", NULL}, {0, "colr", "COLOR", "color", NULL}, {0, "lum ", "LUMINOSITY", "luminosity", NULL}, {0, "mul ", "MULTIPLY", "multiply", NULL}, {0, "scrn", "SCREEN", "screen", NULL}, {0, "diss", "DISSOLVE", "dissolve", NULL}, {0, "over", "OVERLAY", "overlay", NULL}, {0, "hLit", "HARDLIGHT", "hard light", NULL}, {0, "sLit", "SOFTLIGHT", "soft light", NULL}, {0, "diff", "DIFFERENCE", "difference", NULL}, {0, "smud", "EXCLUSION", "exclusion", NULL}, {0, "div ", "COLORDODGE", "color dodge", NULL}, {0, "idiv", "COLORBURN", "color burn", NULL}, // CS {0, "lbrn", "LINEARBURN", "linear burn", NULL}, {0, "lddg", "LINEARDODGE", "linear dodge", NULL}, {0, "vLit", "VIVIDLIGHT", "vivid light", NULL}, {0, "lLit", "LINEARLIGHT", "linear light", NULL}, {0, "pLit", "PINLIGHT", "pin light", NULL}, {0, "hMix", "HARDMIX", "hard mix", NULL}, {0, NULL, NULL, NULL, NULL} }; void layerblendmode(psd_file_t f, int level, int len, struct blend_mode_info *bm){ struct dictentry *d; const char *indent = tabs(level); if(xml && KEYMATCH(bm->sig, "8BIM")){ fprintf(xml, "%s\n", indent, bm->opacity/2.55, bm->clipping); findbykey(f, level+1, bmdict, bm->key, len, 1); if(bm->flags & 1) fprintf(xml, "%s\t\n", indent); if(bm->flags & 2) fprintf(xml, "%s\t\n", indent); if((bm->flags & (8|16)) == (8|16)) // both bits set fprintf(xml, "%s\t\n", indent); fprintf(xml, "%s\n", indent); } if(!xml){ d = findbykey(f, level+1, bmdict, bm->key, len, 1); VERBOSE(" blending mode: sig='%c%c%c%c' key='%c%c%c%c'(%s) opacity=%d(%d%%) clipping=%d(%s)\n\ flags=%#x(transp_prot%s visible%s bit4valid%s pixel_data_irrelevant%s)\n", bm->sig[0],bm->sig[1],bm->sig[2],bm->sig[3], bm->key[0],bm->key[1],bm->key[2],bm->key[3], d ? d->desc : "???", bm->opacity, (bm->opacity*100+127)/255, bm->clipping, bm->clipping ? "non-base" : "base", bm->flags, BITSTR(bm->flags&1), BITSTR(bm->flags&2), BITSTR(bm->flags&8), BITSTR(bm->flags&16) ); } } // CS doc static void blendmode(psd_file_t f, int level, int len, struct dictentry *parent){ char sig[4], key[4]; fread(sig, 1, 4, f); fread(key, 1, 4, f); if(xml && KEYMATCH(sig, "8BIM")){ fprintf(xml, "%s\n", tabs(level)); findbykey(f, level+1, bmdict, key, len, 1); fprintf(xml, "%s\n", tabs(level)); } } static void colorspace(psd_file_t f, int level){ int i, space = get2B(f); fprintf(xml, "%s= -1 && space < (int)(sizeof(colour_spaces)/sizeof(*colour_spaces))-1) fprintf(xml, " NAME='%s'", colour_spaces[space+1]); fputc('>', xml); for(i = 0; i < 4; ++i) fprintf(xml, " %g", i, FIXEDPT(get2Bu(f)), i); fputs(" \n", xml); } void conv_unicodestyles(psd_file_t f, long count, const char *indent){ unsigned short *utf16 = malloc(2*count); short *style = malloc(2*count); int i; size_t inb, outb; char *inbuf, *outbuf, *utf8; if(utf16 && style){ for(i = 0; i < count; i++){ utf16[i] = get2Bu(f); // the UTF-16 char style[i] = get2B(f); // and its corresponding style code } #ifdef HAVE_ICONV_H iconv(ic, NULL, &inb, NULL, &outb); // reset iconv state outb = 6*count; // sloppy overestimate of buffer (FIXME) if( (utf8 = malloc(outb)) ){ inbuf = (char*)utf16; inb = 2*count; outbuf = utf8; if(ic != (iconv_t)-1){ if(iconv(ic, &inbuf, &inb, &outbuf, &outb) != (size_t)-1){ // use CDATA wrap until we can pick through the UTF-8 for & < > ' " and escape them fprintf(xml, "%s\n", xml); // copy to terminal FIXME: UTF-8 may not be useful anyway if(verbose) fwrite(utf8, 1, outbuf-utf8, stdout); }else alwayswarn("iconv() failed, errno=%u\n", errno); } free(utf8); } #endif for(i = 0; i < count; ++i) fprintf(xml, "%s%d\n", indent, style[i]); }else fatal("conv_unicodestyle(): can't get memory"); free(utf16); free(style); } static void ed_typetool(psd_file_t f, int level, int len, struct dictentry *parent){ int i, j, v = get2B(f), mark, type, script, facemark, autokern, charcount, selstart, selend, linecount, orient, align; double size, tracking, kerning, leading, baseshift, scaling, hplace, vplace; static char *coeff[] = {"XX","XY","YX","YY","TX","TY"}; // from CS doc const char *indent = tabs(level); if(xml){ fprintf(xml, "%s%d\n", indent, v); // read transform (6 doubles) fprintf(xml, "%s", indent); for(i = 0; i < 6; ++i) fprintf(xml, " <%s>%g", coeff[i], getdoubleB(f), coeff[i]); fputs(" \n", xml); // read font information v = get2B(f); fprintf(xml, "%s%d\n", indent, v); if(v <= 6){ for(i = get2B(f); i--;){ mark = get2B(f); type = get4B(f); fprintf(xml, "%s\n", script); // doc is unclear, but this may work: fprintf(xml, "%s\t", indent); for(j = get4B(f); j--;) fprintf(xml, " %ld", get4B(f)); fputs(" \n", xml); fprintf(xml, "%s\n", indent); } j = get2B(f); // count of styles for(; j--;){ mark = get2B(f); facemark = get2B(f); size = FIXEDPT(get4B(f)); // of course, which fields are fixed point is undocumented tracking = FIXEDPT(get4B(f)); // so I'm kerning = FIXEDPT(get4B(f)); // taking leading = FIXEDPT(get4B(f)); // a punt baseshift = FIXEDPT(get4B(f)); // on these autokern = fgetc(f); fprintf(xml, "%s\n", indent, fgetc(f)); fprintf(xml, "%s%g\n", indent, fgetc(f)/2.55); fprintf(xml, "%s%g\n", indent, fgetc(f)/2.55); fprintf(xml, "%s%d\n", indent, fgetc(f)); fprintf(xml, "%s%d\n", indent, fgetc(f)); fprintf(xml, "%s%d\n", indent, fgetc(f)); // heh, interpretation is undocumented if(version==2){ colorspace(f, level); colorspace(f, level); } } } // CS doc static void fx_solidfill(psd_file_t f, int level, int len, struct dictentry *parent){ const char *indent = tabs(level); long version; if(xml){ fprintf(xml, "%s%ld\n", indent, version = get4B(f)); // blendmode is the usual 8 bytes; doc only mentions 4 blendmode(f, level, len, parent); colorspace(f, level); fprintf(xml, "%s%g\n", indent, fgetc(f)/2.55); fprintf(xml, "%s%d\n", indent, fgetc(f)); colorspace(f, level); } } // CS doc static void ed_layereffects(psd_file_t f, int level, int len, struct dictentry *parent){ static struct dictentry fxdict[] = { {0, "cmnS", "COMMONSTATE", "common state", fx_commonstate}, {0, "dsdw", "DROPSHADOW", "drop shadow", fx_shadow}, {0, "isdw", "INNERSHADOW", "inner shadow", fx_shadow}, {0, "oglw", "OUTERGLOW", "outer glow", fx_outerglow}, {0, "iglw", "INNERGLOW", "inner glow", fx_innerglow}, {0, "bevl", "BEVEL", "bevel", fx_bevel}, {0, "sofi", "SOLIDFILL", "solid fill", fx_solidfill}, // Photoshop 7.0 {0, NULL, NULL, NULL, NULL} }; int count; if(xml){ fprintf(xml, "%s%d\n", tabs(level), get2B(f)); for(count = get2B(f); count--;) if(!sigkeyblock(f, NULL/*FIXME*/, level, len, fxdict)) break; // got bad signature } } static void mdblock(psd_file_t f, int level, int len){ char sig[4], key[4]; long length; int copy; const char *indent = tabs(level); fread(sig, 1, 4, f); fread(key, 1, 4, f); copy = fgetc(f); fseeko(f, 3, SEEK_CUR); // padding length = get4B(f); if(xml){ fprintf(xml, "%s\n", indent, sig[0],sig[1],sig[2],sig[3], key[0],key[1],key[2],key[3]); fprintf(xml, "\t%s%d\n", indent, copy); fprintf(xml, "\t%s\n", indent, length); // the documentation tells us that it's undocumented fprintf(xml, "%s\n", indent); }else UNQUIET(" (Metadata: sig='%c%c%c%c' key='%c%c%c%c' %ld bytes)\n", sig[0],sig[1],sig[2],sig[3], key[0],key[1],key[2],key[3], length); } // v6 doc static void ed_metadata(psd_file_t f, int level, int len, struct dictentry *parent){ long count; for(count = get4B(f); count--;) mdblock(f, level, len); } struct psd_header *psd_header; // hacky // see libpsd static void ed_layer16(psd_file_t f, int level, int len, struct dictentry *parent){ //struct psd_header h2 = *psd_header; // a kind of 'nested' set of layers; don't alter main PSD header // overwrite main PSD header, mainly because of the 'merged alpha' flag // I *think* they mean us to respect the one in Lr16 because in my test data, // the main layer count is zero, so cannot convey this information. dolayerinfo(f, psd_header); processlayers(f, psd_header); } // v6 doc static void adj_levels(psd_file_t f, int level, int len, struct dictentry *parent){ const char *indent = tabs(level); int i, j, v[5], version = get2B(f); if(xml){ fprintf(xml, "%s%d\n", indent, version); for(i = 0; i < 29; ++i){ for(j = 0; j < 5; ++j) v[j] = get2B(f); fprintf(xml, "%s\n", indent); fprintf(xml, "\t%s%d\n", indent, v[0]); fprintf(xml, "\t%s%d\n", indent, v[1]); fprintf(xml, "\t%s%d\n", indent, v[2]); fprintf(xml, "\t%s%d\n", indent, v[3]); fprintf(xml, "\t%s%g\n", indent, v[4]/100.); fprintf(xml, "%s\n", indent); } } } // v6 doc static void adj_curves(psd_file_t f, int level, int len, struct dictentry *parent){ const char *indent = tabs(level); int i, j, version, count; fgetc(f); // mystery data - not mentioned in v6 doc?! version = get2B(f); get2B(f); // mystery data count = get2B(f); if(xml){ fprintf(xml, "%s%d\n", indent, version); for(i = 0; i < count; ++i){ int points = get2B(f); fprintf(xml, "%s\n", indent); for(j = 0; j < points; ++j){ int output = get2B(f), input = get2B(f); fprintf(xml, "\t%s %d %d \n", indent, output, input); } fprintf(xml, "%s\n", indent); } } } // v6 doc static void adj_huesat4(psd_file_t f, int level, int len, struct dictentry *parent){ static const char *hsl[] = {"HUE", "SATURATION", "LIGHTNESS"}; const char *indent = tabs(level); int i, j, version, mode; version = get2B(f); mode = fgetc(f); fgetc(f); if(xml){ int h = get2B(f), s = get2B(f), l = get2B(f); fprintf(xml, "%s%d\n", indent, version); fprintf(xml, "%s<%s/>\n", indent, mode ? "COLORISE" : "HUEADJUST"); fprintf(xml, "%s%d %d %d\n", indent, h, s, l); for(i = 0; i < 3; ++i){ fprintf(xml, "%s<%s>\n", indent, hsl[i]); for(j = 0; j < 7; ++j) fprintf(xml, "\t%s%d\n", indent, get2B(f)); fprintf(xml, "%s\n", indent, hsl[i]); } } } // v6 doc static void adj_huesat5(psd_file_t f, int level, int len, struct dictentry *parent){ const char *indent = tabs(level); int i, j, version, mode; version = get2B(f); mode = fgetc(f); fgetc(f); if(xml){ int h = get2B(f), s = get2B(f), l = get2B(f); fprintf(xml, "%s%d\n", indent, version); fprintf(xml, "%s<%s/>\n", indent, mode ? "COLORISE" : "HUEADJUST"); fprintf(xml, "%s%d %d %d\n", indent, h, s, l); for(i = 0; i < 6; ++i){ fprintf(xml, "%s\n", indent); for(j = 0; j < 4; ++j) fprintf(xml, "\t%s%d\n", indent, get2B(f)); for(j = 0; j < 3; ++j) fprintf(xml, "\t%s%d\n", indent, get2B(f)); fprintf(xml, "%s\n", indent); } } } // v6 doc static void adj_selcol(psd_file_t f, int level, int len, struct dictentry *parent){ const char *indent = tabs(level); int i, version = get2B(f); static char *colours[] = {"RESERVED", "REDS", "YELLOWS", "GREENS", "CYANS", "BLUES", "MAGENTAS", "WHITES", "NEUTRALS", "BLACKS"}; fgetc(f); if(xml){ fprintf(xml, "%s%d\n", indent, version); for(i = 0; i < 10; ++i){ fprintf(xml, "%s<%s>", indent, colours[i]); fprintf(xml, " %d", get2B(f)); fprintf(xml, " %d", get2B(f)); fprintf(xml, " %d", get2B(f)); fprintf(xml, " %d", get2B(f)); fprintf(xml, " \n", colours[i]); } } } static void adj_brightnesscontrast(psd_file_t f, int level, int len, struct dictentry *parent){ const char *indent = tabs(level); if(xml){ fprintf(xml, "%s%d\n", indent, get2B(f)); fprintf(xml, "%s%d\n", indent, get2B(f)); fprintf(xml, "%s%d\n", indent, get2B(f)); fprintf(xml, "%s%d\n", indent, fgetc(f)); } } void doadditional(psd_file_t f, struct psd_header *h, int level, psd_bytes_t length){ static struct dictentry extradict[] = { // v4.0 {0, "levl", "LEVELS", "Levels", adj_levels}, {0, "curv", "CURVES", "Curves", adj_curves}, {0, "brit", "BRIGHTNESSCONTRAST", "Brightness/contrast", adj_brightnesscontrast}, {0, "blnc", "COLORBALANCE", "Color balance", NULL}, {0, "hue ", "HUESATURATION4", "Old Hue/saturation, Photoshop 4.0", adj_huesat4}, {0, "hue2", "HUESATURATION5", "New Hue/saturation, Photoshop 5.0", adj_huesat5}, {0, "selc", "SELECTIVECOLOR", "Selective color", adj_selcol}, {0, "thrs", "THRESHOLD", "Threshold", NULL}, {0, "nvrt", "INVERT", "Invert", NULL}, {0, "post", "POSTERIZE", "Posterize", NULL}, // v5.0 {0, "lrFX", "EFFECT", "Effects layer", ed_layereffects}, {0, "tySh", "TYPETOOL5", "Type tool (5.0)", ed_typetool}, {0, "luni", "-UNICODENAME", "Unicode layer name", ed_unicodename}, {0, "lyid", "-LAYERID", "Layer ID", ed_long}, // '-' prefix means keep tag value on one line // v6.0 {0, "lfx2", "OBJECTEFFECT", "Object based effects layer", ed_objecteffects}, {0, "Patt", "PATTERN", "Pattern", NULL}, {0, "Pat2", "PATTERNCS", "Pattern (CS)", NULL}, {0, "Anno", "ANNOTATION", "Annotation", ed_annotation}, {0, "clbl", "-BLENDCLIPPING", "Blend clipping", ed_byte}, {0, "infx", "-BLENDINTERIOR", "Blend interior", ed_byte}, {0, "knko", "-KNOCKOUT", "Knockout", ed_byte}, {0, "lspf", "-PROTECTED", "Protected", ed_long}, {0, "lclr", "SHEETCOLOR", "Sheet color", ed_color}, {0, "fxrp", "-REFERENCEPOINT", "Reference point", ed_referencepoint}, {0, "grdm", "GRADIENT", "Gradient", ed_gradient}, {0, "lsct", "SECTIONDIVIDER", "Section divider", ed_sectiondivider}, // CS doc {0, "SoCo", "SOLIDCOLORSHEET", "Solid color sheet", ed_versdesc}, // CS doc {0, "PtFl", "PATTERNFILL", "Pattern fill", ed_versdesc}, // CS doc {0, "GdFl", "GRADIENTFILL", "Gradient fill", ed_versdesc}, // CS doc {0, "vmsk", "VECTORMASK", "Vector mask", ed_vectormask}, // CS doc {0, "TySh", "TYPETOOL6", "Type tool (6.0)", ed_typetool}, // CS doc {0, "ffxi", "-FOREIGNEFFECTID", "Foreign effect ID", ed_long}, // CS doc (this is probably a key too, who knows) {0, "lnsr", "-LAYERNAMESOURCE", "Layer name source", ed_key}, // CS doc (who knew this was a signature? docs fail again - and what do the values mean?) {0, "shpa", "PATTERNDATA", "Pattern data", NULL}, // CS doc {0, "shmd", "METADATASETTING", "Metadata setting", ed_metadata}, // CS doc {0, "brst", "BLENDINGRESTRICTIONS", "Channel blending restrictions", ed_blendingrestrictions}, // CS doc // v7.0 {0, "lyvr", "-LAYERVERSION", "Layer version", ed_long}, // CS doc {0, "tsly", "-TRANSPARENCYSHAPES", "Transparency shapes layer", ed_byte}, // CS doc {0, "lmgm", "-LAYERMASKASGLOBALMASK", "Layer mask as global mask", ed_byte}, // CS doc {0, "vmgm", "-VECTORMASKASGLOBALMASK", "Vector mask as global mask", ed_byte}, // CS doc // CS {0, "mixr", "CHANNELMIXER", "Channel mixer", NULL}, // CS doc {0, "phfl", "PHOTOFILTER", "Photo Filter", NULL}, // CS doc // from libpsd {0, "Lr16", "LAYER16", "Layer 16", ed_layer16}, {0, NULL, NULL, NULL, NULL} }; psd_header = h; while(length >= 12){ psd_bytes_t block = sigkeyblock(f, h, level, length, extradict); if(!block){ warn_msg("bad signature in layer's extra data, skipping the rest"); break; } length -= block; } }