Bitmap Graphics vs WinForm Control Graphics - c#

I'm just using a .NET port of Pdfium named PdfiumViewer. It just works very well once rendered in the WinForm controls but when I try to render it on a Bitmap to show in the WPF windows (or even saving to disk) the rendered text has problem.
var pdfDoc = PdfiumViewer.PdfDocument.Load(FileName);
int width = (int)(this.ActualWidth - 30) / 2;
int height = (int)this.ActualHeight - 30;
var bitmap = new System.Drawing.Bitmap(width, height);
var g = System.Drawing.Graphics.FromImage(bitmap);
g.FillRegion(System.Drawing.Brushes.White, new System.Drawing.Region(
new System.Drawing.RectangleF(0, 0, width, height)));
pdfDoc.Render(1, g, g.DpiX, g.DpiY, new System.Drawing.Rectangle(0, 0, width, height), false);
// Neither of these are readable
image.Source = BitmapHelper.ToBitmapSource(bitmap);
bitmap.Save("test.bmp");
// Directly rendering to a System.Windows.Forms.Panel control works well
var controlGraphics = panel.CreateGraphics();
pdfDoc.Render(1, controlGraphics, controlGraphics.DpiX, controlGraphics.DpiY,
new System.Drawing.Rectangle(0, 0, width, height), false);
It's notable to say that I tested almost every possible options on the Graphics object including TextContrast,TextRenderingHint,SmoothingMode,PixelOffsetMode, ...
Which configurations I'm missing on the Bitmap object that cause this?
Edit 2
After lots of searching and as #BoeseB mentioned I just found that Pdfium render device handle and bitmaps differently by providing a second render method FPDF_RenderPageBitmap and currently I'm struggling to convert its native BGRA bitmap format to managed Bitmap.
Edit
Different modes of TextRenderingHint
Also tried Application.SetCompatibleTextRenderingDefault(false) with no noticeable difference.

