1

I am attempting to save an embedded shape as an image using C#.

If the object is embedded as an actual image (WMF/JPEG) I can retrieve the image without issue but when the object is an embedded shape or an OLE Object that displays as an image in Word I cannot seem to extract or retrieve said object to then either copy to the clipboard or save said image.

Here is my current code sample; either the object is empty or I get the following error:

System.Runtime.InteropServices.ExternalException: 'A generic error occurred in GDI+.'

Any help is appreciated. Thank you

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace ImageMagickSandboxWinForms
{
    public partial class frmMain : Form
    {
        public frmMain()
        {
            InitializeComponent();
        }

        public static BitmapSource ConvertBitmap(Bitmap source)
        {
            return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                          source.GetHbitmap(),
                          IntPtr.Zero,
                          Int32Rect.Empty,
                          BitmapSizeOptions.FromEmptyOptions());
        }

        public static Bitmap BitmapFromSource(BitmapSource bitmapsource)
        {
            Bitmap bitmap;
            using (var outStream = new MemoryStream())
            {
                BitmapEncoder enc = new BmpBitmapEncoder();
                enc.Frames.Add(BitmapFrame.Create(bitmapsource));
                enc.Save(outStream);
                bitmap = new Bitmap(outStream);
            }
            return bitmap;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            string physicsDocLocation = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop));
            physicsDocLocation += @"\[Doc path Here].docx";
            var wordApp = new Microsoft.Office.Interop.Word.Application();

            var wordDoc = wordApp.Documents.Open(physicsDocLocation);
            int iCount = wordDoc.InlineShapes.Count;
            for (int i = 1; i < (wordDoc.InlineShapes.Count + 1); i++)
            {
                var currentInlineShape = wordDoc.InlineShapes[i];
                currentInlineShape.Range.Select();
                wordDoc.ActiveWindow.Selection.Range.Copy();
                BitmapSource clipBoardImage = System.Windows.Clipboard.GetImage();
                Bitmap bmpClipImage = BitmapFromSource(clipBoardImage);
                string finalPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), @"TestConversions");
                finalPath += @"\" + Guid.NewGuid().ToString() + ".jpg";
                using (MemoryStream memory = new MemoryStream())
                {
                    using (FileStream fs = new FileStream(finalPath, FileMode.Create, FileAccess.ReadWrite))
                    {
                        bmpClipImage.Save(memory, ImageFormat.Jpeg); <<<---- Error happens here.
                        byte[] bytes = memory.ToArray();
                        fs.Write(bytes, 0, bytes.Length);
                    }
                }
            }
            wordDoc.Close();
            wordApp.Quit();
        }
    }
}
Chris K.
  • 467
  • 2
  • 5
  • 19
  • Could you please provide more information about what kind of embedded objects these are and, if possible, how they were created? Without such information it's not possible to test what approach might work. An embedded OLE object is a "child" of the program that created it and it would be necessary to access that server in order to "grab" it. In a general sort of way, you might see if right-clicking gives you an option to convert from embedded to something else. Copying (Cutting) then paste special back to a pure image format might also be a possibility. – Cindy Meister Apr 30 '20 at 12:55
  • @CindyMeister, so the source document has math equations and associated diagrams which I assume is from Microsoft's Math Equation Editor (or something like that). The weird thing is that some of the equations can be easily copied via the method above as images from the clipboard but others can't. – Chris K. May 01 '20 at 03:51
  • @CindyMeister, in addition, if I copy/paste manually the equation comes in as an image, as expected. Hope this helps. – Chris K. May 01 '20 at 03:51
  • Then do that, using PasteSpecial so that you can specify the format used when pasting. And the additional information should really be edited into the question, itself and not left in a comment. – Cindy Meister May 01 '20 at 05:19

1 Answers1

0

i have these code in my library, dunno where i have found that but hope you do the job for you: i am using Clippboard to trap the different images, jus t dont forget, Thread is needed to access Clipboard

       for (var i = 1; i <= wordApplication.ActiveDocument.InlineShapes.Count; i++)
        {

            var inlineShapeId = i; 


            var thread = new Thread(() => SaveInlineShapeToFile(inlineShapeId, wordApplication));

            // STA is needed in order to access the clipboard
            // https://stackoverflow.com/a/518724/700926
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
            thread.Join();
        }

   // General idea is based on: https://stackoverflow.com/a/7937590/700926
    protected static void SaveInlineShapeToFile(int inlineShapeId, Application wordApplication)
    {
        // Get the shape, select, and copy it to the clipboard
        var inlineShape = wordApplication.ActiveDocument.InlineShapes[inlineShapeId];
        inlineShape.Select();
        wordApplication.Selection.Copy();

        // Check data is in the clipboard
        if (Clipboard.GetDataObject() != null)
        {
            var data = Clipboard.GetDataObject();

            // Check if the data conforms to a bitmap format
            if (data != null && data.GetDataPresent(DataFormats.Bitmap))
            {
                // Fetch the image and convert it to a Bitmap
                var image = (Image) data.GetData(DataFormats.Bitmap, true);
                var currentBitmap = new Bitmap(image);

                // Save the bitmap to a file
                currentBitmap.Save(@"C:\Users\Username\Documents\" + String.Format("img_{0}.png", inlineShapeId));
            }
        }
    }

following if you are using Winform or WPF the clipboard acts differently for an image:

if (Clipboard.ContainsImage())
{
// ImageUIElement.Source = Clipboard.GetImage(); // does not work
    System.Windows.Forms.IDataObject clipboardData = System.Windows.Forms.Clipboard.GetDataObject();
  if (clipboardData != null)
  {
    if (clipboardData.GetDataPresent(System.Windows.Forms.DataFormats.Bitmap))
    {
        System.Drawing.Bitmap bitmap = (System.Drawing.Bitmap)clipboardData.GetData(System.Windows.Forms.DataFormats.Bitmap);
        ImageUIElement.Source =  System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty,BitmapSizeOptions.FromEmptyOptions());
        Console.WriteLine("Clipboard copied to UIElement");
    }
  }
}

after if its not functionam due to a bug in translation of format, there is this solution . So its infrecnh but its easily to understand the logic of the using of "DeviceIndependentBitmap"

Frenchy
  • 16,386
  • 3
  • 16
  • 39
  • Hello @Frenchy, thank you for sharing. Unfortunately I get this error: ```System.InvalidCastException: 'Unable to cast object of type 'System.Windows.Interop.InteropBitmap' to type 'System.Drawing.Image'.'``` when I attempt to run on your (Image) cast line. – Chris K. May 01 '20 at 03:44
  • hum..i have not error.... could you put your file somewhere on the net? – Frenchy May 01 '20 at 05:49