Angel 3.2
A 2D Game Prototyping Engine
Textures.cpp
1 
2 // Copyright (C) 2008-2013, Shane Liesegang
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of the copyright holder nor the names of any
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 // POSSIBILITY OF SUCH DAMAGE.
29 
30 #include "stdafx.h"
31 #include "../Infrastructure/Textures.h"
32 
33 #include "../AngelConfig.h"
34 #include "../Infrastructure/Log.h"
35 
36 #if ANGEL_MOBILE || ANGEL_DISABLE_DEVIL
37  #define _ANGEL_DISABLE_DEVIL 1
38 #endif
39 
40 #if !_ANGEL_DISABLE_DEVIL
41  #define ILUT_USE_OPENGL
42  #undef _UNICODE
43  #include "IL/il.h"
44  #include "IL/ilu.h"
45  #include "IL/ilut.h"
46 #else
47  #include "png.h"
48 #endif
49 
50 void InitializeTextureLoading()
51 {
52  #if !_ANGEL_DISABLE_DEVIL
53  ilInit();
54  iluInit();
55  ilutInit();
56  glDisable(GL_TEXTURE_2D);
57 
58  // Convert any paletted images
59  ilEnable(IL_CONV_PAL);
60  // Allegedly gets rid of dithering on some nvidia cards.
61  ilutEnable(ILUT_OPENGL_CONV);
62  #endif
63 }
64 
65 void FinalizeTextureLoading()
66 {
67  //nothing right now
68 }
69 
71 {
72  String filename;
73  GLint clampMode;
74  GLint filterMode;
75  GLuint textureIndex;
76  GLuint width;
77  GLuint height;
78  bool dirty;
79 };
80 std::map<String, TextureCacheEntry> theTextureCache;
81 
82 void FlushTextureCache()
83 {
84  // Iterate all the entries in the texture cache and dirty them.
85  std::map<String,TextureCacheEntry>::iterator it = theTextureCache.begin();
86  for (/*no init*/; it != theTextureCache.end(); ++it)
87  {
88  it->second.dirty = true;
89  GetTextureReference(it->second.filename, it->second.clampMode, it->second.filterMode, false);
90  }
91 }
92 
93 const int GetTextureReference(const String& filename, bool bOptional)
94 {
95  return GetTextureReference(filename, GL_CLAMP, GL_LINEAR, bOptional);
96 }
97 
98 bool PurgeTexture(const String& filename)
99 {
100  std::map<String,TextureCacheEntry>::iterator it = theTextureCache.find(filename);
101  if (it == theTextureCache.end())
102  {
103  return false;
104  }
105  glDeleteTextures(1, &it->second.textureIndex);
106  theTextureCache.erase(it);
107  return true;
108 }
109 
110 #if !_ANGEL_DISABLE_DEVIL
111  void HandleDevILErrors(const String& errorPrefix="")
112  {
113  ILenum error = ilGetError();
114  if (error != IL_NO_ERROR)
115  {
116  sysLog.Log("DevIL errors loading: " + errorPrefix);
117  do
118  {
119  sysLog.Log(iluErrorString(error));
120  } while ((error = ilGetError()));
121  }
122  }
123 
124  void ClearDevILErrors()
125  {
126  ILenum error = ilGetError();
127  while (error != IL_NO_ERROR)
128  {
129  error = ilGetError();
130  }
131  }
132 
133  // Modified from the ilut source file for GLBind
134  GLuint BindTexImageWithClampAndFilter(GLint ClampMode, GLint FilterMode)
135  {
136  GLuint TexID = 0;
137 
138  glGenTextures(1, &TexID);
139  glBindTexture(GL_TEXTURE_2D, TexID);
140 
141  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, ClampMode);
142  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, ClampMode);
143 
144  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
145  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, FilterMode);
146  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, FilterMode);
147  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
148  glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
149  glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
150  glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
151  glPixelStorei(GL_UNPACK_SWAP_BYTES, IL_FALSE);
152 
153  if (!ilutGLTexImage(0))
154  {
155  glDeleteTextures(1, &TexID);
156  return 0;
157  }
158 
159  return TexID;
160  }
161 #else
162  bool LoadPNG(const String& filename, png_byte* &PNG_image_buffer, png_uint_32 &width, png_uint_32 &height, bool optional)
163  {
164  FILE *PNG_file = fopen(filename.c_str(), "rb");
165  if (PNG_file == NULL)
166  {
167  if (!optional)
168  sysLog.Printf("ERROR: Couldn't open %s.", filename.c_str());
169  return false;
170  }
171 
172  GLubyte PNG_header[8];
173 
174  fread(PNG_header, 1, 8, PNG_file);
175  if (png_sig_cmp(PNG_header, 0, 8) != 0)
176  {
177  if (!optional)
178  sysLog.Printf("ERROR: %s is not a PNG.", filename.c_str());
179  return false;
180  }
181 
182  png_structp PNG_reader = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
183  if (PNG_reader == NULL)
184  {
185  if (!optional)
186  sysLog.Printf("ERROR: Can't start reading %s.", filename.c_str());
187  fclose(PNG_file);
188  return false;
189  }
190 
191  png_infop PNG_info = png_create_info_struct(PNG_reader);
192  if (PNG_info == NULL)
193  {
194  if (!optional)
195  sysLog.Printf("ERROR: Can't get info for %s.", filename.c_str());
196  png_destroy_read_struct(&PNG_reader, NULL, NULL);
197  fclose(PNG_file);
198  return false;
199  }
200 
201  png_infop PNG_end_info = png_create_info_struct(PNG_reader);
202  if (PNG_end_info == NULL)
203  {
204  if (!optional)
205  sysLog.Printf("ERROR: Can't get end info for %s.", filename.c_str());
206  png_destroy_read_struct(&PNG_reader, &PNG_info, NULL);
207  fclose(PNG_file);
208  return false;
209  }
210 
211  if (setjmp(png_jmpbuf(PNG_reader)))
212  {
213  if (!optional)
214  sysLog.Printf("ERROR: Can't load %s.", filename.c_str());
215  png_destroy_read_struct(&PNG_reader, &PNG_info, &PNG_end_info);
216  fclose(PNG_file);
217  return false;
218  }
219 
220  png_init_io(PNG_reader, PNG_file);
221  png_set_sig_bytes(PNG_reader, 8);
222 
223  png_read_info(PNG_reader, PNG_info);
224 
225  width = png_get_image_width(PNG_reader, PNG_info);
226  height = png_get_image_height(PNG_reader, PNG_info);
227 
228  png_uint_32 bit_depth, color_type;
229  bit_depth = png_get_bit_depth(PNG_reader, PNG_info);
230  color_type = png_get_color_type(PNG_reader, PNG_info);
231 
232  if (color_type == PNG_COLOR_TYPE_PALETTE)
233  {
234  png_set_palette_to_rgb(PNG_reader);
235  }
236 
237  if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
238  {
239  png_set_expand_gray_1_2_4_to_8(PNG_reader);
240  }
241 
242  if (color_type == PNG_COLOR_TYPE_GRAY ||
243  color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
244  {
245  png_set_gray_to_rgb(PNG_reader);
246  }
247 
248  if (png_get_valid(PNG_reader, PNG_info, PNG_INFO_tRNS))
249  {
250  png_set_tRNS_to_alpha(PNG_reader);
251  }
252  else
253  {
254  png_set_filler(PNG_reader, 0xff, PNG_FILLER_AFTER);
255  }
256 
257  if (bit_depth == 16)
258  {
259  png_set_strip_16(PNG_reader);
260  }
261 
262  png_read_update_info(PNG_reader, PNG_info);
263 
264  PNG_image_buffer = (png_byte*)malloc(4 * width * height);
265  png_byte** PNG_rows = (png_byte**)malloc(height * sizeof(png_byte*));
266 
267  unsigned int row;
268  for (row = 0; row < height; ++row)
269  {
270  PNG_rows[height - 1 - row] = PNG_image_buffer + (row * 4 * width);
271  }
272 
273  png_read_image(PNG_reader, PNG_rows);
274 
275  free(PNG_rows);
276 
277  png_destroy_read_struct(&PNG_reader, &PNG_info, &PNG_end_info);
278  fclose(PNG_file);
279 
280  return true;
281  }
282 
283  // adapted from SimpleImage
284  // http://onesadcookie.com/svn/SimpleImage/
285  int BindPNG(const String& filename, GLuint &texRef, GLuint &outWidth, GLuint &outHeight, GLint clampmode, GLint filtermode, bool optional)
286  {
287  png_byte* pngData;
288  png_uint_32 width, height;
289 
290  if (!LoadPNG(filename, pngData, width, height, optional))
291  {
292  //error was output by the load
293  return -1;
294  }
295 
296  outWidth = width;
297  outHeight = height;
298 
299  glGenTextures(1, &texRef);
300  glBindTexture(GL_TEXTURE_2D, texRef);
301 
302  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clampmode);
303  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clampmode);
304 
305  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
306  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtermode);
307  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtermode);
308 
309  glTexImage2D(
310  GL_TEXTURE_2D,
311  0,
312  GL_RGBA,
313  width,
314  height,
315  0,
316  GL_RGBA,
317  GL_UNSIGNED_BYTE,
318  pngData
319  );
320 
321  free(pngData);
322 
323  return 0;
324  }
325 #endif //_ANGEL_DISABLE_DEVIL
326 
327 const int GetTextureReference(const String& filename, GLint clampmode, GLint filtermode, bool optional)
328 {
329  bool cached = false;
330  TextureCacheEntry* currentCacheEntry = NULL;
331 
332  //check cache to see if it's loaded already
333  std::map<String,TextureCacheEntry>::iterator it = theTextureCache.find(filename);
334 
335  // See if we already have it in the cache.
336  if (it != theTextureCache.end())
337  {
338  // If we found it and it's not dirty, bail out with the index.
339  if (!it->second.dirty)
340  {
341  return it->second.textureIndex;
342  }
343 
344  // We found it, but it's dirty. We'll reload the texture below.
345  cached = true;
346  currentCacheEntry = &(it->second);
347  }
348 
349  GLuint texRef;
350  GLuint width = 0;
351  GLuint height = 0;
352  #if !_ANGEL_DISABLE_DEVIL
353  ILuint imgRef;
354 
355  ilGenImages(1, &imgRef);
356  ilBindImage(imgRef);
357 
358  // Load it up, log any errors if we need to
359  if (!ilLoadImage(filename.c_str()))
360  {
361  if (!optional)
362  {
363  HandleDevILErrors(filename);
364  }
365  else
366  {
367  ClearDevILErrors();
368  }
369 
370  ilDeleteImages(1, &imgRef);
371  return -1;
372  }
373 
374  // Store dimensions
375  width = ilGetInteger(IL_IMAGE_WIDTH);
376  height = ilGetInteger(IL_IMAGE_HEIGHT);
377 
378  // Send it to GL
379  texRef = BindTexImageWithClampAndFilter(clampmode, filtermode);
380 
381  // Clear it out
382  ilDeleteImages(1, &imgRef);
383 
384  // output any errors that happened during the binding/deleting
385  HandleDevILErrors(filename);
386  #else
387  int result = BindPNG(filename, texRef, width, height, clampmode, filtermode, optional);
388  if (result != 0)
389  {
390  return -1;
391  }
392  #endif //_ANGEL_DISABLE_DEVIL
393 
394  // If it was cached, clear the dirty flag so we don't try and load it again.
395  if (cached)
396  {
397  currentCacheEntry->dirty = false;
398  }
399  // If we're not cached, add a new entry.
400  else
401  {
402  TextureCacheEntry newEntry;
403  newEntry.filename = filename;
404  newEntry.clampMode = clampmode;
405  newEntry.filterMode = filtermode;
406  newEntry.textureIndex = texRef;
407  newEntry.width = width;
408  newEntry.height = height;
409  newEntry.dirty = false;
410  theTextureCache[filename] = newEntry;
411  }
412 
413  return texRef;
414 }
415 
416 const Vec2i GetTextureSize(const String& filename)
417 {
418  std::map<String,TextureCacheEntry>::iterator it = theTextureCache.find(filename);
419  if (it == theTextureCache.end())
420  {
421  return Vec2i(0, 0);
422  }
423  else
424  {
425  return Vec2i(it->second.width, it->second.height);
426  }
427 }
428 
429 bool GetRawImageData(const String& filename, std::vector<Color> &pixels)
430 {
431  #if _ANGEL_DISABLE_DEVIL
432  png_byte* pngData;
433  float* rawData;
434  png_uint_32 width, height;
435 
436  if (!LoadPNG(filename, pngData, width, height, true))
437  {
438  //error was output by the load
439  return false;
440  }
441 
442  // convert png ubyte data to float array
443  rawData = (float*)malloc(4 * width * height * sizeof(float));
444 
445  int j = 0;
446  for (int i = 0; i < (4 * width * height); i++)
447  {
448  rawData[j] = (float)pngData[i] / 255.0f;
449  j++;
450  }
451 
452  free(pngData);
453  #else
454  ILuint imgRef;
455 
456  ilGenImages(1, &imgRef);
457  ilBindImage(imgRef);
458 
459  // load image into DevIL
460  if (!ilLoadImage(filename.c_str()))
461  {
462  HandleDevILErrors(filename);
463  return false;
464  }
465 
466  // get image datums
467  unsigned int width = ilGetInteger(IL_IMAGE_WIDTH);
468  unsigned int height = ilGetInteger(IL_IMAGE_HEIGHT);
469 
470  // convert it to RGB floats for easy comparison
471  ilConvertImage(IL_RGBA, IL_FLOAT);
472 
473  // flip it if we need to
474  if (ilGetInteger(IL_IMAGE_ORIGIN) != IL_ORIGIN_LOWER_LEFT)
475  {
476  iluFlipImage();
477  }
478 
479  // grab the raw data
480  ILfloat* rawData = (ILfloat*)ilGetData();
481  #endif //_ANGEL_DISABLE_DEVIL
482 
483  // convert every pixel into colors
484  float pixR, pixG, pixB, pixA;
485  unsigned int pixOffset = 0;
486  for (int y=0; y < height; y++)
487  {
488  for (int x=0; x < width; x++)
489  {
490  pixOffset = (y * width * 4) + (x * 4);
491  pixR = rawData[pixOffset];
492  pixG = rawData[pixOffset + 1];
493  pixB = rawData[pixOffset + 2];
494  pixA = rawData[pixOffset + 3];
495  pixels.push_back(Color(pixR, pixG, pixB, pixA));
496  }
497  }
498 
499  // cleanup and return
500  #if _ANGEL_DISABLE_DEVIL
501  free(rawData);
502  #else
503  ilDeleteImages(1, &imgRef);
504  #endif
505 
506  return true;
507 }
508 
509 bool PixelsToPositions(const String& filename, Vector2List &positions, float gridSize, const Color& pixelColor, float tolerance)
510 {
511  #if _ANGEL_DISABLE_DEVIL
512  png_byte* pngData;
513  float* rawData;
514  png_uint_32 width, height;
515 
516  if (!LoadPNG(filename, pngData, width, height, true))
517  {
518  //error was output by the load
519  return false;
520  }
521 
522  // convert png ubyte data to float array
523  rawData = (float*)malloc(3 * width * height * sizeof(float));
524 
525  int j = 0;
526  for (int i = 0; i < (4 * width * height); i++)
527  {
528  if ((i+1) % 4)
529  {
530  rawData[j] = (float)pngData[i] / 255.0f;
531  j++;
532  }
533  }
534 
535  free(pngData);
536  #else
537  ILuint imgRef;
538 
539  ilGenImages(1, &imgRef);
540  ilBindImage(imgRef);
541 
542  // load image into DevIL
543  if (!ilLoadImage(filename.c_str()))
544  {
545  HandleDevILErrors(filename);
546  return false;
547  }
548 
549  // get image datums
550  unsigned int width = ilGetInteger(IL_IMAGE_WIDTH);
551  unsigned int height = ilGetInteger(IL_IMAGE_HEIGHT);
552 
553  // convert it to RGB floats for easy comparison
554  ilConvertImage(IL_RGB, IL_FLOAT);
555 
556  // flip it if we need to
557  if (ilGetInteger(IL_IMAGE_ORIGIN) != IL_ORIGIN_LOWER_LEFT)
558  {
559  iluFlipImage();
560  }
561 
562  // grab the raw data
563  ILfloat* rawData = (ILfloat*)ilGetData();
564  #endif //_ANGEL_DISABLE_DEVIL
565 
566  // check every pixel, see if it's within the tolerance of the target
567  float w = -((float)width)*gridSize/2.f;
568  float h = -((float)height)*gridSize/2.f;
569  Vector2 offset(w, h);
570  float pixR, pixG, pixB;
571  unsigned int pixOffset = 0;
572  for (int y=0; y < height; y++)
573  {
574  for (int x=0; x < width; x++)
575  {
576  pixOffset = (y * width * 3) + (x * 3);
577  pixR = rawData[pixOffset];
578  pixG = rawData[pixOffset + 1];
579  pixB = rawData[pixOffset + 2];
580  if ( fabs(pixR - pixelColor.R) <= tolerance
581  && fabs(pixG - pixelColor.G) <= tolerance
582  && fabs(pixB - pixelColor.B) <= tolerance
583  )
584  {
585  positions.push_back(offset + Vector2(x * gridSize, y * gridSize));
586  }
587  }
588  }
589 
590  // cleanup and return
591  #if _ANGEL_DISABLE_DEVIL
592  free(rawData);
593  #else
594  ilDeleteImages(1, &imgRef);
595  #endif
596 
597  return true;
598 }