#region License /* FNA - XNA4 Reimplementation for Desktop Platforms * Copyright 2009-2022 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) { if (format == SurfaceFormat.ColorSrgbEXT) { if (FNA3D.FNA3D_SupportsSRGBRenderTargets(GraphicsDevice.GLDevice) == 0) { // Renderable but not on this device Format = SurfaceFormat.Color; } else { Format = format; } } else if ( 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 ) { // Not a renderable format period Format = SurfaceFormat.Color; } else { Format = format; } } else { Format = format; } texture = FNA3D.FNA3D_CreateTexture2D( GraphicsDevice.GLDevice, Format, Width, Height, LevelCount, (byte) ((this is IRenderTarget) ? 1 : 0) ); } #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"); } if (startIndex < 0) { throw new ArgumentOutOfRangeException("startIndex"); } if (data.Length < (elementCount + startIndex)) { throw new ArgumentOutOfRangeException("elementCount"); } 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)); int requiredBytes = (w * h * GetFormatSize(Format)) / GetBlockSizeSquared(Format); int availableBytes = elementCount * elementSize; if (requiredBytes > availableBytes) { throw new ArgumentOutOfRangeException("rect", "The region you are trying to upload is larger than the amount of data you provided."); } GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); FNA3D.FNA3D_SetTextureData2D( GraphicsDevice.GLDevice, texture, 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); } FNA3D.FNA3D_SetTextureData2D( GraphicsDevice.GLDevice, texture, 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; } int elementSizeInBytes = Marshal.SizeOf(typeof(T)); ValidateGetDataFormat(Format, elementSizeInBytes); GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); FNA3D.FNA3D_GetTextureData2D( GraphicsDevice.GLDevice, texture, subX, subY, subW, subH, level, handle.AddrOfPinnedObject() + (startIndex * elementSizeInBytes), elementCount * elementSizeInBytes ); handle.Free(); } #endregion #region Public Texture2D Save Methods public void SaveAsJpeg(Stream stream, int width, int height) { int quality; string qualityString = Environment.GetEnvironmentVariable("FNA_GRAPHICS_JPEG_SAVE_QUALITY"); if (string.IsNullOrEmpty(qualityString) || !int.TryParse(qualityString, out quality)) { quality = 100; // FIXME: What does XNA pick for quality? -flibit } int len = Width * Height * GetFormatSize(Format); IntPtr data = Marshal.AllocHGlobal(len); FNA3D.FNA3D_GetTextureData2D( GraphicsDevice.GLDevice, texture, 0, 0, Width, height, 0, data, len ); FNA3D.WriteJPGStream( stream, Width, Height, width, height, data, quality ); Marshal.FreeHGlobal(data); } public void SaveAsPng(Stream stream, int width, int height) { int len = Width * Height * GetFormatSize(Format); IntPtr data = Marshal.AllocHGlobal(len); FNA3D.FNA3D_GetTextureData2D( GraphicsDevice.GLDevice, texture, 0, 0, Width, height, 0, data, len ); FNA3D.WritePNGStream( stream, Width, Height, width, height, data ); Marshal.FreeHGlobal(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); } int width, height, len; IntPtr pixels = FNA3D.ReadImageStream( stream, out width, out height, out len ); Texture2D result = new Texture2D( graphicsDevice, width, height ); result.SetDataPointerEXT( 0, null, pixels, len ); FNA3D.FNA3D_Image_Free(pixels); return result; } public static Texture2D FromStream( GraphicsDevice graphicsDevice, Stream stream, int width, int height, bool zoom ) { if (stream.CanSeek && stream.Position == stream.Length) { stream.Seek(0, SeekOrigin.Begin); } int realWidth, realHeight, len; IntPtr pixels = FNA3D.ReadImageStream( stream, out realWidth, out realHeight, out len, width, height, zoom ); Texture2D result = new Texture2D( graphicsDevice, realWidth, realHeight ); result.SetDataPointerEXT( 0, null, pixels, len ); FNA3D.FNA3D_Image_Free(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 ) { if (stream.CanSeek && stream.Position == stream.Length) { stream.Seek(0, SeekOrigin.Begin); } int len; IntPtr pixPtr = FNA3D.ReadImageStream( stream, out width, out height, out len, requestedWidth, requestedHeight, zoom ); pixels = new byte[len]; Marshal.Copy(pixPtr, pixels, 0, len); FNA3D.FNA3D_Image_Free(pixPtr); } public static Texture2D DDSFromStreamEXT( GraphicsDevice graphicsDevice, Stream stream ) { Texture2D result; // Begin BinaryReader, ignoring a tab! using (BinaryReader reader = new BinaryReader(stream)) { int width, height, levels; bool isCube; SurfaceFormat format; Texture.ParseDDS( reader, out format, out width, out height, out levels, out isCube ); if (isCube) { throw new FormatException("This file contains cube map data!"); } // 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) { int levelSize = Texture.CalculateDDSLevelSize( width >> i, height >> i, format ); result.SetData( i, null, tex, (int) stream.Seek(0, SeekOrigin.Current), levelSize ); stream.Seek( levelSize, SeekOrigin.Current ); } } else { for (int i = 0; i < levels; i += 1) { tex = reader.ReadBytes(Texture.CalculateDDSLevelSize( width >> i, height >> i, format )); result.SetData( i, null, tex, 0, tex.Length ); } } // End BinaryReader } // Finally. return result; } #endregion } }