Rotate Bitmap around point and make that point the new center - c#

I want to rotate a Bitmap around a given point and have that point become the new center of the Bitmap.
Here is what I tried first
Bitmap rotate(Bitmap img, float angle, int cx, int cy)
{
Bitmap result = new Bitmap(img.Width, img.Height);
int middleX = img.Width / 2,
middleY = img.Height / 2;
using (Graphics g = Graphics.FromImage(result))
{
g.Clear(Color.Black);
g.TranslateTransform(cx, cy);
g.RotateTransform(angle);
g.TranslateTransform(-cx, -cy);
g.TranslateTransform(middleX - cx, middleY - cy); //shift (cx, cy) to be at the center, does not work
g.DrawImage(originalImage, new Point(0, 0));
}
return result;
}
But when I go to translate the image after rotation, the translation is in the original space instead of this new rotated space and doesn't come out right.
I tried basically every combination of things I could think of with no luck. Search results only describe how to rotate around a point.
Original image (red dot is the point to rotate around) -
After rotating by 45 degrees the image should be translated so that the red dot is the center of the image

You have to set the matrix order to Append for the final translation. I have no idea why this is what makes it work. I literally just brute force tried every possible way of doing everything until it worked. I'd still be interested in an explanation of what is going on though.
Bitmap rotate(Bitmap img, float angle, int cx, int cy)
{
Bitmap result = new Bitmap(img.Width, img.Height);
int mx = img.Width / 2,
my = img.Height / 2;
using (Graphics g = Graphics.FromImage(result))
{
g.Clear(Color.Black);
g.TranslateTransform(cx, cy);
g.RotateTransform(angle);
g.TranslateTransform(-cx, -cy);
g.TranslateTransform(mx - cx, my - cy, MatrixOrder.Append);
g.DrawImage(originalImage, new Point(0, 0));
}
return result;
}

Related

rotating Graphics/ image in c#

I have the following code which i wrote to try and rotate a bitmap(this is a test) the idea is to take a bitmap and rotate it by some amount of degrees and then draw it on the screen using win forms
protected override void OnDoubleClick(EventArgs e)
{
base.OnDoubleClick(e);
Graphics g = this.CreateGraphics();
Bitmap b = new Bitmap(path);
g.Clear(Color.White);
imagePosition = Cursor.Position;
b = RotateImage(b, 45);
g.DrawImage(b, new Point(100, 100));
}
public Bitmap RotateImage(Bitmap b, float angle)
{
Bitmap returnBitmap = new Bitmap(b.Width, b.Height);
returnBitmap.SetResolution(b.HorizontalResolution, b.VerticalResolution);
Graphics g = Graphics.FromImage(returnBitmap);
g.TranslateTransform((float)b.Width / 2, (float)b.Height / 2);
g.RotateTransform(angle);
g.TranslateTransform(-(float)b.Width / 2, -(float)b.Height / 2);
g.DrawImage(b, new Point(0, 0));
return returnBitmap;
}
this is the image before rotation
and this is the image after rotating 45 degrees like was shown in the code
The reason it's getting clipped is that there isn't enough space to display it rotated. The diagonal is longer than the sides (Pythagoras).
You need to make more space for the image, then it should display OK. How you do that will depend on what the image is contained in.

How do I rotate image then move to the top left 0,0 without cutting off the image

