【问题标题】:Where does this quality loss on Images come from?图像上的这种质量损失来自哪里?
【发布时间】:2014-12-24 01:51:39
【问题描述】:

在通过 Linq to SQL 连接到数据库的 Winforms 应用程序中,我将图像(总是 *.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 尝试了所有可能的 SizeMode 设置(Normal、Stretchimage、AutoSize、CenterImage、Zoom),它仍然看起来像这样。

这也是我如何将图像从数据库加载到图片框:

首先我通过 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;                
    }
}

另外,在我的应用程序中,我有一个 datagridview,其中 Id 存储在第一列中。因此,当我想检索属于该集合的任何图像时,我会这样做:

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

从本地目录加载时,图片在图片框中看起来很好。我还尝试将初始图片转换为二进制并返回(不将其加载到数据库中),在图片框中显示时看起来仍然很好。

在调试来自数据库的图像时,我还意识到了其他一些事情。查看 ImageSize 时,宽度和高度的值都为 16。这很奇怪,因为原始图像的尺寸完全不同。

有什么想法吗?

【问题讨论】:

  • 据此来源:msdn.microsoft.com/de-de/library/ms187993.aspx,图像类型已弃用,应替换为 varbinary。也许这也可以防止您的质量损失。
  • 我刚刚测试了您将图像转换为字节并再次转换回来的两种方法,使用正常的数据集,与您定义和附加的存储过程相同的表,并且图像没有质量损失。似乎您的问题可能在LINQ to SQL
  • 我现在也尝试了 LINQ to SQLvarbinary(max) 并且也没有质量损失。您能否显示从源代码加载图像并加载到图片框的代码?似乎问题可能与您的存储方式无关
  • 图片在这行代码imageList.Images.Add(Helper.ByteArrayToImage(b.Bild.ToArray()));上被转换成Icon,我还在想为什么:)
  • 嗯,这说明了一切。 imageList 非常适合它的功能。但它不能做的是您可能需要的:存储不同大小的图像。您可以并且应该设置它的 Imagesize 和 bitDepth 属性,但如果您的图像具有不同的大小,则不能使用它。如果他们不只是修复 ImageList 并且您已经设置好了!否则用 List 或 List! 替换它

标签: c# image winforms png picturebox


【解决方案1】:

ImageList 非常适合它可以做的事情:在不丢失 GDI 资源的情况下存储大量图像。

但它不能做的是您可能需要的:存储不同尺寸和比例的图像。

您可以并且应该设置它 ImagesizeColorDepth 属性(查看默认值,这显然是为了用作 StateImageList;16x16px 和 8 位深度对于 LargeImageList 来说甚至是糟糕的......)但是你可以'如果您的图像需要具有不同的尺寸和比例,请不要使用它..

如果他们不这样做,也就是说,如果他们至少可以分享他们的比例,只需通过选择一个合适的尺寸来修复ImageList,你就可以了!您添加到 ImageList 的所有图像都将自动缩放到 Size 并转换为 ColorDepth

否则将其替换为List&lt;Image&gt;List&lt;Bitmap&gt;..!

如果您知道图片的常见尺寸和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&lt;Image&gt;List&lt;Bitmap&gt;ma​​ke 它们——也就是:把它们修剪成相同的比例,基本上不会多出几行……:

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

此例程将带有透明像素的图像画布大小扩展到右侧或底部而不缩放像素,因此它应该保持无损清晰。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 一些带有解释的例子会很棒。
    • 已在此线程中讨论过。 stackoverflow.com/questions/10889764/… 但是假设您要将图像保存到 SQL 数据库或平面文件数据库中,使用字符串比使用字节更容易。字节位于根部,只是数字,当直接进入文本时,它们并不总是具有打印的等价物。数字 10 和 13 就是很好的例子。两者都不会打印到屏幕上并被视为空白。
    【解决方案3】:

    当您将GetRezeptImages 中的图像添加到列表时,ImageList 似乎正在将您的图像转换为 MemoryBmp。
    我不知道为什么会这样做,但这就是图像质量下降的原因。您也意识到,图像会转换为 16x16 图像,然后在您的 PictureBox 中将其大小调整为原始大小时,它看起来更俗气。

    编辑:
    来自 TaW 的评论: ImageList 集合无法处理不同大小的图像,因此它将所有图像转换为通用大小。由于没有设置大小,看来默认为 16x16。

    我建议您更改GetRezeptImages 方法以返回List&lt;Image&gt; 而不是ImageList,并相应地使用它来显示图像。

    或者,如果您将始终按照您在问题中显示的相同方式使用 GetRezeptImages 方法,您可以将其更改为始终只返回 Image 对象中的第一张图像并完全丢弃所有列表.

    【讨论】:

    • 我会说它看起来不那么俗气。
    猜你喜欢
    • 2012-10-08
    • 2012-06-22
    • 1970-01-01
    • 1970-01-01
    • 2011-01-28
    • 1970-01-01
    • 2014-03-22
    • 1970-01-01
    相关资源
    最近更新 更多