in imagecore/image/tiledresize.cpp [81:270]
int TiledResizeOperation::performResize()
{
unsigned int targetWidth = (unsigned int)m_OutputWidth;
unsigned int targetHeight = (unsigned int)m_OutputHeight;
// Only downsampling, haven't tested or thought about upsampling.
if( targetWidth > m_ImageReader->getWidth() || targetHeight > m_ImageReader->getHeight() ) {
targetWidth = m_ImageReader->getWidth();
targetHeight = m_ImageReader->getHeight();
}
// Some readers (JPEG) can get us close to our desired size for free.
unsigned int readWidth;
unsigned int readHeight;
m_ImageReader->computeReadDimensions(targetWidth, targetHeight, readWidth, readHeight);
// If we're still more than a power of two away, reduce by powers of two until we're there.
unsigned int reducedWidth = readWidth;
unsigned int reducedHeight = readHeight;
while( reducedWidth / 2 >= targetWidth && reducedHeight / 2 >= targetHeight ) {
reducedWidth /= 2;
reducedHeight /= 2;
}
// Avoid unnecessary filtering if we're close.
if( abs((int)reducedWidth - (int)targetWidth) < 4 && abs((int)reducedHeight - (int)targetHeight) < 4 ) {
targetWidth = reducedWidth;
targetHeight = reducedHeight;
}
bool skipFiltering = false;
if( targetWidth == reducedWidth && targetHeight == reducedHeight ) {
skipFiltering = true;
}
// Compute tile sizes.
unsigned int outMaxRows = 128;
unsigned int inMaxRows = ((readHeight * outMaxRows) / targetHeight);
while( inMaxRows * readWidth > 1024 * 1024 && outMaxRows >= 16 ) {
// The input is huge, so try a smaller output size.
outMaxRows /= 2;
inMaxRows = ((readHeight * outMaxRows) / targetHeight);
}
unsigned int tileOverlap = 12;
unsigned int imagePadding = 12;
bool skipScale = targetWidth == readWidth && targetHeight == readHeight;
if( skipScale || skipFiltering || readHeight <= inMaxRows ) {
// If we're not scaling/filtering, or the entire image fits into a single tile, we don't need to
// have any redundant overlap between tiles, which is usually there for the edges of the tile
// to filter properly.
tileOverlap = 0;
}
EImageColorModel colorModel = m_ImageReader->getNativeColorModel() == kColorModel_RGBA ? kColorModel_RGBA : kColorModel_RGBX;
bool success = false;
// If the image is just too big (> ~8192), don't even try.
// It might work, but it's untested.
if( outMaxRows >= 16 ) {
FilterKernelAdaptive* filterKernelX = createFilterKernel(m_ResizeQuality, reducedWidth, targetWidth);
FilterKernelAdaptive* filterKernelY = createFilterKernel(m_ResizeQuality, reducedHeight, targetHeight);
if( filterKernelX != NULL && filterKernelY != NULL ) {
if( m_ImageReader->beginRead(readWidth, readHeight, colorModel) ) {
if( m_ImageWriter->beginWrite(targetWidth, targetHeight, colorModel) ) {
unsigned int maxSourceHeight = inMaxRows + tileOverlap * 4;
ImageRGBA* sourceImage = ImageRGBA::create(readWidth, maxSourceHeight, imagePadding, 16);
ImageRGBA* destImage1 = skipScale ? NULL : ImageRGBA::create(readWidth, outMaxRows + tileOverlap * 2, imagePadding, 16);
ImageRGBA* destImage2 = skipScale ? NULL : ImageRGBA::create(readWidth, outMaxRows + tileOverlap * 2, imagePadding, 16);
unsigned int prevTileUnprocessed = 0;
unsigned int prevTileOverlap = 0;
if( sourceImage != NULL && ((destImage1 != NULL && destImage2 != NULL) || skipScale) ) {
unsigned int currentInRow = 0;
unsigned int currentOutRow = 0;
unsigned int rowsInRemaining = readHeight;
unsigned int rowsOutRemaining = targetHeight;
bool failed = false;
while( rowsOutRemaining > 0 ) {
unsigned int desiredInRows = inMaxRows;
if( currentOutRow == 0 ) {
// For the first tile we need to load a bit of the next one, for proper filtering.
desiredInRows += tileOverlap;
}
unsigned int effectiveInRows = min(rowsInRemaining + prevTileUnprocessed, inMaxRows);
unsigned int effectiveOutRows = min(rowsOutRemaining, outMaxRows);
unsigned int rowsToRead = min(rowsInRemaining, desiredInRows);
if( rowsOutRemaining == effectiveOutRows ) {
rowsToRead = rowsInRemaining;
}
if( rowsToRead > 0 ) {
sourceImage->setDimensions(readWidth, rowsToRead);
// Start loading the next tile into the image below the part of the previous tile we kept around.
sourceImage->setOffset(0, prevTileUnprocessed + prevTileOverlap);
START_CLOCK(read);
unsigned int rowsRead = m_ImageReader->readRows(sourceImage, 0, rowsToRead);
END_CLOCK(read);
if( rowsRead != rowsToRead ) {
failed = true;
// Abort, stop processing tiles.
break;
}
sourceImage->setOffset(0, 0);
}
unsigned int rowsProcessed = effectiveInRows;
unsigned int rowsAvailable = prevTileUnprocessed + rowsToRead;
unsigned int rowsLeftOver = rowsAvailable - rowsProcessed;
int outPreOverlap = ((targetHeight * prevTileOverlap) / readHeight);
int outPostOverlap = ((targetHeight * rowsLeftOver) / readHeight);
sourceImage->setDimensions(readWidth, rowsProcessed + prevTileOverlap + rowsLeftOver);
// Setting these sample offsets allows us to filter the tile exactly the same way it would be
// if the entire image was being filtered. The filter kernel was constructed for the entire image.
int inSampleOffset = currentInRow - prevTileOverlap;
int outSampleOffset = currentOutRow - outPreOverlap;
filterKernelY->setSampleOffset(inSampleOffset, outSampleOffset);
ImageRGBA* image = NULL;
if( skipScale ) {
image = sourceImage;
} else {
// Perform the resize of the tile.
destImage1->setDimensions(targetWidth, effectiveOutRows + outPreOverlap + outPostOverlap);
destImage2->setDimensions(targetWidth, effectiveOutRows + outPreOverlap + outPostOverlap);
image = resizeTile(sourceImage, destImage1, destImage2, inSampleOffset, outSampleOffset, filterKernelX, filterKernelY, skipFiltering);
// For writing we skip past the top few rows, which are from the previous tile.
image->setOffset(0, outPreOverlap);
image->setDimensions(targetWidth, effectiveOutRows);
}
START_CLOCK(write);
unsigned int rowsWritten = m_ImageWriter->writeRows(image, 0, image->getHeight());
END_CLOCK(write);
if( rowsWritten != image->getHeight() ) {
failed = true;
// Abort, stop processing tiles.
break;
}
image->setOffset(0, 0);
if( rowsLeftOver > 0 ) {
// Copy the bottom of this tile to the top of the image, so it serves as the filter edge padding for the next resize.
sourceImage->setDimensions(readWidth, maxSourceHeight);
sourceImage->copyRect(sourceImage, 0, prevTileOverlap + rowsProcessed - tileOverlap, 0, 0, readWidth, rowsLeftOver + tileOverlap);
sourceImage->setDimensions(readWidth, rowsLeftOver + tileOverlap);
prevTileUnprocessed = rowsLeftOver;
prevTileOverlap = tileOverlap;
} else {
prevTileUnprocessed = 0;
prevTileOverlap = 0;
}
currentInRow += rowsProcessed;
currentOutRow += effectiveOutRows;
rowsOutRemaining -= effectiveOutRows;
rowsInRemaining -= rowsToRead;
}
if( !failed ) {
START_CLOCK(finish);
if( m_ImageReader->endRead() ) {
if( m_ImageWriter->endWrite() ) {
END_CLOCK(finish);
success = true;
}
}
}
}
delete destImage1;
delete destImage2;
delete sourceImage;
}
}
}
delete filterKernelX;
delete filterKernelY;
}
return success ? IMAGECORE_SUCCESS : IMAGECORE_UNKNOWN_ERROR;
}