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