How do I rotate image then move to the top left 0,0 without cutting off the image.
Please read the comments inside the code. I got stuck at STEP 3
I think using trigonometry should be able to solve this problem.
thanks
private Bitmap RotateImage(Bitmap b, float angle)
{
//create a new empty bitmap to hold rotated image
Bitmap returnBitmap = new Bitmap(b.Width, b.Height);
//make a graphics object from the empty bitmap
Graphics g = Graphics.FromImage(returnBitmap);
//STEP 1 move rotation point to top left
g.TranslateTransform((float)0, (float)0);
//STEP 2 rotate
g.RotateTransform(angle);
//STEP 3 move image back to top left without cutting off the image
//SOME trigonometry calculation here
int newY = b.Height;
g.TranslateTransform(-(float)0, -newY);
//draw passed in image onto graphics object
g.DrawImage(b, new Point(0, 0));
return returnBitmap;
}
Does this cover the 'trigonometry'? I have made it step 0 because I think you need to do it first. That way you can calculate the size of the resulting bitmap, which will be bigger - see my comments in the code.
private Bitmap RotateImage(Bitmap b, float Angle) {
// The original bitmap needs to be drawn onto a new bitmap which will probably be bigger
// because the corners of the original will move outside the original rectangle.
// An easy way (OK slightly 'brute force') is to calculate the new bounding box is to calculate the positions of the
// corners after rotation and get the difference between the maximum and minimum x and y coordinates.
float wOver2 = b.Width / 2.0f;
float hOver2 = b.Height / 2.0f;
float radians = -(float)(Angle / 180.0 * Math.PI);
// Get the coordinates of the corners, taking the origin to be the centre of the bitmap.
PointF[] corners = new PointF[]{
new PointF(-wOver2, -hOver2),
new PointF(+wOver2, -hOver2),
new PointF(+wOver2, +hOver2),
new PointF(-wOver2, +hOver2)
};
for (int i = 0; i < 4; i++) {
PointF p = corners[i];
PointF newP = new PointF((float)(p.X * Math.Cos(radians) - p.Y * Math.Sin(radians)), (float)(p.X * Math.Sin(radians) + p.Y * Math.Cos(radians)));
corners[i] = newP;
}
// Find the min and max x and y coordinates.
float minX = corners[0].X;
float maxX = minX;
float minY = corners[0].Y;
float maxY = minY;
for (int i = 1; i < 4; i++) {
PointF p = corners[i];
minX = Math.Min(minX, p.X);
maxX = Math.Max(maxX, p.X);
minY = Math.Min(minY, p.Y);
maxY = Math.Max(maxY, p.Y);
}
// Get the size of the new bitmap.
SizeF newSize = new SizeF(maxX - minX, maxY - minY);
// ...and create it.
Bitmap returnBitmap = new Bitmap((int)Math.Ceiling(newSize.Width), (int)Math.Ceiling(newSize.Height));
// Now draw the old bitmap on it.
using (Graphics g = Graphics.FromImage(returnBitmap)) {
g.TranslateTransform(newSize.Width / 2.0f, newSize.Height / 2.0f);
g.RotateTransform(Angle);
g.TranslateTransform(-b.Width / 2.0f, -b.Height / 2.0f);
g.DrawImage(b, 0, 0);
}
return returnBitmap;
}

What is the fastest method for rotating an image without clipping its edges with GDI+?

There are some looooong and hungry algorithms for doing so, but as of yet I haven't come up with or found anything particularly fast.
The fastest way is to this is to use unsafe calls to manipulate the image memory directly using LockBits. It sounds scary but it's pretty straight forward. If you search for LockBits you'll find plently of examples such as here.
The interesting bit is:
BitmapData originalData = originalBitmap.LockBits(
new Rectangle(0, 0, originalWidth, originalHeight),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppRgb);
Once you have the BitmapData you can pass the pixels and map them into a new image (again using LockBits). This is significantly quicker than using the Graphics API.
Here's what I ended up doing (after an extensive amount of continued research, and the helpful link provided by TheCodeKing):
public Image RotateImage(Image img, float rotationAngle)
{
// When drawing the returned image to a form, modify your points by
// (-(img.Width / 2) - 1, -(img.Height / 2) - 1) to draw for actual co-ordinates.
//create an empty Bitmap image
Bitmap bmp = new Bitmap((img.Width * 2), (img.Height *2));
//turn the Bitmap into a Graphics object
Graphics gfx = Graphics.FromImage(bmp);
//set the point system origin to the center of our image
gfx.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2);
//now rotate the image
gfx.RotateTransform(rotationAngle);
//move the point system origin back to 0,0
gfx.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2);
//set the InterpolationMode to HighQualityBicubic so to ensure a high
//quality image once it is transformed to the specified size
gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
//draw our new image onto the graphics object with its center on the center of rotation
gfx.DrawImage(img, new PointF((img.Width / 2), (img.Height / 2)));
//dispose of our Graphics object
gfx.Dispose();
//return the image
return bmp;
}
Cheers!
void Graphics.RotateTransform(float angle);
This should rotate the image in C#. What is it doing instead?
I haven't experimented too much with GDI+. Remember to reverse the rotation after the image is drawn.
This answer returns both the offset it should be drawn on and the image which has been rotated.It works by recreating the new image to the size it should be without clipping the angles. Originally written by Hisenburg from the #C# IRC chatroom and Bloodyaugust.
public static double NormalizeAngle(double angle)
{
double division = angle / (Math.PI / 2);
double fraction = Math.Ceiling(division) - division;
return (fraction * Math.PI / 2);
}
public static Tuple<Image,Size> RotateImage(Image img, double rotationAngle)
{
double normalizedRotationAngle = NormalizeAngle(rotationAngle);
double widthD = img.Width, heightD = img.Height;
double newWidthD, newHeightD;
newWidthD = Math.Cos(normalizedRotationAngle) * widthD + Math.Sin(normalizedRotationAngle) * heightD;
newHeightD = Math.Cos(normalizedRotationAngle) * heightD + Math.Sin(normalizedRotationAngle) * widthD;
int newWidth, newHeight;
newWidth = (int)Math.Ceiling(newWidthD);
newHeight = (int)Math.Ceiling(newHeightD);
Size offset = new Size((newWidth - img.Width) / 2,(newHeight - img.Height) / 2);
Bitmap bmp = new Bitmap(newWidth, newHeight);
Graphics gfx = Graphics.FromImage(bmp);
//gfx.Clear(Color.Blue);
gfx.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2);
gfx.RotateTransform((float)(rotationAngle / Math.PI * 180));
gfx.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2);
gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
gfx.DrawImage(img, new PointF((bmp.Width / 2 - img.Width / 2), (bmp.Height / 2 - img.Height / 2)));
gfx.Dispose();
return new Tuple<Image,Size>(bmp,offset);
}
System.Drawing.Image imageToRotate = System.Drawing.Image.FromFile(imagePath);
switch (rotationAngle.Value)
{
case "90":
imageToRotate.RotateFlip(RotateFlipType.Rotate90FlipNone);
break;
case "180":
imageToRotate.RotateFlip(RotateFlipType.Rotate180FlipNone);
break;
case "270":
imageToRotate.RotateFlip(RotateFlipType.Rotate270FlipNone);
break;
default:
throw new Exception("Rotation angle not supported.");
}
imageToRotate.Save(imagePath, ImageFormat.Jpeg);

