// ==========================================================
// Upsampling / downsampling classes
//
// Design and implementation by
// - Hervé Drolon (drolon@infonie.fr)
// - Detlev Vendt (detlev.vendt@brillit.de)
//
// This file is part of FreeImage 3
//
// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
// THIS DISCLAIMER.
//
// Use at your own risk!
// ==========================================================
#include "Resize.h"
/**
Filter weights table.
This class stores contribution information for an entire line (row or column).
*/
CWeightsTable::CWeightsTable(CGenericFilter *pFilter, unsigned uDstSize, unsigned uSrcSize) {
unsigned u;
double dWidth;
double dFScale = 1.0;
const double dFilterWidth = pFilter->GetWidth();
// scale factor
const double dScale = double(uDstSize) / double(uSrcSize);
if(dScale < 1.0) {
// minification
dWidth = dFilterWidth / dScale;
dFScale = dScale;
} else {
// magnification
dWidth= dFilterWidth;
}
// allocate a new line contributions structure
//
// window size is the number of sampled pixels
m_WindowSize = 2 * (int)ceil(dWidth) + 1;
m_LineLength = uDstSize;
// allocate list of contributions
m_WeightTable = (Contribution*)malloc(m_LineLength * sizeof(Contribution));
for(u = 0 ; u < m_LineLength ; u++) {
// allocate contributions for every pixel
m_WeightTable[u].Weights = (double*)malloc(m_WindowSize * sizeof(double));
}
// offset for discrete to continuous coordinate conversion
const double dOffset = (0.5 / dScale) - 0.5;
for(u = 0; u < m_LineLength; u++) {
// scan through line of contributions
const double dCenter = (double)u / dScale + dOffset; // reverse mapping
// find the significant edge points that affect the pixel
int iLeft = MAX (0, (int)floor (dCenter - dWidth));
int iRight = MIN ((int)ceil (dCenter + dWidth), int(uSrcSize) - 1);
// cut edge points to fit in filter window in case of spill-off
if ((iRight - iLeft + 1) > int(m_WindowSize)) {
if(iLeft < (int(uSrcSize) - 1 / 2)) {
iLeft++;
} else {
iRight--;
}
}
m_WeightTable[u].Left = iLeft;
m_WeightTable[u].Right = iRight;
int iSrc = 0;
double dTotalWeight = 0; // zero sum of weights
for(iSrc = iLeft; iSrc <= iRight; iSrc++) {
// calculate weights
const double weight = dFScale * pFilter->Filter(dFScale * (dCenter - (double)iSrc));
m_WeightTable[u].Weights[iSrc-iLeft] = weight;
dTotalWeight += weight;
}
if ((dTotalWeight > 0) && (dTotalWeight != 1)) {
// normalize weight of neighbouring points
for(iSrc = iLeft; iSrc <= iRight; iSrc++) {
// normalize point
m_WeightTable[u].Weights[iSrc-iLeft] /= dTotalWeight;
}
// simplify the filter, discarding null weights at the right
iSrc = iRight - iLeft;
while(m_WeightTable[u].Weights[iSrc] == 0){
m_WeightTable[u].Right--;
iSrc--;
if(m_WeightTable[u].Right == m_WeightTable[u].Left)
break;
}
}
}
}
CWeightsTable::~CWeightsTable() {
for(unsigned u = 0; u < m_LineLength; u++) {
// free contributions for every pixel
free(m_WeightTable[u].Weights);
}
// free list of pixels contributions
free(m_WeightTable);
}
// ---------------------------------------------
/**
CResizeEngine
This class performs filtered zoom. It scales an image to the desired dimensions with
any of the CGenericFilter derived filter class.
It works with 8-, 24- and 32-bit buffers.
References :
[1] Paul Heckbert, C code to zoom raster images up or down, with nice filtering.
UC Berkeley, August 1989. [online] http://www-2.cs.cmu.edu/afs/cs.cmu.edu/Web/People/ph/heckbert.html
[2] Eran Yariv, Two Pass Scaling using Filters. The Code Project, December 1999.
[online] http://www.codeproject.com/bitmap/2_pass_scaling.asp
*/
FIBITMAP* CResizeEngine::scale(FIBITMAP *src, unsigned dst_width, unsigned dst_height) {
unsigned src_width = FreeImage_GetWidth(src);
unsigned src_height = FreeImage_GetHeight(src);
unsigned redMask = FreeImage_GetRedMask(src);
unsigned greenMask = FreeImage_GetGreenMask(src);
unsigned blueMask = FreeImage_GetBlueMask(src);
unsigned bpp = FreeImage_GetBPP(src);
if(bpp == 1) {
// convert output to 8-bit
bpp = 8;
}
FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(src);
// allocate the dst image
FIBITMAP *dst = FreeImage_AllocateT(image_type, dst_width, dst_height, bpp, redMask, greenMask, blueMask);
if (!dst) return NULL;
if(bpp == 8) {
if(FreeImage_GetColorType(src) == FIC_MINISWHITE) {
// build an inverted greyscale palette
RGBQUAD *dst_pal = FreeImage_GetPalette(dst);
for(unsigned i = 0; i < 256; i++) {
dst_pal[i].rgbRed = dst_pal[i].rgbGreen = dst_pal[i].rgbBlue = (BYTE)(255 - i);
}
} else {
// build a greyscale palette
RGBQUAD *dst_pal = FreeImage_GetPalette(dst);
for(unsigned i = 0; i < 256; i++) {
dst_pal[i].rgbRed = dst_pal[i].rgbGreen = dst_pal[i].rgbBlue = (BYTE)i;
}
}
}
/**
Decide which filtering order (xy or yx) is faster for this mapping.
--- The theory ---
Try to minimize calculations by counting the number of convolution multiplies
if(dst_width*src_height <= src_width*dst_height) {
// xy filtering
} else {
// yx filtering
}
--- The practice ---
Try to minimize calculations by counting the number of vertical convolutions (the most time consuming task)
if(dst_width*dst_height <= src_width*dst_height) {
// xy filtering
} else {
// yx filtering
}
*/
if(dst_width <= src_width) {
// xy filtering
// -------------
// allocate a temporary image
FIBITMAP *tmp = FreeImage_AllocateT(image_type, dst_width, src_height, bpp, redMask, greenMask, blueMask);
if (!tmp) {
FreeImage_Unload(dst);
return NULL;
}
// scale source image horizontally into temporary image
horizontalFilter(src, src_width, src_height, tmp, dst_width, src_height);
// scale temporary image vertically into result image
verticalFilter(tmp, dst_width, src_height, dst, dst_width, dst_height);
// free temporary image
FreeImage_Unload(tmp);
} else {
// yx filtering
// -------------
// allocate a temporary image
FIBITMAP *tmp = FreeImage_AllocateT(image_type, src_width, dst_height, bpp, redMask, greenMask, blueMask);
if (!tmp) {
FreeImage_Unload(dst);
return NULL;
}
// scale source image vertically into temporary image
verticalFilter(src, src_width, src_height, tmp, src_width, dst_height);
// scale temporary image horizontally into result image
horizontalFilter(tmp, src_width, dst_height, dst, dst_width, dst_height);
// free temporary image
FreeImage_Unload(tmp);
}
return dst;
}
/// Performs horizontal image filtering
void CResizeEngine::horizontalFilter(FIBITMAP *src, unsigned src_width, unsigned src_height, FIBITMAP *dst, unsigned dst_width, unsigned dst_height) {
if(dst_width == src_width) {
// no scaling required, just copy
switch(FreeImage_GetBPP(src)) {
case 1:
{
if(FreeImage_GetBPP(dst) != 8) break;
for(unsigned y = 0; y < dst_height; y++) {
// convert each row
BYTE *src_bits = FreeImage_GetScanLine(src, y);
BYTE *dst_bits = FreeImage_GetScanLine(dst, y);
FreeImage_ConvertLine1To8(dst_bits, src_bits, dst_width);
}
}
break;
default:
{
const BYTE *src_bits = FreeImage_GetBits(src);
BYTE *dst_bits = FreeImage_GetBits(dst);
memcpy(dst_bits, src_bits, dst_height * FreeImage_GetPitch(dst));
}
break;
}
}
else {
// allocate and calculate the contributions
CWeightsTable weightsTable(m_pFilter, dst_width, src_width);
// step through rows
switch(FreeImage_GetImageType(src)) {
case FIT_BITMAP:
{
switch(FreeImage_GetBPP(src)) {
case 1:
{
// scale and convert to 8-bit
if(FreeImage_GetBPP(dst) != 8) break;
for(unsigned y = 0; y < dst_height; y++) {
// scale each row
const BYTE *src_bits = FreeImage_GetScanLine(src, y);
BYTE *dst_bits = FreeImage_GetScanLine(dst, y);
for(unsigned x = 0; x < dst_width; x++) {
// loop through row
double value = 0;
const unsigned iLeft = weightsTable.getLeftBoundary(x); // retrieve left boundary
const unsigned iRight = weightsTable.getRightBoundary(x); // retrieve right boundary
for(unsigned i = iLeft; i <= iRight; i++) {
// scan between boundaries
// accumulate weighted effect of each neighboring pixel
const double weight = weightsTable.getWeight(x, i-iLeft);
const BYTE pixel = (src_bits[i >> 3] & (0x80 >> (i & 0x07))) != 0;
value += (weight * (double)pixel);
}
value *= 255;
// clamp and place result in destination pixel
dst_bits[x] = (BYTE)CLAMP((int)(value + 0.5), 0, 255);
}
}
}
break;
case 8:
case 24:
case 32:
{
// Calculate the number of bytes per pixel (1 for 8-bit, 3 for 24-bit or 4 for 32-bit)
const unsigned bytespp = FreeImage_GetLine(src) / FreeImage_GetWidth(src);
for(unsigned y = 0; y < dst_height; y++) {
// scale each row
const BYTE *src_bits = FreeImage_GetScanLine(src, y);
BYTE *dst_bits = FreeImage_GetScanLine(dst, y);
for(unsigned x = 0; x < dst_width; x++) {
// loop through row
double value[4] = {0, 0, 0, 0}; // 4 = 32 bpp max
const unsigned iLeft = weightsTable.getLeftBoundary(x); // retrieve left boundary
const unsigned iRight = weightsTable.getRightBoundary(x); // retrieve right boundary
for(unsigned i = iLeft; i <= iRight; i++) {
// scan between boundaries
// accumulate weighted effect of each neighboring pixel
const double weight = weightsTable.getWeight(x, i-iLeft);
unsigned index = i * bytespp; // pixel index
for (unsigned j = 0; j < bytespp; j++) {
value[j] += (weight * (double)src_bits[index++]);
}
}
// clamp and place result in destination pixel
for (unsigned j = 0; j < bytespp; j++) {
dst_bits[j] = (BYTE)CLAMP((int)(value[j] + 0.5), 0, 0xFF);
}
dst_bits += bytespp;
}
}
}
break;
}
}
break;
case FIT_UINT16:
case FIT_RGB16:
case FIT_RGBA16:
{
// Calculate the number of words per pixel (1 for 16-bit, 3 for 48-bit or 4 for 64-bit)
const unsigned wordspp = (FreeImage_GetLine(src) / FreeImage_GetWidth(src)) / sizeof(WORD);
for(unsigned y = 0; y < dst_height; y++) {
// scale each row
const WORD *src_bits = (WORD*)FreeImage_GetScanLine(src, y);
WORD *dst_bits = (WORD*)FreeImage_GetScanLine(dst, y);
for(unsigned x = 0; x < dst_width; x++) {
// loop through row
double value[4] = {0, 0, 0, 0}; // 4 = 64 bpp max
const unsigned iLeft = weightsTable.getLeftBoundary(x); // retrieve left boundary
const unsigned iRight = weightsTable.getRightBoundary(x); // retrieve right boundary
for(unsigned i = iLeft; i <= iRight; i++) {
// scan between boundaries
// accumulate weighted effect of each neighboring pixel
const double weight = weightsTable.getWeight(x, i-iLeft);
unsigned index = i * wordspp; // pixel index
for (unsigned j = 0; j < wordspp; j++) {
value[j] += (weight * (double)src_bits[index++]);
}
}
// clamp and place result in destination pixel
for (unsigned j = 0; j < wordspp; j++) {
dst_bits[j] = (WORD)CLAMP((int)(value[j] + 0.5), 0, 0xFFFF);
}
dst_bits += wordspp;
}
}
}
break;
case FIT_FLOAT:
case FIT_RGBF:
case FIT_RGBAF:
{
// Calculate the number of floats per pixel (1 for 32-bit, 3 for 96-bit or 4 for 128-bit)
const unsigned floatspp = (FreeImage_GetLine(src) / FreeImage_GetWidth(src)) / sizeof(float);
for(unsigned y = 0; y < dst_height; y++) {
// scale each row
const float *src_bits = (float*)FreeImage_GetScanLine(src, y);
float *dst_bits = (float*)FreeImage_GetScanLine(dst, y);
for(unsigned x = 0; x < dst_width; x++) {
// loop through row
double value[4] = {0, 0, 0, 0}; // 4 = 128 bpp max
const unsigned iLeft = weightsTable.getLeftBoundary(x); // retrieve left boundary
const unsigned iRight = weightsTable.getRightBoundary(x); // retrieve right boundary
for(unsigned i = iLeft; i <= iRight; i++) {
// scan between boundaries
// accumulate weighted effect of each neighboring pixel
const double weight = weightsTable.getWeight(x, i-iLeft);
unsigned index = i * floatspp; // pixel index
for (unsigned j = 0; j < floatspp; j++) {
value[j] += (weight * (double)src_bits[index++]);
}
}
// place result in destination pixel
for (unsigned j = 0; j < floatspp; j++) {
dst_bits[j] = (float)value[j];
}
dst_bits += floatspp;
}
}
}
break;
}
}
}
/// Performs vertical image filtering
void CResizeEngine::verticalFilter(FIBITMAP *src, unsigned src_width, unsigned src_height, FIBITMAP *dst, unsigned dst_width, unsigned dst_height) {
if(src_height == dst_height) {
// no scaling required, just copy
switch(FreeImage_GetBPP(src)) {
case 1:
{
if(FreeImage_GetBPP(dst) != 8) break;
for(unsigned y = 0; y < dst_height; y++) {
// convert each row
BYTE *src_bits = FreeImage_GetScanLine(src, y);
BYTE *dst_bits = FreeImage_GetScanLine(dst, y);
FreeImage_ConvertLine1To8(dst_bits, src_bits, dst_width);
}
}
break;
default:
{
const BYTE *src_bits = FreeImage_GetBits(src);
BYTE *dst_bits = FreeImage_GetBits(dst);
memcpy(dst_bits, src_bits, dst_height * FreeImage_GetPitch(dst));
}
break;
}
}
else {
// allocate and calculate the contributions
CWeightsTable weightsTable(m_pFilter, dst_height, src_height);
// step through columns
switch(FreeImage_GetImageType(src)) {
case FIT_BITMAP:
{
switch(FreeImage_GetBPP(src)) {
case 1:
{
// scale and convert to 8-bit
if(FreeImage_GetBPP(dst) != 8) break;
const unsigned src_pitch = FreeImage_GetPitch(src);
const unsigned dst_pitch = FreeImage_GetPitch(dst);
for(unsigned x = 0; x < dst_width; x++) {
// work on column x in dst
BYTE *dst_bits = FreeImage_GetBits(dst) + x;
// scale each column
for(unsigned y = 0; y < dst_height; y++) {
// loop through column
double value = 0;
const unsigned iLeft = weightsTable.getLeftBoundary(y); // retrieve left boundary
const unsigned iRight = weightsTable.getRightBoundary(y); // retrieve right boundary
BYTE *src_bits = FreeImage_GetScanLine(src, iLeft);
for(unsigned i = iLeft; i <= iRight; i++) {
// scan between boundaries
// accumulate weighted effect of each neighboring pixel
const double weight = weightsTable.getWeight(y, i-iLeft);
const BYTE pixel = (src_bits[x >> 3] & (0x80 >> (x & 0x07))) != 0;
value += (weight * (double)pixel);
src_bits += src_pitch;
}
value *= 255;
// clamp and place result in destination pixel
*dst_bits = (BYTE)CLAMP((int)(value + 0.5), 0, 0xFF);
dst_bits += dst_pitch;
}
}
}
break;
case 8:
case 24:
case 32:
{
// Calculate the number of bytes per pixel (1 for 8-bit, 3 for 24-bit or 4 for 32-bit)
const unsigned bytespp = FreeImage_GetLine(src) / FreeImage_GetWidth(src);
const unsigned src_pitch = FreeImage_GetPitch(src);
const unsigned dst_pitch = FreeImage_GetPitch(dst);
for(unsigned x = 0; x < dst_width; x++) {
const unsigned index = x * bytespp; // pixel index
// work on column x in dst
BYTE *dst_bits = FreeImage_GetBits(dst) + index;
// scale each column
for(unsigned y = 0; y < dst_height; y++) {
// loop through column
double value[4] = {0, 0, 0, 0}; // 4 = 32 bpp max
const unsigned iLeft = weightsTable.getLeftBoundary(y); // retrieve left boundary
const unsigned iRight = weightsTable.getRightBoundary(y); // retrieve right boundary
const BYTE *src_bits = FreeImage_GetScanLine(src, iLeft) + index;
for(unsigned i = iLeft; i <= iRight; i++) {
// scan between boundaries
// accumulate weighted effect of each neighboring pixel
const double weight = weightsTable.getWeight(y, i-iLeft);
for (unsigned j = 0; j < bytespp; j++) {
value[j] += (weight * (double)src_bits[j]);
}
src_bits += src_pitch;
}
// clamp and place result in destination pixel
for (unsigned j = 0; j < bytespp; j++) {
dst_bits[j] = (BYTE)CLAMP((int)(value[j] + 0.5), 0, 0xFF);
}
dst_bits += dst_pitch;
}
}
}
break;
}
}
break;
case FIT_UINT16:
case FIT_RGB16:
case FIT_RGBA16:
{
// Calculate the number of words per pixel (1 for 16-bit, 3 for 48-bit or 4 for 64-bit)
const unsigned wordspp = (FreeImage_GetLine(src) / FreeImage_GetWidth(src)) / sizeof(WORD);
const unsigned src_pitch = FreeImage_GetPitch(src) / sizeof(WORD);
const unsigned dst_pitch = FreeImage_GetPitch(dst) / sizeof(WORD);
for(unsigned x = 0; x < dst_width; x++) {
const unsigned index = x * wordspp; // pixel index
// work on column x in dst
WORD *dst_bits = (WORD*)FreeImage_GetBits(dst) + index;
// scale each column
for(unsigned y = 0; y < dst_height; y++) {
// loop through column
double value[4] = {0, 0, 0, 0}; // 4 = 64 bpp max
const unsigned iLeft = weightsTable.getLeftBoundary(y); // retrieve left boundary
const unsigned iRight = weightsTable.getRightBoundary(y); // retrieve right boundary
const WORD *src_bits = (WORD*)FreeImage_GetScanLine(src, iLeft) + index;
for(unsigned i = iLeft; i <= iRight; i++) {
// scan between boundaries
// accumulate weighted effect of each neighboring pixel
const double weight = weightsTable.getWeight(y, i-iLeft);
for (unsigned j = 0; j < wordspp; j++) {
value[j] += (weight * (double)src_bits[j]);
}
src_bits += src_pitch;
}
// clamp and place result in destination pixel
for (unsigned j = 0; j < wordspp; j++) {
dst_bits[j] = (WORD)CLAMP((int)(value[j] + 0.5), 0, 0xFFFF);
}
dst_bits += dst_pitch;
}
}
}
break;
case FIT_FLOAT:
case FIT_RGBF:
case FIT_RGBAF:
{
// Calculate the number of floats per pixel (1 for 32-bit, 3 for 96-bit or 4 for 128-bit)
const unsigned floatspp = (FreeImage_GetLine(src) / FreeImage_GetWidth(src)) / sizeof(float);
const unsigned src_pitch = FreeImage_GetPitch(src) / sizeof(float);
const unsigned dst_pitch = FreeImage_GetPitch(dst) / sizeof(float);
for(unsigned x = 0; x < dst_width; x++) {
const unsigned index = x * floatspp; // pixel index
// work on column x in dst
float *dst_bits = (float*)FreeImage_GetBits(dst) + index;
// scale each column
for(unsigned y = 0; y < dst_height; y++) {
// loop through column
double value[4] = {0, 0, 0, 0}; // 4 = 128 bpp max
const unsigned iLeft = weightsTable.getLeftBoundary(y); // retrieve left boundary
const unsigned iRight = weightsTable.getRightBoundary(y); // retrieve right boundary
const float *src_bits = (float*)FreeImage_GetScanLine(src, iLeft) + index;
for(unsigned i = iLeft; i <= iRight; i++) {
// scan between boundaries
// accumulate weighted effect of each neighboring pixel
const double weight = weightsTable.getWeight(y, i-iLeft);
for (unsigned j = 0; j < floatspp; j++) {
value[j] += (weight * (double)src_bits[j]);
}
src_bits += src_pitch;
}
// clamp and place result in destination pixel
for (unsigned j = 0; j < floatspp; j++) {
dst_bits[j] = (float)value[j];
}
dst_bits += dst_pitch;
}
}
}
break;
}
}
}