Scaling an image using the mouse in a WinForms application? - c#

I'm trying to use the position of the mouse to calculate the scaling factor for scaling an image. Basically, the further you get away from the center of the image, the bigger it gets; and the closer to the center you get, the smaller it gets. I have some code so far but it's acting really strange and I have absolutely no more ideas. First I'll let you know, one thing I was trying to do is average out 5 distances to get a more smooth resize animation. Here's my code:
private void pictureBoxScale_MouseMove(object sender, MouseEventArgs e)
{
if (rotateScaleMode && isDraggingToScale)
{
// For Scaling
int sourceWidth = pictureBox1.Image.Width;
int sourceHeight = pictureBox1.Image.Height;
float dCurrCent = 0; // distance between the current mouse pos and the center of the image
float dPrevCent = 0; // distance between the previous mouse pos and the center of the image
System.Drawing.Point imgCenter = new System.Drawing.Point();
imgCenter.X = pictureBox1.Location.X + (sourceWidth / 2);
imgCenter.Y = pictureBox1.Location.Y + (sourceHeight / 2);
// Calculating the distance between the current mouse location and the center of the image
dCurrCent = (float)Math.Sqrt(Math.Pow(e.X - imgCenter.X, 2) + Math.Pow(e.Y - imgCenter.Y, 2));
// Calculating the distance between the previous mouse location and the center of the image
dPrevCent = (float)Math.Sqrt(Math.Pow(prevMouseLoc.X - imgCenter.X, 2) + Math.Pow(prevMouseLoc.Y - imgCenter.Y, 2));
if (smoothScaleCount < 5)
{
dCurrCentSmooth[smoothScaleCount] = dCurrCent;
dPrevCentSmooth[smoothScaleCount] = dPrevCent;
}
if (smoothScaleCount == 4)
{
float currCentSum = 0;
float prevCentSum = 0;
for (int i = 0; i < 4; i++)
{
currCentSum += dCurrCentSmooth[i];
}
for (int i = 0; i < 4; i++)
{
prevCentSum += dPrevCentSmooth[i];
}
float scaleAvg = (currCentSum / 5) / (prevCentSum / 5);
int destWidth = (int)(sourceWidth * scaleAvg);
int destHeight = (int)(sourceHeight * scaleAvg);
// If statement is for limiting the size of the image
if (destWidth > (currentRotatedImage.Width / 2) && destWidth < (currentRotatedImage.Width * 3) && destHeight > (currentRotatedImage.Height / 2) && destWidth < (currentRotatedImage.Width * 3))
{
AForge.Imaging.Filters.ResizeBilinear resizeFilter = new AForge.Imaging.Filters.ResizeBilinear(destWidth, destHeight);
pictureBox1.Image = resizeFilter.Apply((Bitmap)currentRotatedImage);
pictureBox1.Size = pictureBox1.Image.Size;
pictureBox1.Refresh();
}
smoothScaleCount = -1;
}
prevMouseLoc = e.Location;
currentScaledImage = pictureBox1.Image;
smoothScaleCount++;
}
}
EDIT: Thanks to Ben Voigt and Ray everything works well now. The only thing wrong is that with the way I'm doing it the image doesn't keep it's ratio; but I'll fix that later. Here's what I have for those who want to know:
private void pictureBoxScale_MouseMove(object sender, MouseEventArgs e)
{
if (rotateScaleMode && isDraggingToScale)
{
// For Scaling
int sourceWidth = pictureBox1.Image.Width;
int sourceHeight = pictureBox1.Image.Height;
int scale = e.X + p0.X; //p0 is the location of the mouse when the button first came down
int destWidth = (int)(sourceWidth + (scale/10)); //I divide it by 10 to make it slower
int destHeight = (int)(sourceHeight + (scale/10));
if (destWidth > 20 && destWidth < 1000 && destHeight > 20 && destWidth < 1000)
{
AForge.Imaging.Filters.ResizeBilinear resizeFilter = new AForge.Imaging.Filters.ResizeBilinear(destWidth, destHeight);
pictureBox1.Image = resizeFilter.Apply((Bitmap)currentRotatedImage);
pictureBox1.Size = pictureBox1.Image.Size;
pictureBox1.Refresh();
}
currentScaledImage = pictureBox1.Image; // This is only so I can rotate the scaled image in another part of my program
}
}

