//============================================== // 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 "painting.h" #include "manipulationOptions.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 oil painting version of // the image by replacing each pixel with an average of the // original pixel color and the most common pixel color within a local radius. // // A histogram of color values (which fall in the 0-255 range) // is constructed at each pixel for all pixels without a given // radius. The most commonly occuring red, green, and blue color // values are found and used in combination with the current color // to produce the oil effect. This is done because in oil painting // (and water colors) color bleeds out from a given area across the // canvass. By averaging with the most common color in a given // neighborhood the larger blobs spread and the higher frequency // information (details) fade into the background. // // TODO: // The local area idealy would be circular, but currently is square. // // TODO: // Experiment adaptively adjusting the oil radius using local image contrast measure? // // TODO: // Come up with method for avoiding strange color shifts near object boundaries. //---------------------------------------------- //============================================== struct Triplet { int r,g,b; }; //---------------------------------------------- struct Histogram { //histogram data Triplet values[256]; //index of highest count for each component Triplet highestCountIndex; }; //---------------------------------------------- Histogram histogram; //---------------------------------------------- void resetHistogram() { static int i; for(i=0;i<256;i++) { histogram.values[i].r = 0; histogram.values[i].g = 0; histogram.values[i].b = 0; } histogram.highestCountIndex.r = 0; histogram.highestCountIndex.g = 0; histogram.highestCountIndex.b = 0; } //---------------------------------------------- void findHighestCounts() { static int i; for(i = 1; i<256; i++) { if( histogram.values[i].r > histogram.values[ histogram.highestCountIndex.r ].r ) { histogram.highestCountIndex.r = i; } if( histogram.values[i].g > histogram.values[ histogram.highestCountIndex.g ].g ) { histogram.highestCountIndex.g = i; } if( histogram.values[i].b > histogram.values[ histogram.highestCountIndex.b ].b ) { histogram.highestCountIndex.b = i; } } } //---------------------------------------------- QImage* oilPaintingEffect( 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 ); } //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( "oilPaintingEffect", "Applying Oil Painting 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; //construct 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; } //compute the radius using image resolution double minDimen = (double) QMIN( editedImage->width(), editedImage->height() ); const int RADIUS = (int) QMAX( 2, (sqrt(minDimen)/4) ); //iterate over image int originalImageX, originalImageY; int editedImageX, editedImageY; int clampedX, clampedY; int trailingEdgeY, leadingEdgeY; QRgb* rgb; uchar* scanLine; uchar* trailingScanLine; uchar* leadingScanLine; //iterate over columns for( editedImageX=0; editedImageX < editedImage->width(); editedImageX++) { //------------------ //reset histogram object resetHistogram(); //------------------ //fill histogram with data that would have results from Y=-1 for(originalImageY = 0 - 1 - RADIUS; originalImageY <= 0 - 1 + RADIUS; originalImageY++) { clampedY = QMAX( QMIN( originalImageY, originalImage.height() - 1 ), 0 ); scanLine = originalImage.scanLine( clampedY ); for(originalImageX = editedImageX - RADIUS; originalImageX <= editedImageX + RADIUS; originalImageX++) { clampedX = QMAX( QMIN( originalImageX, originalImage.width() - 1 ), 0 ); //get rgb value rgb = ((QRgb*)scanLine+clampedX); //update counts for this r/g/b value histogram.values[ qRed(*rgb) ].r++; histogram.values[ qGreen(*rgb) ].g++; histogram.values[ qBlue(*rgb) ].b++; } //originalX } //originalY //------------------ //now iterate over rows by simply removing trailing edge data and adding leading edge data for( editedImageY=0; editedImageY < editedImage->height(); editedImageY++) { trailingEdgeY = QMAX( QMIN( editedImageY-1-RADIUS, originalImage.height() - 1 ), 0 ); leadingEdgeY = QMAX( QMIN( editedImageY+RADIUS, originalImage.height() - 1 ), 0 ); trailingScanLine = originalImage.scanLine( trailingEdgeY ); leadingScanLine = originalImage.scanLine( leadingEdgeY ); for(originalImageX = editedImageX - RADIUS; originalImageX <= editedImageX + RADIUS; originalImageX++) { clampedX = QMAX( QMIN( originalImageX, originalImage.width() - 1 ), 0 ); //remove trail edge data rgb = ((QRgb*)trailingScanLine+clampedX); histogram.values[ qRed(*rgb) ].r--; histogram.values[ qGreen(*rgb) ].g--; histogram.values[ qBlue(*rgb) ].b--; //add leading edge data rgb = ((QRgb*)leadingScanLine+clampedX); histogram.values[ qRed(*rgb) ].r++; histogram.values[ qGreen(*rgb) ].g++; histogram.values[ qBlue(*rgb) ].b++; } //originalX //find highest color counts findHighestCounts(); //replace each color channel value with average of //current value and most occuring value within neighborhood scanLine = editedImage->scanLine( editedImageY ); rgb = ((QRgb*)scanLine+editedImageX); *rgb = qRgb( (qRed(*rgb) + histogram.highestCountIndex.r) / 2, (qGreen(*rgb) + histogram.highestCountIndex.g) / 2, (qBlue(*rgb) + histogram.highestCountIndex.b) / 2 ); //update status bar if significant progress has been made since last update if(useBusyIndicators) { newProgress++; if(newProgress >= updateIncrement) { newProgress = 0; status->incrementProgress(); qApp->processEvents(); } } } //editedImageX } //editedImageY //return pointer to edited image return editedImage; } //==============================================