/* This file is part of radialdistort, a filter plugin for Adobe Illustrator Copyright (C) 1996-2005 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 "common.h" #if FAKE_GLOBALS struct globals{ #endif AIBlockSuite *sBlock; // AIFilterSuite *sFilter; AIMatchingArtSuite *sMatch; AIFixedMathSuite *sMath; #ifdef kAIRealMathSuite // AI 8.0 or later AIRealMathSuite *sRealMath; #endif AIArtSuite *sArt; AIPathSuite *sPath; AIMdMemorySuite *sMdMemory; ADMBasicSuite *sADMBasic; NUMBER insideradius; POINT centre; RECT bounds; AIPathSegment next_seg; AIArtHandle out_path; int seg_index; #if FAKE_GLOBALS }; #endif AIErr acquire_suites( SPMessageData *m ) { AIErr e; DebugStr("\pacquire_suites"); (e = acq_suite(m,kAIBlockSuite, kAIBlockVersion, &G(sBlock)) ) || (e = acq_suite(m,kAIMatchingArtSuite, kAIMatchingArtVersion, &G(sMatch)) ) || (e = acq_suite(m,kAIFixedMathSuite, kAIFixedMathVersion, &G(sMath)) ) #ifdef kAIRealMathSuite // AI 8.0 or later || (e = acq_suite(m,kAIRealMathSuite, kAIRealMathVersion, &G(sRealMath)) ) #endif || (e = acq_suite(m,kAIPathSuite, kAIPathVersion, &G(sPath)) ) || (e = acq_suite(m,kAIArtSuite, kAIArtVersion, &G(sArt)) ) || (e = acq_suite(m,kAIMdMemorySuite, kAIMdMemoryVersion, &G(sMdMemory)) ) || (e = acq_suite(m,kADMBasicSuite, kADMBasicSuiteVersion, &G(sADMBasic))) ; if(e) DebugStr("\perror in acquire_suites"); return e; } AIErr release_suites( SPMessageData *m ) { m->basic->ReleaseSuite(kAIBlockSuite, kAIBlockVersion); m->basic->ReleaseSuite(kAIMatchingArtSuite, kAIMatchingArtVersion); m->basic->ReleaseSuite(kAIFixedMathSuite, kAIFixedMathVersion); #ifdef kAIRealMathSuite // AI 8.0 or later m->basic->ReleaseSuite(kAIRealMathSuite, kAIRealMathVersion); #endif m->basic->ReleaseSuite(kAIPathSuite, kAIPathVersion); m->basic->ReleaseSuite(kAIArtSuite, kAIArtVersion); m->basic->ReleaseSuite(kAIMdMemorySuite, kAIMdMemoryVersion); m->basic->ReleaseSuite(kADMBasicSuite, kADMBasicSuiteVersion); return kNoErr; } AIErr plugin_startup ( SPInterfaceMessage *im ) { SPMessageData *m = &im->d; AIErr e; PlatformAddFilterData fd; AIFilterHandle fh; AIFilterSuite *sfilter; #if FAKE_GLOBALS m->globals = NewPtr(sizeof(struct globals)); if( !(e = MemError()) ) #endif { if( !(e = acq_suite(&im->d,kAIFilterSuite, kAIFilterVersion, &sfilter) ) ){ // Add our menu item to Illustrator's Filters menu fd.category = "\pTelegraphics"; fd.title = "\pRadial distort"; if( e = sfilter->AddFilter( im->d.self, "telegraphics-radialdistort", &fd, 0, &fh) ) dbg("plugin_startup: AddFilter failed"); im->d.basic->ReleaseSuite(kAIFilterSuite, kAIFilterVersion); } } return e; } /* AIErr unload( SPInterfaceMessage *im ) { m->globals = g; return kNoErr; } AIErr reload( SPInterfaceMessage *im ) { g = m->globals; return kNoErr; } */ AIErr plugin_shutdown( SPInterfaceMessage *im ) { #if FAKE_GLOBALS SPMessageData *m = &im->d; if(m->globals) DisposePtr(m->globals); #endif return kNoErr; } AIErr plugin_about( SPInterfaceMessage *im ) { SPMessageData *m = &im->d; AIErr e; if( !(e = acquire_suites(m)) ){ G(sADMBasic)->MessageAlert("radial distortion filter v0.21\nCopyright © 1996-2002 Toby Thain\nmailto:toby@telegraphics.com.au\nhttp://www.telegraphics.com.au"); release_suites(m); } return e; } /*---------*/ void mid(pt *a,pt *b,pt *c){ a->h = (b->h + c->h)/2; a->v = (b->v + c->v)/2; } void divide_curve(pt c[],pt z[]){ pt f; /* divide the curve into two; calculate the control points for each half */ z[0] = c[0]; z[6] = c[3]; mid(z+1,c,c+1); mid(&f,c+1,c+2); mid(z+5,c+2,c+3); mid(z+2,z+1,&f); mid(z+4,&f,z+5); mid(z+3,z+2,z+4); } /*---------*/ NUMBER tf_angle(AIFilterMessage *fm, POINT *p){ SPMessageData *m = &fm->d; // insideradius happens to be boxwidth/(2pi) return QUOTIENT(p->h - G(bounds).left,G(insideradius)) - (3*PICONSTANT)/2; } void tf(AIFilterMessage *fm, POINT *p){ SPMessageData *m = &fm->d; POINTLENGTHANGLE( G(insideradius) + G(bounds).top - p->v, tf_angle(fm,p), p ); p->h += G(centre).h; p->v += G(centre).v; } void rotate_dir(AIFilterMessage *fm, POINT *p, // on-curve point POINT *v, // tangent vector POINT *v2 // result ){ SPMessageData *m = &fm->d; MATRIX mtx; v2->h = v->h - p->h; v2->v = v->v - p->v; MATRIXSETROTATE(&mtx,tf_angle(fm,p) + (PICONSTANT/2) ); v2->h = PRODUCT( v2->h, QUOTIENT(G(bounds).top - p->v,G(insideradius)) + ONECONSTANT ); MATRIXXFORMPOINT(&mtx,v2,v2); // rotate direction } void tf_dir(AIFilterMessage *fm, POINT *p,POINT *v){ POINT v2; // the simplest way is to transform the direction // but leave the distance the same; i.e. rotate the direction vector // however, the transformation can result in shortened curve control vectors, // i.e. close to the top of the art // so perhaps we use the true transformed direction but obtain the length // by measuring the transformed vector endpoints rotate_dir(fm, p,v,&v2); tf(fm, p); tf(fm, v); /* r = FixDiv(G(sMath)->FixedPointLength(p,v),G(sMath)->FixedLength(v2.h,v2.v)); v2.h = FixMul(v2.h,r); v2.v = FixMul(v2.v,r); */ v->h = v2.h + p->h; v->v = v2.v + p->v; } void tf_curve(AIFilterMessage *fm, POINT c[],int corner,int depth){ SPMessageData *m = &fm->d; POINT z[7],p1,p2,tc[4],c1,c2,perp; /* if(!depth){ char s[0x100]; sprintf(s,"tf_curve (%g,%g) (%g,%g) (%g,%g) (%g,%g) corner:%d", c[0].h/(float)kFixedOne,c[0].v/(float)kFixedOne, c[1].h/(float)kFixedOne,c[1].v/(float)kFixedOne, c[2].h/(float)kFixedOne,c[2].v/(float)kFixedOne, c[3].h/(float)kFixedOne,c[3].v/(float)kFixedOne, corner); dbg(s); } */ // find midpoint of transformed curve tc[0] = c[0]; tc[1] = c[1]; tc[2] = c[2]; tc[3] = c[3]; tf_dir(fm, &tc[0],&tc[1]); tf_dir(fm, &tc[3],&tc[2]); // POSTSCRIPT Red book: curveto p.140 // p1 = p0 + c/3 // p2 = p1 + (c + b)/3 // p3 = p0 + c + b + a // c = 3(p1-p0) // b = 3(p2-p1)-c // a = p3-p0-c-b divide_curve(tc,z); p1 = z[3]; // find midpoint of untransformed curve divide_curve(c,z); p2 = z[3]; tf(fm, &p2); // transform it if( !depth || POINTCLOSE(&p1,&p2,TOLERANCE) ){ // close enough G(next_seg).p = tc[0]; G(next_seg).out = tc[1]; G(next_seg).corner = corner; G(sPath)->SetPathSegments( G(out_path), G(seg_index)++, 1, &G(next_seg) ); G(next_seg).in = tc[2]; G(next_seg).p = tc[3]; }else{ tf_curve(fm, z,corner,depth-1); // z[0..3] describe first half of untransformed curve tf_curve(fm, z+3,0,depth-1); // z[3..6] describe second half } } AIErr radial_distort( AIFilterMessage *fm, AIArtHandle path ){ SPMessageData *m = &fm->d; // meltParms **parms = (meltParms **) m->parameters; short count; AIErr e = kNoErr; AIPathSegment *in_segs,*seg,temp; AIBoolean closed; POINT c[4],temppt; if( !(e = G(sPath)->GetPathClosed( path, &closed )) && !(e = G(sPath)->GetPathSegmentCount( path, &count )) && count && !(e = G(sBlock)->AllocateBlock(sizeof(AIPathSegment)*count,&in_segs)) ){ if( !(e = G(sPath)->GetPathSegments( path, 0, count, in_segs )) && !(e = G(sPath)->SetPathSegmentCount( G(out_path) = path, 0 )) ){ /* {char s[0x100];int i; for(i=0,seg=in_segs;iin.h/(float)kFixedOne,seg->in.v/(float)kFixedOne, seg->p.h/(float)kFixedOne,seg->p.v/(float)kFixedOne, seg->out.h/(float)kFixedOne,seg->out.v/(float)kFixedOne, seg->corner); dbg(s); } } */ if(count == 1){ // path consists of a single point temppt = in_segs->p; tf_dir(fm, &in_segs->p,&in_segs->in); tf_dir(fm, &temppt,&in_segs->out); G(sPath)->SetPathSegments( G(out_path), 0, 1, in_segs ); }else{ G(seg_index) = 0; for (seg = in_segs; --count; seg++) { c[0] = seg->p; c[1] = seg->out; c[2] = seg[1].in; c[3] = seg[1].p; tf_curve(fm, c,seg->corner,MAX_DEPTH); } if(closed){ c[0] = seg->p; c[1] = seg->out; c[2] = in_segs->in; c[3] = in_segs->p; tf_curve(fm, c,seg->corner,MAX_DEPTH); // adjust in-direction of first segment G(sPath)->GetPathSegments( G(out_path), 0, 1, &temp ); temp.in = G(next_seg).in; G(sPath)->SetPathSegments( G(out_path), 0, 1, &temp ); }else // add final segment G(sPath)->SetPathSegments( G(out_path), G(seg_index)++, 1, &G(next_seg) ); } } G(sBlock)->DisposeBlock(in_segs); } return e; } AIErr filter_go( AIFilterMessage *fm ){ SPMessageData *m = &fm->d; AIArtHandle **matches,*p; long i, count; RECT b,bb; AIErr e = kNoErr; AIMatchingArtSpec spec;// = {kPathArt,kArtSelected,kArtSelected}; NUMBER boxwidth; char s[0x100]; if(!(e = acquire_suites(m))){ spec.type = kPathArt; spec.whichAttr = spec.attr = kArtSelected; if(!(e = G(sMatch)->GetMatchingArt( &spec, 1, &matches, &count )) && matches ) { void *deref; G(sMdMemory)->MdMemoryLock((AIMdMemoryHandle)matches,&deref); // compute overall bounding box of selected art p = deref; G(sArt)->GetArtBounds(*p++, &b ); for (i = count - 1; i--;) { G(sArt)->GetArtBounds(*p++, &bb ); RECTUNION( &b, &bb, &b ); } G(bounds) = b; G(centre).h = (b.left + b.right)/2; G(centre).v = b.top + ( G(insideradius) = QUOTIENT(b.right - b.left,2*PICONSTANT) ); /* sprintf(s,"centre=(%g,%g) insideradius=%g boxwidth=%g", G(centre).h/(float)kFixedOne, G(centre).v/(float)kFixedOne, G(insideradius)/(float)kFixedOne, G(boxwidth)/(float)kFixedOne ); dbg(s); */ for (i = count,p = deref; i--;) if(e = radial_distort( fm, *p++ )) break; /* here's a good place to update progress bar */ G(sMdMemory)->MdMemoryDisposeHandle((AIMdMemoryHandle)matches); } release_suites(m); } return e; }