1

I needed to grab the images from a database, so I made some code for it. Issue is getting that error in some images and an Invalid Parameter error in others.

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

The OleImageUnwrap.GetImageBytesFromOLEField is used to remove the header from the OLE object that is the image in the Database and get only the bytes of the image itself :

    public static byte[] GetImageBytesFromOLEField(byte[] oleFieldBytes, int NumMaximoBytesSearch)
    {
        //ref http://stackoverflow.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://stackoverflow.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;
    }
}

So why are these erros even appearing? I get the first error at the 18th and 26th image at the save part and the other at the 25th when generating the image with the memory stream.

5
  • Can you post a link to an .mdb file with the three troublesome images in it (like you did before)? Commented Nov 20, 2013 at 13:03
  • @GordThompson Here it is, the one with the ID = 2 is one of those that show no issue. Commented Nov 20, 2013 at 14:06
  • I can download the file but when I try to open it I get an "Unrecognized database format" error. The file is only 443 KB, so I think it got truncated somehow. Commented Nov 20, 2013 at 14:27
  • @GordThompson Here This time thought I'd send the first 30 except the 1st one. Commented Nov 20, 2013 at 14:41
  • @GordThompson Here's the project if you want to try with the exact same code, only have to change the datasource and save paths. Commented Nov 20, 2013 at 16:04

1 Answer 1

1

The errors were caused by the order in which GetImageBytesFromOLEField() was searching for image signatures. It was searching for the BMP signature first, and unfortunately that signature is very short ('BM') so in a few cases it found that pair of bytes inside the data of the image and extracted what it thought was BMP data.

The fix was to change the order from

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

to

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

Once I did that I could process the entire file:

Size of .mdb before image conversion: 31.8 MB
Size of .mdb after conversion but before Compact and Repair: 37.1 MB
Size of .mdb after Compact and Repair: 8.5 MB

Edit

This is the code that I used to convert the images and write them back to the database:

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

The GetImageBytesFromOLEField() code is the same as I used before, with MaxNumberOfBytesToSearch = 1000000.

Sign up to request clarification or add additional context in comments.

13 Comments

If it's not asking for too much, how did you reenter everything back into a .mdb? I was thinking about making more code for that too since I'm not planing on doing it manually for over 700 rows/images.
And I'm still getting the External Exception in GDI+ Error on the ID = 18 one
@MicaelFlorêncio I have updated my answer. My code didn't have any problems with ID=18, it extracted a 143 KB JPEG and converted it into a 56 KB PNG.
If you wanna try using the side project I made for this, here it is.
@MicaelFlorêncio That file has no headroom, and writing the converted images back to the database file causes it to grow (ref: my initial answer). You'll have to move about half of the records into a separate .mdb file, do a Compact and Repair to free up the space, process the two files separately, Compact and Repair them, and then merge them back together afterward.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.