AlbumShaper  1.0a3
Classes | Defines | Functions | Variables
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.

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, colorTiles, Tile::image, MAX_TILES, TileSet::numInitialized, and TileSet::tiles.

Referenced by mosaicEffect().

{
  //max tiles must be allocated across all colors, so find resolution we'll have for each color
  //channel (e.g. if max tiles is 100, 100^(1/3) ~= 4.6 so we'll use 4 unique red, green, and
  //blue color values for constructing tiles and use 4^3=64 tiles out of the 100 allocated
  int colorRes = (int)pow( MAX_TILES, 1.0/3 );
  
  //always include 0 and 255 so increment is always totalSpan/(count-1)
  int colorIncrement = 255 / (colorRes-1);
  
  colorIncrement = 51;
  
  //create actual tiles
  int tile=0;
  int r,g,b;
  for(r=0; r<=255; r+=colorIncrement)
  {
    for(g=0; g<=255; g+=colorIncrement)
    {
      for(b=0; b<=255; b+=colorIncrement)
      {
        colorTiles.tiles[tile].image.create( tileSize.width(), tileSize.height(), 32);
        colorTiles.tiles[tile].image.fill( qRgb(r, g, b) );
        
        colorTiles.tiles[tile].avgColor = QColor(r,g,b);
        
        int h;
        QColor(r,g,b).getHsv( &h, &(colorTiles.tiles[tile].avgS), &(colorTiles.tiles[tile].avgL) );
        tile++;
      }
    }
  }
  
  //setup number of initialized tiles
  colorTiles.numInitialized = tile;
}
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, MAX_TILES, TileSet::numInitialized, scaleImage(), and TileSet::tiles.

Referenced by mosaicEffect().

{
  //---------------------------------  
  //setup number of initialized tiles
  imageTiles.numInitialized = QMIN(files.size(), MAX_TILES);
  //---------------------------------  
  //create file index list, we'll use this to construct a
  //list of indices to the randomply picked files from the master list
  int* fileIndices = new int[imageTiles.numInitialized];
  int* fileIndicesUsed = new int[files.size()];
  int i;
  for(i=0; i<imageTiles.numInitialized; i++) { fileIndices[i] = -1;    }
  for(i=0; i<((int)files.size()); i++)              { fileIndicesUsed[i] = 0; }
  //---------------------------------  
  //pick the random files, updating the file indices list
  for(i=0; i<imageTiles.numInitialized; i++)
  {
    double percentage = ((double)rand()) / RAND_MAX;
    int fileNum = (int) (  (files.size() - (i+1)) * percentage);
    
    //correct index by offsetting by all files that have been picked before this one 
    int j = 0;
    int realFileNum = fileNum;
    while( fileNum >= 0)
    {
      if( fileIndicesUsed[j] == 1 )  { realFileNum++; }
      else                           { fileNum--;     }
       
      j++;      
    }
    
    //record file index into list
    fileIndices[i] = realFileNum;
    fileIndicesUsed[realFileNum] = 1;
  }
  
  //---------------------------------  
  //sort the file index list - bubble sort is fast enough right? :-)
  int j;
  for( i=imageTiles.numInitialized-1; i>0; i--)
  {
    for( j=0; j<i; j++)
    {
      if( fileIndices[j] > fileIndices[j+1] )
      {
        int tmp = fileIndices[j+1];
        fileIndices[j+1] = fileIndices[j];
        fileIndices[j] = tmp;
      }
    }
  }
  //---------------------------------  
  //construct truncated list of files that we'll use
  QStringList chosenFiles;
  QStringList::iterator it;
  int curFileIndex = 0;
  int nextDesiredFileIndex = 0;
  for(it = files.begin(); it != files.end(); it++ )
  {
    if( curFileIndex == fileIndices[nextDesiredFileIndex] )
    {
      chosenFiles.append( *it );
      nextDesiredFileIndex++;
      
      if( nextDesiredFileIndex >= imageTiles.numInitialized ) break;
    }

    curFileIndex++;  
  }

  //resetting numInitialized should not be necessary, we should have the right
  //number of files in chosenFiles, but as a sanity check, we'll reset it here again.
  imageTiles.numInitialized = QMIN((int)chosenFiles.size(), imageTiles.numInitialized);

  //---------------------------------  
  //free up the temporary index list, it's nolonger needed since we now have an
  //actual list of the chosen files
  delete fileIndices;
  delete fileIndicesUsed;
  fileIndices = NULL;
  fileIndicesUsed = NULL;  
  //---------------------------------  
  //ok, we now have a list of files we actually want to use to create tiles from, that have
  //been randomly chosen from the huge list we were given. now actually create the tiles
  int tile = 0;

  for(it = chosenFiles.begin(); it != chosenFiles.end(); it++ )
  {
    //scale image to definately fill a tileSizeW x tileSizeH region, we'll crop down afterwards
    QSize imageRes;
    getImageSize( *it, imageRes );
  
    int intermediateWidth = -1;
    int intermediateHeight = -1;
    if( ((double)imageRes.width()) / tileSize.width() > ((double)imageRes.height()) / tileSize.height() )
    {
      intermediateHeight = tileSize.height();
      intermediateWidth = (int) ( ((1.0*intermediateHeight*imageRes.width()) / imageRes.height()) + 0.5 );
    }
    else
    {
      intermediateWidth = tileSize.width();
      intermediateHeight = (int) ( ((1.0*intermediateWidth*imageRes.height()) / imageRes.width()) + 0.5 );
    }
    
    QImage scaledImage;
    scaleImage( *it, scaledImage, intermediateWidth, intermediateHeight );
    
    //scaleImage does not like to scale more than 2x, so if image is not the right size scale it up again
    if( scaledImage.width() != tileSize.width() || scaledImage.height() != tileSize.height() )
      scaledImage = scaledImage.scale( tileSize, QImage::ScaleFree );
    
    //construct tile image
    imageTiles.tiles[tile].image.create( tileSize.width(), tileSize.height(), 32);
    imageTiles.tiles[tile].image.fill( qRgb(255,255,255) );
            
    //crop scaledimage to tileSizeW x tileSizeH - simultaniously compute statistics about tile
    int xOffset = (scaledImage.width()  - tileSize.width())/2;
    int yOffset = (scaledImage.height() - tileSize.height())/2;
    int x, y;
    uchar* scaledScanLine;
    uchar* croppedScanLine;
    QRgb* scaledRgb;
    QRgb* croppedRgb;
     
    double avgR=0; double avgG=0; double avgB=0;
    double avgS=0; double avgL=0;

    //sometimes corrupt images can get through, so this check
    //bulletproofs the code
    if( scaledImage.isNull() )
    {
      avgR = avgG = avgB = 255;
      avgS = avgL = 255;
    }
    else
    {
      for( y=0; y<tileSize.height(); y++)
      {
        scaledScanLine  = scaledImage.scanLine(y + yOffset);
        croppedScanLine = imageTiles.tiles[tile].image.scanLine(y);
        
        for( x=0; x<tileSize.width(); x++)
        {
          scaledRgb  = ((QRgb*) scaledScanLine) +x + xOffset;
          croppedRgb = ((QRgb*) croppedScanLine)  + x;
          
          //copy pixel color over
          *croppedRgb = *scaledRgb;
          
          //update statistics
          QColor color( *croppedRgb );
          
          avgR += color.red();
          avgG += color.green();
          avgB += color.blue();
          
          int h,s,l;
          color.getHsv( &h, &s, &l );
          avgS += s;
          avgL += l;
        }
      }
      
      //average red, green, blue, saturation, and luminance sums
      int pixelCount = tileSize.width()*tileSize.height();
      avgR /= pixelCount;
      avgG /= pixelCount;
      avgB /= pixelCount;
      avgS /= pixelCount;
      avgL /= pixelCount;
    }    
    //store statistics    
    imageTiles.tiles[tile].avgColor = QColor( (int)avgR, (int)avgG, (int)avgB );
    imageTiles.tiles[tile].avgS = (int)avgS;
    imageTiles.tiles[tile].avgL = (int)avgL;
                            
    //move on to next tile
    tile++;
  }
  //---------------------------------  
}
QImage* mosaicEffect ( QString  filename,
MosaicOptions options 
)

