#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 |
|
Definition at line 256 of file mosaic.cpp. Referenced by constructColorTiles(). |
|
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 }
|
|
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 }
|
|
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 }
|
|
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 }
|
|
Definition at line 282 of file mosaic.cpp. Referenced by constructColorTiles(). |
|
Definition at line 283 of file mosaic.cpp. Referenced by constructImageTiles(). |