Where does this quality loss on Images come from?
Asked Answered
S

3

8

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:

enter image description here

and later I try to display it. It suddenly looks like this:

enter image description here

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?

Stringent answered 28/10, 2014 at 15:22 Comment(15)
According to this source: msdn.microsoft.com/de-de/library/ms187993.aspx , the image type is deprecated and should be replaced by varbinary. Maybe this will also prevent your loss of quality.Cavalcade
I have just tested your two methods of converting the image to bytes and back again, with a normal DataSet, same table as you defined and attached stored procedures and there is no quality loss of the image. Seems like your issue might lie within LINQ to SQLMisstate
I also tried now with LINQ to SQL and varbinary(max) and also no loss of quality. Could you show the code where you load the image from the source and to the picturebox please? Seems like the issue might be not related to how you store itMisstate
Why do you need to obtain bytes from instance of Image in the first place? Are you sure that image is not manipulated in-between? How does it relate to PictureBox? Try to display the initial image in PictureBox, and see how is it displayed.Emblazon
@BerndLinde added the process of loading the image to the picturebox.Cogitation
@Cavalcade Also changed SQL type Image to varbinary(max)..did not help.Cogitation
@GeorgePolevoy already tried this the initial picture looked fine in the pictureBox. I also tried to convert the initial picture to binary and back, it still looked fine when displayed in the pictureBox.Cogitation
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.Cogitation
Add a static utility function that computes a hash of an image, and use a debugger. Then it should be easy to find at which point it gets broken.Hamartia
Then it has to do what you are actually storing in database. Where exactly does the data come from? Try to compare the bytes in your initial image on disk with the bytes in database.Emblazon
@bodycountPP: I was about to ask you exactly that. Try to find out the reason why the pic is shrinked to 16x16. Chances are your problem is solved then.Gallican
The image is converted to an Icon on this line of code imageList.Images.Add(Helper.ByteArrayToImage(b.Bild.ToArray()));, I am still trying to figure out why :)Misstate
Well, that explains it all. imageList is nice for what it can do. But what it can't do is what you probably need: Store images of varying sizes. You can and should set it Imagesize and bitDepth properties but you can't use it if your images have different sizes. If they don't just fix the ImageList and you're set! Otherwise replace it by a List<Image> or List<Bitmap>!Sweettempered
See my expanded answer, now including a routine to adapt the canvas size of your images!Sweettempered
#1101649Palatable
M
7

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.

Misstate answered 28/10, 2014 at 17:26 Comment(1)
I'd say it literally looks less cheesy.Beachhead
S
4

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.

Sweettempered answered 28/10, 2014 at 17:29 Comment(0)
Q
0

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.

Qumran answered 17/8, 2015 at 3:23 Comment(2)
Some example with explanation would be great.Shortfall
It's been discussed in this thread. #10890264 But say that you want to save your image in to a SQL database or flat-file database, it's easier to work with a string then bytes. Bytes are at, the root, just numbers and when taken directly into text they don't always have a printed equivalent. The numbers 10 and 13 are great examples of this. Neither are printed to the screen and are considered white-space.Qumran

© 2022 - 2024 — McMap. All rights reserved.