RenderTargetBitmap renders image of a wrong size - c#

My task is to show the user a thumbnail of each page of his XPS document. I need all the images to be small, so I render them with dpi set to 72.0 (I have googled that size of an A4 sheet with dpi 72.0 is 635x896). Basically, I do the following:
List<BitmapImage> thumbnails = new List<BitmapImage>();
documentPaginator.ComputePageCount();
int pageCount = documentPaginator.PageCount;
for (int i = 0; i < pageCount; i++)
{
DocumentPage documentPage = documentPaginator.GetPage(i);
bool isLandscape = documentPage.Size.Width > documentPage.Size.Height;
Visual pageVisual = documentPage.Visual;
//I want all the documents to be less or equals to A4
//private const double A4_SHEET_WIDTH = 635;
//private const double A4_SHEET_HEIGHT = 896;
//A4 sheet size in px, considering 72 dpi
RenderTargetBitmap targetBitmap = new RenderTargetBitmap(
(int)(System.Math.Min(documentPage.Size.Width, A4_SHEET_WIDTH)),
(int)(System.Math.Min(documentPage.Size.Height, A4_SHEET_HEIGHT)),
72.0, 72.0,
PixelFormats.Pbgra32);
targetBitmap.Render(pageVisual);
BitmapFrame frame = BitmapFrame.Create(targetBitmap);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(frame);
BitmapImage image = new BitmapImage();
using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
{
encoder.Save(ms);
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
if (isLandscape)
{
image.Rotation = Rotation.Rotate270;
}
image.EndInit();
}
thumbnails.Add(image);
}
But when I render a document page (A4) its size is actually 846x1194 instead of the one I expected. I tried to make the dpi lower (48.0) and the image's size has grown even bigger (I guess, I just do not quite undrestand what dpi is and how it affects the document). I tried to make dpi=96.0, and the size went smaller. I set one of images from the collection of instances of the class BitmapImage generated by the code above as source for the Image control (I am creating a WPF application) and in case dpi is set to 96.0 my program looks like this:
As you can see, part of the page is not shown at all, it does not fit inside the Image control, even though the size of the control is set to 635x896 that's why according to the code above, the image must be displayed correctly and all the text must fit.
What result do I expect in a nutshell: I am trying to create thumbnails of the pages of a document, but I want them to be smaller, relative to some number (I am sorry, I am not quite sure how do I say such stuff in English, basically if the document's page width is 1200 px I want it to be 1200/n, where n is the "some number" that I mentioned earlier), but if the shrinked image's size is still bigger than 635x896 I want the size to be 635x896.
Thanks in advance. And also I am sorry for my bad English.

First of all, DPI means Dots Per Inch, or pixel per inch. In the case of rendering an A4 page - which is 21 by 29.7 cm - to a bitmap at 72 DPI, you would end up with a bitmap of the following size:
Width = 21 cm / (2.54 cm/inch) * 72 pixel/inch = 595 pixel
Height = 29.7 cm / (2.54 cm/inch) * 72 pixel/inch = 842 pixel
Besides this you shouldn't care too much about DPI, with one exception: WPF rendering is done at 96 DPI. This means that an A4-sized page of your document is rendered to a 794 x 1123 bitmap. As a reminder:
Width = 21 cm / (2.54 cm/inch) * 96 pixel/inch = 794 pixel
Height = 29.7 cm / (2.54 cm/inch) * 96 pixel/inch = 1123 pixel
Hence, 794 x 1123 should be the size of your RenderTargetBitmap when it contains a page that is exactly A4. If the page size is less than A4, the bitmap should be smaller. If on the other hand the page is larger than A4, it should be scaled down to 794 x 1123. And that is the trick. Instead of rendering the page visual directly into the RenderTargetBitmap, you can put the visual into a ContainerVisual with a ScaleTransform as shown below.
for (int i = 0; i < paginator.PageCount; i++)
{
DocumentPage page = paginator.GetPage(i);
double width = page.Size.Width;
double height = page.Size.Height;
double maxWidth = Math.Round(21.0 / 2.54 * 96.0); // A4 width in pixels at 96 dpi
double maxHeight = Math.Round(29.7 / 2.54 * 96.0); // A4 height in pixels at 96 dpi
double scale = 1.0;
scale = Math.Min(scale, maxWidth / width);
scale = Math.Min(scale, maxHeight / height);
ContainerVisual containerVisual = new ContainerVisual();
containerVisual.Transform = new ScaleTransform(scale, scale);
containerVisual.Children.Add(page.Visual);
RenderTargetBitmap bitmap = new RenderTargetBitmap(
(int)(width * scale), (int)(height * scale), 96, 96, PixelFormats.Default);
bitmap.Render(containerVisual);
...
}

Related

Determine the max resolution (DPI) on a PDF page

I am using GhostScript.Net to rasterize PDF to page images before sending the page images to the printer. I am doing this so that I can always rasterize to 300dpi. This allows me to print the PDF in a reasonable amount of time regardless of the size of any image in the PDF (mainly scanned PDFs).
However, it strikes me that in some cases there will not be a need to rasterize as high as 300dpi. It may be possible to rasterize to 200dpi or even 100dpi depending on the content of the page.
Has anyone attempted to determine the maximum DPI for the content of a PDF page? Perhaps using iTextSharp?
My current code is this:
var dpiList = new List<int> {50, 100, 150, 200, 250, 300, 350, 400, 450, 500};
string inputPdfPath = #"C:\10page.pdf";
string outputPath = #"C:\Print\";
var lastInstalledVersion =
GhostscriptVersionInfo.GetLastInstalledVersion(
GhostscriptLicense.GPL | GhostscriptLicense.AFPL,
GhostscriptLicense.GPL);
var rasterizer = new GhostscriptRasterizer();
rasterizer.Open(inputPdfPath, lastInstalledVersion, true);
var imageFiles = new List<string>();
for (int pageNumber = 1; pageNumber <= 10; pageNumber++)
{
foreach (var dpi in dpiList)
{
string pageFilePath = System.IO.Path.Combine(outputPath,
string.Format("{0}-{1}-{2}.png", pageNumber, Guid.NewGuid().ToString("N").Substring(0, 8), dpi));
System.Drawing.Image img = rasterizer.GetPage(dpi, dpi, pageNumber);
img.Save(pageFilePath, ImageFormat.Png);
imageFiles.Add(pageFilePath);
Console.WriteLine(pageFilePath);
}
}
var imageCount = 0;
var pd = new PrintDocument();
pd.PrintPage += delegate(object o, PrintPageEventArgs args)
{
var i = System.Drawing.Image.FromFile(imageFiles[imageCount]);
var pageBounds = args.PageBounds;
var margin = 48;
var imageBounds = new System.Drawing.Rectangle
{
Height = pageBounds.Height - margin,
Width = pageBounds.Width - margin,
Location = new System.Drawing.Point(margin / 2, margin / 2)
};
args.Graphics.DrawImage(i, imageBounds);
imageCount++;
};
foreach (var imagefile in imageFiles)
{
pd.Print();
}
PDF pages don't have a resolution. Images within them can be considered to have a resolution, which is given by the width of the image on the page, divided by the number of image samples in the x direction, and the height of the image on the page divided by the number of image samples in the y direction.
So this leaves calculating the width and height of the image on the page. This is given by the image matrix, modified by the Current Transformation Matrix. So in order to work out the width and height on the page, you need to interpret the content stream up to the point where the image is rendered, tracking the graphics state CTM.
For general PDF files, the only way to know this is to use a PDF interpreter. In the strictly limited case where the whole page content is a single image you can gamble that there is no scaling taking place and simply divide the media width by the image width, and the media height by the image height to give the x and y resolutions.
However this definitely won't work in the general case.

InteropBitmap collection to single Image

I'm using a list of InteropBitmap in order to display a set of image frames within Image control which is customized with a datatemplate.
What I'm lookin for is to export set of images in a single image, however what I get is a bad/partial image with wrong colors.
The following is the code snippet I'm using to convert set of InteropBitmap in a single image:
var firstInterop = this.InteropBitmapList[0]; // Get info from first frame, all other one are the same format.
int width = firstInterop .PixelWidth;
int height = firstInterop.PixelHeight;
int bpp = firstInterop Format.BitsPerPixel;
int stride = width * (bpp / 8);
int count = this.InteropBitmapList.Count;
byte[] buffer = new byte[stride * height * count];
for (int i = 0; i < count; i++)
{
var wb = this.InteropBitmapList[i];
wb.CopyPixels(buffer, stride, width * height * i);
}
Finally, I use buffer array to achieve my jpeg image through GDI+ or else wpf instruments. Unfortunately, both the way doesn't work as I expected.
Is there something to wrong in my code?
##EDIT
Well, thanks to Clemens answers, now I'm able to obtain a correct image, except only for its color (all colors are altered).
The issue is true only when I try to create an Image through GDI+, instead, if I use something of WPF susch as JpegBitmapEncoder all works fine.
The following code snippet allow me to achieve the right image:
byte[] buffer = MyFunc.GetBuffer();
// ...
var bitmap = System.Windows.Media.Imaging.BitmapSource.Create(width, height, 300, 300,
System.Windows.Media.PixelFormats.Rgb24, null, buffer, stride);
System.IO.FileStream stream = new System.IO.FileStream("example.jpg", System.IO.FileMode.Create);
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 100;
encoder.Frames.Add(BitmapFrame.Create(bitmap));
encoder.Save(stream);
Instead, the following code return me an image with wrong colors (the red become blue and so on)
byte[] buffer = MyFunc.GetBuffer();
// ...
IntPtr unmanagedPointer = System.Runtime.InteropServices.Marshal.AllocHGlobal(buffer.Length);
System.Runtime.InteropServices.Marshal.Copy(buffer, 0, unmanagedPointer, buffer.Length);
System.Drawing.Imaging.PixelFormat format = System.Drawing.Imaging.PixelFormat.Format24bppRgb (the equivalent of WPF format..)
System.Drawing.Image myImg = new System.Drawing.Bitmap(Width, Height, stride, format, unmanagedPointer);
myImg.Save("example.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
I haven't idea why it doesn't work when I use System.Drawing classes.
The error is in the calculation of the buffer offset for the ith image. Instead of width * height * i it has to calculated as stride * height * i:
wb.CopyPixels(buffer, stride, stride * height * i);
In order to also support bits-per-pixel values that are not an integer multiples of 8, you should calculate the stride like this:
int stride = (width * bpp + 7) / 8;

How to crop or resize Image to set aspect ratio in asp.net

I have an image control in my webform. I set its width 100px and height 100px. but if someone upload an image of ratio 100 * 120. I want it crop or resize and set 100 * 100. I tried to set max width but not worked , I tried bitmap method with code
protected void Button1_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
{
string filename = FileUpload1.FileName;
string directory = Server.MapPath("~/");
Bitmap originalBMP = new Bitmap(FileUpload1.FileContent);
float origWidth = originalBMP.Width;
float origHeight = originalBMP.Height;
float sngRatio = origWidth / origHeight;
float newWidth = 100;
float newHeight = newWidth / sngRatio;
Bitmap newBMP = new Bitmap(originalBMP,Convert.ToInt32(newWidth),Convert.ToInt32(newHeight));
Graphics oGraphics = Graphics.FromImage(newBMP);
oGraphics.SmoothingMode=System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
oGraphics.InterpolationMode=System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
oGraphics.DrawImage(originalBMP, 0, 0, newWidth, newHeight);
newBMP.Save(directory + filename);
originalBMP = null;
newBMP = null;
oGraphics = null;
Label1.Text = "File <b style='color: red;'>" + filename + "<</b> uploaded.";
Image1.ImageUrl = #"~/" + filename;
}
else
{
Label1.Text = "No file uploaded!";
}
}
it worked but it save the resized image in directery I want to save original image in directory and display the resize image in image control.
Check out Web Image Resize handler on codeplex : http://webimageresizer.codeplex.com/
It is a custom handler to process images.
Sample urls
Taken from the codeplex project's home page
// Returns the image mapping to /bla.jpg resized to width of 100 pixels preserving aspect relative to height
/ImageHandler.ashx?src=/bla.jpg&width=100
// Returns the image mapping to /bla.jpg resized to height of 100 pixels preserving aspect relative to width
/ImageHandler.ashx?src=/bla.jpg&height=100
// Returns the image mapping to /bla.jpg resized to width 100 pixels and a height of 50 pixels
/ImageHandler.ashx?src=/bla.jpg&width=100&height=50
Another option is to use http://imageresizing.net/
The benefit is that it registers a handler to handle images transparently, meaning, that you only add querystring variables to your original image url to manipulate the image.
Sample urls
/images/bla.jpg?h=100&w=100

Getting BitmapImage PixelHeight Property causes Initialization Issues

I have a BitmapImage that I want to get the PixelHeight and PixelWidth properties so I can determine whether it has a landscape or portrait layout. After I determine its layout, I need to set either the height or width of the image to get it to fit into my image viewer window without distorting the height:width ratio. However, it appears I have to call BeginInit() in order to do anything with my image. I have to call EndInit() to get the PixelHeight or PixelWidth properties and I cannot call BeginInit() more than once on the same BitmapImage object. So how can I possibly set my image, get the height and width, determine its orientation and then resize the image?
Here the chunk of code that I have been working with:
image.BeginInit();
Uri imagePath = new Uri(path + "\\" + die.Die.ImageID + "_" + blueTape + "_BF.BMP");
image.UriSource = imagePath;
//image.EndInit();
imageHeight = image.PixelHeight;
imageWidth = image.PixelWidth;
//image.BeginInit();
// If the image is taller than it is wide, limit the height of the image
// i.e. DML87s and all non-rotated AOI devices
if (imageHeight > imageWidth)
{
image.DecodePixelHeight = 357;
}
else
{
image.DecodePixelWidth = 475;
}
image.EndInit();
When I run this, I get this run-time exception:
InvalidOperationException:
BitmapImage initialization is not complete. Call the EndInit method to
complete the initialization.
Does anybody know how to deal with this issue?
As far as I know, what you want to do is not possible without decoding the bitmap twice.
I guess it would be a lot simpler to just decode the bitmap to its native size and then set the size of the containing Image control as needed. The bitmap is scaled appropriately, as Stretch is set to Uniform (since both width and height of the Image control are set, Stretch could as well be set to Fill or UniformToFill).
var bitmap = new BitmapImage(new Uri(...));
if (bitmap.Width > bitmap.Height)
{
image.Width = 475;
image.Height = image.Width * bitmap.Height / bitmap.Width;
}
else
{
image.Height = 475;
image.Width = image.Height * bitmap.Width / bitmap.Height;
}
image.Stretch = Stretch.Uniform;
image.Source = bitmap;

Printer's paper sizes and my image

My printer could handle only B3 and A4 sizes, I am working on an application to concatenate images. These images are of different sizes. I would like to know how to neatly arrange all of them into one single image file before printing it in A4 or B3.
For concatenation, I have got the working code but still am stuck at how the images should be arranged to fit the output paper size above. Thank you for any ideas
public Image DrawLstOfItem(List<string> imgPath)
{
string path = imgPath.First();
Image img = Image.FromFile(path);
Bitmap bmp = new Bitmap(img.Width, (int)(img.Height*imgPath.Count+(5.0f*(imgPath.Count-1))));
using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawImage(img, 0, 0);
float nY = img.Height + 5.0f;
float nX = 0.0f;
for (int i = 1; i < imgPath.Count; i++)
{
Image nextImg = Image.FromFile(imgPath[i]);
g.DrawImage(nextImg, nX, nY, nextImg.Width, nextImg.Height);
nY += nextImg.Height + 0.5f;
}
}
return bmp;
}
I need to detect input image sizes, calculate the possible outcomes and arrange them when the requirement is B3 or A4 respectively. I am messed up with all different ideas and really need someone to enlighten me so I can find a likely optimal path to move on.

Categories

Resources