I want to anti-alias like on this screenshot:
I've tried all the TextRenderingHints, but it still looks bad. Notice that when I did the screenshot the PNG smoother it, but you can still see that the second text isn't as good as the first.
Here's my code:
private void Form1_Paint(object sender, PaintEventArgs e) {
try {
SolidBrush solidBrush = new SolidBrush(Color.Black);
PrivateFontCollection fonts;
FontFamily family = LoadFontFamily(Properties.Resources.SegoeWP_Light, out fonts);
Font font = new Font(family, 18);
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.TextRenderingHint = TextRenderingHint.SystemDefault;
e.Graphics.DrawString("Downloads", font, solidBrush, new PointF(135, 80));
} catch (Exception ex) {
MessageBox.Show(ex.ToString());
}
}
Is there a different way to smooth fonts on WinForms (.NET 2.0) ?
I believe Photoshop does oversampling. You might not be able to get results quite as good as Photoshop -- it's got better algorithms for pixel snapping, and it probably does things like grow the outlines slightly to get a darker result -- but if SmoothingMode.AntiAlias (as George suggested) isn't enough, you can try these steps to see if they get the results you want.
Create a new Font with a size twice as big as what you want to end up with.
Measure the text using the larger font, and create a Bitmap of that size. Make sure to create it using a format that supports alpha transparency.
Draw the text to your Bitmap (at (0, 0)). (You probably want to use SmoothingMode.AntiAlias here.)
Draw the Bitmap to your final target, resizing it down by 50%. Make sure to use a high-quality resizing mode.
This will give more of the "smooth" effect from your sample image, because you're basically drawing to a sub-pixel grid. Your results may vary, though -- you might need to draw at (0, 1) instead of (0, 0) to get it to look sharp enough, and you may need to tweak the font size. You could even try drawing at 3x instead of 2x, but you won't get much benefit from going farther than that.
The big concern would be that your text might not come out dark enough -- if lines draw at 1 pixel wide in the 2x size, then they'll only be 50% black in the final result. The best way to deal with this is to pick a font with thicker lines. You could also try overdrawing the text in your Bitmap -- draw it once at (0, 0), then again at (0, 1) or (1,0) -- to see if that thickens the lines enough to look good once you size it back down. You'll have to experiment.
I wouldn't recommend this for general-purpose text drawing (where the user selects the layout and the font size), but if you can be in full control of the font size, you can probably tweak this to get pretty good results.
I would have thought the below would have been enough.
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
If not maybe try the sample on the page below with no changes, then gradually add in your changes until it breaks.
How to: Use Antialiasing with Text
Related
I'm trying to make something similar to paint. I'm trying to figure out how make different brush styles. Like in Paint 3D you get a certain line fills when using the pen tool vs using the paint brush tool.
I have no idea where to even start. I've spent a good portion of the day looking through documentations, and watching YouTube videos. I'm more lost than when I started. The closest thing I came across was line caps, but that's definitely not what I'm looking for.
!!See the UPDATE below!!
Hans' link should point you in the right direction, namely toward TextureBrushes.
To help you further here a few points to observe:
TextureBrush is a brush, not a pen. So you can't follow a path, like the mouse movements to draw along that curve. Instead, you need to find an area to fill with the brush.
This also implies that you need to decide how and when to trigger the drawing; basic options are by time and/or by distance. Usually, the user can set parameters for these often called 'flow' and 'distance'..
Instead of filling a simple shape and drawing many of those, you can keep adding the shapes to a GraphicsPath and fill that path.
To create a TextureBrush you need a pattern file that has transparency. You can either make some or download them from the web where loads of them are around, many for free.
Most are in the Photoshop Brush format 'abr'; if they are not too recent (<=CS5) you can use abrMate to convert them to png files.
You can load a set of brushes to an ImageList, set up for large enough size (max 256x256) and 32bpp to allow alpha.
Most patterns are black with alpha, so if you want color you need to create a colored version of the current brush image (maybe using a ColorMatrix).
You may also want to change its transparency (best also with the ColorMatrix).
And you will want to change the size to the current brush size.
Update
After doing a few tests I have to retract the original assumption that a TextureBrush is a suitable tool for drawing with textured tips.
It is OK for filling areas, but for drawing free-hand style it will not work properly. There are several reasons..:
one is that the TextureBrush will always tile the pattern in some way, flipped or not and this will always look like you are revealing one big underlying pattern instead of piling paint with several strokes.
Another is that finding the area to fill is rather problematic.
Also, tips may or may not be square but unless you fill with a rectangle there will be gaps.
See here for an example of what you don't want at work.
The solution is really simple and much of the above still applies:
What you do is pretty much regular drawing but in the end, you do a DrawImage with the prepared 'brush' pattern.
Regular drawing involves:
A List<List<Point>> curves that hold all the finished mouse paths
A List<Point> curentCurve for the current path
In the Paint event you draw all the curves and, if it has any points, also the current path.
For drawing with a pattern, it is necessary to also know when to draw which pattern version.
If we make sure not to leak them we can cache the brush patterns..:
Bitmap brushPattern = null;
List<Tuple<Bitmap,List<Point>>> curves = new List<Tuple<Bitmap,List<Point>>>();
Tuple<Bitmap, List<Point>> curCurve = null;
This is a simple/simplistic caching method. For better efficiency you could use a Dictionary<string, Bitmap> with a naming scheme that produces a string from the pattern index, size, color, alpha and maybe a rotation angle; this way each pattern would be stored only once.
Here is an example at work:
A few notes:
In the MouseDown we create a new current curve:
curCurve = new Tuple<Bitmap, List<Point>>(brushPattern, new List<Point>());
curCurve.Item2.Add(e.Location);
In the MouseUp I add the current curve to the curves list:
curves.Add(new Tuple<Bitmap, List<Point>>(curCurve.Item1, curCurve.Item2.ToList()));
Since we want to clear the current curve, we need to copy its points list; this is achieved by the ToList() call!
In the MouseMove we simply add a new point to it:
if (e.Button == MouseButtons.Left)
{
curCurve.Item2.Add(e.Location);
panel1.Invalidate();
}
The Paint goes over all curves including the current one:
for (int c = 0; c < curves.Count; c++)
{
e.Graphics.TranslateTransform(-curves[c].Item1.Width / 2, -curves[c].Item1.Height / 2);
foreach (var p in curves[c].Item2)
e.Graphics.DrawImage(curves[c].Item1, p);
e.Graphics.ResetTransform();
}
if (curCurve != null && curCurve.Item2.Count > 0)
{
e.Graphics.TranslateTransform(-curCurve.Item1.Width / 2, -curCurve.Item1.Height / 2);
foreach (var p in curCurve.Item2)
e.Graphics.DrawImage(curCurve.Item1, p);
e.Graphics.ResetTransform();
}
It makes sure the patterns are drawn centered.
The ListView is set to SmallIcons and its SmallImageList points to a smaller copy of the original ImageList.
It is important to make the Panel Doublebuffered! to avoid flicker!
Update: Instead of a Panel, which is a Container control and not really meant to draw onto you can use a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.
Btw: The above quick and dirty example has only 200 (uncommented) lines. Adding brush rotation, preview, a stepping distance, a save button and implementing the brushes cache takes it to 300 lines.
This has become a serious blocker for a program I'm working on to manipulate images that have Alpha channels.
Many of the images I have contain color information where an Alpha channel is completely transparent, and yet as soon as I try to load them into System.Drawing.Graphics, it changes anything with of Alpha of 0, into Black with an Alpha of 0.
Here is a basic sample of the issue.
I have looked around trying to find a reason, answer, or workaround, but I haven't found anything that even alludes to this issue.
Any help would be appreciated at this point.
var myTestTransparentColor = Color.FromArgb(0, 255, 128, 64);
var image = new Bitmap(135, 135, PixelFormat.Format32bppArgb);
using (var g = Graphics.FromImage(image))
{
g.Clear(myTestTransparentColor);
}
var color = image.GetPixel(0, 0);
Debug.Assert(color == myTestTransparentColor, "channels must match original");
EDIT:
After further testing I don't really see a way ahead by using System.Drawing.Graphics, so my only solution which is not really an answer, is to avoid System.Drawing.Graphics entirely. Looking through my code, it looks like I can avoid it.
Its just after years of using System.Drawing.Graphics for drawing shapes, planting text over images, I find it irritating for System.Drawing.Graphics to have a significant drawback like this.
I still would like to know if I can use System.Drawing.Graphics and keep my ARGB intact, but I guess I can live without it for now.
I think Vincent Povirk has answered my question appropriately here: Drawing PixelFormat32bppPARGB images with GDI+ uses conventional formula instead of premultiplied one
"The format of your foreground image doesn't matter (given that it has alpha) because you're setting it to a Gdiplus::Color. Color values are defined as non-premultiplied, so gdiplus multiplies the components by the alpha value when it clears the foreground image. The alternative would be for Color values to have different meaning depending on the format of the render target, and that way lies madness."
"If you really want this level of control over the rendering, you'll have to lock the bitmap bits and do it yourself."
So, I am doing it myself.
I have an image where I need to change the background colour (E.g. changing the background of the example image below to blue).
However, the image is anti-aliased so I cannot simply do a replace of the background colour with a different colour.
One way I have tried is creating a second image that is just the background and changing the colour of that and merging the two images into one, however this does not work as the border between the two images is fuzzy.
Is there any way to do this, or some other way to achieve this that I have no considered?
Example image
Just using GDI+
Image image = Image.FromFile("cloud.png");
Bitmap bmp = new Bitmap(image.Width, image.Height);
using (Graphics g = Graphics.FromImage(bmp)) {
g.Clear(Color.SkyBlue);
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.PixelOffsetMode = PixelOffsetMode.None;
g.DrawImage(image, Point.Empty);
}
resulted in:
Abstractly
Each pixel in your image is a (R, G, B) vector, where each component is in the range [0, 1]. You want a transform, T, that will convert all of the pixels in your image to a new (R', G', B') under the following constraints:
black should stay black
T(0, 0, 0) = (0, 0, 0)
white should become your chosen color C*
T(1, 1, 1) = C*
A straightforward way to do this is to choose the following transform T:
T(c) = C* .* c (where .* denotes element-wise multiplication)
This is just standard image multiplication.
Concretely
If you're not worried about performance, you can use the (very slow) methods GetPixel and SetPixel on your Bitmap to apply this transform for each pixel in it. If it's not clear how to do this, just say so in a comment and I'll add a detailed explanation for that part.
Comparison
Compare this to the method presented by LarsTech. The method presented here is on the top; the method presented by LarsTech is on the bottom. Notice the undesirable edge effects on the bottom icon (white haze on the edges).
And here is the image difference of the two:
Afterthought
If your source image has a transparent (i.e. transparent-white) background and black foreground (as in your example), then you can simply make your transform T(a, r, g, b) = (a, 0, 0, 0) then draw your image on top of whatever background color you want, as LarsTech suggested.
If it is a uniform colour you want to replace you could convert this to an alpha. I wouldn't like to code it myself!
You could use GIMP's Color To Alpha source code (It's GPL), here's a version of it
P.S. Not sure how to get the latest.
Background removal /replacement, IMO is more art than science, you’ll not find one algorithm fit all solution for this BUT depending on how desperate or interested you are in solving this problem, you may want to consider the following explanation:
Let’s assume you have a color image.
Use your choice of decoding mechanism and generate a gray scale / luminosity image of your color image.
Plot a graph (metaphorically speaking) of numeric value of the pixel(x) vs number of pixels in the image for that value(y). Aka. a luminosity histogram.
Now if your background is large enough (or small), you’d see a part of the graph representing the distribution of a range of pixels which constitute your background. You may want to select a slightly wider range to handle the anti-aliasing (based on a fixed offset that you define if you are dealing with similar images) and call it the luminosity range for your background.
It would make your life easier if you know at least one pixel (sample/median pixel value) out of the range of pixels which defines your background, that way you can ‘look up’ the part of the graph which defines your background.
Once you have the range of luminosity pixels for the background, you may run through the original image pixels, compare their luminosity values with the range you have, if it falls within, replace the pixel in the original image with the desired color, preferably luminosity shifted based on the original pixel and the sample pixel, so that the replaced background looks anti-aliased too.
This is not a perfect solution and there are a lot of scenarios where it might fail / partially fail, but again it would work for the sample image that you had attached with your question.
Also there are a lot of performance improvement opportunities, including GPGPU etc.
Another possible solution would be to use some of the pre-built third party image processing libraries, there are a few open source such as Camellia but I am not sure of what features are provided and how sophisticated they are.
I have problem with drawing string in c#
Here is my code:
Graphic.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
Pen pen = new Pen(brush, 2f);
Font font = new Font("Segoue UI", 15);
graphic.DrawString("2", font, brush, new PointF(0f, 0f));
Previously i created picturebox and i did graphic obiect from it.
The problem is that "2" is not drawing in (0;0) but in (4;5). Event if i turn off AntiAlias is still in (4;5).
I check it by drawing two lines from (0;0) to (200;0) and from (0;0) to (0;200) and according to them i calculated that error in drawing string.
Any ideas why? I need draw string in exactly point.
//Edit
i was also trying set font in this way:
Font font = new Font("Microsoft Sans Serif", 10);
and draw "2" in (0,0), the real position of "2" is (3,3). With drawing "1" error is the same (3;3).
Graphics.DrawString is automatically padding the box it uses to draw strings. The reason is that it's purpose is to draw label texts etc. for controls and therefor is "ready-made" so there is padding around the text.
Microsoft eventually realized that this approach wasn't the best for situations when one needed more accurate text drawing (ie. text editors etc.), and for that reason developed the TextRendered class which wraps GDI instead of GDI+ and give back the "old" more accurate string drawing.
Try:
TextRenderer.DrawText(e.Graphics, "2", font, new Point(0, 0), brush);
You might experience padding even with this, but here you can more reliable compensate for that. Check the link below to see what flags you can use with the method.
Note: if you need to measure text it's important to use the TextRenderer.MeasureText() for this.
For more details, go to:
http://msdn.microsoft.com/en-us/library/system.windows.forms.textrenderer.aspx
Flags:
http://msdn.microsoft.com/en-us/library/w3cdh3zw.aspx
what happens if your "string" has a descending letter, like a g or a j? each font allows for descending parts of letters, so you might need to account for that by looking at the font's metrics
http://msdn.microsoft.com/en-us/library/xwf9s90b.aspx
In my program I need to generate a bitmap with all digit characters (0..9) laid from left to right, plus a few other characters.
The user will select from the UI:
the desired font, and
the desired character height in pixels.
So I want to create a Bitmap, then a Graphics from this bitmap, then draw the digits one by one to this bitmap, and then save it to disk.
What I couldn't figure out in hours:
How do I create a font with the correct size so that the digit '0' has the height given by the user?
I played with all the parameters in the Font constructor, with properties of StringFormat, with MeasureString/MeasureText/MeasureCharacterRanges, I tried creating the font with the native CreateFont() via P/Invoke (with positive/negative nHeight). All parameters called "size" or "height" seem to indicate the size of some hypothetical character that's much larger than '0'.
I did read the theory with line height, em height, ascent, descent etc. There seem to be no notion for the real height of a character, without padding and spacing and so on
Again my question: Given the font name and the desired pixel height for the '0' glyph, how can I create a font which draws that glyph with the required height?
I would appreciate any guidance.
LATER EDIT
Some more details about my project: It's an embedded device with a big display, and I need to provide an easy way for the designers to to generate and try out bitmap fonts of their liking. A Windows tool that generates such bitmap fonts seemed like a good solution to me.
While I wasn't far off the mark with what I posted here earlier, it didn't actually work. In banging my head against this I found it interesting to note that graphics.MeasureString("M", ...) reports a height far larger than graphics.MeasureString("MM", ...). In calculating the font sizes by hand I've found the first(which correlates with GetEMSize's response) is actually the full line size. In the end I came to realize the actual character sizes of the digits aren't required to correlate to any of the metrics, whether real or .net.
I believe you could use this Font Constructor to specify the GraphicsUnit to be pixels. Then it should create the font with the appropriate size.
Adding test code - edit accordingly for your case and don't judge for style, I just wanted something I could paste in LINQPad and would produce an image.
using (var font = new Font("Arial",10,FontStyle.Regular, GraphicsUnit.Pixel))
using (var image = new Bitmap(30, 15))
using (var graphics = Graphics.FromImage(image))
{
graphics.FillRectangle(Brushes.White, new Rectangle(0, 0, 30, 15));
graphics.DrawString("Ay", font, Brushes.Black, 0, 0);
image.Save(#"E:\test.bmp", ImageFormat.Bmp);
}
Remember when setting size by Pixels that all characters in the Font need to fit in that range, meaning letters with a descender and letters with an ascender.