AlbumShaper  1.0a3
Classes | Macros | 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
 

Macros

#define MIN(x, y)   ((x) < (y) ? (x) : (y))
 
#define MAX(x, y)   ((x) < (y) ? (x) : (y))
 
#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
 

Macro Definition Documentation

§ MAX

#define MAX (   x,
 
)    ((x) < (y) ? (x) : (y))

Definition at line 20 of file mosaic.cpp.

Referenced by splatBestTile().

§ MAX_TILES

#define MAX_TILES   216

Definition at line 259 of file mosaic.cpp.

Referenced by constructColorTiles(), and constructImageTiles().

§ MIN

#define MIN (   x,
 
)    ((x) < (y) ? (x) : (y))

Definition at line 19 of file mosaic.cpp.

Referenced by constructImageTiles(), and splatBestTile().

Function Documentation

§ constructColorTiles()

void constructColorTiles ( QSize  tileSize)

Definition at line 378 of file mosaic.cpp.

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

Referenced by mosaicEffect().

379 {
380  //max tiles must be allocated across all colors, so find resolution we'll have for each color
381  //channel (e.g. if max tiles is 100, 100^(1/3) ~= 4.6 so we'll use 4 unique red, green, and
382  //blue color values for constructing tiles and use 4^3=64 tiles out of the 100 allocated
383  int colorRes = (int)pow( MAX_TILES, 1.0/3 );
384 
385  //always include 0 and 255 so increment is always totalSpan/(count-1)
386  int colorIncrement = 255 / (colorRes-1);
387 
388  colorIncrement = 51;
389 
390  //create actual tiles
391  int tile=0;
392  int r,g,b;
393  for(r=0; r<=255; r+=colorIncrement)
394  {
395  for(g=0; g<=255; g+=colorIncrement)
396  {
397  for(b=0; b<=255; b+=colorIncrement)
398  {
399  colorTiles.tiles[tile].image.create( tileSize.width(), tileSize.height(), 32);
400  colorTiles.tiles[tile].image.fill( qRgb(r, g, b) );
401 
402  colorTiles.tiles[tile].avgColor = QColor(r,g,b);
403 
404  int h;
405  QColor(r,g,b).getHsv( &h, &(colorTiles.tiles[tile].avgS), &(colorTiles.tiles[tile].avgL) );
406  tile++;
407  }
408  }
409  }
410 
411  //setup number of initialized tiles
412  colorTiles.numInitialized = tile;
413 }
#define MAX_TILES
Definition: mosaic.cpp:259
int avgS
Definition: mosaic.cpp:271
QColor avgColor
Definition: mosaic.cpp:268
long b
Definition: jpegInternal.h:125
QImage image
Definition: mosaic.cpp:265
Tile tiles[MAX_TILES]
Definition: mosaic.cpp:277
int avgL
Definition: mosaic.cpp:271
TileSet colorTiles
Definition: mosaic.cpp:285
int numInitialized
Definition: mosaic.cpp:280

§ constructImageTiles()

void constructImageTiles ( QStringList  files,
QSize  tileSize 
)

Definition at line 416 of file mosaic.cpp.

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

Referenced by mosaicEffect().

