AlbumShaper
1.0a3
|
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 //==============================================