mosaic.cpp File Reference

#include <qimage.h>
#include <qstring.h>
#include <qapplication.h>
#include <cstdlib>
#include <time.h>
#include <math.h>
#include "mosaic.h"
#include "manipulationOptions.h"
#include "../tools/imageTools.h"
#include "../../gui/statusWidget.h"
#include <iostream>

Include dependency graph for mosaic.cpp:

Go to the source code of this file.

Namespaces

namespace  std

Classes

struct  Tile
struct  TileSet

Defines

#define MAX_TILES   216

Functions

void constructColorTiles (QSize tileSize)
void constructImageTiles (QStringList files, QSize tileSize)
void splatBestTile (QImage *image, QPoint topLeftCorner, TileSet *tileSet)
QImage * mosaicEffect (QString filename, MosaicOptions *options)

Variables

TileSet colorTiles
TileSet imageTiles


Define Documentation

#define MAX_TILES   216

Definition at line 256 of file mosaic.cpp.

Referenced by constructColorTiles(), and constructImageTiles().


Function Documentation

void constructColorTiles ( QSize  tileSize  ) 

Definition at line 375 of file mosaic.cpp.

References Tile::avgColor, Tile::avgL, Tile::avgS, b, Tile::image, MAX_TILES, TileSet::numInitialized, and TileSet::tiles.

Referenced by mosaicEffect().

00376 {
00377   //max tiles must be allocated across all colors, so find resolution we'll have for each color
00378   //channel (e.g. if max tiles is 100, 100^(1/3) ~= 4.6 so we'll use 4 unique red, green, and
00379   //blue color values for constructing tiles and use 4^3=64 tiles out of the 100 allocated
00380   int colorRes = (int)pow( MAX_TILES, 1.0/3 );
00381   
00382   //always include 0 and 255 so increment is always totalSpan/(count-1)
00383   int colorIncrement = 255 / (colorRes-1);
00384   
00385   colorIncrement = 51;
00386   
00387   //create actual tiles
00388   int tile=0;
00389   int r,g,b;
00390   for(r=0; r<=255; r+=colorIncrement)
00391   {
00392     for(g=0; g<=255; g+=colorIncrement)
00393     {
00394       for(b=0; b<=255; b+=colorIncrement)
00395       {
00396         colorTiles.tiles[tile].image.create( tileSize.width(), tileSize.height(), 32);
00397         colorTiles.tiles[tile].image.fill( qRgb(r, g, b) );
00398         
00399         colorTiles.tiles[tile].avgColor = QColor(r,g,b);
00400         
00401         int h;
00402         QColor(r,g,b).getHsv( &h, &(colorTiles.tiles[tile].avgS), &(colorTiles.tiles[tile].avgL) );
00403         tile++;
00404       }
00405     }
00406   }
00407   
00408   //setup number of initialized tiles
00409   colorTiles.numInitialized = tile;
00410 }

void constructImageTiles ( QStringList  files,
QSize  tileSize 
)

Definition at line 413 of file mosaic.cpp.

References Tile::avgColor, Tile::avgL, Tile::avgS, getImageSize(), Tile::image, MAX_TILES, TileSet::numInitialized, scaleImage(), and TileSet::tiles.

Referenced by mosaicEffect().

