/* This file is part of icoformat, a Windows Icon (ICO) File Format plugin for Adobe Photoshop Copyright (C) 2002-2010 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 "icoformat.h" #include "ico_io.h" #include "PIProperties.h" int inrb; // used by writepng unsigned char *indata = NULL; // used by writepng static BufferID inBufferID; // This value is stored in the BITMAPINFOHEADER's biSizeImage field. // It is the uncompressed byte count of the image data // (XOR data & AND data together), taking into account row padding. long iconsizeimage(FormatRecordPtr pb, intptr_t *data){ return (long)pb->imageSize.v * ( ICONROWBYTES((long)pb->imageSize.h*G(nbits)) // XOR bitmap data (colour image) + ICONROWBYTES((long)pb->imageSize.h*1) ); // AND bitmap data (1-bit transparency mask) } // This value is stored in the ICONDIRENTRY's dwBytesInRes field. // It is the total byte count of the icon's bitmap header and data // (excluding the icon file's header and directory). long iconbytes(FormatRecordPtr pb, intptr_t *data){ return SIZEOF_BMIHEADER + sizeof(RGBQUAD)*G(ctabsize) // colour table + iconsizeimage(pb, data); // AND bitmap data (1-bit transparency mask) } OSErr writecolourtable(FormatRecordPtr pb, intptr_t *data){ FILECOUNT count; RGBQUAD *ctab, *p; int i; OSErr e = noErr; if(G(ctabsize)){ if( (ctab = (RGBQUAD*)malloc(count = sizeof(RGBQUAD)*G(ctabsize))) ){ i = 0; p = ctab; switch(pb->imageMode){ case plugInModeBitmap: // put black and white entries in colour table for(i = 0, p = ctab; i < G(ncols); ++i, ++p){ p->rgbRed = p->rgbGreen = p->rgbBlue = i-1; p->rgbReserved = 0; } break; case plugInModeGrayScale: // create grey scale entries in colour table, for remapped indices for(i = 0, p = ctab; i < G(ncols); ++i, ++p){ p->rgbRed = p->rgbGreen = p->rgbBlue = G(oldindex)[i]; p->rgbReserved = 0; } break; case plugInModeIndexedColor: // create colour table entries, for remapped indices for(i = 0, p = ctab; i < G(ncols); ++i, ++p){ p->rgbRed = pb->redLUT[G(oldindex)[i]]; p->rgbGreen = pb->greenLUT[G(oldindex)[i]]; p->rgbBlue = pb->blueLUT[G(oldindex)[i]]; p->rgbReserved = 0; } break; } // zero out rest of colour table if necessary for(; i < G(ctabsize); ++i, ++p) p->rgbRed = p->rgbGreen = p->rgbBlue = p->rgbReserved = 0; /* ensure that the index we are using for transparent pixels, is colour (0,0,0) */ if(G(black) != -1) ctab[G(black)].rgbRed = ctab[G(black)].rgbGreen = ctab[G(black)].rgbBlue = 0; e = FSWRITE((FILEREF)pb->dataFork, &count, ctab); free(ctab); DBG("done colour table"); }else e = memFullErr; } return e; } OSErr write_start(FormatRecordPtr pb, intptr_t *data, int idType){ ICONDIR idir; ICONDIRENTRY ide; BITMAPINFOHEADER bih; OSErr e = noErr; int planes; indata = NULL; idir.idType = idType; idir.idReserved = 0; idir.idCount = 1; ide.bWidth = pb->imageSize.h; // Width, in pixels, of the image ide.bHeight = pb->imageSize.v; // Height, in pixels, of the image ide.bColorCount = G(ctabsize); // Number of colors in image (0 if >=8bpp) ide.bReserved = 0; // Reserved ( must be 0) bih.biPlanes = 1; // Color Planes bih.biBitCount = G(nbits); // Bits per pixel if(idType == IDTYPE_ICON){ // Icon ide.wPlanes = bih.biPlanes; ide.wBitCount = bih.biBitCount; } else{ // Cursor intptr_t xhs, yhs; if(!PS_GETPROPERTY(kPhotoshopSignature, propRulerOriginH, 0, &xhs, NULL) && !PS_GETPROPERTY(kPhotoshopSignature, propRulerOriginV, 0, &yhs, NULL)) { ide.xHotSpot = (xhs+0x8000)/0x10000; ide.yHotSpot = (yhs+0x8000)/0x10000; } else{ ide.xHotSpot = ide.yHotSpot = 0; } } ide.dwImageOffset = SIZEOF_ICONDIR + SIZEOF_ICONDIRENTRY; // Where in the file is this image? ide.dwBytesInRes = iconbytes(pb, data); // How many bytes in this resource? // dwBytesInRes will be incorrect for PNG, // but is fixed up after PNG size is known (see writePNG()) SETRECT(pb->theRect, 0, 0, pb->imageSize.h, pb->imageSize.v); planes = pb->planes; if(G(usepng)){ // ensure image data is compatible with PNG supported formats switch(pb->imageMode){ case plugInModeBitmap: case plugInModeIndexedColor: // PNG palette images can handle only 1 plane planes = 1; break; // RGB, Grey Scale only have alpha channel for transparency case plugInModeGrayScale: planes = 1 + (LAYERTRANS != 0); break; case plugInModeRGBColor: planes = 3 + (LAYERTRANS != 0); break; } } pb->loPlane = 0; pb->hiPlane = planes-1; pb->colBytes = planes << (pb->depth == 16); pb->rowBytes = inrb = ((long)pb->imageSize.h*pb->depth*planes+7)/8; pb->planeBytes = 1; /* interleaved */ if( !(e = write_icondir((FILEREF)pb->dataFork, &idir)) && !(e = write_icondirentry((FILEREF)pb->dataFork, &ide)) ) { if(G(usepng)) e = writePNGheader(pb, planes); else{ // see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_1rw2.asp // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dngdi/html/msdn_dibs2.asp bih.biSize = SIZEOF_BMIHEADER; bih.biWidth = pb->imageSize.h; bih.biHeight = 2*pb->imageSize.v; // combined height of XOR and AND (transparency) bitmaps bih.biCompression = 0; bih.biSizeImage = iconsizeimage(pb, data); bih.biXPelsPerMeter = (LONG)(0.5+(pb->imageHRes * (39.37/65536.))); // adding 0.5 achieves rounding bih.biYPelsPerMeter = (LONG)(0.5+(pb->imageVRes * (39.37/65536.))); bih.biClrUsed = G(ctabsize); bih.biClrImportant = 0; if( !(e = write_bitmapinfoheader((FILEREF)pb->dataFork, &bih)) ) e = writecolourtable(pb, data); } } if( !e ){ // OK, all header info was written successfully. // now we can prepare to get image data... e = PS_BUFFER_ALLOC((long)inrb*pb->imageSize.v, &inBufferID); // tell Photoshop where to put image data to be written if(e == noErr) pb->data = indata = (unsigned char*)PS_BUFFER_LOCK(inBufferID, true); } return e; } OSErr writeICO(FormatRecordPtr pb, intptr_t *data){ int i, j, transmask, extraplanes, transval, shift, val, transplane, alphaplane = 0, transxor, imageplanes, istrans, ti = pb->transparentIndex, hasti, index, outrb = ICONROWBYTES((long)pb->imageSize.h*G(nbits)), transrb = ICONROWBYTES((long)pb->imageSize.h*1); FILECOUNT count; unsigned char *inrow, *outrow, *transrow, *in, *out, *trans, *outbuf; OSErr e = noErr; transxor = 0; imageplanes = pb->imageMode == plugInModeRGBColor ? 3 : 1; extraplanes = pb->planes - imageplanes; /* how many "alpha channels" additional to image */ transplane = imageplanes; // derive transparency from first "extra" channel hasti = pb->imageMode == plugInModeIndexedColor && ti < 256; if(LAYERTRANS){ /* we have layer transparency: write a 32-bit icon */ alphaplane = LAYERTRANS; /* take 8-bit alpha from layer transparency */ if(extraplanes > 1){ /* take 1-bit mask from first alpha channel */ if(transplane == LAYERTRANS) ++transplane; }else transplane = 0; // derive AND mask from layer transparency }else{ /* no layer transparency */ if(extraplanes > 1){ /* take 1-bit mask from first alpha channel; take alpha mask from second alpha channel */ transplane = imageplanes; alphaplane = imageplanes+1; }else if(!extraplanes) /* no extra channels */ transplane = 0; } if( (outbuf = (unsigned char*)malloc(count = (outrb+transrb)*pb->imageSize.v)) ){ DBG("got output buffer"); for(j = pb->imageSize.v, inrow = indata + inrb*(pb->imageSize.v-1), outrow = outbuf, transrow = outbuf + outrb*pb->imageSize.v ; j-- ; inrow -= inrb, outrow += outrb, transrow += transrb ){ if(pb->imageMode == plugInModeBitmap){ // bitmap data just gets copied verbatim memcpy(outrow, inrow, outrb); memset(transrow, 0, transrb); }else{ // do one row: convert data from inrow to outrow // and put transparency data in transrow transmask = 0x80; trans = transrow; shift = 8; val = 0; transval = transxor; #ifdef DEBUG p1 = s1; p2 = s2; #endif for( i = pb->imageSize.h, in = inrow, out = outrow ; i-- ; in += pb->planes ) { istrans = transplane && (in[transplane] & 0x80); switch(G(nbits)){ case 1: case 4: shift -= G(nbits); index = hasti || !istrans ? G(newindex)[*in] : G(black); #ifdef DEBUG sprintf(p1," %3d",index); p1 += 4; #endif val |= index << shift; if(!shift){ *out++ = val; val = 0; shift = 8; } break; case 8: index = hasti || !istrans ? G(newindex)[*in] : G(black); #ifdef DEBUG sprintf(p1," %3d",index); p1 += 4; #endif *out++ = index; break; case 24: if(istrans){ *out++ = 0; *out++ = 0; *out++ = 0; }else{ *out++ = in[2];//B *out++ = in[1];//G *out++ = in[0];//R } break; case 32: /* if pixel is completely transparent, make sure XOR data is ZERO. (inspired by Adrian Colegate). Likewise, with 8-bit alpha, set the AND mask transparent only for FULLY-transparent pixels. */ if( in[alphaplane] == 0 ){ if(!transplane) // if deriving AND mask from layer transparency istrans = 1; // set transparent *out++ = 0; *out++ = 0; *out++ = 0; }else{ if(!transplane) // if deriving AND mask from layer transparency istrans = 0; // set opaque *out++ = in[2];//B *out++ = in[1];//G *out++ = in[0];//R } *out++ = in[alphaplane];//alpha break; } // compute next bit of transparency mask // if a transparentIndex is known, use that; otherwise use existing mask channel. if( hasti ? *in == ti : istrans ) transval ^= transmask; #ifdef DEBUG sprintf(p2," %c",transval & transmask ? ' ' : '*'); p2 += 4; #endif if(!(transmask >>= 1)){ *trans++ = transval /* ^ transxor */ ; transval = transxor; transmask = 0x80; } } #ifdef DEBUG dbg(s1); dbg(s2); #endif // flush a partially packed byte if(shift != 8) *out = val; if(transmask != 0x80) *trans = transval /* ^ transxor */ ; } } e = FSWRITE((FILEREF)pb->dataFork, &count, outbuf); free(outbuf); }else e = memFullErr; return e; } OSErr write_continue(FormatRecordPtr pb, intptr_t *data){ // tell Photoshop we're done pb->data = NULL; SETRECT(pb->theRect, 0, 0, 0, 0); return G(usepng) ? writePNG(pb) : writeICO(pb, data); } OSErr write_finish(FormatRecordPtr pb){ if(indata) PS_BUFFER_FREE(inBufferID); return noErr; }