GDI+ 中的外部异常错误保存特定图像

Posted

技术标签:

【中文标题】GDI+ 中的外部异常错误保存特定图像【英文标题】:External Exception in GDI+ error saving specific image 【发布时间】:2013-11-20 12:08:30 【问题描述】:

我需要从数据库中获取图像,所以我为它编写了一些代码。问题是在某些图像中出现该错误,而在其他图像中出现无效参数错误。

        OleDbConnection l = new OleDbConnection(builder.ConnectionString);
        List<Image> listaImagens = new List<Image>();
        List<String> listaNomes = new List<string>();
        string nome = "";

        try
        
            OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT * FROM [Fotografias e Manuais de Equipamentos]  WHERE ID >26 AND ID < 30", l);
            DataSet ds = new DataSet();
            adapter.Fill(ds, "Fotografias e Manuais de Equipamentos");
            //string s = ds.Tables["APP_Equip_Ult_Prox_Calibracao"].Columns[16].ColumnName;
            foreach (DataRow row in ds.Tables["Fotografias e Manuais de Equipamentos"].Rows)
            
                if (row["Designação Equipamento"].ToString().Equals(""))
                
                    nome = "semNome";
                
                else
                
                    nome = row["Designação Equipamento"].ToString();
                
                listaNomes.Add(row["ID"].ToString()+"_"+row["MARCA"].ToString() + "_" + row["MODELO"].ToString() + "_" + nome);
                try
                
                    byte[] b = (byte[])row["FOTO"];
                    byte[] imagebyte = OleImageUnwrap.GetImageBytesFromOLEField(b, 30000);

                    MemoryStream ms = new MemoryStream();
                    ms.Write(imagebyte, 0, imagebyte.Length);

                    listaImagens.Add(Image.FromStream(ms));
                
                catch (Exception)
                
                    try
                    
                        byte[] b = (byte[])row["FOTO"];
                        byte[] imagebyte = OleImageUnwrap.GetImageBytesFromOLEField(b, 100000);

                        MemoryStream ms = new MemoryStream();
                        ms.Write(imagebyte, 0, imagebyte.Length);

                        listaImagens.Add(Image.FromStream(ms));
                    
                    catch (Exception)
                    
                        byte[] b = (byte[])row["FOTO"];
                        byte[] imagebyte = OleImageUnwrap.GetImageBytesFromOLEField(b, 600000);

                        MemoryStream ms = new MemoryStream();
                        ms.Write(imagebyte, 0, imagebyte.Length);
                        Image img = Image.FromStream(ms); // INVALID PARAMETER ERROR CAUGHT HERE IN DEBBUG
                        listaImagens.Add(img);
                    
                
            
            for (int i = 0; i < listaImagens.Count; i++)
        
            listaImagens[i].Save("C:\\Users\\sies4578\\Desktop\\Testes\\Fotos\\" + listaNomes[i] +".png", System.Drawing.Imaging.ImageFormat.Png); //EXTERNAL EXCEPTON IN GDI+ ERROR CAUGHT HERE IN DEBBUG
        
        
        catch (Exception ex)
        
            MessageBox.Show("Deu o berro: "+ex.Message);
                    
    

