I have a Bitmap object created by drawing several controls with the DrawToBitmap method. I would now like to print the bitmap. However, the bitmap is too large to fit on a single page and so it must be scaled down. I'm trying to do that using the following overload of DrawImage:
public void PrintPageHandler(object sender, PrintPageEventArgs e)
{
Bitmap bitmap = GetBitmap();
Rectangle destRect = new Rectangle(
e.MarginBounds.X,
e.MarginBounds.Y,
e.MarginBounds.Width,
e.MarginBounds.Width * bitmap.Height / bitmap.Width);
e.Graphics.DrawImage(
bitmap,
destRect,
0,
0,
bitmap.Width,
bitmap.Height,
System.Drawing.GraphicsUnit.Pixel);
}
Note that the destRect width and height are constructed like this because the bitmap is much wider than it is tall (i.e. width is always the limiting dimension).
My problem is that the image ends up being very blurry when it's printed. Am I scaling this incorrectly? I have a feeling there may be some issue with a GraphicsUnit mismatch between e.MarginBounds and the image dimensions. Any help would be appreciated.
[UPDATE]
I tried resizing the bitmap using the method given in the comment below, but the image still prints blurry. For testing, I saved both the original and resized bitmap to files, opened them in Windows Photo Viewer, and tried to print them from there. The resized image prints blurry like it does from within my c# application, but the original image prints beautifully; whatever algorithm Windows Photo Viewer uses to resize to a single page did not cause the image to get blurred.
I wonder, could Windows Photo Viewer be increasing the pixel density when it resizes for printing? Maybe that's why resizing it in code is causing it to get blurred; the origin pixel density is insufficient to display the scaled down image clearly.
It doesn't look like you are preserving the aspect ratio. You need to calculate the ratio of the width to height of the original image and make sure to scale the output image so that it's dimensions have the same ratio.
Basically:
1 - Calculate the aspect ratio.
2 - Find the largest dimension of the target size.
3 - Resize the output so that the largest dimensions matches, and set the smaller dimension to the larger one multiplied by the ratio.
EDIT
Check the graphics.dpiX and .DpiY proeprties to see if your printer has a different DPI going horizontally from vertically. If they are different you will have to apply some additional adjustments to the dimensions.
Related
I'm implementing image scaling function. When I use HighQualityBicubic interpolation mode (which is preferred from quality perspective) I'm getting black background on resized image(only in rectangle of source image. In padding area of destination rectangle it is still transparent).
Input image is bmp with transparent background.
Output is also bmp 32 bpp.
Interesting thing when I change InterpolationMode to NearestNeighbor with some input images background is preserved as transparent after resizing and with another inputs it doesn't help.
What I'm doing wrong?
public Bitmap DrawScaledImage(Image img, int width, int height, int scaledWidth, int scaledHeight)
{
var resultImg = new Bitmap(width, height, PixelFormat.Format32bppArgb);
resultImg.SetResolution(img.HorizontalResolution, img.VerticalResolution);
using (Graphics grPhoto = Graphics.FromImage(resultImg))
{
grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
grPhoto.DrawImage(img,
new Rectangle(0, 0, scaledWidth, scaledHeight), //0;0 - image is drawn in left top corner
new Rectangle(0, 0, img.Width, img.Height),
GraphicsUnit.Pixel);
}
return resultImg;
}
Update
Found funny thing:
I've picked random transparent pixel in source image and took its ARGB in debug: it is ARGB=(255, 0, 0, 0) - pretty clear
Then I've picked same pixel which was intended to be transparent in destination image after scaling and it is still ARGB=(255, 0, 0, 0) but in Paint.Net it is displayed as black.
Then I've picked another random pixel from destination image that is displayed as transparent in Paint.Net and its ARGB schema is ARGB=(0, 0, 0, 0) - what?
So maybe stupid question - why for 1st pic transparent pixel is [255;0;0;0] but for 2nd [255;0;0;0] means black and transparent is [0;0;0;0].
it looks like something went down-under. and Alfa-channel reversed its meaning from 255- transparent to 255-opaque after scaling. Any ideas?
So I found that reason is some buggy behavior saving file in BMP format after resizing with InterpolationMode = InterpolationMode.HighQualityBicubic. Here are cases for reproduction:
1. If I create brand new image with pixel format PixelFormat.Format32bppArgb make some program manipulations with pixels (e.g. paint some line) and save as bmp - no problem at all, transparency is saved and visible in Paint.Net.
2. If I open existing bmp image already with transparency scale it up/down with Graphics.DrawImage method all seems good in debug. Transparent pixels have Alpha = 0. Then I save that image as bmp. Then Open it again in code like Image.FromFile("resized.bmp") and its pixel format is not same as it was before saving: it is now Format32bppRgb however saved image had Format32bppArgb. On opened image transparent pixels now have Alpha = 255. This is the cause why opening in Paint.Net I see black instead of transparent after resizing.
I found 2 workarounds to preserve transparency after resizing:
1. Change InterpolationMode from InterpolationMode.HighQualityBicubic to InterpolationMode.NearestNeighbor. This solves transparency issue but not acceptable due to low quality method.
2. Save resized image as .png. This saves correct Alpha values and reopening image preserves it still. So it is my solution to refuse using BMP file format at all.
I'd have a program that will allow you to draw lines over an image which will eventually be used for calculating distance.
To make things simple, my current image (which is in a PictureBox) is an image of a ruler. When you left click, a path is created and drawn.
Originally, to zoom in, I had it so that a new bitmap would be created with the images new size and I was able to use Graphics.ScaleTransform and it worked fine but it would just crop the image.
I needed the image to actually change width and height so now what I'm doing is just adding/subtracting a constant zoom amount to the width & height when zooming in/out.
With this approach, I can't seem to scale the graphics and the paths are skewed into different directions and not the right size when the image is zoomed in.
I completely understand why this is happening, because the image is getting larger and the graphics are staying the same, I just need whatever math is required to scale the graphics.
I've tried using Graphics.ScaleTransform as well as moving the graphics x & y to their current position + the current zoom amount (offset)
As directed by #TaW I changed the zooming functionality to calculate a new Width & Height based on the whatever zoom was applied then create a new Bitmap which contained the original image with the new width and height.
I have a usercontrol with an Image property. The image is the resources.resx and has a size of 48x48.
When I draw the image on the control using:
e.Graphics.DrawImage(image, 0, 0);
//or
e.Graphics.DrawImageUnscaled(image, 0, 0);
The image is always scaled to about 1.5 times its size. The only way to keep the image the size I want it is to pass a Rectangle to the methods.
Why is the usercontrol/image behaving like this?
When you say the image is scaled I suppose you are referring to its number of pixels...
But DrawImage use the device resolution to maintain its "physical size".
See this from msdn:
The DrawImage method draws an image using its physical size, so the
image will have its correct size in inches regardless of the
resolution (dots per inch) of the display device. For example, suppose
an image has a pixel width of 216 and a horizontal resolution of 72
dots per inch. If you call DrawImage to draw that image on a device
that has a resolution of 96 dots per inch, the pixel width of the
rendered image will be (216/72)*96 = 288.
...so this is the reason of the function behavior.
The same applies to DrawImageUnscaled.
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).
I made small program to divide large pictures and take part of them.
When i import image that made by "Microsoft Paint" this image is "96 dpi" so my program doing well.
But I've pictures that made by Photoshop its resolution is 71.6 dpi when i crop these pictures the new cropped picture take 96 dpi resolution so the size is deference between them.
I want to crop the picture with keeping its resolution as it.
.
thank you very much
Bitmap.clone lets you create a cropped copy of an image, which you can then save. It shouldn't change resolution or anything (the image will look bigger if you open it in a program that zooms in more when images are smaller). It cannot be used to expand the canvas (you'll get out of memory errors). So, just grab an Image from file, cast to Bitmap, (system.drawing namespace) and clone it to be smaller, then save it.
Example:
using System.Drawing;
//...
Bitmap x = (Bitmap) Image.FromFile(#"c:\tmp\food.png");
Image x2 = x.Clone(new Rectangle(25, 25, 50, 50), x.PixelFormat);
x2.Save(#"c:\tmp\food2.png");
DPI (dots per inch) is just a relationship between pixel size and the size on a medium. If you have an image which is 1024 x 768 pixels, it is 1024 x 768. There is no inherent DPI attached to a bitmap/binary file.
If you want to print that image on a printer which prints at 300 dpi, then you can calculate the size on the paper, for example.
The SetResolution() method of the Bitmap class allows you to specify the resolution of an image.
See http://msdn.microsoft.com/en-us/library/system.drawing.bitmap.setresolution.aspx