AlbumShaper 1.0a3
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)
 
#define MAX(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,
y )
Value:
((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,
y )
Value:
((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.

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
413}
long b
#define MAX_TILES
Definition mosaic.cpp:259
TileSet colorTiles
Definition mosaic.cpp:285
int numInitialized
Definition mosaic.cpp:280
Tile tiles[MAX_TILES]
Definition mosaic.cpp:277
int avgL
Definition mosaic.cpp:271
QColor avgColor
Definition mosaic.cpp:268
int avgS
Definition mosaic.cpp:271
QImage image
Definition mosaic.cpp:265

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

Referenced by mosaicEffect().

◆ constructImageTiles()

void constructImageTiles ( QStringList files,
QSize tileSize )

Definition at line 416 of file mosaic.cpp.

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}
bool scaleImage(QString fileIn, QString fileOut, int newWidth, int newHeight)
Scale image and save copy to disk.
bool getImageSize(const char *filename, QSize &size)
Get image dimensions.
TileSet imageTiles
Definition mosaic.cpp:286
#define MIN(x, y)
Definition mosaic.cpp:19

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

Referenced by mosaicEffect().

◆ mosaicEffect()

QImage * mosaicEffect ( QString filename,
MosaicOptions * options )

Definition at line 293 of file mosaic.cpp.

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++;
363 {
364 newProgress = 0;
366 qApp->processEvents();
367 }
368 }
369
370 }
371 }
372
373 //return pointer to edited image
374 return editedImage;
375}
StatusWidget * getStatus()
QStringList getFileList()
Definition mosaic.cpp:254
QSize getTileSize()
Definition mosaic.cpp:255
void showProgressBar(QString message, int numSteps)
Initializes the progress bar.
void incrementProgress()
Updates the progress bar by one step.
void splatBestTile(QImage *image, QPoint topLeftCorner, TileSet *tileSet)
Definition mosaic.cpp:601
void constructImageTiles(QStringList files, QSize tileSize)
Definition mosaic.cpp:416
void constructColorTiles(QSize tileSize)
Definition mosaic.cpp:378
int updateIncrement
QImage * editedImage
StatusWidget * status
int newProgress

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

◆ splatBestTile()

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

Definition at line 601 of file mosaic.cpp.

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}
#define MAX(x, y)
Definition mosaic.cpp:20

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

Referenced by mosaicEffect().

Variable Documentation

◆ colorTiles

TileSet colorTiles

Definition at line 285 of file mosaic.cpp.

Referenced by constructColorTiles(), and mosaicEffect().

◆ imageTiles

TileSet imageTiles

Definition at line 286 of file mosaic.cpp.

Referenced by constructImageTiles(), and mosaicEffect().