OleImageUnwrap.GetImageBytesFromOLEField 用于从作为数据库中的图像的 OLE 对象中删除标头,并仅获取图像本身的字节:

    public static byte[] GetImageBytesFromOLEField(byte[] oleFieldBytes, int NumMaximoBytesSearch)
    
        //ref http://***.com/questions/19688641/convert-ole-object-in-datarow-into-byte-c-sharp
        // adapted from http://blogs.msdn.com/b/pranab/archive/2008/07/15/removing-ole-header-from-images-stored-in-ms-access-db-as-ole-object.aspx

        int MaxNumberOfBytesToSearch = NumMaximoBytesSearch;
        byte[] imageBytes;  // return value

        var ImageSignatures = new List<byte[]>();
        // BITMAP_ID_BLOCK = "BM"
        ImageSignatures.Add(new byte[]  0x42, 0x4D );
        // JPG_ID_BLOCK = "\u00FF\u00D8\u00FF"
        ImageSignatures.Add(new byte[]  0xFF, 0xD8, 0xFF );
        // PNG_ID_BLOCK = "\u0089PNG\r\n\u001a\n"
        ImageSignatures.Add(new byte[]  0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A );
        // GIF_ID_BLOCK = "GIF8"
        ImageSignatures.Add(new byte[]  0x47, 0x49, 0x46, 0x38 );
        // TIFF_ID_BLOCK = "II*\u0000"
        ImageSignatures.Add(new byte[]  0x49, 0x49, 0x2A, 0x00 );

        int NumberOfBytesToSearch = (oleFieldBytes.Count() < MaxNumberOfBytesToSearch ? oleFieldBytes.Count() : MaxNumberOfBytesToSearch);
        var startingBytes = new byte[NumberOfBytesToSearch];
        Array.Copy(oleFieldBytes, startingBytes, NumberOfBytesToSearch);

        var positions = new List<int>();
        foreach (byte[] BlockSignature in ImageSignatures)
        
            positions = IndexOfSequence(startingBytes, BlockSignature, 0);
            if (positions.Count > 0)
            
                break;
            
        
        int iPos = -1;
        if (positions.Count > 0)
        
            iPos = positions[0];
        

        if (iPos == -1)
            throw new Exception("Unable to determine header size for the OLE Object");

        imageBytes = new byte[oleFieldBytes.LongLength - iPos];
        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        ms.Write(oleFieldBytes, iPos, oleFieldBytes.Length - iPos);
        imageBytes = ms.ToArray();
        ms.Close();
        ms.Dispose();
        return imageBytes;
    

    private static List<int> IndexOfSequence(this byte[] buffer, byte[] pattern, int startIndex)
    
        // ref: http://***.com/a/332667/2144390
        List<int> positions = new List<int>();
        int i = Array.IndexOf<byte>(buffer, pattern[0], startIndex);
        while (i >= 0 && i <= buffer.Length - pattern.Length)
        
            byte[] segment = new byte[pattern.Length];
            Buffer.BlockCopy(buffer, i, segment, 0, pattern.Length);
            if (segment.SequenceEqual<byte>(pattern))
                positions.Add(i);
            i = Array.IndexOf<byte>(buffer, pattern[0], i + 1);
        
        return positions;
    

那么为什么会出现这些错误呢?使用内存流生成图像时,我在保存部分的第 18 和第 26 图像处得到第一个错误,在第 25 个图像处得到另一个错误。

【问题讨论】:

您能否发布一个指向 .mdb 文件的链接,其中包含三个麻烦的图像(就像您之前所做的那样)? @GordThompson Here 是,ID = 2 的那个是没有问题的那个。 我可以下载该文件,但是当我尝试打开它时,我收到“无法识别的数据库格式”错误。该文件只有 443 KB,所以我认为它被截断了。 @GordThompson Here 这次我想我会发送除第一个之外的前 30 个。 @GordThompson Here's 该项目如果您想尝试使用完全相同的代码,只需更改数据源并保存路径。 【参考方案1】:

错误是由GetImageBytesFromOLEField() 搜索图像签名的顺序引起的。它首先搜索 BMP 签名,不幸的是,该签名非常短(“BM”),因此在某些情况下,它发现图像数据中的一对字节inside 并提取了它的想法是 BMP 数据。

解决方法是更改​​顺序

var ImageSignatures = new List<byte[]>();
// BITMAP_ID_BLOCK = "BM"
ImageSignatures.Add(new byte[]  0x42, 0x4D );
// JPG_ID_BLOCK = "\u00FF\u00D8\u00FF"
ImageSignatures.Add(new byte[]  0xFF, 0xD8, 0xFF );
// PNG_ID_BLOCK = "\u0089PNG\r\n\u001a\n"
ImageSignatures.Add(new byte[]  0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A );
// GIF_ID_BLOCK = "GIF8"
ImageSignatures.Add(new byte[]  0x47, 0x49, 0x46, 0x38 );
// TIFF_ID_BLOCK = "II*\u0000"
ImageSignatures.Add(new byte[]  0x49, 0x49, 0x2A, 0x00 );

