AlbumShaper  1.0a3
Defines | Functions
redEye.cpp File Reference
#include <qimage.h>
#include <qstring.h>
#include <qapplication.h>
#include "redEye.h"
#include "redEye_internal.h"
#include "../../gui/statusWidget.h"
Include dependency graph for redEye.cpp:

Go to the source code of this file.

Defines

#define MIN_RED_VAL   40

Functions

QImage * removeRedeyeRegions (QString filename, QPoint topLeftExtreme, QPoint bottomRightExtreme, StatusWidget *statusWidget)
void findRegionOfInterest (QPoint topLeftExtreme, QPoint bottomRightExtreme)
void pushPixel (int x, int y, int id)
void findBlobs ()
void sortBlobsByDecreasingSize ()
void findBestTwoBlobs ()
bool IDedPixel (int x, int y)
double desaturateAlpha (int x, int y)
void desaturateBlobs ()
void desaturateEntireImage (QPoint topLeftExtreme, QPoint bottomRightExtreme)

Define Documentation

#define MIN_RED_VAL   40

Definition at line 302 of file redEye.cpp.

Referenced by findBlobs(), and findRegionOfInterest().


Function Documentation

double desaturateAlpha ( int  x,
int  y 
)

Definition at line 572 of file redEye.cpp.

References IDedPixel().

Referenced by desaturateBlobs().

{
  int n = 0;
  if( IDedPixel(x  ,y  ) ) n++;
  
  if(n == 1)
    return 1.0;
  
  if( IDedPixel(x-1,y-1) ) n++;
  if( IDedPixel(x  ,y-1) ) n++;
  if( IDedPixel(x+1,y-1) ) n++;
  if( IDedPixel(x-1,y  ) ) n++;
  if( IDedPixel(x+1,y  ) ) n++;
  if( IDedPixel(x-1,y+1) ) n++;
  if( IDedPixel(x  ,y+1) ) n++;
  if( IDedPixel(x+1,y+1) ) n++;
  
  if( IDedPixel(x-2,y-2) ) n++;
  if( IDedPixel(x-1,y-2) ) n++;
  if( IDedPixel(x  ,y-2) ) n++;
  if( IDedPixel(x+1,y-2) ) n++;
  if( IDedPixel(x+2,y-2) ) n++;
  
  if( IDedPixel(x-2,y-1) ) n++;
  if( IDedPixel(x+2,y-1) ) n++;
  if( IDedPixel(x-2,y  ) ) n++;
  if( IDedPixel(x+2,y  ) ) n++;
  if( IDedPixel(x-2,y+1) ) n++;
  if( IDedPixel(x+2,y+1) ) n++;
  
  if( IDedPixel(x-2,y+2) ) n++;
  if( IDedPixel(x-1,y+2) ) n++;
  if( IDedPixel(x  ,y+2) ) n++;
  if( IDedPixel(x+1,y+2) ) n++;
  if( IDedPixel(x+2,y+2) ) n++;
  
  
  return ((double)n) / 25;
}
void desaturateBlobs ( )

Definition at line 612 of file redEye.cpp.

References bottomRight, desaturateAlpha(), editedImage, and topLeft.

Referenced by removeRedeyeRegions().

{
  //desaturate bad pixels
  int x, y;
  double r;
  QRgb* rgb;
  uchar* scanLine;
  for( y = QMAX( topLeft.y()-1, 0); 
       y<= QMIN( bottomRight.y()+1, editedImage->height()-1 ); 
       y++)
  {
    scanLine = editedImage->scanLine(y);
    for( x =  QMAX( topLeft.x()-1, 0); 
         x <= QMIN( bottomRight.x()+1, editedImage->width()-1 ); 
         x++)
    {      
      double alpha = desaturateAlpha( x, y );
      if( alpha > 0)
      {
        rgb = ((QRgb*)scanLine+x);
        
        r = alpha*(0.05*qRed(*rgb) + 0.6*qGreen(*rgb) + 0.3*qBlue(*rgb)) +
          (1-alpha)*qRed(*rgb);
        *rgb = qRgb( (int)r,
                     qGreen(*rgb),
                     qBlue(*rgb) );
      } //alpha > 0
    } //x
  } //y  
}
void desaturateEntireImage ( QPoint  topLeftExtreme,
QPoint  bottomRightExtreme 
)

