Item 16 of 32 Previous | Next

2

Fixed

Free Rotate Methods

description

In addition to the 90° fixed methods, support for any angle should be added.
Follow up to http://writeablebitmapex.codeplex.com/workitem/13362

No files are attached

comments

teichgraf wrote Oct 27 2011 at 8:16 PM

Fixed in http://writeablebitmapex.codeplex.com/SourceControl/changeset/changes/82055

montago wrote May 18 2011 at 7:24 AM

correction:

if (crop)
{
newWidth = iWidth;
newHeight = iHeight;
}
else
{
var rad = Degrees / (180 / Math.PI);
newWidth = (int)Math.Ceiling(Math.Abs(Math.Sin(rad) * iHeight) + Math.Abs(Math.Cos(rad) * iWidth));
newHeight = (int)Math.Ceiling(Math.Abs(Math.Sin(rad) * iWidth) + Math.Abs(Math.Cos(rad) * iHeight));
}

montago wrote May 17 2011 at 2:21 PM

/// <summary>
/// Rotate an image by an arbitrary angle, technique is Bilinear filtering.
/// </summary>
/// <param name="Degrees">Arbitrary angle in 360 Degrees (positive = clockwise)</param>
/// <param name="crop">if true: keep the size, false: adjust canvas to new size</param>
/// <returns></returns>

montago wrote May 17 2011 at 2:19 PM

add / change:
public static WriteableBitmap Rotate(this WriteableBitmap bm, double Degrees, bool crop = true)
/---/
if (crop)
{
newWidth = iWidth;
newHeight = iHeight;
}
else
{
newHeight = newWidth = (int)Math.Ceiling(Math.Abs(Math.Sin(Degrees / (180 / Math.PI)) * iWidth) + Math.Abs(Math.Cos(Degrees / (180 / Math.PI)) * iWidth));
}

teichgraf wrote May 17 2011 at 9:15 AM

Great. Please note that the GetPixel and SetPixel methods are quite slow and should be inlined. No problem if you leave those in, I will optimize the code anyhow before it's committed. :)
I would appreciate if you could add a bool parameter which defines if the result image should be cropped or auto-expanded.

Thanks!

montago wrote May 17 2011 at 7:57 AM

I seem to have solved it all... including auto-fitting canvas... might make that an option though...

