Graphics.DrawIcon ignore scale transforms? - c#

Here's my code. It looks like DrawIcon ignores the scale transform, but not the translate transform. Is there any particular reason for this or is it just a bug?
protected override void OnPaint(PaintEventArgs e)
{
Icon icon = SystemIcons.Warning;
Image img = icon.ToBitmap();
// DrawIcon ignores this transform, but not a translate transform
e.Graphics.ScaleTransform(1.5f, 1.5f);
e.Graphics.DrawRectangle(Pens.Red, 60, 90, icon.Width, icon.Height);
e.Graphics.DrawString("Icon", this.Font, Brushes.Black, 100, 100);
e.Graphics.DrawIcon(icon, 60, 90);
e.Graphics.DrawRectangle(Pens.Red, 60, 190, img.Width, img.Height);
e.Graphics.DrawString("Bitmap", this.Font, Brushes.Black, 100, 200);
e.Graphics.DrawImage(img, 60, 190);
}

Yes, there's an explanation for it. GDI+ doesn't support drawing icons. It has no icon support at all. Instead, Graphics.DrawIcon() delegates to Icon.DrawIcon() which calls a Windows api function to draw the icon, DrawIconEx(). The code otherwise forgets to take a scaling factor into account. Probably intentional because negative and mis-matched scaling factors can't work, DrawIconEx() doesn't support that. You can use the DrawIcon(Icon, Rectangle) overload to correct this yourself.

Icon files (typically) contain multiple versions of the same image with different sizes. The .Net Icon class represents a single image from an icon file, not the whole icon file. You choose the size you want when you create the Icon class instance.
Traditionally, icon files had only two sizes (16x16 and 32x32). You'd choose the one you wanted and display it with no scaling. More recent versions of Windows support larger icons, and support scaling for displaying older icons at larger sizes.
The DrawIcon* functions seem to be designed to follow this model.
The DrawIcon(Icon, int, int) function does no scaling.
The DrawIcon(Icon, Rectangle) function does support scaling (but does not respect the scaling transform, presumably because the intention is to scale the icon to an exact pixel size).
The DrawIconUnstretched(Icon, Rectangle) function probably has a different name just because the parameters clash with one of the other DrawIcon overloads.

Related

Not getting exact result using the DrawImage function

Okay, so I have an Image which holds my tile set. Then I have my PictureBox used as my "game screen". All the code does is takes a snippet of my tile set (a tile) and place it on the game screen.
Here's my code.
private void picMap_Click(object sender, EventArgs e)
{
//screenMain = picMap.CreateGraphics();
// Create image.
//gfxTiles = Image.FromFile(#Program.resourceMapFilePath + "poatiles.png");
// Create coordinates for upper-left corner of image.
int x = 0;
int y = 0;
// Create rectangle for source image.
Rectangle srcRect = new Rectangle(16, 16, 16, 16);
GraphicsUnit units = GraphicsUnit.Pixel;
// Draw image to screen.
screenMain.DrawImage(gfxTiles, x, y, srcRect, units);
screenMain.DrawImage(gfxTiles, 16, 0, srcRect, units);
screenMain.DrawImage(gfxTiles, 32, 0, srcRect, units);
screenMain.DrawImage(gfxTiles, 16, 16, srcRect, units);
}
And here is my output:
Any reason why that space between each "tile" is there (it's a 2 pixels gap)? I could ghetto rig the code, but I plan to use algebra to programatically figure out where tiles need to go, etc etc, so a ghetto rig would work, but to do that throughout the entire game would be troublesome, and at the very least, sloppy.
I think the call to DrawImage is okay. In the image you posted it looks like 16x16 tiles next to each other. I'd check poatiles.png. I'm not sure what's at Rectangle(16, 16, 16, 16) in it. It may not be what you think.
EDIT: I don't know what to say. I made a png almost the size of poatiles and put a 16x16 square in it a 16,16, and it drew exactly like you'd expect.
The code looks fine and since it works on smaller images, the only thing I can think of is there's a problem with poatiles.
There's the following comment in MSDN about Graphics.DrawImage Method (Image, Int32, Int32, Rectangle, GraphicsUnit)
This method draws a portion of an image using its physical size, so
the image portion will have its correct size in inches regardless of
the resolution (dots per inch) of the display device. For example,
suppose an image portion has a pixel width of 216 and a horizontal
resolution of 72 dots per inch. If you call this method to draw that
image portion on a device that has a resolution of 96 dots per inch,
the pixel width of the rendered image portion will be (216/72)*96 =
288.
Since you're specifying pixels as the unit I'd expect it to ignore that. But in the absence of better ideas you might want to compare the dpi of poatiles versus the smaller images. (Image.HorizontalResolution & Image.VerticalResolution)
I'm not sure that all of the information is there to start with, but here's some suggestions I have from looking at what you've done so far.
1) Check poatiles.png to make sure that it's definitely a 16x16 pixel image with no black pixels around it.
2) It seems odd that your Rectangle has four int's in its constructor. A rectangle should usually only have a width and height (if any sides have different lengths, then it's not a true rectangle!)
3) You might want to determine your positions on screen by multiplying by width and height of the Rectangle that you're trying to draw and adding that value to the origin (0,0).

