1 ///////////////////////////////////////////////////////////////////////////////
2 //            Copyright (C) 2004-2011 by The Allacrost Project
3 //            Copyright (C) 2012-2016 by Bertram (Valyria Tear)
4 //                         All Rights Reserved
5 //
6 // This code is licensed under the GNU GPL version 2. It is free software
7 // and you may modify it and/or redistribute it under the terms of this license.
8 // See https://www.gnu.org/copyleft/gpl.html for details.
9 ///////////////////////////////////////////////////////////////////////////////
10 
11 /** ****************************************************************************
12 *** \file    image.cpp
13 *** \author  Tyler Olsen, roots@allacrost.org
14 *** \author  Yohann Ferreira, yohann ferreira orange fr
15 *** \brief   Source file for image classes
16 *** ***************************************************************************/
17 
18 #include "image.h"
19 
20 #include "script/script_read.h"
21 #include "engine/system.h"
22 #include "engine/video/color.h"
23 
24 #include "utils/utils_files.h"
25 #include "utils/utils_random.h"
26 #include "utils/utils_strings.h"
27 
28 #include "video.h"
29 
30 #include <SDL_image.h>
31 
32 using namespace vt_utils;
33 using namespace vt_video::private_video;
34 using namespace vt_common;
35 
36 namespace vt_video
37 {
38 
39 // -----------------------------------------------------------------------------
40 // ImageDescriptor class
41 // -----------------------------------------------------------------------------
42 
ImageDescriptor()43 ImageDescriptor::ImageDescriptor() :
44     _texture(nullptr),
45     _width(0.0f),
46     _height(0.0f),
47     _u1(0.0f),
48     _v1(0.0f),
49     _u2(1.0f),
50     _v2(1.0f),
51     _blend(false),
52     _unichrome_vertices(true),
53     _is_static(false),
54     _grayscale(false),
55     _smooth(true)
56 {
57     _color[0] = _color[1] = _color[2] = _color[3] = Color::white;
58 }
59 
~ImageDescriptor()60 ImageDescriptor::~ImageDescriptor()
61 {
62     // The destructor for the inherited class should have disabled grayscale mode
63     // If it didn't, the grayscale image might not have been properly dereferenced
64     // and/or removed from texture memory
65     if(_grayscale) {
66         IF_PRINT_WARNING(VIDEO_DEBUG) << "grayscale mode was still enabled when destructor was invoked "
67                                       << "-- possible memory leak" << std::endl;
68     }
69 
70     // Remove the reference to the original, colored texture
71     if(_texture != nullptr)
72         _RemoveTextureReference();
73 
74     _texture = nullptr;
75 }
76 
77 
78 
ImageDescriptor(const ImageDescriptor & copy)79 ImageDescriptor::ImageDescriptor(const ImageDescriptor &copy) :
80     _texture(copy._texture),
81     _width(copy._width),
82     _height(copy._height),
83     _u1(copy._u1),
84     _v1(copy._v1),
85     _u2(copy._u2),
86     _v2(copy._v2),
87     _blend(copy._blend),
88     _unichrome_vertices(copy._unichrome_vertices),
89     _is_static(copy._is_static),
90     _grayscale(copy._grayscale),
91     _smooth(copy._smooth)
92 {
93     _color[0] = copy._color[0];
94     _color[1] = copy._color[1];
95     _color[2] = copy._color[2];
96     _color[3] = copy._color[3];
97 
98     if(_texture != nullptr)
99         _texture->AddReference();
100 }
101 
102 
103 
operator =(const ImageDescriptor & copy)104 ImageDescriptor &ImageDescriptor::operator=(const ImageDescriptor &copy)
105 {
106     // Handle the case were a dumbass assigns an object to itself
107     if(this == &copy) {
108         return *this;
109     }
110 
111     _width = copy._width;
112     _height = copy._height;
113     _u1 = copy._u1;
114     _v1 = copy._v1;
115     _u2 = copy._u2;
116     _v2 = copy._v2;
117     _blend = copy._blend;
118     _unichrome_vertices = copy._unichrome_vertices;
119     _is_static = copy._is_static;
120     _grayscale = copy._grayscale;
121     _smooth = copy._smooth;
122     _color[0] = copy._color[0];
123     _color[1] = copy._color[1];
124     _color[2] = copy._color[2];
125     _color[3] = copy._color[3];
126 
127 
128     // Update the reference to the previous image texturee
129     if(_texture != nullptr && copy._texture != _texture) {
130         _RemoveTextureReference();
131     }
132 
133     if(copy._texture != nullptr) {
134         copy._texture->AddReference();
135     }
136 
137     _texture = copy._texture;
138     return *this;
139 }
140 
141 
142 
Clear()143 void ImageDescriptor::Clear()
144 {
145     // This will also remove a reference to the grayscale version of the image
146     // FIXME: This is triggering a crash when several images using the same file
147     // are in different states.
148     //if (_grayscale)
149     //    _DisableGrayscale();
150 
151     if(_texture != nullptr)
152         _RemoveTextureReference();
153 
154     _texture = nullptr;
155     _width = 0.0f;
156     _height = 0.0f;
157     _u1 = 0.0f;
158     _v1 = 0.0f;
159     _u2 = 1.0f;
160     _v2 = 1.0f;
161     _color[0] = _color[1] = _color[2] = _color[3] = Color::white;
162     _blend = false;
163     _unichrome_vertices = true;
164     _is_static = false;
165     _grayscale = false;
166     _smooth = true;
167 }
168 
169 
170 
SetColor(const Color & color)171 void ImageDescriptor::SetColor(const Color& color)
172 {
173     _color[0] = color;
174     _color[1] = color;
175     _color[2] = color;
176     _color[3] = color;
177 
178     _blend = !IsFloatEqual(color[3], 1.0f);
179     _unichrome_vertices = true;
180 }
181 
182 
183 
SetVertexColors(const Color & tl,const Color & tr,const Color & bl,const Color & br)184 void ImageDescriptor::SetVertexColors(const Color& tl,
185                                       const Color& tr,
186                                       const Color& bl,
187                                       const Color& br)
188 {
189     _color[0] = tl;
190     _color[1] = tr;
191     _color[2] = bl;
192     _color[3] = br;
193 
194     _blend = !(
195         IsFloatEqual(tl[3], 1.0f) && IsFloatEqual(tr[3], 1.0f) &&
196         IsFloatEqual(bl[3], 1.0f) && IsFloatEqual(br[3], 1.0f)
197     );
198 
199     _unichrome_vertices = (tl == tr && tl == bl && tl == br);
200 }
201 
GetImageInfo(const std::string & filename,uint32_t & rows,uint32_t & cols,uint32_t & bpp)202 bool ImageDescriptor::GetImageInfo(const std::string& filename,
203                                    uint32_t& rows,
204                                    uint32_t& cols,
205                                    uint32_t& bpp)
206 {
207     // Init with invalid data to ease early returns,
208     rows = 0;
209     cols = 0;
210     bpp = 0;
211 
212     SDL_Surface* surf = IMG_Load(filename.c_str());
213 
214     if (!surf) {
215         PRINT_ERROR << "Couldn't load image " << filename
216                     << ": " << IMG_GetError() << std::endl;
217         return false;
218     }
219 
220     rows = surf->h;
221     cols = surf->w;
222     bpp  = surf->format->BitsPerPixel * 8;
223     SDL_FreeSurface(surf);
224 
225     return true;
226 }
227 
LoadMultiImageFromElementSize(std::vector<StillImage> & images,const std::string & filename,const uint32_t elem_width,const uint32_t elem_height)228 bool ImageDescriptor::LoadMultiImageFromElementSize(std::vector<StillImage>& images,
229                                                     const std::string& filename,
230                                                     const uint32_t elem_width,
231                                                     const uint32_t elem_height)
232 {
233     // First retrieve the dimensions of the multi image (in pixels)
234     uint32_t img_height, img_width, bpp;
235     if (!GetImageInfo(filename, img_height, img_width, bpp)) {
236         PRINT_WARNING << "Couldn't load image file info: "
237                       << filename << std::endl;
238         return false;
239     }
240 
241     // Make sure that the element height and width divide evenly into the height and width of the multi image
242     if((img_height % elem_height) != 0 || (img_width % elem_width) != 0) {
243         IF_PRINT_WARNING(VIDEO_DEBUG) << "multi image size not evenly divisible by element size for multi image file: "
244                                       << filename << std::endl;
245         return false;
246     }
247 
248     // Determine the number of rows and columns of element images inside the multi image
249     uint32_t grid_rows = img_height / elem_height;
250     uint32_t grid_cols = img_width / elem_width;
251 
252     // If necessary, resize the images vector so that it is the same size as the number of element images which
253     // we will soon extract from the multi image
254     if(images.size() != grid_rows * grid_cols) {
255         images.resize(grid_rows * grid_cols);
256     }
257 
258     // If the width or height of the StillImages in the images vector were not specified (set to the default 0.0f),
259     // then set those sizes to the element width and height arguments (which are in number of pixels)
260     for(std::vector<StillImage>::iterator i = images.begin(); i < images.end(); ++i) {
261         if(IsFloatEqual(i->_height, 0.0f))
262             i->_height = static_cast<float>(elem_height);
263         if(IsFloatEqual(i->_width, 0.0f))
264             i->_width = static_cast<float>(elem_width);
265     }
266 
267     return _LoadMultiImage(images, filename, grid_rows, grid_cols);
268 } // bool ImageDescriptor::LoadMultiImageFromElementSize(...)
269 
270 
LoadMultiImageFromElementGrid(std::vector<StillImage> & images,const std::string & filename,const uint32_t grid_rows,const uint32_t grid_cols)271 bool ImageDescriptor::LoadMultiImageFromElementGrid(std::vector<StillImage>& images, const std::string &filename,
272         const uint32_t grid_rows, const uint32_t grid_cols)
273 {
274     if(!DoesFileExist(filename)) {
275         PRINT_WARNING << "Multi-image file not found: "
276                       << filename << std::endl;
277         return false;
278     }
279     // First retrieve the dimensions of the multi image (in pixels)
280     uint32_t img_height, img_width, bpp;
281     if (!GetImageInfo(filename, img_height, img_width, bpp)) {
282         PRINT_WARNING << "Couldn't load image file info: "
283                       << filename << std::endl;
284         return false;
285     }
286 
287     // Make sure that the number of grid rows and columns divide evenly into the image size
288     if((img_height % grid_rows) != 0 || (img_width % grid_cols) != 0) {
289         PRINT_WARNING << "Multi image size not evenly divisible by grid rows or columns for multi image file: " << filename << std::endl;
290         return false;
291     }
292 
293     // If necessary, resize the images vector so that it is the same size as the number of element images which
294     // we will soon extract from the multi image
295     if(images.size() != grid_rows * grid_cols) {
296         images.resize(grid_rows * grid_cols);
297     }
298 
299     // If the width or height of the StillImages in the images vector were not specified (set to the default 0.0f),
300     // then set those sizes to the element width and height arguments (which are in number of pixels)
301     float elem_width = static_cast<float>(img_width) / static_cast<float>(grid_cols);
302     float elem_height = static_cast<float>(img_height) / static_cast<float>(grid_rows);
303     for(auto i = images.begin(); i < images.end(); ++i) {
304         if(IsFloatEqual(i->_height, 0.0f))
305             i->_height = static_cast<float>(elem_height);
306         if(IsFloatEqual(i->_width, 0.0f))
307             i->_width = static_cast<float>(elem_width);
308     }
309 
310     return _LoadMultiImage(images, filename, grid_rows, grid_cols);
311 }
312 
SaveMultiImage(const std::vector<StillImage * > & images,const std::string & filename,const uint32_t grid_rows,const uint32_t grid_columns)313 bool ImageDescriptor::SaveMultiImage(const std::vector<StillImage *>& images,
314                                      const std::string& filename,
315                                      const uint32_t grid_rows,
316                                      const uint32_t grid_columns)
317 {
318     // Check there are elements to store
319     if(images.empty()) {
320         IF_PRINT_WARNING(VIDEO_DEBUG) << "images vector argument was empty when saving file: "
321                                       << filename << std::endl;
322         return false;
323     }
324 
325     // Check if the number of images is compatible with the number of rows and columns
326     const uint32_t grid_size = grid_rows * grid_columns;
327     if(images.size() < grid_size) {
328         IF_PRINT_WARNING(VIDEO_DEBUG) << "images vector argument did not contain enough images to save for file: "
329                                       << filename << std::endl;
330         return false;
331     }
332     if(images.size() > grid_size) {
333         IF_PRINT_WARNING(VIDEO_DEBUG) << "images vector argument had a size greater than the number of images to save for file: "
334                                       << filename << std::endl;
335         // NOTE: no return false for this case because we have enough images to continue
336     }
337 
338     // Check that all the images are non-nullptr and are of the same size
339     float img_width = images[0]->_width;
340     float img_height = images[0]->_height;
341     for(uint32_t i = 0; i < images.size(); i++) {
342         if(images[i] == nullptr || images[i]->_image_texture == nullptr) {
343             IF_PRINT_WARNING(VIDEO_DEBUG) << "nullptr StillImage or ImageElement was present in images vector argument when saving file: "
344                                           << filename << std::endl;
345             return false;
346         }
347         if(!IsFloatEqual(images[i]->_width,  img_width) ||
348            !IsFloatEqual(images[i]->_height, img_height)) {
349             IF_PRINT_WARNING(VIDEO_DEBUG) << "images contained in vector argument did not share the same dimensions" << std::endl;
350             return false;
351         }
352     }
353 
354     // Isolate the filename's extension and determine the type of image file we're saving
355     size_t ext_position = filename.rfind('.');
356     if(ext_position == std::string::npos) {
357         IF_PRINT_WARNING(VIDEO_DEBUG) << "failed to decipher file extension for filename: "
358                                       << filename << std::endl;
359         return false;
360     }
361 
362     std::string extension = std::string(filename, ext_position, filename.length() - ext_position);
363 
364     if(extension != ".png") {
365         IF_PRINT_WARNING(VIDEO_DEBUG) << "unsupported file extension: \"" << extension
366                                       << "\" for filename: " << filename << std::endl;
367         return false;
368     }
369 
370     // Initially, we need to grab the Image pointer of the first StillImage, the texture ID of its TextureSheet owner,
371     // and malloc enough memory for the entire sheet so that we can copy over the texture sheet from video memory to
372     // system memory.
373     ImageTexture *img = images[0]->_image_texture;
374     GLuint tex_id = img->texture_sheet->tex_id;
375 
376     ImageMemory texture;
377     ImageMemory save;
378     try {
379         texture.Resize(img->texture_sheet->width, img->texture_sheet->height,
380                        false);
381         save.Resize(static_cast<int32_t>(grid_columns * img_width),
382                     static_cast<int32_t>(grid_rows * img_height), false);
383     }
384     catch(std::exception& e) {
385         PRINT_ERROR << "failed to malloc enough memory to save new image file: "
386                     << filename << std::endl
387                     << e.what() << std::endl;
388         return false;
389     }
390 
391     TextureManager->_BindTexture(tex_id);
392     texture.GlGetTexImage();
393 
394     uint32_t i = 0; // i is used to count through the images vector to get the image to save
395     for(uint32_t x = 0; x < grid_rows; x++) {
396         for(uint32_t y = 0; y < grid_columns; y++) {
397             img = images[i]->_image_texture;
398 
399             // Check if this image has a different texture ID than the last. If it does, we need to re-grab the texture
400             // memory for the texture sheet that the new image is contained within and store it in the texture.pixels
401             // buffer, which is CPU system memory.
402             if(tex_id != img->texture_sheet->tex_id) {
403                 // Get new texture ID
404                 TextureManager->_BindTexture(img->texture_sheet->tex_id);
405                 tex_id = img->texture_sheet->tex_id;
406 
407                 // If the new texture is bigger, reallocate memory
408                 if(texture.GetSize2D() < img->texture_sheet->height * img->texture_sheet->width) {
409                     try {
410                         texture.Resize(img->texture_sheet->width,
411                                        img->texture_sheet->height, false);
412                     }
413                     catch(std::exception& e) {
414                         PRINT_ERROR << "failed to malloc enough memory to save new image file: "
415                                     << filename << std::endl
416                                     << e.what() << std::endl;
417                         return false;
418                     }
419                 }
420                 texture.GlGetTexImage();
421             }
422 
423             // Determine the part of the texture that we are interested in (the part that contains the current image we're saving)
424             save.CopyFrom(texture, texture.GetWidth() * img->y + img->x,
425                           img_width * grid_columns,
426                           (x * grid_columns * img_height + y) * img_width);
427             ++i;
428         }
429     }
430 
431     // save.pixels now contains all the image data we wish to save,
432     // so write it out to the new image file
433     bool success = save.SaveImage(filename);
434 
435     return success;
436 }
437 
DEBUG_PrintInfo()438 void ImageDescriptor::DEBUG_PrintInfo()
439 {
440     PRINT_WARNING << "__ImageDescriptor Properties__" << std::endl;
441     PRINT_WARNING << "* width:                " << _width << std::endl;
442     PRINT_WARNING << "* height:               " << _height << std::endl;
443     PRINT_WARNING << "* UV coordinates:        (" << _u1 << ", " << _v1 << "), (" << _u2 << ", " << _v2 << ")" << std::endl;
444     PRINT_WARNING << "* colors, RGBA format:  " << std::endl;
445     PRINT_WARNING << "  * TL                  " << _color[0].GetRed() << ", " << _color[0].GetGreen() << ", " << _color[0].GetBlue() << ", " << _color[0].GetAlpha() << std::endl;
446     PRINT_WARNING << "  * TR                  " << _color[1].GetRed() << ", " << _color[1].GetGreen() << ", " << _color[1].GetBlue() << ", " << _color[1].GetAlpha() << std::endl;
447     PRINT_WARNING << "  * BL                  " << _color[2].GetRed() << ", " << _color[2].GetGreen() << ", " << _color[2].GetBlue() << ", " << _color[2].GetAlpha() << std::endl;
448     PRINT_WARNING << "  * BR:                 " << _color[3].GetRed() << ", " << _color[3].GetGreen() << ", " << _color[3].GetBlue() << ", " << _color[3].GetAlpha() << std::endl;
449     PRINT_WARNING << "* static:               " << (_is_static ? "true" : "false") << std::endl;
450     PRINT_WARNING << "* grayscale:            " << (_grayscale ? "true" : "false") << std::endl;
451     PRINT_WARNING << std::endl;
452 }
453 
_RemoveTextureReference()454 void ImageDescriptor::_RemoveTextureReference()
455 {
456     if(_texture == nullptr) {
457         IF_PRINT_WARNING(VIDEO_DEBUG) << "_texture member was nullptr upon method invocation" << std::endl;
458         return;
459     }
460 
461     if(_texture->RemoveReference()) {
462         _texture->texture_sheet->RemoveTexture(_texture);
463 
464         // If the image exceeds 512 in either width or height, it has an un-shared texture sheet, which we
465         // should now delete that the image is being removed
466         if(_texture->width > 512 || _texture->height > 512) {
467             TextureManager->_RemoveSheet(_texture->texture_sheet);
468         }
469 //      else {
470 //          // TODO: Otherise simply mark the image as free in the texture sheet
471 //          texture->texture_sheet->FreeTexture(texture);
472 //      }
473         delete _texture;
474     }
475 
476     _texture = nullptr;
477 }
478 
_DrawOrientation() const479 void ImageDescriptor::_DrawOrientation() const
480 {
481     Context &current_context = VideoManager->_current_context;
482 
483     // Fix the image offset according to the current context alignment.
484     // Takes the image width/height and divides it by 2 (equal to * 0.5f)
485     // and applies the offset (left, right, center/top, bottom, center).
486     Position2D align_offset (((current_context.x_align + 1) * _width) * 0.5f
487                              * -current_context.coordinate_system.GetHorizontalDirection(),
488                              ((current_context.y_align + 1) * _height) * 0.5f
489                              * -current_context.coordinate_system.GetVerticalDirection());
490 
491     VideoManager->MoveRelative(align_offset.x, align_offset.y);
492 
493     // x/y draw offsets, which are a function of the flip draw flags,
494     // screen shaking, and the orientation of the current coordinate system
495     Position2D shake_offset;
496 
497     if(current_context.x_flip) {
498         shake_offset.x = _width;
499     }
500     if(current_context.y_flip) {
501         shake_offset.y = _height;
502     }
503 
504     if(VideoManager->IsScreenShaking()) {
505         // Calculate x and y draw offsets due to any screen shaking effects
506         shake_offset.x += VideoManager->_shake_offset.x
507                           * (current_context.coordinate_system.GetRight() - current_context.coordinate_system.GetLeft())
508                           / VIDEO_STANDARD_RES_WIDTH;
509         shake_offset.y += VideoManager->_shake_offset.y
510                           * (current_context.coordinate_system.GetTop() - current_context.coordinate_system.GetBottom())
511                           / VIDEO_STANDARD_RES_HEIGHT;
512     }
513 
514     VideoManager->MoveRelative(shake_offset.x * current_context.coordinate_system.GetHorizontalDirection(),
515                                shake_offset.y * current_context.coordinate_system.GetVerticalDirection());
516 
517     // x/y scale degrees
518     Vector2D scale(_width, _height);
519 
520     if(current_context.coordinate_system.GetHorizontalDirection() < 0.0f)
521         scale.x = -scale.x;
522     if(current_context.coordinate_system.GetVerticalDirection() < 0.0f)
523         scale.y = -scale.y;
524     VideoManager->Scale(scale.x, scale.y);
525 }
526 
_DrawTexture(const Color * draw_color) const527 void ImageDescriptor::_DrawTexture(const Color* draw_color) const
528 {
529     // The vertex positions.
530     float vertex_positions[] =
531     {
532         _u1, _v1, 0.0f, // Vertex One.
533         _u2, _v1, 0.0f, // Vertex Two.
534         _u2, _v2, 0.0f, // Vertex Three.
535         _u1, _v2, 0.0f  // Vertex Four.
536     };
537 
538     // The vertex texture coordinates.
539     float vertex_texture_coordinates[] =
540     {
541         0.0f, 1.0f, // Vertex One.
542         0.0f, 0.0f, // Vertex Two.
543         1.0f, 0.0f, // Vertex Three.
544         1.0f, 1.0f  // Vertex Four.
545     };
546 
547     // The vertex colors.
548     float vertex_colors[] =
549     {
550         1.0f, 1.0f, 1.0f, 1.0f, // Vertex One.
551         1.0f, 1.0f, 1.0f, 1.0f, // Vertex Two.
552         1.0f, 1.0f, 1.0f, 1.0f, // Vertex Three.
553         1.0f, 1.0f, 1.0f, 1.0f  // Vertex Four.
554     };
555 
556     // If no color array was passed, use the image's own vertex colors.
557     if (!draw_color) {
558         draw_color = _color;
559     }
560     assert(draw_color != nullptr);
561 
562     // Set the blending parameters.
563     if (VideoManager->_current_context.blend) {
564         VideoManager->EnableBlending();
565         if (VideoManager->_current_context.blend == 1) {
566             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Normal blending
567         } else {
568             glBlendFunc(GL_SRC_ALPHA, GL_ONE); // Additive blending
569         }
570     } else if (_blend) {
571         VideoManager->EnableBlending();
572         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Normal blending
573     } else {
574         VideoManager->DisableBlending();
575     }
576 
577     // The shader program.
578     gl::ShaderProgram* shader_program = nullptr;
579 
580     // If we have a valid image texture poiner, setup texture coordinates and the texture coordinate array.
581     if (_texture) {
582         // Set the texture coordinates.
583         float s0 = _texture->u1 + (_u1 * (_texture->u2 - _texture->u1));
584         float s1 = _texture->u1 + (_u2 * (_texture->u2 - _texture->u1));
585         float t0 = _texture->v1 + (_v1 * (_texture->v2 - _texture->v1));
586         float t1 = _texture->v1 + (_v2 * (_texture->v2 - _texture->v1));
587 
588         // Swap x texture coordinates if x flipping is enabled.
589         if (VideoManager->_current_context.x_flip) {
590             float temp = s0;
591             s0 = s1;
592             s1 = temp;
593         }
594 
595         // Swap y texture coordinates if y flipping is enabled.
596         if (VideoManager->_current_context.y_flip) {
597             float temp = t0;
598             t0 = t1;
599             t1 = temp;
600         }
601 
602         // The vertex texture coordinates.
603 
604         // Vertex One.
605         vertex_texture_coordinates[0] = s0;
606         vertex_texture_coordinates[1] = t1;
607 
608         // Vertex Two.
609         vertex_texture_coordinates[2] = s1;
610         vertex_texture_coordinates[3] = t1;
611 
612         // Vertex Three.
613         vertex_texture_coordinates[4] = s1;
614         vertex_texture_coordinates[5] = t0;
615 
616         // Vertex Four.
617         vertex_texture_coordinates[6] = s0;
618         vertex_texture_coordinates[7] = t0;
619 
620         // Enable texturing and bind the texture.
621         VideoManager->EnableTexture2D();
622         TextureManager->_BindTexture(_texture->texture_sheet->tex_id);
623         _texture->texture_sheet->Smooth(_smooth);
624 
625         // Load the sprite shader program.
626         shader_program = VideoManager->LoadShaderProgram(gl::shader_programs::Sprite);
627         assert(shader_program != nullptr);
628     } else {
629         //
630         // Otherwise there is no image texture, so we're drawing pure color on the vertices.
631         //
632 
633         // Disable texturing as we're using pure color.
634         VideoManager->DisableTexture2D();
635 
636         // Load the solid shader program.
637         shader_program = VideoManager->LoadShaderProgram(gl::shader_programs::Solid);
638         assert(shader_program != nullptr);
639     }
640 
641     if (_unichrome_vertices) {
642         // Draw the image.
643         VideoManager->DrawSprite(shader_program, vertex_positions, vertex_texture_coordinates, vertex_colors, *draw_color);
644     } else {
645         // For each of the four vertices.
646         for (unsigned i = 0; i < 4; ++i)
647         {
648             // Get the vertex's color.
649             vt_video::Color color = draw_color[i];
650 
651             // Update the vertex colors.
652             vertex_colors[(i * 4) + 0] = color[0];
653             vertex_colors[(i * 4) + 1] = color[1];
654             vertex_colors[(i * 4) + 2] = color[2];
655             vertex_colors[(i * 4) + 3] = color[3];
656         }
657 
658         // Unload the shader program.
659         VideoManager->UnloadShaderProgram();
660 
661         // Load the solid shader program.
662         shader_program = VideoManager->LoadShaderProgram(gl::shader_programs::Solid);
663         assert(shader_program != nullptr);
664 
665         // Draw the image.
666         VideoManager->DrawSprite(shader_program, vertex_positions, vertex_texture_coordinates, vertex_colors);
667     }
668 
669     // Unload the shader program.
670     VideoManager->UnloadShaderProgram();
671 }
672 
_LoadMultiImage(std::vector<StillImage> & images,const std::string & filename,const uint32_t grid_rows,const uint32_t grid_cols)673 bool ImageDescriptor::_LoadMultiImage(std::vector<StillImage>& images, const std::string &filename,
674                                       const uint32_t grid_rows, const uint32_t grid_cols)
675 {
676     uint32_t current_image;
677     uint32_t x, y;
678 
679     bool need_load = false;
680 
681     // 1D vectors storing info for each image element
682     std::vector<std::string> tags;
683     std::vector<bool> loaded;
684 
685     // Construct the tags for each image element and figure out which elements are not
686     // already in texture memory and need to be loaded
687     size_t elements = grid_rows * grid_cols;
688     tags.reserve(elements);
689     loaded.reserve(elements);
690     for(x = 0; x < grid_rows; x++) {
691         for(y = 0; y < grid_cols; y++) {
692             tags.push_back("<X" + NumberToString(x) + "_" + NumberToString(grid_rows) + ">" +
693                            "<Y" + NumberToString(y) + "_" + NumberToString(grid_cols) + ">");
694 
695             if(TextureManager->_IsImageTextureRegistered(filename + tags.back())) {
696                 loaded.push_back(true);
697             } else {
698                 loaded.push_back(false);
699                 need_load = true;
700             }
701         }
702     }
703 
704     // If the image elements are not all loaded, then load the multi image file
705     // from disk and create enough memory to copy over individual sub-image elements from it
706     ImageMemory multi_image;
707     ImageMemory sub_image;
708     if(need_load) {
709         if(multi_image.LoadImage(filename) == false) {
710             IF_PRINT_WARNING(VIDEO_DEBUG) << "Failed to load multi image file: " << filename << std::endl;
711             return false;
712         }
713 
714         try {
715             sub_image.Resize(multi_image.GetWidth() / grid_cols, multi_image.GetHeight() / grid_rows, false);
716         }
717         catch(std::exception& e)
718         {
719             PRINT_ERROR << "Failed to malloc memory for multi image file: " << filename << std::endl
720                         << e.what() << std::endl;
721             return false;
722         }
723     }
724 
725     // One by one, get the subimages
726     current_image = 0;
727     for(x = 0; x < grid_rows; x++) {
728         for(y = 0; y < grid_cols; y++) {
729             ImageTexture *img;
730 
731             // If this image already exists in a texture sheet somewhere, add a reference to it
732             // and add a new ImageElement to the current StillImage
733             if(loaded[current_image]) {
734                 img = TextureManager->_GetImageTexture(filename + tags[current_image]);
735 
736                 if(img == nullptr) {
737                     IF_PRINT_WARNING(VIDEO_DEBUG) << "A nullptr image was found in the TextureManager's _images container "
738                                                   << "-- aborting multi image load operation" << std::endl;
739                     return false;
740                 }
741 
742                 images.at(current_image)._filename = filename;
743                 images.at(current_image)._texture = img;
744                 images.at(current_image)._image_texture = img;
745             }
746 
747             // We have to first extract this image from the larger multi image and add it to a texture sheet.
748             // Then we can add the image data to the StillImage being constructed
749             else {
750                 images.at(current_image)._filename = filename;
751 
752                 sub_image.CopyFrom(multi_image,
753                                    multi_image.GetWidth() * (x * multi_image.GetHeight() / grid_rows)
754                                        + multi_image.GetWidth() * y / grid_cols);
755 
756                 img = new ImageTexture(filename, tags[current_image], sub_image.GetWidth(), sub_image.GetHeight());
757 
758                 // Try to insert the image in a texture sheet
759                 TexSheet *sheet = TextureManager->_InsertImageInTexSheet(img, sub_image, images.at(current_image)._is_static);
760 
761                 if(sheet == nullptr) {
762                     IF_PRINT_WARNING(VIDEO_DEBUG) << "Call to TextureController::_InsertImageInTexSheet failed -- " <<
763                                                   "aborting multi image load operation" << std::endl;
764                     delete img;
765                     return false;
766                 }
767 
768                 images.at(current_image)._texture = img;
769                 images.at(current_image)._image_texture = img;
770             }
771 
772             img->AddReference();
773 
774             // Finally, do a grayscale conversion for the image if grayscale mode is enabled
775             if(images.at(current_image)._grayscale) {
776                 // Set _grayscale to false so that the call doesn't think that the grayscale image is already loaded
777                 // It will be set back to true by the _EnableGrayscale call
778                 images.at(current_image)._grayscale = false;
779                 images.at(current_image)._EnableGrayscale();
780             }
781 
782             current_image++;
783         } // for (y = 0; y < grid_cols; y++)
784     } // for (x = 0; x < grid_rows; x++)
785 
786     return true;
787 }
788 
789 // -----------------------------------------------------------------------------
790 // StillImage class
791 // -----------------------------------------------------------------------------
792 
StillImage(const bool grayscale)793 StillImage::StillImage(const bool grayscale) :
794     ImageDescriptor(),
795     _image_texture(nullptr),
796     _offset(0.0f, 0.0f)
797 {
798     Clear();
799     _grayscale = grayscale;
800 }
801 
~StillImage()802 StillImage::~StillImage()
803 {
804     Clear();
805 }
806 
Clear()807 void StillImage::Clear()
808 {
809     ImageDescriptor::Clear(); // This call will remove the texture reference for us
810     _filename.clear();
811     _image_texture = nullptr;
812     _offset.x = 0.0f;
813     _offset.y = 0.0f;
814 }
815 
Load(const std::string & filename)816 bool StillImage::Load(const std::string &filename)
817 {
818     // Delete everything previously stored in here
819     if(_image_texture != nullptr) {
820         _RemoveTextureReference();
821         _image_texture = nullptr;
822         _width = 0.0f;
823         _height = 0.0f;
824         _offset.x = 0.0f;
825         _offset.y = 0.0f;
826     }
827 
828     _filename = filename;
829 
830     // TEMP: This is a temporary hack to support procedural images by using empty filenames. It should be removed later
831     if(filename.empty()) {
832         return true;
833     }
834 
835     // 1. Check if an image with the same filename has already been loaded.
836     // If so, point to that and increment its reference
837     _image_texture = TextureManager->_GetImageTexture(_filename);
838     if(_image_texture != nullptr) {
839         _texture = _image_texture;
840 
841         if(_image_texture == nullptr) {
842             IF_PRINT_WARNING(VIDEO_DEBUG) << "recovered a nullptr image inside the TextureManager's image map: "
843                                           << _filename << std::endl;
844             return false;
845         }
846 
847         // If the width or height of this object is 0.0, use the pixel width/height of the image texture
848         if(IsFloatEqual(_width, 0.0f))
849             _width = static_cast<float>(_image_texture->width);
850         if(IsFloatEqual(_height, 0.0f))
851             _height = static_cast<float>(_image_texture->height);
852 
853         _texture->AddReference();
854         return true;
855     }
856 
857     // 2. The image file needs to be loaded from disk
858     ImageMemory img_data;
859     if(img_data.LoadImage(_filename) == false) {
860         IF_PRINT_WARNING(VIDEO_DEBUG) << "call to ImageMemory::LoadImage() failed for file: " << _filename << std::endl;
861         return false;
862     }
863 
864     // Create a new texture image and store it in a texture sheet. If the _grayscale member of this class is true,
865     // we first load the color copy of the image to a texture sheet. Then we'll convert the image data to grayscale
866     // and save that image data to texture memory as well
867     _image_texture = new ImageTexture(_filename, "", img_data.GetWidth(), img_data.GetHeight());
868     _texture = _image_texture;
869 
870     if(TextureManager->_InsertImageInTexSheet(_image_texture, img_data, _is_static) == nullptr) {
871         IF_PRINT_WARNING(VIDEO_DEBUG) << "call to TextureController::_InsertImageInTexSheet() failed for file: " << _filename << std::endl;
872         delete _image_texture;
873         _image_texture = nullptr;
874         _texture = nullptr;
875         return false;
876     }
877 
878     _image_texture->AddReference();
879 
880     // If width or height members are zero, set them to the dimensions of the image data (which are in number of pixels)
881     if(IsFloatEqual(_width, 0.0f))
882         _width = static_cast<float>(img_data.GetWidth());
883 
884     if(IsFloatEqual(_height, 0.0f))
885         _height = static_cast<float>(img_data.GetHeight());
886 
887     // If we don't need to create a grayscale version, we finished successfully
888     if(_grayscale == false) {
889         return true;
890     }
891 
892     // 3. If we reached this point, we must now create a grayscale version of this image
893     img_data.ConvertToGrayscale();
894     ImageTexture *gray_image = new ImageTexture(_filename, "<G>", img_data.GetWidth(), img_data.GetHeight());
895     if(TextureManager->_InsertImageInTexSheet(gray_image, img_data, _is_static) == nullptr) {
896         IF_PRINT_WARNING(VIDEO_DEBUG) << "call to TextureController::_InsertImageInTexSheet() failed for file: " << _filename << std::endl;
897 
898         TextureManager->_UnregisterImageTexture(gray_image);
899         delete gray_image;
900         _RemoveTextureReference(); // sets _texture to nullptr
901         _image_texture = nullptr;
902         return false;
903     }
904 
905     _image_texture = gray_image;
906     _texture = _image_texture;
907     _image_texture->AddReference();
908 
909     return true;
910 }
911 
Draw(const Color & draw_color) const912 void StillImage::Draw(const Color &draw_color) const
913 {
914     // Don't draw anything if this image is completely transparent (invisible).
915     if (IsFloatEqual(draw_color[3], 0.0f))
916         return;
917 
918     VideoManager->PushMatrix();
919 
920     if (_offset.x != 0.0f || _offset.y != 0.0f)
921         VideoManager->MoveRelative(_offset.x, _offset.y);
922 
923     _DrawOrientation();
924 
925     // Used to determine if the image color should be modulated by any degree due to screen fading effects.
926     if(draw_color == Color::white) {
927         _DrawTexture(_color);
928     }
929     else {
930         // The color of each vertex point.
931         Color modulated_colors[4];
932         modulated_colors[0] = _color[0] * draw_color;
933         modulated_colors[1] = _color[1] * draw_color;
934         modulated_colors[2] = _color[2] * draw_color;
935         modulated_colors[3] = _color[3] * draw_color;
936 
937         _DrawTexture(modulated_colors);
938     }
939 
940     VideoManager->PopMatrix();
941 }
942 
Save(const std::string & filename) const943 bool StillImage::Save(const std::string &filename) const
944 {
945     if(_image_texture == nullptr) {
946         IF_PRINT_WARNING(VIDEO_DEBUG) << "attempted to save an image that had no texture reference" << std::endl;
947         return false;
948     }
949 
950     // Isolate the file extension
951     size_t ext_position = filename.rfind('.');
952 
953     if(ext_position == std::string::npos) {
954         IF_PRINT_WARNING(VIDEO_DEBUG) << "could not decipher file extension for file: " << filename << std::endl;
955         return false;
956     }
957 
958     std::string extension = std::string(filename, ext_position, filename.length() - ext_position);
959 
960     if(extension != ".png") {
961         IF_PRINT_WARNING(VIDEO_DEBUG) << "unsupported file extension \"" << extension << "\" for file: " << filename << std::endl;
962         return false;
963     }
964 
965     ImageMemory buffer;
966     buffer.CopyFromImage(_image_texture);
967     return buffer.SaveImage(filename);
968 }
969 
_EnableGrayscale()970 void StillImage::_EnableGrayscale()
971 {
972     if(_grayscale)
973         return;
974 
975     _grayscale = true;
976 
977     // If no image texture is available we are done here (when Load() is next called, grayscale will automatically be enabled)
978     if(_image_texture == nullptr)
979         return;
980 
981     // Check if a grayscale version of this image already exists in texture memory and if so, update the ImageTexture pointer and reference
982     std::string search_key = _filename + _image_texture->tags + "<G>";
983     std::string tags = _image_texture->tags;
984     ImageTexture *temp_texture = _image_texture;
985     if((_image_texture = TextureManager->_GetImageTexture(search_key)) != nullptr) {
986         // NOTE: We do not decrement the reference to the colored image, because we want to guarantee that
987         // it remains referenced in texture memory while its grayscale counterpart is being used
988         _texture = _image_texture;
989         _image_texture->AddReference();
990         return;
991     }
992 
993     // If no grayscale version exists, create a copy of the image, convert it to grayscale, and add the gray copy to texture memory
994     ImageMemory gray_img;
995     gray_img.CopyFromImage(temp_texture);
996     gray_img.ConvertToGrayscale();
997 
998     ImageTexture* new_img = new ImageTexture(_filename, tags + "<G>", gray_img.GetWidth(), gray_img.GetHeight());
999 
1000     if(TextureManager->_InsertImageInTexSheet(new_img, gray_img, _is_static) == nullptr) {
1001         IF_PRINT_WARNING(VIDEO_DEBUG) << "failed to insert new grayscale image into texture sheet" << std::endl;
1002         delete new_img;
1003 
1004         return;
1005     }
1006 
1007     _image_texture = new_img;
1008     _texture = _image_texture;
1009     _image_texture->AddReference();
1010 }
1011 
_DisableGrayscale()1012 void StillImage::_DisableGrayscale()
1013 {
1014     if(_grayscale == false)
1015         return;
1016 
1017     _grayscale = false;
1018 
1019     // If no image data is loaded, we're finished
1020     if(_image_texture == nullptr)
1021         return;
1022 
1023     std::string search_key = _image_texture->filename + _image_texture->tags.substr(0, _image_texture->tags.length() - 3);
1024     if((_image_texture = TextureManager->_GetImageTexture(search_key)) == nullptr) {
1025         PRINT_WARNING << "non-grayscale version of image was not found in texture memory: "
1026                       << GetFilename() << std::endl;
1027         return;
1028     }
1029 
1030     // Remove the reference to the grayscale version and grab the reference to the original color image
1031     _RemoveTextureReference();
1032 
1033     // No reference change is needed for the color image, since the color texture did not have a reference
1034     // decrement when the grayscale version was enabled
1035     _texture = _image_texture;
1036 }
1037 
SetWidthKeepRatio(float width)1038 void StillImage::SetWidthKeepRatio(float width)
1039 {
1040     float img_ratio = (_width > 0.0f ? width / _width : 0.0f);
1041     SetDimensions(width, _height * img_ratio);
1042 }
1043 
SetHeightKeepRatio(float height)1044 void StillImage::SetHeightKeepRatio(float height)
1045 {
1046     float img_ratio = (_height > 0.0f ? height / _height : 0.0f);
1047     SetDimensions(_width * img_ratio, height);
1048 }
1049 
1050 // -----------------------------------------------------------------------------
1051 // AnimatedImage class
1052 // -----------------------------------------------------------------------------
1053 
AnimatedImage(const bool grayscale)1054 AnimatedImage::AnimatedImage(const bool grayscale)
1055 {
1056     Clear();
1057     _grayscale = grayscale;
1058     _blended_animation = false;
1059 }
1060 
AnimatedImage(float width,float height,bool grayscale)1061 AnimatedImage::AnimatedImage(float width, float height, bool grayscale)
1062 {
1063     Clear();
1064     _width = width;
1065     _height = height;
1066     _grayscale = grayscale;
1067     _blended_animation = false;
1068 }
1069 
Clear()1070 void AnimatedImage::Clear()
1071 {
1072     ImageDescriptor::Clear();
1073     _frame_index = 0;
1074     _frame_counter = 0;
1075     // clear all animation frame images
1076     for(std::vector<AnimationFrame>::iterator it = _frames.begin(); it != _frames.end(); ++it)
1077         (*it).image.Clear();
1078     _frames.clear();
1079     _animation_time = 0;
1080 }
1081 
LoadFromAnimationScript(const std::string & filename)1082 bool AnimatedImage::LoadFromAnimationScript(const std::string &filename)
1083 {
1084     vt_script::ReadScriptDescriptor image_script;
1085     if(!image_script.OpenFile(filename))
1086         return false;
1087 
1088     if(!image_script.DoesTableExist("animation")) {
1089         PRINT_WARNING << "No animation table in " << filename << std::endl;
1090         image_script.CloseFile();
1091         return false;
1092     }
1093 
1094     image_script.OpenTable("animation");
1095 
1096     std::string image_filename = image_script.ReadString("image_filename");
1097     if (image_script.DoesBoolExist("blended_animation"))
1098         _blended_animation = image_script.ReadBool("blended_animation");
1099 
1100     if(!vt_utils::DoesFileExist(image_filename)) {
1101         PRINT_WARNING << "The image file doesn't exist: " << image_filename << std::endl;
1102         image_script.CloseTable();
1103         image_script.CloseFile();
1104         return false;
1105     }
1106 
1107     uint32_t rows = image_script.ReadUInt("rows");
1108     uint32_t columns = image_script.ReadUInt("columns");
1109 
1110     if(!image_script.DoesTableExist("frames")) {
1111         image_script.CloseTable();
1112         image_script.CloseFile();
1113         PRINT_WARNING << "No 'frames' table in file: " << filename << std::endl;
1114         return false;
1115     }
1116 
1117     std::vector<StillImage> image_frames;
1118     // Load the image data
1119     if(!ImageDescriptor::LoadMultiImageFromElementGrid(image_frames, image_filename, rows, columns)) {
1120         PRINT_WARNING << "Couldn't load elements from image file: " << image_filename
1121                       << " (in file: " << filename << ")" << std::endl;
1122         return false;
1123     }
1124 
1125     // Load requested dimensions and setup default ones if not set
1126     float frame_width = image_script.ReadFloat("frame_width");
1127     float frame_height = image_script.ReadFloat("frame_height");
1128 
1129     if(IsFloatEqual(frame_width, 0.0f) && IsFloatEqual(frame_height, 0.0f)) {
1130         // If the animation dimensions are not set, we're using the first frame size.
1131         frame_width = image_frames.begin()->GetWidth();
1132         frame_height = image_frames.begin()->GetHeight();
1133     }
1134 
1135     std::vector<uint32_t> frames_ids;
1136     std::vector<uint32_t> frames_duration;
1137     std::vector<std::pair<float, float> > frames_offsets;
1138 
1139     image_script.OpenTable("frames");
1140     uint32_t num_frames = image_script.GetTableSize();
1141     for(uint32_t frames_table_id = 0;  frames_table_id < num_frames; ++frames_table_id) {
1142         image_script.OpenTable(frames_table_id);
1143 
1144         int32_t frame_id = image_script.ReadInt("id");
1145         int32_t frame_duration = image_script.ReadInt("duration");
1146 
1147         // Loads the frame offsets
1148         float x_offset = 0.0f;
1149         float y_offset = 0.0f;
1150         if (image_script.DoesFloatExist("x_offset"))
1151             x_offset = image_script.ReadFloat("x_offset");
1152         if (image_script.DoesFloatExist("y_offset"))
1153             y_offset = image_script.ReadFloat("y_offset");
1154 
1155         if(frame_id < 0 || frame_duration < 0 || frame_id >= (int32_t)image_frames.size()) {
1156             PRINT_WARNING << "Invalid frame (" << frames_table_id << ") in file: "
1157                           << filename << std::endl;
1158             PRINT_WARNING << "Request for frame id: " << frame_id << ", duration: "
1159                           << frame_duration << " is not possible." << std::endl;
1160             continue;
1161         }
1162 
1163         frames_ids.push_back((uint32_t)frame_id);
1164         frames_duration.push_back((uint32_t)frame_duration);
1165         frames_offsets.push_back(std::make_pair(x_offset, y_offset));
1166 
1167         image_script.CloseTable(); // frames[frame_table_id] table
1168     }
1169     image_script.CloseTable(); // frames table
1170 
1171     image_script.CloseAllTables();
1172     image_script.CloseFile();
1173 
1174     // Actually create the animation data
1175     _frames.clear();
1176     ResetAnimation();
1177     // First copy the image data raw
1178     for(uint32_t i = 0; i < frames_ids.size(); ++i)
1179         AddFrame(image_frames[frames_ids[i]], frames_duration[i]);
1180 
1181     // Once copied and only at that time, setup the data offsets to avoid the case
1182     // where the offsets might be applied several times on the same origin image,
1183     // breaking the offset resizing when the dimensions are different from the original image.
1184     for (uint32_t i = 0; i < _frames.size(); ++i)
1185         _frames[i].image.SetDrawOffsets(frames_offsets[i].first, frames_offsets[i].second);
1186 
1187     // Then only, set the dimensions
1188     SetDimensions(frame_width, frame_height);
1189 
1190     return true;
1191 }
1192 
1193 
LoadFromFrameSize(const std::string & filename,const std::vector<uint32_t> & timings,const uint32_t frame_width,const uint32_t frame_height,const uint32_t trim)1194 bool AnimatedImage::LoadFromFrameSize(const std::string &filename, const std::vector<uint32_t>& timings,
1195                                       const uint32_t frame_width, const uint32_t frame_height, const uint32_t trim)
1196 {
1197     // Make the multi image call
1198     // TODO: Handle the case where the _grayscale member is true so all frames are loaded in grayscale format
1199     std::vector<StillImage> image_frames;
1200     if(ImageDescriptor::LoadMultiImageFromElementSize(image_frames, filename, frame_width, frame_height) == false) {
1201         return false;
1202     }
1203 
1204     if(trim >= image_frames.size()) {
1205         IF_PRINT_WARNING(VIDEO_DEBUG) << "attempt to trim away more frames than requested to load for file: " << filename << std::endl;
1206         return false;
1207     }
1208 
1209     if(timings.size() < (image_frames.size() - trim)) {
1210         IF_PRINT_WARNING(VIDEO_DEBUG) << "not enough timing data to fill frames grid when loading file: " << filename << std::endl;
1211         return false;
1212     }
1213 
1214     _frames.clear();
1215     ResetAnimation();
1216 
1217     // Add the loaded frame image and timing information
1218     for(uint32_t i = 0; i < image_frames.size() - trim; i++) {
1219         image_frames[i].SetDimensions(_width, _height);
1220         AddFrame(image_frames[i], timings[i]);
1221         if(timings[i] == 0) {
1222             IF_PRINT_WARNING(VIDEO_DEBUG) << "added a frame time value of zero when loading file: " << filename << std::endl;
1223         }
1224     }
1225 
1226     return true;
1227 }
1228 
LoadFromFrameGrid(const std::string & filename,const std::vector<uint32_t> & timings,const uint32_t frame_rows,const uint32_t frame_cols,const uint32_t trim)1229 bool AnimatedImage::LoadFromFrameGrid(const std::string &filename, const std::vector<uint32_t>& timings,
1230                                       const uint32_t frame_rows, const uint32_t frame_cols, const uint32_t trim)
1231 {
1232     if(trim >= frame_rows * frame_cols) {
1233         IF_PRINT_WARNING(VIDEO_DEBUG) << "attempt to trim away more frames than requested to load for file: " << filename << std::endl;
1234         return false;
1235     }
1236 
1237     if(timings.size() < (frame_rows * frame_cols - trim)) {
1238         IF_PRINT_WARNING(VIDEO_DEBUG) << "not enough timing data to fill frames grid when loading file: " << filename << std::endl;
1239         return false;
1240     }
1241 
1242     _frames.clear();
1243     ResetAnimation();
1244 
1245     // Make the multi image call
1246     // TODO: Handle the case where the _grayscale member is true so all frames are loaded in grayscale format
1247     std::vector<StillImage> image_frames;
1248     if(ImageDescriptor::LoadMultiImageFromElementGrid(image_frames, filename, frame_rows, frame_cols) == false) {
1249         return false;
1250     }
1251 
1252     if(image_frames.empty())
1253         return false;
1254 
1255     // If the animation dimensions are not set yet, we're using the first frame
1256     // size.
1257     if(IsFloatEqual(_width, 0.0f) && IsFloatEqual(_height, 0.0f)) {
1258         _width = image_frames.begin()->GetWidth();
1259         _height = image_frames.begin()->GetHeight();
1260     }
1261 
1262     // Add the loaded frame image and timing information
1263     for(uint32_t i = 0; i < frame_rows * frame_cols - trim; i++) {
1264         image_frames[i].SetDimensions(_width, _height);
1265         AddFrame(image_frames[i], timings[i]);
1266         if(timings[i] == 0) {
1267             IF_PRINT_WARNING(VIDEO_DEBUG) << "added zero frame time for an image frame when loading file: " << filename << std::endl;
1268         }
1269     }
1270 
1271     return true;
1272 }
1273 
Draw(const Color & draw_color) const1274 void AnimatedImage::Draw(const Color& draw_color) const
1275 {
1276     if(_frames.empty()) {
1277         IF_PRINT_WARNING(VIDEO_DEBUG) << "no frames were loaded into the AnimatedImage object" << std::endl;
1278         return;
1279     }
1280 
1281     if (!_blended_animation || _frames[_frame_index].frame_time <= 4
1282             || _frame_counter > _frames[_frame_index].frame_time / 4) {
1283         _frames[_frame_index].image.Draw(draw_color);
1284         return;
1285     }
1286 
1287     // Draw the blended animation frames.
1288     Color blended_color = draw_color;
1289     float frame_time = static_cast<float>(_frames[_frame_index].frame_time);
1290     float frame_counter = static_cast<float>(_frame_counter);
1291     uint32_t previous_frame_index = _frame_index == 0 ? _frames.size() - 1 : _frame_index - 1;
1292     float current_frame_alpha = frame_counter / (frame_time / 4.0f);
1293     float previous_frame_alpha = 1.0f - current_frame_alpha;
1294 
1295     blended_color.SetAlpha(previous_frame_alpha);
1296     _frames[previous_frame_index].image.Draw(blended_color);
1297 
1298     blended_color.SetAlpha(current_frame_alpha);
1299     _frames[_frame_index].image.Draw(blended_color);
1300 }
1301 
Save(const std::string & filename,uint32_t grid_rows,uint32_t grid_cols) const1302 bool AnimatedImage::Save(const std::string& filename,
1303                          uint32_t grid_rows,
1304                          uint32_t grid_cols) const
1305 {
1306     std::vector<StillImage *> image_frames;
1307     image_frames.reserve(_frames.size());
1308     for(uint32_t i = 0; i < _frames.size(); i++) {
1309         image_frames.push_back(const_cast<StillImage *>(&(_frames[i].image)));
1310     }
1311 
1312     if(grid_rows == 0 || grid_cols == 0) {
1313         return ImageDescriptor::SaveMultiImage(image_frames, filename, 1,
1314                                                _frames.size());
1315     }
1316     return ImageDescriptor::SaveMultiImage(image_frames, filename,
1317                                            grid_rows, grid_cols);
1318 }
1319 
_EnableGrayscale()1320 void AnimatedImage::_EnableGrayscale()
1321 {
1322     if(_grayscale) {
1323         IF_PRINT_WARNING(VIDEO_DEBUG) << "grayscale mode was already enabled when function was invoked" << std::endl;
1324         return;
1325     }
1326 
1327     _grayscale = true;
1328     for(uint32_t i = 0; i < _frames.size(); i++) {
1329         _frames[i].image._EnableGrayscale();
1330     }
1331 }
1332 
_DisableGrayscale()1333 void AnimatedImage::_DisableGrayscale()
1334 {
1335     if(!_grayscale) {
1336         IF_PRINT_WARNING(VIDEO_DEBUG) << "grayscale mode was already disabled when function was invoked" << std::endl;
1337         return;
1338     }
1339 
1340     _grayscale = false;
1341     for(uint32_t i = 0; i < _frames.size(); i++) {
1342         _frames[i].image._DisableGrayscale();
1343     }
1344 }
1345 
Update(uint32_t elapsed_time)1346 void AnimatedImage::Update(uint32_t elapsed_time)
1347 {
1348     if(_frames.size() <= 1)
1349         return;
1350 
1351     // If the frame time is 0, it means the frame is a terminator and should be displayed 'forever'.
1352     if (_frames[_frame_index].frame_time == 0) {
1353         return;
1354     }
1355 
1356     // Get the amount of milliseconds that have pass since the last display
1357     uint32_t ms_change = (elapsed_time == 0) ? vt_system::SystemManager->GetUpdateTime() : elapsed_time;
1358     _frame_counter += ms_change;
1359 
1360     // If the frame time has expired, update the frame index and counter.
1361     while(_frame_counter >= _frames[_frame_index].frame_time) {
1362         // If the frame time is 0, it means the frame is a terminator and should be displayed 'forever'.
1363         if (_frames[_frame_index].frame_time == 0) {
1364             return;
1365         }
1366 
1367         // Remove the time spent on the current frame before incrementing it.
1368         ms_change = _frame_counter - _frames[_frame_index].frame_time;
1369         _frame_index++;
1370         if(_frame_index >= _frames.size()) {
1371             _frame_index = 0;
1372         }
1373 
1374         // Add the time left already spent on the new frame.
1375         _frame_counter = ms_change;
1376     }
1377 }
1378 
AddFrame(const std::string & frame,uint32_t frame_time)1379 bool AnimatedImage::AddFrame(const std::string &frame, uint32_t frame_time)
1380 {
1381     StillImage img;
1382     img.SetStatic(_is_static);
1383     img.SetVertexColors(_color[0], _color[1], _color[2], _color[3]);
1384     if(!img.Load(frame, _width, _height)) {
1385         return false;
1386     }
1387 
1388     AnimationFrame new_frame;
1389     new_frame.frame_time = frame_time;
1390     new_frame.image = img;
1391     _frames.push_back(new_frame);
1392     _animation_time += frame_time;
1393     return true;
1394 }
1395 
AddFrame(const StillImage & frame,uint32_t frame_time)1396 bool AnimatedImage::AddFrame(const StillImage &frame, uint32_t frame_time)
1397 {
1398     if(!frame._image_texture) {
1399         PRINT_WARNING << "The StillImage argument did not contain any image elements" << std::endl;
1400         return false;
1401     }
1402 
1403     AnimationFrame new_frame;
1404     new_frame.image = frame;
1405     new_frame.frame_time = frame_time;
1406 
1407     _frames.push_back(new_frame);
1408     _animation_time += frame_time;
1409     return true;
1410 }
1411 
SetWidth(float width)1412 void AnimatedImage::SetWidth(float width)
1413 {
1414     _width = width;
1415 
1416     for(uint32_t i = 0; i < _frames.size(); ++i) {
1417         _frames[i].image.SetWidth(width);
1418     }
1419 }
1420 
SetHeight(float height)1421 void AnimatedImage::SetHeight(float height)
1422 {
1423     _height = height;
1424 
1425     for(uint32_t i = 0; i < _frames.size(); i++) {
1426         _frames[i].image.SetHeight(height);
1427     }
1428 }
1429 
SetWidthKeepRatio(float width)1430 void AnimatedImage::SetWidthKeepRatio(float width)
1431 {
1432     float img_ratio = (_width > 0.0f ? width / _width : 0.0f);
1433     SetDimensions(width, _height * img_ratio);
1434 }
1435 
SetHeightKeepRatio(float height)1436 void AnimatedImage::SetHeightKeepRatio(float height)
1437 {
1438     float img_ratio = (_height > 0.0f ? height / _height : 0.0f);
1439     SetDimensions(_width * img_ratio, height);
1440 }
1441 
SetDimensions(float width,float height)1442 void AnimatedImage::SetDimensions(float width, float height)
1443 {
1444     _width = width;
1445     _height = height;
1446 
1447     for(uint32_t i = 0; i < _frames.size(); i++) {
1448         _frames[i].image.SetDimensions(width, height);
1449     }
1450 }
1451 
SetColor(const Color & color)1452 void AnimatedImage::SetColor(const Color &color)
1453 {
1454     ImageDescriptor::SetColor(color);
1455 
1456     for(uint32_t i = 0; i < _frames.size(); i++) {
1457         _frames[i].image.SetColor(color);
1458     }
1459 }
1460 
SetVertexColors(const Color & tl,const Color & tr,const Color & bl,const Color & br)1461 void AnimatedImage::SetVertexColors(const Color &tl, const Color &tr, const Color &bl, const Color &br)
1462 {
1463     ImageDescriptor::SetVertexColors(tl, tr, bl, br);
1464 
1465     for(uint32_t i = 0; i < _frames.size(); i++) {
1466         _frames[i].image.SetVertexColors(tl, tr, bl, br);
1467     }
1468 }
1469 
RandomizeAnimationFrame()1470 void AnimatedImage::RandomizeAnimationFrame() {
1471 
1472     uint32_t nb_frames = _frames.size();
1473     if (nb_frames <= 1)
1474         return;
1475 
1476     uint32_t index = vt_utils::RandomBoundedInteger(0, nb_frames - 1);
1477     _frame_index = index;
1478     _frame_counter = 0;
1479 }
1480 
1481 // -----------------------------------------------------------------------------
1482 // CompositeImage class
1483 // -----------------------------------------------------------------------------
1484 
Clear()1485 void CompositeImage::Clear()
1486 {
1487     ImageDescriptor::Clear();
1488     _elements.clear();
1489 }
1490 
1491 
Draw() const1492 void CompositeImage::Draw() const
1493 {
1494     Draw(Color::white);
1495 }
1496 
1497 
Draw(const Color & draw_color) const1498 void CompositeImage::Draw(const Color &draw_color) const
1499 {
1500     // Don't draw anything if this image is completely transparent (invisible)
1501     if(IsFloatEqual(draw_color[3], 0.0f))
1502         return;
1503 
1504     CoordSys coord_sys = VideoManager->_current_context.coordinate_system;
1505 
1506     Position2D shake(VideoManager->_shake_offset.x
1507                      * (coord_sys.GetRight() - coord_sys.GetLeft())
1508                      / VIDEO_STANDARD_RES_WIDTH,
1509                      VideoManager->_shake_offset.y
1510                      * (coord_sys.GetTop() - coord_sys.GetBottom())
1511                      / VIDEO_STANDARD_RES_HEIGHT);
1512 
1513     Position2D align_offset(((VideoManager->_current_context.x_align + 1) * _width) * 0.5f
1514                             * -coord_sys.GetHorizontalDirection(),
1515                             ((VideoManager->_current_context.y_align + 1) * _height) * 0.5f
1516                             * -coord_sys.GetVerticalDirection());
1517 
1518     // Save the draw cursor position as we move to draw each element
1519     VideoManager->PushMatrix();
1520 
1521     VideoManager->MoveRelative(align_offset.x, align_offset.y);
1522 
1523     for(uint32_t i = 0; i < _elements.size(); ++i) {
1524         Position2D offset;
1525 
1526         if(VideoManager->_current_context.x_flip) {
1527             offset.x = _width - _elements[i].offset.x - _elements[i].image.GetWidth();
1528         } else {
1529             offset.x = _elements[i].offset.x;
1530         }
1531 
1532         if(VideoManager->_current_context.y_flip) {
1533             offset.y = _height - _elements[i].offset.y - _elements[i].image.GetHeight();
1534         } else {
1535             offset.y = _elements[i].offset.y;
1536         }
1537 
1538         offset.x += shake.x;
1539         offset.y += shake.y;
1540 
1541         VideoManager->PushMatrix();
1542         VideoManager->MoveRelative(offset.x * coord_sys.GetHorizontalDirection(),
1543                                    offset.y * coord_sys.GetVerticalDirection());
1544 
1545         Vector2D scale(_elements[i].image.GetWidth(),
1546                        _elements[i].image.GetHeight());
1547 
1548         if(coord_sys.GetHorizontalDirection() < 0.0f)
1549             scale.x = -scale.x;
1550         if(coord_sys.GetVerticalDirection() < 0.0f)
1551             scale.y = -scale.y;
1552 
1553         VideoManager->Scale(scale.x, scale.y);
1554 
1555         if(draw_color == Color::white)
1556             _elements[i].image._DrawTexture(_color);
1557         else {
1558             Color modulated_colors[4];
1559             modulated_colors[0] = _color[0] * draw_color;
1560             modulated_colors[1] = _color[1] * draw_color;
1561             modulated_colors[2] = _color[2] * draw_color;
1562             modulated_colors[3] = _color[3] * draw_color;
1563             _elements[i].image._DrawTexture(modulated_colors);
1564         }
1565         VideoManager->PopMatrix();
1566     }
1567     VideoManager->PopMatrix();
1568 } // void CompositeImage::Draw(const Color& draw_color) const
1569 
1570 
1571 
SetWidth(float width)1572 void CompositeImage::SetWidth(float width)
1573 {
1574     // Case 1: No image elements loaded, just change the internal width
1575     if(_elements.empty()) {
1576         _width = width;
1577         return;
1578     }
1579 
1580     // Case 2: we only have one image element to change its width
1581     if(_elements.size() == 1) {
1582         _width = width;
1583         _elements[0].image.SetWidth(width);
1584         return;
1585     }
1586 
1587     // Case 3: We must set the width of each element appropriately. That is,
1588     // scale its width relative to the width of the composite image
1589     if(IsFloatEqual(_width, 0.0f)) {
1590         IF_PRINT_WARNING(VIDEO_DEBUG) << "internal width was 0.0f when trying to re-size multiple image elements" << std::endl;
1591         return;
1592     }
1593 
1594     for(std::vector<ImageElement>::iterator i = _elements.begin(); i < _elements.end(); ++i) {
1595         if(IsFloatEqual(i->image.GetWidth(), 0.0f) == false)
1596             i->image.SetWidth(width * (_width / i->image.GetWidth()));
1597     }
1598     _width = width;
1599 }
1600 
SetHeight(float height)1601 void CompositeImage::SetHeight(float height)
1602 {
1603     // Case 1: No image elements loaded, just change the internal height
1604     if(_elements.empty()) {
1605         _height = height;
1606         return;
1607     }
1608 
1609     // Case 2: we only have one image element to change its height
1610     if(_elements.size() == 1) {
1611         _height = height;
1612         _elements[0].image.SetHeight(height);
1613         return;
1614     }
1615 
1616     // Case 3: We must set the height of each element appropriately. That is,
1617     // scale its height relative to the height of the composite image
1618     if(IsFloatEqual(_height, 0.0f)) {
1619         IF_PRINT_WARNING(VIDEO_DEBUG) << "internal height was 0.0f when trying to re-size multiple image elements" << std::endl;
1620         return;
1621     }
1622 
1623     for(std::vector<ImageElement>::iterator i = _elements.begin(); i < _elements.end(); ++i) {
1624         if(IsFloatEqual(i->image.GetHeight(), 0.0f) == false)
1625             i->image.SetHeight(height * (_height / i->image.GetHeight()));
1626     }
1627     _height = height;
1628 }
1629 
SetColor(const Color & color)1630 void CompositeImage::SetColor(const Color &color)
1631 {
1632     ImageDescriptor::SetColor(color);
1633 
1634     for(uint32_t i = 0; i < _elements.size(); ++i) {
1635         _elements[i].image.SetColor(color);
1636     }
1637 }
1638 
SetVertexColors(const Color & tl,const Color & tr,const Color & bl,const Color & br)1639 void CompositeImage::SetVertexColors(const Color &tl, const Color &tr, const Color &bl, const Color &br)
1640 {
1641     ImageDescriptor::SetVertexColors(tl, tr, bl, br);
1642 
1643     for(uint32_t i = 0; i < _elements.size(); i++) {
1644         _elements[i].image.SetVertexColors(tl, tr, bl, br);
1645     }
1646 }
1647 
AddImage(const StillImage & img,float x_offset,float y_offset,float u1,float v1,float u2,float v2)1648 void CompositeImage::AddImage(const StillImage &img, float x_offset, float y_offset, float u1, float v1, float u2, float v2)
1649 {
1650     if(x_offset < 0.0f || y_offset < 0.0f) {
1651         IF_PRINT_WARNING(VIDEO_DEBUG) << "negative x or y offset passed to function" << std::endl;
1652         return;
1653     }
1654 
1655     _elements.push_back(ImageElement(img, x_offset, y_offset));
1656 
1657     StillImage &new_image = _elements.back().image;
1658 
1659     new_image.SetUVCoordinates(u1, v1, u2, v2);
1660     new_image.SetDimensions(img.GetWidth(), img.GetHeight());
1661 
1662     // Determine if the width or height of the composite image has grown from adding this new element
1663     float max_x = x_offset + new_image.GetWidth() * u2;
1664     if(max_x > _width)
1665         _width = max_x;
1666 
1667     float max_y = y_offset + new_image.GetHeight() * v2;
1668     if(max_y > _height)
1669         _height = max_y;
1670 }
1671 
DrawCapturedBackgroundImage(const ImageDescriptor & image,float x,float y)1672 void DrawCapturedBackgroundImage(const ImageDescriptor& image, float x, float y)
1673 {
1674     DrawCapturedBackgroundImage(image, x, y, vt_video::Color::white);
1675 }
1676 
DrawCapturedBackgroundImage(const ImageDescriptor & image,float x,float y,const vt_video::Color & color)1677 void DrawCapturedBackgroundImage(const ImageDescriptor& image, float x, float y, const vt_video::Color& color)
1678 {
1679     int32_t width_viewport = VideoManager->GetViewportWidth();
1680     int32_t height_viewport = VideoManager->GetViewportHeight();
1681 
1682     float width_image = image.GetWidth();
1683     float height_image = image.GetHeight();
1684 
1685     assert(width_image > 0.0f);
1686     assert(height_image > 0.0f);
1687 
1688     float scale_width = width_viewport / width_image;
1689     float scale_height = height_viewport / height_image;
1690 
1691     VideoManager->Move(x, y);
1692     VideoManager->Scale(scale_width, scale_height);
1693     image.Draw(color);
1694 }
1695 
1696 }  // namespace vt_video
1697