using System; using System.IO; using System.Collections.Generic; using System.Text; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms; using System.Reflection; using Microsoft.WindowsMobile.DirectX; using Microsoft.WindowsMobile.DirectX.Direct3D; using System.Runtime.InteropServices; // utility class for loading textures // Initial version, 1/20/2007, Stephen Hawley // Feel free to modify and redistribute, but please document your // changes and feed them back to me. // The problem that I have on my HTC phone is that the Direct3D driver crashes // when you try to use TextureLoader.FromBitamp(). I don't know the exact details, // but it looks to me like a bug in their routine to convert from bitmaps to // textures. // // This set of routines is meant to reimplement the TextureLoader functionality // but to actually work and to try to be as efficient as possible. The downside is // that I need to have the entire bitmap and the entire texture in memory at the same // time. // // One difference between the TextureLoader class and mine is that you specify the // Format that you want to use. You can always choose a Format that will fail, so // it's in your best interest to only use a valid Format for your device. Fortunately, // this is easy to figure out now. // // Note that the static utility method, GetValidFormats can be pretty slow since it's // exhaustive. One feature of this class is that when you construct one for a give device, // it will cache the valid Formats for the device. Note that when you reload your textures, // you will also have to make a new ReliableTextureLoader class. // // On bitmap conversion - // there is a two dimensional matrix of pixel format conversion. One axis is all // possible Format values, the other axis is all possible PixelFormat values. // My approach to doing the conversion is to get a delegate to convert a scanline // and write an individual coverter for each element in the table. I chose this // approach because it keeps the parameter count low - only three per converter, // it allows the delegates to be static - no overhead in a this pointer, and it lets // you concentrate of the actual task at hand - pixel conversions. // // Others may choose to contribute converters, and someday the switch should probably // be turned into a table lookup. // // On my device, I only have 2 valid Formats and the available four Compact Framework // PixelFormats that are supported, so that's 8 conversion functions. I haven't tested // all 8 methods yet, so I'm not sure I got all the bit-banging right in the conversions. // // Where possible, I used tables to drive the bit up-scaling, hoping the JIT compiler // will have a field day. // // You need to supply the Bitmap's PixelFormat because LockBits needs it and it isn't provided // as a Property in Bitmap. This is a real bummer, but it's a reasonable concession. // In a game or other 3D app, you should really know a priori what your bitmap format is. namespace Microsoft.Samples.MD3DM { class ReliableTextureLoader { #region FormatInformationTables private static Format[] _allFormats = new Format[] { Format.A1R5G5B5, Format.A4R4G4B4, Format.A8, Format.A8P8, Format.A8R3G3B2, Format.A8R8G8B8, Format.D15S1, Format.D16, Format.D24S8, Format.D24X4S4, Format.D24X8, Format.D32, Format.Dxt1, Format.Dxt2, Format.Dxt3, Format.Dxt4, Format.Dxt5, Format.Index16, Format.Index32, Format.P8, Format.R3G3B2, Format.R5G6B5, Format.R8G8B8, Format.Unknown, Format.UYVY, Format.VertexData, Format.X1R5G5B5, Format.X4R4G4B4, Format.X8R8G8B8, Format.YUY2 }; private static byte[] _alphaDepths = new byte[] { 1, 4, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // I don't know what all of these formats mean // so I chose to simply guess at the lengths // I doubt any of the wrong ones are on mobile devices private static byte[] _allDepths = new byte[] { 16, 16, 8, 16, 16, 32, 16, 16, 32, 32, 32, 32, 16, /* wrong */ 16, // wrong 16, /* wrong */ 16, // wrong 16, /* wrong */ 16, 32, 8, 8, 16, 24, 0, // wrong 32, /* wrong */ 32, // wrong 16, 16, 32, 16 // wrong }; #endregion // for a given device, find all the valid texture formats public static Format[] GetValidFormats(Device d) { List l = new List(); foreach (Format f in _allFormats) { Texture t; try { t = null; t = new Texture(d, 1, 1, 0, Usage.Lockable | Usage.Texture, f, Pool.SystemMemory); l.Add(f); t.Dispose(); } catch { } } return l.ToArray(); } // given a Format, return the depth of its alpha channel public static int AlphaDepth(Format f) { int i = Array.IndexOf(_allFormats, f); if (i < 0) throw new ArgumentException("unknown format", "f"); return (int)_alphaDepths[i]; } // given a Format, return whether or not it has alpha public static bool HasAlpha(Format f) { return AlphaDepth(f) > 0; } // given a Format, return the bit depth of it public static int Depth(Format f) { int i = Array.IndexOf(_allFormats, f); if (i < 0) throw new ArgumentException("unknown format", "f"); return (int)_allDepths[i]; } private Device _d; Format[] _validFormats; public ReliableTextureLoader(Device d) { if (d == null) throw new ArgumentNullException(); _d = d; _validFormats = GetValidFormats(_d); } // best in this case is the same format // as the device's backing store. I'm assuming // that the math is whole lot faster public Format GetBestFormat() { int i = Array.IndexOf(_validFormats, _d.PresentationParameters.BackBufferFormat); if (i >= 0) return _validFormats[i]; return _validFormats[0]; // punt } // get the smallest Format that also has alpha public Format GetSmallestFormatWithAlpha() { List l = new List(); foreach (Format f in _validFormats) { if (HasAlpha(f)) l.Add(f); } if (l.Count == 0) return Format.Unknown; if (l.Count == 1) return l[0]; Format bestFormat = Format.Unknown; int smallestDepth = 128; foreach (Format f in l) { int depth = Depth(f); if (depth < smallestDepth) { smallestDepth = depth; bestFormat = f; } } if (bestFormat != Format.Unknown) return bestFormat; return l[0]; // should never happen } // return true if f is a valid format for this device public bool IsValidFormat(Format f) { return Array.IndexOf(_validFormats, f) >= 0; } public Format[] ValidFormats { get { return _validFormats; } } #region PixelFormatConversion [DllImport("win32.dll")] private extern static void CopyMemory(IntPtr dst, IntPtr src, int length); private delegate void CovertScanlineProc(IntPtr src, IntPtr dst, int width); private static void Format32bppRgbTo32bgra(IntPtr src, IntPtr dst, int width) { CopyMemory(dst, src, width * 4); } private static void Format24bppRgbTo32bgra(IntPtr src, IntPtr dst, int width) { unsafe { byte* s = (byte*)src; byte* d = (byte*)dst; for (int i = 0; i < width; i++) { *d++ = *s++; // b *d++ = *s++; // g *d++ = *s++; // r *d++ = (byte)255; // a } } } static byte[] _fiveBitsTo8Bits = new byte[] { 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248 }; static byte[] _sixBitsTo8Bits = new byte[] { 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152, 156, 160, 164, 168, 172, 176, 180, 184, 188, 192, 196, 200, 204, 208, 212, 216, 220, 224, 228, 232, 236, 240, 244, 248, 252 }; private static void Format16bppRgb565To32bgra(IntPtr src, IntPtr dst, int width) { unsafe { ushort* s = (ushort *)src; byte* d = (byte *)dst; for (int i = 0; i < width; i++) { int val = (int)*s++; *d++ = _fiveBitsTo8Bits[val >> 11]; *d++ = _sixBitsTo8Bits[(val >> 5) & 0x3f]; *d++ = _fiveBitsTo8Bits[val & 0x1f]; *d++ = 255; } } } private static void Format16bppRgb555To32bgra(IntPtr src, IntPtr dst, int width) { unsafe { ushort* s = (ushort*)src; byte* d = (byte*)dst; for (int i = 0; i < width; i++) { int val = (int)*s++; *d++ = _fiveBitsTo8Bits[val >> 11]; *d++ = _fiveBitsTo8Bits[(val >> 6) & 0x1f]; *d++ = _fiveBitsTo8Bits[(val >> 1) & 0x1f]; *d++ = 255; } } } private static void Format32bppRgbTo16bgr(IntPtr src, IntPtr dst, int width) { unsafe { ushort* d = (ushort*)dst; byte* s = (byte*)src; for (int i = 0; i < width; i++) { byte b = *s++; byte g = *s++; byte r = *s++; *d++ = (ushort)(((r & 0xf8) << 8) | ((g & 0xfe) << 3) | (b >> 3)); s++; } } } private static void Format24bppRgbTo16bgr(IntPtr src, IntPtr dst, int width) { unsafe { ushort* d = (ushort*)dst; byte* s = (byte*)src; for (int i = 0; i < width; i++) { byte b = *s++; byte g = *s++; byte r = *s++; *d++ = (ushort)(((r & 0xf8) << 8) | ((g & 0xfe) << 3) | (b >> 3)); } } } private static void Format16bppRgb565To16bgr(IntPtr src, IntPtr dst, int width) { CopyMemory(dst, src, width * 2); } private static void Format16bppRgb555To16bgr(IntPtr src, IntPtr dst, int width) { unsafe { ushort* s = (ushort*)src; ushort* d = (ushort*)dst; for (int i = 0; i < width; i++) { *d++ = (ushort)((*s & 0xffC0) | ((*s & 0x003e) >> 1)); s++; } } } private ReliableTextureLoader.CovertScanlineProc GetScanlineConverter(Format f, PixelFormat bitmapFormat) { switch (bitmapFormat) { case PixelFormat.Format32bppRgb: switch (f) { case Format.A8R8G8B8: return new ReliableTextureLoader.CovertScanlineProc(Format32bppRgbTo32bgra); case Format.R5G6B5: return new ReliableTextureLoader.CovertScanlineProc(Format32bppRgbTo16bgr); default: break; } break; case PixelFormat.Format24bppRgb: switch (f) { case Format.A8R8G8B8: return new ReliableTextureLoader.CovertScanlineProc(Format24bppRgbTo32bgra); case Format.R5G6B5: return new ReliableTextureLoader.CovertScanlineProc(Format24bppRgbTo16bgr); default: break; } break; case PixelFormat.Format16bppRgb565: switch (f) { case Format.A8R8G8B8: return new ReliableTextureLoader.CovertScanlineProc(Format16bppRgb565To32bgra); case Format.R5G6B5: return new ReliableTextureLoader.CovertScanlineProc(Format16bppRgb565To16bgr); default: break; } break; case PixelFormat.Format16bppRgb555: switch (f) { case Format.A8R8G8B8: return new ReliableTextureLoader.CovertScanlineProc(Format16bppRgb555To32bgra); case Format.R5G6B5: return new ReliableTextureLoader.CovertScanlineProc(Format16bppRgb555To16bgr); default: break; } break; default: break; } return null; } #endregion // create a Texture in format f, from the given bitmap in the given PixelFormat, // converting the data as needed public Texture FromBitmap(Format f, Bitmap bm, PixelFormat bitmapPixelFormat) { if (!IsValidFormat(f)) throw new ArgumentException("Invalid format for device"); if (bm == null) throw new ArgumentNullException("bm"); // make the texture Texture t = new Texture(_d, bm.Width, bm.Height, 0, Usage.Texture | Usage.Lockable, f, Pool.SystemMemory); if (t == null) return null; // get the right converter ReliableTextureLoader.CovertScanlineProc converter = GetScanlineConverter(f, bitmapPixelFormat); if (proc == null) throw new NotImplementedException("No pixel format conversion proc available."); // get access to the scanline BitmapData bd = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, bitmapPixelFormat); if (bd == null) throw new ArgumentException("unable to get scanline acess to bitmap", "bm"); int destStride = 0; GraphicsStream gs = null; try { // I've seen this throw gs = t.LockRectangle(0, LockFlags.None, out destStride); IntPtr srcPtr = bd.Scan0; IntPtr dstPtr = gs.InternalData; for (int y = 0; y < bm.Height; y++) { converter(srcPtr, dstPtr, bm.Width); srcPtr = new IntPtr(srcPtr.ToInt32() + bd.Stride); dstPtr = new IntPtr(dstPtr.ToInt32() + destStride); } } finally { t.UnlockRectangle(0); bm.UnlockBits(bd); } return t; } public Texture FromStream(Format f, Stream stm, PixelFormat pf) { using (Bitmap bm = new Bitmap(stm)) { return FromBitmap(f, bm, pf); } } } }