Currently, I'm successfully using the Graphics class to draw a non-rectangular clipped image (the turtle inside):
My code looks something like:
using (var g = Graphics.FromImage(image))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
using (var gfxPath = new GraphicsPath())
{
gfxPath.AddEllipse(r);
using (var region = new Region(r))
{
region.Exclude(gfxPath);
g.ExcludeClip(region);
g.DrawImage(turtleImage, r, r2, GraphicsUnit.Pixel);
}
}
}
This all works just as expected. What I do not know how to solve is to make the image border anti-aliased.
The image zoomed looks like:
I.e. the border where the image ends and the transparent "background" of the image starts is a rough cut, not a smooth alpha blending.
My question is:
Is it possible to clip a drawn image and having anti-aliasing active?
If you want to go for full blown feathering you should consider taking a look at this article:
http://danbystrom.se/2008/08/24/soft-edged-images-in-gdi/
If you want a quick and easy solution you could probably draw the image first then draw a GraphicsPath on top of it using a solid white brush with antialiasing. You would do something like this:
Rectangle outerRect = ClientRectangle;
Rectangle rect = Rectangle.Inflate(outerRect, -20, -20);
using (Image img = new Bitmap("test.jpg"))
{
g.DrawImage(img, outerRect);
using (SolidBrush brush = new SolidBrush(Color.White))
using (GraphicsPath path = new GraphicsPath())
{
g.SmoothingMode = SmoothingMode.AntiAlias;
path.AddEllipse(rect);
path.AddRectangle(outerRect);
g.FillPath(brush, path);
}
}
The other answers here won't work if you want a transparent background because you cannot draw with a transparent brush - it doesn't do anything.
I found other answers that can do it (for example, using SetClip), but it doesn't come out with an anti-aliased edge.
I found this answer that works, but that one is designed to just round the corners, not make it a circle. So I modified it.
Here's how you can crop an image to a circle with a transparent background and anti-aliased edges:
/// <summary>
/// Crop the given image into a circle (or ellipse, if the image isn't square)
/// </summary>
/// <param name="img">The image to modify</param>
/// <returns>The new, round image</returns>
private static Bitmap CropCircle(Image img) {
var roundedImage = new Bitmap(img.Width, img.Height, img.PixelFormat);
using (var g = Graphics.FromImage(roundedImage))
using (var gp = new GraphicsPath()) {
g.Clear(Color.Transparent);
g.SmoothingMode = SmoothingMode.AntiAlias;
Brush brush = new TextureBrush(img);
gp.AddEllipse(0, 0, img.Width, img.Height);
g.FillPath(brush, gp);
}
return roundedImage;
}
The other answers draw the background color on top of the image. Instead, this creates a new, transparent image first, then draws a cut-out of the image on top.
I'll like to share my solution, which is based on the selected answer.
This code Resize and Crop an image into a Circle applying antialias to the edges. It also Prevents the loss of the image on Mouse Over or Window Resize. Cropped image can easily be saved.
/// <summary>Redimensiona y recorta la imagen en forma de Circulo (con Antialias).</summary>
/// <param name="srcImage">Imagen Original a Recortar</param>
/// <param name="size">Tamaño deseado (en pixeles)</param>
/// <param name="BackColor">Color de fondo</param>
public static Image CropToCircle(System.Drawing.Image srcImage, Size size, System.Drawing.Color BackColor)
{
System.Drawing.Image Canvas = new System.Drawing.Bitmap(size.Width, size.Height, srcImage.PixelFormat);
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(Canvas);
System.Drawing.Rectangle outerRect = new System.Drawing.Rectangle(-1, -1, Canvas.Width + 1, Canvas.Height + 1);
System.Drawing.Rectangle rect = System.Drawing.Rectangle.Inflate(outerRect, -2, -2);
g.DrawImage(srcImage, outerRect);
using (System.Drawing.SolidBrush brush = new System.Drawing.SolidBrush(BackColor))
using (System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath())
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
path.AddEllipse(rect);
path.AddRectangle(outerRect);
g.FillPath(brush, path);
}
return Canvas;
}
Usage: (Desired Size is 64x64 pix with White Background)
System.Drawing.Image img = System.Drawing.Image.FromFile(#"E:\Mis Documentos\Mis imágenes\ergo-proxy-fullon-fight.jpg");
System.Drawing.Image circle = Util.CropToCircle(img, new System.Drawing.Size(64,64), System.Drawing.Color.White);
if (circle != null)
{
this.picUser.Image = circle;
}
I had the same issue with constructing a circular profile picture with a transparent background. The tactic I finally settled on was to resize the image to an arbitrary multiple (in my case 5x), do the clipping operation, then shrink it back to the original size while using SmoothingMode.AntiAlias. I get a nicely feathered edge on the picture.
Is it a hack? Yes. Is it performant? Mmm, probably not. Does it work? Perfectly!
Related
I have some C# code that adds a simple text overlay with a border and semi-transparent background to an image. It works great, but I'm trying to get an equivalent result using Magick.NET. (The straight C# code drops the XMP tags from the original image, and I haven't found a way to deal with that.) Magick.NET handles the XMP tags well, but I'm having trouble replicating the original output.
Original code follows:
using (Image i = Image.FromStream(stream))
{
int width = i.Width;
int height = i.Height;
using (Graphics graphics = Graphics.FromImage(i))
{
string measureString = "my string";
Size stringSize = graphics.MeasureString(measureString, stringFont).ToSize();
Point drawLocation = new Point(width - stringSize.Width - 15, height - stringSize.Height - 15);
Rectangle rect = new Rectangle(drawLocation.X, drawLocation.Y, stringSize.Width, stringSize.Height);
graphics.DrawRectangle(blackPen, rect);
graphics.FillRectangle(fillBrush, rect);
graphics.DrawString(measureString, stringFont, Brushes.Yellow, drawLocation);
}
i.Save(outputFolder + Path.GetFileName(imgFileName));
}
I cobbled this together based on the Magick.NET examples. This get close to what I'm looking for, but adding the border removes the transparency value, and I'm left with a dark gray background, instead of the transparency.
var settings = new MagickReadSettings{
Font = "Calibri",
FillColor=MagickColors.Yellow,
StrokeColor=MagickColors.Black,
TextGravity = Gravity.Center,
BackgroundColor = new MagickColor("#66666699"),
BorderColor = MagickColors.Black,
Height = 250, // height of text box
Width = 680 // width of text box
};
using (var image = new MagickImage(inputFile))
{
using (var caption = new MagickImage($"caption:{myString}", settings))
{
//adding this border removes transparency
// caption.BorderColor = MagickColors.Black;
// caption.Border(1);
image.Composite(caption, Gravity.Southeast, CompositeOperator.Over);
image.Write(outputFile);
}
}
In command line ImageMagick, this seems to work for me in that the background color is transparent gray. The following the result may be what you want:
convert -font ubuntu -fill yellow -stroke black -gravity center -background "#66666699" -bordercolor black -size 250x680 caption:"This Is Some Text" result.png
Note: I used -background, not -backgroundcolor. Also BorderColor is not the color for the outline of the text. That is the stroke. You have not used BorderColor, since you have not specified the Border amount (as in -border in command line), which would outline the image rectangle and not the text.
Due to time constraints with my project, I took a slightly different path to make this work. I wound up creating the transparent overlay using my original .NET drawing code, and passing that as a memory stream to Magick.NET to handle the merge.
Workaround:
string measureString = "build custom string here";
using (var tmpStreamImg = new MemoryStream())
{
// Call custom function to get length of my string
System.Drawing.Size stringSize = MeasureString(measureString, stringFont).ToSize();
Rectangle rect = new Rectangle(0, 0, stringSize.Width, stringSize.Height);
using (Bitmap overlay = new Bitmap(rect.Width, rect.Height))
{
overlay.SetResolution(350, 350);
using (Graphics overlayGraphic = Graphics.FromImage(overlay))
{
overlayGraphic.DrawRectangle(blackPen, rect);
overlayGraphic.FillRectangle(fillBrush, rect);
overlayGraphic.DrawString(measureString, stringFont, Brushes.Yellow, 3, 3);
}
overlay.Save(tmpStreamImg, ImageFormat.Png);
}
tmpStreamImg.Position= 0;
using (var originalImage = new MagickImage(imgFileName))
{
using (var overlayImage = new MagickImage(tmpStreamImg))
{
originalImage.Composite(overlayImage, Gravity.Southeast, CompositeOperator.Over);
originalImage.Write(outputFolder + Path.GetFileName(imgFileName));
}
}
}
I have a picture I want to print, but it's too big for one page
so i have decided to split it into multiple images
i have tried a method, but now im using this (Talha Irfan answer)
i also tried the other solutions there but those didnt worked as well
(ex. bm.Clone(rec, bm.PixelFormat);)
and here's my code(this is on non-form class)
Bitmap bm = new Bitmap(frmPrint.Width, frmPrint.Height);
Rectangle rec = new Rectangle(0, 200, 576, 300);
Bitmap bitmap = cropImg(bm, rec);
frmPrint.DrawToBitmap(bitmap, rec);
frmPrint._img = bitmap;
frmPrint.setImage();
and setImage function(on some form)
public void setImage()
{
pictureBox3.BackgroundImage = _img;
this.ShowDialog();
}
and cropImg is the same as cropAtRect
the below shows the original image (on the left)
the wanted result in the blue rectangle
and the actual result on the right
PS
my actual image size is (height = 698, wifht = 576)
Edit - as suggested below
on non-form class
Rectangle cropRect = new Rectangle(0, 0, 576, 698);
Bitmap target = new Bitmap(cropRect.Width, cropRect.Height, bm.PixelFormat);
frmPrint.setImage(bm, target, cropRect);
target.Dispose();
at form class
public void setImage(Bitmap src, Bitmap target, Rectangle cropRect)
{
pictureBox3.Visible = false;
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(src, new Rectangle(pictureBox3.Location.X, pictureBox3.Location.Y, target.Width, target.Height),
cropRect,
GraphicsUnit.Pixel);
}
this.ShowDialog();
}
Control.DrawToBitmap will always try to draw the whole control or form and will always start at the top. The parameter:
targetBounds
Type: System.Drawing.Rectangle
The bounds within which the control is rendered.
as the name implies, sets the target, not the source rectangle. Hence the white space above your result.
Move the line before cropping with a rectangle that holds the full area, maybe like this:
DrawToBitmap(bm, ClientRectangle);
and then crop the lower part as before..
Note that the cropping trick from your link will not work for DrawToBitmap; using a rectangle with a negative offset will cause a parameter exception.
Btw: to safely dispose of a Bitmap in a PictureBox use this:
Bitmap dummy = (Bitmap )somePictureBox.Image;
somePictureBox.Image = null;
if (dummy != null) dummy.Dispose;
And, indeed, the answer by ChrisJJ in the link leaks the Graphics object.
Update:
Since you seem to have lost control over the various changes and suggestions, here is the minimal code change from the original post:
Bitmap bm = new Bitmap(frmPrint.ClientWidth, frmPrint.ClientHeight);
DrawToBitmap(bm, frmPrint.ClientRectangle);
Rectangle rec = new Rectangle(0, 200, 576, 300);
Bitmap bitmap = cropImg(bm, rec);
frmPrint._img = bitmap;
frmPrint.setImage();
With:
public void setImage()
{
Bitmap dummy = pictureBox3.BackgroundImage;
pictureBox3.BackgroundImage = null;
if (dummy != bnull) dummy.Dispose();
pictureBox3.BackgroundImage = _img;
this.ShowDialog();
}
In the cropImg function add a g.Dispose before returning.
I'm looking to expand my 'simple' photography events system to add the ability to add custom text to images we've shot. I technically have this aspect working using the existing picturebox control to display the image and a text box in which text can be entered and this will be added to the image being displayed.
However, being a photographer, I'd like the text to look a little nicer and as such am looking to emulate what I can do in Photoshop, i.e. bevel/emboss, add inner glows and drop shadows to this text but I'm struggling to find any references to this.
I may be simply limited by the fact I'm using winforms and this may have been achievable via WPF, but WPF wasn't about when I stopped being a programmer for a profession and as such stuck to technology I knew... I'm also far too far down the line in the system to re-write it all in WPF, so if its a limitation I'll just look at adding in pre-determined overlays rather than custom text which I know I can achieve.
The code I have so far is as follows and any tips on how to expand this to perform the bevel/emboss, glows etc would be much appreciated.
public static Bitmap addTexttoImage(string imagename, string textnya)
{
float fontSize = 80;
string imagepath = imagename;
Image image = Image.FromStream(new MemoryStream(File.ReadAllBytes(imagepath)));
//read the image we pass
Bitmap bmp = (Bitmap)Image.FromFile(imagepath);
Graphics g = Graphics.FromImage(bmp);
//this will centre align our text at the bottom of the image
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Far;
//define a font to use.
Font f = new Font("Impact", fontSize, FontStyle.Bold, GraphicsUnit.Pixel);
//pen for outline - set width parameter
Pen p = new Pen(ColorTranslator.FromHtml("#77090C"), 8);
p.LineJoin = LineJoin.Round; //prevent "spikes" at the path
//this makes the gradient repeat for each text line
Rectangle fr = new Rectangle(0, bmp.Height - f.Height, bmp.Width, f.Height);
LinearGradientBrush b = new LinearGradientBrush(fr,
ColorTranslator.FromHtml("#FF6493"),
ColorTranslator.FromHtml("#D00F14"),
90);
//this will be the rectangle used to draw and auto-wrap the text.
//basically = image size
Rectangle r = new Rectangle(0, 0, bmp.Width, bmp.Height);
GraphicsPath gp = new GraphicsPath();
gp.AddString(textnya, f.FontFamily, (int)FontStyle.Bold, fontSize, r, sf);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.DrawPath(p, gp);
g.FillPath(b, gp);
//cleanup
gp.Dispose();
b.Dispose();
b.Dispose();
f.Dispose();
sf.Dispose();
g.Dispose();
return bmp;
}
I'm doing some draws that are repetitive, and each of them gives lot of work.
What i need to do is rotate the drawing half-way its definition, something like this:
using (Graphics g = Graphics.FromImage(bmp))
{
//define area do pictureBox e preenche a branco
Brush brush = new SolidBrush(Color.White);
Rectangle area = new Rectangle(0, 0, 520, 520);
g.FillRectangle(brush, area);
//rotate
g.RotateTransform(some angle, some reference point)
//draw some more lines on the top of the rotated previous ones.
}
I tried using g.RotateTransform(90) as there is that function in Winforms, but it didn't change anything. Why??
Any tip?
Edit: if it helps, i only need to rotate fixed angles, 180º
RotateTransform certainly does change the subsequent drawing.
Note that you usually need a TranslateTransform before to set the rotation point. But it 'it didn't change anything' is certainly wrong. Try again! And yes you can rotate (or scale or move) at any point and move/turn it back or completely reset the Graphics object.
And yes, learning about Matrix and MultiplyTransform is also very helpful..
But: You need to understand the Graphics object does not contain any graphic, a common misconception! It is the tool which does the drawing on a Bitmap, most often the surface of a Control. So the rotation will happen but only for the things you draw after:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Rectangle rect = new Rectangle(25, 25, 25, 25);
e.Graphics.TranslateTransform(25, 25);
e.Graphics.FillRectangle(Brushes.Red, rect);
for (int i = 0; i < 15; i++)
{
rect.Inflate(2, 2);
e.Graphics.TranslateTransform(5, 2);
e.Graphics.RotateTransform(2.5f);
e.Graphics.DrawRectangle(Pens.Blue, rect);
}
}
Try this:
use these references:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
I made windows application and put on form1 picturebox then this is the code in form_load:
//Load an image in from a file
Bitmap pImage = new Bitmap(Environment.CurrentDirectory + #"\Image.bmp", true);
//Set our picture box to that image
pictureBox1.Image = (Bitmap)pImage.Clone();
//Store our old image so we can delete it
Image oldImage = pictureBox1.Image;
//Pass in our original image and return a new image rotated 35 degrees right
pictureBox1.Image = RotateImage(pImage, 270);
if (oldImage != null)
{
oldImage.Dispose();
}
Then make static function with parameters of image and rotation angle return the rotated image and call it from form_load as mentioned before :
if (image == null)
{
throw new ArgumentNullException("image");
}
else
{
//create a new empty bitmap to hold rotated image
Bitmap rotatedBmp = new Bitmap(image.Width, image.Height);
rotatedBmp.SetResolution(image.HorizontalResolution, image.VerticalResolution);
//make a graphics object from the empty bitmap
Graphics g = Graphics.FromImage(rotatedBmp);
//move rotation point to center of image
g.TranslateTransform((float)image.Width / 2, (float)image.Height / 2);
//rotate
g.RotateTransform(angle);
//move image back
g.TranslateTransform(-(float)image.Width / 2, -(float)image.Height / 2);
//draw passed in image onto graphics object
g.DrawImage(image, new PointF(0, 0));
return rotatedBmp;
}
You can also use directly the code in form_load by using ready RotateFlipType (Enumeration type) but this with fixed angles like 90,270,.... but the previous method you can use any integer values to rotate the image :
private void Form1_Load(object sender, EventArgs e)
{
//Load an image in from a file
Bitmap pImage = new Bitmap(Environment.CurrentDirectory + #"\Image.bmp", true);
pImage.RotateFlip(RotateFlipType.Rotate90FlipXY);
pictureBox1.Image = pImage;
}
I'm trying to draw images to return as base 64 strings over a web service in .net 4.5. I can get as far as loading a custom background, but then I need to draw text onto that background. The problem is, once I go from Image->Graphics object->Image, I end up with a blank png. If I return the original srcImage (the blank template) over the service, everything works, but my label is blank so I know it must be a problem with my graphics object somewhere.
My code is:
var labelSize = new Size(400, 459);
using (var srcImage = Image.FromFile(HostingEnvironment.MapPath("~/images/labels/" + labelImageFilename))) {
PixelFormat format = srcImage.PixelFormat;
using (Bitmap newImage = new Bitmap(labelSize.Width, labelSize.Height, format))
using (Graphics g = Graphics.FromImage(newImage)) {
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
Rectangle srcRect = new Rectangle(0, 0, srcImage.Width, srcImage.Height);
Rectangle destRect = new Rectangle(0,0, labelSize.Width, labelSize.Height);
g.DrawImage(srcImage, destRect, srcRect, GraphicsUnit.Pixel);
// draw other shapes etc
g.FillRegion(Brushes.Blue,new Region(new Rectangle(0,0,200,200)));
g.Clear(Color.Red);
g.Flush();
return new Bitmap(srcImage, labelSize.Width, labelSize.Height); // this works fine, but my image is just the standard background I'm using
return new Bitmap(labelSize.Width, labelSize.Height, g); // returns a blank image
}
}
Nothing is drawn, neither the template background (srcImage), not the red or blue rectangles.
I think it looks like your graphics object is drawing on newImage, but you are returning srcImage. If you want to return the result of your drawing, I think you need to return newImage.
Try something like:
return new Bitmap(newImage, labelSize.Width, labelSize.Height);