Definition at line 643 of file redEye.cpp.

References editedImage.

Referenced by removeRedeyeRegions().

{
  //desaturate bad pixels
  int x, y;
  QRgb* rgb;
  uchar* scanLine;
  for( y=topLeftExtreme.y(); y<=bottomRightExtreme.y(); y++)
  {
    scanLine = editedImage->scanLine(y);
    for( x=topLeftExtreme.x(); x<=bottomRightExtreme.x(); x++)
    {
      rgb = ((QRgb*)scanLine+x);
      if( qRed(*rgb) > 2*qGreen(*rgb) )
      {
        *rgb = qRgb( (int) (0.05*qRed(*rgb) + 0.6*qGreen(*rgb) + 0.3*qBlue(*rgb)),
                     qGreen(*rgb),
                     qBlue(*rgb) );
      } // > thresh
    } //x
  } //y
}
void findBestTwoBlobs ( )

Definition at line 506 of file redEye.cpp.

References blobCount, id1, id2, ids, ratios, and sizes.

Referenced by removeRedeyeRegions().

{
  id1 = -1;
  id2 = -1;
  int i;
  
  //special case: 2 blobs found, both larger than 1 pixel
  if(blobCount == 2 &&
     sizes[0] > 1 &&
     sizes[1] > 1)
  {
    id1 = ids[0];
    id2 = ids[1];
  }
  else
  {
    for(i=0; i<blobCount-2; i++)
    {
      //once we hit blobs that are only one pixel large stop because they are probably just noise
      if( sizes[i+1] <= 1 ) break;
      
      double as1 = ratios[i];
      double as2 = ratios[i+1];

      if(as1 < 1) as1 = 1.0/as1;
      if(as2 < 1) as2 = 1.0/as2;
      
      if( //both blobs must be semi-circular, prefer those that are wider
          ratios[i] > 0.75 &&   ratios[i] < 2 &&
          ratios[i+1] > 0.75 && ratios[i+1] < 2 &&
          //both blobs must be similar in shape
          QMAX(as2,as1)/QMIN(as2,as1) < 2 &&
          //both blobs must be similar in size
          ((double)QMAX( sizes[i], sizes[i+1] )) / QMIN( sizes[i], sizes[i+1] ) < 1.5 &&
          //both blobs must be above a certain thresh size, this prevents selecting blobs that are very very tiny
          //if only tiny blobs are around we'll end up desaturating entire region
          QMAX( sizes[i], sizes[i+1] ) > 20 )
      {
        id1 = ids[i];
        id2 = ids[i+1];
        break;
      }    
    }
  }
  
  //Comment this sectionin to see what blobs were found and selected
/* cout << "-----\n";
  for(i=0; i<blobCount-1; i++)
  {
    if( ids[i] == id1 || ids[i] == id2 )
      cout << "--->";
    cout << "ID: " << ids[i] << "count: " << sizes[i] << " w:h: " << ratios[i] << "\n";      
  }*/
}
void findBlobs ( )

Definition at line 372 of file redEye.cpp.

References blobAspectRatios, blobBottomRight, blobIDs, blobPixelCount, blobSizes, blobTopLeft, bottomRight, MIN_RED_VAL, pushPixel(), rawImage, regionHeight, regionOfInterest, regionWidth, spreadablePixels, and topLeft.

Referenced by removeRedeyeRegions().

