I am doing some image scaling using GDI+ (C#), and have noticed a problem where the image I am scaling is being cut off along the left and top edges.
http://zctut.com/cutoff.png
To reproduce this, create a new form project, save this image into the bin\debug folder, and add the following code to the form (and, the corresponding events):
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
int scale = 1;
Image img = Image.FromFile("circle.png");
private void Form1_Paint(object sender, PaintEventArgs e) {
//this makes the glitch easier to see
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
RectangleF srcRect = new RectangleF(0f, 0f, img.Width, img.Height);
RectangleF destRect = new RectangleF(0f, 0f, img.Width * scale, img.Height * scale);
e.Graphics.DrawImage(img, destRect, srcRect, GraphicsUnit.Pixel);
}
private void Form1_Click(object sender, EventArgs e) {
scale++;
if (scale > 8) scale = 1;
Invalidate();
}
}
As you can see, the left- and top-most rows of pixels are being cut off, as if the scaling rectangle is starting half-way in the pixel.
Edit: For note, I also tried using a Scale transform instead of using rectangles as above, and it rendered exactly the same.
Now, that said, I did discover a work around. If you change the rectangle declarations in sample above like this:
RectangleF srcRect = new RectangleF(-0.5f, -0.5f, img.Width, img.Height);
So that we correct for the "half-way" thing, then the image renders correctly.
Basically, while this is easy to work around, am I doing something wrong, or is this normal behaviour?
Edit: As per Andrei Pana's suggestion, I tried adding this code before the drawing call:
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.None;
And, unfortunately, it did not affect the rendering. The edge was still cut off.
Try setting the PixelOffsetMode to PixelOffsetMode.Half. By default, for high speed anti aliasing, pixels are offset by -0.5
Set the size of the image to be 2 pixels larger (in each dimension) than the graphic that it contains. I have encountered this as well, and have found that the antialiasing over-shoot is never more than 1 pixel on each side.
In other words, either turn off the anti-aliasing (which will fix this), or change this section of your code:
RectangleF destRect = new RectangleF(0f, 0f, img.Width * scale, img.Height * scale);
to this:
RectangleF destRect = new RectangleF(1f, 1f, img.Width * scale -2, img.Height * scale -2);
(or use an equivalent work-around that uses srcRect)
Yes, this is normal behavior and is a known issue with GDI+/.Net.
Related
I am trying to draw a large image represented as set of tiles on a System.Drawing.Graphics surface.
All is great when I do not need to scale the Graphics. But when I do, I get empty lines between tiles.
How can I draw tiles on a Graphics without gaps between tiles?
Or shall I look somewhere else? Maybe you know non-framework classes, 3rd-party libraries etc. that I could use to overcome the issue?
Below are two sample tiles, image, showing the issue and code to reproduce the issue.
float scale = 1.66f;
int width = (int)(128 * scale);
int height = (int)(256 * scale);
using (Bitmap result = new Bitmap(width, height))
{
using (Graphics graphics = Graphics.FromImage(result))
{
graphics.ScaleTransform(scale, scale);
// tried lines below, DOES NOT always help
//graphics.SmoothingMode = SmoothingMode.HighQuality;
//graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
//graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
//graphics.CompositingQuality = CompositingQuality.HighQuality;
int top;
using (Image bitmap = Image.FromFile("topTile.png"))
{
graphics.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);
top = bitmap.Height;
}
using (Image bitmap = Image.FromFile("bottomTile.png"))
graphics.DrawImage(bitmap, 0, top, bitmap.Width, bitmap.Height);
}
result.Save("result.png");
}
I notice that you're creating bitmaps that have integral width and height by multiplying the scale factor by an initial width and height and then rounding (or likely truncating) to the nearest integer. However, you then do a scale transformation by the floating point scale amount, so the scale amount won't quite match the actual bitmap width and height. Maybe adjusting the scale amount used in the call to ScaleTransformation() would fix the issue?
I had a similar problem, I managed to solve the following lines:
graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
I am trying to draw a crosshair ("plus sign") with inverted colors over an image to show the location of a selected point within the image. This is how I do it:
private static void DrawInvertedCrosshair(Graphics g, Image img, PointF location, float length, float width)
{
float halfLength = length / 2f;
float halfWidth = width / 2f;
Rectangle absHorizRect = Rectangle.Round(new RectangleF(location.X - halfLength, location.Y - halfWidth, length, width));
Rectangle absVertRect = Rectangle.Round(new RectangleF(location.X - halfWidth, location.Y - halfLength, width, length));
ImageAttributes attributes = new ImageAttributes();
float[][] invertMatrix =
{
new float[] {-1, 0, 0, 0, 0 },
new float[] { 0, -1, 0, 0, 0 },
new float[] { 0, 0, -1, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 1, 1, 1, 0, 1 }
};
ColorMatrix matrix = new ColorMatrix(invertMatrix);
attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
g.DrawImage(img, absHorizRect, absHorizRect.X, absHorizRect.Y, absHorizRect.Width, absHorizRect.Height, GraphicsUnit.Pixel, attributes);
g.DrawImage(img, absVertRect, absVertRect.X, absVertRect.Y, absVertRect.Width, absVertRect.Height, GraphicsUnit.Pixel, attributes);
}
It works as expected, however, it is really slow. I want the user to be able to move the selected location around with their mouse by setting the location to the cursor's location whenever it moves. Unfortunately, on my computer, it can update only around once per second for big images.
So, I am looking for an alternative to using Graphics.DrawImage to invert a region of an image. Are there any ways to do this with speeds proportional to the selected region area rather than the entire image area?
Sounds to me you are focusing on the wrong problem. Painting the image is slow, not painting the "cross-hairs".
Large images can certainly be very expensive when you don't help. And System.Drawing makes it very easy to not help. Two basic things you want to do to make the image paint faster, getting it more than 20 times faster is quite achievable:
avoid forcing the image painting code to rescale the image. Instead do it just once so the image can be drawn directly one-to-one without any rescaling. Best time to do so is when you load the image. Possibly again in the control's Resize event handler.
pay attention to the pixel format of the image. The fastest one by a long shot is the pixel format that's directly compatible with the way the image needs to be stored in the video adapter. So the image data can be directly copied to video RAM without having to adjust each individual pixel. That format is PixelFormat.Format32bppPArgb on 99% of all modern machines. Makes a huge difference, it is ten times faster than all the other ones.
A simple helper method that accomplishes both without otherwise dealing with the aspect ratio:
private static Bitmap Resample(Image img, Size size) {
var bmp = new Bitmap(size.Width, size.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (var gr = Graphics.FromImage(bmp)) {
gr.DrawImage(img, new Rectangle(Point.Empty, size));
}
return bmp;
}
Draw the image once on Graphics g, then draw the crosshair on Graphics g directly instead of the image. You can optionally keep track of the places the user clicked so as to save them either in the image or elsewhere as needed.
I currently have a windows form app with a pictureBox in the middle of it which i am drawing various images too. The images are drawing fine except for the fact that they are all being scaled up by exactly 25%. I should also add that i am drawing everything inside a Paint method, using the PaintEventArgs to get the graphics device.
Ive made sure the SizeMode is set to Normal, ive checked over and over that the scale factor of the graphics object is 1 and all the image objects that i pass to the paint method are of the size they should be, but when they get drawn they are a different size.
I have until now just been calling g.drawImage(image, Rectangle) and passing the width and height of the image as the width and height of the Rectangle so that they are forced to be drawn at the correct size but i feel that this should be a short term fix and i am overlooking something simple.
Any help would be great, thanks in advance.
Code is as follows (only the important bits):
public class Level : PictureBox
{
...
private Image image;
...
public Level(TabPage parent, Panel propertiesPanel, ItemManager items, string levelName)
{
...
image = Image.FromFile(#"Levels/" + levelName);
Size = image.Size;
SizeMode = PictureBoxSizeMode.Normal;
MouseClick += new MouseEventHandler(level_MouseClick);
MouseMove += new MouseEventHandler(level_MouseMove);
Paint += new PaintEventHandler(level_Paint);
Invalidate();
}
private void level_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
//With the rectangle fix (drawing to correct size)
g.DrawImage(image, new Rectangle(0, 0, image.Size.Width, image.Size.Height));
////Without the fix (as i thought it should be be this is where it scales it)
//g.DrawImage(image, new Point(0, 0));
drawPlacedItems(g);
drawItemPreview(g);
}
This sounds like the HorizontalResolution and VerticalResolution properties of your image are being applied when you don't want them to, modify your code as per Jeremy's link to Image sizing issue in bitmap that ensures that HorizontalResolution and VerticalResolution are reset or ignored before calling DrawImage.
I'm facing a really perplexing problem..
I have a .Net 2.0 C# WinForms project.
I'm trying to stretch a bitmap onto a drawing area, but for some reason it is not stretched properly - I get alpha channel gradient on the right and bottom margins of my drawing area.
It took me quite a while to isolate this problem. I create a few lines of code that reproduce the problem (see code snippet and screenshot below).
Can anyone please shed some light over this matter?
Thanks in advance.
--
private void Form1_Paint( object sender, PaintEventArgs e )
{
// Create a black bitmap resource sized 10x10
Image resourceImg = new Bitmap( 10, 10 );
Graphics g = Graphics.FromImage( resourceImg );
g.FillRectangle( Brushes.Black, 0, 0, resourceImg.Width, resourceImg.Height );
Rectangle drawingArea = new Rectangle( 0, 0, 200, 200 ); // Set the size of the drawing area
e.Graphics.FillRectangle( Brushes.Aqua, drawingArea ); // Fill an aqua colored rectangle
e.Graphics.DrawImage( resourceImg, drawingArea ); // Stretch the resource image
// Expected result: The resource image should completely cover the aqua rectangle.
// Actual Result: The right and bottom edges become gradiently transparent (revealing the aqua rectangle under it)
}
The behavior has to do with how GDI+ handles edges. In this case, you're scaling a very small image over a large area, and you haven't told GDI+ how to handle the edge. If you use the ImageAttributes class and set the WrapMode appropriately, you can get around this issue.
For example:
private void Form1_Paint(object sender, PaintEventArgs e)
{
using (var resourceImg = new Bitmap(10, 10))
{
using (var g = Graphics.FromImage(resourceImg))
{
g.FillRectangle(Brushes.Black, 0, 0,
resourceImg.Width, resourceImg.Height);
}
var drawingArea = new Rectangle(0, 0, 200, 200);
e.Graphics.FillRectangle(Brushes.Aqua, drawingArea);
using (var attribs = new ImageAttributes())
{
attribs.SetWrapMode(WrapMode.TileFlipXY);
e.Graphics.DrawImage(resourceImg, drawingArea,
0, 0, resourceImg.Width, resourceImg.Height,
GraphicsUnit.Pixel, attribs);
}
}
}
The above code should produce an all black image. If you comment out the attribs.SetWrapMode(WrapMode.TileFlipXY); statement, you should see the blue gradient. With the wrap mode set, you're telling GDI+ to flip the image at the edges, so it will pick up more black and not fade things out at the edge when it scales the image.
I'm building an application that captures video frames from a camera (30fps # 640x480), processes them, and then displays them on a Windows Form. I was initially using DrawImage (see code below) but the performance was terrible. Even with the processing step disabled the best I can get is 20fps on a 2.8GHz Core 2 Duo machine. Double buffering is enabled on the Windows form otherwise I get tearing.
Note: The image used is a Bitmap of format Format24bppRgb. I know that DrawImage is supposed to be faster with a Format32bppArgb formatted image but I am restricted by the format that comes out of the frame grabber.
private void CameraViewForm_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
// Maximize performance
g.CompositingMode = CompositingMode.SourceOver;
g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
g.CompositingQuality = CompositingQuality.HighSpeed;
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.SmoothingMode = SmoothingMode.None;
g.DrawImage(currentFrame, displayRectangle);
}
I tried using Managed DirectX 9 with Textures and Spites (see below) but the performance was even worse. I'm very new to DirectX programming so this may not be the best DirectX code.
private void CameraViewForm_Paint(object sender, PaintEventArgs e)
{
device.Clear(ClearFlags.Target, Color.Black, 1.0f, 0);
device.BeginScene();
Texture texture = new Texture(device, currentFrame, Usage.None, Pool.Managed);
Rectangle textureSize;
using (Surface surface = texture.GetSurfaceLevel(0))
{
SurfaceDescription surfaceDescription = surface.Description;
textureSize = new Rectangle(0, 0, surfaceDescription.Width, surfaceDescription.Height);
}
Sprite sprite = new Sprite(device);
sprite.Begin(SpriteFlags.None);
sprite.Draw(texture, textureSize, new Vector3(0, 0, 0), new Vector3(0, 0, 0), Color.White);
sprite.End();
device.EndScene();
device.Present();
sprite.Dispose();
texture.Dispose();
}
I need this to work on XP, Vista and Windows 7. I don't know if it's worth trying XNA or OpenGL. This seems like it should be a very simple thing to accomplish.
The answer is right in front of you. This is a late answer to an old question, but someone might need the answer so...
Instead of declaring new textures and rectangles inside your draw loop (which is quite intense on the resources) why not create a texture outside the scope? Instead of your one, for example, try this:
Texture texture;
Rectangle textureSize;
private void InitiateTexture()
{
texture = new Texture(device, new Bitmap("CAR.jpg"), Usage.None, Pool.Managed);
using (Surface surface = texture.GetSurfaceLevel(0))
{
SurfaceDescription surfaceDescription = surface.Description;
textureSize = new Rectangle(0, 0,
surfaceDescription.Width,
surfaceDescription.Height);
}
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
device.Clear(ClearFlags.Target, Color.DarkSlateBlue, 1.0f, 0);
device.BeginScene();
Sprite sprite = new Sprite(device);
sprite.Begin(SpriteFlags.None);
sprite.Draw(texture, textureSize,
new Vector3(0, 0, 0),
new Vector3(0, 0, 0), Color.White);
sprite.End();
device.EndScene();
device.Present();
}
and then if you need to initiate multiple textures, do it into a list, then paint from that list. It saves having to use resources then free them each paint.
Is CameraViewForm your entire viewer window? If so, then on Paint, the entire window is redrawn, including all buttons, progress bars, etc. This will be more or less expensive depending on the number of controls on your form, and the visual doo-dads you have enabled for desktop elements in various OSes (e.g. window bar transparency).
Try single-buffering the entire form, but giving the Panel (from which I assume you get displayRectangle) its own BufferedGraphicsContext, by declaring a new one when you call GreateGraphics() or before calling Invalidate() on the Panel. This allows the Panel to be double-buffered separately from the Form.