/* * The ManaPlus Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * Copyright (C) 2011-2012 The ManaPlus 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.h" #include "resources/dye.h" #include "resources/resourcemanager.h" #ifdef USE_OPENGL #include "openglgraphics.h" #include "opengl1graphics.h" #endif #include "client.h" #include "logger.h" #include "main.h" #include "utils/stringutils.h" #include #include #include "debug.h" #ifdef USE_OPENGL int Image::mUseOpenGL = 0; int Image::mTextureType = 0; int Image::mInternalTextureType = GL_RGBA8; int Image::mTextureSize = 0; bool Image::mBlur = true; #endif bool Image::mEnableAlphaCache = false; bool Image::mEnableAlpha = true; Image::Image(SDL_Surface *image, bool hasAlphaChannel0, Uint8 *alphaChannel): mAlpha(1.0f), mHasAlphaChannel(hasAlphaChannel0), mSDLSurface(image), mAlphaChannel(alphaChannel), mIsAlphaVisible(hasAlphaChannel0), mIsAlphaCalculated(false) { #ifdef USE_OPENGL mGLImage = 0; #endif mUseAlphaCache = Image::mEnableAlphaCache; mBounds.x = 0; mBounds.y = 0; mLoaded = false; if (mSDLSurface) { mBounds.w = static_cast(mSDLSurface->w); mBounds.h = static_cast(mSDLSurface->h); mLoaded = true; } else { logger->log( "Image::Image(SDL_Surface*): Couldn't load invalid Surface!"); mBounds.w = 0; mBounds.h = 0; } } #ifdef USE_OPENGL Image::Image(GLuint glimage, int width, int height, int texWidth, int texHeight): mAlpha(1.0f), mHasAlphaChannel(true), mSDLSurface(nullptr), mAlphaChannel(nullptr), mUseAlphaCache(false), mIsAlphaVisible(true), mIsAlphaCalculated(false), mGLImage(glimage), mTexWidth(texWidth), mTexHeight(texHeight) { mBounds.x = 0; mBounds.y = 0; mBounds.w = static_cast(width); mBounds.h = static_cast(height); if (mGLImage) { mLoaded = true; } else { logger->log( "Image::Image(GLuint*, ...): Couldn't load invalid Surface!"); mLoaded = false; } } #endif Image::~Image() { unload(); } Resource *Image::load(SDL_RWops *rw) { SDL_Surface *tmpImage = IMG_Load_RW(rw, 1); if (!tmpImage) { logger->log("Error, image load failed: %s", IMG_GetError()); return nullptr; } Image *image = load(tmpImage); SDL_FreeSurface(tmpImage); return image; } Resource *Image::load(SDL_RWops *rw, Dye const &dye) { SDL_Surface *tmpImage = IMG_Load_RW(rw, 1); if (!tmpImage) { logger->log("Error, image load failed: %s", IMG_GetError()); return nullptr; } SDL_PixelFormat rgba; rgba.palette = nullptr; rgba.BitsPerPixel = 32; rgba.BytesPerPixel = 4; rgba.Rmask = 0xFF000000; rgba.Rloss = 0; rgba.Rshift = 24; rgba.Gmask = 0x00FF0000; rgba.Gloss = 0; rgba.Gshift = 16; rgba.Bmask = 0x0000FF00; rgba.Bloss = 0; rgba.Bshift = 8; rgba.Amask = 0x000000FF; rgba.Aloss = 0; rgba.Ashift = 0; rgba.colorkey = 0; rgba.alpha = 255; SDL_Surface *surf = SDL_ConvertSurface(tmpImage, &rgba, SDL_SWSURFACE); SDL_FreeSurface(tmpImage); Uint32 *pixels = static_cast(surf->pixels); DyePalette *pal = dye.getSPalete(); if (pal) { for (Uint32 *p_end = pixels + surf->w * surf->h; pixels != p_end; ++pixels) { Uint8 *p = (Uint8 *)pixels; const int alpha = *p & 255; if (!alpha) continue; pal->replaceColor(p + 1); } } else { for (Uint32 *p_end = pixels + surf->w * surf->h; pixels != p_end; ++pixels) { const Uint32 p = *pixels; const int alpha = p & 255; if (!alpha) continue; int v[3]; v[0] = (p >> 24) & 255; v[1] = (p >> 16) & 255; v[2] = (p >> 8 ) & 255; dye.update(v); *pixels = (v[0] << 24) | (v[1] << 16) | (v[2] << 8) | alpha; } } Image *image = load(surf); SDL_FreeSurface(surf); return image; } Image *Image::load(SDL_Surface *tmpImage) { #ifdef USE_OPENGL if (mUseOpenGL) return _GLload(tmpImage); #endif return _SDLload(tmpImage); } Image *Image::createTextSurface(SDL_Surface *tmpImage, float alpha) { if (!tmpImage) return nullptr; Image *img; #ifdef USE_OPENGL if (mUseOpenGL) { img = _GLload(tmpImage); if (img) img->setAlpha(alpha); return img; } #endif bool hasAlpha = false; bool converted = false; const int sz = tmpImage->w * tmpImage->h; // The alpha channel to be filled with alpha values Uint8 *alphaChannel = new Uint8[sz]; const SDL_PixelFormat * const fmt = tmpImage->format; if (fmt->Amask) { for (int i = 0; i < sz; ++ i) { Uint32 c = (static_cast(tmpImage->pixels))[i]; unsigned v = (c & fmt->Amask) >> fmt->Ashift; Uint8 a = (v << fmt->Aloss) + (v >> (8 - (fmt->Aloss << 1))); Uint8 a2 = static_cast(static_cast(a) * alpha); c &= ~fmt->Amask; c |= ((a2 >> fmt->Aloss) << fmt->Ashift & fmt->Amask); (static_cast(tmpImage->pixels))[i] = c; if (a != 255) hasAlpha = true; alphaChannel[i] = a; } } SDL_Surface *image; // Convert the surface to the current display format if (hasAlpha) { image = SDL_DisplayFormatAlpha(tmpImage); } else { image = SDL_DisplayFormat(tmpImage); // We also delete the alpha channel since // it's not used. delete [] alphaChannel; alphaChannel = nullptr; } if (!image) { logger->log1("Error: Image convert failed."); delete [] alphaChannel; return nullptr; } if (converted) SDL_FreeSurface(tmpImage); img = new Image(image, hasAlpha, alphaChannel); img->mAlpha = alpha; return img; } void Image::SDLCleanCache() { ResourceManager *resman = ResourceManager::getInstance(); for (std::map::iterator i = mAlphaCache.begin(), i_end = mAlphaCache.end(); i != i_end; ++i) { if (mSDLSurface != i->second) resman->scheduleDelete(i->second); i->second = nullptr; } mAlphaCache.clear(); } void Image::unload() { mLoaded = false; if (mSDLSurface) { SDLCleanCache(); // Free the image surface. SDL_FreeSurface(mSDLSurface); mSDLSurface = nullptr; delete [] mAlphaChannel; mAlphaChannel = nullptr; } #ifdef USE_OPENGL if (mGLImage) { glDeleteTextures(1, &mGLImage); mGLImage = 0; #ifdef DEBUG_OPENGL_LEAKS if (textures_count > 0) textures_count --; #endif } #endif } int Image::useOpenGL() const { #ifdef USE_OPENGL return mUseOpenGL; #else return 0; #endif } bool Image::hasAlphaChannel() { if (mLoaded) return mHasAlphaChannel; #ifdef USE_OPENGL if (mUseOpenGL) return true; #endif return false; } SDL_Surface *Image::getByAlpha(float alpha) { std::map::const_iterator it = mAlphaCache.find(alpha); if (it != mAlphaCache.end()) return (*it).second; return nullptr; } void Image::setAlpha(float alpha) { if (mAlpha == alpha || !mEnableAlpha) return; if (alpha < 0.0f || alpha > 1.0f) return; if (mSDLSurface) { if (mUseAlphaCache) { SDL_Surface *surface = getByAlpha(mAlpha); if (!surface) { 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 SDLCleanCache(); } surface = mSDLSurface; if (surface) mAlphaCache[mAlpha] = surface; } else { logger->log("cache bug"); } surface = getByAlpha(alpha); if (surface) { // logger->log("hit"); if (mSDLSurface == surface) logger->log("bug"); // else // SDL_FreeSurface(mSDLSurface); mAlphaCache.erase(alpha); mSDLSurface = surface; mAlpha = alpha; return; } else { mSDLSurface = Image::SDLDuplicateSurface(mSDLSurface); } // logger->log("miss"); } mAlpha = alpha; if (!hasAlphaChannel()) { // Set the alpha value this image is drawn at SDL_SetAlpha(mSDLSurface, SDL_SRCALPHA, static_cast(255 * mAlpha)); } else { if (SDL_MUSTLOCK(mSDLSurface)) SDL_LockSurface(mSDLSurface); // Precompute as much as possible const int maxHeight = std::min((mBounds.y + mBounds.h), mSDLSurface->h); const int maxWidth = std::min((mBounds.x + mBounds.w), mSDLSurface->w); const int i1 = mBounds.y * mSDLSurface->w + mBounds.x; const int i2 = (maxHeight - 1) * mSDLSurface->w + maxWidth - 1; const SDL_PixelFormat * const fmt = mSDLSurface->format; for (int i = i1; i <= i2; i++) { // Only change the pixel if it was visible at load time... Uint8 sourceAlpha = mAlphaChannel[i]; if (sourceAlpha > 0) { Uint8 a = static_cast( static_cast(sourceAlpha) * mAlpha); Uint32 c = (static_cast(mSDLSurface->pixels))[i]; c &= ~fmt->Amask; c |= ((a >> fmt->Aloss) << fmt->Ashift & fmt->Amask); (static_cast(mSDLSurface->pixels))[i] = c; } } if (SDL_MUSTLOCK(mSDLSurface)) SDL_UnlockSurface(mSDLSurface); } } else { mAlpha = alpha; } } Image* Image::SDLmerge(Image *image, int x, int y) { if (!mSDLSurface || !image || !image->mSDLSurface) return nullptr; SDL_Surface* surface = new SDL_Surface(*(image->mSDLSurface)); Uint32 surface_pix, cur_pix; Uint8 r, g, b, a, p_r, p_g, p_b, p_a; double f_a, f_ca, f_pa; SDL_PixelFormat *current_fmt = mSDLSurface->format; SDL_PixelFormat *surface_fmt = surface->format; int current_offset, surface_offset; int offset_x, offset_y; SDL_LockSurface(surface); SDL_LockSurface(mSDLSurface); const int x0 = (y * mBounds.w) + x; const int maxX = std::min(image->mBounds.w, static_cast(mBounds.w - x)); const int maxY = std::min(image->mBounds.w, static_cast(mBounds.h - y)); // for each pixel lines of a source image for (offset_x = ((x > 0) ? 0 : -x); offset_x < maxX; offset_x++) { const int x1 = x0 + offset_x; for (offset_y = ((y > 0) ? 0 : -y); offset_y < maxY; offset_y++) { // Computing offset on both images current_offset = (offset_y * mBounds.w) + x1; surface_offset = offset_y * surface->w + offset_x; // Retrieving a pixel to merge surface_pix = (static_cast( surface->pixels))[surface_offset]; cur_pix = (static_cast( mSDLSurface->pixels))[current_offset]; // Retreiving each channel of the pixel using pixel format r = static_cast(((surface_pix & surface_fmt->Rmask) >> surface_fmt->Rshift) << surface_fmt->Rloss); g = static_cast(((surface_pix & surface_fmt->Gmask) >> surface_fmt->Gshift) << surface_fmt->Gloss); b = static_cast(((surface_pix & surface_fmt->Bmask) >> surface_fmt->Bshift) << surface_fmt->Bloss); a = static_cast(((surface_pix & surface_fmt->Amask) >> surface_fmt->Ashift) << surface_fmt->Aloss); // Retreiving previous alpha value p_a = static_cast(((cur_pix & current_fmt->Amask) >> current_fmt->Ashift) << current_fmt->Aloss); // new pixel with no alpha or nothing on previous pixel if (a == SDL_ALPHA_OPAQUE || (p_a == 0 && a > 0)) { (static_cast(surface->pixels))[current_offset] = SDL_MapRGBA(current_fmt, r, g, b, a); } else if (a > 0) { // alpha is lower => merge color with previous value f_a = static_cast(a) / 255.0; f_ca = 1.0 - f_a; f_pa = static_cast(p_a) / 255.0; p_r = static_cast(((cur_pix & current_fmt->Rmask) >> current_fmt->Rshift) << current_fmt->Rloss); p_g = static_cast(((cur_pix & current_fmt->Gmask) >> current_fmt->Gshift) << current_fmt->Gloss); p_b = static_cast(((cur_pix & current_fmt->Bmask) >> current_fmt->Bshift) << current_fmt->Bloss); r = static_cast(static_cast(p_r) * f_ca * f_pa + static_cast(r) * f_a); g = static_cast(static_cast(p_g) * f_ca * f_pa + static_cast(g) * f_a); b = static_cast(static_cast(p_b) * f_ca * f_pa + static_cast(b) * f_a); a = (a > p_a ? a : p_a); (static_cast(surface->pixels))[current_offset] = SDL_MapRGBA(current_fmt, r, g, b, a); } } } SDL_UnlockSurface(surface); SDL_UnlockSurface(mSDLSurface); Image *newImage = new Image(surface); return newImage; } Image* Image::SDLgetScaledImage(int width, int height) { // 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; SDL_Surface* scaledSurface = nullptr; if (mSDLSurface) { 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) { scaledImage = load(scaledSurface); SDL_FreeSurface(scaledSurface); } } return scaledImage; } SDL_Surface* Image::convertTo32Bit(SDL_Surface* tmpImage) { if (!tmpImage) return nullptr; SDL_PixelFormat RGBAFormat; RGBAFormat.palette = nullptr; RGBAFormat.colorkey = 0; RGBAFormat.alpha = 0; RGBAFormat.BitsPerPixel = 32; RGBAFormat.BytesPerPixel = 4; #if SDL_BYTEORDER == SDL_BIG_ENDIAN RGBAFormat.Rmask = 0xFF000000; RGBAFormat.Rshift = 0; RGBAFormat.Rloss = 0; RGBAFormat.Gmask = 0x00FF0000; RGBAFormat.Gshift = 8; RGBAFormat.Gloss = 0; RGBAFormat.Bmask = 0x0000FF00; RGBAFormat.Bshift = 16; RGBAFormat.Bloss = 0; RGBAFormat.Amask = 0x000000FF; RGBAFormat.Ashift = 24; RGBAFormat.Aloss = 0; #else RGBAFormat.Rmask = 0x000000FF; RGBAFormat.Rshift = 24; RGBAFormat.Rloss = 0; RGBAFormat.Gmask = 0x0000FF00; RGBAFormat.Gshift = 16; RGBAFormat.Gloss = 0; RGBAFormat.Bmask = 0x00FF0000; RGBAFormat.Bshift = 8; RGBAFormat.Bloss = 0; RGBAFormat.Amask = 0xFF000000; RGBAFormat.Ashift = 0; RGBAFormat.Aloss = 0; #endif return SDL_ConvertSurface(tmpImage, &RGBAFormat, SDL_SWSURFACE); } SDL_Surface* Image::SDLDuplicateSurface(SDL_Surface* tmpImage) { if (!tmpImage || !tmpImage->format) return nullptr; return SDL_ConvertSurface(tmpImage, tmpImage->format, SDL_SWSURFACE); } Image *Image::_SDLload(SDL_Surface *tmpImage) { if (!tmpImage) return nullptr; bool hasAlpha = false; bool converted = false; if (tmpImage->format->BitsPerPixel != 32) { tmpImage = convertTo32Bit(tmpImage); if (!tmpImage) return nullptr; converted = true; } const int sz = tmpImage->w * tmpImage->h; // The alpha channel to be filled with alpha values Uint8 *alphaChannel = new Uint8[sz]; // Figure out whether the image uses its alpha layer if (!tmpImage->format->palette) { const SDL_PixelFormat * const fmt = tmpImage->format; if (fmt->Amask) { for (int i = 0; i < sz; ++ i) { unsigned v = ((static_cast(tmpImage->pixels))[i] & fmt->Amask) >> fmt->Ashift; Uint8 a = (v << fmt->Aloss) + (v >> (8 - (fmt->Aloss << 1))); if (a != 255) hasAlpha = true; alphaChannel[i] = a; } } else { if (SDL_ALPHA_OPAQUE != 255) { hasAlpha = true; memset(alphaChannel, SDL_ALPHA_OPAQUE, sz); } } } else { if (SDL_ALPHA_OPAQUE != 255) { hasAlpha = true; memset(alphaChannel, SDL_ALPHA_OPAQUE, sz); } } SDL_Surface *image; // Convert the surface to the current display format if (hasAlpha) { image = SDL_DisplayFormatAlpha(tmpImage); } else { image = SDL_DisplayFormat(tmpImage); // We also delete the alpha channel since // it's not used. delete [] alphaChannel; alphaChannel = nullptr; } if (!image) { logger->log1("Error: Image convert failed."); delete [] alphaChannel; return nullptr; } if (converted) SDL_FreeSurface(tmpImage); return new Image(image, hasAlpha, alphaChannel); } #ifdef USE_OPENGL Image *Image::_GLload(SDL_Surface *tmpImage) { if (!tmpImage) return nullptr; // Flush current error flag. glGetError(); int width = tmpImage->w; int height = tmpImage->h; int realWidth = powerOfTwo(width); int realHeight = powerOfTwo(height); if (realWidth < width || realHeight < height) { logger->log("Warning: image too large, cropping to %dx%d texture!", tmpImage->w, tmpImage->h); } // Make sure the alpha channel is not used, but copied to destination SDL_SetAlpha(tmpImage, 0, SDL_ALPHA_OPAQUE); // Determine 32-bit masks based on byte order Uint32 rmask, gmask, bmask, amask; #if SDL_BYTEORDER == SDL_BIG_ENDIAN rmask = 0xff000000; gmask = 0x00ff0000; bmask = 0x0000ff00; amask = 0x000000ff; #else rmask = 0x000000ff; gmask = 0x0000ff00; bmask = 0x00ff0000; amask = 0xff000000; #endif SDL_Surface *oldImage = nullptr; if (tmpImage->format->BitsPerPixel != 32 || realWidth != width || realHeight != height) { oldImage = tmpImage; tmpImage = SDL_CreateRGBSurface(SDL_SWSURFACE, realWidth, realHeight, 32, rmask, gmask, bmask, amask); if (!tmpImage) { logger->log("Error, image convert failed: out of memory"); return nullptr; } SDL_BlitSurface(oldImage, nullptr, tmpImage, nullptr); } else { static int cnt = 0; cnt ++; logger->log("fast load: %d", cnt); } GLuint texture; glGenTextures(1, &texture); if (mUseOpenGL == 1) OpenGLGraphics::bindTexture(mTextureType, texture); else if (mUseOpenGL == 2) OpenGL1Graphics::bindTexture(mTextureType, texture); if (SDL_MUSTLOCK(tmpImage)) SDL_LockSurface(tmpImage); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); if (mBlur) { glTexParameteri(mTextureType, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(mTextureType, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { glTexParameteri(mTextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(mTextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } glTexImage2D(mTextureType, 0, mInternalTextureType, tmpImage->w, tmpImage->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmpImage->pixels); /* GLint compressed; glGetTexLevelParameteriv(mTextureType, 0, GL_TEXTURE_COMPRESSED_ARB, &compressed); if (compressed) logger->log("image compressed"); else logger->log("image not compressed"); */ #ifdef DEBUG_OPENGL_LEAKS textures_count ++; #endif if (SDL_MUSTLOCK(tmpImage)) SDL_UnlockSurface(tmpImage); if (oldImage) SDL_FreeSurface(tmpImage); GLenum error = glGetError(); if (error) { std::string errmsg = "Unknown error"; switch (error) { case GL_INVALID_ENUM: errmsg = "GL_INVALID_ENUM"; break; case GL_INVALID_VALUE: errmsg = "GL_INVALID_VALUE"; break; case GL_INVALID_OPERATION: errmsg = "GL_INVALID_OPERATION"; break; case GL_STACK_OVERFLOW: errmsg = "GL_STACK_OVERFLOW"; break; case GL_STACK_UNDERFLOW: errmsg = "GL_STACK_UNDERFLOW"; break; case GL_OUT_OF_MEMORY: errmsg = "GL_OUT_OF_MEMORY"; break; default: break; } logger->log("Error: Image GL import failed: %s", errmsg.c_str()); return nullptr; } return new Image(texture, width, height, realWidth, realHeight); } void Image::setLoadAsOpenGL(int useOpenGL) { Image::mUseOpenGL = useOpenGL; } int Image::powerOfTwo(int input) { int value; if (mTextureType == GL_TEXTURE_2D) { value = 1; while (value < input && value < mTextureSize) value <<= 1; } else { value = input; } return value >= mTextureSize ? mTextureSize : value; } #endif Image *Image::getSubImage(int x, int y, int width, int height) { // Create a new clipped sub-image #ifdef USE_OPENGL if (mUseOpenGL) { return new SubImage(this, mGLImage, x, y, width, height, mTexWidth, mTexHeight); } #endif return new SubImage(this, mSDLSurface, x, y, width, height); } void Image::SDLTerminateAlphaCache() { SDLCleanCache(); mUseAlphaCache = false; } //============================================================================ // SubImage Class //============================================================================ SubImage::SubImage(Image *parent, SDL_Surface *image, int x, int y, int width, int height): Image(image), mParent(parent) { if (mParent) { mParent->incRef(); mParent->SDLTerminateAlphaCache(); mHasAlphaChannel = mParent->hasAlphaChannel(); mIsAlphaVisible = mHasAlphaChannel; mAlphaChannel = mParent->SDLgetAlphaChannel(); } else { mHasAlphaChannel = false; mIsAlphaVisible = false; mAlphaChannel = nullptr; } // Set up the rectangle. mBounds.x = static_cast(x); mBounds.y = static_cast(y); mBounds.w = static_cast(width); mBounds.h = static_cast(height); if (mParent) { mInternalBounds.x = mParent->mBounds.x; mInternalBounds.y = mParent->mBounds.y; mInternalBounds.w = mParent->mBounds.w; mInternalBounds.h = mParent->mBounds.h; } else { mInternalBounds.x = 0; mInternalBounds.y = 0; mInternalBounds.w = 1; mInternalBounds.h = 1; } mUseAlphaCache = false; } #ifdef USE_OPENGL SubImage::SubImage(Image *parent, GLuint image, int x, int y, int width, int height, int texWidth, int texHeight): Image(image, width, height, texWidth, texHeight), mParent(parent) { if (mParent) mParent->incRef(); // Set up the rectangle. mBounds.x = static_cast(x); mBounds.y = static_cast(y); mBounds.w = static_cast(width); mBounds.h = static_cast(height); if (mParent) { mInternalBounds.x = mParent->mBounds.x; mInternalBounds.y = mParent->mBounds.y; mInternalBounds.w = mParent->mBounds.w; mInternalBounds.h = mParent->mBounds.h; } else { mInternalBounds.x = 0; mInternalBounds.y = 0; mInternalBounds.w = 1; mInternalBounds.h = 1; } mIsAlphaVisible = mHasAlphaChannel; } #endif SubImage::~SubImage() { // Avoid destruction of the image mSDLSurface = nullptr; // Avoid possible destruction of its alpha channel mAlphaChannel = nullptr; #ifdef USE_OPENGL mGLImage = 0; #endif if (mParent) { mParent->decRef(); mParent = nullptr; } } Image *SubImage::getSubImage(int x, int y, int w, int h) { if (mParent) return mParent->getSubImage(mBounds.x + x, mBounds.y + y, w, h); else return nullptr; }