.NET TIPS

[ASP.NET]動的にJPEG画像を作成するには?

山田 祥寛
2005/09/30

 データベースなどのデータ・ストアに蓄積されたデータは、何もDataGridコントロールなどでグリッド表示するばかりが能ではない。時として、円グラフや折れ線グラフなどのチャート画像に加工することで、よりユーザーの視覚に訴えるビューを実現できるケースも多いはずだ。

 ASP.NETには、チャートを描画するためのコントロールは標準では用意されていないが、.NET Frameworkには、Windows標準の描画エンジンであるGDI+(Graphical Device Interface +)機能にアクセスするためのクラス・ライブラリが用意されている。本稿では、このクラス・ライブラリを利用して、テキスト・ファイル上で管理された数値データを基に、動的に円グラフを生成してみることにしよう。

 なお、本稿のサンプルを動作させるには、あらかじめグラフの基となる数値データとして、以下のようなタブ区切りテキスト(dotnet.dat)を用意しておく必要がある。

1. すばらしい 123
2. 良かった 102
3. 普通 57
4. 悪い 10
5. なってない 3
円グラフ描画の基となるタブ区切りテキスト(dotnet.dat)

 それではさっそく、具体的な手順を見ていくことにしよう。

1. 画像生成用のHTTPハンドラ・クラスを記述する

 本稿の例のように、ASP.NET上で画像データを生成するには、HTTPハンドラ・クラスを利用すると便利だ。HTTPハンドラ・クラスとは、IISに対してクライアントから要求があった場合に、実際のリクエスト処理を行うためのクラスで、例えばおなじみの.aspxファイルも内部的にはHTTPハンドラ・クラスによって処理されている。

 初歩的なASP.NETアプリケーションを構築するうえでは、HTTPハンドラ・クラスを意識する必要はほとんどないが、アプリケーションに新たなサービスを定義したい場合などでは、HTTPハンドラ・クラスを利用することでスマートにサービスを実装することができる。

 自前のHTTPハンドラ・クラスを作成する方法については、本稿では割愛する。詳細は、「TIPS:[ASP.NET]ASP.NETアプリケーションに独自の拡張子を追加するには?」)を参照いただきたい。

 以下は、データ・ファイルから読み込んだデータを基に円グラフの画像を生成してレスポンスとして返すHTTPハンドラである。

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.IO;
using System.Text;
using System.Web;

namespace Com.Msn.Wings {
  public class ImageGenerator : IHttpHandler {
    public bool IsReusable {
      get { return true; }
    }

