List view control displaying distorted images - c#

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");

Related

C# GDI+ ScaleTransform ok on picturebox but image saved is original

Hi I have the issue that when I use ScaleTransform(zoomFactor,zoomFactor) the image saved on disk is the original version always, while on screen in the picturebox the image is distorted in proportion to the zoomFactor.
Why this could be happening ? Shouldn't I have the final result as applied from e.Graphics on disk written image ?
My code is the following which is a version with matrix. but the instead of matrix I have used the ScaleTransform as well. Result is always the same:
g=e.Graphics;//inside picturebox_paint()
g.ScaleTransform(ratio * zoomFac, ratio * zoomFac);
e.Graphics.DrawImage((Bitmap)bmp, 0, 0);
int seed = Convert.ToInt32(Regex.Match(Guid.NewGuid().ToString(), #"\d+").Value);
String destinationFile = #"C:\tmp\photoid\" + new Random(seed).Next() + "_conv.jpg";
//Here I get always the original image back!!!!
bmp.Save(destinationFile);
I have used as well the following idiom but with same results:
//Matrix matrix = new Matrix();
//matrix.Scale(zoomFac, zoomFac);
//e.Graphics.Transform = matrix;
You need to make the PictureBox draw the things it shows on screen into a new Bitmap, which you then can save!
As it is the Image will be saved in the original form and nothing you did in the Paint event, which actually painst onto the surface of the PictureBox will be saved.
So to save everything, i.e. The Image, possibly a BackgroundImage and all you draw in the Paint event you would call DrawToBitmap somehwere.
Somewhere means somewhere else, not in the Paint event, as it will call the Paint event to create the new Bitmap, causing an endless loop..
To call it you would do something like this:
Bitmap bmpSave = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
pictureBox1.DrawToBitmap(bmpSave, pictureBox1.ClientRectangle);
But maybe this is not really what you want? Maybe you actually want to modify the Image? In that case do not use the Paint event at all!
Instead do something like this:
Bitmap bmpSave = new Bitmap(yourNewWidth, yourNewHeight);
using (Graphics g = Graphics.FromImage(bmpSave))
{
g.ScaleTransform(ratio * zoomFac, ratio * zoomFac);
g.DrawImage((Bitmap)pictureBox1.Image, 0, 0); //
pictureBox1.Image = bmpSave;
bmpSave.Save(...);
}
You could call this from somewhere where the scaling is being triggered from.
Note that doing the scaling repeatedly and each time from the previoulsy scaled version will degrade the quality rather fast. For this always scale from a saved version of the original!!
Btw: Using a Matrix for scaling doesn't really make a difference over ScaleTransform.
But if you want to do a direct scaling why not use the DrawImage overload which takes two Rectangles? This is the most common solution if all you want to to scale and maybe draw other stuff additionally..:
int newWidth = 100; int newHeight = 100; string yourFileName = "D:\\xyz123.jpg";
Bitmap bmpSave = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
Rectangle newRectangle = new Rectangle(0, 0, newWidth, newHeight);
Rectangle oldRectangle = new Rectangle(Point.Empty, pictureBox1.Image.Size);
using (Graphics g = Graphics.FromImage(bmpSave))
{
g.DrawImage((Bitmap)pictureBox1.Image, newRectangle, oldRectangle, GraphicsUnit.Pixel);
bmpSave.Save(yourFileName, ImageFormat.Jpeg);
}
And there there is the scaling Bitmap constructor:
Bitmap bmp = new Bitmap(pictureBox1.Image, newWidth, newHeight);
Which I would recommend if all you want is to scale the Image. As the other solutions it will not change the Image displayed until you assign it back into the PictureBox..:
pictureBox1.Image = bmp ;
Don't forget to dispose of the old Image..
Been a while since I messed with GDI but I think you need to copy back to the Bitmap here.
g.DrawImage(bmp, scaledwidth, scaledheight);
Try something like that before bmp.Save
Edit
Apologies for not seeing that you were copying back to the bitmap. Perhaps the overload which specifies the output rectangle is what you need. Try a DrawImage overload which has the destination Rect. https://msdn.microsoft.com/en-us/library/ms142040(v=vs.110).aspx

Bitmap Graphics vs WinForm Control Graphics

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;
}
}

What's the best way to clip a bitmap using its inscribed ellipse

I'd like to take a bitmap with an ARGB 32 pixel format and clip it so that the contents within its inscribed ellipse remain, and anything outside the ellipse turns into ARGB(0,0,0,0).
I could do it programmatically using GetPixel and SetPixel and some trigonometry to figure out which pixel is out of bounds - but I suspect there's a better, more built-in way to do it.
Any ideas?
Thanks to Alessandro D'Andria for pointing out the region part - I've figured out the rest:
public Bitmap Rasterize()
{
Bitmap ringBmp = new Bitmap(width: _size.Width, height: _size.Height, format: PixelFormat.Format32bppArgb);
//Create an appropriate region from the inscribed ellipse
Drawing2D.GraphicsPath graphicsEllipsePath = new Drawing2D.GraphicsPath();
graphicsEllipsePath.AddEllipse(0, 0, _size.Width, _size.Height);
Region ellipseRegion = new Region(graphicsEllipsePath);
//Create a graphics object from our new bitmap
Graphics gfx = Graphics.FromImage(ringBmp);
//Draw a resized version of our image to our new bitmap while using the highest quality interpolation and within the defined ellipse region
gfx.InterpolationMode = Drawing2D.InterpolationMode.NearestNeighbor;
gfx.SmoothingMode = Drawing2D.SmoothingMode.HighQuality;
gfx.PixelOffsetMode = Drawing2D.PixelOffsetMode.HighQuality;
gfx.PageUnit = GraphicsUnit.Pixel;
gfx.Clear(Color.Transparent);
gfx.Clip = ellipseRegion;
gfx.DrawImage(image: _image, rect: new Rectangle(0, 0, _size.Width, _size.Height));
//Dispose our graphics
gfx.Dispose();
//return the resultant bitmap
return ringBmp;
}
Apparently it is extremely important to set PixelOffsetMode to HighQuality, because otherwise the DrawImage method would crop parts of the resulting image.

Don't scale image when creating new bitmap from existing image

Does anyone know how to create a new bitmap from an existing image with a taller height, but don't scale the image and just have transparent, black or white below the original image in the new bitmap?
I basically have one picture that is taller than the second and I need the second one to be as tall as the first, without stretching it.
img2 = new Bitmap(lImages[2],new Size(pictureBox.Image.Width,pictureBox.Image.Height));
img2 = ((Bitmap)img2).Clone(new Rectangle(0, 0, pictureBox.Image.Width, pictureBox.Image.Height), System.Drawing.Imaging.PixelFormat.Format24bppRgb);
C# .NET 4.0.
By using a Graphics object, you can achieve this easily:
Bitmap temp = new Bitmap(new Size(pictureBox.Image.Width,pictureBox.Image.Height));
using(Graphics g = Graphics.FromImage(temp))
{
g.DrawImage(img2, 0, 0);
}
img2 = temp;
Now img2 references a new Bitmap object of the required size which has the original (unstretched) image painted on it.
Note: To control the color of the extra space, add a call to g.FillRect before drawing the image.
Create your "standart" size bitmap and fill it with, let's say, white color and call Bitmap.MakeTransparent(Color.White) and draw your final image over it.

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);
}

Categories