Mercurial > fife-parpg
diff engine/core/video/sdl/sdlimage.cpp @ 0:4a0efb7baf70
* Datasets becomes the new trunk and retires after that :-)
author | mvbarracuda@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Sun, 29 Jun 2008 18:44:17 +0000 |
parents | |
children | 90005975cdbb |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/core/video/sdl/sdlimage.cpp Sun Jun 29 18:44:17 2008 +0000 @@ -0,0 +1,629 @@ +/*************************************************************************** + * Copyright (C) 2005-2008 by the FIFE team * + * http://www.fifengine.de * + * This file is part of FIFE. * + * * + * FIFE 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 * + * (at your option) 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, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +// Standard C++ library includes +#include <cassert> +#include <iostream> + +// 3rd party library includes + +// FIFE includes +// These includes are split up in two parts, separated by one empty line +// First block: files included from the FIFE root src directory +// Second block: files included from the same folder +#include "util/log/logger.h" +#include "util/structures/rect.h" + +#include "renderbackendsdl.h" +#include "sdlblendingfunctions.h" +#include "sdlimage.h" + +namespace FIFE { + static Logger _log(LM_VIDEO); + + SDLImage::SDLImage(SDL_Surface* surface): + Image(surface) { + resetSdlimage(); + } + + SDLImage::SDLImage(const uint8_t* data, unsigned int width, unsigned int height): + Image(data, width, height) { + resetSdlimage(); + } + + void SDLImage::resetSdlimage() { + m_last_alpha = 255; + m_finalized = false; + m_isalphaoptimized = false; + } + + SDLImage::~SDLImage() { } + + + void SDL_BlitSurfaceWithAlpha( const SDL_Surface* src, const SDL_Rect* srcRect, + SDL_Surface* dst, SDL_Rect* dstRect, unsigned char alpha ) { + if( 0 == alpha ) { + return; + } + + int screenX, screenY; + if( dstRect ) { + screenX = dstRect->x; + screenY = dstRect->y; + } else { + screenX = dst->clip_rect.x; + screenY = dst->clip_rect.y; + } + + int width, height, tX, tY; + if( srcRect ) { + tX = srcRect->x; + tY = srcRect->y; + width = srcRect->w; + height = srcRect->h; + } else { + tX = src->clip_rect.x; + tY = src->clip_rect.y; + width = src->clip_rect.w; + height = src->clip_rect.h; + } + + // Clipping. + if( ( screenX >= ( dst->clip_rect.x + dst->clip_rect.w ) ) || + ( screenY >= ( dst->clip_rect.y + dst->clip_rect.h ) ) || + ( ( screenX + width ) <= dst->clip_rect.x ) || + ( ( screenY + height ) <= dst->clip_rect.y ) ) { + return; + } + + if( screenX < dst->clip_rect.x ) { + int dX = dst->clip_rect.x - screenX; + screenX += dX; + width -= dX; + tX += dX; + } + + if( ( screenX + width ) > ( dst->clip_rect.x + dst->clip_rect.w ) ) { + int dX = ( screenX + width ) - ( dst->clip_rect.x + dst->clip_rect.w ); + width -= dX; + } + + if( screenY < dst->clip_rect.y ) { + int dY = dst->clip_rect.y - screenY; + screenY += dY; + height -= dY; + tY += dY; + } + + if( ( screenY + height ) > ( dst->clip_rect.y + dst->clip_rect.h ) ) { + int dY = ( screenY + height ) - ( dst->clip_rect.y + dst->clip_rect.h ); + height -= dY; + } + + if( ( 0 >= height ) || ( 0 >= width ) ) { + return; + } + + SDL_LockSurface( dst ); + + unsigned char* srcData = reinterpret_cast< unsigned char* > ( src->pixels ); + unsigned char* dstData = reinterpret_cast< unsigned char* > ( dst->pixels ); + + // move data pointers to the start of the pixels we're copying + srcData += tY * src->pitch + tX * src->format->BytesPerPixel; + dstData += screenY * dst->pitch + screenX * dst->format->BytesPerPixel; + + switch( src->format->BitsPerPixel ) { + case 32: { + switch( dst->format->BitsPerPixel ) { + case 16: { + if( 0xFFFF == ( dst->format->Rmask | dst->format->Gmask | dst->format->Bmask ) ) { + for( int y = height; y > 0; --y ) { + SDL_BlendRow_RGBA8_to_RGB565( srcData, dstData, alpha, width ); + srcData += src->pitch; + dstData += dst->pitch; + } + } + } + break; + + case 24: { + for( int y = height; y > 0; --y ) { + SDL_BlendRow_RGBA8_to_RGB8( srcData, dstData, alpha, width ); + srcData += src->pitch; + dstData += dst->pitch; + } + } + break; + + case 32: { + for( int y = height; y > 0; --y ) { + SDL_BlendRow_RGBA8_to_RGBA8( srcData, dstData, alpha, width ); + srcData += src->pitch; + dstData += dst->pitch; + } + } + break; + + default: + break; + } ///< switch( dst->format->BitsPerPixel ) + } + break; + + case 16: { + if( 0x000F == src->format->Amask ) { + if( ( 16 == dst->format->BitsPerPixel ) && + ( 0xFFFF == ( dst->format->Rmask | dst->format->Gmask | dst->format->Bmask ) ) ) { + for( int y = height; y > 0; --y ) { + SDL_BlendRow_RGBA4_to_RGB565( srcData, dstData, alpha, width ); + srcData += src->pitch; + dstData += dst->pitch; + } + } + } + } + break; + + default: + break; + } ///< switch( src->format->BitsPerPixel ) + + SDL_UnlockSurface( dst ); + } + + void SDLImage::render(const Rect& rect, SDL_Surface* screen, unsigned char alpha) { + if (alpha == 0) { + return; + } + + if (rect.right() < 0 || rect.x > static_cast<int>(screen->w) || rect.bottom() < 0 || rect.y > static_cast<int>(screen->h)) { + return; + } + finalize(); + + SDL_Surface* surface = screen; + SDL_Rect r; + r.x = rect.x; + r.y = rect.y; + r.w = rect.w; + r.h = rect.h; + + if (m_surface->format->Amask == 0) { + // Image has no alpha channel. This allows us to use the per-surface alpha. + if (m_last_alpha != alpha) { + m_last_alpha = alpha; + SDL_SetAlpha(m_surface, SDL_SRCALPHA | SDL_RLEACCEL, alpha); + } + SDL_BlitSurface(m_surface, 0, surface, &r); + } else { + if( 255 != alpha ) { + // Special blitting routine with alpha blending: + // dst.rgb = ( src.rgb * src.a * alpha ) + ( dst.rgb * (255 - ( src.a * alpha ) ) ); + SDL_BlitSurfaceWithAlpha( m_surface, 0, surface, &r, alpha ); + } else { + SDL_BlitSurface(m_surface, 0, surface, &r); + } + } + } + + void SDLImage::finalize() { + if( m_finalized ) { + return; + } + m_finalized = true; + SDL_Surface *old_surface = m_surface; + + if (m_surface->format->Amask == 0) { + SDL_SetAlpha(m_surface, SDL_SRCALPHA | SDL_RLEACCEL, 255); + m_surface = SDL_DisplayFormat(m_surface); + } else { + RenderBackendSDL* be = static_cast<RenderBackendSDL*>(RenderBackend::instance()); + m_isalphaoptimized &= be->isAlphaOptimizerEnabled(); + if( m_isalphaoptimized ) { + m_surface = optimize(m_surface); + } else { + SDL_SetAlpha(m_surface, SDL_SRCALPHA, 255); + m_surface = SDL_DisplayFormatAlpha(m_surface); + } + } + SDL_FreeSurface(old_surface); + } + + SDL_Surface* SDLImage::optimize(SDL_Surface* src) { + // The algorithm is originally by "Tim Goya" <tuxdev103@gmail.com> + // Few modifications and adaptions by the FIFE team. + // + // It tries to determine whether an image with a alpha channel + // actually uses that. Often PNGs contains an alpha channels + // as they don't provide a colorkey feature(?) - so to speed + // up SDL rendering we try to remove the alpha channel. + + // As a reminder: src->format->Amask != 0 here + + int transparent = 0; + int opaque = 0; + int semitransparent = 0; + int alphasum = 0; + int alphasquaresum = 0; + bool colors[(1 << 12)]; + memset(colors, 0, (1 << 12) * sizeof(bool)); + + int bpp = src->format->BytesPerPixel; + if(SDL_MUSTLOCK(src)) { + SDL_LockSurface(src); + } + /* In the first pass through we calculate avg(alpha), avg(alpha^2) + and the number of semitransparent pixels. + We also try to find a useable color. + */ + for(int y = 0;y < src->h;y++) { + for(int x = 0;x < src->w;x++) { + Uint8 *pixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp; + Uint32 mapped = 0; + switch(bpp) { + case 1: + mapped = *pixel; + break; + case 2: + mapped = *(Uint16 *)pixel; + break; + case 3: +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + mapped |= pixel[0] << 16; + mapped |= pixel[1] << 8; + mapped |= pixel[2] << 0; +#else + mapped |= pixel[0] << 0; + mapped |= pixel[1] << 8; + mapped |= pixel[2] << 16; +#endif + break; + case 4: + mapped = *(Uint32 *)pixel; + break; + } + Uint8 red, green, blue, alpha; + SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha); + if(alpha < 16) { + transparent++; + } else if (alpha > 240) { + opaque++; + alphasum += alpha; + alphasquaresum += alpha*alpha; + } else { + semitransparent++; + alphasum += alpha; + alphasquaresum += alpha*alpha; + } + // mark the color as used. + if( alpha != 0 ) { + colors[((red & 0xf0) << 4) | (green & 0xf0) | ((blue & 0xf0) >> 4)] = true; + } + } + } + int avgalpha = (opaque + semitransparent) ? alphasum / (opaque + semitransparent) : 0; + int alphavariance = 0; + + if(SDL_MUSTLOCK(src)) { + SDL_UnlockSurface(src); + } + alphasquaresum /= (opaque + semitransparent) ? (opaque + semitransparent) : 1; + alphavariance = alphasquaresum - avgalpha*avgalpha; + if(semitransparent > ((transparent + opaque + semitransparent) / 8) + && alphavariance > 16) { + FL_DBG(_log, LMsg("sdlimage") + << "Trying to alpha-optimize image. FAILED: real alpha usage. " + << " alphavariance=" << alphavariance + << " total=" << (transparent + opaque + semitransparent) + << " semitransparent=" << semitransparent + << "(" << (float(semitransparent)/(transparent + opaque + semitransparent)) + << ")"); + return SDL_DisplayFormatAlpha(src); + } + + // check availability of a suitable color as colorkey + int keycolor = -1; + for(int i = 0;i < (1 << 12);i++) { + if(!colors[i]) { + keycolor = i; + break; + } + } + if(keycolor == -1) { + FL_DBG(_log, LMsg("sdlimage") << "Trying to alpha-optimize image. FAILED: no free color"); + return SDL_DisplayFormatAlpha(src); + } + + SDL_Surface *dst = SDL_CreateRGBSurface(src->flags & ~(SDL_SRCALPHA) | SDL_SWSURFACE, + src->w, src->h, + src->format->BitsPerPixel, + src->format->Rmask, src->format->Gmask, + src->format->Bmask, 0); + bpp = dst->format->BytesPerPixel; + Uint32 key = SDL_MapRGB(dst->format, + (((keycolor & 0xf00) >> 4) | 0xf), + ((keycolor & 0xf0) | 0xf), + (((keycolor & 0xf) << 4) | 0xf)); + if(SDL_MUSTLOCK(src)) { + SDL_LockSurface(src); + } + if(SDL_MUSTLOCK(dst)) { + SDL_LockSurface(dst); + } + for(int y = 0;y < dst->h;y++) { + for(int x = 0;x < dst->w;x++) { + Uint8 *srcpixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp; + Uint8 *dstpixel = (Uint8 *) dst->pixels + y * dst->pitch + x * bpp; + Uint32 mapped = 0; + switch(bpp) { + case 1: + mapped = *srcpixel; + break; + case 2: + mapped = *(Uint16 *)srcpixel; + break; + case 3: +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + mapped |= srcpixel[0] << 16; + mapped |= srcpixel[1] << 8; + mapped |= srcpixel[2] << 0; +#else + mapped |= srcpixel[0] << 0; + mapped |= srcpixel[1] << 8; + mapped |= srcpixel[2] << 16; +#endif + break; + case 4: + mapped = *(Uint32 *)srcpixel; + break; + } + Uint8 red, green, blue, alpha; + SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha); + if(alpha < (avgalpha / 4)) { + mapped = key; + } else { + mapped = SDL_MapRGB(dst->format, red, green, blue); + } + switch(bpp) { + case 1: + *dstpixel = mapped; + break; + case 2: + *(Uint16 *)dstpixel = mapped; + break; + case 3: +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + dstpixel[0] = (mapped >> 16) & 0xff; + dstpixel[1] = (mapped >> 8) & 0xff; + dstpixel[2] = (mapped >> 0) & 0xff; +#else + dstpixel[0] = (mapped >> 0) & 0xff; + dstpixel[1] = (mapped >> 8) & 0xff; + dstpixel[2] = (mapped >> 16) & 0xff; +#endif + break; + case 4: + *(Uint32 *)dstpixel = mapped; + break; + } + } + } + if(SDL_MUSTLOCK(dst)) { + SDL_UnlockSurface(dst); + } + if(SDL_MUSTLOCK(src)) { + SDL_UnlockSurface(src); + } + // Using the per surface alpha value does not + // work out for mostly transparent pixels. + // Thus disabling the part here - this needs a + // more complex refactoring. + // if(avgalpha < 240) { + // SDL_SetAlpha(dst, SDL_SRCALPHA | SDL_RLEACCEL, avgalpha); + //} + SDL_SetColorKey(dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, key); + SDL_Surface *convert = SDL_DisplayFormat(dst); + SDL_FreeSurface(dst); + FL_DBG(_log, LMsg("sdlimage ") + << "Trying to alpha-optimize image. SUCCESS: colorkey is " << key); + return convert; + } // end optimize + + bool SDLImage::putPixel(int x, int y, int r, int g, int b) { + if ((x < 0) || (x >= m_surface->w) || (y < 0) || (y >= m_surface->h)) { + return false; + } + + int bpp = m_surface->format->BytesPerPixel; + SDL_LockSurface(m_surface); + Uint8* p = (Uint8*)m_surface->pixels + y * m_surface->pitch + x * bpp; + Uint32 pixel = SDL_MapRGB(m_surface->format, r, g, b); + switch(bpp) + { + case 1: + *p = pixel; + break; + + case 2: + *(Uint16 *)p = pixel; + break; + + case 3: + if(SDL_BYTEORDER == SDL_BIG_ENDIAN) { + p[0] = (pixel >> 16) & 0xff; + p[1] = (pixel >> 8) & 0xff; + p[2] = pixel & 0xff; + } + else { + p[0] = pixel & 0xff; + p[1] = (pixel >> 8) & 0xff; + p[2] = (pixel >> 16) & 0xff; + } + break; + + case 4: + *(Uint32 *)p = pixel; + break; + } + SDL_UnlockSurface(m_surface); + return true; + } + + void SDLImage::drawLine(const Point& p1, const Point& p2, int r, int g, int b) { + // Draw a line with Bresenham, imitated from guichan + int x1 = p1.x; + int x2 = p2.x; + int y1 = p1.y; + int y2 = p2.y; + int dx = ABS(x2 - x1); + int dy = ABS(y2 - y1); + + if (dx > dy) { + if (x1 > x2) { + // swap x1, x2 + x1 ^= x2; + x2 ^= x1; + x1 ^= x2; + + // swap y1, y2 + y1 ^= y2; + y2 ^= y1; + y1 ^= y2; + } + + if (y1 < y2) { + int y = y1; + int p = 0; + + for (int x = x1; x <= x2; x++) { + putPixel(x, y, r, g, b); + p += dy; + if (p * 2 >= dx) { + y++; + p -= dx; + } + } + } + else { + int y = y1; + int p = 0; + + for (int x = x1; x <= x2; x++) { + putPixel(x, y, r, g, b); + + p += dy; + if (p * 2 >= dx) { + y--; + p -= dx; + } + } + } + } + else { + if (y1 > y2) { + // swap y1, y2 + y1 ^= y2; + y2 ^= y1; + y1 ^= y2; + + // swap x1, x2 + x1 ^= x2; + x2 ^= x1; + x1 ^= x2; + } + + if (x1 < x2) { + int x = x1; + int p = 0; + + for (int y = y1; y <= y2; y++) { + putPixel(x, y, r, g, b); + p += dx; + if (p * 2 >= dy) { + x++; + p -= dy; + } + } + } + else { + int x = x1; + int p = 0; + + for (int y = y1; y <= y2; y++) { + putPixel(x, y, r, g, b); + p += dx; + if (p * 2 >= dy) { + x--; + p -= dy; + } + } + } + } + } + + void SDLImage::drawQuad(const Point& p1, const Point& p2, const Point& p3, const Point& p4, int r, int g, int b) { + drawLine(p1, p2, r, g, b); + drawLine(p2, p3, r, g, b); + drawLine(p3, p4, r, g, b); + drawLine(p4, p1, r, g, b); + } + + void SDLImage::saveImage(const std::string& filename) { + if(m_surface) { + const unsigned int swidth = getWidth(); + const unsigned int sheight = getHeight(); + Uint32 rmask, gmask, bmask, amask; + SDL_Surface *surface = NULL; + + #if SDL_BYTEORDER == SDL_BIG_ENDIAN + rmask = 0xff000000; gmask = 0x00ff0000; bmask = 0x0000ff00; amask = 0x000000ff; + #else + rmask = 0x000000ff; gmask = 0x0000ff00; bmask = 0x00ff0000; amask = 0xff000000; + #endif + + surface = SDL_CreateRGBSurface(SDL_SWSURFACE, swidth, + sheight, 24, + rmask, gmask, bmask, 0); + + if(surface == NULL) { + return; + } + + SDL_BlitSurface(m_surface, NULL, surface, NULL); + + saveAsPng(filename, *surface); + SDL_FreeSurface(surface); + } + } + + void SDLImage::setClipArea(const Rect& cliparea, bool clear) { + SDL_Rect rect; + rect.x = cliparea.x; + rect.y = cliparea.y; + rect.w = cliparea.w; + rect.h = cliparea.h; + SDL_SetClipRect(m_surface, &rect); + if (clear) { + SDL_FillRect(m_surface, &rect, 0x00); + } + } +}