2

Resolved

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

comments

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/

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.

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 :-/ ...

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 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 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));
        }

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 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));
        }

wrote May 27, 2011 at 4:17 AM

wrote Feb 22, 2013 at 12:14 AM

wrote May 16, 2013 at 11:36 AM

wrote May 16, 2013 at 11:36 AM

wrote Jun 14, 2013 at 7:31 AM