/* 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 #include #include "icoformat.h" #include "ico_io.h" #include "ui.h" #include "png.h" #include "PIProperties.h" OSErr readicon(FormatRecordPtr pb,ICONDIRENTRY *pide, ICONIMAGE *pii); OSErr readICO(FormatRecordPtr pb,ICONDIRENTRY *pide, ICONIMAGE *pii); OSErr readctable(FormatRecordPtr pb, ICONIMAGE *pii); BufferID outBufferID; // may be set by readpng unsigned char *outdata = NULL; // may be set by readpng int iconcount; StringPtr name; FILEREF file; OSErr readctable(FormatRecordPtr pb, ICONIMAGE *pii){ OSErr e = noErr; RGBQUAD *ctab, *p; FILECOUNT count; int i,ncols; if(pii->icHeader.biBitCount <= 8){ ncols = pii->icHeader.biClrUsed ? pii->icHeader.biClrUsed : 1 << pii->icHeader.biBitCount; if( (ctab = (RGBQUAD*)malloc(count = sizeof(RGBQUAD)*ncols)) ){ if( !(e = FSREAD((FILEREF)pb->dataFork, &count, ctab)) ){ for(i = 0, p = ctab; i < ncols; ++i, ++p){ pb->redLUT[i] = p->rgbRed; pb->greenLUT[i] = p->rgbGreen; pb->blueLUT[i] = p->rgbBlue; } // pad out the rest of Photoshop's colour table with black for(; i < 0x100; ++i) pb->redLUT[i] = pb->greenLUT[i] = pb->blueLUT[i] = 0; #ifdef PS6 pb->lutCount = ncols; // PS 5.5 or later #endif } free(ctab); }else e = memFullErr; } return e; } // it's a traditional (pre-Vista) ICO with a BITMAPINFOHEADER OSErr readICO(FormatRecordPtr pb, ICONDIRENTRY *pide, ICONIMAGE *pii){ FILECOUNT count; int outrb, inrb, transrb, i, j, flipv, transmask, instep, pix, rowslop, transxor, mask, hasalpha, trans_idx = -1, idx; unsigned char *inbuf, *in, *out, *inrow, *outrow, *trans, *transrow, palette_use[0x100]; OSErr e = noErr; BITMAPINFOHEADER *hdr = &pii->icHeader; hasalpha = hdr->biBitCount == 32; if( !(e = readctable(pb,pii)) ){ // normally, the ICO bitmaps are stored bottom row first // but check for a negative height, which would signal a top-down bitmap flipv = hdr->biHeight > 0; pb->imageSize.h = hdr->biWidth; pb->imageSize.v = hdr->biHeight/2; if(hdr->biBitCount == 16 || hdr->biBitCount == 24 || hdr->biBitCount == 32) { // reading an RGB mode image pb->imageMode = plugInModeRGBColor; pb->depth = 8; pb->planes = 3 + hasalpha + 1; instep = hdr->biBitCount/8; // bytes per pixel rowslop = 0; } else if(hdr->biPlanes == 1 && hdr->biBitCount >= 1 && hdr->biBitCount <= 8){ // reading an Indexed mode image pb->imageMode = plugInModeIndexedColor; pb->depth = 8; pb->planes = hdr->biPlanes + 1; instep = hdr->biPlanes; // bytes per packed pixel rowslop = 16/hdr->biBitCount; // end-of-row slop for unpacking image and transparency rows } else return formatCannotRead; /* adding 0.5 achieves rounding */ pb->imageHRes = hdr->biXPelsPerMeter ? (Fixed)(0.5+(hdr->biXPelsPerMeter / (39.37/65536.))) : DEFAULT_RES; pb->imageVRes = hdr->biYPelsPerMeter ? (Fixed)(0.5+(hdr->biYPelsPerMeter / (39.37/65536.))) : DEFAULT_RES; SETRECT(pb->theRect, 0, 0, pb->imageSize.h, pb->imageSize.v); pb->loPlane = 0; pb->hiPlane = pb->planes-1; pb->colBytes = pb->planes; pb->rowBytes = outrb = ((long)pb->imageSize.h*pb->depth*pb->planes+7)/8 + rowslop; // note extra slop needed for unpacking! pb->planeBytes = 1; // planes are interleaved // pb->planeBytes = outrb*(long)pb->imageSize.v; #ifdef PIFmtCanWriteTransparency if( pb->imageMode == plugInModeRGBColor && hasalpha ) pb->transparencyPlane = 3; // PS 6.0 or later #endif transxor = 0; // compute input row stride (XOR bitmap) inrb = ICONROWBYTES( (long)pb->imageSize.h*hdr->biBitCount*hdr->biPlanes ); // compute transparency row stride (AND bitmap), 1 plane of 1 bit mask transrb = ICONROWBYTES(pb->imageSize.h); // allocate a buffer for output data e = PS_BUFFER_ALLOC(outrb*(long)pb->imageSize.v, &outBufferID); if(e == noErr) { DBG("got output buffer"); pb->data = outdata = (unsigned char*)PS_BUFFER_LOCK(outBufferID, true); // tell Photoshop where it is count = (inrb+transrb)*(long)pb->imageSize.v; // allocate buffer for reading if( (inbuf = (unsigned char*)malloc(count)) ){ DBG("got input buffer"); // now read all image data if( !(e = FSREAD((FILEREF)pb->dataFork, &count, inbuf)) ){ #define IS_TRANS (*trans & transmask) #define NEXT_TRANS \ if(!(transmask >>= 1)){ \ ++trans; \ transmask = 0x80; \ } #define TRANS_PIXEL \ *out++ = transxor ^ (IS_TRANS ? 0xff : 0); \ NEXT_TRANS if(pb->imageMode == plugInModeIndexedColor){ // preprocess image, looking for an unused transparent index memset(palette_use, 0, 0x100); for(j = pb->imageSize.v, inrow = inbuf, transrow = inbuf + inrb*(long)pb->imageSize.v ; j-- ; inrow += inrb, transrow += transrb) { transmask = 0x80; // process one row for(in = inrow, trans = transrow, i = 0; i < pb->imageSize.h; in += instep) { switch(hdr->biBitCount){ case 1: // unpack eight 1-bit pixels from input byte for(mask = 0x100; mask >>= 1;){ if(!IS_TRANS) palette_use[ (*in & mask) != 0 ] = 1; NEXT_TRANS; } i += 8; break; case 4: // unpack two 4-bit pixels from input byte if(!IS_TRANS) palette_use[ *in >> 4 ] = 1; NEXT_TRANS; if(!IS_TRANS) palette_use[ *in & 0xf ] = 1; NEXT_TRANS; i += 2; break; case 8: // copy one 8-bit pixel if(!IS_TRANS) palette_use[ *in ] = 1; NEXT_TRANS; ++i; break; } } } // find unused index for(i = 0; i < 0x100; ++i) if(!palette_use[i]){ trans_idx = i; pb->transparentIndex = trans_idx; break; } } outrow = outdata; if(flipv){ outrow += (long)(pb->imageSize.v-1)*outrb; outrb = -outrb; } for(j = pb->imageSize.v, inrow = inbuf, transrow = inbuf + inrb*(long)pb->imageSize.v ; j-- ; inrow += inrb, outrow += outrb, transrow += transrb) { //DPRINTF("row: j=%d inrow=%#lx trans=%#lx outrow=%#lx",j,inrow,trans,outrow); transmask = 0x80; // process one row for(in = inrow, out = outrow, trans = transrow, i = 0; i < pb->imageSize.h; in += instep) { switch(hdr->biBitCount){ case 1: // unpack eight 1-bit pixels from input byte for(mask = 0x100; mask >>= 1;){ idx = (*in & mask) != 0; *out++ = IS_TRANS && trans_idx != -1 ? trans_idx : idx; TRANS_PIXEL; } i += 8; break; case 4: // unpack two 4-bit pixels from input byte idx = *in >> 4; *out++ = IS_TRANS && trans_idx != -1 ? trans_idx : idx; TRANS_PIXEL; idx = *in & 0xf; *out++ = IS_TRANS && trans_idx != -1 ? trans_idx : idx; TRANS_PIXEL; i += 2; break; case 8: // copy one 8-bit pixel *out++ = IS_TRANS && trans_idx != -1 ? trans_idx : *in; TRANS_PIXEL; ++i; break; case 16: // expand a 16-bit pixel into Photoshop's RGB planes pix = (in[1] << 8) | in[0]; *out++ = pix >> 10; *out++ = (pix >> 5) & 0x1f; *out++ = pix & 0x1f; ++i; TRANS_PIXEL; break; case 24: *out++ = in[2]; //R *out++ = in[1]; //G *out++ = in[0]; //B ++i; TRANS_PIXEL; break; case 32: *out++ = in[2]; //R *out++ = in[1]; //G *out++ = in[0]; //B *out++ = in[3]; //alpha ++i; TRANS_PIXEL; break; } } } } free(inbuf); } DBG("done loop"); } } return e; } #define IS_PNG(pii) ((pii)->icHeader.biSize == PNG_MAGIC) OSErr readicon(FormatRecordPtr pb, ICONDIRENTRY *pide, ICONIMAGE *pii){ return IS_PNG(pii) ? readPNG(pb, pide, pii) : readICO(pb, pide, pii); } OSErr getimageinfo(FILEREF f, ICONDIRENTRY *pide, ICONIMAGE *pii, int idx, const char **desc){ OSErr e; *desc = ""; // empty string (ordinary ICO) if( !(e = SETFPOS(f, fsFromStart, FIRST_ICONDIRENTRY + SIZEOF_ICONDIRENTRY*idx)) && !(e = read_icondirentry(f, pide)) ) { if( !(e = SETFPOS(f, fsFromStart, pide->dwImageOffset)) && !(e = read_bitmapinfoheader(f, &pii->icHeader)) ) { if(IS_PNG(pii)){ // it's a PNG getPNGinfo(f, pide, pii, desc); // may update description string }else{ // it's an ICO, with BITMAPINFOHEADER // position file past header if(pii->icHeader.biSize > SIZEOF_BMIHEADER) e = SETFPOS(f, fsFromMark, pii->icHeader.biSize - SIZEOF_BMIHEADER); } } } return e; } OSErr read_start(FormatRecordPtr pb){ ICONDIR idir; OSErr e; ICONIMAGE ii; const char *desc; outdata = NULL; file = (FILEREF)pb->dataFork; /* used in ui.c */ if( !(e = read_icondir((FILEREF)pb->dataFork, &idir)) ){ // sniff for filetypes sometimes renamed as ICO if( pb->revertInfo || ( (idir.idReserved != 0x4d42 || warnformat(pb, "BMP")) && (idir.idReserved != 0x4947 || warnformat(pb, "GIF")) && (idir.idReserved != 0xd8ff || warnformat(pb, "JPEG")) // relax the check on idType and just issue a warning if it's unexpected && (idir.idType == IDTYPE_ICON || idir.idType == IDTYPE_CUR || warntype(pb, idir.idType)) && idir.idCount) ) { whichicon = 0; #if !(defined(__LP64__) && defined(MAC_ENV) && __LP64__) name = pb->fileSpec->name; #endif iconcount = idir.idCount; if(pb->revertInfo || idir.idCount == 1 || pickicondialog(pb)){ struct revert_info *rev; if(pb->revertInfo){ rev = (struct revert_info*)PS_LOCKHANDLE(pb->revertInfo, false); whichicon = rev->whichicon; } #ifdef PS6 pb->imageIndex = whichicon+1; // PS 6.0 #endif if( !(e = getimageinfo((FILEREF)pb->dataFork, idir.idEntries, &ii, whichicon, &desc)) ) e = readicon(pb, idir.idEntries, &ii); if(!e && idir.idType == IDTYPE_CUR){ PS_SETPROPERTY(kPhotoshopSignature, propRulerOriginH, 0, idir.idEntries[whichicon].xHotSpot << 16, NULL); PS_SETPROPERTY(kPhotoshopSignature, propRulerOriginV, 0, idir.idEntries[whichicon].yHotSpot << 16, NULL); } if(!e && !pb->revertInfo){ pb->revertInfo = PS_NEWHANDLE(sizeof(struct revert_info)); if(pb->revertInfo){ rev = (struct revert_info*)PS_LOCKHANDLE(pb->revertInfo, false); rev->whichicon = whichicon; rev->png = IS_PNG(&ii); } } if(pb->revertInfo) PS_UNLOCKHANDLE(pb->revertInfo); }else e = userCanceledErr; return e; } e = formatCannotRead; // icondir.idType != 1 or idir.idCount == 0 } return e; } OSErr read_continue(FormatRecordPtr pb){ pb->data = NULL; //SETRECT(pb->theRect,0,0,0,0); return noErr; } OSErr read_finish(FormatRecordPtr pb){ if(outdata) PS_BUFFER_FREE(outBufferID); return noErr; }