Main Page | Namespace List | Class Hierarchy | Class List | 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:

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, 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, 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 colorTiles, constructColorTiles(), constructImageTiles(), editedImage, MosaicOptions::getFileList(), ManipulationOptions::getStatus(), MosaicOptions::getTileSize(), imageTiles, 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(), and mosaicEffect().

TileSet imageTiles
 

Definition at line 283 of file mosaic.cpp.

Referenced by constructImageTiles(), and mosaicEffect().


Generated on Sun Mar 4 19:43:01 2007 for AlbumShaper by doxygen 1.3.7