/* 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" #include "png.h" static void writeimage(psd_file_t psd, char *dir, char *name, struct layer_info *li, struct channel_info *chan, int channels, long rows, long cols, struct psd_header *h, int color_type) { FILE *outfile; if(writepng){ if(h->depth == 32){ if((outfile = rawsetupwrite(psd, dir, name, cols, rows, channels, color_type, li, h))) rawwriteimage(outfile, psd, li, chan, channels, h); }else{ if((outfile = pngsetupwrite(psd, dir, name, cols, rows, channels, color_type, li, h))) pngwriteimage(outfile, psd, li, chan, channels, h); } } } static void writechannels(psd_file_t f, char *dir, char *name, struct layer_info *li, struct channel_info *chan, int channels, struct psd_header *h) { char pngname[FILENAME_MAX]; int ch; for(ch = 0; ch < channels; ++ch){ // build PNG file name strcpy(pngname, name); if(chan[ch].id == -2){ if(xml){ fprintf(xml, "\t\t\n", li->mask.top, li->mask.left, li->mask.bottom, li->mask.right, li->mask.rows, li->mask.cols, li->mask.default_colour); if(li->mask.flags & 1) fputs("\t\t\t\n", xml); if(li->mask.flags & 2) fputs("\t\t\t\n", xml); if(li->mask.flags & 4) fputs("\t\t\t\n", xml); } strcat(pngname, ".lmask"); }else if(chan[ch].id == -1){ if(xml) fputs("\t\t\n", xml); strcat(pngname, li ? ".trans" : ".alpha"); }else{ if(xml) fprintf(xml, "\t\t\n", chan[ch].id); if(chan[ch].id < (int)strlen(channelsuffixes[h->mode])) // can identify channel by letter sprintf(pngname+strlen(pngname), ".%c", channelsuffixes[h->mode][chan[ch].id]); else // give up and use a number sprintf(pngname+strlen(pngname), ".%d", chan[ch].id); } if(chan[ch].comptype == -1) alwayswarn("## not writing \"%s\", bad channel compression type\n", pngname); else writeimage(f, dir, pngname, li, chan + ch, 1, chan[ch].rows, chan[ch].cols, h, PNG_COLOR_TYPE_GRAY); if(chan[ch].id == -2){ if(xml) fputs("\t\t\n", xml); }else if(chan[ch].id == -1){ if(xml) fputs("\t\t\n", xml); }else{ if(xml) fputs("\t\t\n", xml); } } } void doimage(psd_file_t f, struct layer_info *li, char *name, struct psd_header *h) { // map channel count to a suitable PNG mode (when scavenging and actual mode is not known) static int png_mode[] = {0, PNG_COLOR_TYPE_GRAY, PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_RGB, PNG_COLOR_TYPE_RGB_ALPHA}; int ch, pngchan, color_type, channels = li ? li->channels : h->channels; psd_bytes_t image_data_end; pngchan = color_type = 0; switch(h->mode){ default: // multichannel, cmyk, lab etc split = 1; case ModeBitmap: case ModeGrayScale: case ModeGray16: case ModeDuotone: case ModeDuotone16: color_type = PNG_COLOR_TYPE_GRAY; pngchan = 1; // check if there is an alpha channel, or if merged data has alpha if( li ? li->chindex[-1] != -1 : channels > 1 && h->mergedalpha ){ color_type = PNG_COLOR_TYPE_GRAY_ALPHA; pngchan = 2; } break; case ModeIndexedColor: color_type = PNG_COLOR_TYPE_PALETTE; pngchan = 1; break; case ModeRGBColor: case ModeRGB48: color_type = PNG_COLOR_TYPE_RGB; pngchan = 3; if( li ? li->chindex[-1] != -1 : channels > 3 && h->mergedalpha ){ color_type = PNG_COLOR_TYPE_RGB_ALPHA; pngchan = 4; } break; case SCAVENGE_MODE: pngchan = channels; color_type = png_mode[pngchan]; break; } if(li){ // Process layer for(ch = 0; ch < channels; ++ch){ VERBOSE(" channel %d:\n", ch); dochannel(f, li, li->chan + ch, 1/*count*/, h); } image_data_end = ftello(f); if(writepng){ nwarns = 0; if(pngchan && !split){ writeimage(f, pngdir, name, li, li->chan, h->depth == 32 ? channels : pngchan, li->bottom - li->top, li->right - li->left, h, color_type); if(h->depth < 32){ // spit out any 'extra' channels (e.g. layer transparency) for(ch = 0; ch < channels; ++ch) if(li->chan[ch].id < -1 || li->chan[ch].id >= pngchan) writechannels(f, pngdir, name, li, li->chan + ch, 1, h); } }else{ UNQUIET("# writing layer as split channels...\n"); writechannels(f, pngdir, name, li, li->chan, channels, h); } } }else{ struct channel_info *merged_chans = checkmalloc(channels*sizeof(struct channel_info)); // The 'merged' or 'composite' image is where the flattened image is stored // when 'Maximise Compatibility' is used. // It consists of: // - the alpha channel for merged image (if mergedalpha is TRUE) // - the merged image (1 or 3 channels) // - any remaining alpha or spot channels. // For an identifiable image mode (Bitmap, GreyScale, Duotone, Indexed or RGB), // we should ideally // 1) write the first 1[2] or 3[4] channels in appropriate PNG format // 2) write the remaining channels as extra GRAY PNG files. // (For multichannel (and maybe other?) modes, we should just write all // channels per step 2) VERBOSE("\n merged channels:\n"); dochannel(f, NULL, merged_chans, channels, h); image_data_end = ftello(f); if(xml) fprintf(xml, "\t\n", channels, h->rows, h->cols); nwarns = 0; ch = 0; if(pngchan && !split){ writeimage(f, pngdir, name, NULL, merged_chans, h->depth == 32 ? channels : pngchan, h->rows, h->cols, h, color_type); ch += pngchan; } if(writepng && ch < channels){ if(split){ UNQUIET("# writing %s image as split channels...\n", mode_names[h->mode]); }else{ UNQUIET("# writing %d extra channels...\n", channels - ch); } writechannels(f, pngdir, name, NULL, merged_chans + ch, channels - ch, h); } if(xml) fputs("\t\n", xml); free(merged_chans); } // caller may expect this file position fseeko(f, image_data_end, SEEK_SET); }