I have to display images in a picturebox. The images are high resolution scans of archives. Because of this high resolution my panning en zoom features are very slow. To solve this problem I reduced the bitmap width and length while maintaining the images readable. In my code in drawOriginalImage(); the variable "quality" is thus the factor which I reduce the size of the bitmap. This is how I did it:
private void drawOriginalImage(int quality) {
try {
int x = originalImage.Width / quality,
y = originalImage.Height / quality;
pictureBox.Image = (Image)new Bitmap(originalImage, x, y);
pictureBox.SizeMode = PictureBoxSizeMode.Zoom;
}
catch (Exception ex) {
throw ex;
}
}
But this solution brings another problem. This step can be very long:
pictureBox.Image = (Image)new Bitmap(originalImage, x, y);
Because of this slowness, I wanted to process this step with the Backgroundworker feature. Now my code looks like this:
private void drawOriginalImage(int quality) {
Cursor = Cursors.AppStarting;
backgroundWorker.RunWorkerAsync(new Point(
originalImage.Width / quality,
originalImage.Height / quality
));
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
Point p = (Point)e.Argument;
e.Result = new Bitmap(originalImage, p.X, p.Y);
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if (e.Error != null) {
MessageBox.Show("Image too big.\nOriginal error:\n" + e.Error);
}
else {
pictureBox.Image = (Image)e.Result;
pictureBox.SizeMode = PictureBoxSizeMode.Zoom;
Cursor = Cursors.Default;
}
}
But it doesn't work. I see the cursor changing from "Cursors.AppStarting" to "Cursors.Default" and thus the task is probably completed. But there is no image in my picturebox? How can that be? What am I doing wrong?
When I debug it, the program never goes in "backgroundWorker_RunWorkerCompleted". How can it be?
I have found it. I had hook up all my events correctly by adding this:
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
Related
I am using AForge.video.dll and AForge.video.DirectShow.dll.
I want to save the exact image which is displayed in picturebox at the time of capture (pbPhoto is the picturebox I used for displaying the camera video)
void cam_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
Bitmap bit = (Bitmap)eventArgs.Frame.Clone();
pbPhoto.Image = bit;
}
private void btImage_Click(object sender, EventArgs e)
{
try
{
photo = true;
if (!scanFlag)
{
btPrintPass.Enabled = false;
scanFlag = true;
cam = new VideoCaptureDevice(webcam[comboBox1.SelectedIndex].MonikerString);
cam.NewFrame += new NewFrameEventHandler(cam_NewFrame);
cam.Start();
this.btImage.Text = "Stop Scan";
}
else
{
btPrintPass.Enabled = true;
scanFlag = false;
if (cam.IsRunning)
{
cam.Stop();
}
this.btImage.Text = "Scan Photo";
string path = "temp.Jpeg";
if (pbPhoto.Image != null)
{
pbPhoto.Image.Save(path, ImageFormat.Jpeg);
//this.pictureBox1.Image.Save(path,ImageFormat.Bmp) ;
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
//long Len = ss.Length ;
//this.pictureBox1.Image.Save(
m_barrImg = new byte[Convert.ToInt32(fs.Length)];
int iBytesRead = fs.Read(m_barrImg, 0, Convert.ToInt32(fs.Length));
fs.Close();
}
}
}
catch (Exception ex)
{
LogError(ex.ToString());
}
finally
{ }
}
As i mentioned in my comment, The issue you're facing seems to come from the fact that the pictuebox.SizeMode is set to Normal.
From MSDN:
By default, in Normal mode, the Image is positioned in the upper-left corner of the PictureBox, and any part of the image that is too big for the PictureBox is clipped.Using the StretchImage value causes the image to stretch or shrink to fit the PictureBox. Using the Zoom value causes the image to be stretched or shrunk to fit the PictureBox; however, the aspect ratio in the original is maintained.
So if you change the SizeMode to StretchImage or Zoom you will see in the picturebox the same exact image.
That said, i would also recommend adding the following checking to your cam_NewFrame, since it might be that until the camera actually stops streaming, you will move a few frames ahead.
void cam_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
if (!scanFlag) return;
Bitmap bit = (Bitmap)eventArgs.Frame.Clone();
pbPhoto.Image = bit;
}
I was thinking of implementing a bitmap into a picturebox (or the otherway round), such that I could have the option to:
1.drag and drop the item (Control function with mousebuttons)
2.rotate the item (bitmap function).
This is what I have currently:
Bitmap bmp = new Bitmap(#"my source");
PictureBox item = new System.Windows.Forms.PictureBox();
I create the picturebox on the click of a button:
private void button2_Click(object sender, EventArgs e)
{
item.BackColor = Color.Blue;
item.Location = new Point(400, 200);
item.MouseDown += new MouseEventHandler(textbox_MouseDown);
item.MouseMove += new MouseEventHandler(textbox_MouseMove);
item.MouseUp += new MouseEventHandler(textbox_MouseUp);
item.MouseWheel += new MouseEventHandler(textbox_MouseWheel);
item.Image = bmp;
item.SizeMode = PictureBoxSizeMode.StretchImage;
this.Controls.Add(item);
item.BringToFront();
}
and in the textbox_MouseMove void, I try to use the middle button to signal the rotation of the image
void textbox_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Middle )
{
if (activeControl == null || activeControl != sender)
{
return;
}
this.Paint += new PaintEventHandler(objectrotation_Paint);
Invalidate();
}
}
where the PaintEvent is responsible for the rotation of the image
private void objectrotation_Paint(object sender, PaintEventArgs d)
{
int dpi = 96;
bmp.SetResolution(dpi, dpi);
if (bmp != null)
{
float bw2 = bmp.Width / 2f;
float bh2 = bmp.Height / 2f;
d.Graphics.TranslateTransform(bw2, bh2);
d.Graphics.RotateTransform(angle);
d.Graphics.TranslateTransform(-bw2, -bh2);
d.Graphics.DrawImage(bmp,0,0);
d.Graphics.ResetTransform();
}
}
This however would create two sets of images, i.e. when I press button 2 I get one image, when I move my mouse with middle button down I get the second image (rotating).Refering to some of the questions on here, people recommend using
item.Image = bmp;
but it simply copies the from picturebox to the bitmap while they have seperate controls. While the picturebox class could not perform rotations without implementing a Bitmap, the Bitmap class does not have the option to perform actions like OnMouseMove etc.
Is there any way to combine my bitmap and the picturebox such that I could achieve my two goals?
I'm using PictureBox control to draw complicated charts, and to optimize for performance i use a cache Bitmap for each layer of the drawing, and draw them on the control image, the upper layers have transparent backgrounds, i need to clear them and redraw on every change.
Assuming g instance of Graphics class of the Bitmap, using g.Clear(Color.White) draws a white rectangle over everything and so hiding lower layers, and g.Clear(Color.Transparent) draws Transparent rectangle over, what means doing nothing.
Isn't there a way to clear the Bitmap returning it to its original state?
private void btn_CancelImage_Click(object sender, EventArgs e)
{
DialogResult dialogResult = MessageBox.Show("Cancel and delete this Image?", "Cancel", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
{
ClearImage();
pictureBox1.Refresh();
}
else if (dialogResult == DialogResult.No)
{
return;
}
}
public void ClearImage()
{
Graphics g = Graphics.FromImage(ImageBitmap);
g.Clear(Color.White);
}
This is maybe not the answer you were looking for but I think it is an insteresting alternative to what you have.
Instead of having to draw all the layers upwards from every change, I stack as many layers on top of each other by nesting a number of PictureBoxes into one bottom PictureBox pBox0:
List<PictureBox> Layers = new List<PictureBox>();
private void Form1_Load(object sender, EventArgs e)
{
Layers.Add(pBox0);
setUpLayers(pBox0 , 20); // stacking 20 layers onto the botton one
timer1.Start(); // simulate changes
}
The stacking is set up like this:
void setUpLayers(Control parent, int count)
{
for (int i = 0; i < count; i++)
{
PictureBox pb = new PictureBox();
pb.BorderStyle = BorderStyle.None;
pb.Size = parent.ClientSize;
Bitmap bmp = new Bitmap(pb.Size.Width,pb.Size.Height,PixelFormat.Format32bppPArgb);
pb.Image = bmp;
pb.Parent = i == 0 ? pBox0 : Layers[i - 1];
Layers.Add(pb);
}
}
For best performance I use Format32bppPArgb as the pixel format.
For testing I run a Tick event that randomly draws onto a layer:
Random R = new Random(9);
private void timer1_Tick(object sender, EventArgs e)
{
int l = R.Next(Layers.Count-1) + 1;
Bitmap bmp = (Bitmap) Layers[l].Image;
using (Graphics G = Graphics.FromImage(Layers[l].Image))
{
G.Clear(Color.Transparent);
using (Font font = new Font("Consolas", 33f))
G.DrawString(l + " " + DateTime.Now.Second , font, Brushes.Gold,
R.Next(bmp.Size.Width), R.Next(bmp.Size.Height));
}
Layers[l].Image = bmp;
}
To collect all layers into one Bitmap you would make use of the DrawToBitmap method:
Bitmap GetComposite(Control ctl)
{
Bitmap bmp = new Bitmap(ctl.ClientSize.Width, ctl.ClientSize.Height,
PixelFormat.Format32bppArgb);
ctl.DrawToBitmap(bmp, ctl.ClientRectangle);
return bmp;
}
The result can then be saved or used in any other way..
Note that creating too many layers this way will hit a limit for window handles; I hit that limit at around 90 layers. If you need more that a few dozen layers a more intricate caching strategy is called for..
In For1 i have this code:
private void timer1_Tick(object sender, EventArgs e)
{
try
{
this.pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.Load(file_array_satellite[file_indxs_satellite]);
file_indxs_satellite = file_indxs_satellite - 1;
if (file_indxs_satellite < 0)
{
file_indxs_satellite = file_array_satellite.Length - 1;
}
}
catch
{
timer1.Enabled = false;
}
}
private void satellitesToolStripMenuItem_Click(object sender, EventArgs e)
{
file_array_satellite = Directory.GetFiles(UrlsPath, "RainImage*.*");
if (file_array_satellite.Length > 0)
{
DateTime[] creationTimes8 = new DateTime[file_array_satellite.Length];
for (int i = 0; i < file_array_satellite.Length; i++)
creationTimes8[i] = new FileInfo(file_array_satellite[i]).CreationTime;
Array.Sort(creationTimes8, file_array_satellite);
file_indxs_satellite = 0;
file_indxs_satellite = file_array_satellite.Length - 1;
timer1.Enabled = true;
}
}
private void pictureBox1_MouseEnter(object sender, EventArgs e)
{
this.pictureBox1.Size = new Size(500, 500);
pictureBox1.Location = new Point(this.Bounds.Width / 2,
this.Bounds.Height / 2);
this.pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.BringToFront();
}
private void pictureBox1_MouseLeave(object sender, EventArgs e)
{
this.pictureBox1.Size = new Size(100, 100);
pictureBox1.Location = new Point(12,
27);
}
In the original the picturebox1 size is 100x100 and each image i stretch to fit in the pictureBox.
When it's 100x100 everything is ok i see the animation of each image in the pictureBox.
Now i did an event that when i enter with the mouse to the pictureBox area it should move to the center of the form resize to 500x500 stretch the images and show the same animation.
And when i leave the pictureBox area it should return to it's original size and location.
When i enter with the mouse to the pictureBox1 area the pictureBox just vanish i don't see it anywhere once i leave the pictureBox area i see it 100x100 in it's original place and size.
Why when i enter with the mouse to the pictureBox1 area it's vanish i don't see it in the center of the form on size 500x500 ?
file_array_satellite is string[] and file_indxs_satellite is int.
RainImage*.* are the files names on the hard disk after downloaded them.
The idea is not to convert/change the files sizes on the hard disk each time i enter or leave so i wanted that once i enter the pictureBox1 area it will stretch the current image in the pictureBox and show it . It's working when it's 100x100 but not on 500x500.
When you mouse over the PictureBox and move it to the center of the form, you are moving it out from under the mouse cursor. This causes the MouseLeave event to immediately trigger, which places it back under your mouse cursor again, which causes the MouseEnter event to trigger again, etc.
You can do something like this:
bool suppressMouseLeave;
private void pictureBox1_MouseEnter(object sender, EventArgs e)
{
suppressMouseLeave = true;
this.pictureBox1.Size = new Size(500, 500);
pictureBox1.Location = new Point(this.Bounds.Width / 2,
this.Bounds.Height / 2);
this.pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.BringToFront();
//point the cursor to the new Position so that it's still kept on the pictureBox1
//This is important because it makes your idea acceptable.
//Otherwise you have to move your mouse onto your pictureBox and leave the
//mouse from it then to restore the pictureBox
Cursor.Position = PointToScreen(new Point(pictureBox1.Left + 250, pictureBox1.Top + 250));
suppressMouseLeave = false;
}
private void pictureBox1_MouseLeave(object sender, EventArgs e)
{
if(suppressMouseLeave) return;
this.pictureBox1.Size = new Size(100, 100);
pictureBox1.Location = new Point(12, 27);
}
I would venture a guess that this.Bounds.Width and this.Bounds.Height are not what you expect them to be, so the PictureBox isn't vanishing, you are just setting it to some location that is offscreen/off your form. Run Visual Studio in Debug mode and put a breakpoint around that line and see what this.Bounds is equal to. This may give you a clue as to the proper location you need to set.
How about in "in place" zoom like this?
private void pictureBox1_MouseEnter(object sender, EventArgs e)
{
Rectangle rc = pictureBox1.Bounds;
rc.Inflate(200, 200);
pictureBox1.Bounds = rc;
pictureBox1.BringToFront();
}
private void pictureBox1_MouseLeave(object sender, EventArgs e)
{
Rectangle rc = pictureBox1.Bounds;
rc.Inflate(-200, -200);
pictureBox1.Bounds = rc;
}
The question I have here is sort of a 2 parter.
I have a picturebox that is positioned inside of a panel. When I open an image, the picturebox is resized to the size of the image, while the panel stays the same size. The panel just has scrollbars to see the whole image.
There are 2 things going wrong with this.
When I resize the picturebox, for some reason I can only draw in the previous portion of the picturebox. Ex. The imagebox starts out by default as 200x200. I open an image that is 500x400. And I can only still draw in the 200x200 portion of the image.
The second issue that I am having is that when I do draw in that selective portion of the picturebox, when I scroll to where my painting is out of view, and come back, the image that i painted gone. I know there is some sort of picturebox.invalidate() that I need. I am just not sure how to use it.
Here is my code to get a good grasp on what I'm doing.
public Form1()
{
InitializeComponent();
DrawArea = new Bitmap(pictureBox1.Size.Width, pictureBox1.Size.Height );
pictureBox1.Image = DrawArea;
objGraphics = this.pictureBox1.CreateGraphics();
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
drawImage(e);
}
public void drawImage(MouseEventArgs e)
{
Rectangle rDraw = new Rectangle();
if (e.Button == MouseButtons.Left)
{
rDraw.X = e.X;
rDraw.Y = e.Y;
rDraw.Width = 3;
rDraw.Height = 3;
objGraphics.DrawEllipse(System.Drawing.Pens.Black, rDraw);
}
}
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
OpenFileDialog open = new OpenFileDialog();
open.Filter = "Image Files(*.jpg; *.bmp)|*.jpg; *.bmp";
if (open.ShowDialog() == DialogResult.OK)
{
Bitmap bit = new Bitmap(open.FileName);
pictureBox1.Size = bit.Size;
DrawArea = bit;
pictureBox1.Image = bit;
}
}
catch (Exception)
{
throw new ApplicationException("Failed loading image");
}
}
Thanks Alot!
You need to draw in the picturebox's Paint event.
You should (almost) never draw on CreateGraphics().