You're scaling won't be smooth if you use the center of the image. Instead, use the initial mouse down point (call it p0). Also, rather than using the distance from that point to the current drag point (e), just take the difference along one axis (e.g. exp(e.Y - p0.Y)).

It looks to me (from the scaleAvg calculation) like you're rescaling the already-scaled image. This is a really bad idea because scaling is lossy and the errors will accumulate. Instead, keep a copy of the crisp original image and scale the original directly to the current size.
Also, I would suggest using a different norm, perhaps Manhattan distance, instead of the current Cartesian distance which is a two-norm.
If you do continue using the two-norm, consider getting rid of the Math.Pow calls. They are probably such a small part of the overall scaling complexity that it doesn't matter, but multiplying by itself should be much faster than Math.Pow for squaring a number.

Related

Scatter Graph In C# With PictureBox

There is an input of points with size of n like below:
S = {x1,y1,x2,y2,...,xn,yn}
I want to display scatter graph of S sequence in a picture box. So for transforming them into picture box dimensions, I have normalized them and multiplied them by width and height of picture box with respecting picture box left and top:
waveData= wave.GetWaveData();
normalizedData = GetSignedNormalized();
n = normalizedData.Count;
picW = pictureBox1.Width;
picH = pictureBox1.Height;
picL = pictureBox1.Left;
picT = pictureBox1.Top;
normalizedInPictureBox = new List<float>();
for (int i=0;i< n; i +=2)
{
float px = normalizedData[i];
float py = normalizedData[i+1];
px = px * (picW - picL);
py = py * (picH - picT) ;
normalizedInPictureBox.Add(px);
normalizedInPictureBox.Add(py);
}
Normalize Method is also:
public List<float> GetSignedNormalized()
{
List<float> data = new List<float>();
short max = waveData.Max();
int m = waveData.Count;
for(int i=0;i< m; i++)
{
data.Add((float)waveData[i] / (float)max);
}
return data;
}
Now I am thinking normalizedInPictureBox List contains vertices in the range of picture box, and here is the code for drawing them on picture box:
In the paint method of picture box:
Graphics gr = e.Graphics;
gr.Clear(Color.Black);
for(int i=0;i< n; i +=2)
{
float x = normalizedInPictureBox[i] ;
float y = normalizedInPictureBox[i+1];
gr.FillEllipse(Brushes.Green, new RectangleF(x, y, 2.25f, 2.25f));
}
But the result is shown below:
I don't Know whats going wrong here , but I think the graph should be horizontal not diagonal ,the desire result is something like this:
I know that I can transform it to center of picture box after this. but How can change my own result to the desire one?
Thanks in advance.
I don't really know why your code doesn't work correctly without having a look at the actual data and playing around with it, but having done chart drawing before, I suggest you go the full way and clearly define your axis ranges and do proper interpolating. It get's much clearer from there.
Here is what I came up with
static Bitmap DrawChart(float[] Values, int Width, int Height)
{
var n = Values.Count();
if (n % 2 == 1) throw new Exception("Invalid data");
//Split the data into lists for easy access
var x = new List<float>();
var y = new List<float>();
for (int i = 0; i < n - 1; i += 2)
{
x.Add(Values[i]);
y.Add(Values[i + 1]);
}
//Chart axis limits, change here to get custom ranges like -1,+1
var minx = x.Min();
var miny = y.Min();
var maxx = x.Max();
var maxy = y.Max();
var dxOld = maxx - minx;
var dyOld = maxy - miny;
//Rescale the y-Range to add a border at the top and bottom
miny -= dyOld * 0.2f;
maxy += dyOld * 0.2f;
var dxNew = (float)Width;
var dyNew = (float)Height;
//Draw the data
Bitmap res = new Bitmap(Width, Height);
using (var g = Graphics.FromImage(res))
{
g.Clear(Color.Black);
for (int i = 0; i < x.Count; i++)
{
//Calculate the coordinates
var px = Interpolate(x[i], minx, maxx, 0, dxNew);
var py = Interpolate(y[i], miny, maxy, 0, dyNew);
//Draw, put the ellipse center around the point
g.FillEllipse(Brushes.ForestGreen, px - 1.0f, py - 1.0f, 2.0f, 2.0f);
}
}
return res;
}
static float Interpolate(float Value, float OldMin, float OldMax, float NewMin, float NewMax)
{
//Linear interpolation
return ((NewMax - NewMin) / (OldMax - OldMin)) * (Value - OldMin) + NewMin;
}
It should be relatively self explanatory. You may consider drawing lines instead of single points, that depends on the look and feel you want to achive. Draw other chart elements to your liking.
Important: The y-Axis is actually inversed in the code above, so positive values go down, negative go up, it is scaled like the screen coordinates. You'll figure out how to fix that :-)
Example with 5000 random-y points (x is indexed):

