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

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

Generated on Sat Apr 2 05:44:04 2005 for AlbumShaper by  doxygen 1.3.9.1