/* * The ManaPlus Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * Copyright (C) 2011-2020 The ManaPlus Developers * Copyright (C) 2020-2023 The ManaVerse Developers * * This file is part of The ManaPlus Client. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "resources/image/image.h" #include "logger.h" #ifdef USE_OPENGL #include "resources/openglimagehelper.h" #endif // USE_OPENGL #include "resources/memorymanager.h" #include "resources/sdlimagehelper.h" #include "resources/image/subimage.h" #include "resources/resourcemanager/resourcemanager.h" #include "utils/cast.h" #include "utils/sdlcheckutils.h" PRAGMA48(GCC diagnostic push) PRAGMA48(GCC diagnostic ignored "-Wshadow") #ifdef USE_SDL2 #include #else // USE_SDL2 #include #endif // USE_SDL2 PRAGMA48(GCC diagnostic pop) #include "debug.h" #ifdef UNITTESTS Image::Image(const int width, const int height) : Resource(), #ifdef USE_OPENGL mGLImage(0), mTexWidth(0), mTexHeight(0), #endif // USE_OPENGL mBounds(), mAlpha(1.0F), mSDLSurface(nullptr), #ifdef USE_SDL2 mTexture(nullptr), #endif // USE_SDL2 mAlphaChannel(nullptr), mAlphaCache(), mLoaded(false), mHasAlphaChannel(false), mUseAlphaCache(false), mIsAlphaVisible(true), mIsAlphaCalculated(false) { mBounds.x = 0; mBounds.y = 0; mBounds.w = width; mBounds.h = height; } #endif // UNITTESTS #ifdef USE_SDL2 Image::Image(SDL_Texture *restrict const image, const int width, const int height) : Resource(), #ifdef USE_OPENGL mGLImage(0), mTexWidth(0), mTexHeight(0), #endif // USE_OPENGL mBounds(), mAlpha(1.0F), mSDLSurface(nullptr), mTexture(image), mAlphaChannel(nullptr), mAlphaCache(), mLoaded(false), mHasAlphaChannel(false), mUseAlphaCache(false), mIsAlphaVisible(true), mIsAlphaCalculated(false) { #ifdef DEBUG_IMAGES logger->log("created image: %p", static_cast(this)); #endif // DEBUG_IMAGES mBounds.x = 0; mBounds.y = 0; if (mTexture) { mBounds.w = CAST_U16(width); mBounds.h = CAST_U16(height); mLoaded = true; } else { mBounds.w = 0; mBounds.h = 0; } } #endif // USE_SDL2 Image::Image(SDL_Surface *restrict const image, const bool hasAlphaChannel0, uint8_t *restrict const alphaChannel) : Resource(), #ifdef USE_OPENGL mGLImage(0), mTexWidth(0), mTexHeight(0), #endif // USE_OPENGL mBounds(), mAlpha(1.0F), mSDLSurface(image), #ifdef USE_SDL2 mTexture(nullptr), #endif // USE_SDL2 mAlphaChannel(alphaChannel), mAlphaCache(), mLoaded(false), mHasAlphaChannel(hasAlphaChannel0), mUseAlphaCache(SDLImageHelper::mEnableAlphaCache), mIsAlphaVisible(hasAlphaChannel0), mIsAlphaCalculated(false) { #ifdef DEBUG_IMAGES logger->log("created image: %p", static_cast(this)); #endif // DEBUG_IMAGES mBounds.x = 0; mBounds.y = 0; if (mSDLSurface != nullptr) { mBounds.w = CAST_U16(mSDLSurface->w); mBounds.h = CAST_U16(mSDLSurface->h); mLoaded = true; } else { mBounds.w = 0; mBounds.h = 0; } } #ifdef USE_OPENGL Image::Image(const GLuint glimage, const int width, const int height, const int texWidth, const int texHeight) : Resource(), mGLImage(glimage), mTexWidth(texWidth), mTexHeight(texHeight), mBounds(), mAlpha(1.0F), mSDLSurface(nullptr), #ifdef USE_SDL2 mTexture(nullptr), #endif // USE_SDL2 mAlphaChannel(nullptr), mAlphaCache(), mLoaded(false), mHasAlphaChannel(true), mUseAlphaCache(false), mIsAlphaVisible(true), mIsAlphaCalculated(false) { #ifdef DEBUG_IMAGES logger->log("created image: %p", static_cast(this)); #endif // DEBUG_IMAGES mBounds.x = 0; mBounds.y = 0; mBounds.w = CAST_U16(width); mBounds.h = CAST_U16(height); if (mGLImage != 0U) { mLoaded = true; } } #endif // USE_OPENGL Image::~Image() { #ifdef DEBUG_IMAGES logger->log("delete image: %p", static_cast(this)); logger->log(" %s, %s", mIdPath.c_str(), mSource.c_str()); #endif // DEBUG_IMAGES unload(); } void Image::SDLCleanCache() { for (std::map::iterator i = mAlphaCache.begin(), i_end = mAlphaCache.end(); i != i_end; ++i) { if (mSDLSurface != i->second) ResourceManager::scheduleDelete(i->second); i->second = nullptr; } mAlphaCache.clear(); } void Image::unload() { mLoaded = false; if (mSDLSurface != nullptr) { SDLCleanCache(); // Free the image surface. MSDL_FreeSurface(mSDLSurface); mSDLSurface = nullptr; delete [] mAlphaChannel; mAlphaChannel = nullptr; } #ifdef USE_SDL2 if (mTexture) { SDL_DestroyTexture(mTexture); mTexture = nullptr; } #endif // USE_SDL2 #ifdef USE_OPENGL if (mGLImage != 0U) { glDeleteTextures(1, &mGLImage); mGLImage = 0; #ifdef DEBUG_OPENGL_LEAKS if (textures_count > 0) textures_count --; #endif // DEBUG_OPENGL_LEAKS } #endif // USE_OPENGL } bool Image::hasAlphaChannel() const { if (mLoaded) return mHasAlphaChannel; #ifdef USE_OPENGL if (OpenGLImageHelper::mUseOpenGL != RENDER_SOFTWARE) return true; #endif // USE_OPENGL return false; } SDL_Surface *Image::getByAlpha(const float alpha) { const std::map::const_iterator it = mAlphaCache.find(alpha); if (it != mAlphaCache.end()) return (*it).second; return nullptr; } void Image::setAlpha(const float alpha) { if (mAlpha == alpha || !ImageHelper::mEnableAlpha) return; if (alpha < 0.0F || alpha > 1.0F) return; if (mSDLSurface != nullptr) { if (mUseAlphaCache) { SDL_Surface *surface = getByAlpha(mAlpha); if (surface == nullptr) { if (mAlphaCache.size() > 100) { #ifdef DEBUG_ALPHA_CACHE logger->log("cleanCache"); for (std::map::const_iterator i = mAlphaCache.begin(), i_end = mAlphaCache.end(); i != i_end; ++i) { logger->log("alpha: " + toString(i->first)); } #endif // DEBUG_ALPHA_CACHE SDLCleanCache(); } surface = mSDLSurface; if (surface != nullptr) mAlphaCache[mAlpha] = surface; } else { logger->log("cache bug"); } surface = getByAlpha(alpha); if (surface != nullptr) { if (mSDLSurface == surface) logger->log("bug"); mAlphaCache.erase(alpha); mSDLSurface = surface; mAlpha = alpha; return; } mSDLSurface = SDLImageHelper::SDLDuplicateSurface(mSDLSurface); if (mSDLSurface == nullptr) return; } mAlpha = alpha; if (!mHasAlphaChannel) { #ifdef USE_SDL2 SDL_SetSurfaceAlphaMod(mSDLSurface, CAST_U8(255 * mAlpha)); #else // USE_SDL2 // Set the alpha value this image is drawn at SDL_SetAlpha(mSDLSurface, SDL_SRCALPHA, CAST_U8(255 * mAlpha)); #endif // USE_SDL2 } else { if (SDL_MUSTLOCK(mSDLSurface)) SDL_LockSurface(mSDLSurface); const int bx = mBounds.x; const int by = mBounds.y; const int bw = mBounds.w; const int bh = mBounds.h; const int bxw = bx + bw; const int sw = mSDLSurface->w; const int maxHeight = std::min(by + bh, mSDLSurface->h); const int maxWidth = std::min(bxw, sw); const int i1 = by * sw + bx; const SDL_PixelFormat * const fmt = mSDLSurface->format; const uint32_t amask = fmt->Amask; const uint32_t invMask = ~fmt->Amask; const uint8_t aloss = fmt->Aloss; const uint8_t ashift = fmt->Ashift; if ((bx == 0) && bxw == sw) { const int i2 = (maxHeight - 1) * sw + maxWidth - 1; for (int i = i1; i <= i2; i++) { const uint8_t sourceAlpha = mAlphaChannel[i]; if (sourceAlpha > 0) { const uint8_t a = CAST_U8( static_cast(sourceAlpha) * mAlpha); uint32_t c = (static_cast( mSDLSurface->pixels))[i]; c &= invMask; c |= ((a >> aloss) << ashift & amask); (static_cast(mSDLSurface->pixels))[i] = c; } } } else { for (int y = by; y < maxHeight; y ++) { const int idx = y * sw; const int x1 = idx + bx; const int x2 = idx + maxWidth; for (int i = x1; i < x2; i ++) { const uint8_t sourceAlpha = mAlphaChannel[i]; if (sourceAlpha > 0) { const uint8_t a = CAST_U8( static_cast(sourceAlpha) * mAlpha); uint32_t c = (static_cast( mSDLSurface->pixels))[i]; c &= invMask; c |= ((a >> aloss) << ashift & amask); (static_cast( mSDLSurface->pixels))[i] = c; } } } } if (SDL_MUSTLOCK(mSDLSurface)) SDL_UnlockSurface(mSDLSurface); } } #ifdef USE_SDL2 else if (mTexture) { mAlpha = alpha; SDL_SetTextureAlphaMod(mTexture, CAST_U8(255 * mAlpha)); } #endif // USE_SDL2 else { mAlpha = alpha; } } Image* Image::SDLgetScaledImage(const int width, const int height) const { // No scaling on incorrect new values. if (width == 0 || height == 0) return nullptr; // No scaling when there is ... no different given size ... if (width == mBounds.w && height == mBounds.h) return nullptr; Image* scaledImage = nullptr; if (mSDLSurface != nullptr) { SDL_Surface *const scaledSurface = zoomSurface(mSDLSurface, static_cast(width) / mBounds.w, static_cast(height) / mBounds.h, 1); // The load function takes care of the SDL<->OpenGL implementation // and about freeing the given SDL_surface*. if (scaledSurface != nullptr) { scaledImage = imageHelper->loadSurface(scaledSurface); MSDL_FreeSurface(scaledSurface); } } return scaledImage; } Image *Image::getSubImage(const int x, const int y, const int width, const int height) { // Create a new clipped sub-image #ifdef USE_OPENGL const RenderType mode = OpenGLImageHelper::mUseOpenGL; if (mode == RENDER_NORMAL_OPENGL || mode == RENDER_SAFE_OPENGL || mode == RENDER_GLES_OPENGL || mode == RENDER_GLES2_OPENGL || mode == RENDER_MODERN_OPENGL) { return new SubImage(this, mGLImage, x, y, width, height, mTexWidth, mTexHeight); } #endif // USE_OPENGL #ifdef USE_SDL2 #ifndef USE_OPENGL const RenderType mode = ImageHelper::mUseOpenGL; #endif // USE_OPENGL if (mode == RENDER_SOFTWARE) return new SubImage(this, mSDLSurface, x, y, width, height); else return new SubImage(this, mTexture, x, y, width, height); #else // USE_SDL2 return new SubImage(this, mSDLSurface, x, y, width, height); #endif // USE_SDL2 } void Image::SDLTerminateAlphaCache() { SDLCleanCache(); mUseAlphaCache = false; } int Image::calcMemoryLocal() const { // +++ this calculation can be wrong for SDL2 int sz = static_cast(sizeof(Image) + sizeof(std::map)) + Resource::calcMemoryLocal(); if (mSDLSurface != nullptr) { sz += CAST_S32(mAlphaCache.size()) * MemoryManager::getSurfaceSize(mSDLSurface); } return sz; } #ifdef USE_OPENGL void Image::decRef() { if ((mGLImage != 0U) && mRefCount <= 1) OpenGLImageHelper::invalidate(mGLImage); Resource::decRef(); } #endif // USE_OPENGL