Definition at line 290 of file mosaic.cpp.

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

Referenced by EditingInterface::applyEffect().

{
  //load 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(); 
  }
  
  //intialize seed using current time
  srand( unsigned(time(NULL)) );
  
  //determine tile size
  QSize tileSize;
  if(options == NULL) tileSize = QSize(6,6); //6 is big enough to be visible, but not so blocky the image looks bad
  else                tileSize =options->getTileSize();
  
  //construct tile set
  TileSet* tileSet = NULL;
  if( options != NULL && options->getFileList().size() > 0 )
  {
    constructImageTiles(options->getFileList(), tileSize);
    tileSet = &imageTiles;
  }
  else
  { 
    constructColorTiles(tileSize);
    tileSet = &colorTiles;
  }

  //setup progress bar
  if(useBusyIndicators)
  {
    QString statusMessage = qApp->translate( "mosaicEffect", "Applying Mosaic Effect:" );
    status->showProgressBar( statusMessage, 100 );
    qApp->processEvents();  
  }

  //update progress bar for every 1% of completion
  const int updateIncrement = (int) ( (0.01 * editedImage->width() * editedImage->height()) / 
                                      (tileSize.width() * tileSize.height()) );
  int newProgress = 0; 

  //iterate over each selected scanline 
  int x, y;
  for(y=0; y<editedImage->height(); y+=tileSize.height())
  {
    for( x=0; x<editedImage->width(); x+=tileSize.width())
    {
      //splat the best tile
      splatBestTile( editedImage, QPoint(x,y), tileSet );
     
      //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;  
}
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().

{
  int x, y;
  QRgb* imageRgb;
  QRgb* tileRgb;
  uchar* imageScanLine;
  uchar* tileScanLine;
  //------------------------------  
  //dermine boundary we'll be iterating over
  int xMin = 0; 
  int xMax = QMIN( tileSet->tiles[0].image.width(),   image->width() - topLeftCorner.x() );
  int yMin = 0;
  int yMax = QMIN( tileSet->tiles[0].image.height(), image->height() - topLeftCorner.y() );
  //------------------------------   
  //find most common hue, and average color, saturation and luminance for this portion of the image 
  double avgR=0; double avgG=0; double avgB=0;
  int hueHist[361];
  int i;
  for(i=0; i<361; i++) { hueHist[i] = 0; }
  double avgS=0; double avgL=0;
  
  for( y=yMin; y<yMax; y++)
  {
    imageScanLine = image->scanLine(y+topLeftCorner.y());
    for( x=xMin; x<xMax; x++)
    {
      imageRgb = ((QRgb*)imageScanLine+x+topLeftCorner.x());
      QColor color( *imageRgb );
      
      avgR += color.red();
      avgG += color.green();
      avgB += color.blue();
      
      int h,s,l;
      color.getHsv( &h, &s, &l );
      hueHist[ QMIN( QMAX(h,0), 360 ) ]++;
      avgS += s;
      avgL += l;
    }
  }
  
  //average red, green, blue, saturation, and luminance sums
  int pixelCount = (yMax-yMin) * (xMax-xMin);
  avgR /= pixelCount;
  avgG /= pixelCount;
  avgB /= pixelCount;
  avgS /= pixelCount;
  avgL /= pixelCount;
  
  //walk through hue histogram and find most common hue
  int mostCommonHue = 0;
  for(i=1; i<361; i++)
  {
    if( hueHist[i] > hueHist[mostCommonHue] ) { mostCommonHue = i; }
  }
  
  //------------------------------  
  //compute distance between this region and all initialized tiles
  double* distances = new double[tileSet->numInitialized];
  
  double dR, dG, dB;
  double rBar;
  for(i=0; i<tileSet->numInitialized; i++)
  {
    dR = tileSet->tiles[i].avgColor.red()   - avgR;
    dG = tileSet->tiles[i].avgColor.green() - avgG;
    dB = tileSet->tiles[i].avgColor.blue()  - avgB;
    rBar = 0.5* (tileSet->tiles[i].avgColor.red() + avgR);
    
    //we could find the distance between this region and the tile by comparing the colors
    //directly as 3d points (sqrt(dR*dR + dG*dG + dB*dB)) but this would not
    //take into account their reltive perceptual weights. I found
    //some work by Thiadmer Riemersma that suggest I use this equation instead...
    //http://www.compuphase.com/cmetric.htm
    distances[i] = ((2+(rBar/256)) * dR * dR) +
      (4 * dG * dG) +
      ((2 + ((255.0-rBar)/256)) * dB * dB);
  }
  //------------------------------  
  //pick tile using pseudo-random distance biased approach
 
  //take reciprocol of all distances and find sum
  double sum = 0;
  double epsilon = 0.000000001;
  for(i=0; i<tileSet->numInitialized; i++)
  {
    distances[i] = 1.0 / QMAX(distances[i], epsilon);
    sum += distances[i];
  } 

  //get a random number and find appropriate tile  
  double percentage = ((double)rand()) / RAND_MAX;
  double number = sum * percentage;
  int TILE = 0;  
  sum = 0;
  for(i =0; i<tileSet->numInitialized; i++)
  {
     sum += distances[i];
     if( sum >= number)
     {
       TILE = i; break;
      }  
  }

  delete distances;
  distances = NULL;
  //------------------------------  
  //determine saturation and luminance multipliers
  double sInc = avgS - tileSet->tiles[TILE].avgS;
  double lInc = avgL - tileSet->tiles[TILE].avgL;
  //------------------------------  
  
  //finally, splat the tile
  for( y=yMin; y<yMax; y++ )
  {
    //iterate over each selected pixel in scanline
    imageScanLine = image->scanLine( (y+topLeftCorner.y()) );
    tileScanLine = tileSet->tiles[TILE].image.scanLine(y);
    for( x=xMin; x<xMax; x++)
    {
      //get the tile color
      tileRgb = ((QRgb*) tileScanLine) + x;;
      QColor color( *tileRgb );
      
      //convert to hsl
      int h,s,l;
      color.getHsv( &h, &s, &l );
      
      //replace hue with the most common hue from this region of the target image
      h = mostCommonHue;
      
      //adjust saturation and luminance to more closely match the average values
      //found in this region of the target image.
      s = (int)QMIN( QMAX( s+sInc, 0), 255 );
      l = (int)QMIN( QMAX( l+lInc, 0), 255 );
      
      //convert back to rgb
      color.setHsv( mostCommonHue, s, l );
      
      //splat the adjusted tile color onto the image
      imageRgb = ((QRgb*)imageScanLine) + x + topLeftCorner.x();
      
      *imageRgb = color.rgb();
    }
  }

}

Variable Documentation

Definition at line 282 of file mosaic.cpp.

Referenced by constructColorTiles(), and mosaicEffect().

Definition at line 283 of file mosaic.cpp.

Referenced by constructImageTiles(), and mosaicEffect().