Word から InlineShape の画像を取り出す

Word 文書に埋め込まれた画像を取り出すにはどうしたら…といろいろ調べて回ってみると、クリップボードを経由して画像を取りだしているサンプルは見つけた。が、Word は拡縮前(劣化前)の画像データを持っている*1のでそれを何とかして取りだしてみた。
スマートなやり方ではない気がするけどポイントは下記の通り

  • Range.get_XML() から該当部分の DOCX(XML) を取得して、Base64Encode された元の画像データを取得する
  • 取得した画像データから実際に Word 上に表示されているサムネイル画像を作成する
  • Word 上の画像の単位は Point なので Pixel へ変換するため、PointsToPixels を用いて変換する

下記サンプルを実行すれば、Shape.png と Thumb.png が生成される。

object missing = Type.Missing;
 
//    取り出したい InlineShape
var shape = ActiveDocument.InlineShapes[1];
 
//    画像じゃなければ処理しない(チャートの場合などがある)
if ( shape.Type != Word.WdInlineShapeType.wdInlineShapePicture )
    return;
 
System.Drawing.Image image = null;    //    元画像
System.Drawing.Image thumb = null;    //    サムネイル
 
//    InlineShape の高さと幅を取得する
//    Width, Height はそのままだと単位が pt なので pixel へ変換する
int pixWidth = (int)shape.Application.PointsToPixels(shape.Width, ref missing);
int pixHeight = (int)shape.Application.PointsToPixels(shape.Height, ref missing);
 
//    素の画像データを取得するため DOCX に埋め込まれる XML を取得する
//    この中に Base64 エンコードされた素の画像データが入っている
XmlDocument xmlShape = new XmlDocument();
xmlShape.LoadXml(shape.Range.get_XML(false));
XmlNamespaceManager nm = new XmlNamespaceManager(xmlShape.NameTable);
nm.AddNamespace("w", "http://schemas.microsoft.com/office/word/2003/wordml");
//    画像データがあるノードを取得する
var binDataNode = xmlShape.DocumentElement.SelectSingleNode("//w:pict/w:binData", nm);
 
if ( binDataNode == null )
{    //    XML 内に画像データが見つからなかった
    //    代替として表示されているものをクリップボードを経由して画像作成
    shape.Select();
    shape.Application.Selection.Copy();
    image = Clipboard.GetImage();
}
else
{    //    素の画像データが取得できた場合
    //    メモリに展開した後に Image クラスを作成
    MemoryStream ms = new MemoryStream(System.Convert.FromBase64String(binDataNode.InnerText));
    image = System.Drawing.Image.FromStream(ms);
 
    //    クリッピングされている場合の処理
    //    PictureFormat に CropXXXX として値が入っているので取得する
    //    (Width とかと同じく pt から pixel へ変換する)
    int cropTop = (int)shape.Application.PointsToPixels(shape.PictureFormat.CropTop, ref missing);
    int cropBottom = (int)shape.Application.PointsToPixels(shape.PictureFormat.CropBottom, ref missing);
    int cropLeft = (int)shape.Application.PointsToPixels(shape.PictureFormat.CropLeft, ref missing);
    int cropRight = (int)shape.Application.PointsToPixels(shape.PictureFormat.CropRight, ref missing);
 
    //    クリッピング用 Rectangle 作成
    System.Drawing.Rectangle clippingRect = new System.Drawing.Rectangle
    {
        X = cropLeft,
        Y = cropTop,
        Width = image.Width - ( cropRight + cropLeft ),
        Height = image.Height - ( cropBottom + cropTop ),
    };
    //    サムネイル用の Bitmap を作成(Height と Width は現在表示しているサイズで)
    thumb = new System.Drawing.Bitmap(pixWidth, pixHeight, image.PixelFormat);
    //    サムネイルに描画する
    var g = System.Drawing.Graphics.FromImage(thumb);
    g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
    g.DrawImage(image,
        new System.Drawing.Rectangle(0, 0, thumb.Width, thumb.Height),
        clippingRect,
        System.Drawing.GraphicsUnit.Pixel);
}
 
image.Save("Shape.png", ImageFormat.Png);
if ( thumb != null )
{
    thumb.Save("Thumb.png", ImageFormat.Png);
}

*1:大きい画像を貼り付けると自動的に縮小されるが、画像を右クリック>サイズからリセットをすれば実寸大に表示される