//============================================== // copyright : (C) 2003-2005 by Will Stokes //============================================== // 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. //============================================== //Systemwide includes #include #include #include //Projectwide includes #include "blur.h" //---------------------------------------------- // Inputs: // ------- // QImage& - image to be blurred // Float sigma - amount of blurring desired // // Description: // ------------ // This method blurs a color image using the approach published // by Ian T. Young and Lucas J. van Vliet, "Recursive implementation // of the Gaussian filter", 1995 Signal Processing. // // The first solution one might take to blurring an image // is to convolve a gaussian filter with an image. This is expensive since // as the gaussian filter matrix grows, processing becomes quite expensive // ( O(M^2 * N^2) where M is the width/height of the gaussian matrix and N is the // width/height of the image. ) // // An alternative is to approximate a gaussian by repeatedly convolving a kernel // such as uniform filter. While faster, this is only an approximation of a // gaussian, and the weights one uses for each iteration must be hand picked in // order to follow the Well's equation: // (W_1^2 + W_2^2 + W_3^2 + W_4^2 = 12*sigma^2 + 4) // // This process is tedious and limits us in applying intermediate blur amounts. // // In theory Young and Vliet's technique (which is O(4N^2) = O(N^2)) is not only // more accurate but faster as well. The only penalty we pay is the extra storage // of a row or column while that row or column is being processed. For a // 2048x2048 image my implementation will require an extra 16kb for this extra // storage space, a drop in the bucket compared to the memory required to hold // the entire image in float memory while being processed. // // Before being blurred color values are converted from integer [0-255] to float // [0.0-1.0] space. This is necessary to avoid compound rounding errors. In order // to minimize space required to store the float buffer, the image is processed // in three passes for each of the red, green, and blue color channels. // // Since the code is broken up nicely, well commented, and is a direct // implementation of Young and Vliet's techinque I point you to their paper for a // better understanding of why/how this techinque actually works. Vliet has a // copy on his personal web site, last seen at: // http://www.ph.tn.tudelft.nl/~lucas/ //---------------------------------------------- void computeCoeffs( float sigma ); void fillBuffer( QImage &image, int channel ); void blurBuffer(); void blurRow( int row ); void blurColumn( int column ); void blurRegionsInRow( int y ); void blurRegionsInCol( int x ); void resetImageData( QImage &image, int channel, bool blurEdges); float edgeValue(int x, int y); float q, b0, b1, b2, b3, B; int width, height; float* buffer; float* rowBuffer; float* colBuffer; float* regionRowBuffer; float* regionColBuffer; QImage* edgeImage; int* regionMap; int regionCount; QPoint displayOffset; QSize fullRes; //============================================== void blurImage( QImage &image, float sigma ) { //supply dummy data for edges, notably NULL for the edge image pointer. //other values have no effect blurImage( image, sigma, QPoint(0,0), image.size(), NULL, NULL, 0, false ); } //============================================== void blurImage( QImage &image, float sigma, QPoint offset, QSize fullImageRes, QImage* edges, int* regions, int numRegions, bool targetEdges) { edgeImage = edges; regionMap = regions; regionCount = numRegions; displayOffset = offset; fullRes = fullImageRes; //compute blurring coeffecients computeCoeffs(sigma); //store image dimensions width = image.width(); height = image.height(); //Construct float buffer that is the size of the image/ //In order to conserve memory process image three times, once for //each color channel. buffer = new float[ width * height ]; rowBuffer = new float[width]; colBuffer = new float[height]; regionRowBuffer = new float[width * numRegions]; regionColBuffer = new float[height * numRegions]; //iterate over each color channel int channel; for( channel = 0; channel <=2; channel++) { //copy color data into float buffer fillBuffer( image, channel ); //blur buffer data blurBuffer(); //reset image data used blurred buffer resetImageData(image, channel, targetEdges); } //delete buffer delete[] buffer; delete[] rowBuffer; delete[] colBuffer; } //============================================== void computeCoeffs( float sigma ) { //compute q as a function of sigma if( sigma >= 2.5f ) { q = 0.98711f*sigma - 0.96330f; } else { q = 3.97156f - 4.14554f * sqrt( 1.0f - 0.26891f*sigma ); } //compute b0, b1, b2, and b3 b0 = 1.57825f + (2.44413f*q) + (1.4281f * q*q ) + (0.422205f * q*q*q ); b1 = (2.44413f * q) + (2.85619f * q*q) + (1.26661 * q*q*q ); b2 = -((1.4281 * q*q) + (1.26661 * q*q*q)); b3 = 0.422205 * q*q*q; //compute B B = 1.0f - ((b1 + b2 + b3) / b0); } //============================================== void fillBuffer( QImage &image, int channel ) { //precompute 1/255 float multiplier = 1.0f / 255.0f; //iterate over each selected scanline int x, y; QRgb* rgb; uchar* scanLine; for( y=0; ywidth()-1) * (x+displayOffset.x())) / (fullRes.width()-1); int edgeY = ((edgeImage->height()-1) * (y+displayOffset.y())) / (fullRes.height()-1); return edgeY*edgeImage->width() + edgeX; } //============================================== float edgeValue(int x, int y) { //compute floating point x and y coordinates for edge image float edgeX = ((edgeImage->width()-1.0f) * (x+displayOffset.x())) / (fullRes.width()-1); float edgeY = ((edgeImage->height()-1.0f) * (y+displayOffset.y())) / (fullRes.height()-1); //compute 4 int values of coordinates int x1 = (int)edgeX; int y1 = (int)edgeY; int x2, y2; if( edgeX > x1 ) x2 = x1+1; else x2 = x1; if( edgeY > y1 ) y2 = y1+1; else y2 = y1; //compute the four indices int index1, index2, index3, index4; index1 = x1 + y1*edgeImage->width(); index2 = x2 + y1*edgeImage->width(); index3 = x1 + y2*edgeImage->width(); index4 = x2 + y2*edgeImage->width(); //find edge quantity for each corner float v1, v2, v3, v4; uchar* scanline = edgeImage->scanLine( y1 ); QRgb* rgb = ((QRgb*)scanline+x1); v1 = ((float) qRed( *rgb )) / 255.0f; rgb = ((QRgb*)scanline+x2); v2 = ((float) qRed( *rgb )) / 255.0f; scanline = edgeImage->scanLine( y2 ); rgb = ((QRgb*)scanline+x1); v3 = ((float) qRed( *rgb )) / 255.0f; rgb = ((QRgb*)scanline+x2); v4 = ((float) qRed( *rgb )) / 255.0f; //blur combine left-right v1 = (edgeX-x1)*v2 + (1 - edgeX + x1)*v1; v3 = (edgeX-x1)*v4 + (1 - edgeX + x1)*v3; //combine top-bottom v1 = (edgeY-y1)*v3 + (1 - edgeY + y1)*v1; //return result return v1; } //============================================== void blurRow( int row ) { int i; int rtw = row*width; //forward rowBuffer[0] = buffer[ 0 + rtw ]; for(i=1; i=0; i--) { buffer[ i + rtw ] = B*rowBuffer[ i ] + ( b1 * buffer[ QMIN(i+1, width-1) + rtw ] + b2 * buffer[ QMIN(i+2, width-1) + rtw ] + b3 * buffer[ QMIN(i+3, width-1) + rtw ]) / b0; } } //============================================== void blurRegionsInRow( int y ) { //--------------------------------- //populate region row buffer. a row has been allocated for //each region. Pixels between regions //take the closest pixel value in that row from that region int yTimesWidth = y*width; int regionTimesWidth; int region,x,x2; //for each region for(region=0; region=0; x--) { regionRowBuffer[ x + regionTimesWidth ] = B*rowBuffer[ x ] + ( b1 * regionRowBuffer[ QMIN(x+1, width-1) + regionTimesWidth ] + b2 * regionRowBuffer[ QMIN(x+2, width-1) + regionTimesWidth ] + b3 * regionRowBuffer[ QMIN(x+3, width-1) + regionTimesWidth ]) / b0; } } //--------------------------------- //copy data from the region row buffers back to the //buffer. for each pixel we choose the correct region //row buffer basedon the original regionidentity of hte pixel for(x=0; x=0; i--) { buffer[ column + i*width ] = B*colBuffer[ i ] + ( b1 * buffer[ column + QMIN(i+1, height-1)*width ] + b2 * buffer[ column + QMIN(i+2, height-1)*width ] + b3 * buffer[ column + QMIN(i+3, height-1)*width ]) / b0; } } //============================================== void blurRegionsInCol( int x ) { //--------------------------------- //populate region col buffer. a col has been allocated for //each region. Pixels between regions //take the closest pixel value in that col from that region // int yTimesWidth = y*width; int regionTimesHeight; int region,y,y2; //for each region for(region=0; region=0; y--) { regionColBuffer[ y + regionTimesHeight ] = B*colBuffer[ y ] + ( b1 * regionColBuffer[ QMIN(y+1, height-1) + regionTimesHeight ] + b2 * regionColBuffer[ QMIN(y+2, height-1) + regionTimesHeight ] + b3 * regionColBuffer[ QMIN(y+3, height-1) + regionTimesHeight ]) / b0; } } //--------------------------------- //copy data from the region row buffers back to the //buffer. for each pixel we choose the correct region //row buffer basedon the original regionidentity of hte pixel for(y=0; y