redEye.cpp

Go to the documentation of this file.
00001 //==============================================
00002 //  copyright            : (C) 2003-2005 by Will Stokes
00003 //==============================================
00004 //  This program is free software; you can redistribute it
00005 //  and/or modify it under the terms of the GNU General
00006 //  Public License as published by the Free Software
00007 //  Foundation; either version 2 of the License, or
00008 //  (at your option) any later version.
00009 //==============================================
00010 
00011 //Systemwide includes
00012 #include <qimage.h>
00013 #include <qstring.h>
00014 #include <qapplication.h>
00015 
00016 //Projectwide includes
00017 #include "redEye.h"
00018 #include "redEye_internal.h"
00019 #include "../../gui/statusWidget.h"
00020 
00021 //----------------------------------------------
00022 // Inputs:
00023 // -------
00024 // QString filename - location of original image on disk
00025 // QPoint topLeftExtreme - top left constraint
00026 // QPoint bottomRightExtreme - botth right constraint
00027 // StatusWidget* status - widget for making progress visible to user
00028 //
00029 // Outputs:
00030 // --------
00031 // QImage* returned - enhanced image
00032 //
00033 // Description:
00034 // ------------
00035 // There are a lot of programs out there that provide some sort of
00036 // red eye tool, but to put it bluntly, most of them really suck.
00037 // To be fair, the red-eye flash function on most digital cameras (my own
00038 // Olympus 3030z included) suck too. 
00039 //
00040 // "Such foolishness, what can men do against such reckless stupidity?"
00041 //                                         -unknown
00042 //
00043 // Well, here I try to provide a better red-eye tool by studying those that suck,
00044 // those that suck less, drawing some conclusions, and coming up with a few tricks
00045 // of my own...
00046 //
00047 // The worst red eye tools suck for two reasons:
00048 // -False positives
00049 // -Horrid red channel desaturation
00050 //
00051 // I've encountered red eye tools that claim to just work 
00052 // by clicking a single button. Guess what, they don't. The sad thing is that
00053 // while you can do a pretty good job figuring out where the red eyes are
00054 // in a picture, the programs to provide these brain-dead interfaces usually don't
00055 // do anything complicated at all and gunk up non-red eye regions all over the place!
00056 //
00057 // The second problem I'd say most programs suffer from is doing a poor job of actually
00058 // correcting the red eye region, which is a shame but also stems from their generally
00059 // poor understanding of where the red eyes are.
00060 //
00061 // Algorithm:
00062 // ----------
00063 // I've developed my own red-eye reduction algorithm that tries to surpass all
00064 // others by:
00065 // -finding the red eyes and
00066 // -carefully fixing the color of these regions only
00067 //
00068 // The second step involving desaturing the red channel of offending pixels
00069 // is largely based on Gaubtz and Ulichney's 2002 IEEE paper titled:
00070 // "Automatic Red-Eye Detection and Correction"
00071 //
00072 // http://www.crl.hpl.hp.com/who/people/ulichney/bib/papers/2002-redeye%20-%20ICIP.pdf
00073 // 
00074 // Gaubtz and Ulichney base their techinque on a complicated face-detection model.
00075 // I know such approaches are error prone, and guess what, we have a semi (if not
00076 // very) intelligent user sitting in front of the screen, why not put them to work!
00077 //
00078 // Instead of detecting face elements automatically, we first have the user select
00079 // a region of the image that two red eyes exist within. Before continuing, we
00080 // attempt to shrink this selection as much as possible by thresholding pixels and
00081 // tightening the boundary as long as no above threshold pixels are cut out.
00082 // 
00083 // threshmet = r > 2*g AND r > MIN_RED_VAL
00084 //
00085 // Red eyes tend to be red, but not nearly as green or blue. The second
00086 // half of the threshold helps throw out low-lying noise by requiring 
00087 // the red channel to be above a minimum threshold.
00088 //
00089 // Many programs JUST use the first half of this test (r > 2*g) to pick pixels
00090 // within a region to fix. I suppose you can get away without the noise test but
00091 // fudging up all these other pixels, even if it isn't very noticable, really bugs me.
00092 // I did extensive testing and tuned that second paramters to filter such changes out.
00093 //
00094 // Once we've shrunk the selected area, we proceed with the heart of the algorithm:
00095 // 1.) finding blobs
00096 // 2.) sorting blobs
00097 // 3.) picking best two blobs
00098 //
00099 // and finally...
00100 //
00101 // 4.) desaturating the best two blobs OR desaturating the entire selected
00102 //     region if good blobs could not be found.
00103 //
00104 // Under the best conditions (most cases) the algorithm finds the offending
00105 // eyes and reduces them only. In the worst case scenario the algorithm
00106 // applying the desaturing procedure of all thresholded pixels within the
00107 // selected area, which is still better than other algorithms in the wild
00108 // since we'll employ a smarter desaturating techinque, but more on that in a bit.
00109 //
00110 // Let's examine each step in detail:
00111 //
00112 // Finding Blobs:
00113 // --------------
00114 // The finding blobs algorithm is actually pretty straight forward.
00115 // An initial pass over the selected region constructs a integer mask where
00116 // 0 indicates a pixel did not met and 1 indictes a pixel that did met the 
00117 // same red threshold test we applied earlier.
00118 //
00119 // If the integer mask is set to 0 move on.
00120 // If the integer mask is set to 1 assign the next unique ID, push all 8 neighbors
00121 // that are 1's in the integer masl into a list and asssociate that pixel in 
00122 // the list with the unique ID we just set.
00123 //
00124 // At the top of the loop we pop pixels off the list while the list is not empty. For each
00125 // pixel we check the current integer mask value it has. If it is 1 we set it to the 
00126 // tagged unique ID and push all it's neighbors that have 1's in the integer 
00127 // mask and move. Below is an example of what the integer mask might look like
00128 // before and after blobs are found.
00129 //
00130 // 0000000000000000000      0000000000000000000
00131 // 0011000111100000100      0022000333300000400
00132 // 0111100000111000110  --> 0222200000333000440
00133 // 0100000000110000010      0200000000330000040
00134 // 0000000000000000000      0000000000000000000
00135 //
00136 // Every time a new pixel is used to start a new blob the old
00137 // blob and a few statistics are pushed into a list. In addition to
00138 // knowledge of the blob ID and inherantly all tagged pixels (we keep around
00139 // the integer mask), we also store the pixel count and the blobs aspect ratio (w/h).
00140 // These stats are useful during the next step.
00141 //
00142 // Sorting Blobs:
00143 // --------------
00144 // At this point we've found all the above threshold blobs which consist of
00145 // connected above threshold pixels, but it is often the case not all blobs
00146 // are eyes. Acne, lipstick, moles, or plain old poor selection by the user, can
00147 // result in a number of false positive blobs getting pushed into our lists.
00148 // Fortunately, eyes are:
00149 // -round
00150 // -roughly the same size and shape
00151 //
00152 // To make actually picking blobs easier, we first sort the blob list by 
00153 // decreasing size, so the biggest ones are up front. You tend to run into a lot more
00154 // small false positives than large ones, and the large ones tend to not be
00155 // very round at all (like lips), so thorwing them out is a lot easier.
00156 //
00157 // Picking Blobs:
00158 // --------------
00159 // Picking the two best blobs is fairly straight forward. If only two 
00160 // blobs are found use those. If more blobs are found then start walking 
00161 // down the list of blobs starting with the largest ones. The first two 
00162 // consequtive blobls that are roughly circular (0.75 < aspect ratio < 2.0), 
00163 // roughly similar in shape (larger aspect ratio / smaller aspect ratio < 2), 
00164 // roughly similar in size (biggerSize / smallersize < 1.5), and both blobs 
00165 // meet a minimum size threshold (20 pixels) are chosen as the best two blobs.
00166 //
00167 // That's all just fine and dandy, but what if two blobs can't be found that
00168 // meet those constraints? Easy, we'll work on the entire region, but usually we
00169 // find the eyes without much trouble, while throwing out the other stuff
00170 // like lips etc.
00171 //
00172 // Desaturing:
00173 // -----------
00174 // There are two aspects of the desaturation process that make
00175 // the results provided by this techinque far better than most of the
00176 // other programs out there.
00177 //
00178 // First, we only desaturate the red channel. A lot of programs convert
00179 // the pixcel color to grayscale, then dim is slightly. This is bad for two
00180 // reasons. First, you lose the true pupil color. Second, dimming the pixel
00181 // causes you to lose the glint that often reflects off the center of the
00182 // eyeball. Instead, we desaturate the red channel only, and instead
00183 // of simply decreasing it, we estimate it's true value using the green and
00184 // and blue components, which tends to look more natural:
00185 //
00186 // r' = 0.05*r + 0.6*g + 0.3*b
00187 //
00188 // The problem with directly desaturing the red channel is that you get seams at
00189 // the blob border. To prevent seams from occuring, we blend the updated
00190 // red channel color with the original using an alpha term based on
00191 // the percentage of pixels within a centered 5x5 grid that were marked as
00192 // blob pixels.
00193 //
00194 // The result of seamless red channel correction for the offending red eyes only.
00195 // The glint in a persons eyes are preserved mainly because of the blob based 
00196 // approach we take (pixels in the center of a blob are not necessary tagged
00197 // since the white glint does not pass the intial threshold test).
00198 //
00199 // A final note, in the situation where two good blobs could not be found
00200 // we simply desaturate all pixels that meet the less stringent r > 2*g
00201 // test using the same r' approach techinque.
00202 //
00203 //----------------------------------------------
00204 
00205 //==============================================
00206 QImage* removeRedeyeRegions( QString filename, 
00207                              QPoint topLeftExtreme, QPoint bottomRightExtreme,
00208                              StatusWidget* statusWidget )
00209 {
00210   //store handle to status widget
00211   status = statusWidget;
00212   
00213   //load original image
00214   rawImage = QImage( filename );
00215   
00216   //sanity check: unable to load image
00217   if(rawImage.isNull()) { return NULL; }
00218 
00219   //convert to 32-bit depth if necessary
00220   if( rawImage.depth() < 32 ) { rawImage = rawImage.convertDepth( 32, Qt::AutoColor ); }
00221    
00222   //sanity check: make sure topLeftExtreme and bottomRightExtreme are within image boundary
00223   topLeftExtreme.setX( QMAX( topLeftExtreme.x(), 0 ) );
00224   topLeftExtreme.setY( QMAX( topLeftExtreme.y(), 0 ) );
00225   bottomRightExtreme.setX( QMIN( bottomRightExtreme.x(), rawImage.width()-1 ) );
00226   bottomRightExtreme.setY( QMIN( bottomRightExtreme.y(), rawImage.height()-1 ) );
00227 
00228   //setup progress bar
00229   QString statusMessage = qApp->translate( "removeRedeyeRegions", "Removing Red-Eye:" );
00230   status->showProgressBar( statusMessage, 100 );
00231   qApp->processEvents();  
00232   
00233   //update progress bar for every 1% of completion
00234   updateIncrement = (int) ( 0.01 * 
00235                             ( bottomRightExtreme.x() - topLeftExtreme.x() + 1 ) *
00236                             ( bottomRightExtreme.y() - topLeftExtreme.y() + 1 ) );
00237   newProgress = 0;   
00238 
00239   //find region of interest: constrain search box to boundary that actually contains red enough pixels
00240   findRegionOfInterest(topLeftExtreme, bottomRightExtreme);
00241 
00242   //if no pixels were found then immediately return a NULL pointer signaling no change
00243   if(topLeft.x() == -1) 
00244   { 
00245     //hide progress bar
00246     status->setStatus( "" );
00247     qApp->processEvents();
00248 
00249     return NULL; 
00250   }
00251 
00252   //load an editing image
00253   //two images mus be loaded becuase pixel values are replaced
00254   //using a compbination of niehgbors and their own in order
00255   //to avoid sharp lines at the edge of the saturated region
00256   editedImage = new QImage( filename );
00257   
00258   //sanity check: unable to allocated edited image
00259   if( editedImage == NULL) 
00260   { 
00261     //hide progress bar
00262     status->setStatus( "" );
00263     qApp->processEvents();
00264 
00265     return NULL; 
00266   }
00267 
00268   //convert to 32-bit depth if necessary
00269   if( editedImage->depth() < 32 )
00270   {
00271     QImage* tmp = editedImage;
00272     editedImage = new QImage( tmp->convertDepth( 32, Qt::AutoColor ) );
00273     delete tmp; tmp=NULL;
00274   }
00275   
00276   findBlobs();
00277   sortBlobsByDecreasingSize();
00278   findBestTwoBlobs();
00279 
00280   //if we found two good blobs then desaturate those only
00281   if(id1 != -1)
00282   {
00283     desaturateBlobs();
00284   }
00285   //else desaturate all pixels above thresh within selection area
00286   else
00287   {
00288     desaturateEntireImage(topLeftExtreme, bottomRightExtreme);
00289   }
00290 
00291   //remove status bar
00292   status->setStatus( "" );
00293   qApp->processEvents();
00294 
00295   //return pointer to edited image
00296   return editedImage;      
00297 }
00298 //==============================================
00299 
00300 // 40 = 15.6% of red channel, a good heuristic for false positives
00301 //at border of face on a dark background.
00302 #define MIN_RED_VAL 40
00303 
00304 //==============================================
00305 void findRegionOfInterest(QPoint topLeftExtreme, QPoint bottomRightExtreme)
00306 {
00307   topLeft = QPoint(-1,-1);
00308   bottomRight = QPoint(-1,-1);
00309   
00310   int x, y;
00311   QRgb* rgb;
00312   uchar* scanLine;
00313   for( y=topLeftExtreme.y(); y<=bottomRightExtreme.y(); y++)
00314   {
00315     scanLine = rawImage.scanLine(y);
00316     for( x=topLeftExtreme.x(); x<=bottomRightExtreme.x(); x++)
00317     {
00318       rgb = ((QRgb*)scanLine+x);
00319       
00320       bool threshMet = qRed(*rgb) > 2*qGreen(*rgb) &&
00321                       qRed(*rgb) > MIN_RED_VAL;
00322       if(threshMet)
00323       {
00324         //first pixel
00325         if(topLeft.x() == -1) 
00326         {
00327           topLeft = QPoint(x,y);
00328           bottomRight = QPoint(x,y);
00329         }
00330         
00331         if(x < topLeft.x() ) topLeft.setX( x );
00332         if(y < topLeft.y() ) topLeft.setY( y );
00333         if(x > bottomRight.x() ) bottomRight.setX( x );
00334         if(y > bottomRight.y() ) bottomRight.setY( y );
00335       }
00336       
00337       //update status bar if significant progress has been made since last update
00338       newProgress++;
00339       if(newProgress >= updateIncrement)
00340       {
00341         newProgress = 0;
00342         status->incrementProgress();
00343         qApp->processEvents();  
00344       }
00345       
00346     }
00347   }  
00348 }
00349 //==============================================
00350 void pushPixel(int x, int y, int id)
00351 {
00352   //if pixel off image or below thresh ignore push attempt
00353   if(  x < 0  || 
00354        y <  0 ||
00355        x >= regionWidth ||
00356        y >= regionHeight ||
00357        regionOfInterest[ x + y*regionWidth ] != 1 )
00358     return;
00359   
00360   //passes! set id and actually put pixel onto stack
00361   regionOfInterest[ x + y*regionWidth] = id;  
00362   spreadablePixels.push( QPoint( x, y ) );
00363   
00364   //increase blob pixel count and update topLeft and bottomRight
00365   blobPixelCount++;
00366   blobTopLeft.setX( QMIN( x, blobTopLeft.x() ) );
00367   blobTopLeft.setY( QMIN( y, blobTopLeft.y() ) );
00368   blobBottomRight.setX( QMAX( x, blobBottomRight.x() ) );
00369   blobBottomRight.setY( QMAX( y, blobBottomRight.y() ) );
00370 }
00371 //==============================================
00372 void findBlobs()
00373 {
00374   //create small matrix for region of interest
00375   regionWidth = bottomRight.x() - topLeft.x() + 1;
00376   regionHeight = bottomRight.y() - topLeft.y() + 1;  
00377   regionOfInterest = new int[ regionWidth * regionHeight ];
00378   
00379   //set all pixels that meet thresh to 1, all others to 0
00380   int x, y;
00381   int x2, y2;
00382   QRgb* rgb;
00383   uchar* scanLine;
00384   for( y=topLeft.y(); y<=bottomRight.y(); y++)
00385   {
00386     y2 = y - topLeft.y();
00387     
00388     scanLine = rawImage.scanLine(y);
00389     for( x=topLeft.x(); x<=bottomRight.x(); x++)
00390     {
00391     
00392       x2 = x - topLeft.x();
00393       
00394       rgb = ((QRgb*)scanLine+x);
00395       
00396       bool threshMet = qRed(*rgb) > 2*qGreen(*rgb) &&
00397                        qRed(*rgb) > MIN_RED_VAL;
00398       
00399       if(threshMet)
00400         regionOfInterest[ x2 + y2*regionWidth ] = 1;
00401       else
00402         regionOfInterest[ x2 + y2*regionWidth ] = 0;
00403     }
00404   } 
00405   
00406   //walk over region of interest and propogate blobs
00407   int nextValidID = 2;
00408   for(x = 0; x<regionWidth; x++)
00409   {
00410     for(y = 0; y<regionHeight; y++)
00411     {
00412       //if any blobs can be propogated handle them first
00413       while( !spreadablePixels.empty() )
00414       {
00415         QPoint point = spreadablePixels.pop();
00416         int id = regionOfInterest[ point.x() + point.y()*regionWidth ];
00417         
00418         pushPixel( point.x()-1, point.y()-1, id );
00419         pushPixel( point.x(),   point.y()-1, id );
00420         pushPixel( point.x()+1, point.y()-1, id );
00421         pushPixel( point.x()-1, point.y(), id );
00422         pushPixel( point.x()+1, point.y(), id );
00423         pushPixel( point.x()-1, point.y()+1, id );
00424         pushPixel( point.x(),   point.y()+1, id );
00425         pushPixel( point.x()+1, point.y()+1, id );
00426       }
00427       
00428       //if this pixel has met thresh and has not yet been assigned a unique ID,
00429       //assign it the next unique id and push all valid neighbors
00430       if( regionOfInterest[ x + y*regionWidth ] == 1 )
00431       {
00432         //print last blob stats
00433         if( nextValidID > 2)
00434         {
00435           blobIDs.push( (nextValidID - 1) );
00436           blobSizes.push( blobPixelCount );
00437           blobAspectRatios.push( ((double)(blobBottomRight.x() - blobTopLeft.x()+1)) / 
00438                                           (blobBottomRight.y() - blobTopLeft.y()+1) );
00439         }
00440         
00441         regionOfInterest[x + y*regionWidth] = nextValidID;
00442         pushPixel( x-1, y-1, nextValidID );
00443         pushPixel( x,   y-1, nextValidID );
00444         pushPixel( x+1, y-1, nextValidID );
00445         pushPixel( x-1, y, nextValidID );
00446         pushPixel( x+1, y, nextValidID );
00447         pushPixel( x-1, y+1, nextValidID );
00448         pushPixel( x,   y+1, nextValidID );
00449         pushPixel( x+1, y+1, nextValidID );
00450         nextValidID++;        
00451         
00452         blobPixelCount = 1;
00453         blobTopLeft = QPoint( x, y );
00454         blobBottomRight = QPoint( x, y );
00455       }
00456     } //y
00457   } //x
00458   
00459   //insert last blob stats
00460   if( nextValidID > 2)
00461   {
00462     blobIDs.push( (nextValidID - 1) );
00463     blobSizes.push( blobPixelCount );
00464     blobAspectRatios.push( ((double)(blobBottomRight.x() - blobTopLeft.x()+1)) / (blobBottomRight.y() - blobTopLeft.y()+1) );
00465   }
00466 }
00467 //==============================================
00468 void sortBlobsByDecreasingSize()
00469 {
00470   blobCount = blobIDs.count();
00471   ids = new int[blobCount];
00472   sizes = new int[blobCount];
00473   ratios = new double[blobCount];
00474   
00475   int i,j;
00476   for(i=0; i<blobCount; i++)
00477   {
00478     ids[i] = blobIDs.pop();
00479     sizes[i] = blobSizes.pop();
00480     ratios[i] = blobAspectRatios.pop();
00481   }
00482   
00483   //quick and dirty bubble sort
00484   for(j = blobCount-1; j>0; j--)
00485   {
00486     for(i=0; i<j; i++)
00487     {
00488       if( sizes[i+1] > sizes[i] )
00489       {
00490         int t = sizes[i+1];
00491         sizes[i+1] = sizes[i];
00492         sizes[i] = t;
00493         
00494         t = ids[i+1];
00495         ids[i+1] = ids[i];
00496         ids[i] = t;
00497         
00498         double tR = ratios[i+1];
00499         ratios[i+1] = ratios[i];
00500         ratios[i] = tR;        
00501       }
00502     }
00503   }
00504 }
00505 //==============================================
00506 void findBestTwoBlobs()
00507 {
00508   id1 = -1;
00509   id2 = -1;
00510   int i;
00511   
00512   //special case: 2 blobs found, both larger than 1 pixel
00513   if(blobCount == 2 &&
00514      sizes[0] > 1 &&
00515      sizes[1] > 1)
00516   {
00517     id1 = ids[0];
00518     id2 = ids[1];
00519   }
00520   else
00521   {
00522     for(i=0; i<blobCount-2; i++)
00523     {
00524       //once we hit blobs that are only one pixel large stop because they are probably just noise
00525       if( sizes[i+1] <= 1 ) break;
00526       
00527       double as1 = ratios[i];
00528       double as2 = ratios[i+1];
00529 
00530       if(as1 < 1) as1 = 1.0/as1;
00531       if(as2 < 1) as2 = 1.0/as2;
00532       
00533       if( //both blobs must be semi-circular, prefer those that are wider
00534           ratios[i] > 0.75 &&   ratios[i] < 2 &&
00535           ratios[i+1] > 0.75 && ratios[i+1] < 2 &&
00536           //both blobs must be similar in shape
00537           QMAX(as2,as1)/QMIN(as2,as1) < 2 &&
00538           //both blobs must be similar in size
00539           ((double)QMAX( sizes[i], sizes[i+1] )) / QMIN( sizes[i], sizes[i+1] ) < 1.5 &&
00540           //both blobs must be above a certain thresh size, this prevents selecting blobs that are very very tiny
00541           //if only tiny blobs are around we'll end up desaturating entire region
00542           QMAX( sizes[i], sizes[i+1] ) > 20 )
00543       {
00544         id1 = ids[i];
00545         id2 = ids[i+1];
00546         break;
00547       }    
00548     }
00549   }
00550   
00551   //Comment this sectionin to see what blobs were found and selected
00552 /* cout << "-----\n";
00553   for(i=0; i<blobCount-1; i++)
00554   {
00555     if( ids[i] == id1 || ids[i] == id2 )
00556       cout << "--->";
00557     cout << "ID: " << ids[i] << "count: " << sizes[i] << " w:h: " << ratios[i] << "\n";      
00558   }*/
00559 }
00560 //==============================================
00561 bool IDedPixel( int x, int y)
00562 {
00563   if( x < topLeft.x() || y < topLeft.y() ||
00564       x > bottomRight.x() || y > bottomRight.y() )
00565     return false;
00566   
00567   int regionIndex = x - topLeft.x() + (y-topLeft.y())*regionWidth;
00568   return ( regionOfInterest[regionIndex] == id1 ||
00569            regionOfInterest[regionIndex] == id2 );
00570 }
00571 //==============================================
00572 double desaturateAlpha(int x, int y)
00573 {
00574   int n = 0;
00575   if( IDedPixel(x  ,y  ) ) n++;
00576   
00577   if(n == 1)
00578     return 1.0;
00579   
00580   if( IDedPixel(x-1,y-1) ) n++;
00581   if( IDedPixel(x  ,y-1) ) n++;
00582   if( IDedPixel(x+1,y-1) ) n++;
00583   if( IDedPixel(x-1,y  ) ) n++;
00584   if( IDedPixel(x+1,y  ) ) n++;
00585   if( IDedPixel(x-1,y+1) ) n++;
00586   if( IDedPixel(x  ,y+1) ) n++;
00587   if( IDedPixel(x+1,y+1) ) n++;
00588   
00589   if( IDedPixel(x-2,y-2) ) n++;
00590   if( IDedPixel(x-1,y-2) ) n++;
00591   if( IDedPixel(x  ,y-2) ) n++;
00592   if( IDedPixel(x+1,y-2) ) n++;
00593   if( IDedPixel(x+2,y-2) ) n++;
00594   
00595   if( IDedPixel(x-2,y-1) ) n++;
00596   if( IDedPixel(x+2,y-1) ) n++;
00597   if( IDedPixel(x-2,y  ) ) n++;
00598   if( IDedPixel(x+2,y  ) ) n++;
00599   if( IDedPixel(x-2,y+1) ) n++;
00600   if( IDedPixel(x+2,y+1) ) n++;
00601   
00602   if( IDedPixel(x-2,y+2) ) n++;
00603   if( IDedPixel(x-1,y+2) ) n++;
00604   if( IDedPixel(x  ,y+2) ) n++;
00605   if( IDedPixel(x+1,y+2) ) n++;
00606   if( IDedPixel(x+2,y+2) ) n++;
00607   
00608   
00609   return ((double)n) / 25;
00610 }
00611 //==============================================
00612 void desaturateBlobs()
00613 {
00614   //desaturate bad pixels
00615   int x, y;
00616   double r;
00617   QRgb* rgb;
00618   uchar* scanLine;
00619   for( y = QMAX( topLeft.y()-1, 0); 
00620        y<= QMIN( bottomRight.y()+1, editedImage->height()-1 ); 
00621        y++)
00622   {
00623     scanLine = editedImage->scanLine(y);
00624     for( x =  QMAX( topLeft.x()-1, 0); 
00625          x <= QMIN( bottomRight.x()+1, editedImage->width()-1 ); 
00626          x++)
00627     {      
00628       double alpha = desaturateAlpha( x, y );
00629       if( alpha > 0)
00630       {
00631         rgb = ((QRgb*)scanLine+x);
00632         
00633         r = alpha*(0.05*qRed(*rgb) + 0.6*qGreen(*rgb) + 0.3*qBlue(*rgb)) +
00634           (1-alpha)*qRed(*rgb);
00635         *rgb = qRgb( (int)r,
00636                      qGreen(*rgb),
00637                      qBlue(*rgb) );
00638       } //alpha > 0
00639     } //x
00640   } //y  
00641 }
00642 //==============================================
00643 void desaturateEntireImage(QPoint topLeftExtreme, QPoint bottomRightExtreme)
00644 {
00645   //desaturate bad pixels
00646   int x, y;
00647   QRgb* rgb;
00648   uchar* scanLine;
00649   for( y=topLeftExtreme.y(); y<=bottomRightExtreme.y(); y++)
00650   {
00651     scanLine = editedImage->scanLine(y);
00652     for( x=topLeftExtreme.x(); x<=bottomRightExtreme.x(); x++)
00653     {
00654       rgb = ((QRgb*)scanLine+x);
00655       if( qRed(*rgb) > 2*qGreen(*rgb) )
00656       {
00657         *rgb = qRgb( (int) (0.05*qRed(*rgb) + 0.6*qGreen(*rgb) + 0.3*qBlue(*rgb)),
00658                      qGreen(*rgb),
00659                      qBlue(*rgb) );
00660       } // > thresh
00661     } //x
00662   } //y
00663 }
00664 //==============================================
00665 
00666 
00667 
00668 
00669 

Generated on Wed Jan 24 05:38:05 2007 for AlbumShaper by  doxygen 1.5.1