Matrix rotateAt c#

Im trying to rotate a image with matrix object and can't get it right
When i rotate the image i got a black spot, it's one pixel wrong and it's the same with 180 angle and 270 angle.
90 angle ex.
A picture of this problem:
http://www.spasm-design.com/rotate/onePixelWrong.jpg
And here is the code:
public System.Drawing.Image Rotate(System.Drawing.Image image, String angle, String direction)
{
Int32 destW, destH;
float destX, destY, rotate;
destW = image.Width;
destH = image.Height;
destX = destY = 0;
if (r == "90" || r == "270")
{
destW = image.Height;
destH = image.Width;
destY = (image.Width - destW) / 2;
destX = (image.Height - destH) / 2;
}
rotate = (direction == "y") ? float.Parse(angle) : float.Parse("-" + angle);
Bitmap b = new Bitmap(destW, destH, PixelFormat.Format24bppRgb);
b.SetResolution(image.HorizontalResolution, image.VerticalResolution);
Matrix x = new Matrix();
x.Translate(destX, destY);
x.RotateAt(rotate, new PointF(image.Width / 2, image.Height / 2));
Graphics g = Graphics.FromImage(b);
g.PageUnit = GraphicsUnit.Pixel;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.Transform = x;
g.DrawImage(image, 0, 0);
g.Dispose();
x.Dispose();
return b;
}
if someone have a good ide why this is happening please tell me.
Have a good day!
I think you're just getting a rounding error on this line:
x.RotateAt(rotate, new PointF(image.Width / 2, image.Height / 2));
Width and Height are both int properties. Try this instead:
x.RotateAt(rotate, new PointF((float)Math.Floor(image.Width / 2),
(float)Math.Floor(image.Height / 2)));
(Not tested, so not sure if this will work.)
Update: I don't think my above fix will work, but it may point you in the direction of the problem. If you can't fix it by adjusting the rounding, you may just need to change destX to -1 to get rid of the black line.
This works:
x.RotateAt(rotate, new PointF(image.Width / 2, image.Height / 2));
this "image.Width / 2" returns float
First i find out what angle is, if it is 90 or 270 flip the image so image.width = image.height and image.height = width
If a do that i get a problem when i rotate the image for the image width can be bigger then height of the image so then i need to reset the image x,y coordinates to 0,0
So this "destY = (image.Width - destW) / 2;" calculate offset of the image to the bitmap
and this "x.Translate(destX, destY);" set the image x equivalent to bitmap x
but something is going wrong for the rotation makes picture 1px to small.
so for my english but im not the best of it, i hope you can read it any why :)
for more questions please send me those and i'm going to try explain what i mean.
A much simpler solution is:
Add one pixel around the image.
Rotate the image.
Remove the one pixel.
Code:
// h and w are the width/height
// cx and cy are the centre of the image
myBitmap = new Bitmap(w + 2, h + 2);
mygraphics = Graphics.FromImage(myBitmap);
mygraphics.TranslateTransform(cx, cy);
mygraphics.RotateTransform(angle);
mygraphics.TranslateTransform(-cx, -cy);
mygraphics.DrawImage(myimage, new Point(1, 1));
// image crop
myBitmap= myBitmap.Clone(new Rectangle(1, 1, (int)w, (int)h), myimage.PixelFormat)
This is the main idea. Hope it helps.

