ウェブページのサムネイル画像の取得方法

はじめに

プログラムに組み込んだウェブブラウザのサムネイル画像を表示に使えば,ぱっとみで分かりやすく内容一覧ができます.WPFでウェブページのサムネイル画像の取得方法をまとめました.

背景

WPFでウェブページを表示するには,System.Windows.Controls.WebBrowserを使用します.これの中身はInternet ExploreそのものでありWPFで書かれたブラウザではありません.これがVisualクラスを継承していればVisualBrushを使い画像に落とすことができるのですが,中身がIEそのものであることが話をややこしくします.
ここでは.NET 3.5 SP1でWPFを使いサンプルコードを作成します.

方法とその長短

サムネイル画像に変換する方法は,以下の3つがあります.

  • ウェブページのサムネイル画像を提供しているオンラインサービスを利用する
  • System.Windows.Controls.WebBrowserの画面表示を取得する
  • WindowsのOleDrawでIEの表示画面を取得するもしくはIEレンダリングエンジンを叩く
  • HTMLをXAMLに変換する

1番目の方法は独自にプログラムを作成する必要がなくて簡単ですが,今回の自力でサムネイル取得する範疇からは外れるので除外します.
2番目のWebBrowserの画面取得はmshtmlなどのの参照が不要です.ですが表示領域しか取得できまないために,長文のウェブページなどでは画面下部が切れたりします.一方で表示画面そのものを取得するために,HTMLに限らずPDFやその他画像などの任意の画面表示を取得できます.
3番目のOleDrawを使用する方法は,ホストしているIEの描画画面をWindowsAPIを叩いて取得するものです.アンマネージドなコードを書かねばならない一方で,画面表示そのものを取得できるため,長文のウェブページでも切れずに全体取得ができます.
4番目の方法はスタイルシートやフォームを含む任意のHTMLを変換するのは,それだけで大仕事であり,ここで取り扱える規模を超えます.
ここでは2および3番目の方法を行います.

WebBrowserの画面を取得する

System.Windows.Controls.WebBrowserの表示画面を取得します.方法は,Using VisualTreeHelper.GetDrawing to get around the Z-Order limitation when hosting Windows Forms controls – Jaime Rodriguez,に紹介されていました.

作成したアプリケーションの画面は左図となります.テキストボックスにURLを入力してリターンを押すと,画面右のウェブブラウザがURL先を表示します.ADDボタンを押すと,ブラウザのスクリーンショットを画面左に追加します.CLEARボタンを押すとスクリーンショットを消去します.

このスクリーンショットを取得するコードが下記のメソッドになります.VisualTreeHelper.GetDrawing()メソッドで取得したDrawingGroupをRectangleに描画します.コード中の_thumbnailはObservableCollection型の配列です.この配列をListBox.ItemsSourceのデータソースに指定して,先ほどの画面左の画像配列表示をさせています.

ポイントは2つあります.1つは画像取得部分をDispatcherから非同期で呼び出すこと.2つめは,その実行優先順位を(たぶん)Renderよりも低く設定することです.
試しに,Dispatcherを使わず直接Delegeteのコードを置いてみると,真っ白な画像しか取れませんでした.またDelegateの実行優先順位をRenderより順位が高いDataBindingに設定しても同様に真っ白な画面しか取れませんでした.

内部動作の情報がないので,どういう理由かは分からないのですが,とりあえず動作はしています.

 void addThumbnail()
        {
            //see http://blogs.msdn.com/jaimer/archive/2007/03/07/using-visualtreehelper-getdrawing-to-get-around-the-z-order-limitation-when-hosting-windows-forms-controls.aspx

            /*
             * in a delegate, web browser drawing is get as a thumbnail image.
             * the delegate must be called throw dispatcher with a low priority (maybe, less than DispatcherPriority.Render), otherwise while box (empty image) is drawn.
             */
            this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background,
                           new System.Windows.Threading.DispatcherOperationCallback(delegate
                           {
                               DrawingGroup dg = VisualTreeHelper.GetDrawing(wfHost);

                               DrawingBrush db = new DrawingBrush(dg);
                               db.TileMode = TileMode.None;
                               db.Stretch = Stretch.Uniform;
                               Rectangle rect = new Rectangle();
                               rect.Width  = wfHost.ActualWidth;
                               rect.Height = wfHost.ActualHeight;
                               rect.Fill = db;
                               Border brd = new Border();
                               brd.Child = rect;
                               _thumbnails.Add(brd);


                               return null;

                           }), null);          
           
        }

DrawingGroupをImageに描画するならば,上記のDelegateの頭部分を次のように書いてもよいです.

Image img = new Image();
img.Width =80; img.Height = 80; // サムネイルのサイズ設定
img.Strech= Strech.Uniform;
DrawingGroup dg = VisualTreeHelper.GetDrawing(wfHost);
img.Source = new DrawingImage(dg);

サンプルコードは以下の2つです.WPF版とWinForm版の2つを作成しました.WinForm版はホストしているSystem.Windows.Forms.WebBrowserの画面を取得しているだけで,先のWPF版とコードはほとんど同じです.
WinFormを作成した理由は,表示しているHTMLドキュメントの情報(タイトルやリンク先)を取得したかったのですが,WPFのSystem.Windows.Controls.WebBrowserのDocumentプロパティを参照するためにはプロジェクトの参照にmshtmlを追加する必要があるが,FormsのWebBrowserはSystem.Windows.Forms.HTMLDocumentから(見た目)マネージドなコードで扱えたためです.

OleDrawを使用する

先ほどのコードでは画面表示部分のみが取得できました.表示領域外も取得したいとなると,WPFの外にいるIEを扱うためにWindowsAPIを叩くことになります.
方法は,表示したWebページ全体をキャプチャしたい,の巻(ソース編) Musi_chan's Blog/ウェブリブログに書かれていました.これをWPFで扱えるようにWebBrowserをホストしてみました.System.Windows.Controls.WebBrowserに置き換えられないかと思ったのですが,System.Windows.Forms.WebBrowserのプロパティ ActiveXInstance に相当するプロパティが見つからず,方法は分かりませんでした.
この方法では,HTMLの表示画面取得はできますが,例えばPDFファイルなどHTMLではないファイル表示は取得できません.
サンプルコードです.

まとめ

ウェブなどのサムネイル表示のための画像取得方法を2つ行いました.Visualを直接取得する方法は,表示している画面領域しか取得できませんが,任意の表示を取得できます.OleDrawを使用する方法は,ウェブブラウザの画面全体を取得できますが,HTML以外の表示,例えばPDFファイルなど,の画像取得はできません.
このようにそれぞれに長短がありました.これを解決する方法としては,画面取得時にVisualを取得する対象のHight,Widthを一時的に大きくする方法が考えられます.
あるいは,実際には画面表示をさせずに仮想的に子要素をホストできるPanelを作成して,その中で仮想的に描画させて表示画像を取得する方法が...あるのか?