view engine/core/video/sdl/sdlimage.cpp @ 631:f3457443c95f

* Added drawRectangle() and fillRectangle() functions to the renderers * Modified FIFE::FloatingTextRenderer to use the new functions * Updated some data types in FIFE::FloatingTextRenderer to use integer types defined in fife_stdint.h
author prock@33b003aa-7bff-0310-803a-e67f0ece8222
date Tue, 05 Oct 2010 15:44:16 +0000
parents 853d25234671
children e3140f01749d
line wrap: on
line source

/***************************************************************************
 *   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 Lesser General Public            *
 *   License as published by the Free Software Foundation; either          *
 *   version 2.1 of the License, or (at your option) any later version.    *
 *                                                                         *
 *   This library 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     *
 *   Lesser General Public License for more details.                       *
 *                                                                         *
 *   You should have received a copy of the GNU Lesser General Public      *
 *   License along with this library; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, 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 "video/renderbackend.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;
		m_colorkey = RenderBackend::instance()->getColorKey();
		m_scale_x = 1.0;
		m_scale_y = 1.0;
		m_zoom_surface = NULL;
	}

	SDLImage::~SDLImage() {
		if (m_zoom_surface) {
			SDL_FreeSurface(m_zoom_surface);
		}
	}

	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 zoomSurface(SDL_Surface* src, SDL_Surface* dst) {
		SDL_Color* src_pointer = (SDL_Color*)src->pixels;
		SDL_Color* src_help_pointer = src_pointer;
		SDL_Color* dst_pointer = (SDL_Color*)dst->pixels;

		int x, y, *sx_ca, *sy_ca;
		int dst_gap = dst->pitch - dst->w * dst->format->BytesPerPixel;
		int sx = static_cast<int>(0xffff * src->w / dst->w);
		int sy = static_cast<int>(0xffff * src->h / dst->h);
		int sx_c = 0;
		int sy_c = 0;

		// Allocates memory and calculates row wide&height
		int* sx_a = (int*)malloc((dst->w + 1) * sizeof(Uint32));
		if (sx_a == NULL) {
			return;
		} else {
			sx_ca = sx_a;
			for (x = 0; x <= dst->w; x++) {
				*sx_ca = sx_c;
				sx_ca++;
				sx_c &= 0xffff;
				sx_c += sx;
			}
		}
		int* sy_a = (int*)malloc((dst->h + 1) * sizeof(Uint32));
		if (sy_a == NULL) {
			free(sx_a);
			return;
		} else {
			sy_ca = sy_a;
			for (y = 0; y <= dst->h; y++) {
				*sy_ca = sy_c;
				sy_ca++;
				sy_c &= 0xffff;
				sy_c += sy;
			}
			sy_ca = sy_a;
		}

		// Transfers the image data

		if(SDL_MUSTLOCK(src))
			SDL_LockSurface(src);
		if(SDL_MUSTLOCK(dst))
			SDL_LockSurface(dst);

		for (y = 0; y < dst->h; y++) {
			src_pointer = src_help_pointer;
			sx_ca = sx_a;
			for (x = 0; x < dst->w; x++) {
				*dst_pointer = *src_pointer;
				sx_ca++;
				src_pointer += (*sx_ca >> 16);
				dst_pointer++;
			}
			sy_ca++;
			src_help_pointer = (SDL_Color*)((Uint8*)src_help_pointer + (*sy_ca >> 16) * src->pitch);
			dst_pointer = (SDL_Color*)((Uint8*)dst_pointer + dst_gap);
		}

		if(SDL_MUSTLOCK(dst))
			SDL_UnlockSurface(dst);
		if(SDL_MUSTLOCK(src))
			SDL_UnlockSurface(src);

		// Free memory
		free(sx_a);
		free(sy_a);
	}

	SDL_Surface* getZoomedSurface(SDL_Surface * src, double zoomx, double zoomy) {
		if (src == NULL)
			return NULL;

		SDL_Surface *zoom_src;
		SDL_Surface *zoom_dst;
		int dst_w = static_cast<int>(round(src->w * zoomx));
		int dst_h = static_cast<int>(round(src->h * zoomy));
		if (dst_w < 1)
			dst_w = 1;
		if (dst_h < 1)
			dst_h = 1;

		// If source surface has no alpha channel then convert it
		if (src->format->Amask == 0) {
			zoom_src = SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h, 32,
					RMASK, GMASK,
					BMASK, AMASK);
			SDL_BlitSurface(src, NULL, zoom_src, NULL);
		} else {
			zoom_src = src;
		}
		// Create destination surface
		zoom_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dst_w, dst_h, 32,
				zoom_src->format->Rmask, zoom_src->format->Gmask,
				zoom_src->format->Bmask, zoom_src->format->Amask);

		// Zoom surface
		zoomSurface(zoom_src, zoom_dst);

		return zoom_dst;
	}

	bool nearlyEqual(float a, float b) {
		return ABS(a - b) <= 0.00001;
	}

	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;

		float scale_x = static_cast<float>(rect.w) / static_cast<float>(m_surface->w);
		float scale_y = static_cast<float>(rect.h) / static_cast<float>(m_surface->h);
		bool zoomed = false;
		bool equal = false;

		if (!nearlyEqual(scale_x, 1.0) && !nearlyEqual(scale_y, 1.0)) {
			zoomed = true;
			if(nearlyEqual(m_scale_x, scale_x) && nearlyEqual(m_scale_y, scale_y)) {
				equal = true;
			} else {
				m_scale_x = scale_x;
				m_scale_y = scale_y;
			}
		}

		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);
			}
			if (!zoomed) {
				SDL_BlitSurface(m_surface, 0, surface, &r);
			} else if (equal && m_zoom_surface) {
				SDL_BlitSurface(m_zoom_surface, 0, surface, &r);
			} else {
				SDL_FreeSurface(m_zoom_surface);
				m_zoom_surface = getZoomedSurface(m_surface, m_scale_x, m_scale_y);
				SDL_BlitSurface(m_zoom_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 ) ) );
				if (!zoomed) {
					SDL_BlitSurfaceWithAlpha( m_surface, 0, surface, &r, alpha );
				} else if (equal && m_zoom_surface) {
					SDL_BlitSurfaceWithAlpha(m_zoom_surface, 0, surface, &r, alpha );
				} else {
					SDL_FreeSurface(m_zoom_surface);
					m_zoom_surface = getZoomedSurface(m_surface, m_scale_x, m_scale_y);
					SDL_BlitSurfaceWithAlpha(m_zoom_surface, 0, surface, &r, alpha );
				}
			} else {
				if (!zoomed) {
					SDL_BlitSurface(m_surface, 0, surface, &r);
				} else if (equal && m_zoom_surface) {
					SDL_BlitSurface(m_zoom_surface, 0, surface, &r);
				} else {
					SDL_FreeSurface(m_zoom_surface);
					m_zoom_surface = getZoomedSurface(m_surface, m_scale_x, m_scale_y);
					SDL_BlitSurface(m_zoom_surface, 0, surface, &r);
				}
			}
		}
	}

	void SDLImage::finalize() {
		if( m_finalized ) {
			return;
		}
		m_finalized = true;
		SDL_Surface *old_surface = m_surface;
		Uint32 key = SDL_MapRGB(m_surface->format, m_colorkey.r, m_colorkey.g, m_colorkey.b);

		if (m_surface->format->Amask == 0) {
			// only use color key if feature is enabled
			if (RenderBackend::instance()->isColorKeyEnabled()) {
				SDL_SetColorKey(m_surface, SDL_SRCCOLORKEY, key);
			}

			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);

				// only use color key if feature is enabled
				if (RenderBackend::instance()->isColorKeyEnabled()) {
					SDL_SetColorKey(m_surface, SDL_SRCCOLORKEY, key);
				}

				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, m_colorkey.r, m_colorkey.g, m_colorkey.b);

		// if the global color key feature is disabled, then use the manually found color key
		if (!RenderBackend::instance()->isColorKeyEnabled()) {
			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, int a) {
		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, int a) {
		// 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, a);
					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, a);

					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, a);
					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, a);
					p += dx;
					if (p * 2 >= dy) {
						x--;
						p -= dy;
					}
				}
			}
		}
	}

	void SDLImage::drawTriangle(const Point& p1, const Point& p2, const Point& p3, int r, int g, int b, int a) {
		drawLine(p1, p2, r, g, b, a);
		drawLine(p2, p3, r, g, b, a);
		drawLine(p3, p1, r, g, b, a);
	}

	void SDLImage::drawRectangle(const Point& p, uint16_t w, uint16_t h, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
		Point p1, p2, p3, p4;

		p1.x = p.x;
		p1.y = p.y;
		p2.x = p.x+w;
		p2.y = p.y;
		p3.x = p.x+w;
		p3.y = p.y+h;
		p4.x = p.x;
		p4.y = p.y+h;

		drawLine(p1, p2, r, g, b, a);
		drawLine(p2, p3, r, g, b, a);
		drawLine(p3, p4, r, g, b, a);
		drawLine(p4, p1, r, g, b, a);
	}

	void SDLImage::fillRectangle(const Point& p, uint16_t w, uint16_t h, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
		SDL_Rect rect;
		rect.x = p.x;
		rect.y = p.y;
		rect.w = w;
		rect.h = h;

		Uint32 color = SDL_MapRGBA(m_surface->format, r, g, b, a);
		SDL_FillRect(m_surface, &rect, color);
	}

	void SDLImage::drawQuad(const Point& p1, const Point& p2, const Point& p3, const Point& p4, int r, int g, int b, int a) {
		drawLine(p1, p2, r, g, b, a);
		drawLine(p2, p3, r, g, b, a);
		drawLine(p3, p4, r, g, b, a);
		drawLine(p4, p1, r, g, b, a);
	}

	void SDLImage::drawVertex(const Point& p, const uint8_t size, int r, int g, int b, int a){
		Point p1 = Point(p.x-size, p.y+size);
		Point p2 = Point(p.x+size, p.y+size);
		Point p3 = Point(p.x+size, p.y-size);
		Point p4 = Point(p.x-size, p.y-size);

		drawLine(p1, p2, r, g, b, a);
		drawLine(p2, p3, r, g, b, a);
		drawLine(p3, p4, r, g, b, a);
		drawLine(p4, p1, r, g, b, a);
	}

	void SDLImage::saveImage(const std::string& filename) {
		if(m_surface) {
			const unsigned int swidth = getWidth();
			const unsigned int sheight = getHeight();
			SDL_Surface *surface = NULL;

			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);
		}
	}
}