Per-pixel collision problem in C#

I am writing a small 2d game engine in C# for my own purposes, and it works fine except for the sprite collision detection. I've decided to make it a per-pixel detection (easiest for me to implement), but it is not working the way it's supposed to. The code detects a collision long before it happens. I've examined every component of the detection, but I can't find the problem.
The collision detection method:
public static bool CheckForCollision(Sprite s1, Sprite s2, bool perpixel) {
if(!perpixel) {
return s1.CollisionBox.IntersectsWith(s2.CollisionBox);
}
else {
Rectangle rect;
Image img1 = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(s1.Image, s1.Position, s1.Origin, s1.Rotation, out rect), s1.Scale);
int posx1 = rect.X;
int posy1 = rect.Y;
Image img2 = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(s2.Image, s2.Position, s2.Origin, s2.Rotation, out rect), s2.Scale);
int posx2 = rect.X;
int posy2 = rect.Y;
Rectangle abounds = new Rectangle(posx1, posy1, (int)img1.Width, (int)img1.Height);
Rectangle bbounds = new Rectangle(posx2, posy2, (int)img2.Width, (int)img2.Height);
if(Utilities.RectangleIntersects(abounds, bbounds)) {
uint[] bitsA = s1.GetPixelData(false);
uint[] bitsB = s2.GetPixelData(false);
int x1 = Math.Max(abounds.X, bbounds.X);
int x2 = Math.Min(abounds.X + abounds.Width, bbounds.X + bbounds.Width);
int y1 = Math.Max(abounds.Y, bbounds.Y);
int y2 = Math.Min(abounds.Y + abounds.Height, bbounds.Y + bbounds.Height);
for(int y = y1; y < y2; ++y) {
for(int x = x1; x < x2; ++x) {
if(((bitsA[(x - abounds.X) + (y - abounds.Y) * abounds.Width] & 0xFF000000) >> 24) > 20 &&
((bitsB[(x - bbounds.X) + (y - bbounds.Y) * bbounds.Width] & 0xFF000000) >> 24) > 20)
return true;
}
}
}
return false;
}
}
The image rotation method:
internal static Image RotateImagePoint(Image img, Vector pos, Vector orig, double rotation, out Rectangle sz) {
if(!(new Rectangle(new Point(0), img.Size).Contains(new Point((int)orig.X, (int)orig.Y))))
Console.WriteLine("Origin point is not present in image bound; unwanted cropping might occur");
rotation = (double)ra_de((double)rotation);
sz = GetRotateDimensions((int)pos.X, (int)pos.Y, img.Width, img.Height, rotation, false);
Bitmap bmp = new Bitmap(sz.Width, sz.Height);
Graphics g = Graphics.FromImage(bmp);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.RotateTransform((float)rotation);
g.TranslateTransform(sz.Width / 2, sz.Height / 2, MatrixOrder.Append);
g.DrawImage(img, (float)-orig.X, (float)-orig.Y);
g.Dispose();
return bmp;
}
internal static Rectangle GetRotateDimensions(int imgx, int imgy, int imgwidth, int imgheight, double rotation, bool Crop) {
Rectangle sz = new Rectangle();
if (Crop == true) {
// absolute trig values goes for all angles
double dera = de_ra(rotation);
double sin = Math.Abs(Math.Sin(dera));
double cos = Math.Abs(Math.Cos(dera));
// general trig rules:
// length(adjacent) = cos(theta) * length(hypotenuse)
// length(opposite) = sin(theta) * length(hypotenuse)
// applied width = lo(img height) + la(img width)
sz.Width = (int)(sin * imgheight + cos * imgwidth);
// applied height = lo(img width) + la(img height)
sz.Height = (int)(sin * imgwidth + cos * imgheight);
}
else {
// get image diagonal to fit any rotation (w & h =diagonal)
sz.X = imgx - (int)Math.Sqrt(Math.Pow(imgwidth, 2.0) + Math.Pow(imgheight, 2.0));
sz.Y = imgy - (int)Math.Sqrt(Math.Pow(imgwidth, 2.0) + Math.Pow(imgheight, 2.0));
sz.Width = (int)Math.Sqrt(Math.Pow(imgwidth, 2.0) + Math.Pow(imgheight, 2.0)) * 2;
sz.Height = sz.Width;
}
return sz;
}
Pixel getting method:
public uint[] GetPixelData(bool useBaseImage) {
Rectangle rect;
Image image;
if (useBaseImage)
image = Image;
else
image = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(Image, Position, Origin, Rotation, out rect), Scale);
BitmapData data;
try {
data = ((Bitmap)image).LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
}
catch (ArgumentException) {
data = ((Bitmap)image).LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat);
}
byte[] rawdata = new byte[data.Stride * image.Height];
Marshal.Copy(data.Scan0, rawdata, 0, data.Stride * image.Height);
((Bitmap)image).UnlockBits(data);
int pixelsize = 4;
if (data.PixelFormat == PixelFormat.Format24bppRgb)
pixelsize = 3;
else if (data.PixelFormat == PixelFormat.Format32bppArgb || data.PixelFormat == PixelFormat.Format32bppRgb)
pixelsize = 4;
double intdatasize = Math.Ceiling((double)rawdata.Length / pixelsize);
uint[] intdata = new uint[(int)intdatasize];
Buffer.BlockCopy(rawdata, 0, intdata, 0, rawdata.Length);
return intdata;
}
The pixel retrieval method works, and the rotation method works as well, so the only place that the code might be wrong is the collision detection code, but I really have no idea where the problem might be.
I don't think many people here will bother to scrutinize your code to figure out what exactly is wrong. But I can come with some hints to how you can find the problem.
If collision happens long before it is supposed to I suggest your bounding box check isn't working properly.
I would change the code to dump out all the data about rectangles at collision. So you can create some code that will display the situation at collision. That might be easier than looking over the numbers.
Apart from that I doubt that per pixel collision detection easier for you to implement. When you allow for rotation and scaling that quickly becomes difficult to get right. I would do polygon based collision detection instead.
I have made my own 2D engine like you but I used polygon based collision detection and that worked fine.
I think I've found your problem.
internal static Rectangle GetRotateDimensions(int imgx, int imgy, int imgwidth, int imgheight, double rotation, bool Crop) {
Rectangle sz = new Rectangle(); // <-- Default constructed rect here.
if (Crop == true) {
// absolute trig values goes for all angles
double dera = de_ra(rotation);
double sin = Math.Abs(Math.Sin(dera));
double cos = Math.Abs(Math.Cos(dera));
// general trig rules:
// length(adjacent) = cos(theta) * length(hypotenuse)
// length(opposite) = sin(theta) * length(hypotenuse)
// applied width = lo(img height) + la(img width)
sz.Width = (int)(sin * imgheight + cos * imgwidth);
// applied height = lo(img width) + la(img height)
sz.Height = (int)(sin * imgwidth + cos * imgheight);
// <-- Never gets the X & Y assigned !!!
}
Since you never assigned imgx and imgy to the X and Y coordinates of the Rectangle, every call of GetRotateDimensions will produce a Rectangle with the same location. They may be of differing sizes, but they will always be in the default X,Y position. This would cause the really early collisions that you are seeing because any time you tried to detect collisions on two sprites, GetRotateDimensions would put their bounds in the same position regardless of where they actually are.
Once you have corrected that problem, you may run into another error:
Rectangle rect;
Image img1 = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(s1.Image, s1.Position, s1.Origin, s1.Rotation, out rect), s1.Scale);
// <-- Should resize rect here.
int posx1 = rect.X;
int posy1 = rect.Y;
You get your boundary rect from the RotateImagePoint function, but you then resize the image. The X and Y from the rect are probably not exactly the same as that of the resized boundaries of the image. I'm guessing that you mean for the center of the image to remain in place while all points contract toward or expand from the center in the resize. If this is the case, then you need to resize rect as well as the image in order to get the correct position.
I doubt this is the actual problem, but LockBits doesn't guarantee that the bits data is aligned to the image's Width.
I.e., there may be some padding. You need to access the image using data[x + y * stride] and not data[x + y * width]. The Stride is also part of the BitmapData.

Categories

Resources