How can I rotate an image by any degree? - c#

I have animated gif and I'm using a class to parse the images(frames) from it.
The class is:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.IO;
public class AnimatedGif
{
private List<AnimatedGifFrame> mImages = new List<AnimatedGifFrame>();
public AnimatedGif(string path)
{
Image img = Image.FromFile(path);
int frames = img.GetFrameCount(FrameDimension.Time);
if (frames <= 1) throw new ArgumentException("Image not animated");
byte[] times = img.GetPropertyItem(0x5100).Value;
int frame = 0;
for (; ; )
{
int dur = BitConverter.ToInt32(times, 4 * frame);
mImages.Add(new AnimatedGifFrame(new Bitmap(img), dur));
if (++frame >= frames) break;
img.SelectActiveFrame(FrameDimension.Time, frame);
}
img.Dispose();
}
public List<AnimatedGifFrame> Images { get { return mImages; } }
}
public class AnimatedGifFrame
{
private int mDuration;
private Image mImage;
internal AnimatedGifFrame(Image img, int duration)
{
mImage = img; mDuration = duration;
}
public Image Image { get { return mImage; } }
public int Duration { get { return mDuration; } }
}
Now in form1 I loop over the frames in this case 4 and I want to rotate the animation by any degree. Now its rotating each 45 or 90 degrees. I want to add more frames(images) to the animation so if I set the rotation to 31 or to 10 degrees so I will see the animation rotating in 10 degrees.
This is the code in Form1 which is not working good. I'm using a function for the rotation which I didn't test yet if its any working.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace AnimatedGifEditor
{
public partial class Form1 : Form
{
Image myImage;
AnimatedGif myGif;
Bitmap bitmap;
public Form1()
{
InitializeComponent();
myImage = Image.FromFile(#"D:\fananimation.gif");
myGif = new AnimatedGif(#"D:\fananimation.gif");
for (int i = 0; i < myGif.Images.Count; i++)
{
pictureBox1.Image = myGif.Images[3].Image;
bitmap = new Bitmap(pictureBox1.Image);
rotateImage(bitmap, 76);
pictureBox1.Image = bitmap;
}
}
private void Form1_Load(object sender, EventArgs e)
{
}
private Bitmap RotateImg(Bitmap bmp, float angle, Color bkColor)
{
int w = bmp.Width;
int h = bmp.Height;
bmp.PixelFormat pf = default(bmp.PixelFormat);
if (bkColor == Color.Transparent)
{
pf = bmp.Format32bppArgb;
}
else
{
pf = bmp.PixelFormat;
}
Bitmap tempImg = new Bitmap(w, h, pf);
Graphics g = Graphics.FromImage(tempImg);
g.Clear(bkColor);
g.DrawImageUnscaled(bmp, 1, 1);
g.Dispose();
GraphicsPath path = new GraphicsPath();
path.AddRectangle(new RectangleF(0f, 0f, w, h));
Matrix mtrx = new Matrix();
//Using System.Drawing.Drawing2D.Matrix class
mtrx.Rotate(angle);
RectangleF rct = path.GetBounds(mtrx);
Bitmap newImg = new Bitmap(Convert.ToInt32(rct.Width), Convert.ToInt32(rct.Height), pf);
g = Graphics.FromImage(newImg);
g.Clear(bkColor);
g.TranslateTransform(-rct.X, -rct.Y);
g.RotateTransform(angle);
g.InterpolationMode = InterpolationMode.HighQualityBilinear;
g.DrawImageUnscaled(tempImg, 0, 0);
g.Dispose();
tempImg.Dispose();
return newImg;
}
}
}
The animated gif I'm using for the test can be found here:

I didn't understand what's your problem but I think that your code could be improved. I think that you don't need to use directly the Matrix class. There are some functions that does this work for you. Infact the only things you need are: set the point of the rotation as the center, rotate the graphics and draw on it, using some functions by the Graphics class.
So to rotate an image you can use this simple code:
private Bitmap RotateImage(Bitmap bmp, float angle) {
Bitmap rotatedImage = new Bitmap(bmp.Width, bmp.Height);
rotatedImage.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
using (Graphics g = Graphics.FromImage(rotatedImage)) {
// Set the rotation point to the center in the matrix
g.TranslateTransform(bmp.Width / 2, bmp.Height / 2);
// Rotate
g.RotateTransform(angle);
// Restore rotation point in the matrix
g.TranslateTransform(- bmp.Width / 2, - bmp.Height / 2);
// Draw the image on the bitmap
g.DrawImage(bmp, new Point(0, 0));
}
return rotatedImage;
}

Based on the previous answers I created this code that doesn't cut the image (the other examples were not working for me)
private Bitmap RotateImage(Bitmap bmp, float angle)
{
float height = bmp.Height;
float width = bmp.Width;
int hypotenuse = System.Convert.ToInt32(System.Math.Floor(Math.Sqrt(height * height + width * width)));
Bitmap rotatedImage = new Bitmap(hypotenuse, hypotenuse);
using (Graphics g = Graphics.FromImage(rotatedImage))
{
g.TranslateTransform((float)rotatedImage.Width / 2, (float)rotatedImage.Height / 2); //set the rotation point as the center into the matrix
g.RotateTransform(angle); //rotate
g.TranslateTransform(-(float)rotatedImage.Width / 2, -(float)rotatedImage.Height / 2); //restore rotation point into the matrix
g.DrawImage(bmp, (hypotenuse - width) / 2, (hypotenuse - height) / 2, width, height);
}
return rotatedImage;
}

I tried the answer of #Omar myself and realized, that the original image gets cut at the sides ... i've rewritten it so it resizes the image to the new sizes:
private static Bitmap RotateImage(Bitmap bmp, float angle)
{
float alpha = angle;
//edit: negative angle +360
while(alpha <0) alpha +=360;
float gamma = 90;
float beta = 180 - angle - gamma;
float c1 = bmp.Height;
float a1 = (float)(c1 * Math.Sin(alpha * Math.PI / 180) / Math.Sin(gamma * Math.PI / 180));
float b1 = (float)(c1 * Math.Sin(beta * Math.PI / 180) / Math.Sin(gamma * Math.PI / 180));
float c2 = bmp.Width;
float a2 = (float)(c2 * Math.Sin(alpha * Math.PI / 180) / Math.Sin(gamma * Math.PI / 180));
float b2 = (float)(c2 * Math.Sin(beta * Math.PI / 180) / Math.Sin(gamma * Math.PI / 180));
int width = Convert.ToInt32(b2 + a1);
int height = Convert.ToInt32(b1 + a2);
Bitmap rotatedImage = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(rotatedImage))
{
g.TranslateTransform(rotatedImage.Width / 2, rotatedImage.Height / 2); //set the rotation point as the center into the matrix
g.RotateTransform(angle); //rotate
g.TranslateTransform(-rotatedImage.Width / 2, -rotatedImage.Height / 2); //restore rotation point into the matrix
g.DrawImage(bmp, new Point((width - bmp.Width) / 2, (height - bmp.Height) / 2)); //draw the image on the new bitmap
}
return rotatedImage;
}

Have you tried RotateFlip?
public partial class Form1 : Form
{
Image myImage;
AnimatedGif myGif;
Bitmap bitmap;
public Form1()
{
InitializeComponent();
myImage = Image.FromFile(#"D:\fananimation.gif");
bitmap = new Bitmap(myImage);
bitmap.RotateFlip(System.Drawing.RotateFlipType.Rotate90FlipNone);
this.pictureBox1.Image = bitmap;
}
}
Source

I was using this function in VB:
Public Function RotateImage(ByRef image As Image, ByVal angle As Single) As Drawing.Bitmap
If image Is Nothing Then
Throw New ArgumentNullException("image")
End If
Dim pi2 As Single = Math.PI / 2.0
Dim oldWidth As Single = image.Width
Dim oldHeight As Single = image.Height
Dim theta As Single = angle * Math.PI / 180.0
Dim locked_theta As Single = theta
If locked_theta < 0.0 Then locked_theta += 2 * Math.PI
Dim newWidth, newHeight As Single
Dim nWidth, nHeight As Integer
Dim adjacentTop, oppositeTop As Single
Dim adjacentBottom, oppositeBottom As Single
If (locked_theta >= 0.0 And locked_theta < pi2) Or _
(locked_theta >= Math.PI And locked_theta < (Math.PI + pi2)) Then
adjacentTop = Math.Abs(Math.Cos(locked_theta)) * oldWidth
oppositeTop = Math.Abs(Math.Sin(locked_theta)) * oldWidth
adjacentBottom = Math.Abs(Math.Cos(locked_theta)) * oldHeight
oppositeBottom = Math.Abs(Math.Sin(locked_theta)) * oldHeight
Else
adjacentTop = Math.Abs(Math.Sin(locked_theta)) * oldHeight
oppositeTop = Math.Abs(Math.Cos(locked_theta)) * oldHeight
adjacentBottom = Math.Abs(Math.Sin(locked_theta)) * oldWidth
oppositeBottom = Math.Abs(Math.Cos(locked_theta)) * oldWidth
End If
newWidth = adjacentTop + oppositeBottom
newHeight = adjacentBottom + oppositeTop
nWidth = Int(Math.Ceiling(newWidth))
nHeight = Int(Math.Ceiling(newHeight))
Dim rotatedBmp As New Drawing.Bitmap(nWidth, nHeight)
Dim g As Graphics = Graphics.FromImage(rotatedBmp)
Dim points(2) As Point
If (locked_theta >= 0.0 And locked_theta < pi2) Then
points(0) = New Point(Int(oppositeBottom), 0)
points(1) = New Point(nWidth, Int(oppositeTop))
points(2) = New Point(0, Int(adjacentBottom))
ElseIf locked_theta >= pi2 And locked_theta < Math.PI Then
points(0) = New Point(nWidth, Int(oppositeTop))
points(1) = New Point(Int(adjacentTop), nHeight)
points(2) = New Point(Int(oppositeBottom), 0)
ElseIf locked_theta >= Math.PI And locked_theta < (Math.PI + pi2) Then
points(0) = New Point(Int(adjacentTop), nHeight)
points(1) = New Point(0, Int(adjacentBottom))
points(2) = New Point(nWidth, Int(oppositeTop))
Else
points(0) = New Point(0, Int(adjacentBottom))
points(1) = New Point(Int(oppositeBottom), 0)
points(2) = New Point(Int(adjacentTop), nHeight)
End If
g.DrawImage(image, points)
g.Dispose()
image.Dispose()
Return rotatedBmp
End Function

I checked the answers and they all have at least one of the following problems:
Cropping/incorrect centering
Unnecessary margin
Errors with some angle ranges
Unnecessarily complicated calculations/code
This solution can handle any angle (positive, negative, over 360° etc.). There is no cropping or excessive margins. No memory leaks either.
public Bitmap RotateBitmap(Bitmap bmp, float angle)
{
double radianAngle = angle / 180.0 * Math.PI;
double cosA = Math.Abs(Math.Cos(radianAngle));
double sinA = Math.Abs(Math.Sin(radianAngle));
int newWidth = (int)(cosA * bmp.Width + sinA * bmp.Height);
int newHeight = (int)(cosA * bmp.Height + sinA * bmp.Width);
var rotatedBitmap = new Bitmap(newWidth, newHeight);
rotatedBitmap.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
using (Graphics g = Graphics.FromImage(rotatedBitmap))
{
g.TranslateTransform(rotatedBitmap.Width / 2, rotatedBitmap.Height / 2);
g.RotateTransform(angle);
g.TranslateTransform(-bmp.Width / 2, -bmp.Height / 2);
g.DrawImage(bmp, new Point(0, 0));
}
bmp.Dispose();//Remove if you want to keep oryginal bitmap
return rotatedBitmap;
}

Depending on Timo's code, i made some improvements, with improvement, negative angles (up to -360) can be give as a parameter succesfully
private static Bitmap RotateImage(Bitmap bmp, float angle)
{
float alpha = angle;
//edit: negative angle +360
while (alpha < 0) alpha += 360;
float gamma = 90;
float beta = 180 - angle - gamma;
float c1 = bmp.Height;
float a1 = Math.Abs((float)(c1 * Math.Sin(alpha * Math.PI / 180)));
float b1 = Math.Abs((float)(c1 * Math.Sin(beta * Math.PI / 180)));
float c2 = bmp.Width;
float a2 = Math.Abs((float)(c2 * Math.Sin(alpha * Math.PI / 180)));
float b2 = Math.Abs((float)(c2 * Math.Sin(beta * Math.PI / 180)));
int width = Convert.ToInt32(b2 + a1);
int height = Convert.ToInt32(b1 + a2);
Bitmap rotatedImage = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(rotatedImage))
{
g.TranslateTransform(rotatedImage.Width / 2, rotatedImage.Height / 2); //set the rotation point as the center into the matrix
g.RotateTransform(angle); //rotate
g.TranslateTransform(-rotatedImage.Width / 2, -rotatedImage.Height / 2); //restore rotation point into the matrix
g.DrawImage(bmp, new Point((width - bmp.Width) / 2, (height - bmp.Height) / 2)); //draw the image on the new bitmap
}
return rotatedImage;
}

Related

Properly rotate an image

How to I rotate an image without it showing like this?
Here's my Rotation Method:
public static Bitmap RotateImageN(Bitmap bmp, float angle)
{
Bitmap rotatedImage = new Bitmap(bmp.Width, bmp.Height);
using (Graphics g = Graphics.FromImage(rotatedImage))
{
// Set the rotation point to the center in the matrix
g.TranslateTransform(bmp.Width / 2, bmp.Height / 2);
// Rotate
g.RotateTransform(angle);
// Restore rotation point in the matrix
g.TranslateTransform(-bmp.Width / 2, -bmp.Height / 2);
// Draw the image on the bitmap
g.DrawImage(bmp, new Point(0, 0));
}
return rotatedImage;
}
Edit: After trying Loocid's Code
Your rotatedImage Bitmap needs to be big enough to accommodate the rotated image.
Say you rotated your original image by 30° you need to get the size of the bounding box like so:
Using some basic trig:
x = L*cos(30 * π / 180) + w*cos(60 * π / 180)
y = L*sin(30 * π / 180) + w*sin(60 * π / 180)
Therefore change the start of your code to:
var x = bmp.Width * Math.Cos(angle * Math.PI / 180) + bmp.Height * Math.Cos((90-angle) * Math.PI / 180)
var y = bmp.Width * Math.Sin(angle * Math.PI / 180) + bmp.Height * Math.Sin((90-angle) * Math.PI / 180)
Bitmap rotatedImage = new Bitmap(x, y);
The issue occurs in the rotating is related to the bounding box. It is clipping the edge because of the image you provided does not fit into the area that you have given.
I also faced this issue. So I tried a solution from here.
Adding the code that works for me.
public static Bitmap RotateImageN(Bitmap bitmap, float angle)
{
Matrix matrix = new Matrix();
matrix.Translate(bitmap.Width / -2, bitmap.Height / -2, MatrixOrder.Append);
matrix.RotateAt(angle, new System.Drawing.Point(0, 0), MatrixOrder.Append);
using (GraphicsPath graphicsPath = new GraphicsPath())
{
graphicsPath.AddPolygon(new System.Drawing.Point[] { new System.Drawing.Point(0, 0), new System.Drawing.Point(bitmap.Width, 0), new System.Drawing.Point(0, bitmap.Height) });
graphicsPath.Transform(matrix);
System.Drawing.PointF[] points = graphicsPath.PathPoints;
Rectangle rectangle = boundingBox(bitmap, matrix);
Bitmap resultBitmap = new Bitmap(rectangle.Width, rectangle.Height);
using (Graphics gDest = Graphics.FromImage(resultBitmap))
{
Matrix mDest = new Matrix();
mDest.Translate(resultBitmap.Width / 2, resultBitmap.Height / 2, MatrixOrder.Append);
gDest.Transform = mDest;
gDest.DrawImage(bitmap, points);
return resultBitmap;
}
}
}
private static Rectangle boundingBox(Image image, Matrix matrix)
{
GraphicsUnit graphicsUnit = new GraphicsUnit();
Rectangle boundingRectangle = Rectangle.Round(image.GetBounds(ref graphicsUnit));
Point topLeft = new Point(boundingRectangle.Left, boundingRectangle.Top);
Point topRight = new Point(boundingRectangle.Right, boundingRectangle.Top);
Point bottomRight = new Point(boundingRectangle.Right, boundingRectangle.Bottom);
Point bottomLeft = new Point(boundingRectangle.Left, boundingRectangle.Bottom);
Point[] points = new Point[] { topLeft, topRight, bottomRight, bottomLeft };
GraphicsPath graphicsPath = new GraphicsPath(points, new byte[] { (byte)PathPointType.Start, (byte)PathPointType.Line, (byte)PathPointType.Line, (byte)PathPointType.Line });
graphicsPath.Transform(matrix);
return Rectangle.Round(graphicsPath.GetBounds());
}

How to draw an audio waveform to a bitmap

I am attempting to extract the audio content of a wav file and export the resultant waveform as an image (bmp/jpg/png).
So I have found the following code which draws a sine wave and works as expected:
string filename = #"C:\0\test.bmp";
int width = 640;
int height = 480;
Bitmap b = new Bitmap(width, height);
for (int i = 0; i < width; i++)
{
int y = (int)((Math.Sin((double)i * 2.0 * Math.PI / width) + 1.0) * (height - 1) / 2.0);
b.SetPixel(i, y, Color.Black);
}
b.Save(filename);
This works completely as expected, what I would like to do is replace
int y = (int)((Math.Sin((double)i * 2.0 * Math.PI / width) + 1.0) * (height - 1) / 2.0);
with something like
int y = converted and scaled float from monoWaveFileFloatValues
So how would I best go about doing this in the simplest manner possible?
I have 2 basic issues I need to deal with (i think)
convert float to int in a way which does not loose information, this is due to SetPixel(i, y, Color.Black); where x & y are both int
sample skipping on the x axis so the waveform fits into the defined space audio length / image width give the number of samples to average out intensity over which would be represented by a single pixel
The other options is find another method of plotting the waveform which does not rely on the method noted above. Using a chart might be a good method, but I would like to be able to render the image directly if possible
This is all to be run from a console application and I have the audio data (minus the header) already in a float array.
UPDATE 1
The following code enabled me to draw the required output using System.Windows.Forms.DataVisualization.Charting but it took about 30 seconds to process 27776 samples and whilst it does do what I need, it is far too slow to be useful. So I am still looking towards a solution which will draw the bitmap directly.
System.Windows.Forms.DataVisualization.Charting.Chart chart = new System.Windows.Forms.DataVisualization.Charting.Chart();
chart.Size = new System.Drawing.Size(640, 320);
chart.ChartAreas.Add("ChartArea1");
chart.Legends.Add("legend1");
// Plot {sin(x), 0, 2pi}
chart.Series.Add("sin");
chart.Series["sin"].LegendText = args[0];
chart.Series["sin"].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline;
//for (double x = 0; x < 2 * Math.PI; x += 0.01)
for (int x = 0; x < audioDataLength; x ++)
{
//chart.Series["sin"].Points.AddXY(x, Math.Sin(x));
chart.Series["sin"].Points.AddXY(x, leftChannel[x]);
}
// Save sin_0_2pi.png image file
chart.SaveImage(#"c:\tmp\example.png", System.Drawing.Imaging.ImageFormat.Png);
Output shown below:
So I managed to figure it out using a code sample found here, though I made some minor changes to the way I interact with it.
public static Bitmap DrawNormalizedAudio(List<float> data, Color foreColor, Color backColor, Size imageSize, string imageFilename)
{
Bitmap bmp = new Bitmap(imageSize.Width, imageSize.Height);
int BORDER_WIDTH = 0;
float width = bmp.Width - (2 * BORDER_WIDTH);
float height = bmp.Height - (2 * BORDER_WIDTH);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(backColor);
Pen pen = new Pen(foreColor);
float size = data.Count;
for (float iPixel = 0; iPixel < width; iPixel += 1)
{
// determine start and end points within WAV
int start = (int)(iPixel * (size / width));
int end = (int)((iPixel + 1) * (size / width));
if (end > data.Count)
end = data.Count;
float posAvg, negAvg;
averages(data, start, end, out posAvg, out negAvg);
float yMax = BORDER_WIDTH + height - ((posAvg + 1) * .5f * height);
float yMin = BORDER_WIDTH + height - ((negAvg + 1) * .5f * height);
g.DrawLine(pen, iPixel + BORDER_WIDTH, yMax, iPixel + BORDER_WIDTH, yMin);
}
}
bmp.Save(imageFilename);
bmp.Dispose();
return null;
}
private static void averages(List<float> data, int startIndex, int endIndex, out float posAvg, out float negAvg)
{
posAvg = 0.0f;
negAvg = 0.0f;
int posCount = 0, negCount = 0;
for (int i = startIndex; i < endIndex; i++)
{
if (data[i] > 0)
{
posCount++;
posAvg += data[i];
}
else
{
negCount++;
negAvg += data[i];
}
}
if (posCount > 0)
posAvg /= posCount;
if (negCount > 0)
negAvg /= negCount;
}
In order to get it working I had to do a couple of things prior to calling the method DrawNormalizedAudio you can see below what I needed to do:
Size imageSize = new Size();
imageSize.Width = 1000;
imageSize.Height = 500;
List<float> lst = leftChannel.OfType<float>().ToList(); //change float array to float list - see link below
DrawNormalizedAudio(lst, Color.Red, Color.White, imageSize, #"c:\tmp\example2.png");
* change float array to float list
The result of this is as follows, a waveform representation of a hand clap wav sample:
I am quite sure there needs to be some updates/revisions to the code, but it's a start and hopefully this will assist someone else who is trying to do the same thing I was.
If you can see any improvements that can be made, let me know.
UPDATES
NaN issue mentioned in the comments now resolved and code above updated.
Waveform Image updated to represent output fixed by removal of NaN values as noted in point 1.
UPDATE 1
Average level (not RMS) was determined by summing the max level for each sample point and dividing by the total number of samples. Examples of this can be seen below:
Silent Wav File:
Hand Clap Wav File:
Brownian, Pink & White Noise Wav File:
Here is a variation you may want to study. It scales the Graphics object so it can use the float data directly.
Note how I translate (i.e. move) the drawing area twice so I can do the drawing more conveniently!
It also uses the DrawLines method for drawing. The benefit in addition to speed is that the lines may be semi-transparent or thicker than one pixel without getting artifacts at the joints. You can see the center line shine through.
To do this I convert the float data to a List<PointF> using a little Linq magick.
I also make sure to put all GDI+ objects I create in using clause so they will get disposed of properly.
...
using System.Windows.Forms;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
..
..
class Program
{
static void Main(string[] args)
{
float[] data = initData(10000);
Size imgSize = new Size(1000, 400);
Bitmap bmp = drawGraph(data, imgSize , Color.Green, Color.Black);
bmp.Save("D:\\wave.png", ImageFormat.Png);
}
static float[] initData(int count)
{
float[] data = new float[count];
for (int i = 0; i < count; i++ )
{
data[i] = (float) ((Math.Sin(i / 12f) * 880 + Math.Sin(i / 15f) * 440
+ Math.Sin(i / 66) * 110) / Math.Pow( (i+1), 0.33f));
}
return data;
}
static Bitmap drawGraph(float[] data, Size size, Color ForeColor, Color BackColor)
{
Bitmap bmp = new System.Drawing.Bitmap(size.Width, size.Height,
PixelFormat.Format32bppArgb);
Padding borders = new Padding(20, 20, 10, 50);
Rectangle plotArea = new Rectangle(borders.Left, borders.Top,
size.Width - borders.Left - borders.Right,
size.Height - borders.Top - borders.Bottom);
using (Graphics g = Graphics.FromImage(bmp))
using (Pen pen = new Pen(Color.FromArgb(224, ForeColor),1.75f))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Clear(Color.Silver);
using (SolidBrush brush = new SolidBrush(BackColor))
g.FillRectangle(brush, plotArea);
g.DrawRectangle(Pens.LightGoldenrodYellow, plotArea);
g.TranslateTransform(plotArea.Left, plotArea.Top);
g.DrawLine(Pens.White, 0, plotArea.Height / 2,
plotArea.Width, plotArea.Height / 2);
float dataHeight = Math.Max( data.Max(), - data.Min()) * 2;
float yScale = 1f * plotArea.Height / dataHeight;
float xScale = 1f * plotArea.Width / data.Length;
g.ScaleTransform(xScale, yScale);
g.TranslateTransform(0, dataHeight / 2);
var points = data.ToList().Select((y, x) => new { x, y })
.Select(p => new PointF(p.x, p.y)).ToList();
g.DrawLines(pen, points.ToArray());
g.ResetTransform();
g.DrawString(data.Length.ToString("###,###,###,##0") + " points plotted.",
new Font("Consolas", 14f), Brushes.Black,
plotArea.Left, plotArea.Bottom + 2f);
}
return bmp;
}
}

Draw an ellipse with a specified "fatness" between 2 points

I have a C# bitmap object, and i am able to draw a line from point A to point B.
I have the 2 points on the edges of the diagram, and I would like to draw an ellipse from A to B. The basic g.DrawEllipse() only draws ellipses either perfectly horizontally or vertically, however I need the ellipse to be kind of diagonal from the one end of the image to the other.
My bitmap: 200 tall by 500 wide
Point A: Column 0, Row 20 (0,20)
Point B: Column 499, Row 60 (499, 60)
Widest Point: 30 - Narrow Radius of the ellipse
Here is what I have so far, the draw ellipse doesnt have the overload I need, so help there please:
using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawLine(pen, new Point(20,0), new Point(499,60));
g.DrawEllipse(pen, 20, 0, someWidth, someHeight);
}
Here is how to use the DrawEllipse method from a rotation, the minor axis and two vertices.
First we calculate the Size of the bounding Rectangle:
Given the Points A and B sitting on the short sides of length smallSize we get the long side with a little Pythagoras:
int longSide = (int)(Math.Sqrt((A.Y - B.Y) * (A.Y - B.Y) + (B.X - A.X) * (B.X - A.X)));
So :
Size size = new System.Drawing.Size(longSide, smallSize);
Next we need the rotation angle:
float angle = -(float)(Math.Atan2(A.Y - B.Y, B.X - A.X) * 180f / Math.PI);
And it will make things easier to also get the center Point C:
Point C = new Point((A.X + B.X)/ 2, (A.Y + B.Y)/ 2);
The last thing we want is a routine that draws an ellipse of a given Size, rotated around C at an angle:
void DrawEllipse(Graphics G, Pen pen, Point center, Size size, float angle)
{
int h2 = size.Height / 2;
int w2 = size.Width / 2;
Rectangle rect = new Rectangle( new Point(center.X - w2, center.Y - h2), size );
G.TranslateTransform(center.X, center.Y);
G.RotateTransform(angle);
G.TranslateTransform(-center.X, -center.Y);
G.DrawEllipse(pen, rect);
G.ResetTransform();
}
Here is a little testbed that brings it all together:
Point A = new Point(200, 200); // *
Point B = new Point(500, 250);
int smallSize = 50;
void doTheDraw(PictureBox pb)
{
Bitmap bmp = new Bitmap(pb.Width, pb.Height);
float angle = -(float)(Math.Atan2(A.Y - B.Y, B.X - A.X) * 180f / Math.PI);
int longSide = (int)(Math.Sqrt((A.Y - B.Y) * (A.Y - B.Y) + (B.X - A.X) * (B.X - A.X)));
Point C = new Point((A.X + B.X) / 2, (A.Y + B.Y) / 2);
Size size = new System.Drawing.Size((int)longSide, smallSize);
using (Pen pen = new Pen(Color.Orange, 3f))
using (Graphics g = Graphics.FromImage(bmp))
{
// a nice background grid (optional):
DrawGrid(g, 0, 0, 100, 50, 10,
Color.LightSlateGray, Color.DarkGray, Color.Gainsboro);
// show the points we use (optional):
g.FillEllipse(Brushes.Red, A.X - 4, A.Y - 4, 8, 8);
g.FillRectangle(Brushes.Red, B.X - 3, B.Y - 3, 7, 7);
g.FillEllipse(Brushes.Red, C.X - 5, C.Y - 5, 11, 11);
// show the connection line (optional):
g.DrawLine(Pens.Orange, A, B);
// here comes the ellipse:
DrawEllipse(g, pen, C, size, angle);
}
pb.Image = bmp;
}
The grid is a nice helper:
void DrawGrid(Graphics G, int ox, int oy,
int major, int medium, int minor, Color c1, Color c2, Color c3)
{
using (Pen pen1 = new Pen(c1, 1f))
using (Pen pen2 = new Pen(c2, 1f))
using (Pen pen3 = new Pen(c3, 1f))
{
pen2.DashStyle = DashStyle.Dash;
pen3.DashStyle = DashStyle.Dot;
for (int x = ox; x < G.VisibleClipBounds.Width; x += major)
G.DrawLine(pen1, x, 0, x, G.VisibleClipBounds.Height);
for (int y = oy; y < G.VisibleClipBounds.Height; y += major)
G.DrawLine(pen1, 0, y, G.VisibleClipBounds.Width, y);
for (int x = ox; x < G.VisibleClipBounds.Width; x += medium)
G.DrawLine(pen2, x, 0, x, G.VisibleClipBounds.Height);
for (int y = oy; y < G.VisibleClipBounds.Height; y += medium)
G.DrawLine(pen2, 0, y, G.VisibleClipBounds.Width, y);
for (int x = ox; x < G.VisibleClipBounds.Width; x += minor)
G.DrawLine(pen3, x, 0, x, G.VisibleClipBounds.Height);
for (int y = oy; y < G.VisibleClipBounds.Height; y += minor)
G.DrawLine(pen3, 0, y, G.VisibleClipBounds.Width, y);
}
}
Note that I made A, B, smallSide class level variables so I can modify them during my tests, (and I did *)..
As you can see I have added a TrackBar to make the smallside dynamic; for even more fun I have added this MouseClick event:
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left)) A = e.Location;
else B = e.Location;
doTheDraw(pictureBox1);
}
Note that I didn't care for disposing of the old Bitmap; you should, of course..!
If you wish to use Graphics to create a diagonal ellipse, perhaps you can use DrawBezier() method.
Here is some code that does it:
// Draws an ellipse using 2 beziers.
private void DrawEllipse(Graphics g, PointF center, float width, float height, double rotation)
{
// Unrotated ellipse frame
float left = center.X - width / 2;
float right = center.X + width / 2;
float top = center.Y - height / 2;
float bottom = center.Y + height / 2;
PointF p1 = new PointF(left, center.Y);
PointF p2 = new PointF(left, top);
PointF p3 = new PointF(right, top);
PointF p4 = new PointF(right, center.Y);
PointF p5 = new PointF(right, bottom);
PointF p6 = new PointF(left, bottom);
// Draw ellipse with rotated points.
g.DrawBezier(Pens.Black, Rotate(p1, center, rotation), Rotate(p2, center, rotation), Rotate(p3, center, rotation), Rotate(p4, center, rotation));
g.DrawBezier(Pens.Black, Rotate(p4, center, rotation), Rotate(p5, center, rotation), Rotate(p6, center, rotation), Rotate(p1, center, rotation));
}
// Rotating a given point by given angel around a given pivot.
private PointF Rotate(PointF point, PointF pivot, double angle)
{
float x = point.X - pivot.X;
float y = point.Y - pivot.Y;
double a = Math.Atan(y / x);
if (x < 0)
{
a += Math.PI;
}
float size = (float)Math.Sqrt(x * x + y * y);
double newAngel = a + angle;
float newX = ((float)Math.Cos(newAngel) * size);
float newY = ((float)Math.Sin(newAngel) * size);
return pivot + new SizeF(newX, newY);
}
The above code computes the frame of the ellipse (proir to the rotation) at points p1, p2, ..., p6. And then, draws the ellipse as 2 beziers with the ellipse frame rotated points.

