AlbumShaper
1.0a3
|
#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>
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 MAX_TILES 216 |
Definition at line 256 of file mosaic.cpp.
Referenced by constructColorTiles(), and constructImageTiles().
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(); } } }
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().