2006年頃からでしょうか、Webサイトのサムネイル画像を掲載するサイトが増えてきています。
サイトイメージのサムネイル化は対象サイトの雰囲気が掴めるということもあり、視覚的にも非常に有効だと思います。
このサンプルではASP.NET2.0環境でも利用できるWebサイトのサムネイル画像を作成するサンプルを作成します。
.net Framework2.0環境でWebサイト情報を取得するにはWebBrowserコントロール(COM)を利用します。
通常、WebBrowserコントロールはWindowsアプリケーションにてブラウザ機能をアプリケーションに実装する際に利用する事が多いとおもいますが、
同コントロールをASP.NET2.0環境でも利用する事ができます。
WebBrowserコントロール(COM)を利用する際に注意すべきことは、COMは『シングルスレッドアパートメント』モード(STAモード)のスレッド内からではないと利用できないということです。
本題に入りますが.NET2.0環境でブラウザイメージをキャプチャーするには以下の方法を利用する事が一般的(簡単)です。
- キャプチャー対象のサイト情報の読み込みにWebBrowserコントロールを利用する。
- ブラウザイメージのキャプチャーにWebBrowser.DrawToBitmap()メソッドを利用する。
インターネットで同様のサンプルサイトを検索した場合も上記手法と同様の実装をしているサンプルが多数ヒットすると思います。
解説は後述しますが、上記手法でYahooサイト(http://www.yahoo.co.jp)やNiftyサイト(http://www.nifty.com)のキャプチャー画像を取得してみて下さい。
恐らく真っ白の画像が取得されるハズです。(2007年8月現在)
WebBrowserコントロールのDrawToBitmap()メソッドですが、このメソッドが実行される際のキャプチャ元の情報はWebBrowser.Document.ActiveElementのエレメント情報を利用しています。(経験測です)
MSDNのActiveElement要素のヘルプを見てみると以下のように記述されています。
ドキュメントにフォーカスがあり、ドキュメントの要素にフォーカスがない場合、ActiveElement は <BODY> タグに対応する要素を返します。
ドキュメントにフォーカスがない場合、ActiveElement は nullを返します。』
言い方を変えれば、ドキュメントにフォーカスがある場合でもドキュメント要素(例えばTextBoxなど)にフォーカスがある場合は
フォーカスがある要素の子要素(エレメント)情報がActiveElementに設定されるのです。
先に記したYahooやNiftyのサイトではサイトLoad後にテキストボックスにフォーカスを移動させる為のJavaScriptが実装されています。
その為、WebBrowser.Document.ActiveElementにはサイト全体のHTML情報ではなくフォーカス移動先のコントロール(エレメント)の情報が設定されているのです。
このような状況下でキャプチャーメソッド(DrawToBitmap)を行っても真っ白な画像が取得されるだけなのです。
この問題の解決方法は二つ考えられます。(以下)
- WebBrowserがJavaScriptを有効にしないように設定する。
- DrawToBitmap()メソッドの代わりにOleDraw()APIを利用する。
上記1のJavaScriptを無効にする方法はIInternetSecurityManagerを実装することにより可能であるみたい(私は試していません)ですが、COMの知識が必要となりますので多少ハードルが高くなります。
OleDraw()メソッドを利用する方法は比較的簡単なので本サンプルではこの方法を利用します。
ASP.NET 2.0環境でWebBrowserコントロールを利用するには2点注意する事があります。
- WebBrowserコントロールは名前空間System.Windows.Forms(system.windows.forms.dll)に属します。
Webアプリなどで利用する場合は、system.windows.forms.dllを参照追加する必要があります。
-
WebBrowser(COM)コントロールはシングルスレッドアパートメント』モード(STAモード)のみで利用できます。
以上を踏まえてサンプルコードを以下に記します。
と、その前にサンプルの動作仕様は以下のようになってます。
- .net Framework 2.0以上(ASP.net 2.0も可能)に対応
- キャプチャー時のウェブサイト接続時にタイムアウト値(5秒)を超えると強制終了されます。
下記のサンプルで以下のようにキャプチャーできます。(Niftyのサイトをキャプチャーしました)
サンプルコードはサイトの一番下のダウンロードボタンより取得できます。
Webブラウザキャプチャ実行クラスとキャプチャー処理実装クラス
using System;
using System.Collections.Generic;
using System.Threading;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
/// <summary>
/// Webサムネイル画像生成クラス(ThumbnailCreateBrowser)のラッパークラス
/// ThumbnailCreateBrowserクラス(内部的にはWebBrowserクラス)はSTAモード
/// (シングルスレッドアパートメントモード)で動作させる必要がある為、
/// 本クラスにてスレッドを生成させています。
/// ※当クラスを利用する事によりASP.NETからも利用する事ができるようになります。
/// </summary>
public class WebThumbnail
{
/// <summary>
/// 画像キャプチャーするブラウザの幅
/// </summary>
private int _browserWidth = 0;
/// <summary>
/// 画像キャプチャーするブラウザの高さ
/// </summary>
private int _browserHeight = 0;
/// <summary>
/// キャプチャー画像の幅(スレッド処理用にテンポラリとして使用)
/// </summary>
private int _imgWidth = 0;
/// <summary>
/// キャプチャー画像の高さ(スレッド処理用にテンポラリとして使用)
/// </summary>
private int _imgHeight = 0;
/// <summary>
/// キャプチャー先のURL(スレッド処理用にテンポラリとして使用)
/// </summary>
private string _targetUrl = "";
/// <summary>
/// キャプチャーした出力画像(スレッド処理用にテンポラリとして使用)
/// </summary>
private Bitmap _outputBmp = null;
/// <summary>
/// 画像作成スレッド完了通知を受ける為のManualResetEvent
/// </summary>
private ManualResetEvent _mre = new ManualResetEvent(false);
/// <summary>
/// WebThumbnailクラスのコンストラクタ
/// ※ブラウザサイズを保持
/// </summary>
/// <param name="browserWidth">ブラウザの幅</param>
/// <param name="browserHeight">ブラウザの高さ</param>
public WebThumbnail(int browserWidth, int browserHeight)
{
this._browserWidth = browserWidth;
this._browserHeight = browserHeight;
}
/// <summary>
/// 対象URLのサムネイル画像の保存処理
/// </summary>
/// <param name="targetUrl">ターゲットURL</param>
/// <param name="imgWidth">サムネイル画像の幅</param>
/// <param name="imgHeight">サムネイル画像の高さ</param>
/// <param name="fileName">画像保存パス(フルパス)</param>
public void SaveWebThumbnailImage(string targetUrl, int imgWidth, int imgHeight, string fileName)
{
using (Bitmap bmp = this.getWebThumbnailImage(targetUrl, imgWidth, imgHeight))
{
if (null != bmp) bmp.Save(fileName);
}
}
/// <summary>
/// 対象URLのサムネイルBitmapの取得処理
/// ※取得したBitmapは不要になり次第、コール元でDispose()して下さい
/// </summary>
/// <param name="targetUrl">ターゲットURL</param>
/// <param name="imgWidth">サムネイル画像の幅</param>
/// <param name="imgHeight">サムネイル画像の高さ</param>
/// <returns>サムネイルイメージを含んだBitmap</returns>
public Bitmap GetWebThumbnailBitmap(string targetUrl, int imgWidth, int imgHeight)
{
Bitmap bmp = this.getWebThumbnailImage(targetUrl, imgWidth, imgHeight);
return bmp;
}
/// <summary>
/// サムネイル画像の取得処理
/// ※画像生成処理(ThumbnailCreateBrowserクラス利用処理)をSTAモードの別スレッドで実行する
/// </summary>
/// <param name="targetUrl">ターゲットURL</param>
/// <param name="imgWidth">サムネイル画像の幅</param>
/// <param name="imgHeight">サムネイル画像の高さ</param>
/// <returns>サムネイル画像</returns>
private Bitmap getWebThumbnailImage(string targetUrl, int imgWidth, int imgHeight)
{
this._targetUrl = targetUrl;
this._imgWidth = imgWidth;
this._imgHeight = imgHeight;
if (this._outputBmp != null)
{
this._outputBmp.Dispose();
this._outputBmp = null;
}
//イベントの状態を非シグナル状態に設定
this._mre.Reset();
//画像作成するスレッドを生成
Thread imgThread = new Thread(new ThreadStart(getWebThumbnailImageForThred));
//WebBrowserはシングルスレッドアパートメントモードでのみ実行可能なのでスレッドのモードを設定して実行する
imgThread.SetApartmentState(ApartmentState.STA);
imgThread.Start();
//スレッドが終了するまで、メインスレッドを待機
this._mre.WaitOne();
//スレッドを終了
imgThread.Abort();
return this._outputBmp;
}
/// <summary>
/// サムネイル画像取得メソッド
/// ※当メソッド内でWebBrowserインスタンスを生成する為、STAモードで実行する必要があります。
/// </summary>
private void getWebThumbnailImageForThred()
{
using (ThumbnailCreateBrowser imgCreater = new ThumbnailCreateBrowser(this._browserWidth, this._browserHeight))
{
Bitmap bmp = imgCreater.GetWebThumbnailBitmap(this._targetUrl, this._imgWidth, this._imgHeight);
this._outputBmp = bmp;
}
if (this._mre != null)
{
this._mre.Set();
}
}
}
/// <summary>
/// Webサムネイルを作成するメインクラス
/// キャプチャー時にタイムアウト時間が経過すると処理を強制終了します
/// </summary>
class ThumbnailCreateBrowser : IDisposable
{
/// <summary>
/// サイトアクセスの既定タイムアウト値
/// </summary>
private const int _DEFAULT_TIME_OUT = 5;
/// <summary>
/// Webサイトキャプチャーに利用するWebBrowser
/// </summary>
private WebBrowser _browser = null;
/// <summary>
/// 通信タイムアウト値格納変数
/// </summary>
private int _timeOutValue = 0;
/// <summary>
/// コンストラクタ(タイムアウト値を既定値に設定)
/// </summary>
/// <param name="browserWidth">キャプチャーブラウザの幅</param>
/// <param name="browserHeight">キャプチャーブラウザの高さ</param>
public ThumbnailCreateBrowser(int browserWidth, int browserHeight)
: this(browserWidth, browserHeight, _DEFAULT_TIME_OUT)
{
}
/// <summary>
/// コンストラクタ(任意のタイムアウト値設定用)
/// </summary>
/// <param name="browserWidth">キャプチャーブラウザの幅</param>
/// <param name="browserHeight">キャプチャーブラウザの高さ</param>
/// <param name="timeOut">通信タイムアウト値(秒)</param>
public ThumbnailCreateBrowser(int browserWidth, int browserHeight, int timeOut)
{
this._browser = new WebBrowser();
//スクロールバーを無効にする。
this._browser.ScrollBarsEnabled = false;
this._browser.ClientSize = new Size(browserWidth, browserHeight);
//タイムアウト値を設定
this._timeOutValue = timeOut;
}
//OleDraw関数(API)を利用する為の宣言
[DllImport("ole32.dll")]
public static extern int OleDraw(IntPtr pUnk, int dwAspect, IntPtr hdcDraw, ref Rectangle lprcBounds);
public enum DVASPECT
{
DVASPECT_CONTENT = 1,
DVASPECT_THUMBNAIL = 2,
DVASPECT_ICON = 4,
DVASPECT_DOCPRINT = 8
}
/// <summary>
/// サムネイル画像の取得処理
/// ※通信時間がタイムアウト値を超えると処理を強制終了します
/// ※ブラウザ画面のキャプチャにはOleDrawを利用しています。
/// WebBrowser.DrawToBitmap()も利用できますが、このメソッドはサイトロード時にフォーカスを移動するスクリプトが
/// 埋め込まれているサイトでは正常にキャプチャできません。
/// </summary>
/// <param name="targetUrl">キャプチャー対象のURL</param>
/// <param name="imgWidth">サムネイル画像幅</param>
/// <param name="imgHeight">サムネイル画像高さ</param>
/// <returns>サムネイル画像</returns>
public Bitmap GetWebThumbnailBitmap(string targetUrl, int imgWidth, int imgHeight)
{
DateTime startTime = DateTime.Now;
//サイトを読み込む
this._browser.Navigate(targetUrl);
while (this._browser.ReadyState != WebBrowserReadyState.Complete)
{
Thread.Sleep(0);
Application.DoEvents();
TimeSpan span = DateTime.Now - startTime;
if (span.Seconds > this._timeOutValue)
{
//タイムアウト値を超えたので強制終了する
return null;
}
}
//画像の取得処理
Bitmap captureBmp = new Bitmap(this._browser.Bounds.Width, this._browser.Bounds.Height);
//DrawToBitmapでもキャプチャは可能だが、起動時にScriptでカーソル移動するサイトでは正常に取得できないのでOleDrawを利用する
//this._browser.DrawToBitmap(captureBmp, this._browser.Bounds);
Graphics captureGraphic = null;
IntPtr ukPtr = IntPtr.Zero;
IntPtr hdc = IntPtr.Zero;
try
{
ukPtr = Marshal.GetIUnknownForObject(this._browser.ActiveXInstance);
captureGraphic = Graphics.FromImage(captureBmp);
hdc = captureGraphic.GetHdc();
Rectangle rect = new Rectangle(0, 0, this._browser.Bounds.Width, this._browser.Bounds.Height);
OleDraw(ukPtr, (int)DVASPECT.DVASPECT_CONTENT, hdc, ref rect);
}
finally
{
if (null != captureGraphic)
{
if (IntPtr.Zero != hdc) captureGraphic.ReleaseHdc(hdc);
captureGraphic.Dispose();
}
if (IntPtr.Zero != ukPtr) Marshal.Release(ukPtr);
}
//サムネイル画像を取得する(以下の処理でも問題はありませんが、綺麗な画像を取得したいなら以下のコードをお勧めします。)
//Image thumbnailImg = captureBmp.GetThumbnailImage(imgWidth, imgHeight, null, IntPtr.Zero);
//captureBmp.Dispose();
//return (Bitmap)thumbnailImg;
Graphics thumbnailGraphic = null;
try
{
Bitmap thumbnailBmp = new Bitmap(imgWidth, imgHeight, captureBmp.PixelFormat);
thumbnailGraphic = Graphics.FromImage(thumbnailBmp);
thumbnailGraphic.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
thumbnailGraphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
thumbnailGraphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
Rectangle thumbnailRect = new Rectangle(0, 0, imgWidth, imgHeight);
thumbnailGraphic.DrawImage(captureBmp, thumbnailRect);
return thumbnailBmp;
}
finally
{
if (null != thumbnailGraphic)
{
thumbnailGraphic.Dispose();
}
if (null != captureBmp)
{
captureBmp.Dispose();
}
}
}
public void Dispose()
{
if (null != this._browser)
{
this._browser.Dispose();
}
}
}
実行結果を確認できるようにしようと思ったのですが、このサイトが稼動している環境のTruest LevelがMediumなので実行できないのです。。。
ということでASP.NET2.0環境で実行できるサンプルを以下よりダウンロードできるようにしました。
こちらで確認してみて下さいな。