// ========================================================== // Bitmap rotation by means of 3 shears. // // Design and implementation by // - Hervé Drolon (drolon@infonie.fr) // - Thorsten Radde (support@IdealSoftware.com) // - Mihail Naydenov (mnaydenov@users.sourceforge.net) // // 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! // ========================================================== /* ============================================================ References : [1] Paeth A., A Fast Algorithm for General Raster Rotation. Graphics Gems, p. 179, Andrew Glassner editor, Academic Press, 1990. [2] Yariv E., High quality image rotation (rotate by shear). [Online] http://www.codeproject.com/bitmap/rotatebyshear.asp [3] Treskunov A., Fast and high quality true-color bitmap rotation function. [Online] http://anton.treskunov.net/Software/doc/fast_and_high_quality_true_color_bitmap_rotation_function.html ============================================================ */ #include "FreeImage.h" #include "Utilities.h" #define RBLOCK 64 // image blocks of RBLOCK*RBLOCK pixels // -------------------------------------------------------------------------- /** Skews a row horizontally (with filtered weights). Limited to 45 degree skewing only. Filters two adjacent pixels. Parameter T can be BYTE, WORD of float. @param src Pointer to source image to rotate @param dst Pointer to destination image @param row Row index @param iOffset Skew offset @param dWeight Relative weight of right pixel @param bkcolor Background color */ template void HorizontalSkewT(FIBITMAP *src, FIBITMAP *dst, int row, int iOffset, double weight, const void *bkcolor = NULL) { int iXPos; const unsigned src_width = FreeImage_GetWidth(src); const unsigned dst_width = FreeImage_GetWidth(dst); T pxlSrc[4], pxlLeft[4], pxlOldLeft[4]; // 4 = 4*sizeof(T) max // background const T pxlBlack[4] = {0, 0, 0, 0 }; const T *pxlBkg = static_cast(bkcolor); // assume at least bytespp and 4*sizeof(T) max if(!pxlBkg) { // default background color is black pxlBkg = pxlBlack; } // calculate the number of bytes per pixel const unsigned bytespp = FreeImage_GetLine(src) / FreeImage_GetWidth(src); // calculate the number of samples per pixel const unsigned samples = bytespp / sizeof(T); BYTE *src_bits = FreeImage_GetScanLine(src, row); BYTE *dst_bits = FreeImage_GetScanLine(dst, row); // fill gap left of skew with background if(bkcolor) { for(int k = 0; k < iOffset; k++) { memcpy(&dst_bits[k * bytespp], bkcolor, bytespp); } AssignPixel((BYTE*)&pxlOldLeft[0], (BYTE*)bkcolor, bytespp); } else { if(iOffset > 0) { memset(dst_bits, 0, iOffset * bytespp); } memset(&pxlOldLeft[0], 0, bytespp); } for(unsigned i = 0; i < src_width; i++) { // loop through row pixels AssignPixel((BYTE*)&pxlSrc[0], (BYTE*)src_bits, bytespp); // calculate weights for(unsigned j = 0; j < samples; j++) { pxlLeft[j] = static_cast(pxlBkg[j] + (pxlSrc[j] - pxlBkg[j]) * weight + 0.5); } // check boundaries iXPos = i + iOffset; if((iXPos >= 0) && (iXPos < (int)dst_width)) { // update left over on source for(unsigned j = 0; j < samples; j++) { pxlSrc[j] = pxlSrc[j] - (pxlLeft[j] - pxlOldLeft[j]); } AssignPixel((BYTE*)&dst_bits[iXPos*bytespp], (BYTE*)&pxlSrc[0], bytespp); } // save leftover for next pixel in scan AssignPixel((BYTE*)&pxlOldLeft[0], (BYTE*)&pxlLeft[0], bytespp); // next pixel in scan src_bits += bytespp; } // go to rightmost point of skew iXPos = src_width + iOffset; if((iXPos >= 0) && (iXPos < (int)dst_width)) { dst_bits = FreeImage_GetScanLine(dst, row) + iXPos * bytespp; // If still in image bounds, put leftovers there AssignPixel((BYTE*)dst_bits, (BYTE*)&pxlOldLeft[0], bytespp); // clear to the right of the skewed line with background dst_bits += bytespp; if(bkcolor) { for(unsigned i = 0; i < dst_width - iXPos - 1; i++) { memcpy(&dst_bits[i * bytespp], bkcolor, bytespp); } } else { memset(dst_bits, 0, bytespp * (dst_width - iXPos - 1)); } } } /** Skews a row horizontally (with filtered weights). Limited to 45 degree skewing only. Filters two adjacent pixels. @param src Pointer to source image to rotate @param dst Pointer to destination image @param row Row index @param iOffset Skew offset @param dWeight Relative weight of right pixel @param bkcolor Background color */ static void HorizontalSkew(FIBITMAP *src, FIBITMAP *dst, int row, int iOffset, double dWeight, const void *bkcolor) { FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(src); switch(image_type) { case FIT_BITMAP: switch(FreeImage_GetBPP(src)) { case 8: case 24: case 32: HorizontalSkewT(src, dst, row, iOffset, dWeight, bkcolor); break; } break; case FIT_UINT16: case FIT_RGB16: case FIT_RGBA16: HorizontalSkewT(src, dst, row, iOffset, dWeight, bkcolor); break; case FIT_FLOAT: case FIT_RGBF: case FIT_RGBAF: HorizontalSkewT(src, dst, row, iOffset, dWeight, bkcolor); break; } } /** Skews a column vertically (with filtered weights). Limited to 45 degree skewing only. Filters two adjacent pixels. Parameter T can be BYTE, WORD of float. @param src Pointer to source image to rotate @param dst Pointer to destination image @param col Column index @param iOffset Skew offset @param dWeight Relative weight of upper pixel @param bkcolor Background color */ template void VerticalSkewT(FIBITMAP *src, FIBITMAP *dst, int col, int iOffset, double weight, const void *bkcolor = NULL) { int iYPos; unsigned src_height = FreeImage_GetHeight(src); unsigned dst_height = FreeImage_GetHeight(dst); T pxlSrc[4], pxlLeft[4], pxlOldLeft[4]; // 4 = 4*sizeof(T) max // background const T pxlBlack[4] = {0, 0, 0, 0 }; const T *pxlBkg = static_cast(bkcolor); // assume at least bytespp and 4*sizeof(T) max if(!pxlBkg) { // default background color is black pxlBkg = pxlBlack; } // calculate the number of bytes per pixel const unsigned bytespp = FreeImage_GetLine(src) / FreeImage_GetWidth(src); // calculate the number of samples per pixel const unsigned samples = bytespp / sizeof(T); const unsigned src_pitch = FreeImage_GetPitch(src); const unsigned dst_pitch = FreeImage_GetPitch(dst); const unsigned index = col * bytespp; BYTE *src_bits = FreeImage_GetBits(src) + index; BYTE *dst_bits = FreeImage_GetBits(dst) + index; // fill gap above skew with background if(bkcolor) { for(int k = 0; k < iOffset; k++) { memcpy(dst_bits, bkcolor, bytespp); dst_bits += dst_pitch; } memcpy(&pxlOldLeft[0], bkcolor, bytespp); } else { for(int k = 0; k < iOffset; k++) { memset(dst_bits, 0, bytespp); dst_bits += dst_pitch; } memset(&pxlOldLeft[0], 0, bytespp); } for(unsigned i = 0; i < src_height; i++) { // loop through column pixels AssignPixel((BYTE*)(&pxlSrc[0]), src_bits, bytespp); // calculate weights for(unsigned j = 0; j < samples; j++) { pxlLeft[j] = static_cast(pxlBkg[j] + (pxlSrc[j] - pxlBkg[j]) * weight + 0.5); } // check boundaries iYPos = i + iOffset; if((iYPos >= 0) && (iYPos < (int)dst_height)) { // update left over on source for(unsigned j = 0; j < samples; j++) { pxlSrc[j] = pxlSrc[j] - (pxlLeft[j] - pxlOldLeft[j]); } dst_bits = FreeImage_GetScanLine(dst, iYPos) + index; AssignPixel(dst_bits, (BYTE*)(&pxlSrc[0]), bytespp); } // save leftover for next pixel in scan AssignPixel((BYTE*)(&pxlOldLeft[0]), (BYTE*)(&pxlLeft[0]), bytespp); // next pixel in scan src_bits += src_pitch; } // go to bottom point of skew iYPos = src_height + iOffset; if((iYPos >= 0) && (iYPos < (int)dst_height)) { dst_bits = FreeImage_GetScanLine(dst, iYPos) + index; // if still in image bounds, put leftovers there AssignPixel((BYTE*)(dst_bits), (BYTE*)(&pxlOldLeft[0]), bytespp); // clear below skewed line with background if(bkcolor) { while(++iYPos < (int)dst_height) { dst_bits += dst_pitch; AssignPixel((BYTE*)(dst_bits), (BYTE*)(bkcolor), bytespp); } } else { while(++iYPos < (int)dst_height) { dst_bits += dst_pitch; memset(dst_bits, 0, bytespp); } } } } /** Skews a column vertically (with filtered weights). Limited to 45 degree skewing only. Filters two adjacent pixels. @param src Pointer to source image to rotate @param dst Pointer to destination image @param col Column index @param iOffset Skew offset @param dWeight Relative weight of upper pixel @param bkcolor Background color */ static void VerticalSkew(FIBITMAP *src, FIBITMAP *dst, int col, int iOffset, double dWeight, const void *bkcolor) { FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(src); switch(image_type) { case FIT_BITMAP: switch(FreeImage_GetBPP(src)) { case 8: case 24: case 32: VerticalSkewT(src, dst, col, iOffset, dWeight, bkcolor); break; } break; case FIT_UINT16: case FIT_RGB16: case FIT_RGBA16: VerticalSkewT(src, dst, col, iOffset, dWeight, bkcolor); break; case FIT_FLOAT: case FIT_RGBF: case FIT_RGBAF: VerticalSkewT(src, dst, col, iOffset, dWeight, bkcolor); break; } } /** Rotates an image by 90 degrees (counter clockwise). Precise rotation, no filters required.
Code adapted from CxImage (http://www.xdp.it/cximage.htm) @param src Pointer to source image to rotate @return Returns a pointer to a newly allocated rotated image if successful, returns NULL otherwise */ static FIBITMAP* Rotate90(FIBITMAP *src) { const unsigned bpp = FreeImage_GetBPP(src); const unsigned src_width = FreeImage_GetWidth(src); const unsigned src_height = FreeImage_GetHeight(src); const unsigned dst_width = src_height; const unsigned dst_height = src_width; FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(src); // allocate and clear dst image FIBITMAP *dst = FreeImage_AllocateT(image_type, dst_width, dst_height, bpp); if(NULL == dst) return NULL; // get src and dst scan width const unsigned src_pitch = FreeImage_GetPitch(src); const unsigned dst_pitch = FreeImage_GetPitch(dst); switch(image_type) { case FIT_BITMAP: if(bpp == 1) { // speedy rotate for BW images BYTE *bsrc = FreeImage_GetBits(src); BYTE *bdest = FreeImage_GetBits(dst); BYTE *dbitsmax = bdest + dst_height * dst_pitch - 1; for(unsigned y = 0; y < src_height; y++) { // figure out the column we are going to be copying to const div_t div_r = div(y, 8); // set bit pos of src column byte const BYTE bitpos = (BYTE)(128 >> div_r.rem); BYTE *srcdisp = bsrc + y * src_pitch; for(unsigned x = 0; x < src_pitch; x++) { // get source bits BYTE *sbits = srcdisp + x; // get destination column BYTE *nrow = bdest + (dst_height - 1 - (x * 8)) * dst_pitch + div_r.quot; for (int z = 0; z < 8; z++) { // get destination byte BYTE *dbits = nrow - z * dst_pitch; if ((dbits < bdest) || (dbits > dbitsmax)) break; if (*sbits & (128 >> z)) *dbits |= bitpos; } } } } else if((bpp == 8) || (bpp == 24) || (bpp == 32)) { // anything other than BW : // This optimized version of rotation rotates image by smaller blocks. It is quite // a bit faster than obvious algorithm, because it produces much less CPU cache misses. // This optimization can be tuned by changing block size (RBLOCK). 96 is good value for current // CPUs (tested on Athlon XP and Celeron D). Larger value (if CPU has enough cache) will increase // speed somehow, but once you drop out of CPU's cache, things will slow down drastically. // For older CPUs with less cache, lower value would yield better results. BYTE *bsrc = FreeImage_GetBits(src); // source pixels BYTE *bdest = FreeImage_GetBits(dst); // destination pixels // 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 all image blocks of RBLOCK*RBLOCK pixels // x-segment for(unsigned xs = 0; xs < dst_width; xs += RBLOCK) { // y-segment for(unsigned ys = 0; ys < dst_height; ys += RBLOCK) { for(unsigned y = ys; y < MIN(dst_height, ys + RBLOCK); y++) { // do rotation const unsigned y2 = dst_height - y - 1; // point to src pixel at (y2, xs) BYTE *src_bits = bsrc + (xs * src_pitch) + (y2 * bytespp); // point to dst pixel at (xs, y) BYTE *dst_bits = bdest + (y * dst_pitch) + (xs * bytespp); for(unsigned x = xs; x < MIN(dst_width, xs + RBLOCK); x++) { // dst.SetPixel(x, y, src.GetPixel(y2, x)); AssignPixel(dst_bits, src_bits, bytespp); dst_bits += bytespp; src_bits += src_pitch; } } } } } break; case FIT_UINT16: case FIT_RGB16: case FIT_RGBA16: case FIT_FLOAT: case FIT_RGBF: case FIT_RGBAF: { BYTE *bsrc = FreeImage_GetBits(src); // source pixels BYTE *bdest = FreeImage_GetBits(dst); // destination pixels // calculate the number of bytes per pixel const unsigned bytespp = FreeImage_GetLine(src) / FreeImage_GetWidth(src); for(unsigned y = 0; y < dst_height; y++) { BYTE *src_bits = bsrc + (src_width - 1 - y) * bytespp; BYTE *dst_bits = bdest + (y * dst_pitch); for(unsigned x = 0; x < dst_width; x++) { AssignPixel(dst_bits, src_bits, bytespp); src_bits += src_pitch; dst_bits += bytespp; } } } break; } return dst; } /** Rotates an image by 180 degrees (counter clockwise). Precise rotation, no filters required. @param src Pointer to source image to rotate @return Returns a pointer to a newly allocated rotated image if successful, returns NULL otherwise */ static FIBITMAP* Rotate180(FIBITMAP *src) { int x, y, k, pos; const int bpp = FreeImage_GetBPP(src); const int src_width = FreeImage_GetWidth(src); const int src_height = FreeImage_GetHeight(src); const int dst_width = src_width; const int dst_height = src_height; FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(src); FIBITMAP *dst = FreeImage_AllocateT(image_type, dst_width, dst_height, bpp); if(NULL == dst) return NULL; switch(image_type) { case FIT_BITMAP: if(bpp == 1) { for(int y = 0; y < src_height; y++) { BYTE *src_bits = FreeImage_GetScanLine(src, y); BYTE *dst_bits = FreeImage_GetScanLine(dst, dst_height - y - 1); for(int x = 0; x < src_width; x++) { // get bit at (x, y) k = (src_bits[x >> 3] & (0x80 >> (x & 0x07))) != 0; // set bit at (dst_width - x - 1, dst_height - y - 1) pos = dst_width - x - 1; k ? dst_bits[pos >> 3] |= (0x80 >> (pos & 0x7)) : dst_bits[pos >> 3] &= (0xFF7F >> (pos & 0x7)); } } break; } // else if((bpp == 8) || (bpp == 24) || (bpp == 32)) FALL TROUGH case FIT_UINT16: case FIT_RGB16: case FIT_RGBA16: case FIT_FLOAT: case FIT_RGBF: case FIT_RGBAF: { // Calculate the number of bytes per pixel const int bytespp = FreeImage_GetLine(src) / FreeImage_GetWidth(src); for(y = 0; y < src_height; y++) { BYTE *src_bits = FreeImage_GetScanLine(src, y); BYTE *dst_bits = FreeImage_GetScanLine(dst, dst_height - y - 1) + (dst_width - 1) * bytespp; for(x = 0; x < src_width; x++) { // get pixel at (x, y) // set pixel at (dst_width - x - 1, dst_height - y - 1) AssignPixel(dst_bits, src_bits, bytespp); src_bits += bytespp; dst_bits -= bytespp; } } } break; } return dst; } /** Rotates an image by 270 degrees (counter clockwise). Precise rotation, no filters required.
Code adapted from CxImage (http://www.xdp.it/cximage.htm) @param src Pointer to source image to rotate @return Returns a pointer to a newly allocated rotated image if successful, returns NULL otherwise */ static FIBITMAP* Rotate270(FIBITMAP *src) { int x2, dlineup; const unsigned bpp = FreeImage_GetBPP(src); const unsigned src_width = FreeImage_GetWidth(src); const unsigned src_height = FreeImage_GetHeight(src); const unsigned dst_width = src_height; const unsigned dst_height = src_width; FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(src); // allocate and clear dst image FIBITMAP *dst = FreeImage_AllocateT(image_type, dst_width, dst_height, bpp); if(NULL == dst) return NULL; // get src and dst scan width const unsigned src_pitch = FreeImage_GetPitch(src); const unsigned dst_pitch = FreeImage_GetPitch(dst); switch(image_type) { case FIT_BITMAP: if(bpp == 1) { // speedy rotate for BW images BYTE *bsrc = FreeImage_GetBits(src); BYTE *bdest = FreeImage_GetBits(dst); BYTE *dbitsmax = bdest + dst_height * dst_pitch - 1; dlineup = 8 * dst_pitch - dst_width; for(unsigned y = 0; y < src_height; y++) { // figure out the column we are going to be copying to const div_t div_r = div(y + dlineup, 8); // set bit pos of src column byte const BYTE bitpos = (BYTE)(1 << div_r.rem); const BYTE *srcdisp = bsrc + y * src_pitch; for(unsigned x = 0; x < src_pitch; x++) { // get source bits const BYTE *sbits = srcdisp + x; // get destination column BYTE *nrow = bdest + (x * 8) * dst_pitch + dst_pitch - 1 - div_r.quot; for(unsigned z = 0; z < 8; z++) { // get destination byte BYTE *dbits = nrow + z * dst_pitch; if ((dbits < bdest) || (dbits > dbitsmax)) break; if (*sbits & (128 >> z)) *dbits |= bitpos; } } } } else if((bpp == 8) || (bpp == 24) || (bpp == 32)) { // anything other than BW : // This optimized version of rotation rotates image by smaller blocks. It is quite // a bit faster than obvious algorithm, because it produces much less CPU cache misses. // This optimization can be tuned by changing block size (RBLOCK). 96 is good value for current // CPUs (tested on Athlon XP and Celeron D). Larger value (if CPU has enough cache) will increase // speed somehow, but once you drop out of CPU's cache, things will slow down drastically. // For older CPUs with less cache, lower value would yield better results. BYTE *bsrc = FreeImage_GetBits(src); // source pixels BYTE *bdest = FreeImage_GetBits(dst); // destination pixels // 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 all image blocks of RBLOCK*RBLOCK pixels // x-segment for(unsigned xs = 0; xs < dst_width; xs += RBLOCK) { // y-segment for(unsigned ys = 0; ys < dst_height; ys += RBLOCK) { for(unsigned x = xs; x < MIN(dst_width, xs + RBLOCK); x++) { // do rotation x2 = dst_width - x - 1; // point to src pixel at (ys, x2) BYTE *src_bits = bsrc + (x2 * src_pitch) + (ys * bytespp); // point to dst pixel at (x, ys) BYTE *dst_bits = bdest + (ys * dst_pitch) + (x * bytespp); for(unsigned y = ys; y < MIN(dst_height, ys + RBLOCK); y++) { // dst.SetPixel(x, y, src.GetPixel(y, x2)); AssignPixel(dst_bits, src_bits, bytespp); src_bits += bytespp; dst_bits += dst_pitch; } } } } } break; case FIT_UINT16: case FIT_RGB16: case FIT_RGBA16: case FIT_FLOAT: case FIT_RGBF: case FIT_RGBAF: { BYTE *bsrc = FreeImage_GetBits(src); // source pixels BYTE *bdest = FreeImage_GetBits(dst); // destination pixels // calculate the number of bytes per pixel const unsigned bytespp = FreeImage_GetLine(src) / FreeImage_GetWidth(src); for(unsigned y = 0; y < dst_height; y++) { BYTE *src_bits = bsrc + (src_height - 1) * src_pitch + y * bytespp; BYTE *dst_bits = bdest + (y * dst_pitch); for(unsigned x = 0; x < dst_width; x++) { AssignPixel(dst_bits, src_bits, bytespp); src_bits -= src_pitch; dst_bits += bytespp; } } } break; } return dst; } /** Rotates an image by a given degree in range [-45 .. +45] (counter clockwise) using the 3-shear technique. @param src Pointer to source image to rotate @param dAngle Rotation angle @return Returns a pointer to a newly allocated rotated image if successful, returns NULL otherwise */ static FIBITMAP* Rotate45(FIBITMAP *src, double dAngle, const void *bkcolor) { const double ROTATE_PI = double(3.1415926535897932384626433832795); unsigned u; const unsigned bpp = FreeImage_GetBPP(src); const double dRadAngle = dAngle * ROTATE_PI / double(180); // Angle in radians const double dSinE = sin(dRadAngle); const double dTan = tan(dRadAngle / 2); const unsigned src_width = FreeImage_GetWidth(src); const unsigned src_height = FreeImage_GetHeight(src); FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(src); // Calc first shear (horizontal) destination image dimensions const unsigned width_1 = src_width + unsigned((double)src_height * fabs(dTan) + 0.5); const unsigned height_1 = src_height; // Perform 1st shear (horizontal) // ---------------------------------------------------------------------- // Allocate image for 1st shear FIBITMAP *dst1 = FreeImage_AllocateT(image_type, width_1, height_1, bpp); if(NULL == dst1) { return NULL; } for(u = 0; u < height_1; u++) { double dShear; if(dTan >= 0) { // Positive angle dShear = (u + 0.5) * dTan; } else { // Negative angle dShear = (double(u) - height_1 + 0.5) * dTan; } int iShear = int(floor(dShear)); HorizontalSkew(src, dst1, u, iShear, dShear - double(iShear), bkcolor); } // Perform 2nd shear (vertical) // ---------------------------------------------------------------------- // Calc 2nd shear (vertical) destination image dimensions const unsigned width_2 = width_1; unsigned height_2 = unsigned((double)src_width * fabs(dSinE) + (double)src_height * cos(dRadAngle) + 0.5) + 1; // Allocate image for 2nd shear FIBITMAP *dst2 = FreeImage_AllocateT(image_type, width_2, height_2, bpp); if(NULL == dst2) { FreeImage_Unload(dst1); return NULL; } double dOffset; // Variable skew offset if(dSinE > 0) { // Positive angle dOffset = (src_width - 1.0) * dSinE; } else { // Negative angle dOffset = -dSinE * (double(src_width) - width_2); } for(u = 0; u < width_2; u++, dOffset -= dSinE) { int iShear = int(floor(dOffset)); VerticalSkew(dst1, dst2, u, iShear, dOffset - double(iShear), bkcolor); } // Perform 3rd shear (horizontal) // ---------------------------------------------------------------------- // Free result of 1st shear FreeImage_Unload(dst1); // Calc 3rd shear (horizontal) destination image dimensions const unsigned width_3 = unsigned(double(src_height) * fabs(dSinE) + double(src_width) * cos(dRadAngle) + 0.5) + 1; const unsigned height_3 = height_2; // Allocate image for 3rd shear FIBITMAP *dst3 = FreeImage_AllocateT(image_type, width_3, height_3, bpp); if(NULL == dst3) { FreeImage_Unload(dst2); return NULL; } if(dSinE >= 0) { // Positive angle dOffset = (src_width - 1.0) * dSinE * -dTan; } else { // Negative angle dOffset = dTan * ( (src_width - 1.0) * -dSinE + (1.0 - height_3) ); } for(u = 0; u < height_3; u++, dOffset += dTan) { int iShear = int(floor(dOffset)); HorizontalSkew(dst2, dst3, u, iShear, dOffset - double(iShear), bkcolor); } // Free result of 2nd shear FreeImage_Unload(dst2); // Return result of 3rd shear return dst3; } /** Rotates a 1-, 8-, 24- or 32-bit image by a given angle (given in degree). Angle is unlimited, except for 1-bit images (limited to integer multiples of 90 degree). 3-shears technique is used. @param src Pointer to source image to rotate @param dAngle Rotation angle @return Returns a pointer to a newly allocated rotated image if successful, returns NULL otherwise */ static FIBITMAP* RotateAny(FIBITMAP *src, double dAngle, const void *bkcolor) { if(NULL == src) { return NULL; } FIBITMAP *image = src; while(dAngle >= 360) { // Bring angle to range of (-INF .. 360) dAngle -= 360; } while(dAngle < 0) { // Bring angle to range of [0 .. 360) dAngle += 360; } if((dAngle > 45) && (dAngle <= 135)) { // Angle in (45 .. 135] // Rotate image by 90 degrees into temporary image, // so it requires only an extra rotation angle // of -45 .. +45 to complete rotation. image = Rotate90(src); dAngle -= 90; } else if((dAngle > 135) && (dAngle <= 225)) { // Angle in (135 .. 225] // Rotate image by 180 degrees into temporary image, // so it requires only an extra rotation angle // of -45 .. +45 to complete rotation. image = Rotate180(src); dAngle -= 180; } else if((dAngle > 225) && (dAngle <= 315)) { // Angle in (225 .. 315] // Rotate image by 270 degrees into temporary image, // so it requires only an extra rotation angle // of -45 .. +45 to complete rotation. image = Rotate270(src); dAngle -= 270; } // If we got here, angle is in (-45 .. +45] if(NULL == image) { // Failed to allocate middle image return NULL; } if(0 == dAngle) { if(image == src) { // Nothing to do ... return FreeImage_Clone(src); } else { // No more rotation needed return image; } } else { // Perform last rotation FIBITMAP *dst = Rotate45(image, dAngle, bkcolor); if(src != image) { // Middle image was required, free it now. FreeImage_Unload(image); } return dst; } } // ========================================================== FIBITMAP *DLL_CALLCONV FreeImage_Rotate(FIBITMAP *dib, double angle, const void *bkcolor) { if(!FreeImage_HasPixels(dib)) return NULL; if(0 == angle) { return FreeImage_Clone(dib); } // DIB are stored upside down ... angle *= -1; try { unsigned bpp = FreeImage_GetBPP(dib); FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib); switch(image_type) { case FIT_BITMAP: if(bpp == 1) { // only rotate for integer multiples of 90 degree if(fmod(angle, 90) != 0) return NULL; // perform the rotation FIBITMAP *dst = RotateAny(dib, angle, bkcolor); if(!dst) throw(1); // build a greyscale palette RGBQUAD *dst_pal = FreeImage_GetPalette(dst); if(FreeImage_GetColorType(dib) == FIC_MINISBLACK) { dst_pal[0].rgbRed = dst_pal[0].rgbGreen = dst_pal[0].rgbBlue = 0; dst_pal[1].rgbRed = dst_pal[1].rgbGreen = dst_pal[1].rgbBlue = 255; } else { dst_pal[0].rgbRed = dst_pal[0].rgbGreen = dst_pal[0].rgbBlue = 255; dst_pal[1].rgbRed = dst_pal[1].rgbGreen = dst_pal[1].rgbBlue = 0; } // copy metadata from src to dst FreeImage_CloneMetadata(dst, dib); return dst; } else if((bpp == 8) || (bpp == 24) || (bpp == 32)) { FIBITMAP *dst = RotateAny(dib, angle, bkcolor); if(!dst) throw(1); if(bpp == 8) { // copy original palette to rotated bitmap RGBQUAD *src_pal = FreeImage_GetPalette(dib); RGBQUAD *dst_pal = FreeImage_GetPalette(dst); memcpy(&dst_pal[0], &src_pal[0], 256 * sizeof(RGBQUAD)); // copy transparency table FreeImage_SetTransparencyTable(dst, FreeImage_GetTransparencyTable(dib), FreeImage_GetTransparencyCount(dib)); // copy background color RGBQUAD bkcolor; if( FreeImage_GetBackgroundColor(dib, &bkcolor) ) { FreeImage_SetBackgroundColor(dst, &bkcolor); } } // copy metadata from src to dst FreeImage_CloneMetadata(dst, dib); return dst; } break; case FIT_UINT16: case FIT_RGB16: case FIT_RGBA16: case FIT_FLOAT: case FIT_RGBF: case FIT_RGBAF: { FIBITMAP *dst = RotateAny(dib, angle, bkcolor); if(!dst) throw(1); // copy metadata from src to dst FreeImage_CloneMetadata(dst, dib); return dst; } break; } } catch(int) { return NULL; } return NULL; }