00414 {
00415   //---------------------------------  
00416   //setup number of initialized tiles
00417   imageTiles.numInitialized = QMIN(files.size(), MAX_TILES);
00418   //---------------------------------  
00419   //create file index list, we'll use this to construct a
00420   //list of indices to the randomply picked files from the master list
00421   int* fileIndices = new int[imageTiles.numInitialized];
00422   int* fileIndicesUsed = new int[files.size()];
00423   int i;
00424   for(i=0; i<imageTiles.numInitialized; i++) { fileIndices[i] = -1;    }
00425   for(i=0; i<((int)files.size()); i++)              { fileIndicesUsed[i] = 0; }
00426   //---------------------------------  
00427   //pick the random files, updating the file indices list
00428   for(i=0; i<imageTiles.numInitialized; i++)
00429   {
00430     double percentage = ((double)rand()) / RAND_MAX;
00431     int fileNum = (int) (  (files.size() - (i+1)) * percentage);
00432     
00433     //correct index by offsetting by all files that have been picked before this one 
00434     int j = 0;
00435     int realFileNum = fileNum;
00436     while( fileNum >= 0)
00437     {
00438       if( fileIndicesUsed[j] == 1 )  { realFileNum++; }
00439       else                           { fileNum--;     }
00440        
00441       j++;      
00442     }
00443     
00444     //record file index into list
00445     fileIndices[i] = realFileNum;
00446     fileIndicesUsed[realFileNum] = 1;
00447   }
00448   
00449   //---------------------------------  
00450   //sort the file index list - bubble sort is fast enough right? :-)
00451   int j;
00452   for( i=imageTiles.numInitialized-1; i>0; i--)
00453   {
00454     for( j=0; j<i; j++)
00455     {
00456       if( fileIndices[j] > fileIndices[j+1] )
00457       {
00458         int tmp = fileIndices[j+1];
00459         fileIndices[j+1] = fileIndices[j];
00460         fileIndices[j] = tmp;
00461       }
00462     }
00463   }
00464   //---------------------------------  
00465   //construct truncated list of files that we'll use
00466   QStringList chosenFiles;
00467   QStringList::iterator it;
00468   int curFileIndex = 0;
00469   int nextDesiredFileIndex = 0;
00470   for(it = files.begin(); it != files.end(); it++ )
00471   {
00472     if( curFileIndex == fileIndices[nextDesiredFileIndex] )
00473     {
00474       chosenFiles.append( *it );
00475       nextDesiredFileIndex++;
00476       
00477       if( nextDesiredFileIndex >= imageTiles.numInitialized ) break;
00478     }
00479 
00480     curFileIndex++;  
00481   }
00482 
00483   //resetting numInitialized should not be necessary, we should have the right
00484   //number of files in chosenFiles, but as a sanity check, we'll reset it here again.
00485   imageTiles.numInitialized = QMIN((int)chosenFiles.size(), imageTiles.numInitialized);
00486 
00487   //---------------------------------  
00488   //free up the temporary index list, it's nolonger needed since we now have an
00489   //actual list of the chosen files
00490   delete fileIndices;
00491   delete fileIndicesUsed;
00492   fileIndices = NULL;
00493   fileIndicesUsed = NULL;  
00494   //---------------------------------  
00495   //ok, we now have a list of files we actually want to use to create tiles from, that have
00496   //been randomly chosen from the huge list we were given. now actually create the tiles
00497   int tile = 0;
00498 
00499   for(it = chosenFiles.begin(); it != chosenFiles.end(); it++ )
00500   {
00501     //scale image to definately fill a tileSizeW x tileSizeH region, we'll crop down afterwards
00502     QSize imageRes;
00503     getImageSize( *it, imageRes );
00504   
00505     int intermediateWidth = -1;
00506     int intermediateHeight = -1;
00507     if( ((double)imageRes.width()) / tileSize.width() > ((double)imageRes.height()) / tileSize.height() )
00508     {
00509       intermediateHeight = tileSize.height();
00510       intermediateWidth = (int) ( ((1.0*intermediateHeight*imageRes.width()) / imageRes.height()) + 0.5 );
00511     }
00512     else
00513     {
00514       intermediateWidth = tileSize.width();
00515       intermediateHeight = (int) ( ((1.0*intermediateWidth*imageRes.height()) / imageRes.width()) + 0.5 );
00516     }
00517     
00518     QImage scaledImage;
00519     scaleImage( *it, scaledImage, intermediateWidth, intermediateHeight );
00520     
00521     //scaleImage does not like to scale more than 2x, so if image is not the right size scale it up again
00522     if( scaledImage.width() != tileSize.width() || scaledImage.height() != tileSize.height() )
00523       scaledImage = scaledImage.scale( tileSize, QImage::ScaleFree );
00524     
00525     //construct tile image
00526     imageTiles.tiles[tile].image.create( tileSize.width(), tileSize.height(), 32);
00527     imageTiles.tiles[tile].image.fill( qRgb(255,255,255) );
00528             
00529     //crop scaledimage to tileSizeW x tileSizeH - simultaniously compute statistics about tile
00530     int xOffset = (scaledImage.width()  - tileSize.width())/2;
00531     int yOffset = (scaledImage.height() - tileSize.height())/2;
00532     int x, y;
00533     uchar* scaledScanLine;
00534     uchar* croppedScanLine;
00535     QRgb* scaledRgb;
00536     QRgb* croppedRgb;
00537      
00538     double avgR=0; double avgG=0; double avgB=0;
00539     double avgS=0; double avgL=0;
00540 
00541     //sometimes corrupt images can get through, so this check
00542     //bulletproofs the code
00543     if( scaledImage.isNull() )
00544     {
00545       avgR = avgG = avgB = 255;
00546       avgS = avgL = 255;
00547     }
00548     else
00549     {
00550       for( y=0; y<tileSize.height(); y++)
00551       {
00552         scaledScanLine  = scaledImage.scanLine(y + yOffset);
00553         croppedScanLine = imageTiles.tiles[tile].image.scanLine(y);
00554         
00555         for( x=0; x<tileSize.width(); x++)
00556         {
00557           scaledRgb  = ((QRgb*) scaledScanLine) +x + xOffset;
00558           croppedRgb = ((QRgb*) croppedScanLine)  + x;
00559           
00560           //copy pixel color over
00561           *croppedRgb = *scaledRgb;
00562           
00563           //update statistics
00564           QColor color( *croppedRgb );
00565           
00566           avgR += color.red();
00567           avgG += color.green();
00568           avgB += color.blue();
00569           
00570           int h,s,l;
00571           color.getHsv( &h, &s, &l );
00572           avgS += s;
00573           avgL += l;
00574         }
00575       }
00576       
00577       //average red, green, blue, saturation, and luminance sums
00578       int pixelCount = tileSize.width()*tileSize.height();
00579       avgR /= pixelCount;
00580       avgG /= pixelCount;
00581       avgB /= pixelCount;
00582       avgS /= pixelCount;
00583       avgL /= pixelCount;
00584     }    
00585     //store statistics    
00586     imageTiles.tiles[tile].avgColor = QColor( (int)avgR, (int)avgG, (int)avgB );
00587     imageTiles.tiles[tile].avgS = (int)avgS;
00588     imageTiles.tiles[tile].avgL = (int)avgL;
00589                             
00590     //move on to next tile
00591     tile++;
00592   }
00593   //---------------------------------  
00594 }

