#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,
}
}