using System; using System.Collections.Generic; using System.Text; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Diagnostics; using SharpDX; using SharpDX.Direct3D9; using Rectangle = System.Drawing.Rectangle; using Color = System.Drawing.Color; namespace FDK { /// /// 縦長・横長の画像を自動で折りたたんでテクスチャ化するCTexture。 /// 例えば、768x30 のテクスチャファイルが入力されたら、 /// 内部で256x90 など、2のべき乗サイズに収めるよう、内部でテクスチャ画像を自動的に折り返す。 /// 必要に応じて、正方形テクスチャにもする。 /// また、t2D描画は、その折り返しを加味して実行する。 /// public class CTextureAf : CTexture, IDisposable { /// /// 指定された画像ファイルから Managed テクスチャを作成する。 /// 利用可能な画像形式は、BMP, JPG, PNG, TGA, DDS, PPM, DIB, HDR, PFM のいずれか。 /// /// Direct3D9 デバイス。 /// 画像ファイル名。 /// テクスチャのフォーマット。 /// 画像の黒(0xFFFFFFFF)を透過させるなら true。 /// 解放漏れを検出しやすくするためのラベル。 /// テクスチャの作成に失敗しました。 public CTextureAf( Device device, string strファイル名, Format format, bool b黒を透過する, string _label = "") : this( device, strファイル名, format, b黒を透過する, Pool.Managed, _label ) { } /// /// 画像ファイルからテクスチャを生成する。 /// 利用可能な画像形式は、BMP, JPG, PNG, TGA, DDS, PPM, DIB, HDR, PFM のいずれか。 /// テクスチャのサイズは、画像のサイズ以上、かつ、D3D9デバイスで生成可能な最小のサイズに自動的に調節される。 /// その際、テクスチャの調節後のサイズにあわせた画像の拡大縮小は行わない。 /// その他、ミップマップ数は 1、Usage は None、イメージフィルタは Point、ミップマップフィルタは None になる。 /// /// Direct3D9 デバイス。 /// 画像ファイル名。 /// テクスチャのフォーマット。 /// 画像の黒(0xFFFFFFFF)を透過させるなら true。 /// テクスチャの管理方法。 /// 解放漏れを検出しやすくするためのラベル。 /// テクスチャの作成に失敗しました。 public CTextureAf( Device device, string strファイル名, Format format, bool b黒を透過する = false, Pool pool = Pool.Managed, string _label = "") { MakeTextureMain( device, strファイル名, null, format, b黒を透過する, pool, _label ); } /// /// 画像ファイルからテクスチャを生成する。 /// 利用可能な画像形式は、BMP, JPG, PNG, TGA, DDS, PPM, DIB, HDR, PFM のいずれか。 /// テクスチャのサイズは、画像のサイズ以上、かつ、D3D9デバイスで生成可能な最小のサイズに自動的に調節される。 /// その際、テクスチャの調節後のサイズにあわせた画像の拡大縮小は行わない。 /// その他、ミップマップ数は 1、Usage は None、イメージフィルタは Point、ミップマップフィルタは None になる。 /// /// Direct3D9 デバイス。 /// Bitmapデータ /// テクスチャのフォーマット。 /// 画像の黒(0xFFFFFFFF)を透過させるなら true。 /// テクスチャの管理方法。 /// 解放漏れを検出しやすくするためのラベル。 /// テクスチャの作成に失敗しました。 public CTextureAf(Device device, Bitmap _image, Format format, bool b黒を透過する = false, Pool pool = Pool.Managed, string _label = "") { MakeTextureMain(device, null, _image, format, b黒を透過する, pool, _label); } public void MakeTextureMain(Device device, string strファイル名, Bitmap _image, Format format, bool b黒を透過する, Pool pool, string _label = "") { if (!File.Exists(strファイル名) && _image == null) // #27122 2012.1.13 from: ImageInformation では FileNotFound 例外は返ってこないので、ここで自分でチェックする。わかりやすいログのために。 throw new FileNotFoundException(string.Format("ファイルが存在せず、Bitmapの引き渡しもありません。\n[{0}]", strファイル名)); bool b条件付きでサイズは2の累乗でなくてもOK = (device.Capabilities.TextureCaps & TextureCaps.NonPow2Conditional) != 0; bool bサイズは2の累乗でなければならない = (device.Capabilities.TextureCaps & TextureCaps.Pow2) != 0; bool b正方形でなければならない = (device.Capabilities.TextureCaps & TextureCaps.SquareOnly) != 0; // そもそもこんな最適化をしなくてよいのなら、さっさとbaseに処理を委ねて終了 if (!bサイズは2の累乗でなければならない && b条件付きでサイズは2の累乗でなくてもOK) { //Debug.WriteLine( Path.GetFileName( strファイル名 ) + ": 最適化は不要です。" ); if (_image == null) { base.MakeTexture(device, strファイル名, format, b黒を透過する, pool, _label); } else { base.MakeTexture(device, _image, format, b黒を透過する, pool, _label); } return; } Byte[] _txData = null; ImageInformation information; int orgWidth, orgHeight; if (_image == null) { _txData = File.ReadAllBytes(strファイル名); information = ImageInformation.FromMemory(_txData); orgWidth = information.Width; orgHeight = information.Height; } else { orgWidth = _image.Width; orgHeight = _image.Height; } int w = orgWidth, h = orgHeight, foldtimes; int nサポート可能な最大幅 = device.Capabilities.MaxTextureWidth; int nサポート可能な最大高 = device.Capabilities.MaxTextureHeight; if ( orgWidth * orgHeight > nサポート可能な最大幅 * nサポート可能な最大高 ) { throw new CTextureCreateFailedException( string.Format("テクスチャの画素数が大きすぎるため、お使いのPCでは指定の画像をテクスチャとして使用できません。[{0}]", strファイル名 ) ); } #if TEST_Direct3D9Ex pool = poolvar; #endif #region [ 折りたたみありで最適なテクスチャサイズがどうなるかを確認する(正方形にするかは考慮せず) ] if ( orgWidth >= orgHeight ) // 横長画像なら { this.b横長のテクスチャである = true; if ( !GetFoldedTextureSize( ref w, ref h, out foldtimes ) ) { //Debug.WriteLine( Path.GetFileName( strファイル名 ) + ": 最適化を断念。" ); if (_image == null) { base.MakeTexture(device, strファイル名, format, b黒を透過する, pool); } else { base.MakeTexture(device, _image, format, b黒を透過する, pool); } return; } } else // 縦長画像なら { this.b横長のテクスチャである = false; if ( !GetFoldedTextureSize( ref h, ref w, out foldtimes ) ) // 縦横入れ替えて呼び出し { //Debug.WriteLine( Path.GetFileName( strファイル名 ) + ": 最適化を断念。" ); if (_image == null) { base.MakeTexture(device, strファイル名, format, b黒を透過する, pool); } else { base.MakeTexture(device, _image, format, b黒を透過する, pool); } return; } } #endregion //Debug.WriteLine( Path.GetFileName( strファイル名 ) + ": texture最適化結果: width=" + w + ", height=" + h + ", 折りたたみ回数=" + foldtimes ); #region [ 折りたたみテクスチャ画像を作り、テクスチャ登録する ] // バイナリ(Byte配列)をBitmapに変換 Bitmap bmpOrg = null; if (_image == null) { MemoryStream mms = new MemoryStream(_txData); bmpOrg = new Bitmap(mms); mms.Close(); } else { bmpOrg = (Bitmap)_image.Clone(); // 参照渡しするとDisposeするのが怖くなるので、Cloneでとりあえず妥協 } Bitmap bmpNew = new Bitmap( w, h ); Graphics g = Graphics.FromImage( bmpNew ); for ( int n = 0; n <= foldtimes; n++ ) { if ( b横長のテクスチャである ) { int x = n * w; int currentHeight = n * orgHeight; int currentWidth = ( n < foldtimes ) ? w : orgWidth - x; Rectangle r = new Rectangle( x, 0, currentWidth, orgHeight ); Bitmap bmpTmp = bmpOrg.Clone( r, bmpOrg.PixelFormat ); // ここがボトルネックになるようなら、後日unsafeコードにする。 g.DrawImage( bmpTmp, 0, currentHeight, currentWidth, orgHeight ); bmpTmp.Dispose(); } else { int y = n * h; int currentWidth = n * orgWidth; int currentHeight = ( n < foldtimes ) ? h : orgHeight - y; Rectangle r = new Rectangle( 0, y, orgWidth, currentHeight ); Bitmap bmpTmp = bmpOrg.Clone( r, bmpOrg.PixelFormat ); // ここがボトルネックになるようなら、後日unsafeコードにする。 g.DrawImage( bmpTmp, currentWidth, 0, orgWidth, currentHeight ); bmpTmp.Dispose(); } }; if ( b黒を透過する ) { bmpNew.MakeTransparent( Color.Black ); } g.Dispose(); g = null; bmpOrg.Dispose(); bmpOrg = null; base.MakeTexture( device, bmpNew, format, b黒を透過する, pool, _label ); bmpNew.Dispose(); bmpNew = null; #endregion _orgWidth = orgWidth; _orgHeight = orgHeight; _foldtimes = foldtimes; this.sz画像サイズ = new Size( orgWidth, orgHeight ); } /// /// 横長画像を適切なサイズに折りたたんだときの最適テクスチャサイズを得る。 /// 縦長画像に対しては、width/heightを入れ替えて呼び出すこと。 /// /// /// /// /// private bool GetFoldedTextureSize( ref int width, ref int height, out int foldtimes ) { int orgWidth = width, orgHeight = height; #region [ widthが、2のべき乗からどれくらい溢れているか確認 ] int pow = 1; while ( orgWidth >= pow ) { pow *= 2; } pow /= 2; #endregion #region [ まず、2のべき乗からあふれる分を折り返して、2のべき乗の正方形サイズに収まるかを確認 ] foldtimes = ( orgWidth == pow ) ? 0 : 1; // 2のべき乗からの溢れがあれば、まずその溢れ分で1回折り畳む if ( foldtimes != 0 ) { //Debug.WriteLine( "powちょうどではないので、溢れあり。まずは1回折りたたむ。" ); // 試しに、widthをpowに切り詰め、1回折り返してみる。 // width>heightを維持しているなら、テクスチャサイズはより最適な状態になったということになる。 if ( pow <= orgHeight * 2 ) // 新width > 新heightを維持できなくなったなら { // 最適化不可とみなし、baseの処理に委ねる return false; } } #endregion #region [ width > height ではなくなるまで、折りたたみ続ける ] width = pow; height = orgHeight * 2; // 初期値=1回折りたたんだ状態 do { width /= 2; foldtimes = ( orgWidth / width ) + ( ( orgWidth % width > 0 ) ? 1 : 0 ) - 1; height = orgHeight * ( foldtimes + 1 ); } while ( width > height ); width *= 2; foldtimes = ( orgWidth / width ) + ( ( orgWidth % width > 0 ) ? 1 : 0 ) - 1; height = orgHeight * ( foldtimes + 1 ); #endregion return true; } /// /// テクスチャを 2D 画像と見なして描画する。 /// /// Direct3D9 デバイス。 /// 描画位置(テクスチャの左上位置の X 座標[dot])。 /// 描画位置(テクスチャの左上位置の Y 座標[dot])。 public new void t2D描画( Device device, int x, int y ) { #if TEST_FOLDTEXTURE base.t2D描画( device, x, y, 1f, rc全画像 ); #else for ( int n = 0; n <= _foldtimes; n++ ) { Rectangle r; if ( b横長のテクスチャである ) { int currentHeight = n * _orgHeight; r = new Rectangle( 0, currentHeight, this.rc全画像.Width, _orgHeight ); base.t2D描画( device, x + n * this.rc全画像.Width, y, 1f, r ); } else { int currentWidth = n * _orgWidth; r = new Rectangle( currentWidth, 0, _orgWidth, this.rc全画像.Height ); base.t2D描画( device, x, y + n * this.rc全画像.Height, 1f, r ); } } #endif } public new void t2D描画( Device device, int x, int y, Rectangle rc ) { Rectangle r; if ( b横長のテクスチャである ) { int beginFold = rc.X / this.rc全画像.Width; int endFold = ( rc.X + rc.Width ) / rc全画像.Width; for ( int i = beginFold; i <= endFold; i++ ) { if ( i > _foldtimes ) break; int newRcY = i * _orgHeight + rc.Y; int newRcX = ( i == beginFold ) ? ( rc.X % this.rc全画像.Width ) : 0; int newRcWidth = ( newRcX + rc.Width > rc全画像.Width ) ? rc全画像.Width - newRcX : rc.Width; r = new Rectangle( newRcX, newRcY, newRcWidth, rc.Height ); base.t2D描画( device, x, y, 1f, r ); int deltaX = ( i == beginFold ) ? ( i + 1 ) * rc全画像.Width - rc.X : rc全画像.Width; int newWidth = rc.Width - deltaX; x += deltaX; rc.Width = newWidth; } } else { int beginFold = rc.Y / this.rc全画像.Height; int endFold = ( rc.Y + rc.Height ) / rc全画像.Height; for ( int i = beginFold; i <= endFold; i++ ) { if ( i > _foldtimes ) break; int newRcX = i * _orgWidth + rc.X; int newRcY = ( i == beginFold ) ? ( rc.Y % this.rc全画像.Height ) : 0; int newRcHeight = ( newRcY + rc.Height > rc全画像.Height ) ? rc全画像.Height - newRcY : rc.Height; r = new Rectangle( newRcX, newRcY, rc.Width, newRcHeight ); base.t2D描画( device, x, y, 1f, r ); int deltaY = ( i == beginFold ) ? ( i + 1 ) * rc全画像.Height - rc.Y : rc全画像.Height; int newHeight = rc.Height - deltaY; y += deltaY; rc.Height = newHeight; } } } public new void t2D描画( Device device, float x, float y ) { t2D描画( device, (int) x, (int) y ); } public new void t2D描画( Device device, float x, float y, Rectangle rc ) { t2D描画( device, (int) x, (int) y, rc ); } #region [ private ] //----------------- private bool b横長のテクスチャである; /// /// 元画像のWidth /// private int _orgWidth; /// /// 元画像のHeight /// private int _orgHeight; /// /// 折りたたみ回数 /// private int _foldtimes; //----------------- #endregion } }