QImage* mosaicEffect ( QString  filename,
MosaicOptions options 
)

Definition at line 290 of file mosaic.cpp.

References constructColorTiles(), constructImageTiles(), editedImage, MosaicOptions::getFileList(), ManipulationOptions::getStatus(), MosaicOptions::getTileSize(), StatusWidget::incrementProgress(), newProgress, StatusWidget::showProgressBar(), splatBestTile(), ManipulationOptions::status, MosaicOptions::tileSize, and updateIncrement.

Referenced by EditingInterface::applyEffect().

00291 {
00292   //load image
00293   QImage* editedImage = new QImage( filename );
00294   
00295   //convert to 32-bit depth if necessary
00296   if( editedImage->depth() < 32 )
00297   {
00298     QImage* tmp = editedImage;
00299     editedImage = new QImage( tmp->convertDepth( 32, Qt::AutoColor ) );
00300     delete tmp; tmp=NULL;
00301   }
00302   
00303   //determine if busy indicators will be used
00304   bool useBusyIndicators = false;
00305   StatusWidget* status = NULL;
00306   if( options != NULL && options->getStatus() != NULL )
00307   {
00308     useBusyIndicators = true;
00309     status = options->getStatus(); 
00310   }
00311   
00312   //intialize seed using current time
00313   srand( unsigned(time(NULL)) );
00314   
00315   //determine tile size
00316   QSize tileSize;
00317   if(options == NULL) tileSize = QSize(6,6); //6 is big enough to be visible, but not so blocky the image looks bad
00318   else                tileSize =options->getTileSize();
00319   
00320   //construct tile set
00321   TileSet* tileSet = NULL;
00322   if( options != NULL && options->getFileList().size() > 0 )
00323   {
00324     constructImageTiles(options->getFileList(), tileSize);
00325     tileSet = &imageTiles;
00326   }
00327   else
00328   { 
00329     constructColorTiles(tileSize);
00330     tileSet = &colorTiles;
00331   }
00332 
00333   //setup progress bar
00334   if(useBusyIndicators)
00335   {
00336     QString statusMessage = qApp->translate( "mosaicEffect", "Applying Mosaic Effect:" );
00337     status->showProgressBar( statusMessage, 100 );
00338     qApp->processEvents();  
00339   }
00340 
00341   //update progress bar for every 1% of completion
00342   const int updateIncrement = (int) ( (0.01 * editedImage->width() * editedImage->height()) / 
00343                                       (tileSize.width() * tileSize.height()) );
00344   int newProgress = 0; 
00345 
00346   //iterate over each selected scanline 
00347   int x, y;
00348   for(y=0; y<editedImage->height(); y+=tileSize.height())
00349   {
00350     for( x=0; x<editedImage->width(); x+=tileSize.width())
00351     {
00352       //splat the best tile
00353       splatBestTile( editedImage, QPoint(x,y), tileSet );
00354      
00355       //update status bar if significant progress has been made since last update
00356       if(useBusyIndicators)
00357       {
00358         newProgress++;
00359         if(newProgress >= updateIncrement)
00360         {
00361           newProgress = 0;
00362           status->incrementProgress();
00363           qApp->processEvents();  
00364         }
00365       }
00366 
00367     }
00368   }
00369    
00370   //return pointer to edited image
00371   return editedImage;  
00372 }

