3

I can't seem to get an answer all together for my real issue Invalid parameter when retrieving image from DB So Imma try piece by piece. Working with Visual Studio 2012 in C# and MS Access 2010. My solution is an app non-web related.

I'm not sure about this part so here my question is on how to correctly get the image of an OLE Object that is in a row from a query into a byte array (byte[]), because certainly it isn't how I'm doing it with the following code. The row I'm talking about is row["FOTO"].

                OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT * FROM [APP_Equipamento_Geral] WHERE COD_ETIQ like '%" + codigo + "%'", l);
                DataSet ds = new DataSet();
                adapter.Fill(ds, "[APP_Equipamento_Geral]");
                string s = ds.Tables["[APP_Equipamento_Geral]"].Columns[16].ColumnName;
                foreach (DataRow row in ds.Tables["[APP_Equipamento_Geral]"].Rows)
                {
                    eq.NSerie = row["N_SERIE"].ToString();
                    eq.NInventario = row["Codigo"].ToString(); 

                    if (row["FOTO"] != DBNull.Value && row["FOTO"] != null)
                    {
                        string str = row["FOTO"].ToString();
                        byte[] b = stringToByteArray(str);
                        byte[] imagebyte = GetImageBytesFromOLEField(b); //Error caught here

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

The method GetImageBytesFromOLEField can be found here. The error that it's giving me it's about the length of the index at the line string strVTemp = strTemp.Substring(0, 300);

Again, main question here is how to turn the OLE Object in the DataRow row["FOTO"] into byte[] to then use in that method.

4
  • What happens if you try Byte[] b = (Byte[])row["FOTO"];? I just checked and row["FOTO"] should already be an Object of type System.Byte[], so perhaps your data is already in the format you need (array of Bytes) and all you need is an explicit cast. Commented Oct 30, 2013 at 18:35
  • @GordThompson In that case: A: If I run that method, I get the custom Unable to determine header size for the OLE Object. B: If I don't run that method, I get the Invalid Parameter error as I mentioned in this question Commented Oct 31, 2013 at 9:46
  • I just tried Byte[] imagebyte = GetImageBytesFromOLEField((Byte[])dr["FOTO"]); and it worked fine for me. If you feel so inclined you can extract one row to a separate table in a separate database file (obfuscating sensitive data if necessary but not messing with the [FOTO] field) and upload it to wikisend.com. Then if you post the link here I can download it and see just what is in that [FOTO] column. Commented Oct 31, 2013 at 11:24
  • @GordThompson Here it is. Commented Oct 31, 2013 at 11:43

1 Answer 1

6

The problem here is that the imbedded image was not a simple BMP or JPEG. It was a

Microsoft Word Picture

and the OLE header information was considerably larger than the 300 byte window of the original GetImageBytesFromOLEField() code. (That is, after scanning 300 bytes it just gave up with "Unable to determine header size...".)

The following is an updated version of that code in its own class. Cursory testing included the supplied Microsoft Word Picture, a simple BMP, and a simple JPEG.

using System;
using System.Collections.Generic;
using System.Linq;

namespace OleImageTest
{
    public static class OleImageUnwrap
    {
        public static byte[] GetImageBytesFromOLEField(byte[] oleFieldBytes)
        {
            // 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

            const int maxNumberOfBytesToSearch = 10000;
            byte[] imageBytes;  // return value

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

            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 = startingBytes.IndexOfSequence(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;
        }
    }
}
Sign up to request clarification or add additional context in comments.

9 Comments

Thanks, the code seems to work perfectly for this specific issue. Hope it's enough to solve the other one too.
Actually it's close to that, worked on some but now there is at least one image that ends up giving the custom error Unable to determine header size for the OLE Object
@MicaelFlorêncio If you send it as before I can have a look at the bytes. (No promises as to a fix, though.)
@MicaelFlorêncio BTW, how many rows (images) are in that table? Those OLE objects are very bloated. The previous test image (OLE wrapped JPEG) is >2.5MB in the database, but when I dumped it to disk and re-saved as PNG via GIMP it shrunk to 128KB.
@MicaelFlorêncio For this one, a 5000-byte scan was not enough. The data started around 6900 bytes in. Changed MaxNumberOfBytesToSearch from 5000 to 10000 and it worked. 16.5MB JPEG file, but when I opened it in GIMP and exported it to PNG it shrunk to 3.5MB.
|

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.