in my Winforms application which is connected to a database via Linq to SQL I am saving images (always *.png) to a table which looks like this:
CREATE TABLE [dbo].[Images] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Bild] IMAGE NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
Before I can store a picture I have to convert it to byte[]
and this is how I do it:
public static byte[] ImageToByteArray(System.Drawing.Image imageIn)
{
using (MemoryStream ms = new MemoryStream())
{
imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
return ms.ToArray();
}
}
Afterwards if I want to load this very same image to a PictureBox in my application I convert it back with this method:
public static Image ByteArrayToImage(byte[] byteArrayIn)
{
using (MemoryStream ms = new MemoryStream(byteArrayIn))
{
Image returnImage = Image.FromStream(ms);
return returnImage;
}
}
It actually works, the only problem appears when I try to display an Image from the database in a Picturebox.
So when I load this Image to the database:
and later I try to display it. It suddenly looks like this:
I already tried all the possible SizeMode settings for the PictureBox (Normal, Stretchimage, AutoSize,CenterImage,Zoom) and it still looks like this.
Also here is how I load the Images from the database to the pictureBox:
First I retrieve the all the Images belonging to a set via id:
public static ImageList GetRezeptImages(int rezeptId)
{
using (CookBookDataContext ctx = new CookBookDataContext(ResourceFile.DBConnection))
{
IEnumerable<RezeptBilder> bilder = from b in ctx.RezeptBilders where b.FKRezept == rezeptId select b;
ImageList imageList = new ImageList();
foreach(RezeptBilder b in bilder)
{
imageList.Images.Add(Helper.ByteArrayToImage(b.Bild.ToArray()));
}
return imageList;
}
}
Also in my application I have a datagridview where Id's are stored in the first column. So when I want to retrieve any Images belonging to the set I do it like this:
private void dgvRezeptListe_CellClick(object sender, DataGridViewCellEventArgs e)
{
pbRezeptBild.Image = DBManager.GetRezeptImages(Int32.Parse(dgvRezeptListe.SelectedRows[0].Cells[0].Value.ToString())).Images[0];
}
When loaded from a local directory the Image looks fine in the pictureBox. I also tried to convert the initial picture to binary and back (without loading it to the database), it still looked fine when displayed in the pictureBox.
There is something else I realized while debugging the Image which came from the database. When looking into the ImageSize, width and height had both the value 16. This is weird because the original image has totally different dimensions.
Any ideas?
It appears that ImageList
is converting your images to MemoryBmp's when you are adding them in GetRezeptImages
to the list.
I am not aware as to why it would do that, but that is the reason for your loss of quality in the image. As you also realised, the image is converted to a 16x16 image and then when that is resized in your PictureBox
to the original size, it just looks more cheesy.
Edit:
From TaW's comment: The ImageList collection cannot handle varying sized images, so it is converting all the images to a common size. Since there is no size set, it seems it defaults to 16x16.
I would recommend that you change the GetRezeptImages
method to return a List<Image>
instead of an ImageList
and use that accordingly to show the images.
Alternatively, if you will always use the GetRezeptImages
method in the same way that you showed in your question, you can change it to always just return the first image in an Image
object and entirely throw away all the lists.
ImageList is nice for what it can do: Store lots of Images without losing GDI resources.
But what it can't do is what you probably need: Store images of varying sizes & proportions.
You can and should set it Imagesize
and ColorDepth
properties (look at the defaults, that are clearly meant for use as a StateImageList; 16x16px and 8bit depth is even bad for a LargeImageList..) but you can't use it if your images need to have different sizes & proportions..
If they don't, that is if they can share at least their proportions, just fix the ImageList
by picking a nice size and you're set! All Images you add to the ImageList will automatically be scaled to that Size
and converted to the ColorDepth
.
Otherwise replace it by a List<Image>
or List<Bitmap>
..!
If you know the common size and ColorDepth
of your Images you can do that:
using (CookBookDataContext ctx = new CookBookDataContext(ResourceFile.DBConnection))
{
IEnumerable<RezeptBilder> bilder =
from b in ctx.RezeptBilders where b.FKRezept == rezeptId select b;
ImageList imageList = new ImageList();
imageList.ColorDepth = ColorDepth.Depth24Bit; //
imageList.ImageSize = yourImageSize; //
foreach(RezeptBilder b in bilder)
{
imageList.Images.Add(Helper.ByteArrayToImage(b.Bild.ToArray()));
}
return imageList;
}
You can either not use an ImageList
but a List<Image>
or List<Bitmap>
instead or make
them - that is: Trim them to the same proportion, basically not much more than a few extra lines..:
Bitmap expandCanvas(Bitmap bmp, Size size)
{
float f1 = 1f * bmp.Width / bmp.Height;
float f2 = 1f * size.Width / size.Height;
Size newSize = size;
if (f1 > f2) newSize = new Size(bmp.Width, (int)(bmp.Height * f1));
else if (f1 < f2) newSize = new Size((int)(bmp.Width / f1), bmp.Height);
Bitmap bmp2 = new Bitmap(newSize.Width, newSize.Height);
bmp2.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
Rectangle RDest = new Rectangle(Point.Empty, bmp.Size);
Rectangle RTgt = new Rectangle(Point.Empty, newSize);
using (Graphics G = Graphics.FromImage(bmp2))
{
G.DrawImage(bmp, RDest, RDest, GraphicsUnit.Pixel);
}
return bmp2;
}
This routine expands the image canvas size with transparent pixels to the right or bottom without scaling the pixels, so it should stay losslessly crisp.
Also You might think about converting it to a string64 instead of byte[]. The code is a little cleaner and once it's a string it can be dropped in just about any file because it's just text.