I want to create a custom control or override the pictuebox's onpaint event such that i get access to the image before its drawn in the picturbox,so that i can rotate the image.
I know that i can do something like this
private void pictureBox1_Paint(object sender, PaintEventArgs e) {
e.Graphics.DrawRectangle(Pens.Black, new Rectangle(10, 10, 20, 20));
}
How to get access to the image and how to create a custom control.
Here is a quick example of a subclass: It hides the original Image property and replaces it with one that does a rotation before assigning it:
class RotatedPictureBox : PictureBox
{
private Image image;
public new Image Image {
get { return image; } // ?? you may want to undo the rotation here ??
set {
Bitmap bmp = value as Bitmap ;
// use the rotation you need!
if ( bmp != null ) bmp.RotateFlip(RotateFlipType.Rotate270FlipX);
image = bmp;
base.Image = Image;
}
}
}
public RotatedPictureBox ()
{
}
}
Caveat: Assigning an Image seems to work but I didn't test it for all possible uses.. Known limitations
It doesn't rotate images assigned via ImageLocation.
I had a crash once, when assigning an image in the Designer, but can't reproduce.
Related
I work on a project where I need to constantly get bitmaps and draw them on a picturebox.
The idea is to draw a first initial bitmap, then retrive the rest of the bitmap and draw them above the initial one. (The first one still displayed in the picturebox, so I want to draw them on the first bitmap).
I tried to design a custom control, to implement the OnPaint event, but the second time the event is fired, it draws the second block and completely conceals the image which had been drawn before.
public class RapidPictureBox: PictureBox
{
public pictureBox1Control()
{
SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.ResizeRedraw, true);
}
public Bitmap block = null;
public int x = 0, y = 0;
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawImage(block, x, y);
}
}
private void Form1_Load(object sender, EventArgs e)
{
RapidPictureBox pictureBox1 = new RapidPictureBox();
pictureBox1.Dock = DockStyle.Fill;
Controls.Add(pictureBox1);
pictureBox1.block = new Bitmap("3.png"); //first initial image
pictureBox1.block = new Bitmap("2.png"); //draw on the initial one.
}
I'm not sure what's wrong in the code. I use the e EventArgs to draw a new block everytime I need but it seems the new drawing compeletly hides the previous bitmap.
you can create a graphics object from the picturebox,then redraw it over the current image
initial =new Bitmap("test.png");
pictureBox1.Image = initial;
var graphics = pictureBox1.CreateGraphics(); //create a graphic objec
graphics.DrawImage(block, x,y);//that's the second method
What you should do is overlay the new image on previous image.
Let's say there is a primary image (first image) and you want to print next image(overlapping image) on that same primary image. Use following method to do that.
private Bitmap GetOvelappedImages(Bitmap primaryImage, Bitmap overlappingImage)
{
//create graphics from main image
using (Graphics g = Graphics.FromImage(primaryImage))
{
//draw other image on top of main Image
g.DrawImage(overlappingImage, new Point(0, 0));
}
return primaryImage;
}
then set this new image to pictureBox1.block.
private void Form1_Load(object sender, EventArgs e)
{
RapidPictureBox pictureBox1 = new RapidPictureBox();
pictureBox1.Dock = DockStyle.Fill;
Controls.Add(pictureBox1);
pictureBox1.block = GetOverlappedImages(new Bitmap("3.png"),new Bitmap("2.png")); //draw on the initial one.
}
That should work for you.
Note: You should dispose images after they are used.
Update:
You need to redraw whole image because, OnPaint is called only when current image shown on picture box needs to be redrawn.
The OnPaint method is overridden to repaint the image each time the form is painted; otherwise the image would only persist until the next repainting.
Read documentation for OnPaint here
The problem with your code is that you are not adding pictures to the control you are replacing them.
Side Note: With the code you have you are not disposing of the Bitmap objects set in the PictureBoxes image and so this could cause memory leaks.
You can create a similar control LayeredPictureBox. This will get all of the images and draw them on top of each other. It will have poor performance and the images will need to have transparency to look layered but you get the idea:
public class LayeredPictureBox : PictureBox
{
public LayeredPictureBox()
{
SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.ResizeRedraw, true);
}
public List<Bitmap> blocks = new List<Bitmap>();
public int x = 0, y = 0;
protected override void OnPaint(PaintEventArgs e)
{
foreach (Bitmap block in blocks)
{
e.Graphics.DrawImage(block, x, y);
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
RapidPictureBox pictureBox1 = new RapidPictureBox();
pictureBox1.Dock = DockStyle.Fill;
Controls.Add(pictureBox1);
pictureBox1.blocks.Add(new Bitmap("3.png")); //first initial image
pictureBox1.blocks.Add(new Bitmap("2.png")); //draw on the initial one.
}
Another option is to merge all of the images together on adding them and then just drawing that image into the PictureBox.
in a UserControl I have a PictureBox and some other controls. For the user control which contains this picturebox named as Graph I have a method to draw a curve on this picture box:
//Method to draw X and Y axis on the graph
private bool DrawAxis(PaintEventArgs e)
{
var g = e.Graphics;
g.DrawLine(_penAxisMain, (float)(Graph.Bounds.Width / 2), 0, (float)(Graph.Bounds.Width / 2), (float)Bounds.Height);
g.DrawLine(_penAxisMain, 0, (float)(Graph.Bounds.Height / 2), Graph.Bounds.Width, (float)(Graph.Bounds.Height / 2));
return true;
}
//Painting the Graph
private void Graph_Paint(object sender, PaintEventArgs e)
{
base.OnPaint(e);
DrawAxis(e);
}
//Public method to draw curve on picturebox
public void DrawData(PointF[] points)
{
var bmp = Graph.Image;
var g = Graphics.FromImage(bmp);
g.DrawCurve(_penAxisMain, points);
Graph.Image = bmp;
g.Dispose();
}
When application starts, the axis are drawn. but when I call the DrawData method I get the exception that says bmp is null. What can be the problem?
I also want to be able to call DrawData multiple times to show multiple curves when user clicks some buttons. What is the best way to achive this?
Thanks
You never assigned Image, right? If you want to draw on a PictureBox’ image you need to create this image first by assigning it a bitmap with the dimensions of the PictureBox:
Graph.Image = new System.Drawing.Bitmap(Graph.Width, Graph.Height);
You only need to do this once, the image can then be reused if you want to redraw whatever’s on there.
You can then subsequently use this image for drawing. For more information, refer to the documentation.
By the way, this is totally independent from drawing on the PictureBox in the Paint event handler. The latter draws on the control directly, while the Image serves as a backbuffer which is painted on the control automatically (but you do need to invoke Invalidate to trigger a redraw, after drawing on the backbuffer).
Furthermore, it makes no sense to re-assign the bitmap to the PictureBox.Image property after drawing. The operation is meaningless.
Something else, since the Graphics object is disposable, you should put it in a using block rather than disposing it manually. This guarantees correct disposing in the face of exceptions:
public void DrawData(PointF[] points)
{
var bmp = Graph.Image;
using(var g = Graphics.FromImage(bmp)) {
// Probably necessary for you:
g.Clear();
g.DrawCurve(_penAxisMain, points);
}
Graph.Invalidate(); // Trigger redraw of the control.
}
You should consider this as a fixed pattern.
I'm trying to build a little test application (and my WinForm skills have rusted somewhat) with an Image and some overlays on top of it.
My image is set to stretch in the PictureBox but my fields on the right hand side I want to be from the origin of the image. Therefore I decided to render directly on the image that the PictureBox is using to ensure that the co-ordinates are always correct. Here's the white box rendering:
private void pbImage_Paint(object sender, PaintEventArgs e)
{
try
{
if (this.rdFront.Checked)
RenderFront(pbImage.Image, true);
else
RenderBack(pbImage.Image, true);
}
catch (ArgumentNullException ex)
{ }
}
public void RenderFront(Image image, bool includeBoxes)
{
// If we have no image then we can't render
if (image == null)
throw new ArgumentNullException("image");
Graphics gfx = Graphics.FromImage(image);
// Get the top label
foreach (MessageConfiguration config in this.config.Values.Where(c => c.Front))
{
if (includeBoxes)
{
// Fill a White rectangle and then surround with a black border
gfx.FillRectangle(Brushes.White, config.X, config.Y, config.Width, config.Height);
gfx.DrawRectangle(Pens.Black, config.X - 1, config.Y - 1, config.Width + 2, config.Height + 2);
}
gfx.DrawString(config.Text, new Font(FontFamily.GenericMonospace, config.FontSize), Brushes.Black, new PointF(config.X, config.Y));
}
}
The problem that I've got is if I do this and always draw on the underlying image then when I move the white overlay, I end up with un-drawn parts of the image. So I decided to clone the image before each re-render (on the basis that I don't care about performance).
I therefore decided to clone the image whenever I need to manually invalidate it, and call this when a setting changes:
public void Refresh()
{
if (this.rdFront.Checked)
pbImage.Image = new Bitmap(front);
else
pbImage.Image = new Bitmap(back);
this.pbImage.Invalidate();
}
Now I'm sure I must be missing something obvious - if I modify one of the values my penguins render with no overlay. However if I force a resize of the application then both the penguins and the overlay suddenly appear.
Can anyone suggest what I might be doing wrong?
Edit
Here's a download link to the project as it's quite small. Paste a path to an image in the 'Front Image' box and try using the controls on the right (set 100x100 height and width). Try re-sizing to see the desired affect. https://dl.dropboxusercontent.com/u/41796243/TemplateTester.zip
Controls and Forms have a Refresh method already. Are you really calling your Refresh method? Aren't you getting a warning that you should use the new keyword? Better give your Refresh method another name (e.g RefreshImage)!
I'm really not sure why you are using a picture box but then decide to do your on painting. I suggest to draw to an image off-screen and then simply assign it to the picture box:
public void RefreshImage()
{
Bitmap bmp;
if (this.rdFront.Checked)
bmp = new Bitmap(front);
else
bmp = new Bitmap(back);
using (Graphics gfx = Graphics.FromImage(bmp)) {
foreach (MessageConfiguration config in this.config.Values.Where(c => c.Front))
{
if (includeBoxes) {
// Fill a White rectangle and then surround with a black border
gfx.FillRectangle(Brushes.White, config.X, config.Y, config.Width, config.Height);
gfx.DrawRectangle(Pens.Black, config.X - 1, config.Y - 1, config.Width + 2, config.Height + 2);
}
gfx.DrawString(config.Text, new Font(FontFamily.GenericMonospace, config.FontSize), Brushes.Black, new PointF(config.X, config.Y));
}
}
pbImage.Image = bmp;
}
and remove the pbImage_Paint method.
Another possibility is to use the pbImage_Paint event handler in another way. Call the base.Paint() handler of the picture box that draws the image but leave the image itself unchanged. Instead draw on top of it by using the Graphics object given by the PaintEventArgs e argument. This Graphics object represents the client area of the picture box. This does not alter the Bitmap assigned to the picture box, but only draws on the screen.
private void pbImage_Paint(object sender, PaintEventArgs e)
{
base.Paint(); // Paints the image
if (this.rdFront.Checked)
RenderFront(e.Graphics, true);
else
RenderBack(e.Graphics, true);
}
public void RenderFront(Graphics g, bool includeBoxes)
{
foreach (MessageConfiguration config in this.config.Values.Where(c => c.Front)) {
if (includeBoxes) {
g.FillRectangle(Brushes.White, config.X, config.Y, config.Width, config.Height);
g.DrawRectangle(Pens.Black, config.X - 1, config.Y - 1, config.Width + 2, config.Height + 2);
}
g.DrawString(config.Text, new Font(FontFamily.GenericMonospace, config.FontSize), Brushes.Black, new PointF(config.X, config.Y));
}
}
How to do Free hand Image Cropping in C# window application??
Okay, you provided very small amount of information, but I'll assume that you are using winforms. There are some tasks dealing with freehand-technique such as:
Drawing
Drag-n-dropping
Cropping
Selecting
They all are very similar. Let's assume that you have a PictureBox and want to crop an image inside it.
// Current selection
private Rectangle _cropRectangle;
// Starting point
private Point _cropStart;
// Dragging flag
private bool _isDragging;
private void pBox_MouseDown(object sender, MouseEventArgs e)
{
_cropRectangle = new Rectangle(e.X, e.Y, 0, 0);
_isDragging = true;
}
private void pBox_MouseUp(object sender, MouseEventArgs e)
{
_isDragging = false;
}
private void pBox_MouseMove(object sender, MouseEventArgs e)
{
if (!_isDragging)
return;
_cropRectangle = new Rectangle(Math.Min(_cropStart.X, e.X),
Math.Min(_cropStart.Y, e.Y),
Math.Abs(e.X - _cropStart.X),
Math.Abs(e.Y - _cropStart.Y));
pBox.Invalidate();
}
private void pBox_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(Pens.Red, _cropRectangle);
}
What happens: I make use of three mouse events (MouseDown, MouseUp, MoseMove) and the Paint event. Basically, whenever you want to do anything from the above list, you'll have handle these four events.
I tried to keep the code short and self-explanatory. There are four event handlers working with three instance fields. The fields are used to store the current state of dragging process.
Feel free to customize the code, especially the pBox_Paint handler. Mine just draws a thin red rectangle around selected area. You might want to do something more elaborate here.
Whenever you're done with your rectangle, you can call the Crop method:
private Image Crop()
{
Bitmap croppedImage = new Bitmap(_cropRectangle.Width, _cropRectangle.Height);
using (Graphics g = Graphics.FromImage(croppedImage))
{
g.DrawImage(pBox.Image, 0, 0, _cropRectangle, GraphicsUnit.Pixel);
}
return croppedImage;
}
It creates a new Bitmap and put the selected portion of source image into it. The returned Image object might be used in any manner you like.
EDIT: trying to simplify the code I made some mistakes earlier, fixed now.
You can use Graphics.DrawImage to draw a cropped image onto the graphics object from a bitmap.
Rectangle cropRect = new Rectangle(...);
Bitmap src = Image.FromFile(fileName) as Bitmap;
Bitmap target = new Bitmap(cropRect.Width, cropRect.Height);
using(Graphics g = Graphics.FromImage(target))
{
g.DrawImage(src, cropRect,
new Rectangle(0, 0, target.Width, target.Height),
GraphicsUnit.Pixel);
}
You can also refer the full code for this....
Refer this *Link*
I have two questions:
1) I have a PictureBox and its Dock is set to Fill. When I resize the Form I cannot create a Graphic on the part of the PictureBox that is extended. What is the problem?
2) I want to convert the Graphic that is created on the PictureBox to Bitmap and save it as
*.JPG or *.bmp. How can I do this?
you can use the handle device to get the bitmap out of the picture box
Graphics g = pictureBox1.CreateGraphics();
Bitmap bitMap = Bitmap.FromHbitmap(g.GetHdc());
bitMap.Save(filePath, System.Drawing.Imaging.ImageFormat.Jpeg);
or even better, if the pictureBox does`nt modify the image, you can directly get the image from the pictureBox control
pictureBox1.Image.Save("path", System.Drawing.Imaging.ImageFormat.Jpeg);
Try this, works fine for me...
private void SaveControlImage(Control ctr)
{
try
{
var imagePath = #"C:\Image.png";
Image bmp = new Bitmap(ctr.Width, ctr.Height);
var gg = Graphics.FromImage(bmp);
var rect = ctr.RectangleToScreen(ctr.ClientRectangle);
gg.CopyFromScreen(rect.Location, Point.Empty, ctr.Size);
bmp.Save(imagePath);
Process.Start(imagePath);
}
catch (Exception)
{
//
}
}
1) Your description is very vague. Do you get an exception? Does it display wrong results? What is happening?
2) You need to get the Image from the PictureBox and use its Save method.
When the Picturebox gets resized to fill the form, it seems it's Image property stays the same.
So what you need to do is handle the PictureBox.OnSizeChanged Event, and then use the following code to resize the image:
private void pictureBox1_SizeChanged(object sender, EventArgs e)
{
if ((pictureBox1.Image != null))
{
pictureBox1.Image = new Bitmap(pictureBox1.Image, pictureBox1.Size);
}
}
To save the image use:
pictureBox1.Image.Save(filename, System.Drawing.Imaging.ImageFormat.Jpeg);
Hope that helps!