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