【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

オートシェイプからJPEGやPNGを抽出

Excelに貼り付けた画像を取り出す方法として、Webページで保存する方法があります。しかしこの方法ではすべての写真が出力されてサムネイルが付くため、枚数が多いと選ぶのが面倒です。

昨日クリップボードにコピーしたオートシェイプを解析しましたが、それを応用すればJPEGPNGファイルを抜き出せます。クリップボードのオートシェイプにJPEGPNGが含まれていれば保存ダイアログが表示されるコードを、パブリックドメインで公開します。

using System;
using System.IO;
using System.Windows.Forms;

class Program
{
    [STAThread]
    static void Main()
    {
        var ms = Clipboard.GetData("Office Drawing Shape Format") as MemoryStream;
        if (ms != null) Parse(ms.ToArray(), 0, (int)ms.Length, "");
    }

    static void Parse(byte[] data, int p, int end, string indent)
    {
        if (p + 8 >= end || (data[p + 3] & ~1) != 0xf0) return;
        int fbt = BitConverter.ToUInt16(data, p + 2), pc = p + 8;
        int size = BitConverter.ToInt32(data, p + 4), pn = pc + size;
        if (fbt == 0xf007) pc += 36;
        if (fbt == 0xf01d)
        {
            using (var sfd = new SaveFileDialog { Filter = "JPEG (*.jpg)|*.jpg" })
                if (sfd.ShowDialog() == DialogResult.OK)
                    using (var fs = new FileStream(sfd.FileName, FileMode.Create))
                        fs.Write(data, pc + 17, pn - pc - 17);
        }
        else if (fbt == 0xf01e)
        {
            using (var sfd = new SaveFileDialog { Filter = "PNG (*.png)|*.png" })
                if (sfd.ShowDialog() == DialogResult.OK)
                    using (var fs = new FileStream(sfd.FileName, FileMode.Create))
                        fs.Write(data, pc + 17, pn - pc - 17);
        }
        Parse(data, pc, pn, indent + "  ");
        Parse(data, pn, end, indent);
    }
}

fbtの値は仕様書の11ページに載っています。JPEGPNGのデータはmsofbtBSE(0xf007)に続くmsofbtBlipJPEG(0xf01d)/msofbtBlipPNG(0xf01e)のレコードの17バイト目以降に格納されています。msofbtBlipJPEG/msofbtBlipPNGの値は仕様書に直接出てきませんが、15ページのMSOBLIPTYPEの値に0xf018を足すと得られます。BMPをコピーすると自動的にPNGに変換して格納されるようです。

【追記:2009.5.26】JPEG抽出作業専用のソフトが公開されています。 → Clip to JFIF