How to Crop a Polygonal Area from an Image in a Winform Picturebox

How to crop a polygonal area from an image in a WinForm pictureBox?

You can make the List of Points into a Polygon, then into a GraphicsPath and then into a Region and after Graphics.Clip(Region) you can Graphics.DrawImage and are done..:

using System.Drawing.Drawing2D;

GraphicsPath gp = new GraphicsPath(); // a Graphicspath
gp.AddPolygon(points.ToArray()); // with one Polygon

Bitmap bmp1 = new Bitmap(555,555); // ..some new Bitmap
// and some old one..:
using (Bitmap bmp0 = (Bitmap)Bitmap.FromFile("D:\\test_xxx.png"))
using (Graphics G = Graphics.FromImage(bmp1))
{
G.Clip = new Region(gp); // restrict drawing region
G.DrawImage(bmp0, 0, 0); // draw clipped
pictureBox1.Image = bmp1; // show maybe in a PictureBox
}
gp.Dispose();

Note that you are free to choose the DrawImage location anywhere, including in the negative area to the left and top of the origin..

Also note that for 'real' cropping some (at least 4) of your points should hit the target Bitmap's borders! - Or you can use the GraphicsPath to get its bounding box:

RectangleF rect = gp.GetBounds();
Bitmap bmp1 = new Bitmap((int)Math.Round(rect.Width, 0),
(int)Math.Round(rect.Height,0));
..

how to crop image with polygon in c#

You would create a new bitmap, at least as large as the bounding box of your polygon. Create a graphics object from this new bitmap. You can then draw the polygon to this bitmap, using the original image as a texture brush. Note that you might need to apply transform matrix to translate from the full image coordinates to the cropped image coordinates.

Note that it looks like you have radiological images. These are typically 16 bit images, so they will need to be converted to 8bit mono, or 24bit RGB before they can be used. This should already be done in the drawing code if you have access to the source. Or you can do it yourself.

C# crop image trapezoid / polygon

One way to do it is to use the Graphics API. This has a FillPolygon method that takes a list of points and a brush. To use the source bitmap you would use the TextureBrush. Put this together and you would end up with something like this:

    public Bitmap FillPolygon(Bitmap sourceBitmap, PointF[] polygonPoints)
{
var targetBitmap = new Bitmap(256, 256);
using var g = Graphics.FromImage(targetBitmap);
using var brush = new TextureBrush(sourceBitmap);
g.FillPolygon(brush, polygonPoints);
return targetBitmap;
}

I think this should work from a windows console. There have historically been a few issues using some graphics APIs without a windows sessions, but I do not think this is a problem anymore.

For maximum compatibility you could always triangulate and rasterize the trapezoid yourself and copy the selected pixels, but it would probably be significantly more work.

How crop image by diagonally?

you can use nested for loop.
pseudo code can be like this:

for(int i=1; i<=1000;i++){
for(int j=1;j<= i;i++){
newImage[i][j] = image[i][j];
}
}

How to select an area on a PictureBox.Image with mouse in C#

I used your code, you were nearly there. You needed to Invalidate the pictureBox1 instead of the rectangle. I also added a check for the Rect so it doesn't get drawn when it's not initialized or has no size.

Another important change: I created the Rectangle only once and I adjusted its location and size. Less garbage to clean up!

EDIT

I added a mouse right-click handler for the Rectangle.

private Point RectStartPoint;
private Rectangle Rect = new Rectangle();
private Brush selectionBrush = new SolidBrush(Color.FromArgb(128, 72, 145, 220));

// Start Rectangle
//
private void pictureBox1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Determine the initial rectangle coordinates...
RectStartPoint = e.Location;
Invalidate();
}

// Draw Rectangle
//
private void pictureBox1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (e.Button != MouseButtons.Left)
return;
Point tempEndPoint = e.Location;
Rect.Location = new Point(
Math.Min(RectStartPoint.X, tempEndPoint.X),
Math.Min(RectStartPoint.Y, tempEndPoint.Y));
Rect.Size = new Size(
Math.Abs(RectStartPoint.X - tempEndPoint.X),
Math.Abs(RectStartPoint.Y - tempEndPoint.Y));
pictureBox1.Invalidate();
}

// Draw Area
//
private void pictureBox1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
// Draw the rectangle...
if (pictureBox1.Image != null)
{
if (Rect != null && Rect.Width > 0 && Rect.Height > 0)
{
e.Graphics.FillRectangle(selectionBrush, Rect);
}
}
}

private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
if (Rect.Contains(e.Location))
{
Debug.WriteLine("Right click");
}
}
}

