1 module evael.graphics.Texture;
2 
3 import std.file : exists;
4 import std.string : lastIndexOf, format, toStringz;
5 import std.exception;
6 import std.typecons : Flag, Yes, No;
7 import core.stdc.string : strlen;
8 
9 import derelict.freeimage.freeimage;
10 
11 import evael.graphics.GL;
12 
13 import evael.system.Asset;
14 
15 import evael.utils.Config;
16 import evael.utils.Size;
17 
18 /**
19  * Texture.
20  */
21 class Texture : IAsset
22 {
23 	private Size!int m_size;
24 	private uint 	 m_id;
25 	
26 	/// NanoVG texture id.
27 	private uint m_nvgId;
28 
29 	/**
30 	 * Texture constructor.
31 	 * Params:
32 	 *		id : texture id
33 	 */
34 	@nogc
35 	public this(in uint id) nothrow
36 	{
37 		this.m_id = id;
38 	}
39 
40 	/**
41 	 * Texture constructor.
42 	 * Params:
43 	 *		id : texture id
44 	 *		size : texture size
45 	 */
46 	@nogc
47 	public this(in uint id, in Size!int size) nothrow
48 	{
49 		this.m_id = id;
50 		this.m_size = size;
51 	}	
52 	
53 	/**
54 	 * Texture destructor.
55 	 */
56 	@nogc
57 	public void dispose() const nothrow 
58 	{
59 		gl.DeleteBuffers(1, &this.m_id);
60 	}
61 
62 	/**
63 	 * Loads texture.
64 	 * Params:
65 	 *		textureName : texture to load
66 	 * Credits: http://r3dux.org/2014/10/how-to-load-an-opengl-texture-using-the-freeimage-library-or-freeimageplus-technically/
67 	 */
68 	public static Texture load(in string textureName, in bool flipTexture = true)
69 	{
70 		immutable fileName = toStringz(Config.Paths.textures!string ~ textureName);
71 
72 		// Determine the format of the image
73 		FREE_IMAGE_FORMAT fiFormat = FreeImage_GetFileType(fileName , 0);
74 	
75 		// Image not found
76 		enforce(fiFormat != -1, new Exception(format("File \"%s\" must exists.", textureName)));
77 
78 		// Found image, but couldn't determine the file format
79 		if (fiFormat == FIF_UNKNOWN)
80 		{
81 			fiFormat = FreeImage_GetFIFFromFilename(fileName);
82 	
83 			if (!FreeImage_FIFSupportsReading(fiFormat) )
84 			{
85 				throw new Exception("File \"%s\" cannot be read.".format(textureName));
86 			}
87 		}
88 	
89 		// If we're here we have a known image format, so load the image into a bitmap
90 		FIBITMAP* bitmap = FreeImage_Load(fiFormat, fileName);
91 	
92 		if(flipTexture)
93 		{
94 			FreeImage_FlipVertical(bitmap);
95 		}
96 
97 		// How many bits-per-pixel is the source image?
98 		immutable bitsPerPixel =  FreeImage_GetBPP(bitmap);
99 	
100 		// Convert our image up to 32 bits (8 bits per channel, Red/Green/Blue/Alpha) -
101 		// but only if the image is not already 32 bits (i.e. 8 bits per channel).
102 		// Note: ConvertTo32Bits returns a CLONE of the image data - so if we
103 		// allocate this back to itself without using our bitmap32 intermediate
104 		// we will LEAK the original bitmap data
105 		FIBITMAP* bitmap32;
106 		if (bitsPerPixel == 32)
107 		{
108 			bitmap32 = bitmap;
109 		}
110 		else
111 		{
112 			bitmap32 = FreeImage_ConvertTo32Bits(bitmap);
113 		}
114 	
115 		// Some basic image info - strip it out if you don't care
116 		immutable imageWidth  = FreeImage_GetWidth(bitmap32);
117 		immutable imageHeight = FreeImage_GetHeight(bitmap32);
118 	
119 		// Get a pointer to the texture data as an array of unsigned bytes.
120 		// Note: At this point bitmap32 ALWAYS holds a 32-bit colour version of our image - so we get our data from that.
121 		// Also, we don't need to delete or delete[] this textureData because it's not on the heap (so attempting to do
122 		// so will cause a crash) - just let it go out of scope and the memory will be returned to the stack.
123 		ubyte* textureData = FreeImage_GetBits(bitmap32);
124 	
125 		// Generate a texture ID and bind to it
126 		auto texture = Texture.generateEmptyTexture();
127 		texture.size = Size!int(imageWidth, imageHeight);
128 
129 		gl.BindTexture(GL_TEXTURE_2D, texture.id);
130 	
131 		// Construct the texture.
132 		// Note: The 'Data format' is the format of the image data as provided by the image library. FreeImage decodes images into
133 		// BGR/BGRA format, but we want to work with it in the more common RGBA format, so we specify the 'Internal format' as such.
134 		gl.TexImage2D(GL_TEXTURE_2D,    // Type of texture
135 					0,                // Mipmap level (0 being the top level i.e. full size)
136 					GL_RGBA,          // Internal format
137 					imageWidth,       // Width of the texture
138 					imageHeight,      // Height of the texture,
139 					0,                // Border in pixels
140 					GL_BGRA,          // Data format
141 					GL_UNSIGNED_BYTE, // Type of texture data
142 					textureData);     // The image data to use for this texture
143 	
144 		auto minificationFilter = GL_LINEAR;
145 		auto magnificationFilter = GL_LINEAR;
146 
147 		// Anisotropic filter
148 		float fLargest;
149 		gl.GetFloatv(0x84FF, &fLargest);
150 		gl.TexParameterf(GL_TEXTURE_2D, 0x84FE, fLargest);
151 
152 		// Specify our minification and magnification filters
153 		gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minificationFilter);
154 		gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magnificationFilter);
155 	
156 		// If we're using MipMaps, then we'll generate them here.
157 		// Note: The glGenerateMipmap call requires OpenGL 3.0 as a minimum.
158 		if (minificationFilter == GL_LINEAR_MIPMAP_LINEAR   ||
159 			minificationFilter == GL_LINEAR_MIPMAP_NEAREST  ||
160 			minificationFilter == GL_NEAREST_MIPMAP_LINEAR  ||
161 			minificationFilter == GL_NEAREST_MIPMAP_NEAREST)
162 		{
163 			gl.GenerateMipmap(GL_TEXTURE_2D);
164 		}
165 	
166 		// Unload the 32-bit colour bitmap
167 		FreeImage_Unload(bitmap32);
168 	
169 		// If we had to do a conversion to 32-bit colour, then unload the original
170 		// non-32-bit-colour version of the image data too. Otherwise, bitmap32 and
171 		// bitmap point at the same data, and that data's already been free'd, so
172 		// don't attempt to free it again! (or we'll crash).
173 		if (bitsPerPixel != 32)
174 		{
175 			FreeImage_Unload(bitmap);
176 		}
177 		
178 		return texture;
179 	}
180 
181 	/**
182 	 * Loads image.
183 	 * Params:
184 	 *		textureName : texture to load
185 	 */
186 	public static ubyte[] loadBytes(in string textureName, in Flag!"flipTexture" flipTexture = Yes.flipTexture)
187 	{
188 		// Texture name without path and extension
189 		immutable fileName = toStringz(Config.Paths.textures!string ~ textureName);
190 
191 		// Determine the format of the image
192 		FREE_IMAGE_FORMAT fiFormat = FreeImage_GetFileType(fileName , 0);
193 	
194 		// Image not found
195 		enforce(fiFormat != -1, new Exception(format("File \"%s\" must exists.", textureName)));
196 
197 		// Found image, but couldn't determine the file format
198 		if (fiFormat == FIF_UNKNOWN)
199 		{
200 			fiFormat = FreeImage_GetFIFFromFilename(fileName);
201 	
202 			if (!FreeImage_FIFSupportsReading(fiFormat) )
203 			{
204 				throw new Exception(format("File \"%s\" cannot be read.", textureName));
205 			}
206 		}
207 	
208 		// If we're here we have a known image format, so load the image into a bitmap
209 		FIBITMAP* bitmap = FreeImage_Load(fiFormat, fileName);
210 	
211 		if(flipTexture)
212 		{
213 			FreeImage_FlipVertical(bitmap);
214 		}
215 
216 		// How many bits-per-pixel is the source image?
217 		immutable bitsPerPixel =  FreeImage_GetBPP(bitmap);
218 	
219 		// Convert our image up to 32 bits (8 bits per channel, Red/Green/Blue/Alpha) -
220 		// but only if the image is not already 32 bits (i.e. 8 bits per channel).
221 		// Note: ConvertTo32Bits returns a CLONE of the image data - so if we
222 		// allocate this back to itself without using our bitmap32 intermediate
223 		// we will LEAK the original bitmap data
224 		FIBITMAP* bitmap32;
225 		if (bitsPerPixel == 32)
226 		{
227 			bitmap32 = bitmap;
228 		}
229 		else
230 		{
231 			bitmap32 = FreeImage_ConvertTo32Bits(bitmap);
232 		}
233 	
234 		// Get a pointer to the texture data as an array of unsigned bytes.
235 		// Note: At this point bitmap32 ALWAYS holds a 32-bit colour version of our image - so we get our data from that.
236 		// Also, we don't need to delete or delete[] this textureData because it's not on the heap (so attempting to do
237 		// so will cause a crash) - just let it go out of scope and the memory will be returned to the stack.
238 		ubyte* bytes = FreeImage_GetBits(bitmap32);
239 
240 		ubyte[] ret = new ubyte[(FreeImage_GetWidth(bitmap32) * FreeImage_GetHeight(bitmap32)) * 4];
241 
242 		for(int i = 0; i < ret.length; i++)
243 		{
244 			ret[i] = bytes[i];
245 		}
246 
247 		// Unload the 32-bit colour bitmap
248 		FreeImage_Unload(bitmap32);
249 	
250 		// If we had to do a conversion to 32-bit colour, then unload the original
251 		// non-32-bit-colour version of the image data too. Otherwise, bitmap32 and
252 		// bitmap point at the same data, and that data's already been free'd, so
253 		// don't attempt to free it again! (or we'll crash).
254 		if (bitsPerPixel != 32)
255 		{
256 			FreeImage_Unload(bitmap);
257 		}
258 		
259 		return ret;
260 	}
261 
262 
263 	/**
264 	 * Generates an empty texture.
265 	 */
266 	public static Texture generateEmptyTexture() nothrow
267 	{
268 		uint id = 0;
269 		gl.GenTextures(1, &id);
270 
271 		return new Texture(id);
272 	}
273 
274 	/**
275 	 * Properties
276 	 */
277 	@nogc
278 	@property nothrow
279 	{
280 		public uint id() const
281 		{
282 			return this.m_id;	
283 		}
284 		
285 		public void id(in uint value)
286 		{
287 			this.m_id = value;
288 		}
289 		
290 		public Size!int size() const
291 		{
292 			return this.m_size;	
293 		}
294 
295 		public void size(in Size!int value)
296 		{
297 			this.m_size = value;
298 		}
299 
300 		public uint nvgId() const
301 		{
302 			return this.m_nvgId;
303 		}
304 
305 		public void nvgId(in uint value)
306 		{
307 			this.m_nvgId = value;
308 		}
309 	}
310 }
311