/*********************************************************************************/ /* LIC 0.13 -- image filter plug-in for The Gimp program */ /* Copyright (C) 1996 Tom Bech */ /*===============================================================================*/ /* E-mail: tomb@ii.uib.no */ /* You can contact the original The Gimp authors at gimp@xcf.berkeley.edu */ /*===============================================================================*/ /* 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., 675 Mass */ /* Ave, Cambridge, MA 02139, USA. */ /*===============================================================================*/ /* In other words, you can't sue me for whatever happens while using this ;) */ /*********************************************************************************/ /* Changes (post 0.10): */ /* -> 0.11: Fixed a bug in the convolution kernels (Tom). */ /* -> 0.12: Added Quartic's bilinear interpolation stuff (Tom). */ /* -> 0.13 Changed some UI stuff causing trouble with the 0.60 release, added */ /* the (GIMP) tags and changed random() calls to rand() (Tom) */ /*********************************************************************************/ /* This plug-in implements the Line Integral Convolution (LIC) as described in */ /* Cabral et al. "Imaging vector fields using line integral convolution" in the */ /* Proceedings of ACM SIGGRAPH 93. Publ. by ACM, New York, NY, USA. p. 263-270. */ /* Some of the code is based on code by Steinar Haugen (thanks!), the Perlin */ /* noise function is practically ripped as is :) */ /*********************************************************************************/ /* @(GIMP) = @(GIMP_DEP) = @(GIMP_OBJ) = @(GIMP_LIB) = @(GIMP_AUTHOR) = @(GIMP_EMAIL) = @(GIMP_DESC) = @(GIMP_VERSION) = <0.13> @(GIMP_URL) = */ #include #include #include #include "gimp.h" /************/ /* Typedefs */ /************/ #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #define CHECKBOUNDS(x,y) (x>=0 && y>=0 && xr=a->r+b->r; a->g=a->g+b->g; a->b=a->b+b->b; } void RGBMul(RGBPixel *a,double b) { a->r=a->r*b; a->g=a->g*b; a->b=a->b*b; } void RGBClamp(RGBPixel *a) { if (a->r>1.0) a->r=1.0; if (a->g>1.0) a->g=1.0; if (a->b>1.0) a->b=1.0; if (a->r<0.0) a->r=0.0; if (a->g<0.0) a->g=0.0; if (a->b<0.0) a->b=0.0; } void SetColor(RGBPixel *a,double r,double g,double b) { a->r=r; a->g=g; a->b=b; } long int XYToIndex(int x,int y) { return((long int)x*(long int)channels+(long int)y*(long int)modulo); } int CheckBounds(int x,int y) { if (x<0 || y<0 || x>width-1 || y>height-1) return(1); else return(0); } unsigned char PeekMap(unsigned char *DispMap,int x,int y) { long int index; index=(long int)x+(long int)width*(long int)y; return(DispMap[index]); } void PokeMap(unsigned char *DispMap,int x,int y,unsigned char value) { long int index; index=(long int)x+(long int)width*(long int)y; DispMap[index]=value; } RGBPixel Peek(unsigned char *data,int x,int y) { long int index=XYToIndex(x,y); RGBPixel color; color.r=((double)data[index])/255.0; color.g=((double)data[index+1])/255.0; color.b=((double)data[index+2])/255.0; return(color); } void Poke(unsigned char *data,int x,int y,RGBPixel *color) { long int index=XYToIndex(x,y); data[index]=(unsigned char)(255.0*color->r); data[index+1]=(unsigned char)(255.0*color->g); data[index+2]=(unsigned char)(255.0*color->b); } /*************/ /* Main part */ /*************/ /***************************************************/ /* Compute the derivative in the x and y direction */ /* We use these convolution kernels: */ /* |1 0 -1| | 1 2 1| */ /* DX: |2 0 -2| DY: | 0 0 0| */ /* |1 0 -1| | -1 -2 -1| */ /* (It's a varation of the Sobel kernels, really) */ /***************************************************/ int gradx(unsigned char *image,int x,int y) { int val=0; if (CHECKBOUNDS(x-1,y-1)) val=val+(int)PeekMap(image,x-1,y-1); if (CHECKBOUNDS(x+1,y-1)) val=val-(int)PeekMap(image,x+1,y-1); if (CHECKBOUNDS(x-1,y)) val=val+2*(int)PeekMap(image,x-1,y); if (CHECKBOUNDS(x+1,y)) val=val-2*(int)PeekMap(image,x+1,y); if (CHECKBOUNDS(x-1,y+1)) val=val+(int)PeekMap(image,x-1,y+1); if (CHECKBOUNDS(x+1,y+1)) val=val-(int)PeekMap(image,x+1,y+1); return(val); } int grady(unsigned char *image,int x,int y) { int val=0; if (CHECKBOUNDS(x-1,y-1)) val=val+(int)PeekMap(image,x-1,y-1); if (CHECKBOUNDS(x,y-1)) val=val+2*(int)PeekMap(image,x,y-1); if (CHECKBOUNDS(x+1,y-1)) val=val+(int)PeekMap(image,x+1,y-1); if (CHECKBOUNDS(x-1,y+1)) val=val-(int)PeekMap(image,x-1,y+1); if (CHECKBOUNDS(x,y+1)) val=val-2*(int)PeekMap(image,x,y+1); if (CHECKBOUNDS(x+1,y+1)) val=val-(int)PeekMap(image,x+1,y+1); return(val); } /************************************/ /* A nice 2nd order cubic spline :) */ /************************************/ double cubic(double t) { double at=fabs(t); if (at<1.0) return 2.0*at*at*at-3.0*at*at+1.0; else return 0.0; } double omega(double u,double v,int i,int j) { while (i<0) i+=NUMX; while (j<0) j+=NUMY; i%=NUMX; j%=NUMY; return cubic(u)*cubic(v)*(G[i][j][0]*u+G[i][j][1]*v); } /*************************************************************/ /* The noise function (2D variant of Perlins noise function) */ /*************************************************************/ double noise(double x,double y) { int i,sti=(int)floor(x/dx); int j,stj=(int)floor(y/dy); double sum=0.0; /* Calculate the double sum */ /* ======================== */ for (i=sti; i<=sti+1; i++) { for (j=stj; j<=stj+1; j++) sum+=omega((x-(double)i*dx)/dx,(y-(double)j*dy)/dy,i,j); } return(sum); } /*************************************************/ /* Generates pseudo-random vectors with length 1 */ /*************************************************/ void generatevectors(void) { double alpha; int i,j; for (i=0; i1.0) I=1.0; I=(I/2.0)+0.5; return(I); } static RGBPixel bilinear(double x, double y, RGBPixel *p) { double m0, m1; double ix, iy; RGBPixel v; x = fmod(x, 1.0); y = fmod(y, 1.0); if (x < 0) x += 1.0; if (y < 0) y += 1.0; ix = 1.0 - x; iy = 1.0 - y; /* Red */ /* === */ m0 = ix * p[0].r + x * p[1].r; m1 = ix * p[2].r + x * p[3].r; v.r = iy * m0 + y * m1; /* Green */ /* ===== */ m0 = ix * p[0].g + x * p[1].g; m1 = ix * p[2].g + x * p[3].g; v.g = iy * m0 + y * m1; /* Blue */ /* ==== */ m0 = ix * p[0].b + x * p[1].b; m1 = ix * p[2].b + x * p[3].b; v.b = iy * m0 + y * m1; return(v); } /* bilinear */ void GetPixel(RGBPixel *p,double u,double v) { register int x1, y1, x2, y2; static RGBPixel pp[4]; x1 = (int)u; y1 = (int)v; if (x1 < 0) x1 = width - (-x1 % width); else x1 = x1 % width; if (y1 < 0) y1 = height - (-y1 % height); else y1 = y1 % height; x2 = (x1 + 1) % width; y2 = (y1 + 1) % height; pp[0] = Peek(dinput, x1, y1); pp[1] = Peek(dinput, x2, y1); pp[2] = Peek(dinput, x1, y2); pp[3] = Peek(dinput, x2, y2); *p=bilinear(u,v,pp); } void LIC_Image(int x,int y,double vx,double vy,RGBPixel *color) { double u,step=2.0*L/ISteps; double xx=(double)x,yy=(double)y; double c,s; RGBPixel col,col1,col2,col3; /* Get vector at x,y */ /* ================= */ c = vx; s = vy; /* Calculate integral numerically */ /* ============================== */ col=Black; GetPixel(&col1,xx+L*c,yy+L*s); RGBMul(&col1,filter(-L)); for (u=-L+step; u<=L; u+=step) { GetPixel(&col2,xx-u*c,yy-u*s); RGBMul(&col2,filter(u)); col3=col1; RGBAdd(&col3,&col2); RGBMul(&col3,0.5*step); RGBAdd(&col,&col3); col1=col2; } RGBMul(&col,1.0/L); RGBClamp(&col); *color=col; } double Maximum(double a,double b,double c) { double max=a; if (b>max) max=b; if (c>max) max=c; return(max); } double Minimum(double a,double b,double c) { double min=a; if (br,col->g,col->b); min=Minimum(col->r,col->g,col->b); if (max==min) *hue=-1.0; else { delta=max-min; if (col->r==max) { *hue=(col->g-col->b)/delta; } else if (col->g==max) { *hue=2.0+(col->b-col->r)/delta; } else if (col->b==max) { *hue=4.0+(col->r-col->g)/delta; } *hue=*hue*60.0; if (*hue<0.0) *hue=*hue+360.0; } } void RGB_To_Saturation(RGBPixel *col,double *sat) { double max,min,l; max=Maximum(col->r,col->g,col->b); min=Minimum(col->r,col->g,col->b); if (max==min) *sat=0.0; else { l=(max+min)/2.0; if (l<=0.5) *sat=(max-min)/(max+min); else *sat=(max-min)/(2.0-max-min); } } void RGB_To_Brightness(RGBPixel *col,double *bri) { double max,min; max=Maximum(col->r,col->g,col->b); min=Minimum(col->r,col->g,col->b); *bri=(max+min)/2.0; } void RGBToHue(Image image,unsigned char **map) { unsigned char *data,dval,*themap; int w,h; RGBPixel color; double val; long int maxc,cnt,index; data=gimp_image_data(image); w=gimp_image_width(image); h=gimp_image_height(image); maxc=(long int)w*(long int)h; themap=(unsigned char *)malloc((size_t)maxc*sizeof(unsigned char)); for (cnt=0;cnt