Why is Graphics.DrawImage cropping part of my image?

If you consider the following image, it's a fairly basic icon, sized at 32x32. Around the icon is a transparent rectangle, although I filled in the four corners with a solid colour while testing.
Now consider this code, which simply draws the image, but at a larger scale:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
e.Graphics.DrawImage(Properties.Resources.icon_32a, new RectangleF(0, 0, 512, 512), new RectangleF(0, 0, 32, 32), GraphicsUnit.Pixel);
}
Note that I'm drawing the full image and I'm not attempting to crop it in any way, just enlarge it.
Finally, this is the output the test gives me:
Notice the problem? Half of the pixels in the top row and left column have vanished. If I then try and overlay a grid on top of this, it looks pretty awful as the grid is correctly aligned, but the image is not. Even just doubling the size to 64, 64 introduces this first row/column crop.
Note, I also tried offset the destination rectangle just in case it was drawing before 0,0, but this was not the case.
I also tried using different interpolation modes, but as far as I could tell through the headache inducing blur, the pixels were still cropped, so I don't believe it's due to the interpolation mode.
I also attempted using different graphics modes, but aside from the fact that it didn't seem to help, I need to stick with pixels anyway.
I tried again with a new copy of the image at 96dpi out of curiosity and got the same effect so I don't think it's the resolution of the source images.
Clutching at straws and using Rectangle instead of RectangleF also had no effect.
Can anyone offer any clues as to why this apparent crop is occurring?
Thanks;
The PixelOffsetMode is set by default to PixelOffsetMode.Half:
Specifies that pixels are offset by -.5 units, both horizontally and
vertically, for high speed antialiasing.
In your case half a pixel in the original image is 8 pixels in the resulting image, which is exactly what you are missing.
Try setting it to PixelOffsetMode.None PixelOffsetMode.HighQuality:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
e.Graphics.DrawImage(Properties.Resources.icon_32a, new RectangleF(0, 0, 512, 512), new RectangleF(0, 0, 32, 32), GraphicsUnit.Pixel);
}
Just covering reply comfirmed by users, I tried it myself and the problem was solved with PixelOffsetMode.HighQuality instead none.
c#
e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
my case c++ managed:
e->graphics->PixelOffsetMode = System::Drawing::Drawing2D::PixelOffsetMode::HighQuality;

How to create a font with precise character height in Windows Forms?

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.

Curve making UI that imitates that of Powerpoint and Photoshop in C#

I've been working on a program in C# that involves image editing and animating.
I discovered that I need an interface that allows my users to draw curves.
I decided that the system that Photoshop and Powerpoint use is pretty intuitive.
I searched around, and discovered many things about this curve system, including that it is called a bezier curve, and how points define them.
However, though they all described how the points define the curve, none of them described the defining system that Powerpoint and Photoshop use, with two line segments that resemble tangent lines.
Intuitively, I think that somehow those segments are used to calculate the points that define the curve, but I am completely oblivious to how.
In short, I am trying to allow my user to draw a bezier curve in a similar manner that Photoshop and Powerpoint do, using two line segments having an endpoint at a given point which define a "tangent," the length and orientation of these segments both affecting the curve.
this.CreateGraphics().DrawBezier(Pens.Black, 0, 0, 20, 80, 80, 20, 100, 100);
this in this case is a form, and I called that in an override of the OnPaint event.
You can draw a bezier to an image as well, and then view it:
var bitmap = new Bitmap(100, 100);
Graphics.FromImage(bitmap).DrawBezier(Pens.Black, 0, 0, 20, 80, 80, 20, 100, 100);
this._pictureBox.Image = bitmap;
The result in my example is:

Any better ways to anti-alias text on .NET 2.0?

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

Categories

Resources