417 {
418  //---------------------------------
419  //setup number of initialized tiles
420  imageTiles.numInitialized = MIN(files.size(), MAX_TILES);
421  //---------------------------------
422  //create file index list, we'll use this to construct a
423  //list of indices to the randomply picked files from the master list
424  int* fileIndices = new int[imageTiles.numInitialized];
425  int* fileIndicesUsed = new int[files.size()];
426  int i;
427  for(i=0; i<imageTiles.numInitialized; i++) { fileIndices[i] = -1; }
428  for(i=0; i<((int)files.size()); i++) { fileIndicesUsed[i] = 0; }
429  //---------------------------------
430  //pick the random files, updating the file indices list
431  for(i=0; i<imageTiles.numInitialized; i++)
432  {
433  double percentage = ((double)rand()) / RAND_MAX;
434  int fileNum = (int) ( (files.size() - (i+1)) * percentage);
435 
436  //correct index by offsetting by all files that have been picked before this one
437  int j = 0;
438  int realFileNum = fileNum;
439  while( fileNum >= 0)
440  {
441  if( fileIndicesUsed[j] == 1 ) { realFileNum++; }
442  else { fileNum--; }
443 
444  j++;
445  }
446 
447  //record file index into list
448  fileIndices[i] = realFileNum;
449  fileIndicesUsed[realFileNum] = 1;
450  }
451 
452  //---------------------------------
453  //sort the file index list - bubble sort is fast enough right? :-)
454  int j;
455  for( i=imageTiles.numInitialized-1; i>0; i--)
456  {
457  for( j=0; j<i; j++)
458  {
459  if( fileIndices[j] > fileIndices[j+1] )
460  {
461  int tmp = fileIndices[j+1];
462  fileIndices[j+1] = fileIndices[j];
463  fileIndices[j] = tmp;
464  }
465  }
466  }
467  //---------------------------------
468  //construct truncated list of files that we'll use
469  QStringList chosenFiles;
470  QStringList::iterator it;
471  int curFileIndex = 0;
472  int nextDesiredFileIndex = 0;
473  for(it = files.begin(); it != files.end(); it++ )
474  {
475  if( curFileIndex == fileIndices[nextDesiredFileIndex] )
476  {
477  chosenFiles.append( *it );
478  nextDesiredFileIndex++;
479 
480  if( nextDesiredFileIndex >= imageTiles.numInitialized ) break;
481  }
482 
483  curFileIndex++;
484  }
485 
486  //resetting numInitialized should not be necessary, we should have the right
487  //number of files in chosenFiles, but as a sanity check, we'll reset it here again.
488  imageTiles.numInitialized = MIN((int)chosenFiles.size(), imageTiles.numInitialized);
489 
490  //---------------------------------
491  //free up the temporary index list, it's nolonger needed since we now have an
492  //actual list of the chosen files
493  delete fileIndices;
494  delete fileIndicesUsed;
495  fileIndices = NULL;
496  fileIndicesUsed = NULL;
497  //---------------------------------
498  //ok, we now have a list of files we actually want to use to create tiles from, that have
499  //been randomly chosen from the huge list we were given. now actually create the tiles
500  int tile = 0;
501 
502  for(it = chosenFiles.begin(); it != chosenFiles.end(); it++ )
503  {
504  //scale image to definately fill a tileSizeW x tileSizeH region, we'll crop down afterwards
505  QSize imageRes;
506  getImageSize( *it, imageRes );
507 
508  int intermediateWidth = -1;
509  int intermediateHeight = -1;
510  if( ((double)imageRes.width()) / tileSize.width() > ((double)imageRes.height()) / tileSize.height() )
511  {
512  intermediateHeight = tileSize.height();
513  intermediateWidth = (int) ( ((1.0*intermediateHeight*imageRes.width()) / imageRes.height()) + 0.5 );
514  }
515  else
516  {
517  intermediateWidth = tileSize.width();
518  intermediateHeight = (int) ( ((1.0*intermediateWidth*imageRes.height()) / imageRes.width()) + 0.5 );
519  }
520 
521  QImage scaledImage;
522  scaleImage( *it, scaledImage, intermediateWidth, intermediateHeight );
523 
524  //scaleImage does not like to scale more than 2x, so if image is not the right size scale it up again
525  if( scaledImage.width() != tileSize.width() || scaledImage.height() != tileSize.height() )
526  scaledImage = scaledImage.scaled( tileSize, Qt::IgnoreAspectRatio );
527 
528  //construct tile image
529  imageTiles.tiles[tile].image.create( tileSize.width(), tileSize.height(), 32);
530  imageTiles.tiles[tile].image.fill( qRgb(255,255,255) );
531 
532  //crop scaledimage to tileSizeW x tileSizeH - simultaniously compute statistics about tile
533  int xOffset = (scaledImage.width() - tileSize.width())/2;
534  int yOffset = (scaledImage.height() - tileSize.height())/2;
535  int x, y;
536  uchar* scaledScanLine;
537  uchar* croppedScanLine;
538  QRgb* scaledRgb;
539  QRgb* croppedRgb;
540 
541  double avgR=0; double avgG=0; double avgB=0;
542  double avgS=0; double avgL=0;
543 
544  //sometimes corrupt images can get through, so this check
545  //bulletproofs the code
546  if( scaledImage.isNull() )
547  {
548  avgR = avgG = avgB = 255;
549  avgS = avgL = 255;
550  }
551  else
552  {
553  for( y=0; y<tileSize.height(); y++)
554  {
555  scaledScanLine = scaledImage.scanLine(y + yOffset);
556  croppedScanLine = imageTiles.tiles[tile].image.scanLine(y);
557 
558  for( x=0; x<tileSize.width(); x++)
559  {
560  scaledRgb = ((QRgb*) scaledScanLine) +x + xOffset;
561  croppedRgb = ((QRgb*) croppedScanLine) + x;
562 
563  //copy pixel color over
564  *croppedRgb = *scaledRgb;
565 
566  //update statistics
567  QColor color( *croppedRgb );
568 
569  avgR += color.red();
570  avgG += color.green();
571  avgB += color.blue();
572 
573  int h,s,l;
574  color.getHsv( &h, &s, &l );
575  avgS += s;
576  avgL += l;
577  }
578  }
579 
580  //average red, green, blue, saturation, and luminance sums
581  int pixelCount = tileSize.width()*tileSize.height();
582  avgR /= pixelCount;
583  avgG /= pixelCount;
584  avgB /= pixelCount;
585  avgS /= pixelCount;
586  avgL /= pixelCount;
587  }
588  //store statistics
589  imageTiles.tiles[tile].avgColor = QColor( (int)avgR, (int)avgG, (int)avgB );
590  imageTiles.tiles[tile].avgS = (int)avgS;
591  imageTiles.tiles[tile].avgL = (int)avgL;
592 
593  //move on to next tile
594  tile++;
595  }
596  //---------------------------------
597 }
#define MAX_TILES
Definition: mosaic.cpp:259
int avgS
Definition: mosaic.cpp:271
QColor avgColor
Definition: mosaic.cpp:268
QImage image
Definition: mosaic.cpp:265
Tile tiles[MAX_TILES]
Definition: mosaic.cpp:277
TileSet imageTiles
Definition: mosaic.cpp:286
int avgL
Definition: mosaic.cpp:271
bool scaleImage(QString fileIn, QString fileOut, int newWidth, int newHeight)
Scale image and save copy to disk.
Definition: imageTools.cpp:157
bool getImageSize(const char *filename, QSize &size)
Get image dimensions.
Definition: imageTools.cpp:192
#define MIN(x, y)
Definition: mosaic.cpp:19
int numInitialized
Definition: mosaic.cpp:280