Isn't it your issue ?
Look recent fix for it.
As you can see, repository owner commited newer version of PdfiumViewer. Now you can write this way:
var pdfDoc = PdfDocument.Load(#"mydoc.pdf");
var pageImage = pdfDoc.Render(pageNum, width, height, dpiX, dpiY, isForPrinting);
pageImage.Save("test.png", ImageFormat.Png);
// to display it on WPF canvas
BitmapSource source = ImageToBitmapSource(pageImage);
canvas.DrawImage(source, rect); // canvas is instance of DrawingContext
Here is a popular approach to convert Image to ImageSource
BitmapSource ImageToBitmapSource(System.Drawing.Image image)
{
using(MemoryStream memory = new MemoryStream())
{
image.Save(memory, ImageFormat.Bmp);
memory.Position = 0;
var source = new BitmapImage();
source.BeginInit();
source.StreamSource = memory;
source.CacheOption = BitmapCacheOption.OnLoad;
source.EndInit();
return source;
}
}

Related

Drawing to a Windows device context (hWnd/hDC) from WPF MediaPlayer/DrawingVisual or convert to System.Drawing.Graphics

I need to draw a video to another window where I could get the device context hDC using GetDCEx. I can already achieve drawing using System.Drawing.Graphics:
g = Graphics.FromHdc(hdc);
// Now I need to get the video frame/bitmap here
g.DrawImage(frameBitmap, 0, 0);
However I don't think System.Drawing has any class for video rendering, so I plan to use System.Windows.Media.MediaPlayer. However, the best I could do is to get it to RenderTargetBitmap. There is a way to use an encoder to render to Bitmap file and then decoding it to System.Drawing.Image, but I think it would be too slow.
So either of these can solve my problem, please tell me if any is possible:
Can a WPF DrawingVisual draw on a hDC?
Can a DrawingVisual somehow draws on a Graphics?
A quick way to get System.Drawing.Bitmap from DrawingVisual/MediaPlayer/VideoDrawing?
I am using Bitmap data copying (LockBits) and it's fast enough. Thanks to this answer.
public Bitmap Render()
{
var bitmapRender = this.CreateRenderTargetBitmap();
using (var drawingContext = this.drawingVisual.RenderOpen())
{
drawingContext.DrawVideo(this.MediaPlayer, this.drawingRect);
drawingContext.Close();
}
bitmapRender.Render(this.drawingVisual);
var result = new Bitmap(this.Width, this.Height, DrawingPixelFormat);
var bitmapData = result.LockBits(
this.renderRect,
System.Drawing.Imaging.ImageLockMode.WriteOnly,
DrawingPixelFormat);
bitmapRender.CopyPixels(
Int32Rect.Empty,
bitmapData.Scan0,
bitmapData.Stride * bitmapData.Height,
bitmapData.Stride);
result.UnlockBits(bitmapData);
return result;
}
RenderTargetBitmap CreateRenderTargetBitmap() => new RenderTargetBitmap(
this.Width, this.Height,
this.Dpi, this.Dpi,
WpfPixelFormat);

Image is not drawn at the correct spot

Bitmap image = ReadBitmap("image.png");
Bitmap imageCopy = new Bitmap(image);
Bitmap canvas = new Bitmap(imageCopy.Width+100, imageCopy.Height);
// From this bitmap, the graphics can be obtained, because it has the right PixelFormat
using(Graphics g = Graphics.FromImage(canvas))
{
// Draw the original bitmap onto the graphics of the new bitmap
g.DrawImage(image, 0, 0);
}
// Use tempBitmap as you would have used originalBmp
InputPictureBox.Image = image;
OutputPictureBox.Image = canvas;
I haven't understood the output of this c# code.
The original image is not placed at the correct position. It should have been at (0, 0).
Also, I need a black background.
So, what is going on and how to correct this?
You are loading an Image, then a copy of this source is created using:
Bitmap bitmap = new Bitmap();
When you create a copy of an Image this way, you sacrifice/alter some details:
Dpi Resolution: if not otherwise specified, the resolution is set to the UI resolution. 96 Dpi, as a standard; it might be different with different screen resolutions and scaling. The System in use also affects this value (Windows 7 and Windows 10 will probably/possibly provide different values)
PixelFormat: If not directly copied from the Image source or explicitly specified, the PixelFormat is set to PixelFormat.Format32bppArgb.
From what you were saying, you probably wanted something like this:
var imageSource = Image.FromStream(new MemoryStream(File.ReadAllBytes(#"[SomeImageOfLena]"))), true, false)
var imageCopy = new Bitmap(imageSource.Width + 100, imageSource.Height, imageSource.PixelFormat))
imageCopy.SetResolution(imageSource.HorizontalResolution, imageSource.VerticalResolution);
using (var g = Graphics.FromImage(imageCopy)) {
g.Clear(Color.Black);
g.CompositingMode = CompositingMode.SourceCopy;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(imageSource, (imageCopy.Width - imageSource.Width) / 2, 0);
pictureBox1.Image?.Dispose();
pictureBox2.Image?.Dispose();
pictureBox1.Image = imageSource;
pictureBox2.Image = imageCopy;
}
This is the result:
(The upper/lower frame black color is actually the Picturebox background color)
When the original Image Dpi Resolution is different from the base Dpi Resolution used when creating an Image copy with new Bitmap(), your results may be different from what is expected.
This is what happens with a source Image of 150, 96 and 72 Dpi in the same scenario:
Another important detail is the IDisposable nature of the Image object.
When you create one, you have to Dispose() of it; explicitly, calling the Dispose method, or implicitly, enclosing the Image contructor in a Using statement.
Also, possibly, don't assign an Image object directly loaded from a FileStream.
GDI+ will lock the file, and you will not be able to copy, move or delete it.
With the file, all resources tied to the Images will also be locked.
Make a copy with new Bitmap() (if you don't care of the above mentioned details), or with Image.Clone(), which will preserve the Image Dpi Resolution and PixelFormat.
I am not completely clear on what you are actually needing to do. But anyway, here is a WPF-friendly example of how to draw an image at a specific position inside another image.
Note if all you want to do is display the image in different size and/or put a black border around it, there are much simpler ways to do simply that, without having to create a second image, such as just laying out the image inside a panel that already has the border style you want.
Notice that I am using classes from the System.Windows.Media namespace because that is what WPF uses. These don't mix easily with the older classes from System.Drawing namespace (some of the class names conflict, and Microsoft's .Net framework lacks built-in methods for converting objects between those types), so normally one needs to simply decide whether to use one or the other sets of drawing tools. I assume you have been trying to use System.Drawing. Each has its own pros and cons that would take too long to explain here.
// using System.Windows.Media;
// using System.Windows.Media.Imaging;
private void DrawTwoImages()
{
// For InputPictureBox
var file = new Uri("C:\\image.png");
var inputImage = new BitmapImage(file);
// If your image is stored in a Resource Dictionary, instead use:
// var inputImage = (BitmapImage) Resources["image.png"];
InputPicture.Source = inputImage;
// imageCopy isn't actually needed for this example.
// But since you had it in yours, here is how it's done, anyway.
var imageCopy = inputImage.Clone();
// Parameters for setting up our output picture
int leftMargin = 50;
int topMargin = 5;
int rightMargin = 50;
int bottomMargin = 5;
int width = inputImage.PixelWidth + leftMargin + rightMargin;
int height = inputImage.PixelHeight + topMargin + bottomMargin;
var backgroundColor = Brushes.Black;
var borderColor = (Pen) null;
// Use a DrawingVisual and DrawingContext for drawing
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
// Draw the black background
dc.DrawRectangle(backgroundColor, borderColor, new Rect(0, 0, width, height));
// Copy input image onto output image at desired position
dc.DrawImage(inputImage, new Rect(leftMargin, topMargin,
inputImage.PixelWidth, inputImage.PixelHeight));
}
// For displaying output image
var rtb = new RenderTargetBitmap( width, height, 96, 96, PixelFormats.Pbgra32 );
rtb.Render(dv);
OutputPicture.Source = rtb;
}

Replicating WinRT Pixel Density changes in WPF possibly using DPI

I am using WPF to generate tile images for my WinRT app. I have a tile UserControl which is converted to PNG and this works well (It does, please don't tell me otherwise). WinRT passes me a scale property 100% (320x150), 140% (434x210) and 180% (558x270) which I use to generate the correct size of image.
For when I want to use images in my tile. I have replicated the image selection functionality of WinRT (You can supply thee scales of images and WinRT apps automatically select the correct scale) in my tile UserControl's code behind. So depending on the scale I select a bigger or smaller image source. However, on the larger scales my font size remains the same size and looks really small. I don't think I need to change the font size based on the scale as this is not what happens in WinRT, so it must be the code I'm using to convert my UserControl to a PNG and something to do with DPI. Here is my conversion Code:
// I pass in 320x150 or 434x210 or 558x270 depending on the scale.
public static MemoryStream ToPng(
this FrameworkElement frameworkElement,
double width,
double height)
{
BitmapSource bitmapSource = ToBitmapSource(frameworkElement, width, height);
PngBitmapEncoder pngBitmapEncoder = new PngBitmapEncoder();
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(bitmapSource));
MemoryStream memoryStream = new MemoryStream();
pngBitmapEncoder.Save(memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
public static BitmapSource ToBitmapSource(
this FrameworkElement frameworkElement,
double width,
double height)
{
Size renderingSize = new Size(width, height);
frameworkElement.Measure(renderingSize);
Rect renderingRectangle = new Rect(new Point(0, 0), renderingSize);
frameworkElement.Arrange(renderingRectangle);
frameworkElement.UpdateLayout();
Rect bounds = VisualTreeHelper.GetDescendantBounds(frameworkElement);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
(int)frameworkElement.ActualWidth,
(int)frameworkElement.ActualHeight,
96,
96,
PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
VisualBrush visualBrush = new VisualBrush(frameworkElement);
drawingContext.DrawRectangle(visualBrush, null, new Rect(new Point(), bounds.Size));
}
renderBitmap.Render(drawingVisual);
return renderBitmap;
}
Thanks for any help. Much appreciated.
I needed to change the above code so that I measure and arrange the frameworkElement as 310x150. I then render it to the final scale size e.g. 558x270 and set the DPI to (96/100)*scale where the scale is 180 in this case.

Repeating the image in horizontal position ( C# Drawing )

am using c#
am having a bitmap image like below
i want create a repeated image like below in horizontal position to get repeted continous image for some given width. i meant i like to draw repeated image like below from the above single bitmap (In simple words,in html we can have a image and set repeat X to get the repeated image.like that) how i can do this in c#.
so that i can draw a new bitmap in my application. How to do this.?
//x- integer value represents no. of times images to repeated horizontally
var destImage = new Bitmap(sourceImage.Width * x, sourceImage.Height, PixelFormat.Format32bppArgb);
using (TextureBrush brush = new TextureBrush(sourceImage, WrapMode.Tile))
using (Graphics g = Graphics.FromImage(destImage))
{
// Do your drawing here
g.FillRectangle(brush, 0, 0, destImage.Width, destImage.Height);
destImage.Save(#"C:\sourceImage.png", ImageFormat.Png);
//mention path of image to save, if needed
}
You can do it like this:
Bitmap myImage = new Bitmap(50, 50); //assuming you want you image to be 50,50
Bitmap originalImage = new Bitmap("myPngSource.png"); //original image to copy
using (Graphics g = Graphics.FromImage(myImage))
{
g.DrawImage(originalImage, new Rectangle(0, 0, originalImage.Width, originalImage.Height));
}
MemoryStream ms = new MemoryStream();
myImage.Save(ms, ImageFormat.Png);
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
MyImageControl.Source = bi;
Or something like that, this is untested, and I just ripped it out of a little utility app I made a while ago. I hope it helps... You just need to change the width of the final image and do a loop over the g.DrawImage call incrementing the second parameter by the width of the originalImage. (i.e. if you want 5 repeats, do a for loop 5 times)
HTH
--Mark
you don't need to create other bitmaps. it's a matter of drawing bitmap. in the place you darw the bitmap use
drawImage method few times and increment the X position of the bitmap by its width. say 16 is the width of your image. make sure that bitmap has been initialized.
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(bmp,x,y);
e.Graphics.DrawImage(bmp,x+16,y);
e.Graphics.DrawImage(bmp,x+32,y);
}

List view control displaying distorted images

I have a problem with the ListView control in a windows forms application.
Even if I create a thumbnail image or resize the real one I get distorted images in the list view.
The image looks like when you zoom in an image very much.
I first thought that the GetThumbnailImage is couseing this but I used a resize code I found here and I have the same result.
I also did not found any bug related to list view control so I gues I'm doing something wrong but I just can't figure out what.
Here is the code I use:
lsvPictures.LargeImageList = m_imagesList;
lsvPictures.LargeImageList.ImageSize = new Size(100, 100);
lsvPictures.View = View.LargeIcon;
lsvPictures.CheckBoxes = true;
for (int i = 0; i < ofd.FileNames.Length; i++)
{
filename = ofd.FileNames[i].ToString();
ListViewItem lvi = new ListViewItem(filename);
m_imagesList.Images.Add(ResizeImage(Image.FromFile(filename), 100, 100));
lvi.ImageIndex = i;
lsvPictures.Items.Add(lvi);
}
And this is the function that resizes images:
public static System.Drawing.Bitmap ResizeImage(System.Drawing.Image image,
int width, int height)
{
//a holder for the result
Bitmap result = new Bitmap(width, height);
//use a graphics object to draw the resized image into the bitmap
using (Graphics graphics = Graphics.FromImage(result))
{
//set the resize quality modes to high quality
graphics.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
graphics.DrawImage(image, 0, 0, result.Width, result.Height);
}
//return the resulting bitmap
return result;
}
Thank you!
Mosu'
I just found the source of the problems:
m_imagesList.ColorDepth = ColorDepth.Depth16Bit;
It seams that, as default, the ColorDepth of the ImageList is 8 bit (or 4 bit, but my guess is 8). If I change this to at least 16 bit everything looks very nice.
To those with similar problems: I changed my Thumbnail method a lot before I realised that the ListView control is not using the color depth the images were having. I put the result of my method on a PictureBox control and saw that the function was working corectly. Atfer this I googled a lot ... and found that silly ColorDepth property.
How did you set the resolution for your image. Also, did what did you set the PixelFormat value to when you created the bitmap? I have a list of images loading into my list view that I am resizing similar to how you are and it is working fine without any distortion in the resulting thumbnail images that are created.
Here is a snippet from my resize method.
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
bitmap.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.Red);
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.DrawImage(image,
new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight),
new Rectangle(sourceX, sourceY, originalWidth, originalHeight),
GraphicsUnit.Pixel);
}
return bitmap;
I was also using a ListView in WinForms to display directories, and had the same problem. I suggest that you check the image file type: icon files (.ico) tend to end up distorted, so try to use an image file with the .png extension. This works for me:
ListView listView = new ListView();
ImageList imageList = new ImageList();
// add image to list:
imageList.Images.Add("image_key", image_path);
// give the listview the imagelist:
listView.SmallImageList = imageList;
// add item to listview:
listView.Items.Add("item_text", "image_key");

Categories