Main Page | Namespace List | Class Hierarchy | Class List | File List | Class Members | File Members

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 Sun Mar 4 19:42:56 2007 for AlbumShaper by doxygen 1.3.7