ASP.NET内部画像生成で、WPFを使ってみる

昨日できるのかな?と書きましたが、何とか形になりましたので^^;

まあ、こんな感じでJPEG画像を作成しています。
上の画像はPSUStatus上のASPXを呼び出し、ASPX内部でWPFのコンポーネントを使用して描画、JpegBitmapEncoderでJPEG化して出力しています。

さすがに、Gridはどうしようもなかったので、画像とFormattedTextの羅列です^^;

ASPX内部で画像生成して、画像を返すなどは、@ITなどでサンプルが出ているのでここでは割愛、以下コードサンプルが少々あって、長くなるので、折りたたんでいます^^;

最初にぶつかったのは、WPFのコンポーネント(Rectangleなど)が、シングルスレッドアパートメント(STA)でないと呼び出せないことでした。
ASP.NETはもともとマルチスレッドアパートメント(MTA)ですので、ここで例外が発生してなかなか厳しい。

最初は、ジェネリックハンドラで作成していたのですが、COM+を使用してやるのは面倒でしたので^^;
ASPXの宣言に

[code:c#]
@ Page Language="C#" AutoEventWireup="true" CodeBehind="Image_PSUStatus_basic.aspx.cs" Inherits="PSUStatus.WebServices.Image_PSUStatus_basic1" AspCompat="true"
[/code]

AspCompat="true"

を記載することで、STAモードとして宣言しました。

using System;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Data;
using System.Windows;
using System.Globalization;
using System.Windows.Threading;
using System.IO;

namespace PSUStatus.WebServices
{

    public class WpfPSUStatus_basic : DispatcherObject
    {
        protected DrawingVisual images;
        protected DrawingContext dc;
        static String ffontName = "Meiryo,MS UI Gothic";

        public WpfPSUStatus_basic()
        {
            images = new DrawingVisual();
        }

        public void RenderOpen()
        {
            dc = images.RenderOpen();
        }

        public void RenderClose()
        {
                dc.Close();
        }
    }
}

次に、WPFを使用して画像生成ですが、まず、DispatcherObjectから派生させたクラスを作り、WPFコンポーネントを呼ぶメソッドに

[STAThread]

属性を付加しています。

        [STAThread]
        public void CreateBG(Color bgc)
        {
            //images = new DrawingVisual();
            //バックグラウンド描画
            SolidColorBrush bgb = new SolidColorBrush(bgc);
            dc.DrawRectangle(bgb, null, new Rect(0, 0, 200, 170));
            //ブラシ
            //Fill
            SolidColorBrush fill = new SolidColorBrush(Color.FromArgb(0xff, 0x47, 0xca, 0xca));
            //Stroke
            SolidColorBrush strokebrush = new SolidColorBrush(Color.FromArgb(0xFF, 0xDE, 0xED, 0xF1));
            Pen stroke = new Pen(strokebrush, 2);
            //Text
            SolidColorBrush textbrush = new SolidColorBrush(Color.FromArgb(0xFF, 0xdA, 0xd6, 0xeA));
            FontFamily hfont = new FontFamily(ffontName);
            dc.DrawRoundedRectangle(fill, stroke, new Rect(1, 1, 198, 168), 10, 10);
            //フッター
            FormattedText ftext = new FormattedText("©SEGA",
                    new CultureInfo("ja-jp"),
                    FlowDirection.LeftToRight,
                    new Typeface(hfont, FontStyles.Normal, FontWeights.Bold, new FontStretch()),
                    11,
                    textbrush);
            dc.DrawText(ftext, new Point(145, 153));
            FormattedText fbtext = new FormattedText("PSUStatus Image Service ©Brichan",
                    new CultureInfo("ja-jp"),
                    FlowDirection.LeftToRight,
                    new Typeface(hfont, FontStyles.Normal, FontWeights.Bold, new FontStretch()),
                    8,
                    textbrush);
            dc.DrawText(fbtext, new Point(25, 1));
        }

DrawingVisualをベースに、DrawingContextを使用して、どんどん書いていきます。

        [STAThread]
        public void WriteJpeg(Stream s)
        {
            RenderTargetBitmap rtb = new RenderTargetBitmap(200, 170, 96, 96, PixelFormats.Pbgra32);
            rtb.Render(images);
            JpegBitmapEncoder jpgenc = new JpegBitmapEncoder();
            jpgenc.QualityLevel = 95;
            jpgenc.Frames.Add(BitmapFrame.Create(rtb));
            jpgenc.Save(s);
        }

書き終わった(DrawingContext.Close)後、RenderTargetBitmapを作成し、RenderTargetBitmapにDrawingVisualを描画します。

そののち、JpegBitmapEncoderのフレームに描画したRenderTargetBitmapを追加、Saveメソッドでストリームに保存しています。

    //ASPX Page_Load
    Response.Clear();
    Response.ContentType = "image/jpeg";
    Response.Flush();
    // Memory Streamを作成
    MemoryStream ms = new MemoryStream();
    // Memory StreamにWPFで作成した画像を書き出す
    wpfClass.WriteJpeg(ms);
    ms.Flush();
    ms.Seek(0, SeekOrigin.Begin);
    Byte[] jpa = ms.ToArray();
    //Memory Streamから画像のバイト配列を取り出して、OutputStreamに書き出す
    Response.OutputStream.Write(jpa, 0, jpa.Length);
    Response.End();

この時、ASPXのResponse.OutputStreamに直接保存しようとすると、NotSupportExceptionではじかれてしまいますので、MemoryStreamをひとつ作って、JpegBitmapEncoderでの保存に使用、MemoryStreamからByte配列を取り出して、Response.OutputStreamに書き出しています。

このような感じで、ASP.NET内部でWPFを使用した画像生成もできるのですね~^^

まあ、STAで動作させることで、パフォーマンスは落ちるとは思えますが、System.Drawingだといろいろめんどくさい角を丸めた矩形が楽に描けるのがうれしい(それだけかい)
後は、Windows Server 2003だとGDI+でフォントがガクガクになってしまうのが、WPFだとWindows 7と同様に描画されるのもポイントでかいです。

Silverlightは先日から使ってましたが、WPFは今回初めてだったので、なんか新鮮です。

Pingbacks and trackbacks (1)+

コメントを書く

Month List