Posted at
ASP.NETDay 24

ASP.NET でお絵かき

More than 3 years have passed since last update.

System.Drawing 名前空間 の注意を見てもらうと ASP.NET では使うなとなっている事を確認できるでしょう。

要するに、ASP .NET の中で System.Drawing を使ったお絵かきはしてはいけません。

HTML5 に対応するクライアントが期待できてブラウザ内で Canvas とかにjavascriptでサラッと書いちゃえば良いならなんとでもなるし問題は解決済みです、気にする必要もありません。

しかし、ですが、

ビジネス的な理由により、ユーザーの目に入るのがグラフの画像であれば良いけど、javascriptで描画するのに使われるような正確なデータをサーバから出してはいけない場合(何とかの値動きとか、データはデータとして売るビジネスとの兼ね合いでデータを元に画像を作ってユーザーに見せるのは良いがデータそのものは出せない)とか、画像を転載転用されるのを防ぐ為にユーザーを識別する情報を画像に透かしで入れたい等、クライアントサイドでの解決が本質的に無理な事等もあったりします。

クライアントにお金が無くて、実行環境がWindows XPやそれについてる古いIEで Canvas とか使えないとかはさすがにもう無いよね?と思いますけど。


System.Drawing はなぜ使ってはいけないのか、何なら使っていいのか

System.Drawing の説明の冒頭にもあるように、System.Drawing での描画機能は GDI+ に依存した描画技術です。 GDI+ および、 GDI は古き良き Windows でウインドウ内容の描画に使われた技術です。

結果として GDI にはメインSTAから使用しなければならない等、諸々の制約がありますし、描画関連で使われるペンやブラシ等のリソースはスレッドセーフではない管理がされています。

要するに ASP.NETのようなウインドウを持たないし、マルチスレッドで非同期に実行される環境で使えるような技術ではないので使っても動く気もしないし、動いたとしたら奇跡ぐらいの位置づけになります。

では何が使えるのかです。Windows での新しい世代の描画APIと言われて Direct X やそれの関連技術を思いつくのは自然ですよね。これならば一部制約はあるもののGDIのような問題はありません。

Direct 2D を使いましょう。っていうのは 2013年から言われている事です。

Using Direct2D from a service in C#

そして上記のBLOG記事でも「the WPF (Windows Presentation Foundation) graphics classes are unsupported in a service context. 」と言及されていますが Direct X の技術に根差している WPF はサービスでは利用できませんので WPF に依存もしてはいけません。

ここで Direct Xに関係する .NET 系テクノロジーのWPFじゃない部分を確認しましょう。


  • Managed Direct X「discon」

  • XNA「discon」

  • Windows API Code Pack 「discon」

なんだこの墓場は!

なんだこの墓場は!


大事な事なので2回言いました


そこに颯爽と現れた Win2D を .NET Framework 4.5.2 な ASP.NET MVC5 なアプリに早速追加

'.NETFramework,Version=v4.5.2' を対象とするプロジェクト 'WebApplication6' に関して、パッケージ 'Win2D.win81.1.11.0' の依存関係情報の収集を試行しています

DependencyBehavior 'Lowest' でパッケージ 'Win2D.win81.1.11.0' の依存関係の解決を試行しています
パッケージ 'Win2D.win81.1.11.0' をインストールするアクションを解決しています
パッケージ 'Win2D.win81.1.11.0' をインストールするアクションが解決されました
インストールに失敗しました。ロールバックします...
パッケージ 'Win2D.win81.1.11.0 : ' はプロジェクト 'WebApplication6' に存在しません
パッケージ 'Win2D.win81.1.11.0 : ' はフォルダー 'c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\packages' に存在しません
パッケージ 'Win2D.win81 1.11.0' をインストールできませんでした。このパッケージを '.NETFramework,Version=v4.5.2' を対象とするプロジェクトにインストールしようとしていますが、そのフレームワークと互換性があるアセンブリ参照またはコンテンツ ファイルがパッケージに含まれていません。詳細については、パッケージの作成者に問い合わせてください。
========== 終了 ==========

やったね!

ここまで読んでくれた読者のみなさん、本当にありがとう。

「できませんしやっちゃいけません。」これが結論です。


諦められない人向けの不完全な解決

今日の advent calendar が埋まってないらしいから書き始めた記事なので不完全な解決で申し訳ない。許して。

PM> Install-Package SharpDX.Direct2D1

