This project has moved. For the latest updates, please go here.

Getting thumbnail of a Docx Document

Jul 3, 2009 at 9:28 AM

Hi

 

I have been wondering how we can get hold of the thumbnail image of Docx document as it is shown in Vista?

I know how to do it for PPTX (using the Thumbnail Part) but for the Word the thumbnail part is null.

 

So how can we do this?

 

Thanks

Akshaya

Coordinator
Jul 3, 2009 at 9:48 AM

Hi Akshaya,

I do not understand your question. You mentioned the "Thumbnail Part" in your post above. Are you talking about a component in the OOXML SDK? DocX as of version 1.0.0.5 no longer uses the OOXML SDK.

Perhapse I can still help, can you explain in greater detail what you are trying to do?

kind regards,
Cathal

Jul 3, 2009 at 10:04 AM

Hi Cathal

Yes, thumbnail part is in OOXML SDK, I didnt really that DocX doesnt use it yet, apologies for that.

Here's what I am trying to do:

1. Input - Path to a Docx document on local server

2. I want that some how I get a GIF/JPG/BMP thumbnail image of the document

3. Display it to the user.

 

The first and third points are not a problem at all...but its the second point where I am stuck!

 

Thanks

Akshaya

Coordinator
Jul 3, 2009 at 11:12 AM

 

Hi akshayakrsh,

