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