// ========================================================== // 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; } } }