'.NETFramework,Version=v4.5.2' を対象とするプロジェクト 'WebApplication6' に関して、パッケージ 'SharpDX.Direct2D1.2.6.3' の依存関係情報の収集を試行しています
DependencyBehavior 'Lowest' でパッケージ 'SharpDX.Direct2D1.2.6.3' の依存関係の解決を試行しています
パッケージ 'SharpDX.Direct2D1.2.6.3' をインストールするアクションを解決しています
パッケージ 'SharpDX.Direct2D1.2.6.3' をインストールするアクションが解決されました
パッケージ 'SharpDX.2.6.3' をフォルダー 'c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\packages' に追加しています
パッケージ 'SharpDX.2.6.3' をフォルダー 'c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\packages' に追加しました
パッケージ 'SharpDX.2.6.3' を 'packages.config' に追加しました
スクリプト ファイル 'c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\packages\SharpDX.2.6.3\tools\Install.ps1' を実行しています
Installing [SharpDX] to project [c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\WebApplication6\WebApplication6.csproj]
'SharpDX 2.6.3' が WebApplication6 に正常にインストールされました
パッケージ 'SharpDX.DXGI.2.6.3' をフォルダー 'c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\packages' に追加しています
パッケージ 'SharpDX.DXGI.2.6.3' をフォルダー 'c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\packages' に追加しました
パッケージ 'SharpDX.DXGI.2.6.3' を 'packages.config' に追加しました
スクリプト ファイル 'c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\packages\SharpDX.DXGI.2.6.3\tools\Install.ps1' を実行しています
Installing [SharpDX.DXGI] to project [c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\WebApplication6\WebApplication6.csproj]
'SharpDX.DXGI 2.6.3' が WebApplication6 に正常にインストールされました
パッケージ 'SharpDX.Direct2D1.2.6.3' をフォルダー 'c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\packages' に追加しています
パッケージ 'SharpDX.Direct2D1.2.6.3' をフォルダー 'c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\packages' に追加しました
パッケージ 'SharpDX.Direct2D1.2.6.3' を 'packages.config' に追加しました
スクリプト ファイル 'c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\packages\SharpDX.Direct2D1.2.6.3\tools\Install.ps1' を実行しています
Installing [SharpDX.Direct2D1] to project [c:\users\kazuk\documents\visual studio 2015\Projects\WebApplication6\WebApplication6\WebApplication6.csproj]
'SharpDX.Direct2D1 2.6.3' が WebApplication6 に正常にインストールされました

とかおもむろに SharpDX.Direct2D1 を NuGet からとってきて

        public ActionResult Image()

{
using (var d2dFactory = new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.Information))
using (var wicFactory = new SharpDX.WIC.ImagingFactory())
using (var dwrFactory = new SharpDX.DirectWrite.Factory(SharpDX.DirectWrite.FactoryType.Shared))
{

var targetProps = new SharpDX.Direct2D1.RenderTargetProperties(SharpDX.Direct2D1.RenderTargetType.Software,
new SharpDX.Direct2D1.PixelFormat(SharpDX.DXGI.Format.B8G8R8A8_UNorm, SharpDX.Direct2D1.AlphaMode.Premultiplied),
0.0f,
0.0f,
SharpDX.Direct2D1.RenderTargetUsage.None,
SharpDX.Direct2D1.FeatureLevel.Level_DEFAULT);

using (var bitmap = new SharpDX.WIC.Bitmap(wicFactory,
100,
100,
SharpDX.WIC.PixelFormat.Format32bppPRGBA,
SharpDX.WIC.BitmapCreateCacheOption.CacheOnLoad))
using (var target = new SharpDX.Direct2D1.WicRenderTarget(d2dFactory, bitmap, targetProps))
using (var redBrush = new SharpDX.Direct2D1.SolidColorBrush(target, SharpDX.Color.Red))
{
target.BeginDraw();
target.Clear(SharpDX.Color4.White);
target.DrawLine(new SharpDX.Vector2(0.0f, 0.0f), new SharpDX.Vector2(50.0f, 50.0f), redBrush, 2.0f);
target.EndDraw();

var memStream = new MemoryStream();
using (var stream = new SharpDX.WIC.WICStream(wicFactory, memStream))
using (var pngEncoder = new SharpDX.WIC.PngBitmapEncoder(wicFactory, stream))
using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(pngEncoder))
{

frameEncode.Initialize();
frameEncode.SetSize(100, 100);
var pngPixForm = SharpDX.WIC.PixelFormat.FormatDontCare;
frameEncode.SetPixelFormat(ref pngPixForm);
frameEncode.WriteSource(bitmap);
frameEncode.Commit();

memStream.Seek(0L, SeekOrigin.Begin);
return File(memStream, "image/png");
}
}
}
}

てな感じで WIC な Render Target こさえて BeginDraw ... EndDraw までで色々描画して png に吐く事でサポートされている方法で描画できる。

はず。なんですが WicRenderTarget の作成で invalid argument になってこのコードは落ちます。


まとめ


  1. ASP.NET では System.Drawing でのグラフィック描画はサポートされない

  2. DirectDraw とか使えになってるけど Win2D 含めて Microsoft の公式ライブラリとかは使えない

  3. SharpDX とかの Direct X のラッパーライブラリとか使うとなんとかする事ができる

  4. ただし、 Direct X 力が足りないと死ぬ(死んだ

  5. Visual Studio 2015 のグラフィック系デバッグ機能強化されたらしいけど、むしろ使い方解らないので VS2013 で倒せた問題が倒せないの何とかしたい

  6. 思い付きでアドベントカレンダー当日参加とかするもんじゃない。