Zooming graphics without scrolling

I have a user control and I'm using ScaleTransform() to implement zoom.
However, in order to keep the center content in the center after the zoom, it is also necessary to scroll. For example, if I zoom in (make things bigger), the X and Y origin should increase so that most of the content does not move down and to the right. (That is, as I zoom in, some of the content should disappear to the left and top.)
Has anyone worked out the calculations of how much to scroll in the X and Y direction in response to a zoom?
For example:
e.Graphics.ScaleTransform(2.0F, 2.0F);
e.Graphics.TranslateTransform(?, ?);
What would be my arguments to TranslateTransform() be so that the center part of the content remains at the center?
Note: I am not displaying an image. I am drawing the graphic content to the surface of my user control.
Or perhaps there's an even easier way?
This should work and I can't imagine any easier way; it assumes you have decided on the center of the zooming. I have chosen to draw centered on the panel:
float zoom = 1f;
private void drawPanel1_Paint(object sender, PaintEventArgs e)
{
Point c = new Point(drawPanel1.ClientSize.Width / 2, drawPanel1.ClientSize.Height / 2);
// a blue sanity check for testing
e.Graphics.FillEllipse(Brushes.DodgerBlue, c.X - 3, c.Y - 3, 6, 6);
// the offsets you were looking for:
float ox = c.X * ( zoom - 1f);
float oy = c.Y * ( zoom - 1f);
// first move and then scale
e.Graphics.TranslateTransform(-ox, -oy);
e.Graphics.ScaleTransform(zoom, zoom);
// now we can draw centered around our point c
Size sz = new Size(300, 400);
int count = 10;
int wx = sz.Width / count;
int wy = sz.Height / count;
for (int i = 0; i < count; i++)
{
Rectangle r = new Rectangle(c.X - i * wx / 2 , c.Y - i * wy / 2, i * wx, i * wy );
e.Graphics.DrawRectangle(Pens.Red, r );
}
}
Note the order of moving and scaling!
I guess you are using some differet interface, but in my case, that's what got the job done (for leaving the mouse in it's original location on the draw after the mouse wheel event):
private void DrawPb_MouseWheel(object sender, MouseEventArgs e)
{
// e contains current mouse location and the wheel direction
int wheelDirection = e.Delta / Math.Abs(e.Delta); // is 'in' or 'out' (1 or -1).
double factor = Math.Exp(wheelDirection * Constants.ZoomFactor); // divide or multiply
double newX = e.X - e.X / factor; // what used to be x is now newX
double newY = e.Y - e.Y / factor; // same for y
Point offset = new Point((int)(-newX), (int)(-newY)); // the offset of the old point to it's new location
Graph.AddOffset(offset); // apply offset
}

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

Crop an image with a different screen resolution

