这些图像质量损失是从哪里来的?

8
在我的Winforms应用程序中,通过Linq to SQL连接到数据库后,我将图像(始终为*.png格式)保存到一个如下所示的表中:
CREATE TABLE [dbo].[Images] (
    [Id]       INT   IDENTITY (1, 1) NOT NULL,
    [Bild]     IMAGE NOT NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC)
);

在我存储图片之前,我需要将其转换为 byte[],以下是我的方法:

public static byte[] ImageToByteArray(System.Drawing.Image imageIn)
{
    using (MemoryStream ms = new MemoryStream())
    {
        imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
        return ms.ToArray();
    }
}

接下来,如果我想在我的应用程序中将这个相同的图像加载到PictureBox上,我将使用以下方法进行转换:

public static Image ByteArrayToImage(byte[] byteArrayIn)
{
    using (MemoryStream ms = new MemoryStream(byteArrayIn))
    {
        Image returnImage = Image.FromStream(ms);
        return returnImage;
    }
}

它实际上是有效的,唯一的问题出现在我尝试在Picturebox中显示来自数据库的图片时。
因此,当我将此图像加载到数据库中时: enter image description here 稍后我尝试显示它。它突然看起来像这样: enter image description here 我已经尝试了PictureBox的所有可能的SizeMode设置(Normal、Stretchimage、AutoSize、CenterImage、Zoom),但它仍然是这个样子。
此外,以下是我如何将图像从数据库加载到pictureBox中:
首先,我通过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;                
    }
}

在我的应用程序中,我有一个数据网格视图,其中ID存储在第一列。因此,当我想检索属于该集合的任何图像时,我会这样做:

private void dgvRezeptListe_CellClick(object sender, DataGridViewCellEventArgs e)
{
    pbRezeptBild.Image = DBManager.GetRezeptImages(Int32.Parse(dgvRezeptListe.SelectedRows[0].Cells[0].Value.ToString())).Images[0];      
}

当从本地目录加载图像时,在pictureBox中看起来很好。我还尝试将初始图片转换为二进制格式并再次转换回来(不将其加载到数据库中),当在pictureBox中显示时,它仍然看起来很好。
另外,我在调试来自数据库的图像时发现了一些问题。当查看ImageSize时,宽度和高度都具有值16。这很奇怪,因为原始图像具有完全不同的尺寸。
有什么想法吗?

1
根据此来源:http://msdn.microsoft.com/de-de/library/ms187993.aspx,图像类型已被弃用,应该用varbinary替换。也许这样做还可以防止您的质量损失。 - DMAN
3
我刚刚测试了你的两种图像转换成字节再转回来的方法,使用了一个正常的数据集,与你定义的相同的表格和附加的存储过程,图像没有质量损失。看起来你的问题可能在于“LINQ to SQL”。 - Bernd Linde
3
我现在也尝试了使用LINQ to SQLvarbinary(max),同样没有质量损失。请问您能展示一下从源加载图像并显示在picturebox上的代码吗?似乎问题可能与您存储图像的方式无关。 - Bernd Linde
3
这行代码imageList.Images.Add(Helper.ByteArrayToImage(b.Bild.ToArray()));将图像转换为图标,我仍在努力弄清原因 :) - Bernd Linde
2
好的,这就解释了一切。imageList很适合它所能做的事情。但是你可能需要的是它不能做的事情:存储不同大小的图像。你可以设置它的Imagesize和bitDepth属性,但如果你的图像大小不同,就无法使用它。如果它们大小相同,只需修复ImageList即可!否则,请用List<Image>或List<Bitmap>替换它! - TaW
显示剩余11条评论
3个回答

7
似乎 ImageList 在将图像添加到列表中的 GetRezeptImages 时将它们转换为 MemoryBmp。我不知道为什么会这样,但这就是图像质量下降的原因。正如你所意识到的那样,图像被转换为 16x16 的图像,然后在 PictureBox 中调整大小到原始大小时,它看起来更加低质。
编辑:从 TaW 的评论中得知:ImageList 集合无法处理大小不同的图像,因此它将所有图像都转换为相同的大小。由于没有设置大小,似乎默认为 16x16。
我建议您将 GetRezeptImages 方法更改为返回 List<Image> 而不是 ImageList,并相应地使用它来显示图像。
或者,如果您总是以与您在问题中展示的方式使用 GetRezeptImages 方法,您可以将其更改为始终仅返回 Image 对象中的第一张图像,并完全放弃所有列表。

3
我会说它看起来确实不那么俗气了。 - Kirk Woll

4

ImageList非常适合存储大量的图像,而不会丢失GDI资源,这是它可以做到的优点。

但是,你可能需要的是它无法做到的:存储大小和比例不同的图像。

你可以设置它的ImagesizeColorDepth属性(查看默认值,它们显然是用作StateImageList的;16x16px和8位深度甚至对于LargeImageList也很糟糕..),但如果你的图像需要具有不同的大小和比例,则无法使用它。

如果它们不需要,也就是说,如果它们至少可以共享它们的比例,请通过选择一个漂亮的尺寸来修复ImageList,然后就可以了!添加到ImageList中的所有图像都会自动缩放到该Size并转换为ColorDepth

否则,将其替换为List<Image>List<Bitmap>..!

如果你知道你的图像的常见大小和ColorDepth,你可以这样做:

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

您可以选择不使用ImageList,而是使用List<Image>List<Bitmap>,或者处理它们 - 即:将它们裁剪到相同的比例,基本上只需要几行额外的代码。
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;
}

这个例程会扩展图像的画布大小,向右或向下填充透明像素,而不会缩放像素,因此它应该保持无损清晰。


0

另外,您可以考虑将其转换为string64而不是byte[]。代码会更加简洁,一旦它成为字符串,它就可以被放置在几乎任何文件中,因为它只是文本。


一些带有解释的示例将很棒。 - Nilambar Sharma
这个主题已经在这个帖子中讨论过了。https://dev59.com/FGgv5IYBdhLWcg3wCsc8但是,假设您想将图像保存到SQL数据库或平面文件数据库中,使用字符串比字节更容易处理。字节本质上只是数字,在直接转换为文本时,并不总是有可打印的等价物。数字10和13就是很好的例子。它们都不会被打印到屏幕上,被认为是空格。 - Kaz Duston McGown

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接