{
  //create small matrix for region of interest
  regionWidth = bottomRight.x() - topLeft.x() + 1;
  regionHeight = bottomRight.y() - topLeft.y() + 1;  
  regionOfInterest = new int[ regionWidth * regionHeight ];
  
  //set all pixels that meet thresh to 1, all others to 0
  int x, y;
  int x2, y2;
  QRgb* rgb;
  uchar* scanLine;
  for( y=topLeft.y(); y<=bottomRight.y(); y++)
  {
    y2 = y - topLeft.y();
    
    scanLine = rawImage.scanLine(y);
    for( x=topLeft.x(); x<=bottomRight.x(); x++)
    {
    
      x2 = x - topLeft.x();
      
      rgb = ((QRgb*)scanLine+x);
      
      bool threshMet = qRed(*rgb) > 2*qGreen(*rgb) &&
                       qRed(*rgb) > MIN_RED_VAL;
      
      if(threshMet)
        regionOfInterest[ x2 + y2*regionWidth ] = 1;
      else
        regionOfInterest[ x2 + y2*regionWidth ] = 0;
    }
  } 
  
  //walk over region of interest and propogate blobs
  int nextValidID = 2;
  for(x = 0; x<regionWidth; x++)
  {
    for(y = 0; y<regionHeight; y++)
    {
      //if any blobs can be propogated handle them first
      while( !spreadablePixels.empty() )
      {
        QPoint point = spreadablePixels.pop();
        int id = regionOfInterest[ point.x() + point.y()*regionWidth ];
        
        pushPixel( point.x()-1, point.y()-1, id );
        pushPixel( point.x(),   point.y()-1, id );
        pushPixel( point.x()+1, point.y()-1, id );
        pushPixel( point.x()-1, point.y(), id );
        pushPixel( point.x()+1, point.y(), id );
        pushPixel( point.x()-1, point.y()+1, id );
        pushPixel( point.x(),   point.y()+1, id );
        pushPixel( point.x()+1, point.y()+1, id );
      }
      
      //if this pixel has met thresh and has not yet been assigned a unique ID,
      //assign it the next unique id and push all valid neighbors
      if( regionOfInterest[ x + y*regionWidth ] == 1 )
      {
        //print last blob stats
        if( nextValidID > 2)
        {
          blobIDs.push( (nextValidID - 1) );
          blobSizes.push( blobPixelCount );
          blobAspectRatios.push( ((double)(blobBottomRight.x() - blobTopLeft.x()+1)) / 
                                          (blobBottomRight.y() - blobTopLeft.y()+1) );
        }
        
        regionOfInterest[x + y*regionWidth] = nextValidID;
        pushPixel( x-1, y-1, nextValidID );
        pushPixel( x,   y-1, nextValidID );
        pushPixel( x+1, y-1, nextValidID );
        pushPixel( x-1, y, nextValidID );
        pushPixel( x+1, y, nextValidID );
        pushPixel( x-1, y+1, nextValidID );
        pushPixel( x,   y+1, nextValidID );
        pushPixel( x+1, y+1, nextValidID );
        nextValidID++;        
        
        blobPixelCount = 1;
        blobTopLeft = QPoint( x, y );
        blobBottomRight = QPoint( x, y );
      }
    } //y
  } //x
  
  //insert last blob stats
  if( nextValidID > 2)
  {
    blobIDs.push( (nextValidID - 1) );
    blobSizes.push( blobPixelCount );
    blobAspectRatios.push( ((double)(blobBottomRight.x() - blobTopLeft.x()+1)) / (blobBottomRight.y() - blobTopLeft.y()+1) );
  }
}
void findRegionOfInterest ( QPoint  topLeftExtreme,
QPoint  bottomRightExtreme 
)

Definition at line 305 of file redEye.cpp.

References bottomRight, StatusWidget::incrementProgress(), MIN_RED_VAL, newProgress, rawImage, status, topLeft, and updateIncrement.

Referenced by removeRedeyeRegions().