var ImageSignatures = new List<byte[]>();
// JPG_ID_BLOCK = "\u00FF\u00D8\u00FF"
ImageSignatures.Add(new byte[]  0xFF, 0xD8, 0xFF );
// PNG_ID_BLOCK = "\u0089PNG\r\n\u001a\n"
ImageSignatures.Add(new byte[]  0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A );
// GIF_ID_BLOCK = "GIF8"
ImageSignatures.Add(new byte[]  0x47, 0x49, 0x46, 0x38 );
// TIFF_ID_BLOCK = "II*\u0000"
ImageSignatures.Add(new byte[]  0x49, 0x49, 0x2A, 0x00 );
// BITMAP_ID_BLOCK = "BM"
ImageSignatures.Add(new byte[]  0x42, 0x4D );

一旦我这样做了,我就可以处理整个文件:

图像转换前 .mdb 的大小:31.8 MB 转换后但压缩和修复之前的 .mdb 大小:37.1 MB 压缩和修复后的 .mdb 大小:8.5 MB

编辑

这是我用来转换图像并将它们写回数据库的代码:

private void btnStart_Click(object sender, EventArgs e)

    using (var con = new OleDbConnection())
    
        con.ConnectionString =
                @"Provider=Microsoft.ACE.OLEDB.12.0;" +
                @"Data Source=C:\__tmp\test\Bd Fotos Equipamentos 2.mdb;";
        con.Open();
        using (OleDbCommand cmdIn = new OleDbCommand(), cmdOut = new OleDbCommand())
        
            cmdOut.Connection = con;
            cmdOut.CommandText = "UPDATE [Fotografias e Manuais de Equipamentos] SET [FOTO]=? WHERE [ID]=?";
            cmdOut.Parameters.Add("?", OleDbType.VarBinary);
            cmdOut.Parameters.Add("?", OleDbType.Integer);

            cmdIn.Connection = con;
            cmdIn.CommandText = "SELECT [ID], [FOTO] FROM [Fotografias e Manuais de Equipamentos]";
            OleDbDataReader rdr = cmdIn.ExecuteReader();
            while (rdr.Read())
            
                int i = Convert.ToInt32(rdr["ID"]);
                lblStatus.Text = string.Format("Processing ID 0...", i);
                lblStatus.Refresh();
                byte[] b = (byte[])rdr["FOTO"];
                byte[] imageBytes = OleImageUnwrap.GetImageBytesFromOLEField(b);
                byte[] pngBytes;
                using (MemoryStream msIn = new MemoryStream(imageBytes), msOut = new MemoryStream())
                
                    Image img = Image.FromStream(msIn);
                    img.Save(msOut, System.Drawing.Imaging.ImageFormat.Png);
                    img.Dispose();
                    pngBytes = msOut.ToArray();
                
                cmdOut.Parameters[0].Value = pngBytes;
                cmdOut.Parameters[1].Value = rdr["ID"];
                cmdOut.ExecuteNonQuery();
            
        
        con.Close();
    
    this.Close();

GetImageBytesFromOLEField() 代码与我之前使用的代码相同,为 MaxNumberOfBytesToSearch = 1000000

【讨论】:

如果不是要求太多,您是如何将所有内容重新输入到 .mdb 中的?我也在考虑为此编写更多代码,因为我不打算为超过 700 行/图像手动编写代码。 而且我仍然在 ID = 18 one 上的 GDI+ 错误中遇到外部异常 @MicaelFlorêncio 我已经更新了我的答案。我的代码 ID=18 没有任何问题,它提取了 143 KB JPEG 并将其转换为 56 KB PNG。 如果你想尝试使用我为此制作的副项目,here 是。 @MicaelFlorêncio 该文件没有空间,将转换后的图像写回数据库文件会导致它增长(参考:我的初始答案)。您必须将大约一半的记录移动到单独的 .mdb 文件中,执行压缩和修复以释放空间,分别处理这两个文件,压缩并修复它们,然后将它们合并在一起。跨度>

以上是关于GDI+ 中的外部异常错误保存特定图像的主要内容,如果未能解决你的问题,请参考以下文章

c#中利用system.timers多线程做图像处理,图像保存时提示“GDI+ 中发生一般性错误”,如何解决?

c# GDI+ 中的外部异常

GDI+ 中发生一般性错误。 究竟是啥问题不是一般性的图片上传出错。

当字节数组的源是 jpg 时,从存储的字节数组创建位图并保存到磁盘会引发 GDI+ 异常

C# 如何从存储在数据库中的 OLE 对象中保存图像

使用带有内存流的 c# 在控制台应用程序中保存图像时,GDI + 中发生一般错误