What I need is to crop images at the same place but with different resolution.
For example:
Image 1 created with 1024 x 768
Image 2 created with 1440 x 900
Now I have to crop images but at the same place let's say it will be
X = 10%
Y = 10%
WIDTH = 30%
HEIGHT = 20%
I use the following code to do it but it doesn't work like I need.
Any clue?
THANK YOU!!!
int x = 0;
int y = 0;
int w = 0;
int h = 0;
int inputX = 10;
int inputY = 10;
int inputW = 20;
int inputH = 30;
x = int.Parse(Math.Round(decimal.Parse((__Bitmap.Width * inputX / 100).ToString()), 0).ToString());
y = int.Parse(Math.Round(decimal.Parse((__Bitmap.Height * inputY / 100).ToString()), 0).ToString());
w = int.Parse(Math.Round(decimal.Parse((__Bitmap.Width * inputW / 100).ToString()), 0).ToString());
h = int.Parse(Math.Round(decimal.Parse((__Bitmap.Height * inputH / 100).ToString()), 0).ToString());
Rectangle cropArea = new Rectangle(x, y, w,h);
Bitmap bmpCrop = __Bitmap.Clone(cropArea, __Bitmap.PixelFormat);
I mean if there technically logic to do it?
I guess I can do like (pseudo-code)
if (Resolution == "1024x768")
int inputX = 10;
int inputY = 10;
int inputW = 20;
int inputH = 30;
else if (Resolution == "1440x900")
int inputX = 8;
int inputY = 8;
int inputW = 19;
int inputH = 28;
and etc...
I am not sure of there is any coefficient to recalculate % depending on resolution to do it... It is like a crop-factor.
UPDATE:
A quick and dirty example of what I mean by, you'll always get the same image section, when calculating your crop window with percentages:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// 100x80 image
Image asdf = Image.FromFile("asdf.bmp", true);
// twice the size, 200x160
Image asdf2 = Image.FromFile("asdf2.bmp", true);
// same image, different aspect ratio: 200x80
Image asdf3 = Image.FromFile("asdf3.bmp", true);
Bitmap asdfBmp = new Bitmap(asdf);
Bitmap asdf2Bmp = new Bitmap(asdf2);
Bitmap asdf3Bmp = new Bitmap(asdf3);
pictureBox1.Image = cropImage(asdfBmp);
pictureBox2.Image = cropImage(asdf2Bmp);
pictureBox3.Image = cropImage(asdf3Bmp);
}
private Bitmap cropImage(Bitmap sourceBitmap)
{
double x = 0;
double y = 0;
double w = 0;
double h = 0;
double inputX = 10;
double inputY = 10;
double inputW = 50;
double inputH = 50;
// integer division " 10 / 100 " will return 0, use doubles or floats.
// furthermore you don't have to convert anything to a string or back here.
x = sourceBitmap.Width * inputX / 100.0;
y = sourceBitmap.Height * inputY / 100.0;
w = sourceBitmap.Width * inputW / 100.0;
h = sourceBitmap.Height * inputH / 100.0;
// casting to int will just cut off all decimal places. you could also round.
Rectangle cropArea = new Rectangle((int)x, (int)y, (int)w, (int)h);
return sourceBitmap.Clone(cropArea, sourceBitmap.PixelFormat);
}
}
Sources:
Result:
As you can see, all result images show the same section of the image. So I either still don't get what you're aiming at, or your error must be somewhere else.
Considering your unnecessary type conversions and integer division bug, you should perhaps have a look at a c# tutorial about types.
First calculate the center of the crop. I assume that you get somehow the required x,y,w,h values. Then this center point need to be recalculated to the center of the second image: i.e. if the center is [25;50], then for the 1024x768 image it is respectively [25/1024;50/768], which gives [2.44%;6.51%]. So on the second image, let's say 1440x900 it gives us [1440*2.44%;900*6.51%] => [35;59] in pixels of course.
Now you need width and height of the new image. If the aspect ratio is the same it is easy, because you can calculate dimensions as a cropWidth/firstImageWidth*secondImageWidth, but otherwise you need to multiply it by the correct aspect ratio.
Anyway I don't think you understand the problem. If the aspect ratios of similar images are different, it is either different part of the image or image is distorted.
Below I've corrected your example. I won't explain it because it's quite obvious.. I hope. Just take a look at the parts covered by the transparent black and white areas...

How to retrieve zoom factor of a WinForms PictureBox?