{
  topLeft = QPoint(-1,-1);
  bottomRight = QPoint(-1,-1);
  
  int x, y;
  QRgb* rgb;
  uchar* scanLine;
  for( y=topLeftExtreme.y(); y<=bottomRightExtreme.y(); y++)
  {
    scanLine = rawImage.scanLine(y);
    for( x=topLeftExtreme.x(); x<=bottomRightExtreme.x(); x++)
    {
      rgb = ((QRgb*)scanLine+x);
      
      bool threshMet = qRed(*rgb) > 2*qGreen(*rgb) &&
                      qRed(*rgb) > MIN_RED_VAL;
      if(threshMet)
      {
        //first pixel
        if(topLeft.x() == -1) 
        {
          topLeft = QPoint(x,y);
          bottomRight = QPoint(x,y);
        }
        
        if(x < topLeft.x() ) topLeft.setX( x );
        if(y < topLeft.y() ) topLeft.setY( y );
        if(x > bottomRight.x() ) bottomRight.setX( x );
        if(y > bottomRight.y() ) bottomRight.setY( y );
      }
      
      //update status bar if significant progress has been made since last update
      newProgress++;
      if(newProgress >= updateIncrement)
      {
        newProgress = 0;
        status->incrementProgress();
        qApp->processEvents();  
      }
      
    }
  }  
}
bool IDedPixel ( int  x,
int  y 
)

Definition at line 561 of file redEye.cpp.

References bottomRight, id1, id2, regionIndex(), regionOfInterest, regionWidth, and topLeft.

Referenced by desaturateAlpha().

{
  if( x < topLeft.x() || y < topLeft.y() ||
      x > bottomRight.x() || y > bottomRight.y() )
    return false;
  
  int regionIndex = x - topLeft.x() + (y-topLeft.y())*regionWidth;
  return ( regionOfInterest[regionIndex] == id1 ||
           regionOfInterest[regionIndex] == id2 );
}
void pushPixel ( int  x,
int  y,
int  id 
)

Definition at line 350 of file redEye.cpp.

References blobBottomRight, blobPixelCount, blobTopLeft, regionHeight, regionOfInterest, regionWidth, and spreadablePixels.

Referenced by findBlobs().

{
  //if pixel off image or below thresh ignore push attempt
  if(  x < 0  || 
       y <  0 ||
       x >= regionWidth ||
       y >= regionHeight ||
       regionOfInterest[ x + y*regionWidth ] != 1 )
    return;
  
  //passes! set id and actually put pixel onto stack
  regionOfInterest[ x + y*regionWidth] = id;  
  spreadablePixels.push( QPoint( x, y ) );
  
  //increase blob pixel count and update topLeft and bottomRight
  blobPixelCount++;
  blobTopLeft.setX( QMIN( x, blobTopLeft.x() ) );
  blobTopLeft.setY( QMIN( y, blobTopLeft.y() ) );
  blobBottomRight.setX( QMAX( x, blobBottomRight.x() ) );
  blobBottomRight.setY( QMAX( y, blobBottomRight.y() ) );
}
QImage* removeRedeyeRegions ( QString  filename,
QPoint  topLeftExtreme,
QPoint  bottomRightExtreme,
StatusWidget statusWidget 
)

Definition at line 206 of file redEye.cpp.

References desaturateBlobs(), desaturateEntireImage(), editedImage, findBestTwoBlobs(), findBlobs(), findRegionOfInterest(), id1, newProgress, rawImage, StatusWidget::setStatus(), StatusWidget::showProgressBar(), sortBlobsByDecreasingSize(), status, topLeft, and updateIncrement.

Referenced by EditingInterface::removeRedeye().

