// Function Prototypes; //=============================================================================== BOOL laplacian2D(Raw2D* pSrc, int bound=1); // Find Laplacian of 'src'. // (resize us as needed) // bound%2=0: use Von Neumann boundary cond. // bound%2=1: use Dirichlet (default). BOOL poisson2D_naive(Raw2D *pLap, int maxCount, int bound=1, double maxErr=0.001); // Slow Poisson Solver for 2D images. Given // desired Laplacian pLap, iteratively // find an image that fits it well; // Convergence rate is O(N^2) // bound%2=0: use Von Neumann bounds // bound%2=1: use Dirichlet bounds(DEFAULT) BOOL poisson2D_GaussSeidel(Raw2D *pLap, int maxCount, int bound=1, double maxErr=0.001); // Still slow--2X as fast as 'naive' method. BOOL poisson2D_GaussSeidelRB(Raw2D *pLap, int maxCount, int bound=1, double maxErr=0.001); // Better: Red-black checkerboard.. BOOL poisson2D_SORfix(Raw2D *pLap, int maxCount, int bound=1, double maxErr=0.001); // Better: convergence rate approx O(N) with // a scale factor. BOOL poisson2D_SOR(Raw2D *pLap, int maxCount, int bound=1, double maxErr=0.001); // Better: convergence rate approx O(N) // with Chebyshev acceleration. //=============================================================================== // Function Bodies: //=============================================================================== BOOL Raw2D::laplacian2D(Raw2D* pSrc, int bound) //------------------------------------------------------------------------------ // Laplacian estimates from N,S,E,W neighbors. Recall that Laplacian for scalar // function F is another scalar, written del^2 F or del-dot-del F, and is the // sum of the 2nd derivs in each spatial direction: // Laplacian(F) = d^2 F/dx + d^2 F/dy. If we assume our pixel sample positions // are at integer locations, then we can approximate the Laplacian using forward // differences. If we call F(x,y)==C, and define the north,south,east,west, // neighbors as N,S,E,W, then Laplacian at x,y is (N+S+E+W)-4C. // // bound%2=0: use Von Neumann boundary condition: presume gradient across the // outer edge of the image is zero (as if image extended to infinity by copying // the outermost image pixels endlessly) // bound%2=1: use Dirichlet boundary condition (default): presume all pixels // outside of the image are zero-valued. Return FALSE on error. { #define JT_BORD 1 // size of border used for bound. conditions Raw2D jtmp; int i,j,imax,jmax; PIXTYPE lap; if(pSrc==NULL) { BEEP; DPF("\nRaw2D::laplacian2D() given NULL input!\n"); return(FALSE); } if(pSrc->getYsize()<=0 || pSrc->getXsize()<=0) { BEEP; // complain; exit. DPF("\nRaw2D::laplacian2D() given zero-sized input!\n"); return(FALSE); } sizer(pSrc); // make sure we match the size of the input img. // Copy 'src' into 'tmp', but with a JT_BORD-pixel wide border on all sides imax = pSrc->getXsize() + JT_BORD;//Find address of 1st pix in far border) jmax = pSrc->getYsize() + JT_BORD; jtmp.sizer(JT_BORD + imax, JT_BORD + jmax); if(bound==1) // Initialize the buffer: { // for (default) Dirichlet boundary conditions, jtmp.putConst((PIXTYPE)0.0); // set image & surrounds to zero. } for(j=JT_BORD; jget(i-JT_BORD,j-JT_BORD)); } } if(bound==0) // for the VonNeumann conditions, { // copy some pixels to fill the borders: for(j=0; jgetYsize()<=0 || pLap->getXsize()<=0) { BEEP; // complain; exit. DPF("Raw2D::Poisson2D_naive() given zero-sized input!\n"); return(FALSE); } // Make a pair of double-buffered images that // can hold pLap plus a border of 'JT_BORD' // pixels wide on all 4 sides: buf[0].sizer(2*JT_BORD + pLap->getXsize(),2*JT_BORD + pLap->getYsize()); buf[1].sizer(2*JT_BORD + pLap->getXsize(),2*JT_BORD + pLap->getYsize()); newB = 0; oldB = 1; // init. vars that tell us which buffer holds // old results vs. new values we're computing. imax = JT_BORD + pLap->getXsize();// Find largest address within the border) jmax = JT_BORD + pLap->getYsize(); if(bound==1) // Initialize the buffers: { // for (default) Dirichlet boundary conditions, buf[newB].putConst((PIXTYPE)0.0); // set image & surrounds to zero. buf[oldB].putConst((PIXTYPE)0.0); } else { // for (not reccommended) VonNeumann conditions, buf[newB].putConst((PIXTYPE)0.5); // grow darker & lighter from the buf[oldB].putConst((PIXTYPE)0.5); // mid-level gray value of 0.5 } for(k=0; kget(i-JT_BORD,j-JT_BORD) - lapNow; // correct a fraction of it to make new output: buf[newB].put(i,j,buf[oldB].get(i,j) - JT_FRAC*lapErr*(PIXTYPE)0.25); // buf[newB].put(i ,j,buf[oldB].get(i ,j)-JT_FRAC*lapErr*(PIXTYPE)0.5); // buf[newB].put(i+1,j,buf[oldB].get(i+1,j)+JT_FRAC*lapErr*(PIXTYPE)0.125); // buf[newB].put(i-1,j,buf[oldB].get(i+1,j)+JT_FRAC*lapErr*(PIXTYPE)0.125); // buf[newB].put(i,j+1,buf[oldB].get(i,j+1)+JT_FRAC*lapErr*(PIXTYPE)0.125); // buf[newB].put(i,j-1,buf[oldB].get(i,j-1)+JT_FRAC*lapErr*(PIXTYPE)0.125); #else // Use Jacobi iteration: it's simpler and faster: lapWanted = pLap->get(i-JT_BORD, j-JT_BORD); lapAvg = (buf[oldB].get(i+1,j) + buf[oldB].get(i-1,j) + buf[oldB].get(i,j+1) + buf[oldB].get(i,j-1)); // before we change things, measure error... lapNow = lapAvg - (PIXTYPE)4.0*buf[oldB].get(i,j); lapErr = lapWanted - lapNow; // Correct exactly 1/4 of the laplacian error; // we can ignore current pixel value and get: buf[newB].put(i,j, (PIXTYPE)0.25 * (lapAvg - lapWanted)); // (compute the error). #endif if(err2max < (double)(lapErr*lapErr)) { // find image's largest error; err2max = (double)(lapErr*lapErr); } } if(j%200==0) DPF(".");// print little dots every 200 scanlines... } /////Set Boundary conditions------------------------------------------- // bound%2==1 means Dirichlet Boundary conditions:----------- // ***We already have them! // Border pixels were initialized to zero, and remain unchanged. if(bound%2==0) // VonNeumann Boundary conditions---------- { // update the border pixels so that their // values match the adjacent pixel: for(j=0; jgetYsize()<=0 || pLap->getXsize()<=0) { BEEP; // complain; exit. DPF("Raw2D::Poisson2D_GaussSeidel() given zero-sized input!\n"); return(FALSE); } // Make a single output image buffer that // can hold pLap plus a border of 'JT_BORD' // pixels wide on all 4 sides: buf.sizer(2*JT_BORD + pLap->getXsize(),2*JT_BORD + pLap->getYsize()); imax = JT_BORD + pLap->getXsize();// Find largest address within the border) jmax = JT_BORD + pLap->getYsize(); if(bound==1) // Initialize the buffers: { // for (default) Dirichlet boundary conditions, buf.putConst((PIXTYPE)0.0); // set image & surrounds to zero. } else { // for (not reccommended) VonNeumann conditions, buf.putConst((PIXTYPE)0.5); // grow darker & lighter from the } // mid-level gray value of 0.5 for(k=0; kget(i-JT_BORD, j-JT_BORD); lapAvg = (buf.get(i+1,j) + buf.get(i-1,j) + buf.get(i,j+1) + buf.get(i,j-1)); lapNow = lapAvg - (PIXTYPE)4.0*buf.get(i,j);//current Laplacian lapErr = lapWanted - lapNow; // If we correct 1/4 of the laplacian error, // then computing the new pixel value is simple: buf.put(i,j, (PIXTYPE)0.25 * (lapAvg - lapWanted)); // (compute the error). if(err2max < (double)(lapErr*lapErr)) { // find image's largest error; err2max = (double)(lapErr*lapErr); } }// end of one scanline, if(j%200==0) DPF(".");// print little dots every 200 scanlines... }// end of one pass, /////Set Boundary conditions------------------------------------------- // bound%2==1 means Dirichlet Boundary conditions:------------ // We already have them! Border pixels initialized to zero & unchanged. if(bound%2==0) // Von Neumann Boundary conditions---------- { // update the border pixels so that their // values match the adjacent pixel: for(j=0; jgetYsize()<=0 || pLap->getXsize()<=0) { BEEP; // complain; exit. DPF("Raw2D::Poisson2D_GaussSeidelRB() given zero-sized input!\n"); return(FALSE); } // Make a single output image buffer that // can hold pLap plus a border of 'JT_BORD' // pixels wide on all 4 sides: buf.sizer(2*JT_BORD + pLap->getXsize(),2*JT_BORD + pLap->getYsize()); imax = JT_BORD + pLap->getXsize();// Find largest address within the border) jmax = JT_BORD + pLap->getYsize(); if(bound==1) // Initialize the buffers: { // for (default) Dirichlet boundary conditions, buf.putConst((PIXTYPE)0.0); // set image & surrounds to zero. } else { // for (not reccommended) VonNeumann conditions, buf.putConst((PIXTYPE)0.5); // grow darker & lighter from the } // mid-level gray value of 0.5 for(k=0; kget(i-JT_BORD, j-JT_BORD); lapAvg = (buf.get(i+1,j) + buf.get(i-1,j) + buf.get(i,j+1) + buf.get(i,j-1)); lapNow = lapAvg - (PIXTYPE)4.0*buf.get(i,j);//current Laplacian lapErr = lapWanted - lapNow; // If we correct 1/4 of the laplacian error, // then computing the new pixel value is simple: buf.put(i,j, (PIXTYPE)0.25 * (lapAvg - lapWanted)); // (compute the error). if(err2max < (double)(lapErr*lapErr)) { // find image's largest error; err2max = (double)(lapErr*lapErr); }// end of one pixel, }// end of one scanline, chk = (chk+1)%2; // toggle 'chk' on each scanline }// end of one half-sweep, if(j%200==0) DPF(".");// print little dots every 200 scanlines... /////Set Boundary conditions--------------------------------------- // bound%2==1 means Dirichlet Boundary conditions:----------- // We already have them! Border pixels initialized to zero & unchanged. if(bound%2==0) // VonNeumann Boundary conditions---------- { // update the border pixels so that their // values match the adjacent pixel: for(j=0; jgetYsize()<=0 || pLap->getXsize()<=0) { BEEP; // complain; exit. DPF("Raw2D::Poisson2D_SOR() given zero-sized input!\n"); return(FALSE); } // Make a single output image buffer that // can hold pLap plus a border of 'JT_BORD' // pixels wide on all 4 sides: buf.sizer(2*JT_BORD + pLap->getXsize(),2*JT_BORD + pLap->getYsize()); imax = JT_BORD + pLap->getXsize();// Find largest address within the border) jmax = JT_BORD + pLap->getYsize(); if(bound==1) // Initialize the buffers: { // for (default) Dirichlet boundary conditions, buf.putConst((PIXTYPE)0.0); // set image & surrounds to zero. } else { // for (not reccommended) VonNeumann conditions, buf.putConst((PIXTYPE)0.5); // grow darker & lighter from the } // mid-level gray value of 0.5 chebW = (PIXTYPE)1.99; for(k=0; kget(i-JT_BORD, j-JT_BORD); lapAvg = (buf.get(i+1,j) + buf.get(i-1,j) + buf.get(i,j+1) + buf.get(i,j-1)); lapNow = lapAvg - (PIXTYPE)4.0*buf.get(i,j);//current Laplacian lapErr = lapWanted - lapNow; // If we correct 1/4 of the laplacian error, // then computing the new pixel value is simple: buf.put(i,j, buf.get(i,j) - (PIXTYPE)0.25 *chebW * lapErr); // (compute the error). // if(err2max < (double)(lapErr*lapErr)) // { // find image's largest error; // err2max = (double)(lapErr*lapErr); // }// end of one pixel, err2max += (double)(lapErr*lapErr); // sum-of-square err }// end of one scanline, chk = (chk+1)%2; // toggle 'chk' on each scanline }// end of one half-sweep, //if(j%100==0) DPF(".");// print little dots every 100 scanlines... /////Set Boundary conditions--------------------------------------- // bound%2==1 means Dirichlet Boundary conditions: // We already have them! Border pixels initialized to zero & unchanged. if(bound%2==0) // VonNeumann Boundary conditions---------- { // update the border pixels so that their // values match the adjacent pixel: for(j=0; jgetYsize()<=0 || pLap->getXsize()<=0) { BEEP; // complain; exit. DPF("Raw2D::Poisson2D_SORcheby() given zero-sized input!\n"); return(FALSE); } // Make a single output image buffer that // can hold pLap plus a border of 'JT_BORD' // pixels wide on all 4 sides: buf.sizer(2*JT_BORD + pLap->getXsize(),2*JT_BORD + pLap->getYsize()); imax = JT_BORD + pLap->getXsize();// Find largest address within the border) jmax = JT_BORD + pLap->getYsize(); if(bound==1) // Initialize the buffers: { // for (default) Dirichlet boundary conditions, buf.putConst((PIXTYPE)0.0); // set image & surrounds to zero. } else { // for (not reccommended)VonNeumann conditions, buf.putConst((PIXTYPE)0.5); // grow darker & lighter from the } // mid-level gray value of 0.5 // To compute the Chebyshev acceleration weight chebW we need to know the // radius of convergence for Jacobi iterations for our solver. Rectangular // boundaries (like ours) with either Dirichlet or VonNeumann conditions // used in solution to a Poisson equation have a radius of convergence given // by an analytical expression (eqn. 19.5.24, pg. 869, Numerical Recipes) rjac = (cos(M_PI/imax) + cos(M_PI/jmax)) / 2.0; rjac2 = rjac*rjac; // (Note rjac is asymptotically 1.0 as imax and jmax grows.) chebW = (PIXTYPE)1.0; // for 1st pass, no acceleration. for(k=0; kget(i-JT_BORD, j-JT_BORD); lapAvg = (buf.get(i+1,j) + buf.get(i-1,j) + // sum NSEW neighbors buf.get(i,j+1) + buf.get(i,j-1)); lapNow = lapAvg - (PIXTYPE)4.0*buf.get(i,j);//current Laplacian lapErr = lapWanted - lapNow; // in-place correction of 1/4 of the Laplacian error // but exaggerate the correction by chebW factor, // which grows as large as 2.0. buf.put(i,j, buf.get(i,j) - (PIXTYPE)0.25 *chebW * lapErr); // (compute the error). err2sum += (double)(lapErr*lapErr); // sum-of-square err }// end of one scanline, chk = (chk+1)%2; // toggle 'chk' on each scanline if(k==0 && isOdd==0)// Initial pass on chekerboard's 2nd half? { // calc the Chebyshev polynomial: chebW = (PIXTYPE)( 1.0 / (1.0 - 0.5*rjac2)); } // (chebW should be ~2.0) else // No? asymptotically go to optimal value: { chebW = (PIXTYPE)(1.0 / (1.0 - 0.25*rjac2*chebW)); } // (chebW should be ~2.0) }// end of one half-sweep, //if(j%100==0) DPF(".");// print little dots every 100 scanlines... /////Set Boundary conditions--------------------------------------- // bound%2==1 means Dirichlet Boundary conditions: // We already have them! Border pixels initialized to zero & unchanged. if(bound%2==0) // VonNeumann Boundary conditions---------- { // update the border pixels so that their // values match the adjacent pixel: for(j=0; j