public static WriteableBitmap Rotate(this WriteableBitmap bm, double Degrees)
{
// rotating clockwise, so it's negative relative to Cartesian quadrants
double cnAngle = -1.0 * (Math.PI / 180) * Degrees;

// general iterators
int i, j;
// calculated indices in Cartesian coordinates
int x, y;
double fDistance, fPolarAngle;
// for use in neighbouring indices in Cartesian coordinates
int iFloorX, iCeilingX, iFloorY, iCeilingY;
// calculated indices in Cartesian coordinates with trailing decimals
double fTrueX, fTrueY;
// for interpolation
double fDeltaX, fDeltaY;

// pixel colours
Color clrTopLeft, clrTopRight, clrBottomLeft, clrBottomRight;

// interpolated "top" pixels
double fTopRed, fTopGreen, fTopBlue, fTopAlpha;

// interpolated "bottom" pixels
double fBottomRed, fBottomGreen, fBottomBlue, fBottomAlpha;

// final interpolated colour components
int iRed, iGreen, iBlue, iAlpha;

int iCentreX, iCentreY;
int iDestCentreX, iDestCentreY;
int iWidth, iHeight, newWidth, newHeight;
int iDiagonal;

iWidth = bm.PixelWidth;
iHeight = bm.PixelHeight;

//iDiagonal = (int)(Math.Ceiling(Math.Sqrt((double)(iWidth * iWidth + iHeight * iHeight)))) + cnSizeBuffer;

iCentreX = iWidth / 2;
iCentreY = iHeight / 2;

newHeight = newWidth = (int)Math.Ceiling(Math.Abs(Math.Sin(Degrees/(180/Math.PI)) * iWidth) + Math.Abs(Math.Cos(Degrees/(180/Math.PI)) * iWidth));


iDestCentreX = newWidth / 2;
iDestCentreY = newHeight / 2;

WriteableBitmap bmBilinearInterpolation = new WriteableBitmap(newWidth, newHeight);

for (i = 0; i < newHeight; ++i)
{
for (j = 0; j < newWidth; ++j)
{
bmBilinearInterpolation.SetPixel(j, i, Colors.Transparent);
}
}

// assigning pixels of destination image from source image
// with bilinear interpolation
for (i = 0; i < newHeight; ++i)
{
for (j = 0; j < newWidth; ++j)
{
// convert raster to Cartesian
x = j - iDestCentreX;
y = iDestCentreY - i;

// convert Cartesian to polar
fDistance = Math.Sqrt(x * x + y * y);
fPolarAngle = 0.0;
if (x == 0)
{
if (y == 0)
{
// centre of image, no rotation needed
bmBilinearInterpolation.SetPixel(j, i, bm.GetPixel(iCentreX, iCentreY));
continue;
}
else if (y < 0)
{
fPolarAngle = 1.5 * Math.PI;
}
else
{
fPolarAngle = 0.5 * Math.PI;
}
}
else
{
fPolarAngle = Math.Atan2((double)y, (double)x);
}

// the crucial rotation part
// "reverse" rotate, so minus instead of plus
fPolarAngle -= cnAngle;

// convert polar to Cartesian
fTrueX = fDistance * Math.Cos(fPolarAngle);
fTrueY = fDistance * Math.Sin(fPolarAngle);

// convert Cartesian to raster
fTrueX = fTrueX + (double)iCentreX;
fTrueY = (double)iCentreY - fTrueY;

iFloorX = (int)(Math.Floor(fTrueX));
iFloorY = (int)(Math.Floor(fTrueY));
iCeilingX = (int)(Math.Ceiling(fTrueX));
iCeilingY = (int)(Math.Ceiling(fTrueY));

// check bounds
if (iFloorX < 0 || iCeilingX < 0 || iFloorX >= iWidth || iCeilingX >= iWidth || iFloorY < 0 || iCeilingY < 0 || iFloorY >= iHeight || iCeilingY >= iHeight) continue;

fDeltaX = fTrueX - (double)iFloorX;
fDeltaY = fTrueY - (double)iFloorY;

clrTopLeft = bm.GetPixel(iFloorX, iFloorY);
clrTopRight = bm.GetPixel(iCeilingX, iFloorY);
clrBottomLeft = bm.GetPixel(iFloorX, iCeilingY);
clrBottomRight = bm.GetPixel(iCeilingX, iCeilingY);

// linearly interpolate horizontally between top neighbours
fTopRed = (1 - fDeltaX) * clrTopLeft.R + fDeltaX * clrTopRight.R;
fTopGreen = (1 - fDeltaX) * clrTopLeft.G + fDeltaX * clrTopRight.G;
fTopBlue = (1 - fDeltaX) * clrTopLeft.B + fDeltaX * clrTopRight.B;
fTopAlpha = (1 - fDeltaX) * clrTopLeft.A + fDeltaX * clrTopRight.A;

// linearly interpolate horizontally between bottom neighbours
fBottomRed = (1 - fDeltaX) * clrBottomLeft.R + fDeltaX * clrBottomRight.R;
fBottomGreen = (1 - fDeltaX) * clrBottomLeft.G + fDeltaX * clrBottomRight.G;
fBottomBlue = (1 - fDeltaX) * clrBottomLeft.B + fDeltaX * clrBottomRight.B;
fBottomAlpha = (1 - fDeltaX) * clrBottomLeft.A + fDeltaX * clrBottomRight.A;

// linearly interpolate vertically between top and bottom interpolated results
iRed = (int)(Math.Round((1 - fDeltaY) * fTopRed + fDeltaY * fBottomRed));
iGreen = (int)(Math.Round((1 - fDeltaY) * fTopGreen + fDeltaY * fBottomGreen));
iBlue = (int)(Math.Round((1 - fDeltaY) * fTopBlue + fDeltaY * fBottomBlue));
iAlpha = (int)(Math.Round((1 - fDeltaY) * fTopAlpha + fDeltaY * fBottomAlpha));

// make sure colour values are valid
if (iRed < 0) iRed = 0;
if (iRed > 255) iRed = 255;
if (iGreen < 0) iGreen = 0;
if (iGreen > 255) iGreen = 255;
if (iBlue < 0) iBlue = 0;
if (iBlue > 255) iBlue = 255;
if (iAlpha < 0) iAlpha = 0;
if (iAlpha > 255) iAlpha = 255;

bmBilinearInterpolation.SetPixel(j, i, Color.FromArgb((byte)iAlpha, (byte)iRed, (byte)iGreen, (byte)iBlue));
}
}
return bmBilinearInterpolation;
}

teichgraf wrote May 13 2011 at 10:24 PM

Nokola summed it up quite nicely here:
http://nokola.com/blog/post/2010/01/27/The-Most-Important-Silverlight-WriteableBitmap-Gotcha-Does-It-LoseChange-Colors.aspx

montago wrote May 13 2011 at 9:20 PM

How do i compensate for premultiplied Alpha ?... is this something going on in the extensions or in the WriteableBitmap itself ?

my problem is that the drawing gets opaque instead of using the Alpha :-/ ...

teichgraf wrote May 13 2011 at 5:56 PM

@montago:
Thanks for the info. Seems like a promising implementation.
Contributions are always welcome and will be credited on the start page. :)

Please keep in mind that WriteableBitmap uses pARGB (pre-multiplied alpha). Check the GetPixel and SetPixel methods to see how this needs to be implemented.

teichgraf wrote May 13 2011 at 5:53 PM

Comments from 15869 (Duplicate)
montago wrote Today at 2:53 PM
I'm trying to implement the algorithm using WriteableBitmap... but my Alphachannel is F*cked ... hmmm

montago wrote Today at 12:49 PM
Algorithms for rotation:
http://polymathprogrammer.com/2008/10/06/image-rotation-with-bilinear-interpolation/
http://polymathprogrammer.com/2010/04/05/image-rotation-with-bilinear-interpolation-and-no-clipping/