I'm experiencing unexpected results from Graphics.DrawImage - c#

To reproduce this issue, please create a 2x2 pixel black image in Microsoft Paint, saved as D:\small.png. Then create a new WinForms app in Visual Studio, with a no-margin PictureBox. Then use the following code:
void f6(Graphics g)
{
var img = Image.FromFile(#"d:\small3.png");
var srcRect = new Rectangle(0, 0, img.Width, img.Height);
int factor = 400;
var destRect = new Rectangle(0, 0, img.Width * factor, img.Height * factor);
g.DrawRectangle(new Pen(Color.Blue), destRect);
g.DrawImage(img, destRect, srcRect, GraphicsUnit.Pixel);
}
void pictureBox1_Paint(object sender, PaintEventArgs e)
{
f6(e.Graphics);
}
I expect the entire rectangle inside the blue margins be black while the output is as follows:
Why is this happening?
ok, thanks. i din't know about interpolation. now, let's change the code as following:
void f6(Graphics g)
{
var img = Image.FromFile(#"d:\small3.png");
var srcRect = new Rectangle(0, 0, img.Width, img.Height);
int factor = 200;
var destRect = new Rectangle(0, 0, img.Width * factor, img.Height * factor);
g.FillRectangle(new SolidBrush(Color.DarkCyan), pictureBox1.ClientRectangle);
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.DrawRectangle(new Pen(Color.Blue), destRect);
g.DrawImage(img, destRect, srcRect, GraphicsUnit.Pixel);
}
it produces the following result:
Result3
which is still unacceptable.
i've tried 60x60 images too. the problem is not because it's a 2x2 image. they produce the same effect. the problem is that why GDI+ decides not to fill the entire destRect with the entire srcRect?!
the original problem is that i've a big image tiled in smaller ones. i need adjacent tiles neither overlap nor seam exist between them. in C++, StretchBlt works properly. but it doesn't produce a smooth stretched image.

GDI+'s definition of the source rectangle is a bit odd.
(0,0) in the source image is actually the center of the upper-left pixel in the image. (width-1,height-1) is the center of the lower-right pixel in the image.
That means that the upper-left pixel is the rectangle from (-0.5,-0.5) to (0.5,0.5), and the lower-right pixel is the rectangle from (width-1.5,height-1.5) to (width-0.5,height-0.5). Thus, your source rectangle is actually outside the image by 0.5 pixels to the right and bottom.
So, you actually need a source rectangle of (-0.5, -0.5, img.Width, img.Height).
I guess you can also try setting PixelOffsetMode as Hans suggests. That would actually make sense of the behavior, but I wouldn't have expected it to apply to source rectangles.

Looks like the image was resized using bicubic interpolation. The normal Bicubic sizing algorithm normally requires 16 pixels to interpolate one, but you only have 4 in the image, so the remaining 12 pixels are never set, staying white.
Try changing the resizing method to nearest-neighbor. This is done by setting the InterpolationMode property of your Graphics object g to InterpolationMode.NearestNeighbor:
void f6(Graphics g)
{
var img = Image.FromFile(#"d:\small3.png");
var srcRect = new Rectangle(0, 0, img.Width, img.Height);
int factor = 400;
var destRect = new Rectangle(0, 0, img.Width * factor, img.Height * factor);
g.ImterpolatonMode = InterpolationMode.NearestNeighbor;
g.DrawRectangle(new Pen(Color.Blue), destRect);
g.DrawImage(img, destRect, srcRect, GraphicsUnit.Pixel);
}

Related

Precision in rotating images

I searched a code to rotate an image in C#, and found this to be almost perfect. It does the job while keeping the same Height and Width properties of my old photo and maintaining its quality.
But, could you improve the code to make it more precise. For example when I send an angle of 0.0001 It doesn't seem to rotate it! (I really need to be so precise in this step)
public static Image RotateImage(Image img, float rotationAngle)
{
Bitmap bmp = new Bitmap(img.Width, img.Height);
turn the Bitmap into a Graphics object
Graphics gfx = Graphics.FromImage(bmp);
gfx.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2);
gfx.RotateTransform(rotationAngle);
gfx.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2);
gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
gfx.DrawImage(img, new Point(0, 0));
gfx.Dispose();
return bmp;
}

size of bitmap in drawImage

I've asked question on proper scaling without rim on right and bottom. link
I've checked and size of bitmap is 32 by 32 pixels.
When i try to draw it on pictureBox with drawImage(Image, int, int) it draws it little bit bigger than size of bitmap like 36 by 36 or 40 by 40. Not sure.
But when i add width and height to drawImage it draws it 32 by 32. Is this supposed to happend.
Edit:
internal void draw(Graphics g, int x, int y)
{
int s = Game.scale;
Bitmap resized = (s > 1) ? ResizeImage(image, width*s, height*s) : image;
g.DrawImage(resized, x * width * s, y * height * s);
}
private static Bitmap ResizeImage(Image image, int width, int height)
{
var destRect = new Rectangle(0, 0, width, height);
var destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
Feature, not a bug. If you don't specify the target size then DrawImage() pays attention to the physical size of the image. The size it had when it was created, in inches. Something you can see back in the debugger, look at the bitmap's HorizontalResolution and VerticalResolution properties.
With the diagnostic that it is probably 120 dots-per-inch, programmers commonly run their video adapter at 125% today. So the drawn image becomes 1.25 * 32 = 40 pixels.
Keep in mind that this kind of rescaling tends to be important, such an image can easily turn into but a fleck of dust on an upscale 4K monitor. They are getting pretty affordable today. Whether you want this or not depends on how the rest of your UI rescales. Check out this post about dpiAwareness in a Winforms app.

how to draw a part of a png image c#

I'm trying to draw a part of a .png image but the code i found is not working. this is my code
//define canvas
canvas = pb.CreateGraphics();
sPicture = new Bitmap(pb.Width, pb.Height);
sCanvas = Graphics.FromImage(sPicture);
// Create a Bitmap object from a file.
Image image = Image.FromFile(#"");
// Clone a portion of the Bitmap object.
Rectangle cloneRect = new Rectangle(0, 0, 11, 6);
System.Drawing.Imaging.PixelFormat format =
image.PixelFormat;
Image cloneBitmap = image.Clone(cloneRect, format); //Error: No overload for method 'Clone' takes2 arguments
// Draw the cloned portion of the Bitmap object.
canvas.DrawImage(cloneBitmap, 0, 0);
This is for a sprite sheet and thanks.
You don't need to use Clone(), you can do this directly with Graphics.DrawImage(). It looks like you are trying to do this in WinForms. If, so handle OnPaint for the control you want to draw on. In the example below I'm drawing directly on the form.
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics graphics = e.Graphics;
int width = 60;
int height = 60;
// Create a Bitmap object from a file.
Image sourceImage = Image.FromFile(#"C:\Users\Mike\Downloads\logo.png");
// Draw a portion of the source image.
Rectangle sourceRect = new Rectangle(0, 0, width, height);
graphics.DrawImage(sourceImage, 0, 0, sourceRect, GraphicsUnit.Pixel);
}
If you want to do this without WinForms, there is the extra step of creating the target Graphics instance.
int width = 60;
int height = 60;
// Create a Bitmap object from a file.
Image sourceImage = Image.FromFile(#"C:\Users\Mike\Downloads\logo.png");
// Create a drawing target
Bitmap bitmap = new Bitmap(width, height, sourceImage.PixelFormat);
Graphics graphics = Graphics.FromImage(bitmap);
// Draw a portion of the source image.
Rectangle sourceRect = new Rectangle(0, 0, width, height);
graphics.DrawImage(sourceImage, 0, 0, sourceRect, GraphicsUnit.Pixel);
// Save
bitmap.Save(#"C:\Users\Mike\Downloads\out.png");

Overlay bitmap on another bitmap

On a single bitmap I need to display graphs and text values. So what I did is create a bitmap with points and creating a another bitmap with the text and place on the large bitmap.
I tried using the brush to write the text, but I am not able to see the underlying graphics even though trasparency is set.
Instead I thought to set the transparency for the text bitmap, but the bitmap which I have created are 24 bit rgb. So can we set the transparency for the 24 bit map.
Bitmap textBitmap = null;
textBitmap = new Bitmap(10, 10, PixelFormat.Format24bppRgb);
using (Graphics memoryGrahics =
Graphics.FromImage(textBitmap))
{
memoryGrahics.FillRectangle(Brushes.Black, new Rectangle(0, 0, 100, 100));
memoryGrahics.DrawString(result, f, Brushes.White, x, y);
}
//placing the text bitmap on the graphbitmap
using (Graphics g = Graphics.FromImage(GraphBitmap))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
textBitmap.MakeTransparent();
g.DrawImage(textBitmap, 0, 0);
return GraphBitmap;
}
well.. it seems like you are using 2 different Graphical objects... although 1 Graphics objects with 1 bitmap can handle multiple layouts of custom drawings, like so:
int width = 800, height = 600;
var bit = new Bitmap(width, height);
var g = Graphics.FromImage(bit);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
var area = new Rectangle(0, 0, width, height);
g.FillRectangle(new LinearGradientBrush(area, Color.PaleGoldenrod, Color.OrangeRed, 45), area);
g.DrawImage(Image.FromFile(#"your image"), new Point(10, 10));
g.DrawString("sample", new System.Drawing.Font("Tahoma", 56), new SolidBrush(Color.Black), new PointF(50, 50));
pictureBox1.Image = bit;
note that g.DrawImage method can be used to load other bitmaps as well

How do I resize an image without stretching it?

I am trying to resize an image (bitmap) in C# without stretching the image.
Say the image is 100x100 pixels.
I am looking to make it 100x110 pixels, and leave a white gap at the bottom of the image where it added the extra pixels.
I have done this, but cannot find a way to specify the pixel format. I need it to be 8bppindexed. I've attached an example to show the before and after image.
Here is the code I have so far.
string visit2 = "C:\\users\\moorez\\desktop\\visit2.bmp";
Bitmap orig = new Bitmap(visit2);
int width = orig.Width;
int height = orig.Height;
int newHeight = height + 2;
Bitmap newImage = orig.Clone(new Rectangle(0, 0, width, height), System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
newImage.Save("C:\\users\\moorez\\desktop\\visit3.bmp");
Bitmap test = new Bitmap(width, newHeight);
Graphics g = Graphics.FromImage(test);
g.DrawImage(newImage, new Point(0, 0));
test.Save("C:\\users\\moorez\\desktop\\visit4.bmp");
You can try this
Bitmap bmp = new Bitmap(newImage.Width, newHeight);
Graphics g = Graphics.FromImage(bmp);
g.Clear(Color.White);
g.DrawImageUnscaled(newImage, 0, 0, newImage.Width, newHeight);
bmp.Save(#"C:\\users\\moorez\\desktop\\visit3.bmp", ImageFormat.Jpeg);

Categories