本記事では、ASP.NET MVC 5で任意のフォントをEmbedded Resource(埋め込まれたリソース)としてアプリに組み込み、そのフォントを使って文字を描画し動的に画像を生成する方法を紹介します。
まゆるさんジェネレーターではASP.NET MVC上でゴリゴリ画像を弄ることをやっています。まるゆさんジェネレーターは100PV/日くらいの極小規模のWebサービスということもあってWebアプリ内で画像を弄っているのですがもう少し規模が大きくなった場合のベストプラクティスはどういう方法なんでしょうかね……
過去に「ASP.NET MVC 5でサーバーサイドで動的に生成した画像を返す - 酢ろぐ!」を紹介しました。さらにその応用例として「ASP.NET MVC 5で動的に生成したグラデーションを描画した画像を返す - 酢ろぐ!」を紹介しました。
ASP.NET MVCプロジェクトを作成する
とりあえず、ASP.NET MVC 5プロジェクトを作成します。ここではプロジェクト名を「WebApplication1」という名前を付けました。
埋め込まれたリソースへは、{プロジェクト名}.{フォルダ名}.{リソース名}のような書式でアクセスします。なのでプロジェクト名がWebApplication1と異なる場合は適切なリソース名に変えてください。
任意のフォントをプロジェクトに追加する
任意のフォントをプロジェクトに追加しましょう。WebApplication1プロジェクトのApp_Data
フォルダにフォントを追加することにします。ソリューションエクスプローラーのApp_Dataフォルダを右クリックして、コンテキストメニューから[追加]、[既存の項目]を選択します。
ここでは鉄瓶ゴシックを使います。なかなかフリーで使用できるフォントで極太のものがない中、このフォントは極太でゴツゴツとしています。追加するフォントファイルは07鉄瓶ゴシック.otfという名前のままにしました。
フォントファイルを追加すると、おそらくビルドアクションはコンテンツになっているかと思います。ビルドアクションを埋め込まれたリソースに変更します。
これでプロジェクトへフォントの追加が完了しました。
Views/Home/Index.cshtmlで画像を表示できるようにする
ウィザードによって生成されたプロジェクトには色々と不要なものが混じっています。Views/Home/Index.cshtml
の中身を整理しましょう。極力要素を無くしてimgタグのみのシンプルな構成にしてみました。
@{ ViewBag.Title = "Home Page"; } <img src="~/home/image" class="img-responsive" />
http://localhost:5033/
などにアクセスされると、HomeコントローラーのIndexアクションが上記のhtmlを返します。
imgタグのsrcが~/home/image
となっているため、、HomeコントローラーのImageアクションでのレスポンスで返した画像が表示します。
HomeコントローラーにIndexアクションとImageアクションを実装する。
こちらにも色々と不要なものが混じっています。事前に説明しやすいようにHomeController.cs
のIndexアクション
以外削除してしまいます。
using System; using System.Linq; using System.Web; using System.Web.Mvc; namespace WebApplication1.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } } }
OSにインストールしていないフォントを使用するという処理でいうと、確かWinFormsとも同じ処理なので特にASP.NET MVCだからと気を付けるところは無いと思います。
OSにインストールされていないフォントを使用するには、PrivateFontCollectionクラス
を使います。このPrivateFontCollectionクラスは、OSにインストールされているフォントに依存せずにクライアントアプリ側で持っているフォントを使用する機能が提供されています。
PrivateFontCollectionにフォントを追加するには、ファイルパスを指定するか、バッファを渡すかのどちらかですので、ファイルパスを指定できないAzure Webサイトで動かすためにバッファを渡す方法を選択しました。
リソースからフォントファイルをbyte型配列のfontBuffer
に読み出します。fontBuffer
のポインタとバッファの長さをPrivateFontCollectionクラスのインスタンスに渡してフォントの登録をおこないます。ポインタを使うのでunsafe
を利用することになります*1。
// フォントを読み込む byte[] fontBuffer = null; var asm = System.Reflection.Assembly.GetExecutingAssembly(); using (var strm = asm.GetManifestResourceStream( "WebApplication1.App_Data.07鉄瓶ゴシック.otf")) { fontBuffer = new byte[strm.Length]; strm.Read(fontBuffer, 0, fontBuffer.Length); } // フォントデータの読み込みと登録 var pfc = new System.Drawing.Text.PrivateFontCollection(); unsafe { fixed (byte* p = fontBuffer) { pfc.AddMemoryFont((IntPtr)p, fontBuffer.Length); } }
後はいつも通りFontを使ってテキストを描画します。Fontインスタンスを取得するのに決め打ちでpfc.Families[0]
としていますが、本当はキチンとチェックした方が良いでしょう。
// 文字列を描画する using (var font = new System.Drawing.Font(pfc.Families[0], 110, FontStyle.Bold)) using (var fontBrush = new SolidBrush(Color.Red)) { g.DrawString("繊維喪失", font, fontBrush, new PointF(0, 130)); }
unsafeコードを使うために設定を変更する
ビルドが通らないので「アンセーフ コードの許可」にチェックを入れます。
ソリューションエクスプローラーのプロジェクト名の部分を右クリックし、コンテキストメニューの[プロパティ]を選択します。
[ビルド]タブの全般グループに[アンセーフ コードの許可]のチェックボックスがあるので有効します。
これでビルドエラーになっていた部分が通るようになります。
Imageアクションの実装が完了!
HomeコントローラーのImageアクションの全体像は下記のコードのような感じです。Graphicsクラスを使ってガリガリ描画しているところはWinFormsのそれと大差ないでしょう。
using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Web; using System.Web.Mvc; namespace WebApplication1.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult Image() { // フォントを読み込む byte[] fontBuffer = null; var asm = System.Reflection.Assembly.GetExecutingAssembly(); using (var strm = asm.GetManifestResourceStream( "WebApplication1.App_Data.07鉄瓶ゴシック.otf")) { fontBuffer = new byte[strm.Length]; strm.Read(fontBuffer, 0, fontBuffer.Length); } // フォントデータの読み込みと登録 var pfc = new System.Drawing.Text.PrivateFontCollection(); unsafe { fixed (byte* p = fontBuffer) { pfc.AddMemoryFont((IntPtr)p, fontBuffer.Length); } } // 画像を生成する var ms = new MemoryStream(); using (var image = new Bitmap(640, 480)) { using (var g = Graphics.FromImage(image)) { // 画像の背景色を描画する using (var fillBrush = new SolidBrush(Color.White)) { g.FillRectangle(fillBrush, 0, 0, 640, 480); } // 画像の枠色を描画する using (var borderBrush = new SolidBrush(Color.Red)) using (var borderPen = new Pen(borderBrush, 2)) { g.DrawRectangle(borderPen, 0, 0, 640, 480); } // 文字列を描画する using (var font = new System.Drawing.Font(pfc.Families[0], 110, FontStyle.Bold)) using (var fontBrush = new SolidBrush(Color.Red)) { g.DrawString("繊維喪失", font, fontBrush, new PointF(0, 130)); } } // 描画したビットマップをPNGとしてストリームへ保存する image.Save(ms, System.Drawing.Imaging.ImageFormat.Png); ms.Position = 0; } pfc.Dispose(); // サーバーサイドで実装した画像を返す return new FileStreamResult(ms, "image/png"); } } }
完成したのでデバッグ実行してみましょう。鉄瓶フォントの強調されながらも独特な優しさが分かるかと思います。
ただ、ちょっと文字の周りがギザギザになっているのが気になりますね……。
(おまけ)テキストのレンダリング
文字の周りがジャギっているのはアンチエイリアス処理が有効になっていないからです。境界(ふち)部分をスムーズにするためにはGraphicsのSmoothingModeプロパティ
とTextRenderingHintプロパティ
を別途指定する必要があります。
// 画像を生成する var ms = new MemoryStream(); using (var image = new Bitmap(640, 480)) { using (var g = Graphics.FromImage(image)) { // 塗りつぶし領域の境界線をスムーズ(アンチエイリアス処理)にする g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; // テキストレンダリング時にアンチエイリアス処理を有効にする g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; // (省略)
アンチエイリアスを有効にすると下記のように境界部分が綺麗になります。
ここまで書いて気付いたけど、繊維喪失じゃなくて戦維喪失だった。さらにおまけ。
やはりキルラキルネタは、フォントがラグランパンチじゃないと違和感が……
*1:この辺りはWin32API的な縛りなのかな?