Where does this quality loss on Images come from?

2020-08-26 10:58发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

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.