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;
Related
I am creating an Circle on a bitmap but want to have a hole in it. After serching for half an hour I only found ways to crop an image to a circle. The hard thing is, that the hole in the middle should be transparent as the rest of the Image.
This is the base image and the yellow circle represents the transparent area that should be added.
Thanks for any kind of help.
The start is simple: Create a transparent bitmap by doing a g.Clear(Color.Transparent) and then draw/fill a circle in a color.
The next step is a bit trickier: You next want to paint the hole with transparency.
To do so you need to switch the Graphics object to the right CompositingMode; default is SourceOver but you want SourceCopy. The former overlays the alpha values creating mixed colors. The latter will do what we want: Draw the hole by copying the drawn colors including alpha right over the old ones..
Here is an example:
Bitmap bmp = new Bitmap(500, 500);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Transparent);
//g.SmoothingMode = SmoothingMode.AntiAlias;
g.CompositingMode = CompositingMode.SourceCopy;
g.FillEllipse(Brushes.DarkGreen, 100, 100, 300, 300);
g.FillEllipse(Brushes.Transparent, 200, 200, 100, 100);
}
pictureBox1.Image = bmp;
This is what is looks like in a PictureBox with a BackgroundImage:
A few notes:
You can also use a semi-transparent brush to create a 'tinted' hole; do not use anti-aliasing for this though, as it would introduce colored fringes.
We used simple circles here but with a GraphicsPath you can create and fill shapes of almost any shape and complexity..
And using a GraphicsPath would also have been an alternative to filling with transparency: By first adding the large and then the smaller, inner ellipse the path would have been created with a hole and filling it would have had the very same result! But I found the solution above more instructive..
Final note: As clarkitect noted, to save, do use a format that supports transparency. Png is always recommended..
I have an image that load a .PNG file and then combines it with other graphics I create. The thing is that I need to rotate only the .PNG file, not the whole thing. Picture a speedometer, you have a background image that goes from 0 to 200. That image remains static all the time. Now, on top of it, you have an arrow pointing to your current speed. That's the one I want to rotate.
This is what I have so far. It does display the graphics, but doesn't rotate the .PNG (the arrow)
Bitmap bitmap = new Bitmap(500, 280, PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(bitmap);
g.Clear(Color.White);
//this.Arrow = path to the .PNG
Image i = Image.FromFile(this.Arrow);
Bitmap a = new Bitmap(i.Width, i.Height);
Graphics ga = Graphics.FromImage(a);
a.SetResolution(ga.DpiX, ga.DpiY);
//It shouldn't rotate having the pivot at the (centre, centre)
//coordinates, but at the bottom of the image.
//The (21, 110) coordinates are right relative to the .PNG file
ga.TranslateTransform(21, 110);
ga.RotateTransform(45); //<--- Not rotating
ga.DrawImage(i, 0, 0);
g.DrawImage(i, new Rectangle(new Point(229, 120), new Size(i.Width, i.Height)));
g.DrawLine(new Pen(new SolidBrush(Color.Aquamarine), 1), 250, 0, 250, 280);
g.DrawLine(new Pen(new SolidBrush(Color.Aquamarine), 1), 0, 230, 500, 230);
Any ideas?
Without a good, minimal, complete code example that reliably reproduces the problem, it is impossible to know for sure what the problem is. There are several possible problems in the code you posted; the most obvious being that you are drawing the image i into the Graphics object g, rather than the image a into which you've drawn i at a 45-degree rotation.
You will likely get results closer to that desired if you change that program statement to this:
g.DrawImage(a, new Rectangle(new Point(229, 120), new Size(a.Width, a.Height)));
Other issues include:
Failing to dispose ga and g when done with them
Failing to translate ga back to the desired location after the rotation (i.e. you translate to get the rotation of the image to happen around the point (21, 110) of the image, but have left the image translated by that amount after the rotation, which may not be what you want
Using positive offsets for the previously-mentioned translation
Because of these issues (and possibly others), I don't think the above change will result in exactly the desired output, but at least you will see some evidence of rotation.
While your error does not seem to be that which I've addressed in my answer to the question commenter Keith M mentioned, I do think you would benefit from reading the code example there, as it does basically the same thing you are trying to do here.
I am currently working on a PDF generator. I need to first draw the background image and then draw a dark layer with 85 percent of transparency. I can draw it nicely, but when I want to draw two images after that, those images also get the transparency and that is not what I want.
XBrush brush = new XSolidBrush(XColor.FromArgb((int)(.85 * 255), 255, 255, 255));
DrawPageBackground(gfx,backgroundImage,page.Width.Value,page.Height.Value);
gfx.DrawRectangle(b,0,0,gfx.PageSize.Width,gfx.PageSize.Height );
gfx.DrawImage(otherImage,25,25);
Is there any reason why I can't just draw the images without the transparency? Is it just something simple that I am not doing right now?
Thanks.
I did successfully fix this issue by saving the XGraphicsState before the drawing of the background with darkened layer. After the drawing I used the XGraphicsState to restore and draw the images without any transparency. See the following piece of code.
XGraphicsState state = gfx.Save();
XBrush brush = new XSolidBrush(XColor.FromArgb((int)(.85 * 255), 255, 255, 255));
DrawPageBackground(gfx,backgroundImage,page.Width.Value,page.Height.Value);
gfx.DrawRectangle(b,0,0,gfx.PageSize.Width,gfx.PageSize.Height );
gfx.DrawImage(otherImage,25,25);
gfx.Restore(state);
The method DrawPageBackground:
private static void DrawPageBackground(XGraphics gfx, XImage image, double pageWidth, double pageHeight)
{
if (image.Size.Width > pageWidth)
gfx.DrawImage(image, CalculateDiffImageCenterToPageCenter(image,pageWidth), 0, CalculateBackgroundImageWidth(image,pageHeight), pageHeight);
else
gfx.DrawImage(image, 0, 0, CalculateBackgroundImageWidth(image, pageHeight),pageHeight);
}
This is just an helper method to draw the background image to the right scale and center it.
I confirm what I wrote in a comment: it is a bug that the transparency set for a brush was also applied to the images.
With version 1.50.3915-beta2 published earlier this week, this bug was fixed.
Using Save() and Restore() is a suitable workaround for earlier versions, but with the latest version this workaround is no longer needed.
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).
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.