I have spent all morning working on this because I found it to be an interesting problem. Please find pasted below ShellThumbnail.cs. To the best of my knowledge, this class contains the actual code that the OOXML SDK uses to get thumbnails of documents. Unfortunately this process does not support DocX yet :-(

If you build the attached ShellThumbnail.cs and you can then use it like so
ShellThumbnail shellThumbnail = new ShellThumbnail();
Bitmap bmp = shellThumbnail.GetThumbnail(@"C:\Users\user\Desktop\Test.pptx");
bmp.Save(@"Test.bmp");
You will get a thumbnail for the PowerPoint document Test.pptx. However if you try and use it with a DocX document it throws an exception.
ShellThumbnail shellThumbnail = new ShellThumbnail();
Bitmap bmp = shellThumbnail.GetThumbnail(@"C:\Users\user\Desktop\Test.docx");
bmp.Save(@"Test.bmp");
I will take another look at this when I finish work today. I have not given up on it yet.
kind regards,
Cathal
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

public class ShellThumbnail : IDisposable
{

  [Flags]
    private enum ESTRRET
  {
    STRRET_WSTR = 0,
    STRRET_OFFSET = 1,
    STRRET_CSTR = 2
  }

  [Flags]
    private enum ESHCONTF
  {
    SHCONTF_FOLDERS = 32,
    SHCONTF_NONFOLDERS = 64,
    SHCONTF_INCLUDEHIDDEN = 128,
  }

  [Flags]
    private enum ESHGDN
  {
    SHGDN_NORMAL = 0,
    SHGDN_INFOLDER = 1,
    SHGDN_FORADDRESSBAR = 16384,
    SHGDN_FORPARSING = 32768
  }

  [Flags]
    private enum ESFGAO
  {
    SFGAO_CANCOPY = 1,
    SFGAO_CANMOVE = 2,
    SFGAO_CANLINK = 4,
    SFGAO_CANRENAME = 16,
    SFGAO_CANDELETE = 32,
    SFGAO_HASPROPSHEET = 64,
    SFGAO_DROPTARGET = 256,
    SFGAO_CAPABILITYMASK = 375,
    SFGAO_LINK = 65536,
    SFGAO_SHARE = 131072,
    SFGAO_READONLY = 262144,
    SFGAO_GHOSTED = 524288,
    SFGAO_DISPLAYATTRMASK = 983040,
    SFGAO_FILESYSANCESTOR = 268435456,
    SFGAO_FOLDER = 536870912,
    SFGAO_FILESYSTEM = 1073741824,
    SFGAO_HASSUBFOLDER = -2147483648,
    SFGAO_CONTENTSMASK = -2147483648,
    SFGAO_VALIDATE = 16777216,
    SFGAO_REMOVABLE = 33554432,
    SFGAO_COMPRESSED = 67108864,
  }

  private enum EIEIFLAG
  {
    IEIFLAG_ASYNC = 1,
    IEIFLAG_CACHE = 2,
    IEIFLAG_ASPECT = 4,
    IEIFLAG_OFFLINE = 8,
    IEIFLAG_GLEAM = 16,
    IEIFLAG_SCREEN = 32,
    IEIFLAG_ORIGSIZE = 64,
    IEIFLAG_NOSTAMP = 128,
    IEIFLAG_NOBORDER = 256,
    IEIFLAG_QUALITY = 512
  }

  [StructLayout(LayoutKind.Sequential, Pack=4, Size=0, CharSet=CharSet.Auto)]
    private struct STRRET_CSTR
  {
    public ESTRRET uType;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=520)]
    public byte[] cStr;
  }

  [StructLayout(LayoutKind.Explicit, CharSet=CharSet.Auto)]
  private struct STRRET_ANY
  {
    [FieldOffset(0)]
    public ESTRRET uType;
    [FieldOffset(4)]
    public IntPtr pOLEString;
  }
  [StructLayoutAttribute(LayoutKind.Sequential)]
  private struct SIZE
  {
    public int cx;
    public int cy;
  }
 
  [ComImport(), Guid("00000000-0000-0000-C000-000000000046")]
  [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  private interface IUnknown
  {

    [PreserveSig()]
    IntPtr QueryInterface(ref Guid riid, ref IntPtr pVoid);

    [PreserveSig()]
    IntPtr AddRef();

    [PreserveSig()]
    IntPtr Release();
  }
 
  [ComImportAttribute()]
  [GuidAttribute("00000002-0000-0000-C000-000000000046")]
  [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
  private interface IMalloc
  {

    [PreserveSig()]
    IntPtr Alloc(int cb);

    [PreserveSig()]
    IntPtr Realloc(IntPtr pv, int cb);

    [PreserveSig()]
    void Free(IntPtr pv);

    [PreserveSig()]
    int GetSize(IntPtr pv);

    [PreserveSig()]
    int DidAlloc(IntPtr pv);

    [PreserveSig()]
    void HeapMinimize();
  }
 
  [ComImportAttribute()]
  [GuidAttribute("000214F2-0000-0000-C000-000000000046")]
  [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
  private interface IEnumIDList
  {

    [PreserveSig()]
    int Next(int celt, ref IntPtr rgelt, ref int pceltFetched);

    void Skip(int celt);

    void Reset();

    void Clone(ref IEnumIDList ppenum);
  }
 
  [ComImportAttribute()]
  [GuidAttribute("000214E6-0000-0000-C000-000000000046")]
  [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
  private interface IShellFolder
  {

    void ParseDisplayName(IntPtr hwndOwner, IntPtr pbcReserved,    
      [MarshalAs(UnmanagedType.LPWStr)]string lpszDisplayName,
      ref int pchEaten, ref IntPtr ppidl, ref int pdwAttributes);

    void EnumObjects(IntPtr hwndOwner,    
      [MarshalAs(UnmanagedType.U4)]ESHCONTF grfFlags,
      ref IEnumIDList ppenumIDList);

    void BindToObject(IntPtr pidl, IntPtr pbcReserved, ref Guid riid,
      ref IShellFolder ppvOut);

    void BindToStorage(IntPtr pidl, IntPtr pbcReserved, ref Guid riid, IntPtr ppvObj);

    [PreserveSig()]
    int CompareIDs(IntPtr lParam, IntPtr pidl1, IntPtr pidl2);

    void CreateViewObject(IntPtr hwndOwner, ref Guid riid,
      IntPtr ppvOut);

    void GetAttributesOf(int cidl, IntPtr apidl,    
      [MarshalAs(UnmanagedType.U4)]ref ESFGAO rgfInOut);

    void GetUIObjectOf(IntPtr hwndOwner, int cidl, ref IntPtr apidl, ref Guid riid, ref int prgfInOut, ref IUnknown ppvOut);

    void GetDisplayNameOf(IntPtr pidl,    
      [MarshalAs(UnmanagedType.U4)]ESHGDN uFlags,
      ref STRRET_CSTR lpName);

    void SetNameOf(IntPtr hwndOwner, IntPtr pidl,    
      [MarshalAs(UnmanagedType.LPWStr)]string lpszName,    
      [MarshalAs(UnmanagedType.U4)] ESHCONTF uFlags,
      ref IntPtr ppidlOut);
  }
  [ComImportAttribute(), GuidAttribute("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
  private interface IExtractImage
  {
    void GetLocation( [Out(), MarshalAs(UnmanagedType.LPWStr)]
      StringBuilder pszPathBuffer, int cch, ref int pdwPriority, ref SIZE prgSize, int dwRecClrDepth, ref int pdwFlags);

    void Extract(ref IntPtr phBmpThumbnail);
  }

  private class UnmanagedMethods
  {

    [DllImport("shell32", CharSet=CharSet.Auto)]
    internal extern static int SHGetMalloc(ref IMalloc ppMalloc);

    [DllImport("shell32", CharSet=CharSet.Auto)]
    internal extern static int SHGetDesktopFolder(ref IShellFolder ppshf);

    [DllImport("shell32", CharSet=CharSet.Auto)]
    internal extern static int SHGetPathFromIDList(IntPtr pidl, StringBuilder pszPath);

    [DllImport("gdi32", CharSet=CharSet.Auto)]
    internal extern static int DeleteObject(IntPtr hObject);

  }

  ~ShellThumbnail()
  {
    Dispose();
  }

  private IMalloc alloc = null;
  private bool disposed = false;
  private Size _desiredSize = new Size(100, 100);
  private Bitmap _thumbNail;

  public Bitmap ThumbNail
  {
    get
    {
      return _thumbNail;
    }
  }

  public Size DesiredSize
  {
    get { return _desiredSize; }
    set { _desiredSize = value; }
  }
  private IMalloc Allocator
  {
      get
      {
          if (!disposed)
          {
              if (alloc == null)
              {
                  UnmanagedMethods.SHGetMalloc(ref alloc);
              }
          }
          else
          {
              Debug.Assert(false, "Object has been disposed.");
          }
          return alloc;
      }
  }

  public Bitmap GetThumbnail(string fileName)
  {
      if (!File.Exists(fileName) && !Directory.Exists(fileName))
      {
          throw new FileNotFoundException(string.Format("The file '{0}' does not exist", fileName), fileName);
      }
      if (_thumbNail != null)
      {
          _thumbNail.Dispose();
          _thumbNail = null;
      }
      IShellFolder folder = null;
      try
      {
          folder = getDesktopFolder;
      }
      catch (Exception ex)
      {
          throw ex;
      }
      if (folder != null)
      {
          IntPtr pidlMain = IntPtr.Zero;
          try
          {
              int cParsed = 0;
              int pdwAttrib = 0;
              string filePath = Path.GetDirectoryName(fileName);
              folder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, filePath, ref cParsed, ref pidlMain, ref pdwAttrib);
          }
          catch (Exception ex)
          {
              Marshal.ReleaseComObject(folder);
              throw ex;
          }
          if (pidlMain != IntPtr.Zero)
          {
              Guid iidShellFolder = new Guid("000214E6-0000-0000-C000-000000000046");
              IShellFolder item = null;
              try
              {
                  folder.BindToObject(pidlMain, IntPtr.Zero, ref iidShellFolder, ref item);
              }
              catch (Exception ex)
              {
                  Marshal.ReleaseComObject(folder);
                  Allocator.Free(pidlMain);
                  throw ex;
              }
              if (item != null)
              {
                  IEnumIDList idEnum = null;
                  try
                  {
                      item.EnumObjects(IntPtr.Zero, (ESHCONTF.SHCONTF_FOLDERS | ESHCONTF.SHCONTF_NONFOLDERS), ref idEnum);
                  }
                  catch (Exception ex)
                  {
                      Marshal.ReleaseComObject(folder);
                      Allocator.Free(pidlMain);
                      throw ex;
                  }
                  if (idEnum != null)
                  {
                      int hRes = 0;
                      IntPtr pidl = IntPtr.Zero;
                      int fetched = 0;
                      bool complete = false;
                      while (!complete)
                      {
                          hRes = idEnum.Next(1, ref pidl, ref fetched);
                          if (hRes != 0)
                          {
                              pidl = IntPtr.Zero;
                              complete = true;
                          }
                          else
                          {
                              if (_getThumbNail(fileName, pidl, item))
                              {
                                  complete = true;
                              }
                          }
                          if (pidl != IntPtr.Zero)
                          {
                              Allocator.Free(pidl);
                          }
                      }
                      Marshal.ReleaseComObject(idEnum);
                  }
                  Marshal.ReleaseComObject(item);
              }
              Allocator.Free(pidlMain);
          }
          Marshal.ReleaseComObject(folder);
      }
      return ThumbNail;
  }

  private bool _getThumbNail(string file, IntPtr pidl, IShellFolder item)
  {
      IntPtr hBmp = IntPtr.Zero;
      IExtractImage extractImage = null;
      try
      {
          string pidlPath = PathFromPidl(pidl);
          if (Path.GetFileName(pidlPath).ToUpper().Equals(Path.GetFileName(file).ToUpper()))
          {
              IUnknown iunk = null;
              int prgf = 0;
              Guid iidExtractImage = new Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1");
              item.GetUIObjectOf(IntPtr.Zero, 1, ref pidl, ref iidExtractImage, ref prgf, ref iunk);
              extractImage = (IExtractImage)iunk;
              if (extractImage != null)
              {
                  Console.WriteLine("Got an IExtractImage object!");
                  SIZE sz = new SIZE();
                  sz.cx = DesiredSize.Width;
                  sz.cy = DesiredSize.Height;
                  StringBuilder location = new StringBuilder(260, 260);
                  int priority = 0;
                  int requestedColourDepth = 32;
                  EIEIFLAG flags = EIEIFLAG.IEIFLAG_ASPECT | EIEIFLAG.IEIFLAG_SCREEN;
                  int uFlags = (int)flags;
                  extractImage.GetLocation(location, location.Capacity, ref priority, ref sz, requestedColourDepth, ref uFlags);
                  extractImage.Extract(ref hBmp);
                  if (hBmp != IntPtr.Zero)
                  {
                      _thumbNail = Bitmap.FromHbitmap(hBmp);
                  }
                  Marshal.ReleaseComObject(extractImage);
                  extractImage = null;
              }
              return true;
          }
          else
          {
              return false;
          }
      }
      catch (Exception ex)
      {
          if (hBmp != IntPtr.Zero)
          {
              UnmanagedMethods.DeleteObject(hBmp);
          }
          if (extractImage != null)
          {
              Marshal.ReleaseComObject(extractImage);
          }
          throw ex;
      }
  }

  private string PathFromPidl(IntPtr pidl)
  {
      StringBuilder path = new StringBuilder(260, 260);
      int result = UnmanagedMethods.SHGetPathFromIDList(pidl, path);
      if (result == 0)
      {
          return string.Empty;
      }
      else
      {
          return path.ToString();
      }
  }

  private IShellFolder getDesktopFolder
  {
      get
      {
          IShellFolder ppshf = null;
          int r = UnmanagedMethods.SHGetDesktopFolder(ref ppshf);
          return ppshf;
      }
  }

  public void Dispose()
  {
      if (!disposed)
      {
          if (alloc != null)
          {
              Marshal.ReleaseComObject(alloc);
          }
          alloc = null;
          if (_thumbNail != null)
          {
              _thumbNail.Dispose();
          }
          disposed = true;
      }
  }

}

 

Jul 3, 2009 at 12:02 PM

Hi Cathal

I can help you with the problem why it doesnt work with docx and works with pptx.

But before that, here's the equivalent code that I have done for getting the thumbnail using the OOXML, which also has the same problem as urs, that it doesnt work with docx....psssss, there's a surprise for you at the end ;)

Its goes like this:

        private void GetThumnail(string docFilePath, string destImgPath)
        {
            var wordprocessingDocument = WordprocessingDocument.Open(docFilePath, false);
            
            var thumbnailPart = wordprocessingDocument.ThumbnailPart;
            
            var stream = thumbnailPart.GetStream(FileMode.Open, FileAccess.Read);
            var byteArr = ReadAllBytes(stream);

            var img = byteArrayToImage(byteArr);
            img.Save(destImgPath);
        }


        public static byte[] ReadAllBytes(Stream source)
        {
            var originalPosition = source.Position;
            source.Position = 0;
            try
            {
                var readBuffer = new byte[4096];
                var totalBytesRead = 0;
                int bytesRead;
                while ((bytesRead = source.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0)
                {
                    totalBytesRead += bytesRead;
                    if (totalBytesRead != readBuffer.Length) continue;
                    var nextByte = source.ReadByte();
                    if (nextByte == -1) continue;
                    var temp = new byte[readBuffer.Length*2];

                    System.Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length);
                    System.Buffer.SetByte(temp, totalBytesRead, (byte)nextByte);
                    readBuffer = temp;
                    totalBytesRead++;
                }

                var buffer = readBuffer;
                if (readBuffer.Length == totalBytesRead) return buffer;
                buffer = new byte[totalBytesRead];
                System.Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead);
                return buffer;
            }
            finally
            {
                source.Position = originalPosition;
            }
        }


        public Image byteArrayToImage(byte[] byteArrayIn)
        {
            var ms = new MemoryStream(byteArrayIn);
            var returnImage = Image.FromStream(ms);
            return returnImage;
        }

 

What I have done here is:

  1. I open the docx file from the given path and get the thumbnail part of the docx.
  2. I get the steam of this thumbnail part, read all the bytes then convert this bytes into an Image object
  3. The image is a thumbnail and I store at the desired location.

Now why does my code and your code doesnt work for the Docx Documents? The reason is really very annoying, but still its THE reason. When we create a PPTX file in Office 2007 and save it, Office creates a thumbnail for it automatically. However for Word, it doesnt do so. For Word, when you save the file, in the dialog box where you give the path and name of the file, in the same dialog box below there's a checkbox "Save Thumbnail", only when you select that, the thumbnail of the document is stored with the Docx file and then my code starts to work, and I am very sure its the same reason with your code as well. So, try creating a Docx file and when u save it, select the Save Thumbnail option and see if your code works with that file.

So, now the goal is What do we do when the docx file doesnt have a thumbnail? One option would be to open the docx file and save it again but this time "Save Thumbnail" option (I am not sure if this is possible programatically). The other option would be to some how generate the thumbnail using a Third party tool and use it.

One more thing I would like to know from you, all the operations that are done using your API, does it require that a word instance has to be opened on the local machine or it's all programatic as in the OOXML?

 

Thanks a lot for your efforts.

Akshaya

Coordinator
Jul 6, 2009 at 8:22 AM

HI Akshaya,

thank you for doing this research and sharing the results with me. To answer your question, Docx does not require (Office or the OOXML SDK) to be installed on the local machine. It functions simularly to the OOXML SDK in this respect. I think that Word does some very interesting and complicated work to generate thumbnails. This process would be identical to the process used for printing a word document, the only difference is that the documents are scaled down in size.

I will do some more research myself, this would be a great feature to add to DocX. If you find out anything more, I would be very grateful if you share your research with me.

kind regards,
Cathal