//============================================== // copyright : (C) 2003-2005 by Will Stokes //============================================== // This program is free software; you can redistribute it // and/or modify it under the terms of the GNU General // Public License as published by the Free Software // Foundation; either version 2 of the License, or // (at your option) any later version. //============================================== //Systemwide includes #include #include #include #include //Projectwide includes #include "emboss.h" #include "manipulationOptions.h" #include "../tools/imageTools.h" #include "../../gui/statusWidget.h" //---------------------------------------------- // Inputs: // ------- // QString filename - location of original image on disk // StatusWidget* status - widget for making progress visible to user // // Outputs: // -------- // QImage* returned - constructed image // // Description: // ------------ // This method constructs an embossed version of // the image by modifying the luminance of each pixel // by taking a weighted average of pixel luminance within // a local neighborhood. Most embossing algorithms convert an // image to grayscale and then combine a pixel's gray value with // its neighbors using the following convolution matrix: // // [ -1 -1 0 // -1 1 1 // 0 1 1 ] // // This apporach has two problems. First, all color information // is lost. Second, the convolution filter is not a function of image // size, and as a result embossing results on high resolution images are // barely when viewed on a high resolution screen or printed at high DPI. // // My solution to this problem is to: // // 1.) Apply the emboss effect on pixel luminance and leave pixel // color untouched. The varying lightness that results produces // the same effect, and if a grayscale image is really desired the // image can be converted to grayscale either before or after the // emboss effect takes place. // // 2.) using neighboring pixels that are a distance D from the // pixel being modified in the X or Y dimensions. This simply // modification allows the embossing procedure to run at the // same speed (larger convolution matrices would slow the algorithm // to at best O(N^2 * M^2) where there are N^2 pixels in the image // and the convolution matrix is M^2 in size) while providing the // same effective emboss effect at any image resolution. This is // done by computing distance D using the minimum image dimension: // // D = MIN( width, height ) // // At each pixel, we compute an average luminance of the 6 // neighbors and combine these values with a 50% luminance value for // the pixel in question. This luminance value is used to replace // the current pixel luminance before converting it back to RGB // space and storing in the edited image object. // // Neighbor pixels that are clamped to be on the image plane to // prevent lookups of neighbor pixels with negative coordinates or // coordinates beyond the width/height of the image in question. //---------------------------------------------- //============================================== QImage* embossEffect( QString filename, ManipulationOptions* options ) { //load original image QImage originalImage( filename ); //convert to 32-bit depth if necessary if( originalImage.depth() < 32 ) { originalImage = originalImage.convertDepth( 32, Qt::AutoColor ); } //create edited image QImage* editedImage = new QImage( filename ); //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; } //determine if busy indicators will be used bool useBusyIndicators = false; StatusWidget* status = NULL; if( options != NULL && options->getStatus() != NULL ) { useBusyIndicators = true; status = options->getStatus(); } //setup progress bar if(useBusyIndicators) { QString statusMessage = qApp->translate( "embossEffect", "Applying Emboss Effect:" ); status->showProgressBar( statusMessage, 100 ); qApp->processEvents(); } //update progress bar for every 1% of completion const int updateIncrement = (int) ( 0.01 * originalImage.width() * originalImage.height() ); int newProgress = 0; //iterate over each selected scanline int x, y; QRgb* rgb; uchar* scanLine; int yPrev, yNext, xPrev, xNext; //compute the radius using image resolution double minDimen = (double) QMIN( editedImage->width(), editedImage->height() ); const int embossRadius = (int) QMAX( 1, (sqrt(minDimen)/8) ); for( y=0; yheight(); y++) { scanLine = originalImage.scanLine(y); //compute previous and next y pixel coordinates yPrev = QMAX( y-embossRadius, 0 ); yNext = QMIN( y+embossRadius, editedImage->height() - 1 ); //iterate over each selected pixel in scanline for( x=0; xwidth(); x++) { //compute previous and next x pixel coordinates xPrev = QMAX( x-embossRadius, 0 ); xNext = QMIN( x+embossRadius, editedImage->width() - 1 ); //start with a default luminance of 128 (50% luminance) int sum = 128; //sum weighted gray values of neighbors scanLine = originalImage.scanLine( yPrev ); sum-= qGray( *((QRgb*)scanLine + xPrev ) ); sum-= qGray( *((QRgb*)scanLine + x ) ); scanLine = originalImage.scanLine( y ); sum-= qGray( *((QRgb*)scanLine + xPrev ) ); sum+= qGray( *((QRgb*)scanLine + xNext ) ); scanLine = originalImage.scanLine( yNext ); sum+= qGray( *((QRgb*)scanLine + x ) ); sum+= qGray( *((QRgb*)scanLine + xNext ) ); //clamp sum to within 0-255 range sum = QMAX( QMIN( sum, 255), 0 ); //get original pixel color in HSV space scanLine = editedImage->scanLine(y); rgb = ((QRgb*)scanLine+x); double r = ((double)qRed(*rgb) )/255.0; double g = ((double)qGreen(*rgb) )/255.0; double b = ((double)qBlue(*rgb) )/255.0; //convert to hsv double h,s,v; RGBtoHSV(r,g,b,&h,&s,&v); //reset v v = ((double)sum)/255; //convert adjusted color back to rgb colorspace and clamp HSVtoRGB( &r,&g,&b, h,s,v); int rp = (int) QMIN( QMAX((r*255), 0), 255 ); int gp = (int) QMIN( QMAX((g*255), 0), 255 ); int bp = (int) QMIN( QMAX((b*255), 0), 255 ); //set adjusted color value *rgb = qRgb(rp,gp,bp); //update status bar if significant progress has been made since last update if(useBusyIndicators) { newProgress++; if(newProgress >= updateIncrement) { newProgress = 0; status->incrementProgress(); qApp->processEvents(); } } } } //return pointer to edited image return editedImage; } //==============================================