/* This file is part of PSPFormat, a File Format plugin for Adobe Photoshop Copyright (C) 2003-7 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 */ /* PSP File Format Specification is Copyright (C) 2000 Jasc Software, Inc. See http://www.jasc.com/specs/psp.asp */ //#include #include #include #include "pspformat.h" #include "str.h" #include "misc.h" #include "ui.h" #include "PIProperties.h" #define OUTFILE (FILEREF)pb->dataFork extern int imageplanes, firstalpha; extern unsigned char *indata; int channelinfosize, alphainfosize, blkheadersize, psprb; FILEPOS layerstartpos, alphabankpos; long layerblocksize, alphabanksize; PSP_layer_info_chunk li; PSP_alpha_bank_info_chunk abi; struct revertinfo rev; static OSErr do_palette(FormatRecordPtr pb, int vers){ OSErr e = noErr; PSP_palette_info_chunk pi; RGBQUAD palette[256]; unsigned i; FILECOUNT count; pi.chunk_size = vers > PSP_SPEC5 ? 8 : 4; pi.entry_count = 256; if(pb->lutCount) pi.entry_count = pb->lutCount; // PS 5.5 or later count = sizeof(RGBQUAD)*pi.entry_count; /* size of actual palette data */ if( pb->imageMode == plugInModeIndexedColor && !(e = write_block(OUTFILE, vers, PSP_COLOR_BLOCK, pi.chunk_size, pi.chunk_size + count)) && !(e = psp_write_palette_info_chunk(OUTFILE, vers, &pi)) ){ /* now write palette entries chunk */ for(i = 0; i < pi.entry_count; ++i){ palette[i].rgbRed = pb->redLUT[i]; palette[i].rgbGreen = pb->greenLUT[i]; palette[i].rgbBlue = pb->blueLUT[i]; palette[i].rgbReserved = 0; } e = FSWRITE(OUTFILE, &count, palette); } return e; } OSErr write_start(FormatRecordPtr pb, intptr_t *data){ OSErr e = noErr; PSP_file_header h; PSP_image_attr_chunk gia; PSP7_layer_bitmap_chunk lb; int i, seq, layerchannels; indata = NULL; // if not NULL, this block is freed by write_finish() getrevertinfo(pb, &rev); psprb = G->psprb; imageplanes = pb->imageMode==plugInModeRGBColor ? 3 : 1; layerchannels = imageplanes + (pb->transparencyPlane != 0); /* work out the processing sequence for image planes */ /* first, do image channels, in order */ seq = 0; for(i = 0; i < imageplanes; ++i) pb->planeMap[seq++] = i; /* if present, do transparency next. */ if(pb->transparencyPlane) pb->planeMap[seq++] = pb->transparencyPlane; /* then do alpha channels */ firstalpha = seq; for(; i < pb->planes; ++i) if( !pb->transparencyPlane || i != pb->transparencyPlane ) pb->planeMap[seq++] = i; blkheadersize = rev.savevers > PSP_SPEC5 ? 10 : 14; channelinfosize = rev.savevers > PSP_SPEC5 ? 16 : 12; alphainfosize = rev.savevers > PSP_SPEC5 ? 38 /* +strlen(ALPHANAME) */ : 292; //channelsize = (long)psprb*pb->imageSize.v; memcpy(h.signature, PSP_SIGNATURE, PSP_SIG_LENGTH); h.major_version = rev.savevers; h.minor_version = 0; gia.chunk_size = rev.savevers > PSP_SPEC5 ? 46 : 38; gia.image_width = pb->imageSize.h; gia.image_height = pb->imageSize.v; gia.resolution = pb->imageHRes/65536.; gia.res_metric = PSP_METRIC_INCH; gia.comp_type = rev.savecomp; gia.bit_depth = pb->depth*imageplanes; gia.plane_count = 1; gia.colour_count = 1L<imageMode == plugInModeGrayScale; gia.total_size = imageplanes*G->channelsize; gia.active_layer = 0; gia.layer_count = 1; gia.graphic_contents = keGCRasterLayers; if(pb->planes > firstalpha) gia.graphic_contents |= keGCAlphaChannels; // if there's no layer transparency, should keGCFlatImage also be set ? memset(li.name, 0, PSP_LAYER_NAME_MAX); strcpy(li.name, "Photoshop Image"); li.chunk_size = rev.savevers > PSP_SPEC5 ? 119+2+strlen(li.name) : 375; li.type = rev.savevers > PSP_SPEC5 ? keGLTRaster : PSP_LAYER_NORMAL; SETRECT(li.image_rect, 0, 0, pb->imageSize.h, pb->imageSize.v); li.saved_rect = li.image_rect; li.opacity = 0xff; li.blending_mode = LAYER_BLEND_NORMAL; li.flags = keVisibleFlag /*|keMaskPresenceFlag*/; li.transparency_flags = false; li.link_group_id = 0; SETRECT(li.mask_rect, 0,0,0,0); li.saved_mask_rect = li.mask_rect; li.mask_linked = li.mask_disabled = li.invert_mask_on_blend = false; li.blend_range_count = 0; memset(li.blend_range_data, 0, PSP_BLEND_RANGE_BYTES); li.bitmap_count = lb.bitmap_count = 1; /* what does this really mean? */ li.channel_count = lb.channel_count = layerchannels; lb.chunk_size = rev.savevers > PSP_SPEC5 ? 8 : 0; layerblocksize = li.chunk_size + lb.chunk_size /* layer bitmap chunk (v7 only) */ + layerchannels*(blkheadersize + channelinfosize); if( !(e = psp_write_file_header(OUTFILE, &h)) && !(e = write_block(OUTFILE, rev.savevers, PSP_IMAGE_BLOCK, gia.chunk_size, gia.chunk_size)) && !(e = psp_write_image_attr_chunk(OUTFILE, rev.savevers, &gia)) && !(e = do_palette(pb, rev.savevers)) ) { if( !(e = GETFPOS(OUTFILE, &layerstartpos)) && !(e = write_block(OUTFILE, rev.savevers, PSP_LAYER_START_BLOCK, 0, blkheadersize+layerblocksize+layerchannels*G->channelsize)) && !(e = write_block(OUTFILE, rev.savevers, PSP_LAYER_BLOCK, li.chunk_size, layerblocksize+layerchannels*G->channelsize)) && !(e = psp_write_layer_info_chunk(OUTFILE, rev.savevers, &li)) ) { if( rev.savevers > PSP_SPEC5 ) e = psp7_write_layer_bitmap_chunk(OUTFILE, &lb); if( !e && (indata = malloc(G->channelsize)) ){ SETRECT(pb->theRect, 0, 0, pb->imageSize.h, pb->imageSize.v); pb->loPlane = pb->hiPlane = 0; pb->colBytes = 1; /* not interleaved */ pb->rowBytes = psprb; pb->data = indata; //pb->planeBytes = channelsize; // ignored }else e = memFullErr; } } return e; } OSErr write_continue(FormatRecordPtr pb, intptr_t *data){ OSErr e = noErr; PSP_channel_info_chunk ci; PSP_alpha_channel_info_chunk aci; static PSP7_alpha_channel_chunk ac; long count, i, *lp; FILEPOS pos, channelblockpos, alphablockpos; int len; Handle hname; /* process one plane of image data */ if(pb->loPlane == firstalpha && !(e = GETFPOS(OUTFILE, &alphabankpos)) ){ /* beginning of alpha bank */ abi.chunk_size = rev.savevers > PSP_SPEC5 ? 6 : 2; abi.alpha_count = pb->planes - firstalpha; ac.bitmap_count = ac.channel_count = 1; ac.chunk_size = rev.savevers > PSP_SPEC5 ? 8 : 0; alphabanksize = abi.chunk_size + abi.alpha_count*( blkheadersize + alphainfosize /* alpha channel header, alpha channel info chunk */ + ac.chunk_size /* alpha channel chunk (v7 only) */ + blkheadersize + channelinfosize /* channel sub-block, channel info chunk */ ); if( !(e = write_block(OUTFILE, rev.savevers, PSP_ALPHA_BANK_BLOCK, abi.chunk_size, alphabanksize + abi.alpha_count*G->channelsize)) ) e = psp_write_alpha_bank_info_chunk(OUTFILE, rev.savevers, &abi); } if( !e && pb->loPlane >= firstalpha && !(e = GETFPOS(OUTFILE, &alphablockpos)) ){ /* it's an alpha channel */ memset(aci.name, 0, PSP_LAYER_NAME_MAX); // get correct channel name from Photoshop if( PIGETPROPERTY && !PIGETPROPERTY(kPhotoshopSignature, propChannelName, pb->planeMap[pb->loPlane], NULL, &hname) ){ len = PIGETHANDLESIZE(hname); memcpy(aci.name, PILOCKHANDLE(hname,false), len); // aci.name[len] = 0; dbg(aci.name); PIDISPOSEHANDLE(hname); }else // can't get real name, use boring default name len = sprintf(aci.name, "Alpha %d", pb->loPlane-firstalpha+1); aci.chunk_size = alphainfosize; if(rev.savevers > PSP_SPEC5){ aci.chunk_size += len; // it's a variable length string alphabanksize += len; } SETRECT(aci.alpha_rect, 0, 0, pb->imageSize.h, pb->imageSize.v); aci.saved_rect = aci.alpha_rect; aci.bitmap_count = aci.channel_count = 1; if( !(e = write_block(OUTFILE, rev.savevers, PSP_ALPHA_CHANNEL_BLOCK, aci.chunk_size, aci.chunk_size + ac.chunk_size + blkheadersize + channelinfosize + G->channelsize)) && !(e = psp_write_alpha_channel_info_chunk(OUTFILE, rev.savevers, &aci)) ) { DPRINTF("alpha channel block [%d] @ %d", pb->loPlane, alphablockpos); if( rev.savevers > PSP_SPEC5 ) e = psp7_write_alpha_channel_chunk(OUTFILE, &ac); } }else{ /* for bitmap mode, invert data */ if(pb->imageMode == plugInModeBitmap) for(i = G->channelsize/sizeof(long), lp = (long*)pb->data; i--;) *lp++ ^= -1; } ci.chunk_size = channelinfosize; ci.comp_length = G->channelsize; // for compressed files, this is fixed up later. ci.uncomp_length = G->channelsize; ci.bitmap_type = pb->loPlane < firstalpha ? PSP_DIB_IMAGE : PSP_DIB_ALPHA_MASK; if(pb->transparencyPlane && pb->loPlane == imageplanes) ci.bitmap_type = PSP_DIB_TRANS_MASK; ci.channel_type = pb->imageMode == plugInModeRGBColor && pb->loPlane < imageplanes ? PSP_CHANNEL_RED+pb->loPlane : PSP_CHANNEL_COMPOSITE; if( !(e = GETFPOS(OUTFILE, &channelblockpos)) && !(e = write_block(OUTFILE, rev.savevers, PSP_CHANNEL_BLOCK, ci.chunk_size, ci.chunk_size + G->channelsize)) && !(e = psp_write_channel_info_chunk(OUTFILE, rev.savevers, &ci)) && !(e = compress_channel(OUTFILE, rev.savecomp, indata, pb->imageSize.v, psprb, &count)) ) { DPRINTF("channelblockpos=%d\ncompress_channel[%d] : %d bytes", channelblockpos, pb->loPlane, count); ci.comp_length = count; if(pb->loPlane < firstalpha) layerblocksize += ci.comp_length; else alphabanksize += ci.comp_length; if(rev.savecomp != PSP_COMP_NONE && !(e = GETFPOS(OUTFILE, &pos)) ){ /* fix up channel block header, channel info chunk */ DPRINTF("FIXUP: PSP_CHANNEL_BLOCK @ %d, ci.chunk_size=%d, ci.chunk_size + ci.comp_length=%d", channelblockpos, ci.chunk_size, ci.chunk_size + ci.comp_length); if( !(e = SETFPOS(OUTFILE, fsFromStart, channelblockpos)) && !(e = write_block(OUTFILE, rev.savevers, PSP_CHANNEL_BLOCK, ci.chunk_size, ci.chunk_size + ci.comp_length)) ) e = psp_write_channel_info_chunk(OUTFILE, rev.savevers, &ci); /* fix up alpha channel block header, alpha channel info chunk */ if( !e && pb->loPlane >= firstalpha && !(e = SETFPOS(OUTFILE, fsFromStart, alphablockpos)) ) { DPRINTF("FIXUP: PSP_ALPHA_CHANNEL_BLOCK @ %d, aci.chunk_size=%d, block len=%d", alphablockpos, aci.chunk_size, aci.chunk_size + ac.chunk_size + blkheadersize + channelinfosize + ci.comp_length); e = write_block(OUTFILE, rev.savevers, PSP_ALPHA_CHANNEL_BLOCK, aci.chunk_size, aci.chunk_size + ac.chunk_size + blkheadersize + channelinfosize + ci.comp_length); } if(!e) e = SETFPOS(OUTFILE, fsFromStart, pos); } } if(pb->loPlane < pb->planes-1){ pb->hiPlane = ++pb->loPlane; pb->data = indata; }else{ pb->data = NULL; SETRECT(pb->theRect, 0,0,0,0); } return e; } OSErr write_finish(FormatRecordPtr pb, intptr_t *data){ OSErr e = noErr; if(rev.savecomp != PSP_COMP_NONE){ /* fix up block header fields with correct compressed data size */ if( !(e = SETFPOS(OUTFILE, fsFromStart, layerstartpos)) && !(e = write_block(OUTFILE, rev.savevers, PSP_LAYER_START_BLOCK, 0, blkheadersize+layerblocksize)) ) e = write_block(OUTFILE, rev.savevers, PSP_LAYER_BLOCK, li.chunk_size, layerblocksize); } if(rev.savecomp != PSP_COMP_NONE || rev.savevers > PSP_SPEC5){ /* except in v5, always do fixup for alpha bank block, due to variable length channel names */ if( pb->planes > firstalpha && !(e = SETFPOS(OUTFILE, fsFromStart, alphabankpos)) ) e = write_block(OUTFILE, rev.savevers, PSP_ALPHA_BANK_BLOCK, abi.chunk_size, alphabanksize); } if(indata) free(indata); return e; }