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 "emboss.h" 00019 #include "../tools/imageTools.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 embossed version of 00035 // the image by modifying the luminance of each pixel 00036 // by taking a weighted average of pixel luminance within 00037 // a local neighborhood. Most embossing algorithms convert an 00038 // image to grayscale and then combine a pixel's gray value with 00039 // its neighbors using the following convolution matrix: 00040 // 00041 // [ -1 -1 0 00042 // -1 1 1 00043 // 0 1 1 ] 00044 // 00045 // This apporach has two problems. First, all color information 00046 // is lost. Second, the convolution filter is not a function of image 00047 // size, and as a result embossing results on high resolution images are 00048 // barely when viewed on a high resolution screen or printed at high DPI. 00049 // 00050 // My solution to this problem is to: 00051 // 00052 // 1.) Apply the emboss effect on pixel luminance and leave pixel 00053 // color untouched. The varying lightness that results produces 00054 // the same effect, and if a grayscale image is really desired the 00055 // image can be converted to grayscale either before or after the 00056 // emboss effect takes place. 00057 // 00058 // 2.) using neighboring pixels that are a distance D from the 00059 // pixel being modified in the X or Y dimensions. This simply 00060 // modification allows the embossing procedure to run at the 00061 // same speed (larger convolution matrices would slow the algorithm 00062 // to at best O(N^2 * M^2) where there are N^2 pixels in the image 00063 // and the convolution matrix is M^2 in size) while providing the 00064 // same effective emboss effect at any image resolution. This is 00065 // done by computing distance D using the minimum image dimension: 00066 // 00067 // D = MIN( width, height ) 00068 // 00069 // At each pixel, we compute an average luminance of the 6 00070 // neighbors and combine these values with a 50% luminance value for 00071 // the pixel in question. This luminance value is used to replace 00072 // the current pixel luminance before converting it back to RGB 00073 // space and storing in the edited image object. 00074 // 00075 // Neighbor pixels that are clamped to be on the image plane to 00076 // prevent lookups of neighbor pixels with negative coordinates or 00077 // coordinates beyond the width/height of the image in question. 00078 //---------------------------------------------- 00079 00080 //============================================== 00081 QImage* embossEffect( QString filename, StatusWidget* status ) 00082 { 00083 //load original image 00084 QImage originalImage( filename ); 00085 00086 //create edited image 00087 QImage* editedImage = new QImage( filename ); 00088 00089 //determine if busy indicators will be used 00090 bool useBusyIndicators = (status != NULL); 00091 00092 //setup progress bar 00093 if(useBusyIndicators) 00094 { 00095 QString statusMessage = qApp->translate( "embossEffect", "Applying Emboss Effect:" ); 00096 status->showProgressBar( statusMessage, 100 ); 00097 qApp->processEvents(); 00098 } 00099 00100 //update progress bar for every 1% of completion 00101 const int updateIncrement = (int) ( 0.01 * originalImage.width() * originalImage.height() ); 00102 int newProgress = 0; 00103 00104 //iterate over each selected scanline 00105 int x, y; 00106 QRgb* rgb; 00107 uchar* scanLine; 00108 00109 int yPrev, yNext, xPrev, xNext; 00110 00111 //compute the radius using image resolution 00112 double minDimen = (double) QMIN( editedImage->width(), editedImage->height() ); 00113 const int embossRadius = (int) QMAX( 1, (sqrt(minDimen)/8) ); 00114 00115 for( y=0; y<editedImage->height(); y++) 00116 { 00117 scanLine = originalImage.scanLine(y); 00118 00119 //compute previous and next y pixel coordinates 00120 yPrev = QMAX( y-embossRadius, 0 ); 00121 yNext = QMIN( y+embossRadius, editedImage->height() - 1 ); 00122 00123 //iterate over each selected pixel in scanline 00124 for( x=0; x<editedImage->width(); x++) 00125 { 00126 //compute previous and next x pixel coordinates 00127 xPrev = QMAX( x-embossRadius, 0 ); 00128 xNext = QMIN( x+embossRadius, editedImage->width() - 1 ); 00129 00130 //start with a default luminance of 128 (50% luminance) 00131 int sum = 128; 00132 00133 //sum weighted gray values of neighbors 00134 scanLine = originalImage.scanLine( yPrev ); 00135 sum-= qGray( *((QRgb*)scanLine + xPrev ) ); 00136 sum-= qGray( *((QRgb*)scanLine + x ) ); 00137 00138 scanLine = originalImage.scanLine( y ); 00139 sum-= qGray( *((QRgb*)scanLine + xPrev ) ); 00140 sum+= qGray( *((QRgb*)scanLine + xNext ) ); 00141 00142 scanLine = originalImage.scanLine( yNext ); 00143 sum+= qGray( *((QRgb*)scanLine + x ) ); 00144 sum+= qGray( *((QRgb*)scanLine + xNext ) ); 00145 00146 //clamp sum to within 0-255 range 00147 sum = QMAX( QMIN( sum, 255), 0 ); 00148 00149 //get original pixel color in HSV space 00150 scanLine = editedImage->scanLine(y); 00151 rgb = ((QRgb*)scanLine+x); 00152 double r = ((double)qRed(*rgb) )/255.0; 00153 double g = ((double)qGreen(*rgb) )/255.0; 00154 double b = ((double)qBlue(*rgb) )/255.0; 00155 00156 //convert to hsv 00157 double h,s,v; 00158 RGBtoHSV(r,g,b,&h,&s,&v); 00159 00160 //reset v 00161 v = ((double)sum)/255; 00162 00163 //convert adjusted color back to rgb colorspace and clamp 00164 HSVtoRGB( &r,&g,&b, h,s,v); 00165 int rp = (int) QMIN( QMAX((r*255), 0), 255 ); 00166 int gp = (int) QMIN( QMAX((g*255), 0), 255 ); 00167 int bp = (int) QMIN( QMAX((b*255), 0), 255 ); 00168 00169 //set adjusted color value 00170 *rgb = qRgb(rp,gp,bp); 00171 00172 //update status bar if significant progress has been made since last update 00173 if(useBusyIndicators) 00174 { 00175 newProgress++; 00176 if(newProgress >= updateIncrement) 00177 { 00178 newProgress = 0; 00179 status->incrementProgress(); 00180 qApp->processEvents(); 00181 } 00182 } 00183 00184 } 00185 } 00186 00187 //return pointer to edited image 00188 return editedImage; 00189 } 00190 //==============================================