I need the precise position of my mouse pointer over a PictureBox.
I use the MouseMove event of the PictureBox.
On this PictureBox, I use the "zoom" property to show an image.
What is the correct way for getting the position of the mouse on the original (unzoomed) image?
Is there a way to find the scale factor and use it?
I think need to use imageOriginalSize/imageShowedSize to retrieve this scale factor.
I use this function:
float scaleFactorX = mypic.ClientSize.Width / mypic.Image.Size.Width;
float scaleFactorY = mypic.ClientSize.Height / mypic.Image.Size.Height;
Is possible to use this value to get the correct position of the cursor over the image?
I had to solve this same problem today. I wanted it to work for images of any width:height ratio.
Here's my method to find the point 'unscaled_p' on the original full-sized image.
Point p = pictureBox1.PointToClient(Cursor.Position);
Point unscaled_p = new Point();
// image and container dimensions
int w_i = pictureBox1.Image.Width;
int h_i = pictureBox1.Image.Height;
int w_c = pictureBox1.Width;
int h_c = pictureBox1.Height;
The first trick is to determine if the image is a horizontally or vertically larger relative to the container, so you'll know which image dimension fills the container completely.
float imageRatio = w_i / (float)h_i; // image W:H ratio
float containerRatio = w_c / (float)h_c; // container W:H ratio
if (imageRatio >= containerRatio)
{
// horizontal image
float scaleFactor = w_c / (float)w_i;
float scaledHeight = h_i * scaleFactor;
// calculate gap between top of container and top of image
float filler = Math.Abs(h_c - scaledHeight) / 2;
unscaled_p.X = (int)(p.X / scaleFactor);
unscaled_p.Y = (int)((p.Y - filler) / scaleFactor);
}
else
{
// vertical image
float scaleFactor = h_c / (float)h_i;
float scaledWidth = w_i * scaleFactor;
float filler = Math.Abs(w_c - scaledWidth) / 2;
unscaled_p.X = (int)((p.X - filler) / scaleFactor);
unscaled_p.Y = (int)(p.Y / scaleFactor);
}
return unscaled_p;
Note that because Zoom centers the image, the 'filler' length has to be factored in to determine the dimension that is not filled by the image. The result, 'unscaled_p', is the point on the unscaled image that 'p' correlates to.
Hope that helps!
If I have understood you correctly I believe you would want to do something of this nature:
Assumption: the PictureBox fits to the image width/height, there is no space between the border of the PictureBox and the actual image.
ratioX = e.X / pictureBox.ClientSize.Width;
ratioY = e.Y / pictureBox.ClientSize.Height;
imageX = image.Width * ratioX;
imageY = image.Height * ratioY;
this should give you the points ot the pixel in the original image.
Here is a simple function to solve this:
private Point RemapCursorPosOnZoomedImage(PictureBox pictureBox, int x, int y, out bool isInImage)
{
// original size of image in pixel
float imgSizeX = pictureBox.Image.Width;
float imgSizeY = pictureBox.Image.Height;
// current size of picturebox (without border)
float cSizeX = pictureBox.ClientSize.Width;
float cSizeY = pictureBox.ClientSize.Height;
// calculate scale factor for both sides
float facX = (cSizeX / imgSizeX);
float facY = (cSizeY / imgSizeY);
// use smaller one to fit picturebox zoom layout
float factor = Math.Min(facX, facY);
// calculate current size of the displayed image
float rSizeX = imgSizeX * factor;
float rSizeY = imgSizeY * factor;
// calculate offsets because image is centered
float startPosX = (cSizeX - rSizeX) / 2;
float startPosY = (cSizeY - rSizeY) / 2;
float endPosX = startPosX + rSizeX;
float endPosY = startPosY + rSizeY;
// check if cursor hovers image
isInImage = true;
if (x < startPosX || x > endPosX) isInImage = false;
if (y < startPosY || y > endPosY) isInImage = false;
// remap cursor position
float cPosX = ((float)x - startPosX) / factor;
float cPosY = ((float)y - startPosY) / factor;
// create new point with mapped coords
return new Point((int)cPosX, (int)cPosY);
}

Categories

Resources