    public void ProcessRequest(HttpContext context) {
      int i = 0;
      float sum = 0;
      float[] angle = new float[5];

      // 550×500ピクセルのキャンバスを生成したうえで
      // 背景色を白でクリア
      Bitmap bImage =
        new Bitmap(550, 500, PixelFormat.Format64bppPArgb);
      Graphics graph = Graphics.FromImage(bImage);
      graph.Clear(Color.White);

      // 画像生成に使用するブラシ(SolidBrushコントロール)を定義
      SolidBrush[] brush = new SolidBrush[5];
      brush[0] = new SolidBrush(Color.Lime);
      brush[1] = new SolidBrush(Color.Blue);
      brush[2] = new SolidBrush(Color.DarkGreen);
      brush[3] = new SolidBrush(Color.Red);
      brush[4] = new SolidBrush(Color.Silver);

      // リクエストURLからベース名のみを取得し、
      // 「<ベース名>.dat」をデータ・ファイルとして読み込む
      String basePath =
        Path.GetFileNameWithoutExtension(context.Request.FilePath);
      StreamReader sReader = new StreamReader(
        context.Server.MapPath(basePath + ".dat"),
        Encoding.GetEncoding("Shift_JIS"));

      // 読み込んだデータから数値合計(sum)と
      //個々の値(angle)をセット
      // また、「■ <項目名>」の形式で凡例を出力
      while (sReader.Peek() > -1) {
        String[] line = sReader.ReadLine().Split('\t');
        sum += Single.Parse(line[1]);
        angle[i] = Single.Parse(line[1]);
        graph.DrawString("■ " + line[0],
          new Font("HG丸ゴシックM-PRO",16),
          brush[i], new PointF(380,20+i*25));
        i++;
      }
      sReader.Close();

      // 個々の値(angle)と合計値(sum)から円グラフの角度を計算
      for (i =0;i < angle.Length; i++) {
        angle[i] = (int)(angle[i] * 360 / sum);
      }
      float start = -90;
      // FillPieメソッドで個々のデータに基づき、円弧を描画
      for (i = 0; i < angle.Length; i++) {
        graph.FillPie(brush[i], 10, 10, 400, 400, start, angle[i]);
        start += angle[i];
      }
      // コンテンツ・タイプを設定したうえで生成された画像を出力
      context.Response.ContentType = "image/jpg";
      bImage.Save(context.Response.OutputStream, ImageFormat.Jpeg);
    }
  }
}
数値データから動的に円グラフを生成するHTTPハンドラ・クラス(C#版:ImageGenerator.cs)
 
Imports System
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Drawing.Drawing2D
Imports System.IO
Imports System.Text
Imports System.Web
Imports Microsoft.VisualBasic

Namespace Com.Msn.Wings
  Public Class ImageGenerator : Implements IHttpHandler
    Public ReadOnly Property IsReusable As Boolean Implements IHttpHandler.IsReusable
      Get
        Return True
      End Get
    End Property

    Sub ProcessRequest(context As HttpContext) Implements IHttpHandler.ProcessRequest
      Dim i As Single=0
      Dim sum As Integer=0
      Dim angle(4) As Integer

      ' 550×500ピクセルのキャンバスを生成したうえで、
      '背景色を白でクリア
      Dim bImage As New BitMap( _
        550, 500, PixelFormat.Format64bppPARGB)
      Dim graph As Graphics = Graphics.FromImage(bImage)
      graph.Clear(Color.White)

      ' 画像生成に使用するブラシ(SolidBrushコントロール)を定義
      Dim brush(4) As SolidBrush
      brush(0) = New SolidBrush(Color.Lime)
      brush(1) = New SolidBrush(Color.Blue)
      brush(2) = New SolidBrush(Color.DarkGreen)
      brush(3) = New SolidBrush(Color.Red)
      brush(4) = New SolidBrush(Color.Silver)

      ' リクエストURLからベース名のみを取得し、

      ' 「<ベース名>.dat」をデータ・ファイルとして読み込む
      Dim basePath As String = _
        Path.GetFileNameWithoutExtension(context.Request.FilePath)
      Dim sReader As New StreamReader( _
        context.Server.MapPath(basePath & ".dat"), _
        Encoding.GetEncoding("Shift_JIS"))

      ' 読み込んだデータから数値合計(sum)と
      ' 個々の値(angle)をセット
      ' また、「■ <項目名>」の形式で凡例を出力
      Do While sReader.Peek() > -1
        Dim line As String() = sReader.ReadLine().Split(Chr(9))
        sum = sum + line(1)
        angle(i) = line(1)
        graph.DrawString("■ " & line(0), _
          New Font("HG丸ゴシックM-PRO",16), brush(i), _
          New PointF(380,20+i*25))
        i = i + 1
      Loop
      sReader.Close()

       ' 個々の値(angle)と合計値(sum)から円グラフの角度を計算
      For i = 0 To angle.Length ? 1
        angle(i) = CInt(angle(i) *360 / sum)
      Next
      Dim start As Integer = -90

      ' FillPieメソッドで個々のデータに基づき、円弧を描画
      For i = 0 To angle.Length - 1
        graph.FillPie(brush(i), 10, 10, 400, 400, start, angle(i))
        start += angle(i)
      Next

      ' コンテンツ・タイプを設定したうえで生成された画像を出力
      context.Response.ContentType = "image/jpg"
      bImage.Save(context.Response.OutputStream, ImageFormat.Jpeg)
    End Sub
  End Class
End Namespace
数値データから動的に円グラフを生成するHTTPハンドラ・クラス(VB.NET版:ImageGenerator.vb)

 Bitmapクラス、Graphicsクラス(System.Drawing名前空間)に関する基本は、ビットマップに関する各TIPSとコード内のコメントを参照していただくとして、ここでは円グラフを出力するロジックを追ってみることにしよう。

 円グラフを構成する円弧を出力するのは、GraphicsクラスのFillPieメソッドの役割だ。本稿で使用しているFillPieメソッドの構文は、以下のとおり。

public void FillPie(Brush brush, float x, float y,
  float width, float height, float startAngle, float sweepAngle)
Overloads Public Sub FillPie(ByVal brush As Brush, _
  ByVal x As Single, ByVal y As Single, _
  ByVal width As Single, ByVal height As Single, _
  ByVal startAngle As Single, ByVal sweepAngle As Single)
Graphics.FillPieメソッドのシグネチャ(上:C#、下:VB.NET)

 FillPieメソッドのパラメータは、先頭から「使用するブラシ」「中心のX座標」「中心のY座標」「高さ」「幅」「開始角度」「描画角度」である。つまり、ここでは−90度を開始角度(start)とし、各データ項目の角度(angle)を加算しながら円弧を描画することで、円弧を順番に積み重ね、1つの円グラフを生成しているというわけだ。

 生成した画像(Graphicsオブジェクト)は、最終的にBitmapオブジェクトのSaveメソッドで出力できる。本稿で使用しているSaveメソッドの構文は、以下のとおり。

public void Save(Stream stream, ImageFormat format)
Overloads Public Sub Save(ByVal stream As Stream, ByVal format As ImageFormat)
Bitmap.Saveメソッドのシグネチャ(上:C#、下:VB.NET)

 このメソッドでは、指定された出力先(Streamオブジェクト)に対して、指定フォーマット(ImageFormatオブジェクト)で画像を出力することができる。ImageFormatオブジェクト(System.Drawing.Imaging名前空間)のプロパティには、本稿で使用しているJPEG画像のほか、Bmp、Exif、Gif、Png、Tiffなど主要な画像形式を指定可能だ。

 また、画像データのようなバイナリ・データをクライアントに出力したい場合には、バイナリ出力ストリームを利用すること。バイナリ出力ストリームは、HttpResponseオブジェクトのOutputStreamプロパティで取得できる。

2. HTTPハンドラ・クラスを配置する

 HTTPハンドラ・クラスは、使用に先立ってコマンド・プロンプトからコンパイルを行う必要がある。コンパイルの構文は以下のとおり。

> csc /t:library ImageGenerator.cs
> vbc /t:library /r:System.dll /r:System.Drawing.dll /r:System.Web.dll ImageGenerator.vb
HTTPハンドラ・クラスのコンパイル方法(上:C#、下:VB.NET)

 上記のコンパイルに成功した場合、C#、VB.NETいずれにおいてもImageGenerator.dllが生成されるはずなので、これをアプリケーション・ルート配下の「\bin」フォルダにコピーすればよい。

3. web.configの設定を行う

 次に、アプリケーション・ルート配下のweb.configに以下の設定を追加する。「TIPS:[ASP.NET]特定の拡張子に対するアクセスを制限するには?」でも紹介したように、これによって、いま作成したImageGenerator.dllアセンブリ内のImageGeneratorクラス(Com.Msn.Wings名前空間)が拡張子「.img」処理用のHTTPハンドラ・クラスとして追加される。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
  <system.web>
    <httpHandlers>
      <add verb="GET" path="*.img"
        type="Com.Msn.Wings.ImageGenerator, ImageGenerator" />
    </httpHandlers>
  </system.web>
</configuration>
拡張子「.img」の設定を追加したweb.config

 <httpHandlers>要素配下の<add>要素のpath属性に「*.img」のようなワイルドカードを含んだパスを指定することで、拡張子「.img」に対するリクエストをすべてImageGeneratorクラスで処理できるというわけだ。

 前述のImageGenerator.cs(ImageGenerator.vb)のソース・コードをご覧いただければ分かるように、後はリクエストURLから動的に処理を分岐するロジックを、HTTPハンドラ・クラスに追加すればよい。

 ここでは、リクエストURLから拡張子を取り除いたベース名を抽出し、StreamReaderクラス(System.IO名前空間)で「<ベース名>.dat」をデータ・ファイルとして読み込んでいる。このような仕組みを採用することで、リクエストURLを変えれば、参照すべきデータ・ファイルを動的に変更できるというわけだ。汎用的なサービスを提供する場合にはよく利用する手法なので、覚えておくとよいだろう。

4. IISに拡張子のマッピングを行う

 ただし、ASP.NET標準でない拡張子を追加した場合、web.configの設定だけではHTTPハンドラ・クラスは正しく実行できない。というのも、HTTPハンドラ・クラスへの振り分けはASP.NETエンジン(aspnet_isapi.dll)が行うからだ。「.img」はIISのデフォルト設定では認識されない拡張子であるから、当然、拡張子「.img」へのリクエストがあってもaspnet_isapi.dllは起動しない。

 自前の拡張子を認識させるには、IIS上で拡張子「.img」とASP.NETエンジンとのマッピングを行う必要がある。この方法については、「TIPS:[ASP.NET].htmlや.pdfファイルをフォーム認証やロギングの対象にするには?」で紹介しているので、そちらを参照してほしい。

 以上で、サンプル・アプリケーションを動作させるための一連の作業は完了だ。ImageGeneratorクラスの動作を確認するために、以下のようなWebフォームを作成しよう。

<%@ Page ContentType="text/html" Language="VB" %>
<html>
<head>
<title>画像を動的に生成する</title>
</head>
<body>
<h1>アンケート調査結果</h1>
<h3>1. 本記事を読んだ感想は?</h3>
<img src="dotnet.img" width="550" height="500" />
</body>
</html>
ImageGeneratorを利用するためのWebフォーム(showImage.aspx)

 画像データを出力するImageGeneratorクラスは、<img>タグのsrc属性からあたかも静的な画像ファイルであるかのように呼び出すことが可能だ。以上を実行した結果、以下のような結果が出力されれば成功だ。

showImage.aspxの実行結果

 異なるデータ・ファイルを作成し、出力画像が動的に切り替わることも確認していただきたい。なお、本稿ではデータ項目の数が5つ以内であることを前提としているが、当然、5つ以上の項目に対応することも可能だ。余力のある方は、ぜひ挑戦してみていただきたい。End of Article

カテゴリ:Webフォーム 処理対象:グラフ
使用ライブラリ:Bitmapクラス(System.Drawing名前空間)
使用ライブラリ:Graphicsクラス(System.Drawing名前空間)
使用ライブラリ:ImageFormatクラス(System.Drawing.Imaging名前空間)
関連TIPS:[ASP.NET]ASP.NETアプリケーションに独自の拡張子を追加するには?
関連TIPS:[ASP.NET]特定の拡張子に対するアクセスを制限するには?
関連TIPS:[ASP.NET].htmlや.pdfファイルをフォーム認証やロギングの対象にするには?
 
この記事と関連性の高い別の.NET TIPS
[ASP.NET]ASP.NETアプリケーションに独自の拡張子を追加するには?
[ASP.NET]特定の拡張子に対するアクセスを制限するには?
[ASP.NET]アプリケーションにログ参照のユーティリティを追加するには?
[ASP.NET]ユーザーから入力されたデータを任意のあて先にメールするには?
[ASP.NET]特定のサービスを無効にするには?
このリストは、(株)デジタルアドバンテージが開発した
自動関連記事探索システム Jigsaw(ジグソー) により自動抽出したものです。
generated by

「.NET TIPS」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間