Main Page | Namespace List | Class Hierarchy | Class List | Directories | File List | Class Members | File Members

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:

Include dependency graph

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().


Function Documentation

void constructColorTiles QSize  tileSize  ) 
 

Definition at line 375 of file mosaic.cpp.

References Tile::avgColor, Tile::avgL, Tile::avgS, b, colorTiles, 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, imageTiles, 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(), status, 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.

Referenced by constructColorTiles().

TileSet imageTiles
 

Definition at line 283 of file mosaic.cpp.

Referenced by constructImageTiles().


Generated on Mon Apr 11 18:28:16 2005 for AlbumShaper by  doxygen 1.3.9.1