I have created a sample app in Winforms running on .NET 4.7.1 printing a graphic font in image. Code is written in Paint handler of form as follows:
private void Form1_Paint_1(object sender, PaintEventArgs e)
{
System.Drawing.Graphics graphicsObj;
graphicsObj = this.CreateGraphics();
Font myFont = new Font("Arial", 12);
Brush myBrush = new SolidBrush(System.Drawing.Color.Red);
graphicsObj.DrawString("Hello C#", myFont, myBrush, 30, 30);
Bitmap bitmap = new Bitmap(Convert.ToInt32(200), Convert.ToInt32(200), System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(bitmap);
// Add drawing commands here
g.DrawString("Hello C#", myFont, myBrush, 30, 30);
bitmap.Save(#"net45.png", ImageFormat.Png);
}
We are porting this code to .NET 6.0 without changing anything. But the font size is different in both case.
It seems the height property in .NET 4.5
is different from the height used in .NET 6.0
How to make both font sizes the same?
We tried changing font constructor parameters, font family but result is coming same as above.
Related
I'm drawing a string on a Bitmap with transparent background using Graphics.DrawString() and I get text with a black contour, when the Font size is smaller than 23 millimeters (the Font is created with GraphicsUnit.Millimeter).
Code:
Bitmap bmp = new Bitmap(2000, 2000);
Color alpha = Color.FromArgb(0, 0, 0, 0);
for (int x = 0; x < bmp.Width; x++)
for (int y = 0; y < bmp.Height; y++)
bmp.SetPixel(x, y, alpha);
Graphics g = Graphics.FromImage(bmp);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
Font labelFont = new Font("Cascadia Mono SemiBold", 23/*22*/, FontStyle.Regular, GraphicsUnit.Millimeter);
Brush brush = new SolidBrush(Color.White);
g.DrawString("Some text", labelFont, brush, 200, 200);
23 Millimeters-Units font:
22 Millimeters-Units font:
I tried to use TextRenderer, but this draws text without transparent background.
The code presented here has multiple problems:
The initial loop is counter-productive for multiple reasons:
Tries to fill a Bitmap with a transparent color, but this is already the non-color associated with a newly created Bitmap (Color.FromArb(0, 0, 0, 0))
Uses the SetPixel() method, the slowest possible tool for the task
If needed, a Bitmap can be filled with a Color using the Graphics.Clear() method, which calls a native GDI+ function to perform the task
Setting an InterpolationMode in this context is not useful, this property selects the algorithm used to scale or rotate images
The SmoothingMode property selects the algorithm used to anti-aliasing lines, curves and the edges of filled areas. It doesn't apply to the rendering of Fonts, so has no effect on the drawn text. It applies to text rendered with a GraphicsPath, since the text is converted to curves
None of the disposable objects (Graphics, Font, Brush) is either disposed explicitly or declared with using statements (which is pretty bad). It's not clear when the Bitmap is disposed, but could be the duty of the code that uses it
To specify the rendering mode of Fonts, the TextRenderingHint property is used instead. Since it's not specified, the System default smoothing of Font is used, usually ClearType.
About this rendering form, see the notes in:
Drawing a Long String on to a Bitmap results in Drawing Issues
ClearType uses intra-pixel smoothing, designed initially for LCD screens, to blend text with a background; it's especially effective with small Fonts sizes. It doesn't support alpha colors (not in this context, at least).
The device context in which the text is rendered, a GDI+ MemoryBitmap, doesn't use or understand this type of hinting (smoothing), so the pixels that fail to render are filled with an empty color, which notoriously appears as black
A black-ish contour might manifests with different Font sizes (not just the measures reported in the question), when the ClearType hinting fill is less than one pixel
To fix the rendering, remove the clutter, specify a suitable TextRenderingHint mode and declare correctly all disposable objects.
I'm not including the Bitmap, because I don't know how it's used. It must be disposed at some point, of course (very important, it allocates unmanaged resources, the Garbage Collector cannot help you)
var bmp = new Bitmap(2000, 2000);
using (var g = Graphics.FromImage(bmp))
using (var font = new Font("Cascadia Mono SemiBold", 22, FontStyle.Regular, GraphicsUnit.Millimeter))
using (var brush = new SolidBrush(Color.White)) {
g.CompositingQuality = CompositingQuality.HighQuality;
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
g.DrawString("Some text", font, brush, 200, 200);
}
TextRenderingHint.AntiAliasGridFit is appropriate here (see also the linked notes), the characters are drawn using their anti-aliased glyph bitmap and hinting (smoothing)
TextRenderingHint.AntiAlias can also be used in this context (and costs slightly less)
CompositingQuality.HighQuality is not actually used here, there isn't really any composition, since no image is rendered against a background (which may require gamma correction), but also has no cost. You can keep it, in case you decide to draw a bitmap onto the current, at some point; or simply remove it.
About TextRenderer draws text without transparent background
This is not correct. TextRenderer (GDI) can of course render text with a transparent background, it just doesn't support an alpha color (in this context)
As mentioned, we're working with an in-memory GDI+ Device Context
But, if you draw the same text in a different Device Context, e.g., the surface of a Control, then things change.
Also note that TextRenderer cannot be used to render text when printing (for the same reasons previously described).
Test this code, subscribing to the Paint event of a PictureBox, also adding a background Image (without a background image the result doesn't change, it's just more visible)
Graphics.DrawString() is used to render text with a semi-transparent (ARGB) Color
TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.Top;
private void someControl_Paint(object sender, PaintEventArgs e)
{
using (var font = new Font("Segoe UI", 22, FontStyle.Regular, GraphicsUnit.Millimeter))
using (var brush = new SolidBrush(Color.White)) {
TextRenderer.DrawText(e.Graphics, "Some text", font,
new Rectangle(new Point(0, 10), pictureBox1.ClientSize), Color.White, Color.Transparent, flags);
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
e.Graphics.DrawString("Some text", font, brush, 0, 130);
}
using (Font font1 = new Font("Segoe UI", 8.0f, FontStyle.Regular, GraphicsUnit.Millimeter))
using (Font font2 = new Font("Segoe UI", 5.5f, FontStyle.Regular, GraphicsUnit.Millimeter))
using (var brush = new SolidBrush(Color.FromArgb(100, Color.Black))) {
e.Graphics.DrawString("← TextRenderer", font1, brush, 610, 70);
e.Graphics.DrawString("ForeColor: White, BackColor: Transparent", font2, brush, 610, 110);
e.Graphics.DrawString("← GDI+ Graphics", font1, brush, 610, 190);
e.Graphics.DrawString("ForeColor: White, Hinting: AntiAliasGridFit", font2, brush, 610, 230);
}
}
Resulting in (enlarge it):
I am trying to create a very simple system tray icon that is nothing but a dynamically colored circle with a white border. To do that, we are using the Webdings font. "n" in Webdings is just a plain circle.
What I'm currently doing is almost there, but on some PC's (yet not all) it ends up having a choppy, ugly black border around it:
Here's what I've got:
protected static Icon GetTrayIconFromCache(Color statusColor)
{
Bitmap bmp = new Bitmap(16,16);
Graphics circleGraphic = Graphics.FromImage(bmp);
circleGraphic.DrawString("n", new Font("Webdings", 12F, FontStyle.Regular), Brushes.White, -3f, -2f);
circleGraphic.DrawString("n", new Font("Webdings", 9F, FontStyle.Regular), new SolidBrush(statusColor), 0f, -1f);
Icon ico = Icon.FromHandle((bmp).GetHicon());
return ico;
}
No matter what I try, I can't get rid of those ugly black dots around the outside of the circle. They don't show up for everyone.... some of the developers don't see it and it looks crisp and clean. We've not yet figured out what the commonality is between those PC's where it looks good and where it doesn't.
But... is there a better way to do this?
Add a TextRenderingHint of AntiAliasGridFit.
protected static Icon GetTrayIconFromCache(Color statusColor)
{
Bitmap bmp = new Bitmap(16,16);
Graphics circleGraphic = Graphics.FromImage(bmp);
circleGraphic.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
circleGraphic.DrawString("n", new Font("Webdings", 12F, FontStyle.Regular), Brushes.White, -3f, -2f);
circleGraphic.DrawString("n", new Font("Webdings", 9F, FontStyle.Regular), new SolidBrush(statusColor), 0f, -1f);
Icon ico = Icon.FromHandle((bmp).GetHicon());
return ico;
}
Does GDI have any method of setting an overall clipping area, in the same way the GDI+ Graphics object does?
Specifically:
Graphics.DrawString uses GDI+ which includes the clipping bounds system.
TextRenderer.DrawText uses GDI which does not.
My new scrollbar system automatically resizes the Graphics draw area as needed. While tidying up a control which uses it, I switched some inline string rendering calls over to my text layout class, which uses TextRenderer.DrawText. I can't quite remember why I used that instead of just Graphics.DrawString, but before I refactor that I wanted to check if there was some way of fixing the problem as it stands.
public class GraphicsClipExample : Form
{
public GraphicsClipExample()
{
this.ClientSize = new Size(this.ClientSize.Width, 100);
}
protected override void OnPaint(PaintEventArgs e)
{
// update the graphics clip area
e.Graphics.Clear(Color.Cyan);
e.Graphics.SetClip(new Rectangle(0, 0, e.ClipRectangle.Width, 50));
e.Graphics.Clear(Color.Magenta);
Font font = new Font("Calibri", 24);
e.Graphics.DrawString("Testing", font, Brushes.Black, 10, 28);
TextRenderer.DrawText(e.Graphics, "Testing", font, new Point(150, 28), Color.Black);
}
}
This produces the following output:
Is there any way to provide a simple clipping area for GDI overall, or TextRenderer specifically?
Many thanks
Try using the PreserveGraphicsClipping format flag:
TextRenderer.DrawText(e.Graphics, "Testing", font, new Point(150, 28),
Color.Black, Color.Empty,
TextFormatFlags.PreserveGraphicsClipping);
Because of lack of Graphics object in certain places in my application, I decided to use TextRenderer class. What is quite surprising though is that it adds a lot of margins to measured text. For example:
private void button1_Click(object sender, EventArgs e) {
using (var g = this.CreateGraphics()) {
Font font = new Font("Calibri", 20.0f, GraphicsUnit.Pixel);
Size size = TextRenderer.MeasureText("Ala ma kota", font);
g.DrawRectangle(Pens.Red, new Rectangle(new Point(10, 10), size));
TextRenderer.DrawText(g, "Ala ma kota", font, new Point(10, 10), Color.Black);
}
}
Gives the following result:
Why does it do so? Is there a way to force it to get the real text size? (and of course draw it in the same rectangle it returns)
From MSDN:
For example, the default behavior of the TextRenderer is to add padding to the bounding rectangle of the drawn text to accommodate overhanging glyphs. If you need to draw a line of text without these extra spaces, use the versions of DrawText and MeasureText that take a Size and TextFormatFlags parameter, as shown in the example.
You must also pass the Graphics object for correct results, because:
This overload of MeasureText(String, Font, Size, TextFormatFlags) will ignore a TextFormatFlags value of NoPadding or LeftAndRightPadding. If you are specifying a padding value other than the default, you should use the overload of MeasureText(IDeviceContext, String, Font, Size, TextFormatFlags) that takes a IDeviceContext object.
Size size = TextRenderer.MeasureText(g,
"Ala ma kota",
font,
new Size(int.MaxValue, int.MaxValue),
TextFormatFlags.NoPadding);
TextRenderer.DrawText(g, "Ala ma kota", font,
new Point(10, 10),
Color.Black,
TextFormatFlags.NoPadding);
g.DrawRectangle(Pens.Red, new Rectangle(new Point(10, 10), size));
Also have a look at using the Graphics methods directly: GDI+ MeasureString() is incorrectly trimming text
I have a strong amount of pictures, which i would like to "protect" by adding watermark on them. Is there any way of adding watermark by using vb.net or C# ?
public void AddWatermark(string filename, string watermarkText, Stream outputStream) {
Bitmap bitmap = Bitmap.FromFile(filename);
Font font = new Font("Arial", 20, FontStyle.Bold, GraphicsUnit.Pixel);
Color color = Color.FromArgb(10, 0, 0, 0); //Adds a black watermark with a low alpha value (almost transparent).
Point atPoint = new Point(100, 100); //The pixel point to draw the watermark at (this example puts it at 100, 100 (x, y)).
SolidBrush brush = new SolidBrush(color);
Graphics graphics = null;
try {
graphics = Graphics.FromImage(bitmap);
} catch {
Bitmap temp = bitmap;
bitmap = new Bitmap(bitmap.Width, bitmap.Height);
graphics = Graphics.FromImage(bitmap);
graphics.DrawImage(temp, new Rectangle(0, 0, bitmap.Width, bitmap.Height), 0, 0, bitmap.Width, bitmap.Height, GraphicsUnit.Pixel);
temp.Dispose();
}
graphics.DrawString(text, font, brush, atPoint);
graphics.Dispose();
bitmap.Save(outputStream);
}
Use ImageMagick with the .Net wrapper.
This blog post promises:
This article shall describe an approach to building a simple watermarking utility that may be used to add watermarks to any supported image file format. The resulting application shall permit the user to open any supported image file format into a scrollable picture box, to define the text to be applied as a watermark (with a default version supplied), to set the font and color of the watermark, to define the opacity of the watermark, to determine whether or not the watermark appears at the top or bottom of the image, and to preview the watermark prior to saving it to the image.
It should provide a good starting point.