painting.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 #include <math.h>
00016 
00017 //Projectwide includes
00018 #include "painting.h"
00019 #include "manipulationOptions.h"
00020 #include "../../gui/statusWidget.h"
00021 
00022 //----------------------------------------------
00023 // Inputs:
00024 // -------
00025 // QString filename - location of original image on disk
00026 // StatusWidget* status - widget for making progress visible to user
00027 //
00028 // Outputs:
00029 // --------
00030 // QImage* returned - constructed image
00031 //
00032 // Description:
00033 // ------------
00034 // This method constructs an oil painting version of
00035 // the image by replacing each pixel with an average of the
00036 // original pixel color and the most common pixel color within a local radius.
00037 //
00038 // A histogram of color values (which fall in the 0-255 range) 
00039 // is constructed at each pixel for all pixels without a given 
00040 // radius. The most commonly occuring red, green, and blue color 
00041 // values are found and used in combination with the current color 
00042 // to produce the oil effect. This is done because in oil painting 
00043 // (and water colors) color bleeds out from a given area across the
00044 // canvass. By averaging with the most common color in a given 
00045 // neighborhood the larger blobs spread and the higher frequency 
00046 // information (details) fade into the background.
00047 //
00048 // TODO:
00049 // The local area idealy would be circular, but currently is square.
00050 //
00051 // TODO:
00052 // Experiment adaptively adjusting the oil radius using local image contrast measure?
00053 //
00054 // TODO:
00055 // Come up with method for avoiding strange color shifts near object boundaries.
00056 //----------------------------------------------
00057 
00058 //==============================================
00059 struct Triplet
00060 { int r,g,b; };
00061 //----------------------------------------------
00062 struct Histogram
00063 {
00064   //histogram data
00065   Triplet values[256]; 
00066   
00067   //index of highest count for each component
00068   Triplet highestCountIndex;
00069 };
00070 //----------------------------------------------
00071 Histogram histogram;
00072 //----------------------------------------------
00073 void resetHistogram()
00074 {
00075   static int i;
00076   for(i=0;i<256;i++)
00077   {
00078     histogram.values[i].r = 0;
00079     histogram.values[i].g = 0;
00080     histogram.values[i].b = 0;
00081   }
00082   histogram.highestCountIndex.r = 0;
00083   histogram.highestCountIndex.g = 0;
00084   histogram.highestCountIndex.b = 0;
00085 }
00086 //----------------------------------------------
00087 void findHighestCounts()
00088 {
00089   static int i;
00090   for(i = 1; i<256; i++)
00091   {    
00092     if( histogram.values[i].r > histogram.values[ histogram.highestCountIndex.r ].r )    
00093     { histogram.highestCountIndex.r = i; }
00094 
00095     if( histogram.values[i].g > histogram.values[ histogram.highestCountIndex.g ].g )    
00096     { histogram.highestCountIndex.g = i; }
00097   
00098     if( histogram.values[i].b > histogram.values[ histogram.highestCountIndex.b ].b )    
00099     { histogram.highestCountIndex.b = i; }                    
00100   }
00101 }
00102 //----------------------------------------------
00103 QImage* oilPaintingEffect( QString filename, ManipulationOptions* options )
00104 {
00105   //load original image  
00106   QImage originalImage( filename );
00107 
00108   //convert to 32-bit depth if necessary
00109   if( originalImage.depth() < 32 ) { originalImage = originalImage.convertDepth( 32, Qt::AutoColor ); }
00110 
00111 //determine if busy indicators will be used
00112   bool useBusyIndicators = false;
00113   StatusWidget* status = NULL;
00114   if( options != NULL && options->getStatus() != NULL )
00115   {
00116     useBusyIndicators = true;
00117     status = options->getStatus(); 
00118   }
00119   
00120   //setup progress bar
00121   if(useBusyIndicators)
00122   {
00123     QString statusMessage = qApp->translate( "oilPaintingEffect", "Applying Oil Painting Effect:" );
00124     status->showProgressBar( statusMessage, 100 );
00125     qApp->processEvents();  
00126   }
00127 
00128   //update progress bar for every 1% of completion
00129   const int updateIncrement = (int) ( 0.01 * originalImage.width() * originalImage.height() );
00130   int newProgress = 0; 
00131   
00132   //construct edited image
00133   QImage* editedImage = new QImage( filename );
00134   
00135   //convert to 32-bit depth if necessary
00136   if( editedImage->depth() < 32 )
00137   {
00138     QImage* tmp = editedImage;
00139     editedImage = new QImage( tmp->convertDepth( 32, Qt::AutoColor ) );
00140     delete tmp; tmp=NULL;
00141   }
00142   
00143   //compute the radius using image resolution
00144   double minDimen = (double) QMIN( editedImage->width(), editedImage->height() );  
00145   const int RADIUS = (int) QMAX( 2, (sqrt(minDimen)/4) );
00146   
00147   //iterate over image
00148   int originalImageX, originalImageY;
00149   int editedImageX, editedImageY;
00150   int clampedX, clampedY;
00151   int trailingEdgeY, leadingEdgeY;
00152   
00153   QRgb* rgb;
00154   uchar* scanLine;  
00155   uchar* trailingScanLine;
00156   uchar* leadingScanLine;
00157 
00158   //iterate over columns    
00159   for( editedImageX=0; editedImageX < editedImage->width(); editedImageX++)
00160   {
00161     //------------------
00162     //reset histogram object
00163     resetHistogram();
00164     //------------------
00165     //fill histogram with data that would have results from Y=-1
00166     for(originalImageY =  0 - 1 - RADIUS; 
00167         originalImageY <= 0 - 1 + RADIUS; 
00168         originalImageY++)
00169     {
00170       clampedY = QMAX( QMIN( originalImageY, originalImage.height() - 1 ), 0 );        
00171       scanLine = originalImage.scanLine( clampedY );
00172       
00173       for(originalImageX =  editedImageX - RADIUS;
00174           originalImageX <= editedImageX + RADIUS; 
00175           originalImageX++)
00176       {
00177         clampedX = QMAX( QMIN( originalImageX, originalImage.width() - 1 ), 0 );
00178         
00179         //get rgb value
00180         rgb = ((QRgb*)scanLine+clampedX);
00181         
00182         //update counts for this r/g/b value
00183         histogram.values[ qRed(*rgb)   ].r++;
00184         histogram.values[ qGreen(*rgb) ].g++;
00185         histogram.values[ qBlue(*rgb)   ].b++;        
00186       } //originalX
00187     } //originalY
00188     //------------------
00189 
00190     //now iterate over rows by simply removing trailing edge data and adding leading edge data
00191     for( editedImageY=0; editedImageY < editedImage->height(); editedImageY++)
00192     {             
00193       trailingEdgeY = QMAX( QMIN( editedImageY-1-RADIUS, originalImage.height() - 1 ), 0 );        
00194       leadingEdgeY  = QMAX( QMIN( editedImageY+RADIUS, originalImage.height() - 1 ), 0 );        
00195     
00196       trailingScanLine = originalImage.scanLine( trailingEdgeY );
00197       leadingScanLine  = originalImage.scanLine( leadingEdgeY );
00198   
00199       for(originalImageX =  editedImageX - RADIUS;
00200           originalImageX <= editedImageX + RADIUS; 
00201           originalImageX++)
00202       {
00203         clampedX = QMAX( QMIN( originalImageX, originalImage.width() - 1 ), 0 );
00204        
00205         //remove trail edge data
00206         rgb = ((QRgb*)trailingScanLine+clampedX);
00207         histogram.values[ qRed(*rgb)   ].r--;
00208         histogram.values[ qGreen(*rgb) ].g--;
00209         histogram.values[ qBlue(*rgb)   ].b--;        
00210         
00211         //add leading edge data
00212         rgb = ((QRgb*)leadingScanLine+clampedX);
00213         histogram.values[ qRed(*rgb)   ].r++;
00214         histogram.values[ qGreen(*rgb) ].g++;
00215         histogram.values[ qBlue(*rgb)   ].b++;        
00216       } //originalX
00217 
00218       //find highest color counts
00219       findHighestCounts();
00220       
00221       //replace each color channel value with average of 
00222       //current value and most occuring value within neighborhood
00223       scanLine = editedImage->scanLine( editedImageY );
00224       rgb = ((QRgb*)scanLine+editedImageX);                                         
00225       *rgb = qRgb( (qRed(*rgb)   + histogram.highestCountIndex.r) / 2,
00226                    (qGreen(*rgb) + histogram.highestCountIndex.g) / 2,
00227                    (qBlue(*rgb)  + histogram.highestCountIndex.b) / 2 );                            
00228 
00229 
00230       //update status bar if significant progress has been made since last update
00231       if(useBusyIndicators)
00232       {
00233         newProgress++;
00234         if(newProgress >= updateIncrement)
00235         {
00236           newProgress = 0;
00237           status->incrementProgress();
00238           qApp->processEvents();  
00239         }
00240       }
00241       
00242     } //editedImageX
00243   } //editedImageY
00244   
00245   //return pointer to edited image
00246   return editedImage;  
00247 }
00248 //==============================================

Generated on Thu Jan 3 10:54:40 2008 for AlbumShaper by  doxygen 1.5.4