#region License /* FNA - XNA4 Reimplementation for Desktop Platforms * Copyright 2009-2020 Ethan Lee and the MonoGame Team * * Released under the Microsoft Public License. * See LICENSE for details. */ #endregion #region Using Statements using System; using System.IO; using System.Runtime.InteropServices; #endregion namespace Microsoft.Xna.Framework.Graphics { public class Texture2D : Texture { #region Public Properties public int Width { get; private set; } public int Height { get; private set; } public Rectangle Bounds { get { return new Rectangle(0, 0, Width, Height); } } #endregion #region Public Constructors public Texture2D( GraphicsDevice graphicsDevice, int width, int height ) : this( graphicsDevice, width, height, false, SurfaceFormat.Color ) { } public Texture2D( GraphicsDevice graphicsDevice, int width, int height, bool mipMap, SurfaceFormat format ) { if (graphicsDevice == null) { throw new ArgumentNullException("graphicsDevice"); } GraphicsDevice = graphicsDevice; Width = width; Height = height; LevelCount = mipMap ? CalculateMipLevels(width, height) : 1; // TODO: Use QueryRenderTargetFormat! if ( this is IRenderTarget && format != SurfaceFormat.Color && format != SurfaceFormat.Rgba1010102 && format != SurfaceFormat.Rg32 && format != SurfaceFormat.Rgba64 && format != SurfaceFormat.Single && format != SurfaceFormat.Vector2 && format != SurfaceFormat.Vector4 && format != SurfaceFormat.HalfSingle && format != SurfaceFormat.HalfVector2 && format != SurfaceFormat.HalfVector4 && format != SurfaceFormat.HdrBlendable ) { Format = SurfaceFormat.Color; } else { Format = format; } texture = GraphicsDevice.GLDevice.CreateTexture2D( Format, Width, Height, LevelCount, (this is IRenderTarget) ); } #endregion #region Public SetData Methods public void SetData(T[] data) where T : struct { SetData( 0, null, data, 0, data.Length ); } public void SetData( T[] data, int startIndex, int elementCount ) where T : struct { SetData( 0, null, data, startIndex, elementCount ); } public void SetData( int level, Rectangle? rect, T[] data, int startIndex, int elementCount ) where T : struct { if (data == null) { throw new ArgumentNullException("data"); } int x, y, w, h; if (rect.HasValue) { x = rect.Value.X; y = rect.Value.Y; w = rect.Value.Width; h = rect.Value.Height; } else { x = 0; y = 0; w = Math.Max(Width >> level, 1); h = Math.Max(Height >> level, 1); } int elementSize = Marshal.SizeOf(typeof(T)); GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); GraphicsDevice.GLDevice.SetTextureData2D( texture, Format, x, y, w, h, level, handle.AddrOfPinnedObject() + startIndex * elementSize, elementCount * elementSize ); handle.Free(); } public void SetDataPointerEXT( int level, Rectangle? rect, IntPtr data, int dataLength ) { if (data == IntPtr.Zero) { throw new ArgumentNullException("data"); } int x, y, w, h; if (rect.HasValue) { x = rect.Value.X; y = rect.Value.Y; w = rect.Value.Width; h = rect.Value.Height; } else { x = 0; y = 0; w = Math.Max(Width >> level, 1); h = Math.Max(Height >> level, 1); } GraphicsDevice.GLDevice.SetTextureData2D( texture, Format, x, y, w, h, level, data, dataLength ); } #endregion #region Public GetData Methods public void GetData(T[] data) where T : struct { GetData( 0, null, data, 0, data.Length ); } public void GetData( T[] data, int startIndex, int elementCount ) where T : struct { GetData( 0, null, data, startIndex, elementCount ); } public void GetData( int level, Rectangle? rect, T[] data, int startIndex, int elementCount ) where T : struct { if (data == null || data.Length == 0) { throw new ArgumentException("data cannot be null"); } if (data.Length < startIndex + elementCount) { throw new ArgumentException( "The data passed has a length of " + data.Length.ToString() + " but " + elementCount.ToString() + " pixels have been requested." ); } int subX, subY, subW, subH; if (rect == null) { subX = 0; subY = 0; subW = Width >> level; subH = Height >> level; } else { subX = rect.Value.X; subY = rect.Value.Y; subW = rect.Value.Width; subH = rect.Value.Height; } GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); GraphicsDevice.GLDevice.GetTextureData2D( texture, Format, Width >> level, Height >> level, level, subX, subY, subW, subH, handle.AddrOfPinnedObject(), startIndex, elementCount, Marshal.SizeOf(typeof(T)) ); handle.Free(); } #endregion #region Public Texture2D Save Methods public void SaveAsJpeg(Stream stream, int width, int height) { // Get the Texture2D pixels byte[] data = new byte[Width * Height * GetFormatSize(Format)]; GetData(data); FNAPlatform.SaveJPG( stream, width, height, Width, Height, data ); } public void SaveAsPng(Stream stream, int width, int height) { // Get the Texture2D pixels byte[] data = new byte[Width * Height * GetFormatSize(Format)]; GetData(data); FNAPlatform.SavePNG( stream, width, height, Width, Height, data ); } #endregion #region Public Static Texture2D Load Methods public static Texture2D FromStream(GraphicsDevice graphicsDevice, Stream stream) { if (stream.CanSeek && stream.Position == stream.Length) { stream.Seek(0, SeekOrigin.Begin); } // Read the image data from the stream int width, height, len; IntPtr pixels; FNAPlatform.TextureDataFromStreamPtr( stream, out width, out height, out pixels, out len ); // Create the Texture2D from the raw pixel data Texture2D result = new Texture2D( graphicsDevice, width, height ); result.SetDataPointerEXT( 0, null, pixels, len ); Marshal.FreeHGlobal(pixels); return result; } public static Texture2D FromStream( GraphicsDevice graphicsDevice, Stream stream, int width, int height, bool zoom ) { // Read the image data from the stream int realWidth, realHeight, len; IntPtr pixels; FNAPlatform.TextureDataFromStreamPtr( stream, out realWidth, out realHeight, out pixels, out len, width, height, zoom ); // Create the Texture2D from the raw pixel data Texture2D result = new Texture2D( graphicsDevice, realWidth, realHeight ); result.SetDataPointerEXT( 0, null, pixels, len ); Marshal.FreeHGlobal(pixels); return result; } #endregion #region Public Static Texture2D Extensions /// /// Loads image data from a given stream. /// /// /// This is an extension of XNA 4 and is not compatible with XNA. It exists to help with dynamically reloading /// textures while games are running. Games can use this method to read a stream into memory and then call /// SetData on a texture with that data, rather than having to dispose the texture and recreate it entirely. /// /// The stream from which to read the image data. /// Outputs the width of the image. /// Outputs the height of the image. /// Outputs the pixel data of the image, in non-premultiplied RGBA format. /// Preferred width of the resulting image data /// Preferred height of the resulting image data /// false to maintain aspect ratio, true to crop image public static void TextureDataFromStreamEXT( Stream stream, out int width, out int height, out byte[] pixels, int requestedWidth = -1, int requestedHeight = -1, bool zoom = false ) { FNAPlatform.TextureDataFromStream( stream, out width, out height, out pixels, requestedWidth, requestedHeight, zoom ); } // DDS loading extension, based on MojoDDS public static Texture2D DDSFromStreamEXT( GraphicsDevice graphicsDevice, Stream stream ) { // A whole bunch of magic numbers, yay DDS! const uint DDS_MAGIC = 0x20534444; const uint DDS_HEADERSIZE = 124; const uint DDS_PIXFMTSIZE = 32; const uint DDSD_CAPS = 0x1; const uint DDSD_HEIGHT = 0x2; const uint DDSD_WIDTH = 0x4; const uint DDSD_PITCH = 0x8; const uint DDSD_FMT = 0x1000; const uint DDSD_LINEARSIZE = 0x80000; const uint DDSD_REQ = ( DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_FMT ); const uint DDSCAPS_MIPMAP = 0x400000; const uint DDSCAPS_TEXTURE = 0x1000; const uint DDPF_FOURCC = 0x4; const uint DDPF_RGB = 0x40; const uint FOURCC_DXT1 = 0x31545844; const uint FOURCC_DXT3 = 0x33545844; const uint FOURCC_DXT5 = 0x35545844; // const uint FOURCC_DX10 = 0x30315844; const uint pitchAndLinear = ( DDSD_PITCH | DDSD_LINEARSIZE ); Texture2D result; // Begin BinaryReader, ignoring a tab! using (BinaryReader reader = new BinaryReader(stream)) { // File should start with 'DDS ' if (reader.ReadUInt32() != DDS_MAGIC) { return null; } // Texture info uint size = reader.ReadUInt32(); if (size != DDS_HEADERSIZE) { return null; } uint flags = reader.ReadUInt32(); if ((flags & DDSD_REQ) != DDSD_REQ) { return null; } if ((flags & pitchAndLinear) == pitchAndLinear) { return null; } int height = reader.ReadInt32(); int width = reader.ReadInt32(); reader.ReadUInt32(); // dwPitchOrLinearSize, unused reader.ReadUInt32(); // dwDepth, unused int levels = reader.ReadInt32(); // "Reserved" reader.ReadBytes(4 * 11); // Format info uint formatSize = reader.ReadUInt32(); if (formatSize != DDS_PIXFMTSIZE) { return null; } uint formatFlags = reader.ReadUInt32(); uint formatFourCC = reader.ReadUInt32(); uint formatRGBBitCount = reader.ReadUInt32(); uint formatRBitMask = reader.ReadUInt32(); uint formatGBitMask = reader.ReadUInt32(); uint formatBBitMask = reader.ReadUInt32(); uint formatABitMask = reader.ReadUInt32(); // dwCaps "stuff" uint caps = reader.ReadUInt32(); if ((caps & DDSCAPS_TEXTURE) == 0) { return null; } uint caps2 = reader.ReadUInt32(); if (caps2 != 0) { return null; } reader.ReadUInt32(); // dwCaps3, unused reader.ReadUInt32(); // dwCaps4, unused // "Reserved" reader.ReadUInt32(); // Mipmap sanity check if ((caps & DDSCAPS_MIPMAP) != DDSCAPS_MIPMAP) { levels = 1; } // Determine texture format SurfaceFormat format; int levelSize; int blockSize = 0; if ((formatFlags & DDPF_FOURCC) == DDPF_FOURCC) { if (formatFourCC == FOURCC_DXT1) { format = SurfaceFormat.Dxt1; blockSize = 8; } else if (formatFourCC == FOURCC_DXT3) { format = SurfaceFormat.Dxt3; blockSize = 16; } else if (formatFourCC == FOURCC_DXT5) { format = SurfaceFormat.Dxt5; blockSize = 16; } else { throw new NotSupportedException( "Unsupported DDS texture format" ); } levelSize = ( ((width > 0 ? ((width + 3) / 4) : 1) * blockSize) * (height > 0 ? ((height + 3) / 4) : 1) ); } else if ((formatFlags & DDPF_RGB) == DDPF_RGB) { if ( formatRGBBitCount != 32 || formatRBitMask != 0x00FF0000 || formatGBitMask != 0x0000FF00 || formatBBitMask != 0x000000FF || formatABitMask != 0xFF000000 ) { throw new NotSupportedException( "Unsupported DDS texture format" ); } format = SurfaceFormat.ColorBgraEXT; levelSize = (int) ( (((width * formatRGBBitCount) + 7) / 8) * height ); } else { throw new NotSupportedException( "Unsupported DDS texture format" ); } // Allocate/Load texture result = new Texture2D( graphicsDevice, width, height, levels > 1, format ); byte[] tex = null; if ( stream is MemoryStream && ((MemoryStream) stream).TryGetBuffer(out tex) ) { for (int i = 0; i < levels; i += 1) { result.SetData( i, null, tex, (int) stream.Seek(0, SeekOrigin.Current), levelSize ); stream.Seek( levelSize, SeekOrigin.Current ); levelSize = Math.Max( levelSize >> 2, blockSize ); } } else { for (int i = 0; i < levels; i += 1) { tex = reader.ReadBytes(levelSize); result.SetData( i, null, tex, 0, tex.Length ); levelSize = Math.Max( levelSize >> 2, blockSize ); } } // End BinaryReader } // Finally. return result; } #endregion } }