§ mosaicEffect()

QImage* mosaicEffect ( QString  filename,
MosaicOptions options 
)

Definition at line 293 of file mosaic.cpp.

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

Referenced by EditingInterface::applyEffect().

294 {
295  //load image
296  QImage* editedImage = new QImage( filename );
297 
298  //convert to 32-bit depth if necessary
299  if( editedImage->depth() < 32 )
300  {
301  QImage* tmp = editedImage;
302  editedImage = new QImage( tmp->convertDepth( 32, Qt::AutoColor ) );
303  delete tmp; tmp=NULL;
304  }
305 
306  //determine if busy indicators will be used
307  bool useBusyIndicators = false;
308  StatusWidget* status = NULL;
309  if( options != NULL && options->getStatus() != NULL )
310  {
311  useBusyIndicators = true;
312  status = options->getStatus();
313  }
314 
315  //intialize seed using current time
316  srand( unsigned(time(NULL)) );
317 
318  //determine tile size
319  QSize tileSize;
320  if(options == NULL) tileSize = QSize(6,6); //6 is big enough to be visible, but not so blocky the image looks bad
321  else tileSize =options->getTileSize();
322 
323  //construct tile set
324  TileSet* tileSet = NULL;
325  if( options != NULL && options->getFileList().size() > 0 )
326  {
327  constructImageTiles(options->getFileList(), tileSize);
328  tileSet = &imageTiles;
329  }
330  else
331  {
332  constructColorTiles(tileSize);
333  tileSet = &colorTiles;
334  }
335 
336  //setup progress bar
337  if(useBusyIndicators)
338  {
339  QString statusMessage = qApp->translate( "mosaicEffect", "Applying Mosaic Effect:" );
340  status->showProgressBar( statusMessage, 100 );
341  qApp->processEvents();
342  }
343 
344  //update progress bar for every 1% of completion
345  const int updateIncrement = (int) ( (0.01 * editedImage->width() * editedImage->height()) /
346  (tileSize.width() * tileSize.height()) );
347  int newProgress = 0;
348 
349  //iterate over each selected scanline
350  int x, y;
351  for(y=0; y<editedImage->height(); y+=tileSize.height())
352  {
353  for( x=0; x<editedImage->width(); x+=tileSize.width())
354  {
355  //splat the best tile
356  splatBestTile( editedImage, QPoint(x,y), tileSet );
357 
358  //update status bar if significant progress has been made since last update
359  if(useBusyIndicators)
360  {
361  newProgress++;
362  if(newProgress >= updateIncrement)
363  {
364  newProgress = 0;
365  status->incrementProgress();
366  qApp->processEvents();
367  }
368  }
369 
370  }
371  }
372 
373  //return pointer to edited image
374  return editedImage;
375 }
int updateIncrement
QStringList getFileList()
Definition: mosaic.cpp:254
StatusWidget * getStatus()
void incrementProgress()
Updates the progress bar by one step.
void showProgressBar(QString message, int numSteps)
Initializes the progress bar.
void splatBestTile(QImage *image, QPoint topLeftCorner, TileSet *tileSet)
Definition: mosaic.cpp:601
TileSet imageTiles
Definition: mosaic.cpp:286
StatusWidget * status
void constructImageTiles(QStringList files, QSize tileSize)
Definition: mosaic.cpp:416
QSize getTileSize()
Definition: mosaic.cpp:255
TileSet colorTiles
Definition: mosaic.cpp:285
QImage * editedImage
void constructColorTiles(QSize tileSize)
Definition: mosaic.cpp:378
int newProgress