void splatBestTile ( QImage *  image,
QPoint  topLeftCorner,
TileSet tileSet 
)

Definition at line 598 of file mosaic.cpp.

References Tile::avgColor, Tile::avgL, Tile::avgS, Tile::image, TileSet::numInitialized, and TileSet::tiles.

Referenced by mosaicEffect().

00599 {
00600   int x, y;
00601   QRgb* imageRgb;
00602   QRgb* tileRgb;
00603   uchar* imageScanLine;
00604   uchar* tileScanLine;
00605   //------------------------------  
00606   //dermine boundary we'll be iterating over
00607   int xMin = 0; 
00608   int xMax = QMIN( tileSet->tiles[0].image.width(),   image->width() - topLeftCorner.x() );
00609   int yMin = 0;
00610   int yMax = QMIN( tileSet->tiles[0].image.height(), image->height() - topLeftCorner.y() );
00611   //------------------------------   
00612   //find most common hue, and average color, saturation and luminance for this portion of the image 
00613   double avgR=0; double avgG=0; double avgB=0;
00614   int hueHist[361];
00615   int i;
00616   for(i=0; i<361; i++) { hueHist[i] = 0; }
00617   double avgS=0; double avgL=0;
00618   
00619   for( y=yMin; y<yMax; y++)
00620   {
00621     imageScanLine = image->scanLine(y+topLeftCorner.y());
00622     for( x=xMin; x<xMax; x++)
00623     {
00624       imageRgb = ((QRgb*)imageScanLine+x+topLeftCorner.x());
00625       QColor color( *imageRgb );
00626       
00627       avgR += color.red();
00628       avgG += color.green();
00629       avgB += color.blue();
00630       
00631       int h,s,l;
00632       color.getHsv( &h, &s, &l );
00633       hueHist[ QMIN( QMAX(h,0), 360 ) ]++;
00634       avgS += s;
00635       avgL += l;
00636     }
00637   }
00638   
00639   //average red, green, blue, saturation, and luminance sums
00640   int pixelCount = (yMax-yMin) * (xMax-xMin);
00641   avgR /= pixelCount;
00642   avgG /= pixelCount;
00643   avgB /= pixelCount;
00644   avgS /= pixelCount;
00645   avgL /= pixelCount;
00646   
00647   //walk through hue histogram and find most common hue
00648   int mostCommonHue = 0;
00649   for(i=1; i<361; i++)
00650   {
00651     if( hueHist[i] > hueHist[mostCommonHue] ) { mostCommonHue = i; }
00652   }
00653   
00654   //------------------------------  
00655   //compute distance between this region and all initialized tiles
00656   double* distances = new double[tileSet->numInitialized];
00657   
00658   double dR, dG, dB;
00659   double rBar;
00660   for(i=0; i<tileSet->numInitialized; i++)
00661   {
00662     dR = tileSet->tiles[i].avgColor.red()   - avgR;
00663     dG = tileSet->tiles[i].avgColor.green() - avgG;
00664     dB = tileSet->tiles[i].avgColor.blue()  - avgB;
00665     rBar = 0.5* (tileSet->tiles[i].avgColor.red() + avgR);
00666     
00667     //we could find the distance between this region and the tile by comparing the colors
00668     //directly as 3d points (sqrt(dR*dR + dG*dG + dB*dB)) but this would not
00669     //take into account their reltive perceptual weights. I found
00670     //some work by Thiadmer Riemersma that suggest I use this equation instead...
00671     //http://www.compuphase.com/cmetric.htm
00672     distances[i] = ((2+(rBar/256)) * dR * dR) +
00673       (4 * dG * dG) +
00674       ((2 + ((255.0-rBar)/256)) * dB * dB);
00675   }
00676   //------------------------------  
00677   //pick tile using pseudo-random distance biased approach
00678  
00679   //take reciprocol of all distances and find sum
00680   double sum = 0;
00681   double epsilon = 0.000000001;
00682   for(i=0; i<tileSet->numInitialized; i++)
00683   {
00684     distances[i] = 1.0 / QMAX(distances[i], epsilon);
00685     sum += distances[i];
00686   } 
00687 
00688   //get a random number and find appropriate tile  
00689   double percentage = ((double)rand()) / RAND_MAX;
00690   double number = sum * percentage;
00691   int TILE = 0;  
00692   sum = 0;
00693   for(i =0; i<tileSet->numInitialized; i++)
00694   {
00695      sum += distances[i];
00696      if( sum >= number)
00697      {
00698        TILE = i; break;
00699       }  
00700   }
00701 
00702   delete distances;
00703   distances = NULL;
00704   //------------------------------  
00705   //determine saturation and luminance multipliers
00706   double sInc = avgS - tileSet->tiles[TILE].avgS;
00707   double lInc = avgL - tileSet->tiles[TILE].avgL;
00708   //------------------------------  
00709   
00710   //finally, splat the tile
00711   for( y=yMin; y<yMax; y++ )
00712   {
00713     //iterate over each selected pixel in scanline
00714     imageScanLine = image->scanLine( (y+topLeftCorner.y()) );
00715     tileScanLine = tileSet->tiles[TILE].image.scanLine(y);
00716     for( x=xMin; x<xMax; x++)
00717     {
00718       //get the tile color
00719       tileRgb = ((QRgb*) tileScanLine) + x;;
00720       QColor color( *tileRgb );
00721       
00722       //convert to hsl
00723       int h,s,l;
00724       color.getHsv( &h, &s, &l );
00725       
00726       //replace hue with the most common hue from this region of the target image
00727       h = mostCommonHue;
00728       
00729       //adjust saturation and luminance to more closely match the average values
00730       //found in this region of the target image.
00731       s = (int)QMIN( QMAX( s+sInc, 0), 255 );
00732       l = (int)QMIN( QMAX( l+lInc, 0), 255 );
00733       
00734       //convert back to rgb
00735       color.setHsv( mostCommonHue, s, l );
00736       
00737       //splat the adjusted tile color onto the image
00738       imageRgb = ((QRgb*)imageScanLine) + x + topLeftCorner.x();
00739       
00740       *imageRgb = color.rgb();
00741     }
00742   }
00743 
00744 }


Variable Documentation

TileSet colorTiles

Definition at line 282 of file mosaic.cpp.

TileSet imageTiles

Definition at line 283 of file mosaic.cpp.


Generated on Thu Jan 3 10:52:48 2008 for AlbumShaper by  doxygen 1.5.4