CodePlexProject Hosting for Open Source Software
Item 16 of 32 Previous | Next
Fixed
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
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 PMI'm trying to implement the algorithm using WriteableBitmap... but my Alphachannel is F*cked ... hmmmmontago wrote Today at 12:49 PMAlgorithms 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/
Sign in to add a comment or to set email notifications
Keyboard shortcuts are available for this page.