How to draw a transparent shape over an Image

This method makes use of two GraphicsPath objects and a TextureBrush to draw transparent holes inside a Bitmap (see the description of this functionality in the Worker methods part).

When the Bitmap we want to work with is loaded, (here, using File.ReadAllBytes() and a MemoryStream to avoid locking the image file on disk), it's assigned to a private Field, drawingBitmap which is then cloned to create the object shown in a PictureBox.Image property (the original Image is always duplicated in a way or another, we never modify it).

▶ The selectionRect Field keeps track of the area selected (with different means, as shown in the visual sample).

▶ The shapeOfHole Field is an Enumerator that specifies the type of the shape that selectionRect is describing (here, a Rectangle or an Ellipse, but it could be any other shape: using GraphicsPaths as containers makes it even simpler to add polygon shapes).

▶ The preserveImage boolean Field is a selector used to determine whether the new holes are added to the existing Image or a new hole is created each time.

In the sample code here, two Buttons, btnLoadImage and btnPaintHole are used to activate the main functions (loading and assigning the Image and drawing one or more holes in the selected Bitmap).

picCanvas is the PictureBox used to show the Image.

Private drawingBitmap As Image = Nothing
Private selectionRect As RectangleF = New RectangleF(100, 100, 50, 50)
Private shapeOfHole As ShapeType = ShapeType.Rectangle
Private preserveImage as Boolean = False

Private Sub btnLoadImage_Click(sender As Object, e As EventArgs) Handles btnLoadImage.Click
Dim imagePath As String = [Your Image Path]
drawingBitmap = Image.FromStream(New MemoryStream(File.ReadAllBytes(imagePath)))
picCanvas.Image?.Dispose()
picCanvas.Image = DirectCast(drawingBitmap.Clone(), Bitmap)
End Sub

Private Sub btnPaintHole_Click(sender As Object, e As EventArgs) Handles btnPaintHole.Click
Dim newImage As Image = Nothing
If preserveImage AndAlso picCanvas.Image IsNot Nothing Then
newImage = DrawHole(picCanvas.Image, picCanvas, selectionRect, shapeOfHole)
Else
newImage = DrawHole(drawingBitmap, picCanvas, selectionRect, shapeOfHole)
End If

If newImage IsNot Nothing Then
picCanvas.Image?.Dispose()
picCanvas.Image = newImage
End If
End Sub

Visual sample of the functionality:

GraphicsPath Draw Holes

▶ The Image used as the PictureBox.BackgroundImage to simulate the classic transparent background .

Worker methods:

▶ The DrawHole() method uses two GraphicsPath objects.

The imagePath object is sized as the original Image, the selectionPath object is sized as the current selection area (will be scaled to match the Image real size after).

Using the FillMode.Alternate mode, the imagePath.AddPath(selectionPath, True) method sets the connect argument to True, specifying that the added selectionPath becomes part of imagePath. Since FillMode.Alternate is an XOR operation, we create a hole in imagePath.

The Graphics.FillPath() method then uses a TextureBrush to fill the GraphicsPath, except the XOR-ed part, with the Bitmap object, which will then contain an anti-aliased transparent area (the Graphics object uses the SmoothingMode.AntiAlias mode).

▶ The GetScaledSelectionRect() method uses a trick to simplify the calculation of the unscaled coordinates of the selection Rectangle inside a scaled Image (the PictureBox Control SizeMode is most probably set to PictureBoxSizeMode.Zoom): it reads the .Net PictureBox class ImageRectangle property (who knows why, private), to determine the Image scaled bounds and calculates the offset and scale of the selection rectangle based on this measure.

Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Reflection

Friend Enum ShapeType
Rectangle
Ellipse
End Enum

Friend Function DrawHole(srcImage As Image, canvas As PictureBox, holeShape As RectangleF, typeOfShape As ShapeType) As Image
Dim cropped = New Bitmap(srcImage.Width, srcImage.Height, PixelFormat.Format32bppArgb)

Dim imageRect = New RectangleF(Point.Empty, srcImage.Size)
Dim selectionRect = GetScaledSelectionRect(canvas, holeShape)

Using tBrush = New TextureBrush(srcImage),
imagePath = New GraphicsPath(FillMode.Alternate),
selectionPath = New GraphicsPath(),
g = Graphics.FromImage(cropped)

