1 /*
2  *******************************************************************************************
3  * Dgame (a D game framework) - Copyright (c) Randy Schütt
4  *
5  * This software is provided 'as-is', without any express or implied warranty.
6  * In no event will the authors be held liable for any damages arising from
7  * the use of this software.
8  *
9  * Permission is granted to anyone to use this software for any purpose,
10  * including commercial applications, and to alter it and redistribute it
11  * freely, subject to the following restrictions:
12  *
13  * 1. The origin of this software must not be misrepresented; you must not claim
14  *    that you wrote the original software. If you use this software in a product,
15  *    an acknowledgment in the product documentation would be appreciated but is
16  *    not required.
17  *
18  * 2. Altered source versions must be plainly marked as such, and must not be
19  *    misrepresented as being the original software.
20  *
21  * 3. This notice may not be removed or altered from any source distribution.
22  *******************************************************************************************
23  */
24 module Dgame.Graphic.Texture;
25 
26 private:
27 
28 import derelict.opengl3.gl;
29 
30 import Dgame.Math.Rect;
31 
32 import Dgame.Graphic.Surface;
33 import Dgame.Graphic.Color;
34 
35 import Dgame.Internal.m3;
36 
37 public:
38 
39 /**
40  * A Texture is a 2 dimensional pixel reprasentation.
41  * It is a wrapper of an OpenGL Texture.
42  *
43  * Author: Randy Schuett (rswhite4@googlemail.com)
44  */
45 struct Texture {
46     /**
47      * Supported Texture Format
48      */
49     enum Format {
50         None = 0,   /// Take this if you want to declare that you give no Format.
51         RGB = GL_RGB,   /// Alias for GL_RGB
52         RGBA = GL_RGBA, /// Alias for GL_RGBA
53         BGR = GL_BGR,   /// Alias for GL_BGR
54         BGRA = GL_BGRA, /// Alias for GL_BGRA
55         RGB8 = GL_RGB8,   /// 8 Bit RGB Format
56         RGB16 = GL_RGB16, /// 16 Bit RGB Format
57         RGBA8 = GL_RGBA8, /// 8 Bit RGBA Format
58         RGBA16 = GL_RGBA16, /// 16 Bit RGBA Format
59         Alpha = GL_ALPHA,   /// Alias for GL_ALPHA
60         Luminance = GL_LUMINANCE,   /// Alias for GL_LUMINANCE
61         LuminanceAlpha = GL_LUMINANCE_ALPHA /// Alias for GL_LUMINANCE_ALPHA
62     }
63 
64 private:
65     uint _texId;
66 
67     uint _width;
68     uint _height;
69     ubyte _depth;
70 
71     bool _isSmooth;
72     bool _isRepeated;
73 
74     Format _format;
75 
76     @nogc
77     void _init() nothrow {
78         if (_texId == 0) {
79             glGenTextures(1, &_texId);
80         }
81     }
82 
83 public:
84     /**
85      * CTor
86      */
87     @nogc
88     this(void* memory, uint width, uint height, Format fmt) nothrow {
89         this.loadFromMemory(memory, width, height, fmt);
90     }
91 
92     /**
93      * CTor
94      */
95     @nogc
96     this(const Surface srfc, Format fmt = Format.None) nothrow {
97         this.loadFrom(srfc, fmt);
98     }
99 
100     /**
101      * Postblit is disabled
102      */
103     @disable
104     this(this);
105 
106     /**
107      * DTor
108      */
109     @nogc
110     ~this() nothrow {
111         if (_texId != 0)
112             glDeleteTextures(1, &_texId);
113     }
114 
115     /**
116      * Returns the currently bound texture id.
117      */
118     @nogc
119     static int currentlyBound() nothrow {
120         int current;
121         glGetIntegerv(GL_TEXTURE_BINDING_2D, &current);
122 
123         return current;
124     }
125 
126     /**
127      * Returns the Texture Id.
128      */
129     @property
130     @nogc
131     uint id() const pure nothrow {
132         return _texId;
133     }
134 
135     /**
136      * Returns if the texture is used.
137      */
138     @nogc
139     bool isValid() const pure nothrow {
140         return _texId != 0;
141     }
142 
143     /**
144      * Returns the width of this Texture
145      */
146     @property
147     @nogc
148     uint width() const pure nothrow {
149         return _width;
150     }
151 
152     /**
153      * Returns the height of this Texture.
154      */
155     @property
156     @nogc
157     uint height() const pure nothrow {
158         return _height;
159     }
160 
161     /**
162      * Returns the depth. May often 24 or 32.
163      */
164     @property
165     @nogc
166     ubyte depth() const pure nothrow {
167         return _depth;
168     }
169 
170     /**
171      * Returns the Format.
172      *
173      * See: Format enum.
174      */
175     @property
176     @nogc
177     Format format() const pure nothrow {
178         return _format;
179     }
180 
181     /**
182      * Binds this Texture.
183      * Means this Texture is now activated.
184      */
185     @nogc
186     void bind() const nothrow {
187         glBindTexture(GL_TEXTURE_2D, _texId);
188     }
189 
190     /**
191      * Binds this Texture.
192      * Means this Texture is now deactivated.
193      */
194     @nogc
195     void unbind() const nothrow {
196         glBindTexture(GL_TEXTURE_2D, 0);
197     }
198 
199     /**
200      * Returns true, if this Texture is currently activated.
201      */
202     @nogc
203     bool isCurrentlyBound() const nothrow {
204         return Texture.currentlyBound() == _texId;
205     }
206 
207     /**
208      * Set smooth filter.
209      */
210     @nogc
211     void setSmooth(bool smooth) nothrow {
212         if (smooth != _isSmooth) {
213             _isSmooth = smooth;
214 
215             if (_texId != 0) {
216                 this.bind();
217 
218                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _isSmooth ? GL_LINEAR : GL_NEAREST);
219                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _isSmooth ? GL_LINEAR : GL_NEAREST);
220 
221                 this.unbind();
222             }
223         }
224     }
225 
226     /**
227      * Returns if smooth filter are activated.
228      */
229     @nogc
230     bool isSmooth() const pure nothrow {
231         return _isSmooth;
232     }
233 
234     /**
235      * Set repeating.
236      **/
237     @nogc
238     void setRepeat(bool repeat) nothrow {
239         if (repeat != _isRepeated) {
240             _isRepeated = repeat;
241 
242             if (_texId != 0) {
243                 this.bind();
244 
245                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _isRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE);
246                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _isRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE);
247 
248                 this.unbind();
249             }
250         }
251     }
252 
253     /**
254      * Returns if repeating is enabled.
255      */
256     @nogc
257     bool isRepeated() const pure nothrow {
258         return _isRepeated;
259     }
260 
261     /**
262      * Load from Surface
263      */
264     @nogc
265     void loadFrom(const Surface srfc, Format fmt = Format.None) nothrow {
266         assert(srfc.isValid(), "Cannot load invalid Surface");
267 
268         if (fmt == Format.None) {
269             fmt = bitsToFormat(srfc.bits);
270             version (BigEndian) fmt = switchFormat(fmt);
271         }
272 
273         this.loadFromMemory(srfc.pixels, srfc.width, srfc.height, fmt);
274     }
275 
276     /**
277      * Load from memory.
278      */
279     @nogc
280     void loadFromMemory(const void* memory, uint width, uint height, Format fmt) nothrow {
281         _init();
282 
283         assert(width != 0 && height != 0, "Width and height cannot be 0.");
284         assert(fmt != Format.None, "Need a valid format.");
285 
286         _format = fmt == Format.None ? bitsToFormat(depth) : fmt;
287         assert(_format != Format.None, "Need Texture.Format or depth > 24");
288 
289         if (width == _width && height == _height)
290             this.update(memory);
291         else {
292             _depth = formatToBits(_format);
293 
294             this.bind();
295 
296             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _isRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE);
297             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _isRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE);
298             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _isSmooth ? GL_LINEAR : GL_NEAREST);
299             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _isSmooth ? GL_LINEAR : GL_NEAREST);
300             glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
301             glTexImage2D(GL_TEXTURE_2D, 0, depth / 8, width, height, 0, _format, GL_UNSIGNED_BYTE, memory);
302 
303             _width  = width;
304             _height = height;
305 
306             this.unbind();
307         }
308     }
309 
310     /**
311      * Set a colorkey.
312      */
313     @nogc
314     void setColorkey(const Color4b colorkey) nothrow {
315         if (_texId == 0)
316             return;
317 
318         // Get the pixel memory
319         immutable size_t msize = this.getByteSize();
320         void[] mem = make!(void[])(msize);
321         scope(exit) unmake(mem);
322 
323         void[] memory = this.getPixels(mem[0 .. msize]);
324 
325         // Go through pixels
326         for (uint i = 0; i < memory.length; ++i) {
327             // Get pixel colors
328             uint* color = cast(uint*) &memory[i];
329             // Color matches
330             if (color[0] == colorkey.red
331                 && color[1] == colorkey.green
332                 && color[2] == colorkey.blue
333                 && (0 == colorkey.alpha || color[3] == colorkey.alpha))
334             {
335                 // Make transparent
336                 color[0] = 255;
337                 color[1] = 255;
338                 color[2] = 255;
339                 color[3] = 0;
340             }
341         }
342 
343         this.update(memory.ptr);
344     }
345 
346     /**
347      * Returns the byte size of the Texture
348      */
349     @nogc
350     size_t getByteSize() const pure nothrow {
351         if (_depth > 0)
352             return _width * _height * (_depth / 8);
353         return 0;
354     }
355 
356     /**
357      * Returns the pixel data of this Texture or null if this Texture isn't valid.
358      * pixels is used to store the pixel data.
359      */
360     @nogc
361     void[] getPixels(void[] pixels) const nothrow {
362         immutable size_t msize = this.getByteSize();
363         if (msize == 0)
364             return null;
365 
366         assert(pixels.length >= msize, "Your given memory is too short.");
367 
368         this.bind();
369 
370         glGetTexImage(GL_TEXTURE_2D, 0, _format, GL_UNSIGNED_BYTE, pixels.ptr);
371 
372         this.unbind();
373 
374         return pixels;
375     }
376 
377     /**
378      * Returns the pixel of this Texture or null if this Texture isn't valid.
379      *
380      * Note: this method <b>allocates</b> GC memory.
381      */
382     void[] getPixels() const nothrow {
383         immutable size_t msize = this.getByteSize();
384         if (msize == 0)
385             return null;
386 
387         void[] pixels = new void[msize];
388 
389         return this.getPixels(pixels);
390     }
391 
392     /**
393      * Update the pixel data of this Texture.
394      * The second parameter is a pointer to the area which is updated.
395      * If it is null (default) the whole Texture will be updated.
396      * The third parameter is the format of the pixels.
397      */
398     @nogc
399     void update(const void* memory, const Rect* rect = null, Format fmt = Format.None) const nothrow {
400         if (_texId == 0)
401             return;
402 
403         assert(memory, "Invalid memory");
404         assert(_width != 0 && _height != 0, "width or height is 0.");
405 
406         fmt = fmt == Format.None ? _format : fmt;
407 
408         uint width = _width, height = _height;
409         int x = 0, y = 0;
410 
411         if (rect) {
412             assert(rect.width <= _width && rect.height <= _height, "Rect is greater as the Texture.");
413             assert(rect.x < _width && rect.y < _height, "x or y of the Rect is greater as the Texture.");
414 
415             width  = rect.width;
416             height = rect.height;
417 
418             x = rect.x;
419             y = rect.y;
420         }
421 
422         this.bind();
423 
424         glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, fmt, GL_UNSIGNED_BYTE, memory);
425 
426         this.unbind();
427     }
428 }
429 
430 /**
431  * Format a Texture.Format into the related bit count.
432  * If the format is not supported, it returns 0.
433  *
434  * Examples:
435  * ---
436  * assert(formatToBits(Texture.Format.RGBA) == 32);
437  * assert(formatToBits(Texture.Format.RGB) == 24);
438  * assert(formatToBits(Texture.Format.BGRA) == 32);
439  * assert(formatToBits(Texture.Format.BGR) == 24);
440  * ---
441  */
442 @nogc
443 ubyte formatToBits(Texture.Format fmt) pure nothrow {
444     switch (fmt) {
445         case Texture.Format.RGB:
446         case Texture.Format.BGR:
447             return 24;
448         case Texture.Format.RGBA:
449         case Texture.Format.BGRA:
450             return 32;
451         case Texture.Format.RGBA16: return 16;
452         case Texture.Format.RGBA8: return 8;
453         default: return 0;
454     }
455 }
456 
457 unittest {
458     assert(Texture.Format.RGB.formatToBits() == 24);
459     assert(Texture.Format.BGR.formatToBits() == 24);
460     assert(Texture.Format.RGBA.formatToBits() == 32);
461     assert(Texture.Format.BGRA.formatToBits() == 32);
462 }
463 
464 /**
465  * Format a bit count into the related Texture.Format.
466  * If no bit count is supported, it returns Texture.Format.None.
467  *
468  * Examples:
469  * ---
470  * assert(bitsToFormat(32) == Texture.Format.RGBA);
471  * assert(bitsToFormat(24) == Texture.Format.RGB);
472  * assert(bitsToFormat(32, true) == Texture.Format.BGRA);
473  * assert(bitsToFormat(24, true) == Texture.Format.BGR);
474  * ---
475  */
476 @nogc
477 Texture.Format bitsToFormat(ubyte bits, bool reverse = false) pure nothrow {
478     switch (bits) {
479         case 32: return !reverse ? Texture.Format.RGBA : Texture.Format.BGRA;
480         case 24: return !reverse ? Texture.Format.RGB : Texture.Format.BGR;
481         case 16: return Texture.Format.RGBA16;
482         case  8: return Texture.Format.RGBA8;
483         default: return Texture.Format.None;
484     }
485 }
486 
487 unittest {
488     assert(bitsToFormat(8) == Texture.Format.RGBA8);
489     assert(bitsToFormat(16) == Texture.Format.RGBA16);
490     assert(bitsToFormat(24) == Texture.Format.RGB);
491     assert(bitsToFormat(32) == Texture.Format.RGBA);
492     assert(bitsToFormat(24, true) == Texture.Format.BGR);
493     assert(bitsToFormat(32, true) == Texture.Format.BGRA);
494 }
495 
496 /**
497  * Switch/Reverse Texture.Format.
498  *
499  * Examples:
500  * ---
501  * assert(switchFormat(Texture.Format.RGB) == Texture.Format.BGR);
502  * assert(switchFormat(Texture.Format.RGB, true) == Texture.Format.BGRA);
503  * assert(switchFormat(Texture.Format.RGBA) == Texture.Format.BGRA);
504  * assert(switchFormat(Texture.Format.RGBA, true) == Texture.Format.BGRA);
505  * ---
506  */
507 @nogc
508 Texture.Format switchFormat(Texture.Format fmt, bool alpha = false) pure nothrow {
509     switch (fmt) {
510         case Texture.Format.RGB:
511             if (alpha) goto case Texture.Format.RGBA;
512             return Texture.Format.BGR;
513         case Texture.Format.BGR:
514             if (alpha) goto case Texture.Format.BGRA;
515             return Texture.Format.RGB;
516         case Texture.Format.RGBA: return Texture.Format.BGRA;
517         case Texture.Format.BGRA: return Texture.Format.RGBA;
518         default: return fmt;
519     }
520 }