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_base.cpp
13 *** \author  Tyler Olsen, roots@allacrost.org
14 *** \author  Yohann Ferreira, yohann ferreira orange fr
15 *** \brief   Source file for image base classes
16 *** ***************************************************************************/
17 
18 #include "image_base.h"
19 
20 #include "video.h"
21 
22 #include "utils/utils_common.h"
23 
24 #include <cassert>
25 
26 #include <SDL2/SDL_image.h>
27 #include <SDL2/SDL_endian.h>
28 #include <png.h>
29 
30 using namespace vt_utils;
31 
32 namespace vt_video
33 {
34 
35 namespace private_video
36 {
37 
38 // -----------------------------------------------------------------------------
39 // ImageMemory class
40 // -----------------------------------------------------------------------------
ImageMemory()41 ImageMemory::ImageMemory() :
42     _width(0),
43     _height(0),
44     _rgb_format(false)
45 {
46 }
47 
ImageMemory(const SDL_Surface * surface)48 ImageMemory::ImageMemory(const SDL_Surface* surface) :
49     _width(surface->w),
50     _height(surface->h),
51     _rgb_format(surface->format->BytesPerPixel == 3)
52 {
53     _pixels.reserve(_width * _height * GetBytesPerPixel());
54     for(size_t i = 0; i < _width * _height * GetBytesPerPixel(); ++i) {
55         _pixels.push_back(static_cast<const uint8_t*>(surface->pixels)[i]);
56     }
57 }
58 
Resize(size_t width,size_t height,bool is_rgb)59 void ImageMemory::Resize(size_t width, size_t height, bool is_rgb)
60 {
61     _rgb_format = is_rgb;
62     _width = width;
63     _height = height;
64 
65     _pixels.clear();
66     _pixels.resize(_width * _height * GetBytesPerPixel());
67 }
68 
LoadImage(const std::string & filename)69 bool ImageMemory::LoadImage(const std::string& filename)
70 {
71     assert(_pixels.empty());
72     if (!_pixels.empty()) {
73         IF_PRINT_WARNING(VIDEO_DEBUG) << "_pixels member was not empty upon function invocation" << std::endl;
74     }
75 
76     SDL_Surface* temp_surf = IMG_Load(filename.c_str());
77     if (temp_surf == nullptr) {
78         PRINT_ERROR << "Couldn't load image file: " << filename << std::endl;
79         return false;
80     }
81 
82     SDL_Surface* alpha_surf = SDL_ConvertSurfaceFormat(temp_surf, SDL_PIXELFORMAT_ARGB8888, 0);
83 
84     // Tells whether the alpha image will be used
85     bool alpha_format = true;
86     if (alpha_surf == nullptr) {
87         // use the default image in that case
88         alpha_surf = temp_surf;
89         temp_surf = nullptr;
90         alpha_format = false;
91     }
92 
93     // Now allocate the pixel values
94     Resize(alpha_surf->w, alpha_surf->h, 3 == alpha_surf->format->BytesPerPixel);
95 
96     // convert the data so that it works in our format
97     uint8_t* img_pixel = nullptr;
98     uint8_t* dst_pixel = nullptr;
99 
100     for (uint32_t y = 0; y < _height; ++y) {
101         for (uint32_t x = 0; x < _width; ++x) {
102             img_pixel = static_cast<uint8_t *>(alpha_surf->pixels) + y * alpha_surf->pitch + x * alpha_surf->format->BytesPerPixel;
103             dst_pixel = &_pixels[(y * _width + x) * GetBytesPerPixel()];
104 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
105             if (alpha_format) {
106                 dst_pixel[0] = img_pixel[0];
107                 dst_pixel[1] = img_pixel[1];
108                 dst_pixel[2] = img_pixel[2];
109                 dst_pixel[3] = img_pixel[3];
110             } else {
111                 dst_pixel[2] = img_pixel[0];
112                 dst_pixel[1] = img_pixel[1];
113                 dst_pixel[0] = img_pixel[2];
114                 dst_pixel[3] = img_pixel[3];
115             }
116 #else
117             if (alpha_format) { // ARGB8888
118 #ifdef __APPLE__
119                 dst_pixel[3] = img_pixel[3];
120                 dst_pixel[0] = img_pixel[2];
121                 dst_pixel[1] = img_pixel[1];
122                 dst_pixel[2] = img_pixel[0];
123 #else
124                 dst_pixel[2] = img_pixel[0];
125                 dst_pixel[1] = img_pixel[1];
126                 dst_pixel[0] = img_pixel[2];
127                 dst_pixel[3] = img_pixel[3];
128 #endif
129             } else {
130                 dst_pixel[0] = img_pixel[0];
131                 dst_pixel[1] = img_pixel[1];
132                 dst_pixel[2] = img_pixel[2];
133                 dst_pixel[3] = img_pixel[3];
134             }
135 #endif
136             // GL_LINEAR white artifact removal
137             // Make the r,g,b values black to prevent OpenGL to make linear average with
138             // another color when smoothing.
139             // This is removing the white edges often seen on sprites.
140             if (dst_pixel[3] == 0) {
141                 dst_pixel[0] = 0;
142                 dst_pixel[1] = 0;
143                 dst_pixel[2] = 0;
144             }
145         }
146     }
147 
148     if (temp_surf != nullptr) {
149         SDL_FreeSurface(temp_surf);
150         temp_surf = nullptr;
151     }
152 
153     if (alpha_surf != nullptr) {
154         SDL_FreeSurface(alpha_surf);
155         alpha_surf = nullptr;
156     }
157 
158     return true;
159 }
160 
SaveImage(const std::string & filename)161 bool ImageMemory::SaveImage(const std::string& filename)
162 {
163     assert(!_pixels.empty());
164     if (_pixels.empty()) {
165         IF_PRINT_WARNING(VIDEO_DEBUG) << "The _pixels member was empty upon function invocation for file: " << filename << std::endl;
166         return false;
167     }
168 
169     // Define all variables which require clean up.
170     FILE* fp = nullptr;
171     png_structp png_ptr = nullptr;
172     png_infop info_ptr = nullptr;
173     png_bytep* row_pointers = nullptr;
174 
175     // Define a clean up function.
176     auto CleanUp = [&]()
177     {
178         if (row_pointers != nullptr) {
179             delete[] row_pointers;
180             row_pointers = nullptr;
181         }
182 
183         // Assuming an 'info_ptr' without a 'png_ptr' is impossible by design.
184         if (png_ptr == nullptr) {
185             assert(info_ptr == nullptr);
186         }
187 
188         if (png_ptr != nullptr && info_ptr != nullptr) {
189             png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
190         }
191 
192         if (png_ptr != nullptr) {
193             // Note: A second parameter of 'nullptr' is valid for this function.
194             png_destroy_write_struct(&png_ptr, &info_ptr);
195             png_ptr = nullptr;
196             info_ptr = nullptr;
197         }
198 
199         if (fp != nullptr) {
200             fclose(fp);
201             fp = nullptr;
202         }
203     };
204 
205     // Open a file for writing.
206     assert(fp == nullptr);
207     fp = fopen(filename.c_str(), "wb");
208     if (fp == nullptr) {
209         IF_PRINT_WARNING(VIDEO_DEBUG) << "Could not open file: " << filename << std::endl;
210         CleanUp();
211         return false;
212     }
213 
214     // Create a write structure.
215     assert(png_ptr == nullptr);
216     png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
217                                       (png_voidp)nullptr, nullptr, nullptr);
218     if (png_ptr == nullptr) {
219         IF_PRINT_WARNING(VIDEO_DEBUG) << "png_create_write_struct() failed for file: "
220                                       << filename << std::endl;
221         CleanUp();
222         return false;
223     }
224 
225     // Create a place to store meta data.
226     assert(info_ptr == nullptr);
227     info_ptr = png_create_info_struct(png_ptr);
228     if (info_ptr == nullptr) {
229         IF_PRINT_WARNING(VIDEO_DEBUG) << "png_create_info_struct() failed for file: "
230                                       << filename << std::endl;
231         CleanUp();
232         return false;
233     }
234 
235     // Setup error handling.
236     if (setjmp(png_jmpbuf(png_ptr))) {
237         IF_PRINT_WARNING(VIDEO_DEBUG) << "setjmp returned non-zero for file: "
238                                       << filename << std::endl;
239         CleanUp();
240         return false;
241     }
242 
243     // Set the file pointer.
244     png_init_io(png_ptr, fp);
245 
246     // Write the header.
247     int32_t color_type = _rgb_format ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA;
248     png_set_IHDR(png_ptr, info_ptr, _width, _height, 8, color_type,
249                  PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
250                  PNG_FILTER_TYPE_DEFAULT);
251 
252 
253     png_write_info(png_ptr, info_ptr);
254     png_set_packing(png_ptr);
255 
256     // Create the row array.
257     assert(row_pointers == nullptr);
258     row_pointers = new png_bytep[_height];
259     if (row_pointers == nullptr) {
260         IF_PRINT_WARNING(VIDEO_DEBUG) << "Couldn't allocate row_pointers for: "
261                                       << filename << std::endl;
262         CleanUp();
263         return false;
264     }
265 
266     // Initialize the row array.
267     int32_t bytes_per_row = _width * GetBytesPerPixel();
268     for (uint32_t i = 0; i < _height; ++i) {
269         row_pointers[i] = static_cast<png_bytep>(&_pixels[bytes_per_row * i]);
270     }
271 
272     // Define the rows.
273     png_set_rows(png_ptr, info_ptr, row_pointers);
274 
275     // Write the image.
276     png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);
277     png_write_image(png_ptr, row_pointers);
278     png_write_end(png_ptr, info_ptr);
279 
280     CleanUp();
281 
282     return true;
283 }
284 
ConvertToGrayscale()285 void ImageMemory::ConvertToGrayscale()
286 {
287     if (_pixels.empty()) {
288         IF_PRINT_WARNING(VIDEO_DEBUG) << "No image data (_pixels is empty)"
289                                       << std::endl;
290         return;
291     }
292 
293     assert(_width > 0);
294     assert(_height > 0);
295 
296     uint8_t bytes_per_pixel = GetBytesPerPixel();
297 
298     assert(_pixels.size() > bytes_per_pixel);
299 
300     // We are going to increment through the loop by 'bytes_per_pixel'.
301     // So, the size of the array must be divisible by 'bytes_per_pixel'.
302     assert(_pixels.size() % bytes_per_pixel == 0);
303     if (_pixels.size() % bytes_per_pixel == 0) {
304 
305         auto current_pixel = _pixels.begin();
306         auto end_pixel = _pixels.end();
307 
308         while (current_pixel != end_pixel) {
309 
310             //
311             // Calculate the grayscale value for this pixel based on RGB values: 0.30R + 0.59G + 0.11B.
312             //
313 
314             uint8_t red   = *(current_pixel + 0);
315             uint8_t green = *(current_pixel + 1);
316             uint8_t blue  = *(current_pixel + 2);
317 
318             // Compute the sum.
319             uint32_t sum = (30 * red) + (59 * green) + (11 * blue);
320 
321             // Scale the sum.
322             uint8_t value = static_cast<uint8_t>(sum * 0.01f);
323 
324             // Store the result.
325             *(current_pixel + 0) = value;
326             *(current_pixel + 1) = value;
327             *(current_pixel + 2) = value;
328             // *(current_pixel + 3) for RGBA is the alpha value and is left unmodified.
329 
330             // Increment to the next pixel.
331             current_pixel = current_pixel + bytes_per_pixel;
332         }
333     }
334 }
335 
RGBAToRGB()336 void ImageMemory::RGBAToRGB()
337 {
338     if(_pixels.empty()) {
339         IF_PRINT_WARNING(VIDEO_DEBUG) << "No image data (pixels is empty)"
340                                       << std::endl;
341         return;
342     }
343 
344     if(_rgb_format) {
345         IF_PRINT_WARNING(VIDEO_DEBUG) << "Image data was said to already be in RGB format"
346                                       << std::endl;
347         return;
348     }
349 
350     uint8_t* pixel_index = &_pixels[0];
351     uint8_t* pixel_source = pixel_index;
352 
353     for(uint32_t i = 0; i < _height * _width; ++i, pixel_index += 4) {
354         int32_t index = 3 * i;
355         pixel_source[index] = *pixel_index;
356         pixel_source[index + 1] = *(pixel_index + 1);
357         pixel_source[index + 2] = *(pixel_index + 2);
358     }
359 
360     // Reduce the memory consumed by 1/4
361     // since we no longer need to contain alpha data
362     _pixels.resize(_width * _height * GetBytesPerPixel());
363     std::vector<uint8_t> new_pixels(_pixels);
364     std::swap(_pixels, new_pixels);
365     _rgb_format = true;
366 }
367 
CopyFromTexture(TexSheet * texture)368 void ImageMemory::CopyFromTexture(TexSheet *texture)
369 {
370     assert(texture != nullptr);
371 
372     Resize(texture->width, texture->height, false);
373 
374     if (_pixels.empty()) {
375         PRINT_ERROR << "Failed to malloc enough memory to copy the texture."
376                     << std::endl;
377     }
378 
379     TextureManager->_BindTexture(texture->tex_id);
380     glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, &_pixels[0]);
381 }
382 
CopyFromImage(BaseTexture * img)383 void ImageMemory::CopyFromImage(BaseTexture *img)
384 {
385     assert(img != nullptr);
386 
387     // First copy the image's entire texture sheet to memory
388     CopyFromTexture(img->texture_sheet);
389 
390     // Check that the image to copy is smaller than its texture sheet (usually true).
391     // If so, then copy over only the sub-rectangle area of the image from its texture.
392     if (_height <= img->height && _width <= img->width)
393         return;
394 
395     uint32_t src_bytes = _width * GetBytesPerPixel();
396     uint32_t dst_bytes = img->width * GetBytesPerPixel();
397     uint32_t src_offset = (img->y * _width + img->x) * GetBytesPerPixel();
398 
399     std::vector<uint8_t> img_pixels;
400     try {
401         img_pixels.reserve(img->width * img->height * GetBytesPerPixel());
402     }
403     catch (std::exception&) {
404         PRINT_ERROR << "Failed to malloc enough memory to copy the image"
405                     << std::endl;
406         return;
407     }
408 
409     for (uint32_t i = 0; i < img->height; ++i) {
410         std::vector<uint8_t>::const_iterator start =
411             _pixels.begin() + i * src_bytes + src_offset;
412         std::vector<uint8_t>::const_iterator end = start + dst_bytes;
413         img_pixels.insert(img_pixels.end(), start, end);
414     }
415 
416     _height = img->height;
417     _width = img->width;
418 
419     std::swap(_pixels, img_pixels);
420 }
421 
CopyFrom(const ImageMemory & src,uint32_t src_offset,uint32_t dst_bytes,uint32_t dst_offset)422 void ImageMemory::CopyFrom(const ImageMemory& src,
423                            uint32_t src_offset,
424                            uint32_t dst_bytes,
425                            uint32_t dst_offset)
426 {
427     size_t src_bytes = src.GetWidth() * GetBytesPerPixel();
428     dst_bytes *= GetBytesPerPixel();
429     src_offset *= GetBytesPerPixel();
430     dst_offset *= GetBytesPerPixel();
431     uint32_t bytes = _width * GetBytesPerPixel();
432 
433     for(size_t line = 0; line < _height; ++line) {
434         memcpy(&_pixels[0] + line * dst_bytes + dst_offset,
435                &src._pixels[0] + line * src_bytes + src_offset,
436                bytes);
437     }
438 }
439 
CopyFrom(const ImageMemory & src,uint32_t src_offset)440 void ImageMemory::CopyFrom(const ImageMemory& src, uint32_t src_offset)
441 {
442     size_t src_bytes = src.GetWidth() * GetBytesPerPixel();
443     size_t dst_bytes = GetWidth() * GetBytesPerPixel();
444     src_offset *= GetBytesPerPixel();
445     uint32_t bytes = _width * GetBytesPerPixel();
446 
447     for(size_t line = 0; line < _height; ++line) {
448         memcpy(&_pixels[0] + line * dst_bytes,
449                &src._pixels[0] + line * src_bytes + src_offset,
450                bytes);
451     }
452 }
453 
GlGetTexImage()454 void ImageMemory::GlGetTexImage()
455 {
456     glGetTexImage(GL_TEXTURE_2D, 0,
457                   _rgb_format ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, &_pixels[0]);
458 }
459 
GlTexSubImage(int32_t x,int32_t y)460 void ImageMemory::GlTexSubImage(int32_t x, int32_t y)
461 {
462     glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, _width, _height,
463                     _rgb_format ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, &_pixels[0]);
464 }
465 
GlReadPixels(int32_t x,int32_t y)466 void ImageMemory::GlReadPixels(int32_t x, int32_t y)
467 {
468     glReadPixels(x, y, _width, _height,
469                  _rgb_format ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, &_pixels[0]);
470 }
471 
VerticalFlip()472 void ImageMemory::VerticalFlip()
473 {
474     std::vector<uint8_t> flipped;
475     flipped.reserve(_pixels.size());
476 
477     for (uint32_t i = 1; i <= _height; ++i) {
478         std::vector<uint8_t>::const_iterator start =
479             _pixels.end() - (i * _width * GetBytesPerPixel());
480         std::vector<uint8_t>::const_iterator end =
481             start + _width * GetBytesPerPixel();
482         flipped.insert(flipped.end(), start, end);
483     }
484     std::swap(flipped, _pixels);
485 }
486 
487 // -----------------------------------------------------------------------------
488 // BaseTexture class
489 // -----------------------------------------------------------------------------
490 
BaseTexture()491 BaseTexture::BaseTexture() :
492     texture_sheet(nullptr),
493     width(0),
494     height(0),
495     x(0),
496     y(0),
497     u1(0.0f),
498     v1(0.0f),
499     u2(0.0f),
500     v2(0.0f),
501     smooth(false),
502     ref_count(0)
503 {}
504 
BaseTexture(uint32_t width_,uint32_t height_)505 BaseTexture::BaseTexture(uint32_t width_, uint32_t height_) :
506     texture_sheet(nullptr),
507     width(width_),
508     height(height_),
509     x(0),
510     y(0),
511     u1(0.0f),
512     v1(0.0f),
513     u2(0.0f),
514     v2(0.0f),
515     smooth(false),
516     ref_count(0)
517 {}
518 
BaseTexture(TexSheet * texture_sheet_,uint32_t width_,uint32_t height_)519 BaseTexture::BaseTexture(TexSheet *texture_sheet_,
520                          uint32_t width_,
521                          uint32_t height_) :
522     texture_sheet(texture_sheet_),
523     width(width_),
524     height(height_),
525     x(0),
526     y(0),
527     u1(0.0f),
528     v1(0.0f),
529     u2(0.0f),
530     v2(0.0f),
531     smooth(false),
532     ref_count(0)
533 {}
534 
~BaseTexture()535 BaseTexture::~BaseTexture()
536 {
537     if(ref_count > 0) {
538         IF_PRINT_WARNING(VIDEO_DEBUG) << "destructor invoked when the object had a reference count greater than zero: "
539                                       << ref_count << std::endl;
540     }
541 }
542 
RemoveReference()543 bool BaseTexture::RemoveReference()
544 {
545     ref_count--;
546 
547     if(ref_count < 0) {
548         IF_PRINT_WARNING(VIDEO_DEBUG) << "texture ref_count member is now negative: "
549                                       << ref_count << std::endl;
550         return true;
551     }
552     return ref_count == 0;
553 }
554 
555 // -----------------------------------------------------------------------------
556 // ImageTexture class
557 // -----------------------------------------------------------------------------
558 
ImageTexture(const std::string & filename_,const std::string & tags_,int32_t width_,int32_t height_)559 ImageTexture::ImageTexture(const std::string &filename_,
560                            const std::string &tags_,
561                            int32_t width_, int32_t height_) :
562     BaseTexture(width_, height_),
563     filename(filename_),
564     tags(tags_)
565 {
566     if(VIDEO_DEBUG) {
567         if(TextureManager->_IsImageTextureRegistered(filename + tags))
568             PRINT_WARNING << "constructor invoked when ImageTexture was already referenced for: "
569                           << filename << tags << std::endl;
570     }
571 
572     TextureManager->_RegisterImageTexture(this);
573 }
574 
ImageTexture(TexSheet * texture_sheet_,const std::string & filename_,const std::string & tags_,int32_t width_,int32_t height_)575 ImageTexture::ImageTexture(TexSheet *texture_sheet_,
576                            const std::string &filename_,
577                            const std::string &tags_,
578                            int32_t width_, int32_t height_) :
579     BaseTexture(texture_sheet_, width_, height_),
580     filename(filename_),
581     tags(tags_)
582 {
583     if(VIDEO_DEBUG) {
584         if(TextureManager->_IsImageTextureRegistered(filename + tags))
585             PRINT_WARNING << "constructor invoked when ImageTexture was already referenced for: "
586                           << filename << tags << std::endl;
587     }
588 
589     TextureManager->_RegisterImageTexture(this);
590 }
591 
~ImageTexture()592 ImageTexture::~ImageTexture()
593 {
594     // Remove this instance from the texture manager
595     TextureManager->_UnregisterImageTexture(this);
596 }
597 
598 } // namespace private_video
599 
600 } // namespace vt_video
601