{
  //store handle to status widget
  status = statusWidget;
  
  //load original image
  rawImage = QImage( filename );
  
  //sanity check: unable to load image
  if(rawImage.isNull()) { return NULL; }

  //convert to 32-bit depth if necessary
  if( rawImage.depth() < 32 ) { rawImage = rawImage.convertDepth( 32, Qt::AutoColor ); }
   
  //sanity check: make sure topLeftExtreme and bottomRightExtreme are within image boundary
  topLeftExtreme.setX( QMAX( topLeftExtreme.x(), 0 ) );
  topLeftExtreme.setY( QMAX( topLeftExtreme.y(), 0 ) );
  bottomRightExtreme.setX( QMIN( bottomRightExtreme.x(), rawImage.width()-1 ) );
  bottomRightExtreme.setY( QMIN( bottomRightExtreme.y(), rawImage.height()-1 ) );

  //setup progress bar
  QString statusMessage = qApp->translate( "removeRedeyeRegions", "Removing Red-Eye:" );
  status->showProgressBar( statusMessage, 100 );
  qApp->processEvents();  
  
  //update progress bar for every 1% of completion
  updateIncrement = (int) ( 0.01 * 
                            ( bottomRightExtreme.x() - topLeftExtreme.x() + 1 ) *
                            ( bottomRightExtreme.y() - topLeftExtreme.y() + 1 ) );
  newProgress = 0;   

  //find region of interest: constrain search box to boundary that actually contains red enough pixels
  findRegionOfInterest(topLeftExtreme, bottomRightExtreme);

  //if no pixels were found then immediately return a NULL pointer signaling no change
  if(topLeft.x() == -1) 
  { 
    //hide progress bar
    status->setStatus( "" );
    qApp->processEvents();

    return NULL; 
  }

  //load an editing image
  //two images mus be loaded becuase pixel values are replaced
  //using a compbination of niehgbors and their own in order
  //to avoid sharp lines at the edge of the saturated region
  editedImage = new QImage( filename );
  
  //sanity check: unable to allocated edited image
  if( editedImage == NULL) 
  { 
    //hide progress bar
    status->setStatus( "" );
    qApp->processEvents();

    return NULL; 
  }

  //convert to 32-bit depth if necessary
  if( editedImage->depth() < 32 )
  {
    QImage* tmp = editedImage;
    editedImage = new QImage( tmp->convertDepth( 32, Qt::AutoColor ) );
    delete tmp; tmp=NULL;
  }
  
  findBlobs();
  sortBlobsByDecreasingSize();
  findBestTwoBlobs();

  //if we found two good blobs then desaturate those only
  if(id1 != -1)
  {
    desaturateBlobs();
  }
  //else desaturate all pixels above thresh within selection area
  else
  {
    desaturateEntireImage(topLeftExtreme, bottomRightExtreme);
  }

  //remove status bar
  status->setStatus( "" );
  qApp->processEvents();

  //return pointer to edited image
  return editedImage;      
}
void sortBlobsByDecreasingSize ( )

Definition at line 468 of file redEye.cpp.

References blobAspectRatios, blobCount, blobIDs, blobSizes, ids, ratios, and sizes.

Referenced by removeRedeyeRegions().

{
  blobCount = blobIDs.count();
  ids = new int[blobCount];
  sizes = new int[blobCount];
  ratios = new double[blobCount];
  
  int i,j;
  for(i=0; i<blobCount; i++)
  {
    ids[i] = blobIDs.pop();
    sizes[i] = blobSizes.pop();
    ratios[i] = blobAspectRatios.pop();
  }
  
  //quick and dirty bubble sort
  for(j = blobCount-1; j>0; j--)
  {
    for(i=0; i<j; i++)
    {
      if( sizes[i+1] > sizes[i] )
      {
        int t = sizes[i+1];
        sizes[i+1] = sizes[i];
        sizes[i] = t;
        
        t = ids[i+1];
        ids[i+1] = ids[i];
        ids[i] = t;
        
        double tR = ratios[i+1];
        ratios[i+1] = ratios[i];
        ratios[i] = tR;        
      }
    }
  }
}