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 ©) :
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 ©)
105 {
106 // Handle the case were a dumbass assigns an object to itself
107 if(this == ©) {
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 ¤t_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