// xImaTran.cpp : Transformation functions
/* 07/08/2001 v1.00 - Davide Pizzolato - www.xdp.it
* CxImage version 6.0.0 02/Feb/2008
*/
#include "ximage.h"
#include "ximath.h"
#if CXIMAGE_SUPPORT_BASICTRANSFORMATIONS
////////////////////////////////////////////////////////////////////////////////
bool CxImage::GrayScale()
{
if (!pDib) return false;
if (head.biBitCount<=8){
RGBQUAD* ppal=GetPalette();
int gray;
//converts the colors to gray, use the blue channel only
for(DWORD i=0;i
= 0) info.nBkgndIndex = ppal[info.nBkgndIndex].rgbBlue;
//create a "real" 8 bit gray scale image
if (head.biBitCount==8){
BYTE *img=info.pImage;
for(DWORD i=0;i> 1]&((BYTE)0x0F<> pos)].rgbBlue;
} else {
BYTE pos = (BYTE)(7-x%8);
iDst[x]= ppal[(BYTE)((iSrc[x >> 3]&((BYTE)0x01<> pos)].rgbBlue;
}
}
}
Transfer(ima);
}
} else { //from RGB to 8 bit gray scale
BYTE *iSrc=info.pImage;
CxImage ima;
ima.CopyInfo(*this);
if (!ima.Create(head.biWidth,head.biHeight,8,info.dwType)) return false;
ima.SetGrayPalette();
#if CXIMAGE_SUPPORT_SELECTION
ima.SelectionCopy(*this);
#endif //CXIMAGE_SUPPORT_SELECTION
#if CXIMAGE_SUPPORT_ALPHA
ima.AlphaCopy(*this);
#endif //CXIMAGE_SUPPORT_ALPHA
BYTE *img=ima.GetBits();
long l8=ima.GetEffWidth();
long l=head.biWidth * 3;
for(long y=0; y < head.biHeight; y++) {
for(long x=0,x8=0; x < l; x+=3,x8++) {
img[x8+y*l8]=(BYTE)RGB2GRAY(*(iSrc+x+2),*(iSrc+x+1),*(iSrc+x+0));
}
iSrc+=info.dwEffWidth;
}
Transfer(ima);
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/**
* \sa Mirror
* \author [qhbo]
*/
bool CxImage::Flip(bool bFlipSelection, bool bFlipAlpha)
{
if (!pDib) return false;
BYTE *buff = (BYTE*)malloc(info.dwEffWidth);
if (!buff) return false;
BYTE *iSrc,*iDst;
iSrc = GetBits(head.biHeight-1);
iDst = GetBits(0);
for (long i=0; i<(head.biHeight/2); ++i)
{
memcpy(buff, iSrc, info.dwEffWidth);
memcpy(iSrc, iDst, info.dwEffWidth);
memcpy(iDst, buff, info.dwEffWidth);
iSrc-=info.dwEffWidth;
iDst+=info.dwEffWidth;
}
free(buff);
if (bFlipSelection){
#if CXIMAGE_SUPPORT_SELECTION
SelectionFlip();
#endif //CXIMAGE_SUPPORT_SELECTION
}
if (bFlipAlpha){
#if CXIMAGE_SUPPORT_ALPHA
AlphaFlip();
#endif //CXIMAGE_SUPPORT_ALPHA
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/**
* \sa Flip
*/
bool CxImage::Mirror(bool bMirrorSelection, bool bMirrorAlpha)
{
if (!pDib) return false;
CxImage* imatmp = new CxImage(*this,false,true,true);
if (!imatmp) return false;
if (!imatmp->IsValid()){
delete imatmp;
return false;
}
BYTE *iSrc,*iDst;
long wdt=(head.biWidth-1) * (head.biBitCount==24 ? 3:1);
iSrc=info.pImage + wdt;
iDst=imatmp->info.pImage;
long x,y;
switch (head.biBitCount){
case 24:
for(y=0; y < head.biHeight; y++){
for(x=0; x <= wdt; x+=3){
*(iDst+x)=*(iSrc-x);
*(iDst+x+1)=*(iSrc-x+1);
*(iDst+x+2)=*(iSrc-x+2);
}
iSrc+=info.dwEffWidth;
iDst+=info.dwEffWidth;
}
break;
case 8:
for(y=0; y < head.biHeight; y++){
for(x=0; x <= wdt; x++)
*(iDst+x)=*(iSrc-x);
iSrc+=info.dwEffWidth;
iDst+=info.dwEffWidth;
}
break;
default:
for(y=0; y < head.biHeight; y++){
for(x=0; x <= wdt; x++)
imatmp->SetPixelIndex(x,y,GetPixelIndex(wdt-x,y));
}
}
if (bMirrorSelection){
#if CXIMAGE_SUPPORT_SELECTION
imatmp->SelectionMirror();
#endif //CXIMAGE_SUPPORT_SELECTION
}
if (bMirrorAlpha){
#if CXIMAGE_SUPPORT_ALPHA
imatmp->AlphaMirror();
#endif //CXIMAGE_SUPPORT_ALPHA
}
Transfer(*imatmp);
delete imatmp;
return true;
}
////////////////////////////////////////////////////////////////////////////////
#define RBLOCK 64
////////////////////////////////////////////////////////////////////////////////
bool CxImage::RotateLeft(CxImage* iDst)
{
if (!pDib) return false;
long newWidth = GetHeight();
long newHeight = GetWidth();
CxImage imgDest;
imgDest.CopyInfo(*this);
imgDest.Create(newWidth,newHeight,GetBpp(),GetType());
imgDest.SetPalette(GetPalette());
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()) imgDest.AlphaCreate();
#endif
#if CXIMAGE_SUPPORT_SELECTION
if (SelectionIsValid()) imgDest.SelectionCreate();
#endif
long x,x2,y,dlineup;
// Speedy rotate for BW images
if (head.biBitCount == 1) {
BYTE *sbits, *dbits, *dbitsmax, bitpos, *nrow,*srcdisp;
ldiv_t div_r;
BYTE *bsrc = GetBits(), *bdest = imgDest.GetBits();
dbitsmax = bdest + imgDest.head.biSizeImage - 1;
dlineup = 8 * imgDest.info.dwEffWidth - imgDest.head.biWidth;
imgDest.Clear(0);
for (y = 0; y < head.biHeight; y++) {
// Figure out the Column we are going to be copying to
div_r = ldiv(y + dlineup, (long)8);
// set bit pos of src column byte
bitpos = (BYTE)(1 << div_r.rem);
srcdisp = bsrc + y * info.dwEffWidth;
for (x = 0; x < (long)info.dwEffWidth; x++) {
// Get Source Bits
sbits = srcdisp + x;
// Get destination column
nrow = bdest + (x * 8) * imgDest.info.dwEffWidth + imgDest.info.dwEffWidth - 1 - div_r.quot;
for (long z = 0; z < 8; z++) {
// Get Destination Byte
dbits = nrow + z * imgDest.info.dwEffWidth;
if ((dbits < bdest) || (dbits > dbitsmax)) break;
if (*sbits & (128 >> z)) *dbits |= bitpos;
}
}
}//for y
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()) {
for (x = 0; x < newWidth; x++){
x2=newWidth-x-1;
for (y = 0; y < newHeight; y++){
imgDest.AlphaSet(x,y,BlindAlphaGet(y, x2));
}//for y
}//for x
}
#endif //CXIMAGE_SUPPORT_ALPHA
#if CXIMAGE_SUPPORT_SELECTION
if (SelectionIsValid()) {
imgDest.info.rSelectionBox.left = newWidth-info.rSelectionBox.top;
imgDest.info.rSelectionBox.right = newWidth-info.rSelectionBox.bottom;
imgDest.info.rSelectionBox.bottom = info.rSelectionBox.left;
imgDest.info.rSelectionBox.top = info.rSelectionBox.right;
for (x = 0; x < newWidth; x++){
x2=newWidth-x-1;
for (y = 0; y < newHeight; y++){
imgDest.SelectionSet(x,y,BlindSelectionGet(y, x2));
}//for y
}//for x
}
#endif //CXIMAGE_SUPPORT_SELECTION
} else {
//anything other than BW:
//bd, 10. 2004: 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 *srcPtr, *dstPtr; //source and destionation for 24-bit version
int xs, ys; //x-segment and y-segment
for (xs = 0; xs < newWidth; xs+=RBLOCK) { //for all image blocks of RBLOCK*RBLOCK pixels
for (ys = 0; ys < newHeight; ys+=RBLOCK) {
if (head.biBitCount==24) {
//RGB24 optimized pixel access:
for (x = xs; x < min(newWidth, xs+RBLOCK); x++){ //do rotation
info.nProgress = (long)(100*x/newWidth);
x2=newWidth-x-1;
dstPtr = (BYTE*) imgDest.BlindGetPixelPointer(x,ys);
srcPtr = (BYTE*) BlindGetPixelPointer(ys, x2);
for (y = ys; y < min(newHeight, ys+RBLOCK); y++){
//imgDest.SetPixelColor(x, y, GetPixelColor(y, x2));
*(dstPtr) = *(srcPtr);
*(dstPtr+1) = *(srcPtr+1);
*(dstPtr+2) = *(srcPtr+2);
srcPtr += 3;
dstPtr += imgDest.info.dwEffWidth;
}//for y
}//for x
} else {
//anything else than 24bpp (and 1bpp): palette
for (x = xs; x < min(newWidth, xs+RBLOCK); x++){
info.nProgress = (long)(100*x/newWidth); //
x2=newWidth-x-1;
for (y = ys; y < min(newHeight, ys+RBLOCK); y++){
imgDest.SetPixelIndex(x, y, BlindGetPixelIndex(y, x2));
}//for y
}//for x
}//if (version selection)
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()) {
for (x = xs; x < min(newWidth, xs+RBLOCK); x++){
x2=newWidth-x-1;
for (y = ys; y < min(newHeight, ys+RBLOCK); y++){
imgDest.AlphaSet(x,y,BlindAlphaGet(y, x2));
}//for y
}//for x
}//if (alpha channel)
#endif //CXIMAGE_SUPPORT_ALPHA
#if CXIMAGE_SUPPORT_SELECTION
if (SelectionIsValid()) {
imgDest.info.rSelectionBox.left = newWidth-info.rSelectionBox.top;
imgDest.info.rSelectionBox.right = newWidth-info.rSelectionBox.bottom;
imgDest.info.rSelectionBox.bottom = info.rSelectionBox.left;
imgDest.info.rSelectionBox.top = info.rSelectionBox.right;
for (x = xs; x < min(newWidth, xs+RBLOCK); x++){
x2=newWidth-x-1;
for (y = ys; y < min(newHeight, ys+RBLOCK); y++){
imgDest.SelectionSet(x,y,BlindSelectionGet(y, x2));
}//for y
}//for x
}//if (selection)
#endif //CXIMAGE_SUPPORT_SELECTION
}//for ys
}//for xs
}//if
//select the destination
if (iDst) iDst->Transfer(imgDest);
else Transfer(imgDest);
return true;
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::RotateRight(CxImage* iDst)
{
if (!pDib) return false;
long newWidth = GetHeight();
long newHeight = GetWidth();
CxImage imgDest;
imgDest.CopyInfo(*this);
imgDest.Create(newWidth,newHeight,GetBpp(),GetType());
imgDest.SetPalette(GetPalette());
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()) imgDest.AlphaCreate();
#endif
#if CXIMAGE_SUPPORT_SELECTION
if (SelectionIsValid()) imgDest.SelectionCreate();
#endif
long x,y,y2;
// Speedy rotate for BW images
if (head.biBitCount == 1) {
BYTE *sbits, *dbits, *dbitsmax, bitpos, *nrow,*srcdisp;
ldiv_t div_r;
BYTE *bsrc = GetBits(), *bdest = imgDest.GetBits();
dbitsmax = bdest + imgDest.head.biSizeImage - 1;
imgDest.Clear(0);
for (y = 0; y < head.biHeight; y++) {
// Figure out the Column we are going to be copying to
div_r = ldiv(y, (long)8);
// set bit pos of src column byte
bitpos = (BYTE)(128 >> div_r.rem);
srcdisp = bsrc + y * info.dwEffWidth;
for (x = 0; x < (long)info.dwEffWidth; x++) {
// Get Source Bits
sbits = srcdisp + x;
// Get destination column
nrow = bdest + (imgDest.head.biHeight-1-(x*8)) * imgDest.info.dwEffWidth + div_r.quot;
for (long z = 0; z < 8; z++) {
// Get Destination Byte
dbits = nrow - z * imgDest.info.dwEffWidth;
if ((dbits < bdest) || (dbits > dbitsmax)) break;
if (*sbits & (128 >> z)) *dbits |= bitpos;
}
}
}
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()){
for (y = 0; y < newHeight; y++){
y2=newHeight-y-1;
for (x = 0; x < newWidth; x++){
imgDest.AlphaSet(x,y,BlindAlphaGet(y2, x));
}
}
}
#endif //CXIMAGE_SUPPORT_ALPHA
#if CXIMAGE_SUPPORT_SELECTION
if (SelectionIsValid()){
imgDest.info.rSelectionBox.left = info.rSelectionBox.bottom;
imgDest.info.rSelectionBox.right = info.rSelectionBox.top;
imgDest.info.rSelectionBox.bottom = newHeight-info.rSelectionBox.right;
imgDest.info.rSelectionBox.top = newHeight-info.rSelectionBox.left;
for (y = 0; y < newHeight; y++){
y2=newHeight-y-1;
for (x = 0; x < newWidth; x++){
imgDest.SelectionSet(x,y,BlindSelectionGet(y2, x));
}
}
}
#endif //CXIMAGE_SUPPORT_SELECTION
} else {
//anything else but BW
BYTE *srcPtr, *dstPtr; //source and destionation for 24-bit version
int xs, ys; //x-segment and y-segment
for (xs = 0; xs < newWidth; xs+=RBLOCK) {
for (ys = 0; ys < newHeight; ys+=RBLOCK) {
if (head.biBitCount==24) {
//RGB24 optimized pixel access:
for (y = ys; y < min(newHeight, ys+RBLOCK); y++){
info.nProgress = (long)(100*y/newHeight); //
y2=newHeight-y-1;
dstPtr = (BYTE*) imgDest.BlindGetPixelPointer(xs,y);
srcPtr = (BYTE*) BlindGetPixelPointer(y2, xs);
for (x = xs; x < min(newWidth, xs+RBLOCK); x++){
//imgDest.SetPixelColor(x, y, GetPixelColor(y2, x));
*(dstPtr) = *(srcPtr);
*(dstPtr+1) = *(srcPtr+1);
*(dstPtr+2) = *(srcPtr+2);
dstPtr += 3;
srcPtr += info.dwEffWidth;
}//for x
}//for y
} else {
//anything else than BW & RGB24: palette
for (y = ys; y < min(newHeight, ys+RBLOCK); y++){
info.nProgress = (long)(100*y/newHeight); //
y2=newHeight-y-1;
for (x = xs; x < min(newWidth, xs+RBLOCK); x++){
imgDest.SetPixelIndex(x, y, BlindGetPixelIndex(y2, x));
}//for x
}//for y
}//if
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()){
for (y = ys; y < min(newHeight, ys+RBLOCK); y++){
y2=newHeight-y-1;
for (x = xs; x < min(newWidth, xs+RBLOCK); x++){
imgDest.AlphaSet(x,y,BlindAlphaGet(y2, x));
}//for x
}//for y
}//if (has alpha)
#endif //CXIMAGE_SUPPORT_ALPHA
#if CXIMAGE_SUPPORT_SELECTION
if (SelectionIsValid()){
imgDest.info.rSelectionBox.left = info.rSelectionBox.bottom;
imgDest.info.rSelectionBox.right = info.rSelectionBox.top;
imgDest.info.rSelectionBox.bottom = newHeight-info.rSelectionBox.right;
imgDest.info.rSelectionBox.top = newHeight-info.rSelectionBox.left;
for (y = ys; y < min(newHeight, ys+RBLOCK); y++){
y2=newHeight-y-1;
for (x = xs; x < min(newWidth, xs+RBLOCK); x++){
imgDest.SelectionSet(x,y,BlindSelectionGet(y2, x));
}//for x
}//for y
}//if (has alpha)
#endif //CXIMAGE_SUPPORT_SELECTION
}//for ys
}//for xs
}//if
//select the destination
if (iDst) iDst->Transfer(imgDest);
else Transfer(imgDest);
return true;
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::Negative()
{
if (!pDib) return false;
if (head.biBitCount<=8){
if (IsGrayScale()){ //GRAYSCALE, selection
if (pSelection){
for(long y=info.rSelectionBox.bottom; y invert transparent color too
info.nBkgndColor.rgbBlue = (BYTE)(255-info.nBkgndColor.rgbBlue);
info.nBkgndColor.rgbGreen = (BYTE)(255-info.nBkgndColor.rgbGreen);
info.nBkgndColor.rgbRed = (BYTE)(255-info.nBkgndColor.rgbRed);
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
#endif //CXIMAGE_SUPPORT_BASICTRANSFORMATIONS
////////////////////////////////////////////////////////////////////////////////
#if CXIMAGE_SUPPORT_TRANSFORMATION
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
bool CxImage::Rotate(float angle, CxImage* iDst)
{
if (!pDib) return false;
// Copyright (c) 1996-1998 Ulrich von Zadow
// Negative the angle, because the y-axis is negative.
double ang = -angle*acos((float)0)/90;
int newWidth, newHeight;
int nWidth = GetWidth();
int nHeight= GetHeight();
double cos_angle = cos(ang);
double sin_angle = sin(ang);
// Calculate the size of the new bitmap
POINT p1={0,0};
POINT p2={nWidth,0};
POINT p3={0,nHeight};
POINT p4={nWidth,nHeight};
CxPoint2 newP1,newP2,newP3,newP4, leftTop, rightTop, leftBottom, rightBottom;
newP1.x = (float)p1.x;
newP1.y = (float)p1.y;
newP2.x = (float)(p2.x*cos_angle - p2.y*sin_angle);
newP2.y = (float)(p2.x*sin_angle + p2.y*cos_angle);
newP3.x = (float)(p3.x*cos_angle - p3.y*sin_angle);
newP3.y = (float)(p3.x*sin_angle + p3.y*cos_angle);
newP4.x = (float)(p4.x*cos_angle - p4.y*sin_angle);
newP4.y = (float)(p4.x*sin_angle + p4.y*cos_angle);
leftTop.x = min(min(newP1.x,newP2.x),min(newP3.x,newP4.x));
leftTop.y = min(min(newP1.y,newP2.y),min(newP3.y,newP4.y));
rightBottom.x = max(max(newP1.x,newP2.x),max(newP3.x,newP4.x));
rightBottom.y = max(max(newP1.y,newP2.y),max(newP3.y,newP4.y));
leftBottom.x = leftTop.x;
leftBottom.y = rightBottom.y;
rightTop.x = rightBottom.x;
rightTop.y = leftTop.y;
newWidth = (int) floor(0.5f + rightTop.x - leftTop.x);
newHeight= (int) floor(0.5f + leftBottom.y - leftTop.y);
CxImage imgDest;
imgDest.CopyInfo(*this);
imgDest.Create(newWidth,newHeight,GetBpp(),GetType());
imgDest.SetPalette(GetPalette());
#if CXIMAGE_SUPPORT_ALPHA
if(AlphaIsValid()) //MTA: Fix for rotation problem when the image has an alpha channel
{
imgDest.AlphaCreate();
imgDest.AlphaClear();
}
#endif //CXIMAGE_SUPPORT_ALPHA
int x,y,newX,newY,oldX,oldY;
if (head.biClrUsed==0){ //RGB
for (y = (int)leftTop.y, newY = 0; y<=(int)leftBottom.y; y++,newY++){
info.nProgress = (long)(100*newY/newHeight);
if (info.nEscape) break;
for (x = (int)leftTop.x, newX = 0; x<=(int)rightTop.x; x++,newX++){
oldX = (long)(x*cos_angle + y*sin_angle + 0.5);
oldY = (long)(y*cos_angle - x*sin_angle + 0.5);
imgDest.SetPixelColor(newX,newY,GetPixelColor(oldX,oldY));
#if CXIMAGE_SUPPORT_ALPHA
imgDest.AlphaSet(newX,newY,AlphaGet(oldX,oldY)); //MTA: copy the alpha value
#endif //CXIMAGE_SUPPORT_ALPHA
}
}
} else { //PALETTE
for (y = (int)leftTop.y, newY = 0; y<=(int)leftBottom.y; y++,newY++){
info.nProgress = (long)(100*newY/newHeight);
if (info.nEscape) break;
for (x = (int)leftTop.x, newX = 0; x<=(int)rightTop.x; x++,newX++){
oldX = (long)(x*cos_angle + y*sin_angle + 0.5);
oldY = (long)(y*cos_angle - x*sin_angle + 0.5);
imgDest.SetPixelIndex(newX,newY,GetPixelIndex(oldX,oldY));
#if CXIMAGE_SUPPORT_ALPHA
imgDest.AlphaSet(newX,newY,AlphaGet(oldX,oldY)); //MTA: copy the alpha value
#endif //CXIMAGE_SUPPORT_ALPHA
}
}
}
//select the destination
if (iDst) iDst->Transfer(imgDest);
else Transfer(imgDest);
return true;
}
////////////////////////////////////////////////////////////////////////////////
/**
* Rotates image around it's center.
* Method can use interpolation with paletted images, but does not change pallete, so results vary.
* (If you have only four colours in a palette, there's not much room for interpolation.)
*
* \param angle - angle in degrees (positive values rotate clockwise)
* \param *iDst - destination image (if null, this image is changed)
* \param inMethod - interpolation method used
* (IM_NEAREST_NEIGHBOUR produces aliasing (fast), IM_BILINEAR softens picture a bit (slower)
* IM_SHARPBICUBIC is slower and produces some halos...)
* \param ofMethod - overflow method (how to choose colour of pixels that have no source)
* \param replColor - replacement colour to use (OM_COLOR, OM_BACKGROUND with no background colour...)
* \param optimizeRightAngles - call faster methods for 90, 180, and 270 degree rotations. Faster methods
* are called for angles, where error (in location of corner pixels) is less
* than 0.25 pixels.
* \param bKeepOriginalSize - rotates the image without resizing.
*
* \author ***bd*** 2.2004
*/
bool CxImage::Rotate2(float angle,
CxImage *iDst,
InterpolationMethod inMethod,
OverflowMethod ofMethod,
RGBQUAD *replColor,
bool const optimizeRightAngles,
bool const bKeepOriginalSize)
{
if (!pDib) return false; //no dib no go
double ang = -angle*acos(0.0f)/90.0f; //convert angle to radians and invert (positive angle performs clockwise rotation)
float cos_angle = (float) cos(ang); //these two are needed later (to rotate)
float sin_angle = (float) sin(ang);
//Calculate the size of the new bitmap (rotate corners of image)
CxPoint2 p[4]; //original corners of the image
p[0]=CxPoint2(-0.5f,-0.5f);
p[1]=CxPoint2(GetWidth()-0.5f,-0.5f);
p[2]=CxPoint2(-0.5f,GetHeight()-0.5f);
p[3]=CxPoint2(GetWidth()-0.5f,GetHeight()-0.5f);
CxPoint2 newp[4]; //rotated positions of corners
//(rotate corners)
if (bKeepOriginalSize){
for (int i=0; i<4; i++) {
newp[i].x = p[i].x;
newp[i].y = p[i].y;
}//for
} else {
for (int i=0; i<4; i++) {
newp[i].x = (p[i].x*cos_angle - p[i].y*sin_angle);
newp[i].y = (p[i].x*sin_angle + p[i].y*cos_angle);
}//for i
if (optimizeRightAngles) {
//For rotations of 90, -90 or 180 or 0 degrees, call faster routines
if (newp[3].Distance(CxPoint2(GetHeight()-0.5f, 0.5f-GetWidth())) < 0.25)
//rotation right for circa 90 degrees (diagonal pixels less than 0.25 pixel away from 90 degree rotation destination)
return RotateRight(iDst);
if (newp[3].Distance(CxPoint2(0.5f-GetHeight(), -0.5f+GetWidth())) < 0.25)
//rotation left for ~90 degrees
return RotateLeft(iDst);
if (newp[3].Distance(CxPoint2(0.5f-GetWidth(), 0.5f-GetHeight())) < 0.25)
//rotation left for ~180 degrees
return Rotate180(iDst);
if (newp[3].Distance(p[3]) < 0.25) {
//rotation not significant
if (iDst) iDst->Copy(*this); //copy image to iDst, if required
return true; //and we're done
}//if
}//if
}//if
//(read new dimensions from location of corners)
float minx = (float) min(min(newp[0].x,newp[1].x),min(newp[2].x,newp[3].x));
float miny = (float) min(min(newp[0].y,newp[1].y),min(newp[2].y,newp[3].y));
float maxx = (float) max(max(newp[0].x,newp[1].x),max(newp[2].x,newp[3].x));
float maxy = (float) max(max(newp[0].y,newp[1].y),max(newp[2].y,newp[3].y));
int newWidth = (int) floor(maxx-minx+0.5f);
int newHeight= (int) floor(maxy-miny+0.5f);
float ssx=((maxx+minx)- ((float) newWidth-1))/2.0f; //start for x
float ssy=((maxy+miny)- ((float) newHeight-1))/2.0f; //start for y
float newxcenteroffset = 0.5f * newWidth;
float newycenteroffset = 0.5f * newHeight;
if (bKeepOriginalSize){
ssx -= 0.5f * GetWidth();
ssy -= 0.5f * GetHeight();
}
//create destination image
CxImage imgDest;
imgDest.CopyInfo(*this);
imgDest.Create(newWidth,newHeight,GetBpp(),GetType());
imgDest.SetPalette(GetPalette());
#if CXIMAGE_SUPPORT_ALPHA
if(AlphaIsValid()) imgDest.AlphaCreate(); //MTA: Fix for rotation problem when the image has an alpha channel
#endif //CXIMAGE_SUPPORT_ALPHA
RGBQUAD rgb; //pixel colour
RGBQUAD rc;
if (replColor!=0)
rc=*replColor;
else {
rc.rgbRed=255; rc.rgbGreen=255; rc.rgbBlue=255; rc.rgbReserved=0;
}//if
float x,y; //destination location (float, with proper offset)
float origx, origy; //origin location
int destx, desty; //destination location
y=ssy; //initialize y
if (!IsIndexed()){ //RGB24
//optimized RGB24 implementation (direct write to destination):
BYTE *pxptr;
#if CXIMAGE_SUPPORT_ALPHA
BYTE *pxptra=0;
#endif //CXIMAGE_SUPPORT_ALPHA
for (desty=0; destyTransfer(imgDest);
else Transfer(imgDest);
return true;
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::Rotate180(CxImage* iDst)
{
if (!pDib) return false;
long wid = GetWidth();
long ht = GetHeight();
CxImage imgDest;
imgDest.CopyInfo(*this);
imgDest.Create(wid,ht,GetBpp(),GetType());
imgDest.SetPalette(GetPalette());
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()) imgDest.AlphaCreate();
#endif //CXIMAGE_SUPPORT_ALPHA
long x,y,y2;
for (y = 0; y < ht; y++){
info.nProgress = (long)(100*y/ht); //
y2=ht-y-1;
for (x = 0; x < wid; x++){
if(head.biClrUsed==0)//RGB
imgDest.SetPixelColor(wid-x-1, y2, BlindGetPixelColor(x, y));
else //PALETTE
imgDest.SetPixelIndex(wid-x-1, y2, BlindGetPixelIndex(x, y));
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()) imgDest.AlphaSet(wid-x-1, y2,BlindAlphaGet(x, y));
#endif //CXIMAGE_SUPPORT_ALPHA
}
}
//select the destination
if (iDst) iDst->Transfer(imgDest);
else Transfer(imgDest);
return true;
}
////////////////////////////////////////////////////////////////////////////////
/**
* Resizes the image. mode can be 0 for slow (bilinear) method ,
* 1 for fast (nearest pixel) method, or 2 for accurate (bicubic spline interpolation) method.
* The function is faster with 24 and 1 bpp images, slow for 4 bpp images and slowest for 8 bpp images.
*/
bool CxImage::Resample(long newx, long newy, int mode, CxImage* iDst)
{
if (newx==0 || newy==0) return false;
if (head.biWidth==newx && head.biHeight==newy){
if (iDst) iDst->Copy(*this);
return true;
}
float xScale, yScale, fX, fY;
xScale = (float)head.biWidth / (float)newx;
yScale = (float)head.biHeight / (float)newy;
CxImage newImage;
newImage.CopyInfo(*this);
newImage.Create(newx,newy,head.biBitCount,GetType());
newImage.SetPalette(GetPalette());
if (!newImage.IsValid()){
strcpy(info.szLastError,newImage.GetLastError());
return false;
}
switch (mode) {
case 1: // nearest pixel
{
for(long y=0; y=head.biHeight) yy = head.biHeight-1;
for(int n=-1; n<3; n++) {
r2 = r1 * KernelBSpline(b - (float)n);
xx = i_x+n;
if (xx<0) xx=0;
if (xx>=head.biWidth) xx=head.biWidth-1;
if (head.biClrUsed){
rgb = GetPixelColor(xx,yy);
} else {
iDst = info.pImage + yy*info.dwEffWidth + xx*3;
rgb.rgbBlue = *iDst++;
rgb.rgbGreen= *iDst++;
rgb.rgbRed = *iDst;
}
rr += rgb.rgbRed * r2;
gg += rgb.rgbGreen * r2;
bb += rgb.rgbBlue * r2;
}
}
if (head.biClrUsed)
newImage.SetPixelColor(x,y,RGB(rr,gg,bb));
else {
iDst = newImage.info.pImage + y*newImage.info.dwEffWidth + x*3;
*iDst++ = (BYTE)bb;
*iDst++ = (BYTE)gg;
*iDst = (BYTE)rr;
}
}
}
break;
}
default: // bilinear interpolation
if (!(head.biWidth>newx && head.biHeight>newy && head.biBitCount==24)) {
// (c) 1999 Steve McMahon (steve@dogma.demon.co.uk)
long ifX, ifY, ifX1, ifY1, xmax, ymax;
float ir1, ir2, ig1, ig2, ib1, ib2, dx, dy;
BYTE r,g,b;
RGBQUAD rgb1, rgb2, rgb3, rgb4;
xmax = head.biWidth-1;
ymax = head.biHeight-1;
for(long y=0; y
const long ACCURACY = 1000;
long i,j; // index for faValue
long x,y; // coordinates in source image
BYTE* pSource;
BYTE* pDest = newImage.info.pImage;
long* naAccu = new long[3 * newx + 3];
long* naCarry = new long[3 * newx + 3];
long* naTemp;
long nWeightX,nWeightY;
float fEndX;
long nScale = (long)(ACCURACY * xScale * yScale);
memset(naAccu, 0, sizeof(long) * 3 * newx);
memset(naCarry, 0, sizeof(long) * 3 * newx);
int u, v = 0; // coordinates in dest image
float fEndY = yScale - 1.0f;
for (y = 0; y < head.biHeight; y++){
info.nProgress = (long)(100*y/head.biHeight); //
if (info.nEscape) break;
pSource = info.pImage + y * info.dwEffWidth;
u = i = 0;
fEndX = xScale - 1.0f;
if ((float)y < fEndY) { // complete source row goes into dest row
for (x = 0; x < head.biWidth; x++){
if ((float)x < fEndX){ // complete source pixel goes into dest pixel
for (j = 0; j < 3; j++) naAccu[i + j] += (*pSource++) * ACCURACY;
} else { // source pixel is splitted for 2 dest pixels
nWeightX = (long)(((float)x - fEndX) * ACCURACY);
for (j = 0; j < 3; j++){
naAccu[i] += (ACCURACY - nWeightX) * (*pSource);
naAccu[3 + i++] += nWeightX * (*pSource++);
}
fEndX += xScale;
u++;
}
}
} else { // source row is splitted for 2 dest rows
nWeightY = (long)(((float)y - fEndY) * ACCURACY);
for (x = 0; x < head.biWidth; x++){
if ((float)x < fEndX){ // complete source pixel goes into 2 pixel
for (j = 0; j < 3; j++){
naAccu[i + j] += ((ACCURACY - nWeightY) * (*pSource));
naCarry[i + j] += nWeightY * (*pSource++);
}
} else { // source pixel is splitted for 4 dest pixels
nWeightX = (int)(((float)x - fEndX) * ACCURACY);
for (j = 0; j < 3; j++) {
naAccu[i] += ((ACCURACY - nWeightY) * (ACCURACY - nWeightX)) * (*pSource) / ACCURACY;
*pDest++ = (BYTE)(naAccu[i] / nScale);
naCarry[i] += (nWeightY * (ACCURACY - nWeightX) * (*pSource)) / ACCURACY;
naAccu[i + 3] += ((ACCURACY - nWeightY) * nWeightX * (*pSource)) / ACCURACY;
naCarry[i + 3] = (nWeightY * nWeightX * (*pSource)) / ACCURACY;
i++;
pSource++;
}
fEndX += xScale;
u++;
}
}
if (u < newx){ // possibly not completed due to rounding errors
for (j = 0; j < 3; j++) *pDest++ = (BYTE)(naAccu[i++] / nScale);
}
naTemp = naCarry;
naCarry = naAccu;
naAccu = naTemp;
memset(naCarry, 0, sizeof(int) * 3); // need only to set first pixel zero
pDest = newImage.info.pImage + (++v * newImage.info.dwEffWidth);
fEndY += yScale;
}
}
if (v < newy){ // possibly not completed due to rounding errors
for (i = 0; i < 3 * newx; i++) *pDest++ = (BYTE)(naAccu[i] / nScale);
}
delete [] naAccu;
delete [] naCarry;
}
}
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()){
newImage.AlphaCreate();
for(long y=0; yTransfer(newImage);
else Transfer(newImage);
return true;
}
////////////////////////////////////////////////////////////////////////////////
/**
* New simpler resample. Adds new interpolation methods and simplifies code (using GetPixelColorInterpolated
* and GetAreaColorInterpolated). It also (unlike old method) interpolates alpha layer.
*
* \param newx, newy - size of resampled image
* \param inMethod - interpolation method to use (see comments at GetPixelColorInterpolated)
* If image size is being reduced, averaging is used instead (or simultaneously with) inMethod.
* \param ofMethod - what to replace outside pixels by (only significant for bordering pixels of enlarged image)
* \param iDst - pointer to destination CxImage or NULL.
* \param disableAveraging - force no averaging when shrinking images (Produces aliasing.
* You probably just want to leave this off...)
*
* \author ***bd*** 2.2004
*/
bool CxImage::Resample2(
long newx, long newy,
InterpolationMethod const inMethod,
OverflowMethod const ofMethod,
CxImage* const iDst,
bool const disableAveraging)
{
if (newx<=0 || newy<=0 || !pDib) return false;
if (head.biWidth==newx && head.biHeight==newy) {
//image already correct size (just copy and return)
if (iDst) iDst->Copy(*this);
return true;
}//if
//calculate scale of new image (less than 1 for enlarge)
float xScale, yScale;
xScale = (float)head.biWidth / (float)newx;
yScale = (float)head.biHeight / (float)newy;
//create temporary destination image
CxImage newImage;
newImage.CopyInfo(*this);
newImage.Create(newx,newy,head.biBitCount,GetType());
newImage.SetPalette(GetPalette());
if (!newImage.IsValid()){
strcpy(info.szLastError,newImage.GetLastError());
return false;
}
//and alpha channel if required
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()) newImage.AlphaCreate();
BYTE *pxptra = 0; // destination alpha data
#endif
float sX, sY; //source location
long dX,dY; //destination pixel (int value)
if ((xScale<=1 && yScale<=1) || disableAveraging) {
//image is being enlarged (or interpolation on demand)
if (!IsIndexed()) {
//RGB24 image (optimized version with direct writes)
RGBQUAD q; //pixel colour
BYTE *pxptr; //pointer to destination pixel
for(dY=0; dYTransfer(newImage);
else
Transfer(newImage);
return true;
}
////////////////////////////////////////////////////////////////////////////////
/**
* Reduces the number of bits per pixel to nbit (1, 4 or 8).
* ppal points to a valid palette for the final image; if not supplied the function will use a standard palette.
* ppal is not necessary for reduction to 1 bpp.
*/
bool CxImage::DecreaseBpp(DWORD nbit, bool errordiffusion, RGBQUAD* ppal, DWORD clrimportant)
{
if (!pDib) return false;
if (head.biBitCount < nbit){
strcpy(info.szLastError,"DecreaseBpp: target BPP greater than source BPP");
return false;
}
if (head.biBitCount == nbit){
if (clrimportant==0) return true;
if (head.biClrImportant && (head.biClrImportant4) return false;
CxImage tmp;
tmp.CopyInfo(*this);
tmp.Create(head.biWidth,head.biHeight,4,info.dwType);
tmp.SetPalette(GetPalette(),GetNumColors());
if (!tmp.IsValid()){
strcpy(info.szLastError,tmp.GetLastError());
return false;
}
#if CXIMAGE_SUPPORT_SELECTION
tmp.SelectionCopy(*this);
#endif //CXIMAGE_SUPPORT_SELECTION
#if CXIMAGE_SUPPORT_ALPHA
tmp.AlphaCopy(*this);
#endif //CXIMAGE_SUPPORT_ALPHA
for (long y=0;y8) return false;
CxImage tmp;
tmp.CopyInfo(*this);
tmp.Create(head.biWidth,head.biHeight,8,info.dwType);
tmp.SetPalette(GetPalette(),GetNumColors());
if (!tmp.IsValid()){
strcpy(info.szLastError,tmp.GetLastError());
return false;
}
#if CXIMAGE_SUPPORT_SELECTION
tmp.SelectionCopy(*this);
#endif //CXIMAGE_SUPPORT_SELECTION
#if CXIMAGE_SUPPORT_ALPHA
tmp.AlphaCopy(*this);
#endif //CXIMAGE_SUPPORT_ALPHA
for (long y=0;y24) return false;
CxImage tmp;
tmp.CopyInfo(*this);
tmp.Create(head.biWidth,head.biHeight,24,info.dwType);
if (!tmp.IsValid()){
strcpy(info.szLastError,tmp.GetLastError());
return false;
}
if (info.nBkgndIndex>=0) //translate transparency
tmp.info.nBkgndColor=GetPaletteColor((BYTE)info.nBkgndIndex);
#if CXIMAGE_SUPPORT_SELECTION
tmp.SelectionCopy(*this);
#endif //CXIMAGE_SUPPORT_SELECTION
#if CXIMAGE_SUPPORT_ALPHA
tmp.AlphaCopy(*this);
if (AlphaPaletteIsValid() && !AlphaIsValid()) tmp.AlphaCreate();
#endif //CXIMAGE_SUPPORT_ALPHA
for (long y=0;y 128) {
tmp.SetPixelIndex(x, y, 1);
error = level - 255;
} else {
tmp.SetPixelIndex(x, y, 0);
error = level;
}
nlevel = GetPixelIndex(x + 1, y) + (error * 8) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + 1, y, level);
nlevel = GetPixelIndex(x + 2, y) + (error * 4) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + 2, y, level);
int i;
for (i = -2; i < 3; i++) {
switch (i) {
case -2:
coeff = 2;
break;
case -1:
coeff = 4;
break;
case 0:
coeff = 8;
break;
case 1:
coeff = 4;
break;
case 2:
coeff = 2;
break;
}
nlevel = GetPixelIndex(x + i, y + 1) + (error * coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + i, y + 1, level);
}
}
}
break;
}
case 3:
{
//Stucki error diffusion (Thanks to Franco Gerevini)
int TotalCoeffSum = 42;
long error, nlevel, coeff=1;
BYTE level;
for (long y = 0; y < head.biHeight; y++) {
info.nProgress = (long)(100 * y / head.biHeight);
if (info.nEscape)
break;
for (long x = 0; x < head.biWidth; x++) {
level = BlindGetPixelIndex(x, y);
if (level > 128) {
tmp.SetPixelIndex(x, y, 1);
error = level - 255;
} else {
tmp.SetPixelIndex(x, y, 0);
error = level;
}
nlevel = GetPixelIndex(x + 1, y) + (error * 8) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + 1, y, level);
nlevel = GetPixelIndex(x + 2, y) + (error * 4) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + 2, y, level);
int i;
for (i = -2; i < 3; i++) {
switch (i) {
case -2:
coeff = 2;
break;
case -1:
coeff = 4;
break;
case 0:
coeff = 8;
break;
case 1:
coeff = 4;
break;
case 2:
coeff = 2;
break;
}
nlevel = GetPixelIndex(x + i, y + 1) + (error * coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + i, y + 1, level);
}
for (i = -2; i < 3; i++) {
switch (i) {
case -2:
coeff = 1;
break;
case -1:
coeff = 2;
break;
case 0:
coeff = 4;
break;
case 1:
coeff = 2;
break;
case 2:
coeff = 1;
break;
}
nlevel = GetPixelIndex(x + i, y + 2) + (error * coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + i, y + 2, level);
}
}
}
break;
}
case 4:
{
//Jarvis, Judice and Ninke error diffusion (Thanks to Franco Gerevini)
int TotalCoeffSum = 48;
long error, nlevel, coeff=1;
BYTE level;
for (long y = 0; y < head.biHeight; y++) {
info.nProgress = (long)(100 * y / head.biHeight);
if (info.nEscape)
break;
for (long x = 0; x < head.biWidth; x++) {
level = BlindGetPixelIndex(x, y);
if (level > 128) {
tmp.SetPixelIndex(x, y, 1);
error = level - 255;
} else {
tmp.SetPixelIndex(x, y, 0);
error = level;
}
nlevel = GetPixelIndex(x + 1, y) + (error * 7) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + 1, y, level);
nlevel = GetPixelIndex(x + 2, y) + (error * 5) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + 2, y, level);
int i;
for (i = -2; i < 3; i++) {
switch (i) {
case -2:
coeff = 3;
break;
case -1:
coeff = 5;
break;
case 0:
coeff = 7;
break;
case 1:
coeff = 5;
break;
case 2:
coeff = 3;
break;
}
nlevel = GetPixelIndex(x + i, y + 1) + (error * coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + i, y + 1, level);
}
for (i = -2; i < 3; i++) {
switch (i) {
case -2:
coeff = 1;
break;
case -1:
coeff = 3;
break;
case 0:
coeff = 5;
break;
case 1:
coeff = 3;
break;
case 2:
coeff = 1;
break;
}
nlevel = GetPixelIndex(x + i, y + 2) + (error * coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + i, y + 2, level);
}
}
}
break;
}
case 5:
{
//Sierra error diffusion (Thanks to Franco Gerevini)
int TotalCoeffSum = 32;
long error, nlevel, coeff=1;
BYTE level;
for (long y = 0; y < head.biHeight; y++) {
info.nProgress = (long)(100 * y / head.biHeight);
if (info.nEscape)
break;
for (long x = 0; x < head.biWidth; x++) {
level = BlindGetPixelIndex(x, y);
if (level > 128) {
tmp.SetPixelIndex(x, y, 1);
error = level - 255;
} else {
tmp.SetPixelIndex(x, y, 0);
error = level;
}
nlevel = GetPixelIndex(x + 1, y) + (error * 5) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + 1, y, level);
nlevel = GetPixelIndex(x + 2, y) + (error * 3) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + 2, y, level);
int i;
for (i = -2; i < 3; i++) {
switch (i) {
case -2:
coeff = 2;
break;
case -1:
coeff = 4;
break;
case 0:
coeff = 5;
break;
case 1:
coeff = 4;
break;
case 2:
coeff = 2;
break;
}
nlevel = GetPixelIndex(x + i, y + 1) + (error * coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + i, y + 1, level);
}
for (i = -1; i < 2; i++) {
switch (i) {
case -1:
coeff = 2;
break;
case 0:
coeff = 3;
break;
case 1:
coeff = 2;
break;
}
nlevel = GetPixelIndex(x + i, y + 2) + (error * coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(x + i, y + 2, level);
}
}
}
break;
}
case 6:
{
//Stevenson and Arce error diffusion (Thanks to Franco Gerevini)
int TotalCoeffSum = 200;
long error, nlevel;
BYTE level;
for (long y = 0; y < head.biHeight; y++) {
info.nProgress = (long)(100 * y / head.biHeight);
if (info.nEscape)
break;
for (long x = 0; x < head.biWidth; x++) {
level = BlindGetPixelIndex(x, y);
if (level > 128) {
tmp.SetPixelIndex(x, y, 1);
error = level - 255;
} else {
tmp.SetPixelIndex(x, y, 0);
error = level;
}
int tmp_index_x = x + 2;
int tmp_index_y = y;
int tmp_coeff = 32;
nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(tmp_index_x, tmp_index_y, level);
tmp_index_x = x - 3;
tmp_index_y = y + 1;
tmp_coeff = 12;
nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(tmp_index_x, tmp_index_y, level);
tmp_index_x = x - 1;
tmp_coeff = 26;
nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(tmp_index_x, tmp_index_y, level);
tmp_index_x = x + 1;
tmp_coeff = 30;
nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(tmp_index_x, tmp_index_y, level);
tmp_index_x = x + 3;
tmp_coeff = 16;
nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(tmp_index_x, tmp_index_y, level);
tmp_index_x = x - 2;
tmp_index_y = y + 2;
tmp_coeff = 12;
nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(tmp_index_x, tmp_index_y, level);
tmp_index_x = x;
tmp_coeff = 26;
nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(tmp_index_x, tmp_index_y, level);
tmp_index_x = x + 2;
tmp_coeff = 12;
nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(tmp_index_x, tmp_index_y, level);
tmp_index_x = x - 3;
tmp_index_y = y + 3;
tmp_coeff = 5;
nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(tmp_index_x, tmp_index_y, level);
tmp_index_x = x - 1;
tmp_coeff = 12;
nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(tmp_index_x, tmp_index_y, level);
tmp_index_x = x + 1;
tmp_coeff = 12;
nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(tmp_index_x, tmp_index_y, level);
tmp_index_x = x + 3;
tmp_coeff = 5;
nlevel = GetPixelIndex(tmp_index_x, tmp_index_y) + (error * tmp_coeff) / TotalCoeffSum;
level = (BYTE)min(255, max(0, (int)nlevel));
SetPixelIndex(tmp_index_x, tmp_index_y, level);
}
}
break;
}
case 7:
{
// Bayer ordered dither
int order = 4;
//create Bayer matrix
if (order>4) order = 4;
int size = (1 << (2*order));
BYTE* Bmatrix = (BYTE*) malloc(size * sizeof(BYTE));
for(int i = 0; i < size; i++) {
int n = order;
int x = i / n;
int y = i % n;
int dither = 0;
while (n-- > 0){
dither = (((dither<<1)|((x&1) ^ (y&1)))<<1) | (y&1);
x >>= 1;
y >>= 1;
}
Bmatrix[i] = (BYTE)(dither);
}
int scale = max(0,(8-2*order));
int level;
for (long y=0;y> scale;
if(level > Bmatrix[ (x % order) + order * (y % order) ]){
tmp.SetPixelIndex(x,y,1);
} else {
tmp.SetPixelIndex(x,y,0);
}
}
}
free(Bmatrix);
break;
}
default:
{
// Floyd-Steinberg error diffusion (Thanks to Steve McMahon)
long error,nlevel,coeff=1;
BYTE level;
for (long y=0;y 128){
tmp.SetPixelIndex(x,y,1);
error = level-255;
} else {
tmp.SetPixelIndex(x,y,0);
error = level;
}
nlevel = GetPixelIndex(x+1,y) + (error * 7)/16;
level = (BYTE)min(255,max(0,(int)nlevel));
SetPixelIndex(x+1,y,level);
for(int i=-1; i<2; i++){
switch(i){
case -1:
coeff=3; break;
case 0:
coeff=5; break;
case 1:
coeff=1; break;
}
nlevel = GetPixelIndex(x+i,y+1) + (error * coeff)/16;
level = (BYTE)min(255,max(0,(int)nlevel));
SetPixelIndex(x+i,y+1,level);
}
}
}
}
}
tmp.SetPaletteColor(0,0,0,0);
tmp.SetPaletteColor(1,255,255,255);
Transfer(tmp);
return true;
}
////////////////////////////////////////////////////////////////////////////////
/**
* CropRotatedRectangle
* \param topx,topy : topmost and leftmost point of the rectangle
(topmost, and if there are 2 topmost points, the left one)
* \param width : size of the right hand side of rect, from (topx,topy) roundwalking clockwise
* \param height : size of the left hand side of rect, from (topx,topy) roundwalking clockwise
* \param angle : angle of the right hand side of rect, from (topx,topy)
* \param iDst : pointer to destination image (if 0, this image is modified)
* \author [VATI]
*/
bool CxImage::CropRotatedRectangle( long topx, long topy, long width, long height, float angle, CxImage* iDst)
{
if (!pDib) return false;
long startx,starty,endx,endy;
double cos_angle = cos(angle/*/57.295779513082320877*/);
double sin_angle = sin(angle/*/57.295779513082320877*/);
// if there is nothing special, call the original Crop():
if ( fabs(angle)<0.0002 )
return Crop( topx, topy, topx+width, topy+height, iDst);
startx = min(topx, topx - (long)(sin_angle*(double)height));
endx = topx + (long)(cos_angle*(double)width);
endy = topy + (long)(cos_angle*(double)height + sin_angle*(double)width);
// check: corners of the rectangle must be inside
if ( IsInside( startx, topy )==false ||
IsInside( endx, endy ) == false )
return false;
// first crop to bounding rectangle
CxImage tmp(*this, true, false, true);
// tmp.Copy(*this, true, false, true);
if (!tmp.IsValid()){
strcpy(info.szLastError,tmp.GetLastError());
return false;
}
if (!tmp.Crop( startx, topy, endx, endy)){
strcpy(info.szLastError,tmp.GetLastError());
return false;
}
// the midpoint of the image now became the same as the midpoint of the rectangle
// rotate new image with minus angle amount
if ( false == tmp.Rotate( (float)(-angle*57.295779513082320877) ) ) // Rotate expects angle in degrees
return false;
// crop rotated image to the original selection rectangle
endx = (tmp.head.biWidth+width)/2;
startx = (tmp.head.biWidth-width)/2;
starty = (tmp.head.biHeight+height)/2;
endy = (tmp.head.biHeight-height)/2;
if ( false == tmp.Crop( startx, starty, endx, endy ) )
return false;
if (iDst) iDst->Transfer(tmp);
else Transfer(tmp);
return true;
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::Crop(const RECT& rect, CxImage* iDst)
{
return Crop(rect.left, rect.top, rect.right, rect.bottom, iDst);
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::Crop(long left, long top, long right, long bottom, CxImage* iDst)
{
if (!pDib) return false;
long startx = max(0L,min(left,head.biWidth));
long endx = max(0L,min(right,head.biWidth));
long starty = head.biHeight - max(0L,min(top,head.biHeight));
long endy = head.biHeight - max(0L,min(bottom,head.biHeight));
if (startx==endx || starty==endy) return false;
if (startx>endx) {long tmp=startx; startx=endx; endx=tmp;}
if (starty>endy) {long tmp=starty; starty=endy; endy=tmp;}
CxImage tmp(endx-startx,endy-starty,head.biBitCount,info.dwType);
if (!tmp.IsValid()){
strcpy(info.szLastError,tmp.GetLastError());
return false;
}
tmp.SetPalette(GetPalette(),head.biClrUsed);
tmp.info.nBkgndIndex = info.nBkgndIndex;
tmp.info.nBkgndColor = info.nBkgndColor;
switch (head.biBitCount) {
case 1:
case 4:
{
for(long y=starty, yd=0; y
for(long x=startx, xd=0; x> 3;
BYTE* pDest = tmp.info.pImage;
BYTE* pSrc = info.pImage + starty * info.dwEffWidth + (startx*head.biBitCount >> 3);
for(long y=starty; y
memcpy(pDest,pSrc,linelen);
pDest+=tmp.info.dwEffWidth;
pSrc+=info.dwEffWidth;
}
}
}
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()){ //
tmp.AlphaCreate();
if (!tmp.AlphaIsValid()) return false;
BYTE* pDest = tmp.pAlpha;
BYTE* pSrc = pAlpha + startx + starty*head.biWidth;
for (long y=starty; yTransfer(tmp);
else Transfer(tmp);
return true;
}
////////////////////////////////////////////////////////////////////////////////
/**
* \param xgain, ygain : can be from 0 to 1.
* \param xpivot, ypivot : is the center of the transformation.
* \param bEnableInterpolation : if true, enables bilinear interpolation.
* \return true if everything is ok
*/
bool CxImage::Skew(float xgain, float ygain, long xpivot, long ypivot, bool bEnableInterpolation)
{
if (!pDib) return false;
float nx,ny;
CxImage tmp(*this);
if (!tmp.IsValid()){
strcpy(info.szLastError,tmp.GetLastError());
return false;
}
long xmin,xmax,ymin,ymax;
if (pSelection){
xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
} else {
xmin = ymin = 0;
xmax = head.biWidth; ymax=head.biHeight;
}
for(long y=ymin; y top) || (x < left) || (x > right)) {
tmp.SetPixelIndex(x,y, pixel);
} else {
tmp.SetPixelIndex(x,y,GetPixelIndex(x-left,y-bottom));
}
}
}
break;
}
case 8:
case 24:
{
if (head.biBitCount == 8) {
BYTE pixel = tmp.GetNearestIndex( canvascolor);
memset(tmp.info.pImage, pixel, + (tmp.info.dwEffWidth * newHeight));
} else {
for (long y = 0; y < newHeight; ++y) {
BYTE *pDest = tmp.info.pImage + (y * tmp.info.dwEffWidth);
for (long x = 0; x < newWidth; ++x) {
*pDest++ = canvascolor.rgbBlue;
*pDest++ = canvascolor.rgbGreen;
*pDest++ = canvascolor.rgbRed;
}
}
}
BYTE* pDest = tmp.info.pImage + (tmp.info.dwEffWidth * bottom) + (left*(head.biBitCount >> 3));
BYTE* pSrc = info.pImage;
for(long y=bottom; y <= top; y++){
info.nProgress = (long)(100*y/(1 + top - bottom));
memcpy(pDest,pSrc,(head.biBitCount >> 3) * (right - left + 1));
pDest+=tmp.info.dwEffWidth;
pSrc+=info.dwEffWidth;
}
}
}
#if CXIMAGE_SUPPORT_SELECTION
if (SelectionIsValid()){
if (!tmp.SelectionCreate())
return false;
BYTE* pSrc = SelectionGetPointer();
BYTE* pDst = tmp.SelectionGetPointer(left,bottom);
for(long y=bottom; y <= top; y++){
memcpy(pDst,pSrc, (right - left + 1));
pSrc+=head.biWidth;
pDst+=tmp.head.biWidth;
}
tmp.info.rSelectionBox.left = info.rSelectionBox.left + left;
tmp.info.rSelectionBox.right = info.rSelectionBox.right + left;
tmp.info.rSelectionBox.top = info.rSelectionBox.top + bottom;
tmp.info.rSelectionBox.bottom = info.rSelectionBox.bottom + bottom;
}
#endif //CXIMAGE_SUPPORT_SELECTION
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()){
if (!tmp.AlphaCreate())
return false;
tmp.AlphaSet(canvascolor.rgbReserved);
BYTE* pSrc = AlphaGetPointer();
BYTE* pDst = tmp.AlphaGetPointer(left,bottom);
for(long y=bottom; y <= top; y++){
memcpy(pDst,pSrc, (right - left + 1));
pSrc+=head.biWidth;
pDst+=tmp.head.biWidth;
}
}
#endif //CXIMAGE_SUPPORT_ALPHA
//select the destination
if (iDst) iDst->Transfer(tmp);
else Transfer(tmp);
return true;
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::Expand(long newx, long newy, RGBQUAD canvascolor, CxImage* iDst)
{
//thanks to
if (!pDib) return false;
if ((newx < head.biWidth) || (newy < head.biHeight)) return false;
int nAddLeft = (newx - head.biWidth) / 2;
int nAddTop = (newy - head.biHeight) / 2;
return Expand(nAddLeft, nAddTop, newx - (head.biWidth + nAddLeft), newy - (head.biHeight + nAddTop), canvascolor, iDst);
}
////////////////////////////////////////////////////////////////////////////////
/**
* Resamples the image with the correct aspect ratio, and fills the borders.
* \param newx, newy = thumbnail size.
* \param canvascolor = border color.
* \param iDst = pointer to destination image (if it's 0, this image is modified).
* \return true if everything is ok.
* \author [Colin Urquhart]
*/
bool CxImage::Thumbnail(long newx, long newy, RGBQUAD canvascolor, CxImage* iDst)
{
if (!pDib) return false;
if ((newx <= 0) || (newy <= 0)) return false;
CxImage tmp(*this);
if (!tmp.IsValid()){
strcpy(info.szLastError,tmp.GetLastError());
return false;
}
// determine whether we need to shrink the image
if ((head.biWidth > newx) || (head.biHeight > newy)) {
float fScale;
float fAspect = (float) newx / (float) newy;
if (fAspect * head.biHeight > head.biWidth) {
fScale = (float) newy / head.biHeight;
} else {
fScale = (float) newx / head.biWidth;
}
tmp.Resample((long) (fScale * head.biWidth), (long) (fScale * head.biHeight), 0);
}
// expand the frame
tmp.Expand(newx, newy, canvascolor, iDst);
//select the destination
if (iDst) iDst->Transfer(tmp);
else Transfer(tmp);
return true;
}
////////////////////////////////////////////////////////////////////////////////
/**
* Perform circle_based transformations.
* \param type - for different transformations
* - 0 for normal (proturberant) FishEye
* - 1 for reverse (concave) FishEye
* - 2 for Swirle
* - 3 for Cilinder mirror
* - 4 for bathroom
*
* \param rmax - effect radius. If 0, the whole image is processed
* \param Koeff - only for swirle
* \author Arkadiy Olovyannikov ark(at)msun(dot)ru
*/
bool CxImage::CircleTransform(int type,long rmax,float Koeff)
{
if (!pDib) return false;
long nx,ny;
double angle,radius,rnew;
CxImage tmp(*this);
if (!tmp.IsValid()){
strcpy(info.szLastError,tmp.GetLastError());
return false;
}
long xmin,xmax,ymin,ymax,xmid,ymid;
if (pSelection){
xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
} else {
xmin = ymin = 0;
xmax = head.biWidth; ymax=head.biHeight;
}
xmid = (long) (tmp.GetWidth()/2);
ymid = (long) (tmp.GetHeight()/2);
if (!rmax) rmax=(long)sqrt((float)((xmid-xmin)*(xmid-xmin)+(ymid-ymin)*(ymid-ymin)));
if (Koeff==0.0f) Koeff=1.0f;
for(long y=ymin; yhead.biWidth || newy>head.biHeight) {
//let me repeat... this method can't enlarge image
strcpy(info.szLastError,"QIShrink can't enlarge image");
return false;
}
if (newx==head.biWidth && newy==head.biHeight) {
//image already correct size (just copy and return)
if (iDst) iDst->Copy(*this);
return true;
}//if
//create temporary destination image
CxImage newImage;
newImage.CopyInfo(*this);
newImage.Create(newx,newy,(bChangeBpp)?24:head.biBitCount,GetType());
newImage.SetPalette(GetPalette());
if (!newImage.IsValid()){
strcpy(info.szLastError,newImage.GetLastError());
return false;
}
//and alpha channel if required
#if CXIMAGE_SUPPORT_ALPHA
if (AlphaIsValid()) newImage.AlphaCreate();
#endif
const int oldx = head.biWidth;
const int oldy = head.biHeight;
int accuCellSize = 4;
#if CXIMAGE_SUPPORT_ALPHA
BYTE *alphaPtr;
if (AlphaIsValid()) accuCellSize=5;
#endif
unsigned int *accu = new unsigned int[newx*accuCellSize]; //array for suming pixels... one pixel for every destination column
unsigned int *accuPtr; //pointer for walking through accu
//each cell consists of blue, red, green component and count of pixels summed in this cell
memset(accu, 0, newx * accuCellSize * sizeof(unsigned int)); //clear accu
if (!IsIndexed()) {
//RGB24 version with pointers
BYTE *destPtr, *srcPtr, *destPtrS, *srcPtrS; //destination and source pixel, and beginnings of current row
srcPtrS=(BYTE*)BlindGetPixelPointer(0,0);
destPtrS=(BYTE*)newImage.BlindGetPixelPointer(0,0);
int ex=0, ey=0; //ex and ey replace division...
int dy=0;
//(we just add pixels, until by adding newx or newy we get a number greater than old size... then
// it's time to move to next pixel)
for(int y=0; yoldx) { //when we reach oldx, it's time to move to new slot
accuPtr += accuCellSize;
ex -= oldx; //(substract oldx from ex and resume from there on)
}//if (ex overflow)
}//for x
if (ey>=oldy) { //now when this happens
ey -= oldy; //it's time to move to new destination row
destPtr = destPtrS; //reset pointers to proper initial values
accuPtr = accu;
#if CXIMAGE_SUPPORT_ALPHA
alphaPtr = newImage.AlphaGetPointer(0, dy++);
#endif
for (int k=0; koldx) { //when we reach oldx, it's time to move to new slot
accuPtr += accuCellSize;
ex -= oldx; //(substract oldx from ex and resume from there on)
}//if (ex overflow)
}//for x
if (ey>=oldy) { //now when this happens
ey -= oldy; //it's time to move to new destination row
accuPtr = accu;
for (int dx=0; dxTransfer(newImage);
else
Transfer(newImage);
return true;
}
////////////////////////////////////////////////////////////////////////////////
#endif //CXIMAGE_SUPPORT_TRANSFORMATION