ManaPlus
image.cpp
Go to the documentation of this file.
1 /*
2  * The ManaPlus Client
3  * Copyright (C) 2004-2009 The Mana World Development Team
4  * Copyright (C) 2009-2010 The Mana Developers
5  * Copyright (C) 2011-2019 The ManaPlus Developers
6  * Copyright (C) 2019-2021 Andrei Karas
7  *
8  * This file is part of The ManaPlus Client.
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program. If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 #include "resources/image/image.h"
25 
26 #include "logger.h"
27 
28 #ifdef USE_OPENGL
30 #endif // USE_OPENGL
31 
34 
36 
38 
39 #include "utils/cast.h"
40 #include "utils/sdlcheckutils.h"
41 
42 PRAGMA48(GCC diagnostic push)
43 PRAGMA48(GCC diagnostic ignored "-Wshadow")
44 #ifdef USE_SDL2
45 #include <SDL2_rotozoom.h>
46 #else // USE_SDL2
47 #include <SDL_rotozoom.h>
48 #endif // USE_SDL2
49 PRAGMA48(GCC diagnostic pop)
50 
51 #include "debug.h"
52 
53 #ifdef UNITTESTS
54 Image::Image(const int width,
55  const int height) :
56  Resource(),
57 #ifdef USE_OPENGL
58  mGLImage(0),
59  mTexWidth(0),
60  mTexHeight(0),
61 #endif // USE_OPENGL
62  mBounds(),
63  mAlpha(1.0F),
64  mSDLSurface(nullptr),
65 #ifdef USE_SDL2
66  mTexture(nullptr),
67 #endif // USE_SDL2
68  mAlphaChannel(nullptr),
69  mAlphaCache(),
70  mLoaded(false),
71  mHasAlphaChannel(false),
72  mUseAlphaCache(false),
73  mIsAlphaVisible(true),
74  mIsAlphaCalculated(false)
75 {
76  mBounds.x = 0;
77  mBounds.y = 0;
78  mBounds.w = width;
79  mBounds.h = height;
80 }
81 #endif // UNITTESTS
82 
83 #ifdef USE_SDL2
84 Image::Image(SDL_Texture *restrict const image,
85  const int width, const int height) :
86  Resource(),
87 #ifdef USE_OPENGL
88  mGLImage(0),
89  mTexWidth(0),
90  mTexHeight(0),
91 #endif // USE_OPENGL
92  mBounds(),
93  mAlpha(1.0F),
94  mSDLSurface(nullptr),
95  mTexture(image),
96  mAlphaChannel(nullptr),
97  mAlphaCache(),
98  mLoaded(false),
99  mHasAlphaChannel(false),
100  mUseAlphaCache(false),
101  mIsAlphaVisible(true),
102  mIsAlphaCalculated(false)
103 {
104 #ifdef DEBUG_IMAGES
105  logger->log("created image: %p",
106  static_cast<void*>(this));
107 #endif // DEBUG_IMAGES
108 
109  mBounds.x = 0;
110  mBounds.y = 0;
111 
112  if (mTexture)
113  {
114  mBounds.w = CAST_U16(width);
115  mBounds.h = CAST_U16(height);
116 
117  mLoaded = true;
118  }
119  else
120  {
121  mBounds.w = 0;
122  mBounds.h = 0;
123  }
124 }
125 #endif // USE_SDL2
126 
127 Image::Image(SDL_Surface *restrict const image, const bool hasAlphaChannel0,
128  uint8_t *restrict const alphaChannel) :
129  Resource(),
130 #ifdef USE_OPENGL
131  mGLImage(0),
132  mTexWidth(0),
133  mTexHeight(0),
134 #endif // USE_OPENGL
135  mBounds(),
136  mAlpha(1.0F),
137  mSDLSurface(image),
138 #ifdef USE_SDL2
139  mTexture(nullptr),
140 #endif // USE_SDL2
141  mAlphaChannel(alphaChannel),
142  mAlphaCache(),
143  mLoaded(false),
144  mHasAlphaChannel(hasAlphaChannel0),
145  mUseAlphaCache(SDLImageHelper::mEnableAlphaCache),
146  mIsAlphaVisible(hasAlphaChannel0),
147  mIsAlphaCalculated(false)
148 {
149 #ifdef DEBUG_IMAGES
150  logger->log("created image: %p", static_cast<void*>(this));
151 #endif // DEBUG_IMAGES
152 
153  mBounds.x = 0;
154  mBounds.y = 0;
155 
156  if (mSDLSurface != nullptr)
157  {
158  mBounds.w = CAST_U16(mSDLSurface->w);
159  mBounds.h = CAST_U16(mSDLSurface->h);
160 
161  mLoaded = true;
162  }
163  else
164  {
165  mBounds.w = 0;
166  mBounds.h = 0;
167  }
168 }
169 
170 #ifdef USE_OPENGL
171 Image::Image(const GLuint glimage, const int width, const int height,
172  const int texWidth, const int texHeight) :
173  Resource(),
174  mGLImage(glimage),
175  mTexWidth(texWidth),
176  mTexHeight(texHeight),
177  mBounds(),
178  mAlpha(1.0F),
179  mSDLSurface(nullptr),
180 #ifdef USE_SDL2
181  mTexture(nullptr),
182 #endif // USE_SDL2
183  mAlphaChannel(nullptr),
184  mAlphaCache(),
185  mLoaded(false),
186  mHasAlphaChannel(true),
187  mUseAlphaCache(false),
188  mIsAlphaVisible(true),
189  mIsAlphaCalculated(false)
190 {
191 #ifdef DEBUG_IMAGES
192  logger->log("created image: %p", static_cast<void*>(this));
193 #endif // DEBUG_IMAGES
194 
195  mBounds.x = 0;
196  mBounds.y = 0;
197  mBounds.w = CAST_U16(width);
198  mBounds.h = CAST_U16(height);
199 
200  if (mGLImage != 0U)
201  {
202  mLoaded = true;
203  }
204 }
205 #endif // USE_OPENGL
206 
207 Image::~Image()
208 {
209 #ifdef DEBUG_IMAGES
210  logger->log("delete image: %p", static_cast<void*>(this));
211  logger->log(" %s, %s", mIdPath.c_str(), mSource.c_str());
212 #endif // DEBUG_IMAGES
213 
214  unload();
215 }
216 
217 void Image::SDLCleanCache()
218 {
219  for (std::map<float, SDL_Surface*>::iterator
220  i = mAlphaCache.begin(), i_end = mAlphaCache.end();
221  i != i_end; ++i)
222  {
223  if (mSDLSurface != i->second)
225  i->second = nullptr;
226  }
227  mAlphaCache.clear();
228 }
229 
230 void Image::unload()
231 {
232  mLoaded = false;
233 
234  if (mSDLSurface != nullptr)
235  {
236  SDLCleanCache();
237  // Free the image surface.
238  MSDL_FreeSurface(mSDLSurface);
239  mSDLSurface = nullptr;
240 
241  delete [] mAlphaChannel;
242  mAlphaChannel = nullptr;
243  }
244 #ifdef USE_SDL2
245  if (mTexture)
246  {
247  SDL_DestroyTexture(mTexture);
248  mTexture = nullptr;
249  }
250 #endif // USE_SDL2
251 
252 #ifdef USE_OPENGL
253  if (mGLImage != 0U)
254  {
255  glDeleteTextures(1, &mGLImage);
256  mGLImage = 0;
257 #ifdef DEBUG_OPENGL_LEAKS
258  if (textures_count > 0)
259  textures_count --;
260 #endif // DEBUG_OPENGL_LEAKS
261  }
262 #endif // USE_OPENGL
263 }
264 
265 bool Image::hasAlphaChannel() const
266 {
267  if (mLoaded)
268  return mHasAlphaChannel;
269 
270 #ifdef USE_OPENGL
272  return true;
273 #endif // USE_OPENGL
274 
275  return false;
276 }
277 
278 SDL_Surface *Image::getByAlpha(const float alpha)
279 {
280  const std::map<float, SDL_Surface*>::const_iterator
281  it = mAlphaCache.find(alpha);
282  if (it != mAlphaCache.end())
283  return (*it).second;
284  return nullptr;
285 }
286 
287 void Image::setAlpha(const float alpha)
288 {
289  if (mAlpha == alpha || !ImageHelper::mEnableAlpha)
290  return;
291 
292  if (alpha < 0.0F || alpha > 1.0F)
293  return;
294 
295  if (mSDLSurface != nullptr)
296  {
297  if (mUseAlphaCache)
298  {
299  SDL_Surface *surface = getByAlpha(mAlpha);
300  if (surface == nullptr)
301  {
302  if (mAlphaCache.size() > 100)
303  {
304 #ifdef DEBUG_ALPHA_CACHE
305  logger->log("cleanCache");
306  for (std::map<float, SDL_Surface*>::const_iterator
307  i = mAlphaCache.begin(), i_end = mAlphaCache.end();
308  i != i_end; ++i)
309  {
310  logger->log("alpha: " + toString(i->first));
311  }
312 #endif // DEBUG_ALPHA_CACHE
313 
314  SDLCleanCache();
315  }
316  surface = mSDLSurface;
317  if (surface != nullptr)
318  mAlphaCache[mAlpha] = surface;
319  }
320  else
321  {
322  logger->log("cache bug");
323  }
324 
325  surface = getByAlpha(alpha);
326  if (surface != nullptr)
327  {
328  if (mSDLSurface == surface)
329  logger->log("bug");
330  mAlphaCache.erase(alpha);
331  mSDLSurface = surface;
332  mAlpha = alpha;
333  return;
334  }
335  mSDLSurface = SDLImageHelper::SDLDuplicateSurface(mSDLSurface);
336  if (mSDLSurface == nullptr)
337  return;
338  }
339 
340  mAlpha = alpha;
341 
342  if (!mHasAlphaChannel)
343  {
344 #ifdef USE_SDL2
345  SDL_SetSurfaceAlphaMod(mSDLSurface,
346  CAST_U8(255 * mAlpha));
347 #else // USE_SDL2
348 
349  // Set the alpha value this image is drawn at
350  SDL_SetAlpha(mSDLSurface, SDL_SRCALPHA,
351  CAST_U8(255 * mAlpha));
352 #endif // USE_SDL2
353  }
354  else
355  {
356  if (SDL_MUSTLOCK(mSDLSurface))
357  SDL_LockSurface(mSDLSurface);
358 
359  const int bx = mBounds.x;
360  const int by = mBounds.y;
361  const int bw = mBounds.w;
362  const int bh = mBounds.h;
363  const int bxw = bx + bw;
364  const int sw = mSDLSurface->w;
365  const int maxHeight = std::min(by + bh, mSDLSurface->h);
366  const int maxWidth = std::min(bxw, sw);
367  const int i1 = by * sw + bx;
368  const SDL_PixelFormat * const fmt = mSDLSurface->format;
369  const uint32_t amask = fmt->Amask;
370  const uint32_t invMask = ~fmt->Amask;
371  const uint8_t aloss = fmt->Aloss;
372  const uint8_t ashift = fmt->Ashift;
373 
374  if ((bx == 0) && bxw == sw)
375  {
376  const int i2 = (maxHeight - 1) * sw + maxWidth - 1;
377  for (int i = i1; i <= i2; i++)
378  {
379  const uint8_t sourceAlpha = mAlphaChannel[i];
380  if (sourceAlpha > 0)
381  {
382  const uint8_t a = CAST_U8(
383  static_cast<float>(sourceAlpha) * mAlpha);
384 
385  uint32_t c = (static_cast<uint32_t*>(
386  mSDLSurface->pixels))[i];
387  c &= invMask;
388  c |= ((a >> aloss) << ashift & amask);
389  (static_cast<uint32_t*>(mSDLSurface->pixels))[i] = c;
390  }
391  }
392  }
393  else
394  {
395  for (int y = by; y < maxHeight; y ++)
396  {
397  const int idx = y * sw;
398  const int x1 = idx + bx;
399  const int x2 = idx + maxWidth;
400  for (int i = x1; i < x2; i ++)
401  {
402  const uint8_t sourceAlpha = mAlphaChannel[i];
403  if (sourceAlpha > 0)
404  {
405  const uint8_t a = CAST_U8(
406  static_cast<float>(sourceAlpha) * mAlpha);
407 
408  uint32_t c = (static_cast<uint32_t*>(
409  mSDLSurface->pixels))[i];
410  c &= invMask;
411  c |= ((a >> aloss) << ashift & amask);
412  (static_cast<uint32_t*>(
413  mSDLSurface->pixels))[i] = c;
414  }
415  }
416  }
417  }
418 
419  if (SDL_MUSTLOCK(mSDLSurface))
420  SDL_UnlockSurface(mSDLSurface);
421  }
422  }
423 #ifdef USE_SDL2
424  else if (mTexture)
425  {
426  mAlpha = alpha;
427  SDL_SetTextureAlphaMod(mTexture,
428  CAST_U8(255 * mAlpha));
429  }
430 #endif // USE_SDL2
431  else
432  {
433  mAlpha = alpha;
434  }
435 }
436 
437 Image* Image::SDLgetScaledImage(const int width, const int height) const
438 {
439  // No scaling on incorrect new values.
440  if (width == 0 || height == 0)
441  return nullptr;
442 
443  // No scaling when there is ... no different given size ...
444  if (width == mBounds.w && height == mBounds.h)
445  return nullptr;
446 
447  Image* scaledImage = nullptr;
448 
449  if (mSDLSurface != nullptr)
450  {
451  SDL_Surface *const scaledSurface = zoomSurface(mSDLSurface,
452  static_cast<double>(width) / mBounds.w,
453  static_cast<double>(height) / mBounds.h,
454  1);
455 
456  // The load function takes care of the SDL<->OpenGL implementation
457  // and about freeing the given SDL_surface*.
458  if (scaledSurface != nullptr)
459  {
460  scaledImage = imageHelper->loadSurface(scaledSurface);
461  MSDL_FreeSurface(scaledSurface);
462  }
463  }
464  return scaledImage;
465 }
466 
467 Image *Image::getSubImage(const int x, const int y,
468  const int width, const int height)
469 {
470  // Create a new clipped sub-image
471 #ifdef USE_OPENGL
473  if (mode == RENDER_NORMAL_OPENGL ||
474  mode == RENDER_SAFE_OPENGL ||
475  mode == RENDER_GLES_OPENGL ||
476  mode == RENDER_GLES2_OPENGL ||
477  mode == RENDER_MODERN_OPENGL)
478  {
479  return new SubImage(this,
480  mGLImage,
481  x, y,
482  width, height,
483  mTexWidth, mTexHeight);
484  }
485 #endif // USE_OPENGL
486 
487 #ifdef USE_SDL2
488 #ifndef USE_OPENGL
489  const RenderType mode = ImageHelper::mUseOpenGL;
490 #endif // USE_OPENGL
491 
492  if (mode == RENDER_SOFTWARE)
493  return new SubImage(this, mSDLSurface, x, y, width, height);
494  else
495  return new SubImage(this, mTexture, x, y, width, height);
496 #else // USE_SDL2
497 
498  return new SubImage(this, mSDLSurface, x, y, width, height);
499 #endif // USE_SDL2
500 }
501 
502 void Image::SDLTerminateAlphaCache()
503 {
504  SDLCleanCache();
505  mUseAlphaCache = false;
506 }
507 
508 int Image::calcMemoryLocal() const
509 {
510  // +++ this calculation can be wrong for SDL2
511  int sz = static_cast<int>(sizeof(Image) +
512  sizeof(std::map<float, SDL_Surface*>)) +
514  if (mSDLSurface != nullptr)
515  {
516  sz += CAST_S32(mAlphaCache.size()) *
517  MemoryManager::getSurfaceSize(mSDLSurface);
518  }
519  return sz;
520 }
521 
522 #ifdef USE_OPENGL
523 void Image::decRef()
524 {
525  if ((mGLImage != 0U) && mRefCount <= 1)
528 }
529 #endif // USE_OPENGL
SDL_Surface * zoomSurface(SDL_Surface *src, double zoomx, double zoomy, int smooth)
Zoom a surface by independent horizontal and vertical factors with optional smoothing.
#define CAST_U16
Definition: cast.h:29
#define CAST_S32
Definition: cast.h:30
#define CAST_U8
Definition: cast.h:27
static RenderType mUseOpenGL
Definition: imagehelper.h:118
virtual Image * loadSurface(SDL_Surface *const)
Definition: imagehelper.h:73
static bool mEnableAlpha
Definition: imagehelper.h:117
void log(const char *const log_text,...)
Definition: logger.cpp:269
static int getSurfaceSize(const SDL_Surface *const surface)
static void invalidate(const GLuint textureId)
virtual void decRef()
Definition: resource.cpp:50
int calcMemoryLocal() const
Definition: resource.cpp:76
static SDL_Surface * SDLDuplicateSurface(SDL_Surface *const tmpImage)
#define MSDL_FreeSurface(surface)
Definition: debug.h:54
int textures_count
Definition: client.cpp:138
ImageHelper * imageHelper
Definition: imagehelper.cpp:44
#define restrict
Definition: localconsts.h:165
#define PRAGMA48(str)
Definition: localconsts.h:199
#define nullptr
Definition: localconsts.h:45
Logger * logger
Definition: logger.cpp:89
std::string toString(T const &value)
converts any type to a string
Definition: catch.hpp:1774
@ SubImage
Definition: imagetype.h:30
Image * getSubImage(Image *const parent, const int x, const int y, const int width, const int height)
void unload()
Definition: net.cpp:180
void scheduleDelete(SDL_Surface *const surface)
RenderType
Definition: rendertype.h:26
@ RENDER_SAFE_OPENGL
Definition: rendertype.h:29
@ RENDER_GLES2_OPENGL
Definition: rendertype.h:33
@ RENDER_GLES_OPENGL
Definition: rendertype.h:30
@ RENDER_MODERN_OPENGL
Definition: rendertype.h:32
@ RENDER_NORMAL_OPENGL
Definition: rendertype.h:28
@ RENDER_SOFTWARE
Definition: rendertype.h:27