Select Case typeOfShape
Case ShapeType.Ellipse
selectionPath.AddEllipse(selectionRect)
Case ShapeType.Rectangle
selectionPath.AddRectangle(selectionRect)
End Select
imagePath.AddRectangle(imageRect)
imagePath.AddPath(selectionPath, True)
g.SmoothingMode = SmoothingMode.AntiAlias
g.FillPath(tBrush, imagePath)
Return cropped
End Using
End Function

Friend Function GetScaledSelectionRect(canvas As PictureBox, selectionRect As RectangleF) As RectangleF
If canvas.Image Is Nothing Then Return selectionRect
Dim flags = BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.GetProperty

Dim imageRect = DirectCast(canvas.GetType().GetProperty("ImageRectangle", flags).GetValue(canvas), Rectangle)

Dim scaleX = CSng(canvas.Image.Width) / imageRect.Width
Dim scaleY = CSng(canvas.Image.Height) / imageRect.Height

Dim selectionOffset = RectangleF.Intersect(imageRect, selectionRect)
selectionOffset.Offset(-imageRect.X, -imageRect.Y)
Return New RectangleF(selectionOffset.X * scaleX, selectionOffset.Y * scaleY,
selectionOffset.Width * scaleX, selectionOffset.Height * scaleY)
End Function

C# version:

private Image drawingBitmap = null;
private RectangleF selectionRect = new RectangleF(100, 100, 50, 50);
private ShapeType shapeOfHole = ShapeType.Rectangle;
private bool preserveImage = false;

private void btnLoadImage_Click(object sender, EventArgs e)
{
string imagePath = [Your Image Path];
drawingBitmap = Image.FromStream(new MemoryStream(File.ReadAllBytes(imagePath)));
picCanvas.Image?.Dispose();
picCanvas.Image = drawingBitmap.Clone() as Bitmap;
}

private void btnPaintHole_Click(object sender, EventArgs e)
{
Image newImage = null;
if (preserveImage && picCanvas.Image != null) {
newImage = DrawHole(picCanvas.Image, picCanvas, selectionRect, shapeOfHole);
}
else {
newImage = DrawHole(drawingBitmap, picCanvas, selectionRect, shapeOfHole);
}

if (newImage != null) {
picCanvas.Image?.Dispose();
picCanvas.Image = newImage;
}
}

Worker methods:

Note: GetScaledSelectionRect(), as described, uses Reflection to read the PictureBox private ImageRectangle property from the .Net control.

Since this method is called from the drawing procedure, it's probably better to re-implement this method in a custom PictureBox control, or perform the calculations without invoking the underlying method (reflection is not as slow as sometimes advertised, but it's of course slower than using some math directly, here).

Some possible implementations are shown (for example) here:

Zoom and translate an Image from the mouse location

Translate Rectangle Position in a Picturebox with SizeMode.Zoom

internal enum ShapeType {
Rectangle,
Ellipse
}

internal Image DrawHole(Image srcImage, PictureBox canvas, RectangleF holeShape, ShapeType typeOfShape)
{
var cropped = new Bitmap(srcImage.Width, srcImage.Height, PixelFormat.Format32bppArgb);
var imageRect = new RectangleF(Point.Empty, srcImage.Size);
RectangleF selectionRect = GetScaledSelectionRect(canvas, holeShape);

using (var tBrush = new TextureBrush(srcImage))
using (var imagePath = new GraphicsPath(FillMode.Alternate))
using (var selectionPath = new GraphicsPath())
using (var g = Graphics.FromImage(cropped)) {

switch (typeOfShape) {
case ShapeType.Ellipse:
selectionPath.AddEllipse(selectionRect);
break;
case ShapeType.Rectangle:
selectionPath.AddRectangle(selectionRect);
break;
}
imagePath.AddRectangle(imageRect);
imagePath.AddPath(selectionPath, true);

g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillPath(tBrush, imagePath);
return cropped;
}
}

internal RectangleF GetScaledSelectionRect(PictureBox canvas, RectangleF selectionRect)
{
if (canvas.Image == null) return selectionRect;
var flags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetProperty;

var imageRect = (Rectangle)canvas.GetType().GetProperty("ImageRectangle", flags).GetValue(canvas);
var scaleX = (float)canvas.Image.Width / imageRect.Width;
var scaleY = (float)canvas.Image.Height / imageRect.Height;

var selectionOffset = RectangleF.Intersect(imageRect, selectionRect);
selectionOffset.Offset(-imageRect.X, -imageRect.Y);

return new RectangleF(selectionOffset.X * scaleX, selectionOffset.Y * scaleY,
selectionOffset.Width * scaleX, selectionOffset.Height * scaleY);
}


Related Topics



Leave a reply



Submit