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*
Related
This question already has an answer here:
DrawToBitmap returning blank image
(1 answer)
Closed 3 years ago.
I'm trying to make an application similar to Paint.
Everything works well, but I have a problem with saving the image to a file.
The function for saving works okay, the file saves in the selected location, but it is empty when something is drawn.
It works only when I change the background color, then the image saves with this color.
When I 'draw' something like that
the saved image looks like this
Code:
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
int width = pnl_Draw.Width;
int height = pnl_Draw.Height;
Bitmap bm = new Bitmap(width, height);
SaveFileDialog sf = new SaveFileDialog();
sf.Filter = "Bitmap Image (.bmp)|*.bmp|Gif Image (.gif)|*.gif|JPEG Image (.jpeg)|*.jpeg|Png Image (.png)|*.png|Tiff Image (.tiff)|*.tiff|Wmf Image (.wmf)|*.wmf";
sf.ShowDialog();
var path = sf.FileName;
pnl_Draw.DrawToBitmap(bm, new Rectangle(0, 0, width, height));
bm.Save(path, ImageFormat.Jpeg);
}
Drawing:
private void pnl_Draw_MouseMove(object sender, MouseEventArgs e)
{
if(startPaint)
{
//Setting the Pen BackColor and line Width
Pen p = new Pen(btn_PenColor.BackColor,float.Parse(cmb_PenSize.Text));
//Drawing the line.
g.DrawLine(p, new Point(initX ?? e.X, initY ?? e.Y), new Point(e.X, e.Y));
initX = e.X;
initY = e.Y;
}
}
Where is your "g" (System.Drawing.Graphics?) object come from when you DrawLine on it? I also can't see where you fill the background color, but I have the suspiction that your drawing gets discarded/overwritten - but given the little code visible here, it's hard to tell.
I'd just suggest what worked for me in the past:
Use a Bitmap object to draw your lines etc into, not the panel directly. And when you want to save it, just save the bitmap.
To make the drawing visible on your panel, call Invalidate(...) on your panel after a new line stroke was made, with the bounding rectangle around the line stroke as the update-rectangle passed to Invalidate.
In the OnPaint handler of your panel, then make sure to only draw that portion that's new, e.g. that rectangle I mentioned. This will be passed as the clip bounds of the OnPaint call. If only the changed portion of the whole image is drawn in the OnPaint handler, it will be much faster than always drawing the whole bitmap onto the panel.
For that, you need to creage a graphics object from the drawn-to bitmap, and keep both the bitmap and the graphics object alive throughout your drawing session (i.e. don't let it get garbage collected by not having references to them somewhere)
Something roughly like this:
// assuming you're all doing this directly in your main form, as a simple experimental app:
// Somewhere in your form class:
Bitmap drawingBitmap;
Graphics gfx;
// in your form class' constructor, AFTER the InitializeComponent() call, so the sizes are known:
// using a pixelformat which usually yields good speed vs. non-premultiplied ones
drawingBitmap = new Bitmap( pnl.Width, pnl.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb );
gfx = Graphics.FromImage( drawingBitmap );
private void pnl_Draw_MouseMove(object sender, MouseEventArgs e)
{
if(startPaint)
{
//Setting the Pen BackColor and line Width
Pen p = new Pen(btn_PenColor.BackColor,float.Parse(cmb_PenSize.Text));
//Drawing the line.
var p1 = new Point(initX ?? e.X, initY ?? e.Y);
var p2 = new Point(e.X, e.Y);
gfx.DrawLine( p, p1, p2 ); // !!! NOTE: gfx instance variable is used
initX = e.X;
initY = e.Y;
// makes the panel's Paint handler being called soon - with a clip rectangle just covering your new little drawing stroke, not more.
pnl.Invalidate( new Rectangle( p1.X, p1.Y, 1+p2.X-p1.X, 1+p2.Y-p1.Y ));
}
}
private void pnl_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
// use the clip bounds of the Graphics object to draw only the relevant area, not always everything.
var sourceBounds = new RectangleF( g.ClipRectangle.X, g.ClipRectangle.Y, g.ClipRectangle.Width, g.ClipRectangle.Height );
// The bitmap and drawing panel are assumed to have the same size, to make things easier for this example.
// Actually you might want have a drawing bitmap of a totally unrelated size, and need to apply scaling to a setting changeable by the user.
g.DrawImage( drawingBitmap, g.ClipRectangle.X, g.ClipRectangle.Y, sourceBounds );
}
Here is a little bit about that, although pretty old article. But GDI+ hasn't change that much since then, as it's a wrapper around the Win32 drawing stuff.
https://www.codeproject.com/Articles/1355/Professional-C-Graphics-with-GDI
NOTE: What I don't have time right now to check and do not remember is whether you are allowed to, at the same time:
have an "open" graphics object of a bitmap and draw into the bitmap
paint that same bitmap onto something else (like the panel)
save the bitmap to file
You'd have to check that, and manage the existence of the graphics object with its claws on your drawing bitmap accordingly.
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.
I made several drawings using Windows forms application visual studio Bitmap, my goal is to clear all drawings when I press the push button (make the background clear)
The Following code is not exactly what my original code but this is how I created each drawing
Rectangle area;
Bitmap creature;//this only one drawing but I have several
Graphics scG;
Bitmap background;
private void Form1_Load(object sender, EventArgs e)
{
background = new Bitmap(Width, Height);
area = new Rectangle(50, 50, 50, 50);
creature = new Bitmap(#"C:\Users\Desktop\Image.png");
}
public Bitmap Draw()
{
Graphics scG = Graphics.FromImage(background);
scG.DrawImage(creature, area);
return background;
}
}
private void Button_Click(object sender, EventArgs e)
{
// I want to clear all the drawings by push this button
}
There is no way to remove things drawn on a bitmap.
Why don't just dump the old background bitmap and create a new one?
this is duplicate , see this link :
How to delete a drawn circle in c# windows form?
or test these Code :
Graphics.Clear();
or
Control.Invalidate();
or
this.Invalidate();
The real way is to use event Paint:
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(creature, area); // draw your image here...
}
To delete, just do nothing in Form1_Paint, and call Refresh();
I'm trying to render bitmap created from drawing to screen but only render after minimize and maximize again.
I follow these steps: Using Bitmaps for Persistent Graphics in C#
But only can render bitmap in screen outside of Load_Form.
If I put the code:
using System.Drawing;
...
Graphics graphicsObj;
myBitmap = new Bitmap(this.ClientRectangle.Width,
this.ClientRectangle.Height,
Imaging.PixelFormat.Format24bppRgb);
graphicsObj = Graphics.FromImage(myBitmap);
Pen myPen = new Pen(Color.Plum, 3);
Rectangle rectangleObj = new Rectangle(10, 10, 200, 200);
graphicsObj.DrawEllipse(myPen, rectangleObj);
graphicsObj.Dispose();
In other place, for example a button, I need to minimize and maximize to see the image.
Edit:
bmp is a Bitmap global variable I create an instance in form event Load_Form1
bmp = new Bitmap(this.ClientRectangle.Width,
this.ClientRectangle.Height,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Paint event of Form for redraw:
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics graphicsObj = e.Graphics;
graphicsObj.DrawImage(myBitmap, 0, 0, myBitmap.Width, myBitmap.Height);
graphicsObj.Dispose();
}
But I need draw inmediately after create the drawing.
Not telling us about the bigger picture makes it hard to recommend the best course of action but let me simply assume two possible aims:
either you simply want something drawn onto the Form
or you want to display a bitmap into which you sucessively draw more and more things.
For the first all you need is to code the Paint event like this:
private void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle rectangleObj = new Rectangle(10, 10, 200, 200);
using (Pen myPen = new Pen(Color.Plum, 3))
e.Graphics.DrawEllipse(myPen, rectangleObj);
}
If the data that control the drawing are dynamic you should store them at class level variables or lists of them and change them as needed so you can use them in the Paint event.
For the latter aim there will be various times when you add to the Bitmap.
So you start by creating a, probably, class level Bitmap:
public Form1()
{
InitializeComponent();
bmp = new Bitmap(this.ClientRectangle.Width,
this.ClientRectangle.Height);
}
Bitmap bmp = null;
And have one or more places where you draw into it like this:
void drawALittle()
{
Rectangle rectangleObj = new Rectangle(10, 10, 200, 200);
using (Pen myPen = new Pen(Color.Plum, 3))
using (Graphics G = Graphics.FromImage(bmp))
{
G.DrawEllipse(myPen, rectangleObj);
//..
}
this.Invalidate();
}
Note how I Invalidate the Form after changing the Bitmap, so the Paint event is triggered.
Also note that if these update happen really often it will be a good idea to keep the Graphics object alive between calls; either by making it a class variable like the Bitmap or by keeping it locally in the method that does all the updates and passing it out as a parameter to the drawing method..
In the form's Paint event all you need is
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(bmp, 0, 0);
}
Also note that 32bppARGB is the recommended default format. Is is used for anything you display in any case, so it is most efficient to use it to begin with..
I have a problem:
I have 3 picture boxes with 3 different images as in Image
what can i set to pictureBox3 so both images look same.....
EDITED:
I want to move pictureBox3 on pictureBox2,
So there is no Option to merge them to single image
Make sure the image in pictureBox3 is transparent. Set the BackColor to transparent. In code, set the Parent property of the pictureBox3 to be pictureBox2. Adjust the Location coordinates of pictureBox3 since they will be relative to the coordinates of pictureBox2 once you've changed the Parent.
private void Form1_Load(object sender, EventArgs e)
{
pictureBox3.Parent = pictureBox2;
pictureBox3.Location =
new Point(
pictureBox3.Location.X
- pictureBox2.Location.X,
pictureBox3.Location.Y
- pictureBox2.Location.Y);
}
In designer you will not see the transparency, but at runtime you will.
Update
In the image, the left side shows the designer view, the right side is the runtime version.
Another update
I really don't understand how it would be possible that this doesn't work for you. I suppose there must be something we are doing different. I'll describe the exact steps to take to create a working sample. If you follow the exact same steps, I wonder if we'll get the same results or not. Next steps describe what to do and use two images I found on the net.
Using Visual Studio 2008, create a New Project using template Windows Forms Application. Make sure the project is targeted at the .NET Framework 3.5.
Set the Size of the Form to 457;483.
Drag a PictureBox control onto the form. Set its Location to 0;0 and its Size to 449;449.
Click the ellipsis besides its Image property, click the Import... button and import the image at http://a.dryicons.com/files/graphics_previews/retro_blue_background.jpg (just type the URL in the File name text box and click Open). Then click OK to use the image.
Drag another PictureBox onto the form, set its Location to 0;0 and its Size to 256;256. Also set its BackColor property to Transparent.
Using the same method as described above, import image http://www.axdn.com/redist/axiw_i.png which is a transparent image.
Now place the following code in the form's OnLoad event handler:
private void Form1_Load(object sender, EventArgs e)
{
pictureBox2.Parent = pictureBox1;
}
That's it! If I run this program I get a transparent image on top of another image.
I'll add another example that according to the updated requirement allows for moving image3.
To get it working, put an image with transparency in Resources\transp.png
This uses the same image for all three images, but you can simply replace transparentImg for image1 and image2 to suitable images.
Once the demo is started the middle image can be dragged-dropped around the form.
public partial class Form1 : Form
{
private readonly Image transparentImg; // The transparent image
private bool isMoving = false; // true while dragging the image
private Point movingPicturePosition = new Point(80, 20); // the position of the moving image
private Point offset; // mouse position inside the moving image while dragging
public Form1()
{
InitializeComponent();
//
// pictureBox1
//
this.pictureBox1.Location = new System.Drawing.Point(0, 0);
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(231, 235);
this.pictureBox1.TabIndex = 0;
this.pictureBox1.TabStop = false;
this.pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBox1_Paint);
this.pictureBox1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.pictureBox1_MouseDown);
this.pictureBox1.MouseMove += new System.Windows.Forms.MouseEventHandler(this.pictureBox1_MouseMove);
this.pictureBox1.MouseUp += new System.Windows.Forms.MouseEventHandler(this.pictureBox1_MouseUp);
this.Controls.Add(this.pictureBox1);
transparentImg = Image.FromFile("..\\..\\Resources\\transp.png");
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.DrawImageUnscaled(transparentImg, new Point(20, 20)); // image1
g.DrawImageUnscaled(transparentImg, new Point(140, 20)); // image2
g.DrawImageUnscaled(transparentImg, movingPicturePosition); // image3
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
var r = new Rectangle(movingPicturePosition, transparentImg.Size);
if (r.Contains(e.Location))
{
isMoving = true;
offset = new Point(movingPicturePosition.X - e.X, movingPicturePosition.Y - e.Y);
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (isMoving)
{
movingPicturePosition = e.Location;
movingPicturePosition.Offset(offset);
pictureBox1.Invalidate();
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
isMoving = false;
}
}
This code will do the trick:
using (Graphics g = Graphics.FromImage(pictureBox1.Image))
{
g.DrawImage(pictureBox2.Image,
(int)((pictureBox1.Image.Width - pictureBox2.Image.Width) / 2),
(int)((pictureBox1.Image.Height - pictureBox2.Image.Height) / 2));
g.Save();
pictureBox1.Refresh();
}
It will draw the image from pictureBox2 on the existing image of pictureBox1.
For starters, set the BackColor property of PictureBox3 to Transparent. This should work in almost all cases.
You should also use an image with a transparent background instead of white so you do not have the white borders around your purple circle. (Recommended image format: PNG)
Update
Following the replies I got, it appears setting the BackColor to Transparent doesn't work. In that case, it's best you handle the Paint event of the PictureBox and do the painting of the new image yourself as Albin suggested.
You might do some hack by overriding OnPaint and stuff, example here.
But I'd recommend to merge the pictures in pictureBox2 and 3 into a single image before displaying them in a single pictureBox.