Rotating several images radially around a center

I am trying to build an app that loads n number of images and transform them radially around a center and show the result in image control. I am able to load two image by defining each of their locations and transform them by using this code:
private void button1_Click(object sender, RoutedEventArgs e)
{
BitmapImage src = new BitmapImage();
src.BeginInit();
src.UriSource = new Uri(#"Location\Car\Car89.png", UriKind.Absolute);
src.CacheOption = BitmapCacheOption.OnLoad;
src.EndInit();
double deg = 90;
double rad = deg / 180.0 * Math.PI;
//get the bounds of the rotated image
double w = Math.Abs(Math.Cos(-rad) * src.PixelWidth) + Math.Abs(Math.Sin(-rad) * src.PixelHeight);
double h = Math.Abs(Math.Sin(-rad) * src.PixelWidth) + Math.Abs(Math.Cos(-rad) * src.PixelHeight);
BitmapImage src2 = new BitmapImage();
src2.BeginInit();
src2.UriSource = new Uri(#"Location\Car\Car269.png", UriKind.Absolute);
src2.CacheOption = BitmapCacheOption.OnLoad;
src2.EndInit();
double w2 = Math.Abs(Math.Cos(rad) * src2.PixelWidth) + Math.Abs(Math.Sin(rad) * src2.PixelHeight);
double h2 = Math.Abs(Math.Sin(rad) * src2.PixelWidth) + Math.Abs(Math.Cos(rad) * src2.PixelHeight);
Double radius = 50;
//Size sz = new Size(w + w2 + radius * 2, h + h2 + radius * 2);
double c1X = Math.Abs(Math.Sin(rad)) * (radius + src.PixelHeight / 2.0);
double c1Y = Math.Abs(Math.Cos(rad)) * (radius + src.PixelHeight / 2.0);
Size sz = new Size((w / 2 + c1X) * 2.0, (h / 2 + c1Y) * 2.0);
DrawingVisual dv = new DrawingVisual();
RenderOptions.SetBitmapScalingMode(dv, BitmapScalingMode.Fant);
RenderTargetBitmap rtb = null;
using (DrawingContext dc = dv.RenderOpen())
{
dc.DrawRectangle(Brushes.Black, null, new Rect(0, 0, (int)sz.Width, (int)sz.Height));
dc.DrawEllipse(Brushes.White, null, new Point(sz.Width / 2.0, sz.Height / 2.0), radius, radius);
//translate to first center
dc.PushTransform(new TranslateTransform(sz.Width / 2.0 - c1X, sz.Height / 2.0 - c1Y));
//rotate
dc.PushTransform(new RotateTransform(-deg));
//draw
dc.DrawImage(src, new Rect(-src.PixelWidth / 2.0, -src.PixelHeight / 2.0, src.PixelWidth, src.PixelHeight));
dc.Pop();
dc.Pop();
dc.PushTransform(new TranslateTransform(sz.Width / 2.0 + c1X, sz.Height / 2.0 - c1Y));
dc.PushTransform(new RotateTransform(deg));
dc.DrawImage(src2, new Rect(-src2.PixelWidth / 2.0, -src2.PixelHeight / 2.0, src2.PixelWidth, src2.PixelHeight));
dc.Pop();
dc.Pop();
}
rtb = new RenderTargetBitmap((int)sz.Width, (int)sz.Height, 96, 96, PixelFormats.Pbgra32);
RenderOptions.SetBitmapScalingMode(rtb, BitmapScalingMode.Fant);
rtb.Render(dv);
this.image1.Source = rtb;
}
But I wasn't able to make it load n number of images automatically named for example Car0.png, Car1.png,..., CarN.png
How can I achieve this?
Sincerely,
Samuel Farid

rotating an image modifies the resolution and clarity

Consider the code below to rotate an image.
The problem is the resolution of the image is getting low and the image is getting unclear.
How can I avoid this problem?
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);
//move rotation point to center of image
g.TranslateTransform((float)this.Width / 2, (float)this.Height / 2);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
//rotate
g.RotateTransform(angle);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.TranslateTransform(-(float)this.Width / 2, -(float)this.Height / 2);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
//draw passed in image onto graphics object
g.DrawImage(b, new Point(0, 0));
g.Dispose();
return returnBitmap;
}
Repeated rotations will cause quality to drop as the repeated interpolations take their toll on the image. The best way to avoid this is to only rotate the source image once. If you're building a system in which an image gets rotated multiple times simply rotate the source image by the total rotation amount instead of applying small delta rotations to an already rotated image each time.
I have a method that I use to do rotation and it works without reducing quality of image dramatically (that I have seen).
public static Bitmap RotateImage(Image image, float angle)
{
if(image == null)
throw new ArgumentNullException("image");
const double pi2 = Math.PI / 2.0;
double oldWidth = (double) image.Width;
double oldHeight = (double) image.Height;
// Convert degrees to radians
double theta = ((double) angle) * Math.PI / 180.0;
double locked_theta = theta;
// Ensure theta is now [0, 2pi)
while( locked_theta < 0.0 )
locked_theta += 2 * Math.PI;
double newWidth, newHeight;
int nWidth, nHeight; // The newWidth/newHeight expressed as ints
double adjacentTop, oppositeTop;
double adjacentBottom, oppositeBottom;
if( (locked_theta >= 0.0 && locked_theta < pi2) ||
(locked_theta >= Math.PI && locked_theta < (Math.PI + pi2) ) )
{
adjacentTop = Math.Abs(Math.Cos(locked_theta)) * oldWidth;
oppositeTop = Math.Abs(Math.Sin(locked_theta)) * oldWidth;
adjacentBottom = Math.Abs(Math.Cos(locked_theta)) * oldHeight;
oppositeBottom = Math.Abs(Math.Sin(locked_theta)) * oldHeight;
}
else
{
adjacentTop = Math.Abs(Math.Sin(locked_theta)) * oldHeight;
oppositeTop = Math.Abs(Math.Cos(locked_theta)) * oldHeight;
adjacentBottom = Math.Abs(Math.Sin(locked_theta)) * oldWidth;
oppositeBottom = Math.Abs(Math.Cos(locked_theta)) * oldWidth;
}
newWidth = adjacentTop + oppositeBottom;
newHeight = adjacentBottom + oppositeTop;
nWidth = (int) Math.Ceiling(newWidth);
nHeight = (int) Math.Ceiling(newHeight);
Bitmap rotatedBmp = new Bitmap(nWidth, nHeight);
using(Graphics g = Graphics.FromImage(rotatedBmp))
{
Point [] points;
if( locked_theta >= 0.0 && locked_theta < pi2 )
{
points = new Point[] {
new Point( (int) oppositeBottom, 0 ),
new Point( nWidth, (int) oppositeTop ),
new Point( 0, (int) adjacentBottom )
};
}
else if( locked_theta >= pi2 && locked_theta < Math.PI )
{
points = new Point[] {
new Point( nWidth, (int) oppositeTop ),
new Point( (int) adjacentTop, nHeight ),
new Point( (int) oppositeBottom, 0 )
};
}
else if( locked_theta >= Math.PI && locked_theta < (Math.PI + pi2) )
{
points = new Point[] {
new Point( (int) adjacentTop, nHeight ),
new Point( 0, (int) adjacentBottom ),
new Point( nWidth, (int) oppositeTop )
};
}
else
{
points = new Point[] {
new Point( 0, (int) adjacentBottom ),
new Point( (int) oppositeBottom, 0 ),
new Point( (int) adjacentTop, nHeight )
};
}
g.DrawImage(image, points);
}
return rotatedBmp;
}
sample code with RotateFlip:
Bitmap bitmap1;
private void InitializeBitmap()
{
try
{
bitmap1 = (Bitmap)Bitmap.FromFile(#"C:\test.bmp");
PictureBox1.SizeMode = PictureBoxSizeMode.AutoSize;
PictureBox1.Image = bitmap1;
}
catch(System.IO.FileNotFoundException)
{
MessageBox.Show("There was an error." +
"Check the path to the bitmap.");
}
}
private void Button1_Click(System.Object sender, System.EventArgs e)
{
if (bitmap1 != null)
{
bitmap1.RotateFlip(RotateFlipType.Rotate180FlipNone);
PictureBox1.Image = bitmap1;
}
}
Specifies how much an image is rotated and the axis used to flip the image.
http://msdn.microsoft.com/en-us/library/system.drawing.rotatefliptype.aspx

Categories

Resources