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.

  • Can you post a link to an .mdb file with the three troublesome images in it (like you did before)? – Gord Thompson Nov 20 '13 at 13:03
  • @GordThompson [Here](http://wikisend.com/download/282096/Bd%20Fotos%20Equipamentos%202.mdb) it is, the one with the ID = 2 is one of those that show no issue. – Micael Florêncio Nov 20 '13 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. – Gord Thompson Nov 20 '13 at 14:27
  • @GordThompson [Here](http://wikisend.com/download/494504/Bd%20Fotos%20Equipamentos%202.mdb) This time thought I'd send the first 30 except the 1st one. – Micael Florêncio Nov 20 '13 at 14:41
  • @GordThompson [Here's](http://wikisend.com/download/432528/WindowsFormsApplication1.rar) the project if you want to try with the exact same code, only have to change the datasource and save paths. – Micael Florêncio Nov 20 '13 at 16:04

1 Answers1

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.

Gord Thompson
  • 116,920
  • 32
  • 215
  • 418
  • 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. – Micael Florêncio Nov 20 '13 at 15:50
  • And I'm still getting the External Exception in GDI+ Error on the ID = 18 one – Micael Florêncio Nov 20 '13 at 16:00
  • @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. – Gord Thompson Nov 20 '13 at 16:11
  • If you wanna try using the side project I made for this, [here](http://wikisend.com/download/432528/WindowsFormsApplication1.rar) it is. – Micael Florêncio Nov 20 '13 at 16:14
  • I tried your code to re-insert the images and with the .mdb I sent you worked perfectly too, but when I tried it on the .mdb that's full it gave the error "OleDbException unhandled". To be accurate it's size is 1,95 GB (2.103.562.240 bytes), with 692 rows. – Micael Florêncio Nov 20 '13 at 16:34
  • 1
    @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. – Gord Thompson Nov 20 '13 at 16:39
  • Thanks a lot, doing that now. However could you find out what's wrong with that project to be giving still the error on the ID=18? It could be helpful to have a piece of code successfully extracting all the images out of the .mdb in one go, or a few goes. – Micael Florêncio Nov 20 '13 at 16:51
  • And don't know if you tried running the code over the .mdb a second time after successfully running a first time, but if you run a second it gives the error *ArgumentException* on the line of **Image img = Image.FromStream(msIn);** on ID = 7. I had to because on ID=62 the OLE didn't have an image in it and gave error because of it, so the rest wasn't parsed. – Micael Florêncio Nov 20 '13 at 17:21
  • @MicaelFlorêncio Strange, I just ran my code a second time on the converted database and I didn't get an error. All it did was bloat up the database from 8.5 MB to 14.3 MB, presumably because it was extracting the raw PNG files and then writing them back into the database again. (After a Compact and Repair the file shrunk back down to 8.5 MB.) – Gord Thompson Nov 20 '13 at 17:38
  • Very. More precisely it says it's an invalid parameter. My solution was to grab the original and split it again. Either way, the first part is already done went from 600 something MBs into 132 MBs. – Micael Florêncio Nov 20 '13 at 17:59
  • @MicaelFlorêncio Did the rest of the images get converted okay? BTW, I tried to open your project but I couldn't. ("This project is incompatible with the current version of Visual Studio." I'm still using 2010.) – Gord Thompson Nov 20 '13 at 20:30
  • Almost all converted. When I grabbed the second little Database there are some that just won't convert with the ArgumentException - Invalid parameter. [Here](http://wikisend.com/download/898466/Fotos.rar) is the the Database and a printscreen of the error if you wanna give it a try. – Micael Florêncio Nov 21 '13 at 09:34
  • @MicaelFlorêncio For ID=12 the JPEG data starts at offset 0x10369E (1,062,558). The other three IDs (7, 10, and 11) are just weird. Probably easiest at this point to simply convert them manually: double-click them in Access to open them in Word, then right-click the image and choose "Save as Picture...". Once they have been saved to disk as individual PNG files, use a bit of C# code to insert them into the appropriate database records as raw binary. – Gord Thompson Nov 21 '13 at 10:26