§ splatBestTile()

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

Definition at line 601 of file mosaic.cpp.

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

Referenced by mosaicEffect().

602 {
603  int x, y;
604  QRgb* imageRgb;
605  QRgb* tileRgb;
606  uchar* imageScanLine;
607  uchar* tileScanLine;
608  //------------------------------
609  //dermine boundary we'll be iterating over
610  int xMin = 0;
611  int xMax = MIN( tileSet->tiles[0].image.width(), image->width() - topLeftCorner.x() );
612  int yMin = 0;
613  int yMax = MIN( tileSet->tiles[0].image.height(), image->height() - topLeftCorner.y() );
614  //------------------------------
615  //find most common hue, and average color, saturation and luminance for this portion of the image
616  double avgR=0; double avgG=0; double avgB=0;
617  int hueHist[361];
618  int i;
619  for(i=0; i<361; i++) { hueHist[i] = 0; }
620  double avgS=0; double avgL=0;
621 
622  for( y=yMin; y<yMax; y++)
623  {
624  imageScanLine = image->scanLine(y+topLeftCorner.y());
625  for( x=xMin; x<xMax; x++)
626  {
627  imageRgb = ((QRgb*)imageScanLine+x+topLeftCorner.x());
628  QColor color( *imageRgb );
629 
630  avgR += color.red();
631  avgG += color.green();
632  avgB += color.blue();
633 
634  int h,s,l;
635  color.getHsv( &h, &s, &l );
636  hueHist[ MIN( MAX(h,0), 360 ) ]++;
637  avgS += s;
638  avgL += l;
639  }
640  }
641 
642  //average red, green, blue, saturation, and luminance sums
643  int pixelCount = (yMax-yMin) * (xMax-xMin);
644  avgR /= pixelCount;
645  avgG /= pixelCount;
646  avgB /= pixelCount;
647  avgS /= pixelCount;
648  avgL /= pixelCount;
649 
650  //walk through hue histogram and find most common hue
651  int mostCommonHue = 0;
652  for(i=1; i<361; i++)
653  {
654  if( hueHist[i] > hueHist[mostCommonHue] ) { mostCommonHue = i; }
655  }
656 
657  //------------------------------
658  //compute distance between this region and all initialized tiles
659  double* distances = new double[tileSet->numInitialized];
660 
661  double dR, dG, dB;
662  double rBar;
663  for(i=0; i<tileSet->numInitialized; i++)
664  {
665  dR = tileSet->tiles[i].avgColor.red() - avgR;
666  dG = tileSet->tiles[i].avgColor.green() - avgG;
667  dB = tileSet->tiles[i].avgColor.blue() - avgB;
668  rBar = 0.5* (tileSet->tiles[i].avgColor.red() + avgR);
669 
670  //we could find the distance between this region and the tile by comparing the colors
671  //directly as 3d points (sqrt(dR*dR + dG*dG + dB*dB)) but this would not
672  //take into account their reltive perceptual weights. I found
673  //some work by Thiadmer Riemersma that suggest I use this equation instead...
674  //http://www.compuphase.com/cmetric.htm
675  distances[i] = ((2+(rBar/256)) * dR * dR) +
676  (4 * dG * dG) +
677  ((2 + ((255.0-rBar)/256)) * dB * dB);
678  }
679  //------------------------------
680  //pick tile using pseudo-random distance biased approach
681 
682  //take reciprocol of all distances and find sum
683  double sum = 0;
684  double epsilon = 0.000000001;
685  for(i=0; i<tileSet->numInitialized; i++)
686  {
687  distances[i] = 1.0 / MAX(distances[i], epsilon);
688  sum += distances[i];
689  }
690 
691  //get a random number and find appropriate tile
692  double percentage = ((double)rand()) / RAND_MAX;
693  double number = sum * percentage;
694  int TILE = 0;
695  sum = 0;
696  for(i =0; i<tileSet->numInitialized; i++)
697  {
698  sum += distances[i];
699  if( sum >= number)
700  {
701  TILE = i; break;
702  }
703  }
704 
705  delete distances;
706  distances = NULL;
707  //------------------------------
708  //determine saturation and luminance multipliers
709  double sInc = avgS - tileSet->tiles[TILE].avgS;
710  double lInc = avgL - tileSet->tiles[TILE].avgL;
711  //------------------------------
712 
713  //finally, splat the tile
714  for( y=yMin; y<yMax; y++ )
715  {
716  //iterate over each selected pixel in scanline
717  imageScanLine = image->scanLine( (y+topLeftCorner.y()) );
718  tileScanLine = tileSet->tiles[TILE].image.scanLine(y);
719  for( x=xMin; x<xMax; x++)
720  {
721  //get the tile color
722  tileRgb = ((QRgb*) tileScanLine) + x;;
723  QColor color( *tileRgb );
724 
725  //convert to hsl
726  int h,s,l;
727  color.getHsv( &h, &s, &l );
728 
729  //replace hue with the most common hue from this region of the target image
730  h = mostCommonHue;
731 
732  //adjust saturation and luminance to more closely match the average values
733  //found in this region of the target image.
734  s = (int)MIN( MAX( s+sInc, 0), 255 );
735  l = (int)MIN( MAX( l+lInc, 0), 255 );
736 
737  //convert back to rgb
738  color.setHsv( mostCommonHue, s, l );
739 
740  //splat the adjusted tile color onto the image
741  imageRgb = ((QRgb*)imageScanLine) + x + topLeftCorner.x();
742 
743  *imageRgb = color.rgb();
744  }
745  }
746 
747 }
int avgS
Definition: mosaic.cpp:271
QColor avgColor
Definition: mosaic.cpp:268
QImage image
Definition: mosaic.cpp:265
Tile tiles[MAX_TILES]
Definition: mosaic.cpp:277
int avgL
Definition: mosaic.cpp:271
#define MAX(x, y)
Definition: mosaic.cpp:20
#define MIN(x, y)
Definition: mosaic.cpp:19
int numInitialized
Definition: mosaic.cpp:280

Variable Documentation

§ colorTiles

TileSet colorTiles

Definition at line 285 of file mosaic.cpp.

Referenced by mosaicEffect().

§ imageTiles

TileSet imageTiles

Definition at line 286 of file mosaic.cpp.

Referenced by mosaicEffect().