#region Licence // //This file is part of ArcEngine. //Copyright (C)2008-2009 Adrien Hémery ( iliak@mimicprod.net ) // //ArcEngine is free software: you can redistribute it and/or modify //it under the terms of the GNU General Public License as published by //the Free Software Foundation, either version 3 of the License, or //any later version. // //ArcEngine is distributed in the hope that it will be useful, //but WITHOUT ANY WARRANTY; without even the implied warranty of //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //GNU General Public License for more details. // //You should have received a copy of the GNU General Public License //along with Foobar. If not, see . // #endregion using System; using System.ComponentModel; using System.Drawing; using System.IO; using System.Runtime.InteropServices; using Imaging = System.Drawing.Imaging; using TK = OpenTK.Graphics.OpenGL; using ArcEngine.Storage; using System.Collections.Generic; namespace ArcEngine.Graphic { /// /// Base class for texture /// public abstract class Texture : IDisposable { /// /// /// public Texture() { Handle = -1; if (InUse == null) InUse = new List(); InUse.Add(this); } /// /// Destructor /// ~Texture() { if (!IsDisposed) { //throw new Exception(this + " not disposed, Call Dispose() !!"); System.Windows.Forms.MessageBox.Show(this + " not disposed, Call Dispose() !!"); } } /// /// Dispose resources /// public abstract void Dispose(); /// /// Generate mipmap /// public void GenerateMipmap() { Display.Texture = this; TK.GL.GenerateMipmap((TK.GenerateMipmapTarget)Target); } #region Image IO /// /// Load an image from bank and convert it to a texture /// /// Reference face /// File name to load /// True if success or false if something went wrong protected bool LoadImage(TextureTarget target, string filename) { using (Stream stream = ResourceManager.Load(filename)) return FromStream(target, stream); } /// /// Load a texture from a stream (ie resource files) /// /// Reference face /// Stream handle /// True on success protected bool FromStream(TextureTarget target, Stream stream) { if (stream == null) return false; Bitmap bm = new Bitmap(stream); bool ret = FromBitmap(target, bm); if (bm != null) bm.Dispose(); return ret; } /// /// Loads a texture from a Bitmap /// /// Reference face /// Bitmap handle /// True on success protected bool FromBitmap(TextureTarget target, Bitmap bitmap) { if (bitmap == null) return false; Display.Texture = this; SetSize(target, bitmap.Size); SetData(target, bitmap, Point.Empty); return true; } /// /// Loads a Png picture from a byte[] /// /// Reference face /// Binary of a png file /// True if successful, or false protected bool LoadImage(TextureTarget target, byte[] data) { if (data == null) return false; MemoryStream stream = new MemoryStream(data); bool ret = FromStream(target, stream); stream.Dispose(); return ret; } /// /// Save the texture to the disk as a PNG file /// /// Reference face /// Name of the texture on the disk /// True if successful or false if an error occured protected bool SaveToDisk(TextureTarget target, string name) { if (string.IsNullOrEmpty(name)) return false; Bitmap bm = ToBitmap(target, new Rectangle(Point.Empty, Size)); if (bm == null) return false; bm.Save(name, Imaging.ImageFormat.Png); bm.Dispose(); return true; } /// /// Convert the texture to a Bitmap /// /// Reference face /// Rectangle bounds /// Bitmap handle or null protected Bitmap ToBitmap(TextureTarget target, Rectangle rectangle) { if (!Lock(target, ImageLockMode.ReadOnly, rectangle)) return null; Bitmap bm = new Bitmap(rectangle.Width, rectangle.Height); System.Drawing.Imaging.BitmapData bmd = bm.LockBits(rectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); System.Runtime.InteropServices.Marshal.Copy(Data, 0, bmd.Scan0, Data.Length); bm.UnlockBits(bmd); Unlock(target); return bm; } /// /// Save the texture as a PNG image in the bank /// /// Reference face /// Storage handle /// Asset name in the bank /// True on success protected bool SaveToStorage(TextureTarget target, StorageBase storage, string assetname) { if (storage == null || string.IsNullOrEmpty(assetname)) return false; // Create tmp bitmap using (Bitmap bm = new Bitmap(Size.Width, Size.Height)) { // Lock texture if (!Lock(target, ImageLockMode.ReadOnly)) return false; // Copy texture to the bitmap System.Drawing.Imaging.BitmapData bmd = bm.LockBits(new Rectangle(Point.Empty, Size), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); System.Runtime.InteropServices.Marshal.Copy(Data, 0, bmd.Scan0, Data.Length); bm.UnlockBits(bmd); // Unlock texture Unlock(target); // Save bitmap to a stream //using (Stream stream = new MemoryStream()) using (Stream stream = storage.OpenFile(assetname, FileAccess.Write)) { // Save bitmap to the stream bm.Save(stream, System.Drawing.Imaging.ImageFormat.Png); // Rewind stream //stream.Seek(0, SeekOrigin.Begin); // Save to bank //ResourceManager.SaveAsset(bankname, assetname, stream); } } return true; } #endregion /// /// Sets the size of the texture /// /// Reference face /// Desired size protected void SetSize(TextureTarget target, Size size) { Size = size; // Trace.WriteDebugLine("[Texture2D] : Resize() {0}", this); Lock(target, ImageLockMode.WriteOnly, new Rectangle(Point.Empty, size)); Data = null; Unlock(target); } #region Blitting /// /// Blits a Bitmap on the texture /// /// Reference face /// Bitmap handle /// Location on the texture protected void SetData(TextureTarget target, Bitmap bitmap, Point location) { if (bitmap == null) { Trace.WriteDebugLine("[Texture2D] : SetData() failed. bitmap == null - {0}", this); return; } Imaging.BitmapData bmdata = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), Imaging.ImageLockMode.ReadOnly, Imaging.PixelFormat.Format32bppArgb); byte[] data = new byte[bitmap.Size.Width * bitmap.Size.Height * 4]; Marshal.Copy(bmdata.Scan0, data, 0, bitmap.Size.Width * bitmap.Size.Height * 4); bitmap.UnlockBits(bmdata); Display.GetLastError("before bind texture"); Display.Texture = this; Display.GetLastError("after bind texture"); Display.GetLastError("before TexSubImage2D"); TK.GL.TexSubImage2D((TK.TextureTarget) target, 0, location.X, location.Y, bitmap.Width, bitmap.Height, (TK.PixelFormat) PixelFormat, TK.PixelType.UnsignedByte, data); Display.GetLastError("after TexSubImage2D"); } #endregion #region Locking / unlocking /// /// Locks the bitmap texture to system memory /// /// Reference mode /// Access mode /// True if locked, or false if an error occured protected bool Lock(TextureTarget target, ImageLockMode mode) { return Lock(target, mode, new Rectangle(Point.Empty, Size)); } /// /// Locks the bitmap texture to system memory /// /// Reference mode /// Access mode /// Zone to lock /// True if locked, or false if an error occured protected bool Lock(TextureTarget target, ImageLockMode mode, Rectangle rectangle) { // No texture bounds if (Handle == -1 || IsLocked) { Trace.WriteDebugLine("[Texture] : Lock() failed. Lockmode = {0} - {1}", mode.ToString(), this); return false; } // Trace.WriteDebugLine("[Texture] : Lock() Lockmode = {0} - {1}", mode.ToString(), this); Data = new byte[rectangle.Width * rectangle.Height * 4]; LockMode = mode; IsLocked = true; if (mode == ImageLockMode.WriteOnly) return true; Display.Texture = this; // Get the whole texture if (rectangle == new Rectangle(Point.Empty, Size)) { TK.GL.GetTexImage((TK.TextureTarget)target, 0, (TK.PixelFormat)PixelFormat, TK.PixelType.UnsignedByte, Data); } else { //TODO: Use Pixel Buffer Object instead TK.GL.ReadPixels(rectangle.Left, rectangle.Top, rectangle.Width, rectangle.Height, (TK.PixelFormat)PixelFormat, TK.PixelType.UnsignedByte, Data); } LockBound = rectangle; return true; } /// /// Unlocks the texture's bitmap from system memory. /// /// Reference face protected void Unlock(TextureTarget target) { if (!IsLocked) { IsLocked = false; Trace.WriteDebugLine("[Texture2D] : Lock() failed. Already locked - {0}", this); return; } IsLocked = false; if (LockMode == ImageLockMode.ReadOnly) return; Display.Texture = this; // The below is almost OK. The problem is the GL_RGBA. On certain platforms, the GPU prefers that red and blue be swapped (GL_BGRA). // If you supply GL_RGBA, then the driver will do the swapping for you which is slow. TK.GL.TexImage2D((TK.TextureTarget) target, 0, PixelInternalFormat, Size.Width, Size.Height, 0, (TK.PixelFormat) PixelFormat, TK.PixelType.UnsignedByte, Data); //TK.GL.TexSubImage2D((TK.TextureTarget) target, 0, // LockBound.Left, LockBound.Top, // LockBound.Width, LockBound.Height, // (TK.PixelFormat) PixelFormat, TK.PixelType.UnsignedByte, Data); //IsLocked = false; Data = null; LockBound = Rectangle.Empty; // Trace.WriteDebugLine("[Texture2D] : Unlock() {0}", this); } /// /// Returns a read only array of Color of the texture /// /// Reference mode /// Zone to collect /// A bi dimensional array of RGBA colors protected Color[,] DataToColor(TextureTarget target, Rectangle rectangle) { Lock(target, ImageLockMode.ReadOnly, rectangle); Color[,] colors = new Color[rectangle.Width, rectangle.Height]; for (int y = rectangle.Top ; y < rectangle.Height ; y++) { for (int x = rectangle.Left ; x < rectangle.Width ; x++) { int offset = y * rectangle.Width *4 + x * 4; colors[x, y] = Color.FromArgb( Data[offset + 3], Data[offset + 2], Data[offset + 1], Data[offset + 0]); } } Unlock(target); return colors; } #endregion #region Statics /// /// Default magnify filter /// static public TextureMagFilter DefaultMagFilter = TextureMagFilter.Nearest; /// /// Default minify filter /// static public TextureMinFilter DefaultMinFilter = TextureMinFilter.Nearest; /// /// Default border color /// static public Color DefaultBorderColor = Color.Black; /// /// Default horizontal wrap filter /// static public TextureWrapFilter DefaultHorizontalWrapFilter = TextureWrapFilter.Clamp; /// /// Default vertical wrap filter /// static public TextureWrapFilter DefaultVerticalWrapFilter = TextureWrapFilter.Clamp; /// /// GL_ARB_texture_compression extension supported /// static internal bool HasTextureCompression { get { return Display.Capabilities.Extensions.Contains("GL_ARB_texture_compression"); } } /// /// Number of textre in use /// public static List InUse { get; protected set; } #endregion #region Properties /// /// Does resource is disposed /// public bool IsDisposed { get; protected set; } /// /// Returns the lock status of the texture /// public bool IsLocked { get; protected set; } /// /// Lock mode /// protected ImageLockMode LockMode; /// /// Lock rectangle /// Rectangle LockBound; /// /// Gets the internal RenderDevice ID of the texture /// public int Handle { get; protected set; } /// /// Specifies the number of color components in the texture. /// protected TK.PixelInternalFormat PixelInternalFormat; /// /// Specifies the format of the pixel data. /// public PixelFormat PixelFormat { get; protected set; } /// /// Gets / sets a border color. /// public Color BorderColor { get { Display.Texture = this; int[] color = new int[4]; TK.GL.GetTexParameter((TK.TextureTarget) Target, TK.GetTextureParameter.TextureBorderColor, color); return Color.FromArgb(color[0], color[1], color[2], color[3]); } set { Display.Texture = this; int[] color = new int[4]; color[0] = value.A; color[1] = value.R; color[2] = value.G; color[3] = value.B; TK.GL.TexParameter((TK.TextureTarget) Target, TK.TextureParameterName.TextureBorderColor, color); } } /// /// Gets / sets the texture minifying function /// public TextureMinFilter MinFilter { get { Display.Texture = this; int value; TK.GL.GetTexParameter((TK.TextureTarget) Target, TK.GetTextureParameter.TextureMinFilter, out value); return (TextureMinFilter) value; } set { Display.Texture = this; TK.GL.TexParameter((TK.TextureTarget) Target, TK.TextureParameterName.TextureMinFilter, (int) value); } } /// /// Gets / sets the texture magnification function /// public TextureMagFilter MagFilter { get { Display.Texture = this; int value; TK.GL.GetTexParameter((TK.TextureTarget) Target, TK.GetTextureParameter.TextureMagFilter, out value); return (TextureMagFilter) value; } set { Display.Texture = this; TK.GL.TexParameter((TK.TextureTarget) Target, TK.TextureParameterName.TextureMagFilter, (int) value); } } /// /// Gets / sets the texture minifying function /// public float AnisotropicFilter { get { if (!Display.Capabilities.HasAnisotropicFiltering) return 0.0f; Display.Texture = this; float value; TK.GL.GetTexParameter((TK.TextureTarget) Target, (TK.GetTextureParameter) TK.ExtTextureFilterAnisotropic.TextureMaxAnisotropyExt, out value); return value; } set { if (!Display.Capabilities.HasAnisotropicFiltering) return; Display.Texture = this; TK.GL.TexParameter((TK.TextureTarget) Target, (TK.TextureParameterName) TK.ExtTextureFilterAnisotropic.TextureMaxAnisotropyExt, value); } } /// /// Gets / sets the size of the texture /// [Description("Texture size")] [Category("Dimension")] public Size Size { get; protected set; } /// /// Bitmap of the texture /// public byte[] Data { get; set; } /// /// Texture target /// public TextureTarget Target { get; protected set; } /// /// /// /// public override string ToString() { return String.Format("{0} (id {1}) ({2}x{3})", Target, Handle, Size.Width, Size.Height); } #endregion } /// /// Target texture /// public enum TextureTarget { /// /// One dimensional texture /// Texture1D = TK.TextureTarget.Texture1D, /// /// Two dimensional texture /// Texture2D = TK.TextureTarget.Texture2D, /// /// Three dimensional texture /// Texture3D = TK.TextureTarget.Texture3D, /// /// Cube map texture /// CubeMap = TK.TextureTarget.TextureCubeMap, /// /// Rectangle texture /// TextureRectangle = TK.TextureTarget.TextureRectangle, /* /// /// /// NegativeX = TK.TextureTarget.TextureCubeMapNegativeX, /// /// /// NegativeY = TK.TextureTarget.TextureCubeMapNegativeY, /// /// /// NegativeZ = TK.TextureTarget.TextureCubeMapNegativeZ, /// /// /// PositiveX = TK.TextureTarget.TextureCubeMapPositiveX, /// /// /// PositiveY = TK.TextureTarget.TextureCubeMapPositiveY, /// /// /// PositiveZ = TK.TextureTarget.TextureCubeMapPositiveZ, */ } /// /// Spécifie la position de l'image sur le contrôle. /// public enum TextureLayout { /// /// Image is top left aligned /// None = 0, /// /// The image is tiled /// Tile = 1, /// /// The image is centered /// Center = 2, /// /// The image is stretched /// Stretch = 3, /// /// The image is zoomed (proportions keeped) /// Zoom = 4, } /// /// Sets the wrap parameter for texture coordinate /// to either CLAMP or REPEAT /// public enum TextureWrapFilter { /// /// Causes coordinates to be clamped to the range [0,1] and is useful /// for preventing wrapping artifacts when mapping a single image onto an object. /// Clamp = TK.TextureWrapMode.Clamp, /// /// Causes the integer part of the coordinate to be ignored; the GL uses only /// the fractional part, thereby creating a repeating pattern. Border texture elements are /// accessed only if wrapping is set to GL_CLAMP. /// Repeat = TK.TextureWrapMode.Repeat, /// /// /// ClampToEdge = TK.TextureWrapMode.ClampToEdge, /// /// /// ClampToBorder = TK.TextureWrapMode.ClampToBorder, /// /// /// MirrorRepeat = TK.TextureWrapMode.MirroredRepeat, } /// /// Specifies flags that are passed to the flags parameter of the Overload:System.Drawing.Bitmap.LockBits ///method. The Overload:System.Drawing.Bitmap.LockBits method locks a portion ///of an image so that you can read or write the pixel data. /// public enum ImageLockMode { /// /// Specifies that a portion of the image is locked for reading. /// ReadOnly = 1, /// /// Specifies that a portion of the image is locked for writing. /// WriteOnly = 2, /// /// Specifies that a portion of the image is locked for reading or writing. /// ReadWrite = 3, } /// /// Pixel format /// public enum PixelFormat { /// /// /// DepthComponent = TK.PixelFormat.DepthComponent, /// /// /// Red = TK.PixelFormat.Red, /// /// /// Green = TK.PixelFormat.Green, /// /// /// Blue = TK.PixelFormat.Blue, /// /// /// Alpha = TK.PixelFormat.Alpha, /// /// /// Rgb = TK.PixelFormat.Rgb, /// /// /// Rgba = TK.PixelFormat.Rgba, /// /// /// Bgr = TK.PixelFormat.Bgr, /// /// /// Bgra = TK.PixelFormat.Bgra, /// /// /// DepthStencil = TK.PixelFormat.DepthStencil, } /// /// /// public enum TextureEnvMode { /// /// /// Add = TK.TextureEnvMode.Add, /// /// /// Blend = TK.TextureEnvMode.Blend, /// /// /// Replace = TK.TextureEnvMode.Replace, /// /// /// Modulate = TK.TextureEnvMode.Modulate, /// /// /// Decal = TK.TextureEnvMode.Decal, /// /// /// Combine = TK.TextureEnvMode.Combine, } /// /// The texture magnification function is used when the pixel being textured /// maps to an area less than or equal to one texture element. /// public enum TextureMagFilter { /// /// Returns the value of the texture element that is nearest /// (in Manhattan distance) to the center of the pixel being textured. /// Nearest = TK.TextureMagFilter.Nearest, /// /// Returns the weighted average of the four texture elements that are closest /// to the center of the pixel being textured. These can include border texture /// elements, depending on the values of GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T, /// and on the exact mapping. /// Linear = TK.TextureMagFilter.Linear, } /// /// The texture minifying function is used whenever the pixel being /// textured maps to an area greater than one texture element. /// public enum TextureMinFilter { /// /// Returns the value of the texture element that is nearest (in Manhattan distance) /// to the center of the pixel being textured. /// Nearest = TK.TextureMinFilter.Nearest, /// /// Returns the weighted average of the four texture elements that are closest to the center of the pixel /// being textured. These can include border texture elements, depending on the values of /// GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T, and on the exact mapping. /// Linear = TK.TextureMinFilter.Linear, /// /// Chooses the mipmap that most closely matches the size of the pixel being textured and uses the /// GL_NEAREST criterion (the texture element nearest to the center of the pixel) to produce a texture value. /// NearestMipmapNearest = TK.TextureMinFilter.NearestMipmapNearest, /// /// Chooses the mipmap that most closely matches the size of the pixel being textured and uses the /// GL_LINEAR criterion (a weighted average of the four texture elements that are closest to the /// center of the pixel) to produce a texture value. /// LinearMipmapNearest = TK.TextureMinFilter.LinearMipmapNearest, /// /// Chooses the two mipmaps that most closely match the size of the pixel being textured and uses /// the GL_NEAREST criterion (the texture element nearest to the center of the pixel) to produce /// a texture value from each mipmap. The final texture value is a weighted average of those two values. /// NearestMipmapLinear = TK.TextureMinFilter.NearestMipmapLinear, /// /// Chooses the two mipmaps that most closely match the size of the pixel being textured and uses /// the GL_LINEAR criterion (a weighted average of the four texture elements that are closest to /// the center of the pixel) to produce a texture value from each mipmap. /// The final texture value is a weighted average of those two values. /// LinearMipmapLinear = TK.TextureMinFilter.LinearMipmapLinear, } }