/* This file is part of "5_6_5", a filter plugin for Adobe Photoshop Copyright (C) 2002-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 */ /* Quantise each channel of an image to RGB samples of 5/6/5 or 5/5/5 bits */ #include #include #include #include "world.h" #include "PIFilter.h" #include "PIAbout.h" #ifdef __SC__ #include #define malloc NewPtr #define free DisposePtr // we need to avoid global data in the 68K build. // so, all global references are indirected via the "data" parameter // that Photoshop maintains for the plugin, and passes to it. #define FAKEGLOBALS #define G(F) ((struct globals*)*data)->F // #include "sprintf_tiny.h" #define SPRINTF sprintf_tiny #else #define SPRINTF sprintf #define G #endif #include "entry.h" #include "dbg.h" #ifdef FAKEGLOBALS struct globals{ #endif long toprow, chunkrows; #ifdef FAKEGLOBALS }; #endif typedef struct{ int r,g,b; } ERRTYPE; DLLEXPORT MACPASCAL void ENTRYPOINT(short selector, FilterRecordPtr epb, intptr_t *data, short *result); void DoAbout (AboutRecordPtr); void DoPrepare (FilterRecordPtr epb, intptr_t *data); OSErr DoStart (FilterRecordPtr epb, intptr_t *data); void process(FilterRecordPtr pb); OSErr DoContinue (FilterRecordPtr epb, intptr_t *data); void DoFinish (FilterRecordPtr epb, intptr_t *data); void RequestNext (FilterRecordPtr epb, long); void startplane(FilterRecordPtr pb, intptr_t *data); int clamp(int v); void fsdither(unsigned char *in, unsigned char *out, ERRTYPE *e0, ERRTYPE *e1); long errbytes; ERRTYPE *err0,*err1; DLLEXPORT MACPASCAL void ENTRYPOINT(short selector, FilterRecordPtr pb, intptr_t *data, short *result) { OSErr e = noErr; #ifdef FAKEGLOBALS if(selector != filterSelectorAbout && !*data){ BufferID tempId; if( (*result = PS_BUFFER_ALLOC(sizeof(struct globals), &tempId)) ) return; *data = (intptr_t)PS_BUFFER_LOCK(tempId, true); } #endif EnterCodeResource(); switch (selector){ case filterSelectorAbout: DoAbout((AboutRecordPtr)pb); break; case filterSelectorParameters: break; case filterSelectorPrepare: DoPrepare(pb,data); break; case filterSelectorStart: e = DoStart(pb,data); break; case filterSelectorContinue: e = DoContinue(pb,data); break; case filterSelectorFinish: DoFinish(pb,data); break; default: e = filterBadParameters; } *result = e; ExitCodeResource(); } void DoPrepare(FilterRecordPtr pb, intptr_t *data){ /* maxSpace lets the plug-in know the maximum number of bytes of information it can expect to be able to access at once (input area size + output area size + mask area size + bufferSpace). bufferSpace If the plug-in is planning on allocating any large internal buffers or tables, it should set this field during the filterSelectorPrepare call to the number of bytes it is planning to allocate. Photoshop will then try to free up the requested amount of space before calling the filterSelectorStart routine. What this means for us is, since we're NOT planning to allocate any buffers (we only need access to input data and output data), we can work out how many rows it would be prudent to request at one time: */ //pb->maxSpace = 2 * chunkrows * pb->planes * ((pb->imageSize.h * pb->depth + 7)/8); G(chunkrows) = (pb->maxSpace/4) / (pb->planes*(long)pb->imageSize.h); if(G(chunkrows) > (pb->filterRect.bottom - pb->filterRect.top)) G(chunkrows) = pb->filterRect.bottom - pb->filterRect.top; // tell Photoshop how much we really need pb->maxSpace = 2*G(chunkrows)*(pb->planes*(long)pb->imageSize.h) + (64L<<10); // 64K slop } void RequestNext(FilterRecordPtr pb,long toprow){ /* Request next block of the image */ pb->inLoPlane = pb->outLoPlane = 0; pb->inHiPlane = pb->outHiPlane = 2; pb->inRect.left = pb->outRect.left = pb->filterRect.left; pb->inRect.right = pb->outRect.right = pb->filterRect.right; pb->inRect.top = pb->outRect.top = toprow; pb->inRect.bottom = toprow + G(chunkrows); if(pb->inRect.bottom > pb->filterRect.bottom) pb->inRect.bottom = pb->filterRect.bottom; pb->outRect.bottom = pb->inRect.bottom; // SPRINTF(s,"RequestNext inRect = %d,%d,%d,%d",pb->inRect.left,pb->inRect.top,pb->inRect.right,pb->inRect.bottom); dbg(s); } void startplane(FilterRecordPtr pb, intptr_t *data) { RequestNext(pb,G(toprow) = pb->filterRect.top); if(errbytes){ memset(err0,0,errbytes); memset(err1,0,errbytes); } } OSErr DoStart(FilterRecordPtr pb, intptr_t *data) { if(pb->planes == 3){ if(1){ /* get error buffers for F-S dither */ errbytes = sizeof(ERRTYPE)*(pb->imageSize.h+2); if( !(err0 = malloc(errbytes)) || !(err1 = malloc(errbytes)) ){ dbg("Can't allocate err0/err1!"); return memFullErr; } }else errbytes = 0; startplane(pb,data); return noErr; }else return filterBadMode; } OSErr DoContinue (FilterRecordPtr pb, intptr_t *data) { /* See if user has aborted the operation */ if (pb->abortProc()) return userCanceledErr; else{ pb->progressProc(pb->inLoPlane*(pb->filterRect.bottom - pb->filterRect.top) + G(toprow) - pb->filterRect.top, pb->planes*(pb->filterRect.bottom - pb->filterRect.top)); // SPRINTF(s,"RequestNext inRect = %d,%d,%d,%d",pb->inRect.left,pb->inRect.top,pb->inRect.right,pb->inRect.bottom); dbg(s); // got a chunk of the image; process it process(pb); G(toprow) += G(chunkrows); if(G(toprow) >= pb->filterRect.bottom){ pb->inRect.left = pb->inRect.right = pb->inRect.top = pb->inRect.bottom = 0 ; pb->outRect = pb->maskRect = pb->inRect; }else RequestNext(pb,G(toprow)); return noErr; } } void DoFinish(FilterRecordPtr pb, intptr_t *data) { if(errbytes){ free(err0); free(err1); } } int clamp(int v){ return v < 0 ? 0 : (v > 0xff ? 0xff : v); } void fsdither(unsigned char *in, unsigned char *out, ERRTYPE *e0, ERRTYPE *e1) { ERRTYPE val,targ; // compute target RGB (taking accumulated error into account) targ.r = in[0] + e0[0].r/16; targ.g = in[1] + e0[0].g/16; targ.b = in[2] + e0[0].b/16; // get output RGB (FIXME: doesn't extend correctly if xBITS < 4) val.r = clamp(targ.r + (1<<(7-RBITS))) & -(1<<(8-RBITS)); val.r |= val.r >> RBITS; val.g = clamp(targ.g + (1<<(7-GBITS))) & -(1<<(8-GBITS)); val.g |= val.g >> GBITS; val.b = clamp(targ.b + (1<<(7-BBITS))) & -(1<<(8-BBITS)); val.b |= val.b >> BBITS; // compute error targ.r -= val.r; targ.g -= val.g; targ.b -= val.b; // distribute error to neighbourhood, per Floyd-Steinberg (1975): // * 7 // 3 5 1 e0[ 1].r += 7*targ.r; e0[ 1].g += 7*targ.g; e0[ 1].b += 7*targ.b; e1[-1].r += 3*targ.r; e1[-1].g += 3*targ.g; e1[-1].b += 3*targ.b; e1[ 0].r += 5*targ.r; e1[ 0].g += 5*targ.g; e1[ 0].b += 5*targ.b; e1[ 1].r += targ.r; e1[ 1].g += targ.g; e1[ 1].b += targ.b; out[0] = val.r; out[1] = val.g; out[2] = val.b; } void process(FilterRecordPtr pb) { ERRTYPE *e0,*e1,*t; unsigned char *inrow,*outrow,*inp,*outp; long rows,cols,ncols = pb->inRect.right - pb->inRect.left; // actually munge the data. // each input pixel (byte) is quantised to sixteen values: 0,17,34,...,255 // (this slightly increases contrast, as input values are effectively multiplied by 17/16) for(inrow=pb->inData,outrow=pb->outData,rows=pb->inRect.bottom-pb->inRect.top ; rows-- ; inrow+=pb->inRowBytes,outrow+=pb->outRowBytes){ for(inp=inrow,outp=outrow,cols=ncols,e0=err0,e1=err1 ; cols-- ; inp += 3,outp += 3) fsdither(inp,outp,++e0,++e1); if(errbytes){ t = err0; err0 = err1; err1 = t; memset(err1,0,errbytes); // clear new error row } } }