wesnoth/src/sdl/utils.cpp
2014-11-17 14:35:20 -05:00

2494 lines
60 KiB
C++

/*
Copyright (C) 2003 - 2014 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
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
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file
* Support-routines for the SDL-graphics-library.
*/
#include "global.hpp"
#include "sdl/utils.hpp"
#include "sdl/alpha.hpp"
#include "sdl/rect.hpp"
#include "floating_point_emulation.hpp"
#include "neon.hpp"
#include "video.hpp"
#include "xBRZ/xbrz.hpp"
#include <algorithm>
#include <cassert>
#include <cstring>
#include <iostream>
#include <boost/math/constants/constants.hpp>
surface_lock::surface_lock(surface &surf) : surface_(surf), locked_(false)
{
if (SDL_MUSTLOCK(surface_))
locked_ = SDL_LockSurface(surface_) == 0;
}
surface_lock::~surface_lock()
{
if (locked_)
SDL_UnlockSurface(surface_);
}
const_surface_lock::const_surface_lock(const surface &surf) : surface_(surf), locked_(false)
{
if (SDL_MUSTLOCK(surface_))
locked_ = SDL_LockSurface(surface_) == 0;
}
const_surface_lock::~const_surface_lock()
{
if (locked_)
SDL_UnlockSurface(surface_);
}
SDL_Color int_to_color(const Uint32 rgb)
{
SDL_Color result;
result.r = (0x00FF0000 & rgb )>> 16;
result.g = (0x0000FF00 & rgb) >> 8;
result.b = (0x000000FF & rgb);
#ifdef SDL_GPU
result.unused = 255;
#elif SDL_VERSION_ATLEAST(2,0,0)
result.a = 0;
#else
result.unused = 0;
#endif
return result;
}
SDL_Color create_color(const unsigned char red
, unsigned char green
, unsigned char blue
, unsigned char alpha)
{
SDL_Color result;
result.r = red;
result.g = green;
result.b = blue;
#if SDL_VERSION_ATLEAST(2,0,0)
result.a = alpha;
#else
result.unused = alpha;
#endif
return result;
}
SDLKey sdl_keysym_from_name(std::string const &keyname)
{
#if SDL_VERSION_ATLEAST(2,0,0)
return SDL_GetKeyFromName(keyname.c_str());
#else
static bool initialized = false;
typedef std::map<std::string const, SDLKey> keysym_map_t;
static keysym_map_t keysym_map;
if (!initialized) {
for(SDLKey i = SDLK_FIRST; i < SDLK_LAST; i = SDLKey(int(i) + 1)) {
std::string name = SDL_GetKeyName(i);
if (!name.empty())
keysym_map[name] = i;
}
initialized = true;
}
keysym_map_t::const_iterator it = keysym_map.find(keyname);
if (it != keysym_map.end())
return it->second;
else
return SDLK_UNKNOWN;
#endif
}
bool operator<(const surface& a, const surface& b)
{
return a.get() < b.get();
}
bool is_neutral(const surface& surf)
{
return (surf->format->BytesPerPixel == 4 &&
surf->format->Rmask == 0xFF0000u &&
(surf->format->Amask | 0xFF000000u) == 0xFF000000u);
}
static SDL_PixelFormat& get_neutral_pixel_format()
{
static bool first_time = true;
static SDL_PixelFormat format;
if(first_time) {
first_time = false;
surface surf(SDL_CreateRGBSurface(SDL_SWSURFACE,1,1,32,0xFF0000,0xFF00,0xFF,0xFF000000));
format = *surf->format;
format.palette = NULL;
}
return format;
}
surface make_neutral_surface(const surface &surf)
{
if(surf == NULL) {
std::cerr << "null neutral surface...\n";
return NULL;
}
surface const result = SDL_ConvertSurface(surf,&get_neutral_pixel_format(),SDL_SWSURFACE);
if(result != NULL) {
SDL_SetAlpha(result,SDL_SRCALPHA,SDL_ALPHA_OPAQUE);
}
return result;
}
surface create_neutral_surface(int w, int h)
{
if (w < 0 || h < 0) {
std::cerr << "error : neutral surface with negative dimensions\n";
return NULL;
}
SDL_PixelFormat format = get_neutral_pixel_format();
surface result = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h,
format.BitsPerPixel,
format.Rmask,
format.Gmask,
format.Bmask,
format.Amask);
return result;
}
surface create_optimized_surface(const surface &surf)
{
if(surf == NULL)
return NULL;
surface const result = display_format_alpha(surf);
if(result == surf) {
std::cerr << "resulting surface is the same as the source!!!\n";
} else if(result == NULL) {
return surf;
}
SDL_SetAlpha(result,SDL_SRCALPHA|SDL_RLEACCEL,SDL_ALPHA_OPAQUE);
return result;
}
surface stretch_surface_horizontal(
const surface& surf, const unsigned w, const bool optimize)
{
// Since SDL version 1.1.5 0 is transparent, before 255 was transparent.
assert(SDL_ALPHA_TRANSPARENT==0);
if(surf == NULL)
return NULL;
if(static_cast<int>(w) == surf->w) {
return surf;
}
assert(w > 0);
surface dst(create_neutral_surface(w, surf->h));
surface src(make_neutral_surface(surf));
// Now both surfaces are always in the "neutral" pixel format
if(src == NULL || dst == NULL) {
std::cerr << "Could not create surface to scale onto\n";
return NULL;
}
{
// Extra scoping used for the surface_lock.
const_surface_lock src_lock(src);
surface_lock dst_lock(dst);
const Uint32* const src_pixels = src_lock.pixels();
Uint32* dst_pixels = dst_lock.pixels();
for(unsigned y = 0; y < static_cast<unsigned>(src->h); ++y) {
const Uint32 pixel = src_pixels [y * src->w];
for(unsigned x = 0; x < w; ++x) {
*dst_pixels++ = pixel;
}
}
}
return optimize ? create_optimized_surface(dst) : dst;
}
surface stretch_surface_vertical(
const surface& surf, const unsigned h, const bool optimize)
{
// Since SDL version 1.1.5 0 is transparent, before 255 was transparent.
assert(SDL_ALPHA_TRANSPARENT==0);
if(surf == NULL)
return NULL;
if(static_cast<int>(h) == surf->h) {
return surf;
}
assert(h > 0);
surface dst(create_neutral_surface(surf->w, h));
surface src(make_neutral_surface(surf));
// Now both surfaces are always in the "neutral" pixel format
if(src == NULL || dst == NULL) {
std::cerr << "Could not create surface to scale onto\n";
return NULL;
}
{
// Extra scoping used for the surface_lock.
const_surface_lock src_lock(src);
surface_lock dst_lock(dst);
const Uint32* const src_pixels = src_lock.pixels();
Uint32* dst_pixels = dst_lock.pixels();
for(unsigned y = 0; y < static_cast<unsigned>(h); ++y) {
for(unsigned x = 0; x < static_cast<unsigned>(src->w); ++x) {
*dst_pixels++ = src_pixels[x];
}
}
}
return optimize ? create_optimized_surface(dst) : dst;
}
#ifdef PANDORA
static void
scale_surface_down(surface& dst, const surface& src, const int w_dst, const int h_dst)
{
const_surface_lock src_lock(src);
surface_lock dst_lock(dst);
const Uint32* const src_pixels = src_lock.pixels();
Uint32* const dst_pixels = dst_lock.pixels();
int y_dst = 0; // The current y in the destination surface
int y_src = 0; // The current y in the source surface
int y_src_next = 0; // The next y in the source surface
int y_step = 0; // The y stepper
int h_src = src->h; // The height of the source surface
for( ; y_dst != h_dst; ++y_dst, y_src = y_src_next) {
y_step += h_src;
do {
++y_src_next;
y_step -= h_dst;
} while(y_step >= h_dst);
int x_dst = 0; // The current x in the destination surface
int x_src = 0; // The current x in the source surface
int x_src_next = 0; // The next x in the source surface
int x_step = 0; // The x stepper
int w_src = src->w; // The width of the source surface
for( ; x_dst != w_dst; ++x_dst, x_src = x_src_next) {
x_step += w_src;
do {
++x_src_next;
x_step -= w_dst;
} while(x_step >= w_dst);
int r_sum = 0, g_sum = 0, b_sum = 0, a_sum = 0;
int samples = 0;
// We now have a rectangle, (xsrc,ysrc,xratio,yratio)
// which we want to derive the pixel from
for(int x = x_src; x < x_src_next; ++x) {
for(int y = y_src; y < y_src_next; ++y) {
++samples;
const Uint32 pixel = src_pixels[y_src * w_src + x_src];
const Uint8 a = pixel >> 24;
if(a) {
a_sum += a;
r_sum += a * static_cast<Uint8>(pixel >> 16);
g_sum += a * static_cast<Uint8>(pixel >> 8);
b_sum += a * static_cast<Uint8>(pixel);
}
}
}
if(a_sum) {
const int adjustment = (a_sum | 1) >> 1;
r_sum += adjustment;
g_sum += adjustment;
b_sum += adjustment;
r_sum /= a_sum;
g_sum /= a_sum;
b_sum /= a_sum;
assert(samples == (x_src_next - x_src) * (y_src_next - y_src));
if(samples != 1) {
a_sum += (samples | 1) >> 1;
a_sum /= samples;
}
}
dst_pixels[y_dst * w_dst + x_dst] =
static_cast<Uint8>(a_sum) << 24
| static_cast<Uint8>(r_sum) << 16
| static_cast<Uint8>(g_sum) << 8
| static_cast<Uint8>(b_sum);
}
}
}
#endif
surface scale_surface_xbrz(const surface & surf, size_t z)
{
if(surf == NULL)
return NULL;
if (z > 5) {
std::cerr << "Cannot use xbrz scaling with zoom factor > 5." << std::endl;
z = 1;
}
if (z == 1) {
return create_optimized_surface(surf);
}
surface dst(create_neutral_surface(surf->w *z, surf->h * z));
if (z == 0) {
std::cerr << "Create an empty image\n";
return create_optimized_surface(dst);
}
surface src(make_neutral_surface(surf));
if(src == NULL || dst == NULL) {
std::cerr << "Could not create surface to scale onto\n";
return NULL;
}
{
const_surface_lock src_lock(src);
surface_lock dst_lock(dst);
xbrz::scale(z, src_lock.pixels(), dst_lock.pixels(), surf->w, surf->h);
}
return create_optimized_surface(dst);
}
surface scale_surface_nn (const surface & surf, int w, int h)
{
// Since SDL version 1.1.5 0 is transparent, before 255 was transparent.
assert(SDL_ALPHA_TRANSPARENT==0);
if (surf == NULL)
return NULL;
if(w == surf->w && h == surf->h) {
return surf;
}
assert(w >= 0);
assert(h >= 0);
surface dst(create_neutral_surface(w,h));
if (w == 0 || h ==0) {
std::cerr << "Create an empty image\n";
return create_optimized_surface(dst);
}
surface src(make_neutral_surface(surf));
// Now both surfaces are always in the "neutral" pixel format
if(src == NULL || dst == NULL) {
std::cerr << "Could not create surface to scale onto\n";
return NULL;
}
{
const_surface_lock src_lock(src);
surface_lock dst_lock(dst);
xbrz::nearestNeighborScale(src_lock.pixels(), surf->w, surf->h, dst_lock.pixels(), w, h);
}
return create_optimized_surface(dst);
}
// NOTE: Don't pass this function 0 scaling arguments.
surface scale_surface(const surface &surf, int w, int h, bool optimize)
{
// Since SDL version 1.1.5 0 is transparent, before 255 was transparent.
assert(SDL_ALPHA_TRANSPARENT==0);
if(surf == NULL)
return NULL;
if(w == surf->w && h == surf->h) {
return surf;
}
assert(w >= 0);
assert(h >= 0);
surface dst(create_neutral_surface(w,h));
if (w == 0 || h ==0) {
std::cerr << "Create an empty image\n";
return create_optimized_surface(dst);
}
surface src(make_neutral_surface(surf));
// Now both surfaces are always in the "neutral" pixel format
if(src == NULL || dst == NULL) {
std::cerr << "Could not create surface to scale onto\n";
return NULL;
}
{
const_surface_lock src_lock(src);
surface_lock dst_lock(dst);
const Uint32* const src_pixels = src_lock.pixels();
Uint32* const dst_pixels = dst_lock.pixels();
fixed_t xratio = fxpdiv(surf->w,w);
fixed_t yratio = fxpdiv(surf->h,h);
fixed_t ysrc = ftofxp(0.0);
for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) {
fixed_t xsrc = ftofxp(0.0);
for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) {
const int xsrcint = fxptoi(xsrc);
const int ysrcint = fxptoi(ysrc);
const Uint32* const src_word = src_pixels + ysrcint*src->w + xsrcint;
Uint32* const dst_word = dst_pixels + ydst*dst->w + xdst;
const int dx = (xsrcint + 1 < src->w) ? 1 : 0;
const int dy = (ysrcint + 1 < src->h) ? src->w : 0;
Uint8 r,g,b,a;
Uint32 rr,gg,bb,aa, temp;
Uint32 pix[4], bilin[4];
// This next part is the fixed point
// equivalent of "take everything to
// the right of the decimal point."
// These fundamental weights decide
// the contributions from various
// input pixels. The labels assume
// that the upper left corner of the
// screen ("northeast") is 0,0 but the
// code should still be consistent if
// the graphics origin is actually
// somewhere else.
//
// That is, the bilin array holds the
// "geometric" weights. I.E. If I'm scaling
// a 2 x 2 block a 10 x 10 block, then for
// pixel (2,2) of ouptut, the upper left
// pixel should be 10:1 more influential than
// the upper right, and also 10:1 more influential
// than lower left, and 100:1 more influential
// than lower right.
const fixed_t e = 0x000000FF & xsrc;
const fixed_t s = 0x000000FF & ysrc;
const fixed_t n = 0xFF - s;
const fixed_t w = 0xFF - e;
pix[0] = *src_word; // northwest
pix[1] = *(src_word + dx); // northeast
pix[2] = *(src_word + dy); // southwest
pix[3] = *(src_word + dx + dy); // southeast
bilin[0] = n*w;
bilin[1] = n*e;
bilin[2] = s*w;
bilin[3] = s*e;
int loc;
rr = bb = gg = aa = 0;
for (loc=0; loc<4; loc++) {
a = pix[loc] >> 24;
r = pix[loc] >> 16;
g = pix[loc] >> 8;
b = pix[loc] >> 0;
//We also have to implement weighting by alpha for the RGB components
//If a unit has some parts solid and some parts translucent,
//i.e. a red cloak but a dark shadow, then when we scale in
//the shadow shouldn't appear to become red at the edges.
//This part also smoothly interpolates between alpha=0 being
//transparent and having no contribution, vs being opaque.
temp = (a * bilin[loc]);
rr += r * temp;
gg += g * temp;
bb += b * temp;
aa += temp;
}
a = aa >> (16); // we average the alphas, they don't get weighted by any other factor besides bilin
if (a != 0) {
rr /= a; // finish alpha weighting: divide by sum of alphas
gg /= a;
bb /= a;
}
r = rr >> (16); // now shift over by 16 for the bilin part
g = gg >> (16);
b = bb >> (16);
*dst_word = (a << 24) + (r << 16) + (g << 8) + b;
}
}
}
return optimize ? create_optimized_surface(dst) : dst;
}
// NOTE: Don't pass this function 0 scaling arguments.
surface scale_surface_legacy(const surface &surf, int w, int h, bool optimize)
{
// Since SDL version 1.1.5 0 is transparent, before 255 was transparent.
assert(SDL_ALPHA_TRANSPARENT==0);
if(surf == NULL)
return NULL;
if(w == surf->w && h == surf->h) {
return surf;
}
assert(w >= 0);
assert(h >= 0);
surface dst(create_neutral_surface(w,h));
if (w == 0 || h ==0) {
std::cerr << "Create an empty image\n";
return create_optimized_surface(dst);
}
surface src(make_neutral_surface(surf));
// Now both surfaces are always in the "neutral" pixel format
if(src == NULL || dst == NULL) {
std::cerr << "Could not create surface to scale onto\n";
return NULL;
}
{
const_surface_lock src_lock(src);
surface_lock dst_lock(dst);
const Uint32* const src_pixels = src_lock.pixels();
Uint32* const dst_pixels = dst_lock.pixels();
fixed_t xratio = fxpdiv(surf->w,w);
fixed_t yratio = fxpdiv(surf->h,h);
fixed_t ysrc = ftofxp(0.0);
for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) {
fixed_t xsrc = ftofxp(0.0);
for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) {
const int xsrcint = fxptoi(xsrc);
const int ysrcint = fxptoi(ysrc);
const Uint32* const src_word = src_pixels + ysrcint*src->w + xsrcint;
Uint32* const dst_word = dst_pixels + ydst*dst->w + xdst;
const int dx = (xsrcint + 1 < src->w) ? 1 : 0;
const int dy = (ysrcint + 1 < src->h) ? src->w : 0;
Uint8 r,g,b,a;
Uint32 rr,gg,bb,aa;
Uint16 avg_r, avg_g, avg_b, avg_a;
Uint32 pix[4], bilin[4];
// This next part is the fixed point
// equivalent of "take everything to
// the right of the decimal point."
// These fundamental weights decide
// the contributions from various
// input pixels. The labels assume
// that the upper left corner of the
// screen ("northeast") is 0,0 but the
// code should still be consistent if
// the graphics origin is actually
// somewhere else.
const fixed_t e = 0x000000FF & xsrc;
const fixed_t s = 0x000000FF & ysrc;
const fixed_t n = 0xFF - s;
const fixed_t w = 0xFF - e;
pix[0] = *src_word; // northwest
pix[1] = *(src_word + dx); // northeast
pix[2] = *(src_word + dy); // southwest
pix[3] = *(src_word + dx + dy); // southeast
bilin[0] = n*w;
bilin[1] = n*e;
bilin[2] = s*w;
bilin[3] = s*e;
// Scope out the neighboorhood, see
// what the pixel values are like.
int count = 0;
avg_r = avg_g = avg_b = avg_a = 0;
int loc;
for (loc=0; loc<4; loc++) {
a = pix[loc] >> 24;
r = pix[loc] >> 16;
g = pix[loc] >> 8;
b = pix[loc] >> 0;
if (a != 0) {
avg_r += r;
avg_g += g;
avg_b += b;
avg_a += a;
count++;
}
}
if (count>0) {
avg_r /= count;
avg_b /= count;
avg_g /= count;
avg_a /= count;
}
// Perform modified bilinear interpolation.
// Don't trust any color information from
// an RGBA sample when the alpha channel
// is set to fully transparent.
//
// Some of the input images are hex tiles,
// created using a hexagon shaped alpha channel
// that is either set to full-on or full-off.
//
// If intermediate alpha values are introduced
// along a hex edge, it produces a gametime artifact.
// Moving the mouse around will leave behind
// "hexagon halos" from the temporary highlighting.
// In other words, the Wesnoth rendering engine
// freaks out.
//
// The alpha thresholding step attempts
// to accommodates this limitation.
// There is a small loss of quality.
// For example, skeleton bowstrings
// are not as good as they could be.
rr = gg = bb = aa = 0;
for (loc=0; loc<4; loc++) {
a = pix[loc] >> 24;
r = pix[loc] >> 16;
g = pix[loc] >> 8;
b = pix[loc] >> 0;
if (a == 0) {
r = static_cast<Uint8>(avg_r);
g = static_cast<Uint8>(avg_g);
b = static_cast<Uint8>(avg_b);
}
rr += r * bilin[loc];
gg += g * bilin[loc];
bb += b * bilin[loc];
aa += a * bilin[loc];
}
r = rr >> 16;
g = gg >> 16;
b = bb >> 16;
a = aa >> 16;
a = (a < avg_a/2) ? 0 : avg_a;
*dst_word = (a << 24) + (r << 16) + (g << 8) + b;
}
}
}
return optimize ? create_optimized_surface(dst) : dst;
}
surface scale_surface_sharp(const surface& surf, int w, int h, bool optimize)
{
// Since SDL version 1.1.5 0 is transparent, before 255 was transparent.
assert(SDL_ALPHA_TRANSPARENT==0);
if(surf == NULL)
return NULL;
if(w == surf->w && h == surf->h) {
return surf;
}
assert(w >= 0);
assert(h >= 0);
surface dst(create_neutral_surface(w,h));
if (w == 0 || h ==0) {
std::cerr << "Create an empty image\n";
return create_optimized_surface(dst);
}
surface src(make_neutral_surface(surf));
// Now both surfaces are always in the "neutral" pixel format
if(src == NULL || dst == NULL) {
std::cerr << "Could not create surface to scale onto\n";
return NULL;
}
#ifdef PANDORA
scale_surface_down(dst, src, w, h);
#else
{
const_surface_lock src_lock(src);
surface_lock dst_lock(dst);
const Uint32* const src_pixels = src_lock.pixels();
Uint32* const dst_pixels = dst_lock.pixels();
tfloat xratio = tfloat(surf->w) / w;
tfloat yratio = tfloat(surf->h) / h;
tfloat ysrc;
for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) {
tfloat xsrc;
for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) {
tfloat red, green, blue, alpha;
tfloat summation;
// We now have a rectangle, (xsrc,ysrc,xratio,yratio)
// which we want to derive the pixel from
for(tfloat xloc = xsrc; xloc < xsrc+xratio; xloc += 1) {
const tfloat xsize = std::min<tfloat>(floor(xloc + 1)-xloc,xsrc+xratio-xloc);
for(tfloat yloc = ysrc; yloc < ysrc+yratio; yloc += 1) {
const int xsrcint = std::max<int>(0,std::min<int>(src->w-1,xsrc.to_int()));
const int ysrcint = std::max<int>(0,std::min<int>(src->h-1,ysrc.to_int()));
const tfloat ysize = std::min<tfloat>(floor(yloc+1)-yloc,ysrc+yratio-yloc);
Uint8 r,g,b,a;
SDL_GetRGBA(src_pixels[ysrcint*src->w + xsrcint],src->format,&r,&g,&b,&a);
tfloat value = xsize * ysize;
summation += value;
if (!a) continue;
value *= a;
alpha += value;
red += r * value;
green += g * value;
blue += b * value;
}
}
if (alpha != 0) {
red = red / alpha + 0.5;
green = green / alpha + 0.5;
blue = blue / alpha + 0.5;
alpha = alpha / summation + 0.5;
}
dst_pixels[ydst*dst->w + xdst] = SDL_MapRGBA(
dst->format
, red.to_int()
, green.to_int()
, blue.to_int()
, alpha.to_int());
}
}
}
#endif
return optimize ? create_optimized_surface(dst) : dst;
}
surface tile_surface(const surface& surf, int w, int h, bool optimize)
{
if (surf->w == w && surf->h == h) {
return surf;
}
surface dest(create_neutral_surface(w, h));
surface src(make_neutral_surface(surf));
if (src == NULL || dest == NULL) {
std::cerr << "failed to make neutral surface\n";
return NULL;
}
{
const_surface_lock srclock(src);
surface_lock destlock(dest);
const Uint32* srcpixels = srclock.pixels();
Uint32* destpixels = destlock.pixels();
const int& sw = src->w;
const int& sh = src->h;
const int xoff = (w - sw) / 2;
const int yoff = (h - sh) / 2;
for (int i = 0; i<w*h; ++i) {
int x = ((i % w) - xoff);
int y = ((i / w) - yoff);
while ((x += sw) < 0) { /* DO NOTHING */ }
while ((y += sh) < 0) { /* DO NOTHING */ }
const int sx = x % sw;
const int sy = y % sh;
destpixels[i] = srcpixels[sy*sw + sx];
}
}
return optimize ? create_optimized_surface(dest) : dest;
}
surface adjust_surface_color(const surface &surf, int red, int green, int blue, bool optimize)
{
if(surf == NULL)
return NULL;
if((red == 0 && green == 0 && blue == 0))
return optimize ? create_optimized_surface(surf) : surf;
surface nsurf(make_neutral_surface(surf));
if(nsurf == NULL) {
std::cerr << "failed to make neutral surface\n";
return NULL;
}
{
surface_lock lock(nsurf);
Uint32* beg = lock.pixels();
Uint32* end = beg + nsurf->w*surf->h;
while(beg != end) {
Uint8 alpha = (*beg) >> 24;
if(alpha) {
Uint8 r, g, b;
r = (*beg) >> 16;
g = (*beg) >> 8;
b = (*beg) >> 0;
r = std::max<int>(0,std::min<int>(255,int(r)+red));
g = std::max<int>(0,std::min<int>(255,int(g)+green));
b = std::max<int>(0,std::min<int>(255,int(b)+blue));
*beg = (alpha << 24) + (r << 16) + (g << 8) + b;
}
++beg;
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
surface greyscale_image(const surface &surf, bool optimize)
{
if(surf == NULL)
return NULL;
surface nsurf(make_neutral_surface(surf));
if(nsurf == NULL) {
std::cerr << "failed to make neutral surface\n";
return NULL;
}
{
surface_lock lock(nsurf);
Uint32* beg = lock.pixels();
Uint32* end = beg + nsurf->w*surf->h;
while(beg != end) {
Uint8 alpha = (*beg) >> 24;
if(alpha) {
Uint8 r, g, b;
r = (*beg) >> 16;
g = (*beg) >> 8;
b = (*beg);
//const Uint8 avg = (red+green+blue)/3;
// Use the correct formula for RGB to grayscale conversion.
// Ok, this is no big deal :)
// The correct formula being:
// gray=0.299red+0.587green+0.114blue
const Uint8 avg = static_cast<Uint8>((
77 * static_cast<Uint16>(r) +
150 * static_cast<Uint16>(g) +
29 * static_cast<Uint16>(b) ) / 256);
*beg = (alpha << 24) | (avg << 16) | (avg << 8) | avg;
}
++beg;
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
surface alpha_to_greyscale(const surface &surf, bool optimize)
{
if(surf == NULL)
return NULL;
surface nsurf(make_neutral_surface(surf));
if(nsurf == NULL) {
std::cerr << "failed to make neutral surface\n";
return NULL;
}
{
surface_lock lock(nsurf);
Uint32* beg = lock.pixels();
Uint32* end = beg + nsurf->w*surf->h;
while(beg != end) {
Uint8 alpha = (*beg) >> 24;
*beg = (0xff << 24) | (alpha << 16) | (alpha << 8) | alpha;
++beg;
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
surface wipe_alpha(const surface &surf, bool optimize)
{
if(surf == NULL)
return NULL;
surface nsurf(make_neutral_surface(surf));
if(nsurf == NULL) {
std::cerr << "failed to make neutral surface\n";
return NULL;
}
{
surface_lock lock(nsurf);
Uint32* beg = lock.pixels();
Uint32* end = beg + nsurf->w*surf->h;
while(beg != end) {
*beg = 0xff000000 | *beg;
++beg;
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
surface shadow_image(const surface &surf, bool optimize)
{
if(surf == NULL)
return NULL;
// we blur it, and reuse the neutral surface created by the blur function (optimized = false)
surface nsurf (blur_alpha_surface(surf, 2, false));
if(nsurf == NULL) {
std::cerr << "failed to blur the shadow surface\n";
return NULL;
}
{
surface_lock lock(nsurf);
Uint32* beg = lock.pixels();
Uint32* end = beg + nsurf->w*surf->h;
while(beg != end) {
Uint8 alpha = (*beg) >> 24;
if(alpha) {
// increase alpha and color in black (RGB=0)
// with some stupid optimization for handling maximum values
if (alpha < 255/4)
*beg = (alpha*4) << 24;
else
*beg = 0xFF000000; // we hit the maximum
}
++beg;
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
surface recolor_image(surface surf, const std::map<Uint32, Uint32>& map_rgb, bool optimize){
if(surf == NULL)
return NULL;
if(!map_rgb.empty()){
surface nsurf(make_neutral_surface(surf));
if(nsurf == NULL) {
std::cerr << "failed to make neutral surface\n";
return NULL;
}
surface_lock lock(nsurf);
Uint32* beg = lock.pixels();
Uint32* end = beg + nsurf->w*surf->h;
while(beg != end) {
Uint8 alpha = (*beg) >> 24;
if(alpha){ // don't recolor invisible pixels.
// palette use only RGB channels, so remove alpha
Uint32 oldrgb = (*beg) & 0x00FFFFFF;
std::map<Uint32, Uint32>::const_iterator i = map_rgb.find(oldrgb);
if(i != map_rgb.end()){
*beg = (alpha << 24) + i->second;
}
}
++beg;
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
return surf;
}
surface brighten_image(const surface &surf, fixed_t amount, bool optimize)
{
if(surf == NULL) {
return NULL;
}
surface nsurf(make_neutral_surface(surf));
if(nsurf == NULL) {
std::cerr << "could not make neutral surface...\n";
return NULL;
}
{
surface_lock lock(nsurf);
Uint32* beg = lock.pixels();
Uint32* end = beg + nsurf->w*surf->h;
if (amount < 0) amount = 0;
while(beg != end) {
Uint8 alpha = (*beg) >> 24;
if(alpha) {
Uint8 r, g, b;
r = (*beg) >> 16;
g = (*beg) >> 8;
b = (*beg);
r = std::min<unsigned>(unsigned(fxpmult(r, amount)),255);
g = std::min<unsigned>(unsigned(fxpmult(g, amount)),255);
b = std::min<unsigned>(unsigned(fxpmult(b, amount)),255);
*beg = (alpha << 24) + (r << 16) + (g << 8) + b;
}
++beg;
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
surface adjust_surface_alpha(const surface &surf, fixed_t amount, bool optimize)
{
if(surf== NULL) {
return NULL;
}
surface nsurf(make_neutral_surface(surf));
if(nsurf == NULL) {
std::cerr << "could not make neutral surface...\n";
return NULL;
}
{
surface_lock lock(nsurf);
Uint32* beg = lock.pixels();
Uint32* end = beg + nsurf->w*surf->h;
if (amount < 0) amount = 0;
while(beg != end) {
Uint8 alpha = (*beg) >> 24;
if(alpha) {
Uint8 r, g, b;
r = (*beg) >> 16;
g = (*beg) >> 8;
b = (*beg);
alpha = std::min<unsigned>(unsigned(fxpmult(alpha,amount)),255);
*beg = (alpha << 24) + (r << 16) + (g << 8) + b;
}
++beg;
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
surface adjust_surface_alpha_add(const surface &surf, int amount, bool optimize)
{
if(surf== NULL) {
return NULL;
}
surface nsurf(make_neutral_surface(surf));
if(nsurf == NULL) {
std::cerr << "could not make neutral surface...\n";
return NULL;
}
{
surface_lock lock(nsurf);
Uint32* beg = lock.pixels();
Uint32* end = beg + nsurf->w*surf->h;
while(beg != end) {
Uint8 alpha = (*beg) >> 24;
if(alpha) {
Uint8 r, g, b;
r = (*beg) >> 16;
g = (*beg) >> 8;
b = (*beg);
alpha = Uint8(std::max<int>(0,std::min<int>(255,int(alpha) + amount)));
*beg = (alpha << 24) + (r << 16) + (g << 8) + b;
}
++beg;
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
surface mask_surface(const surface &surf, const surface &mask, bool* empty_result, const std::string& filename)
{
if(surf == NULL) {
return NULL;
}
if(mask == NULL) {
return surf;
}
surface nsurf = make_neutral_surface(surf);
surface nmask(make_neutral_surface(mask));
if(nsurf == NULL || nmask == NULL) {
std::cerr << "could not make neutral surface...\n";
return NULL;
}
if (nsurf->w != nmask->w) {
// we don't support efficiently different width.
// (different height is not a real problem)
// This function is used on all hexes and usually only for that
// so better keep it simple and efficient for the normal case
std::stringstream ss;
ss << "Detected an image with bad dimensions: ";
if(!filename.empty()) ss << filename << ": ";
ss << nsurf->w << "x" << nsurf->h << "\n";
std::cerr << ss.str();
std::cerr << "It will not be masked, please use: "<< nmask->w << "x" << nmask->h << "\n";
return nsurf;
}
bool empty = true;
{
surface_lock lock(nsurf);
const_surface_lock mlock(nmask);
Uint32* beg = lock.pixels();
Uint32* end = beg + nsurf->w*surf->h;
const Uint32* mbeg = mlock.pixels();
const Uint32* mend = mbeg + nmask->w*nmask->h;
while(beg != end && mbeg != mend) {
Uint8 alpha = (*beg) >> 24;
if(alpha) {
Uint8 r, g, b;
r = (*beg) >> 16;
g = (*beg) >> 8;
b = (*beg);
Uint8 malpha = (*mbeg) >> 24;
if (alpha > malpha) {
alpha = malpha;
}
if(alpha)
empty = false;
*beg = (alpha << 24) + (r << 16) + (g << 8) + b;
}
++beg;
++mbeg;
}
}
if(empty_result)
*empty_result = empty;
return nsurf;
//return create_optimized_surface(nsurf);
}
bool in_mask_surface(const surface &surf, const surface &mask)
{
if(surf == NULL) {
return false;
}
if(mask == NULL){
return true;
}
if (surf->w != mask->w || surf->h != mask->h ) {
// not same size, consider it doesn't fit
return false;
}
surface nsurf = make_neutral_surface(surf);
surface nmask(make_neutral_surface(mask));
if(nsurf == NULL || nmask == NULL) {
std::cerr << "could not make neutral surface...\n";
return false;
}
{
surface_lock lock(nsurf);
const_surface_lock mlock(nmask);
const Uint32* mbeg = mlock.pixels();
const Uint32* mend = mbeg + nmask->w*nmask->h;
Uint32* beg = lock.pixels();
// no need for 'end', because both surfaces have same size
while(mbeg != mend) {
Uint8 malpha = (*mbeg) >> 24;
if(malpha == 0) {
Uint8 alpha = (*beg) >> 24;
if (alpha)
return false;
}
++mbeg;
++beg;
}
}
return true;
}
surface submerge_alpha(const surface &surf, int depth, float alpha_base, float alpha_delta, bool optimize)
{
if(surf== NULL) {
return NULL;
}
surface nsurf(make_neutral_surface(surf));
{
surface_lock lock(nsurf);
Uint32* beg = lock.pixels();
Uint32* limit = beg + (nsurf->h-depth) * nsurf->w ;
Uint32* end = beg + nsurf->w * nsurf->h;
beg = limit; // directlt jump to the bottom part
while(beg != end){
Uint8 alpha = (*beg) >> 24;
if(alpha) {
Uint8 r, g, b;
r = (*beg) >> 16;
g = (*beg) >> 8;
b = (*beg);
int d = (beg-limit)/nsurf->w; // current depth in pixels
float a = alpha_base - d * alpha_delta;
fixed_t amount = ftofxp(a<0?0:a);
alpha = std::min<unsigned>(unsigned(fxpmult(alpha,amount)),255);
*beg = (alpha << 24) + (r << 16) + (g << 8) + b;
}
++beg;
}
/*
for(int y = submerge_height; y < nsurf->h; ++y) {
Uint32* cur = beg + y * nsurf->w;
Uint32* row_end = beg + (y+1) * nsurf->w;
float d = y * 1.0 / depth;
double a = 0.2;//std::max<double>(0, (1-d)*0.3);
fixed_t amount = ftofxp(a);
while(cur != row_end) {
Uint8 alpha = (*cur) >> 24;
if(alpha) {
Uint8 r, g, b;
r = (*cur) >> 16;
g = (*cur) >> 8;
b = (*cur);
alpha = std::min<unsigned>(unsigned(fxpmult(alpha,amount)),255);
*cur = (alpha << 24) + (r << 16) + (g << 8) + b;
}
++cur;
}
}*/
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
surface light_surface(const surface &surf, const surface &lightmap, bool optimize)
{
if(surf == NULL) {
return NULL;
}
if(lightmap == NULL) {
return surf;
}
surface nsurf = make_neutral_surface(surf);
if(nsurf == NULL) {
std::cerr << "could not make neutral surface...\n";
return NULL;
}
if (nsurf->w != lightmap->w) {
// we don't support efficiently different width.
// (different height is not a real problem)
// This function is used on all hexes and usually only for that
// so better keep it simple and efficient for the normal case
std::cerr << "Detected an image with bad dimensions: " << nsurf->w << "x" << nsurf->h << "\n";
std::cerr << "It will not be lighted, please use: "<< lightmap->w << "x" << lightmap->h << "\n";
return nsurf;
}
{
surface_lock lock(nsurf);
const_surface_lock llock(lightmap);
Uint32* beg = lock.pixels();
Uint32* end = beg + nsurf->w * nsurf->h;
const Uint32* lbeg = llock.pixels();
const Uint32* lend = lbeg + lightmap->w * lightmap->h;
while(beg != end && lbeg != lend) {
Uint8 alpha = (*beg) >> 24;
if(alpha) {
Uint8 lr, lg, lb;
lr = (*lbeg) >> 16;
lg = (*lbeg) >> 8;
lb = (*lbeg);
Uint8 r, g, b;
r = (*beg) >> 16;
g = (*beg) >> 8;
b = (*beg);
int dr = (static_cast<int>(lr) - 128) * 2;
int dg = (static_cast<int>(lg) - 128) * 2;
int db = (static_cast<int>(lb) - 128) * 2;
//note that r + dr will promote r to int (needed to avoid Uint8 math)
r = std::max<int>(0,std::min<int>(255, r + dr));
g = std::max<int>(0,std::min<int>(255, g + dg));
b = std::max<int>(0,std::min<int>(255, b + db));
*beg = (alpha << 24) + (r << 16) + (g << 8) + b;
}
++beg;
++lbeg;
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
surface blur_surface(const surface &surf, int depth, bool optimize)
{
if(surf == NULL) {
return NULL;
}
surface res = make_neutral_surface(surf);
if(res == NULL) {
std::cerr << "could not make neutral surface...\n";
return NULL;
}
SDL_Rect rect = sdl::create_rect(0, 0, surf->w, surf->h);
blur_surface(res, rect, depth);
return optimize ? create_optimized_surface(res) : res;
}
void blur_surface(surface& surf, SDL_Rect rect, unsigned depth)
{
if(surf == NULL) {
return;
}
const unsigned max_blur = 256;
if(depth > max_blur) {
depth = max_blur;
}
Uint32 queue[max_blur];
const Uint32* end_queue = queue + max_blur;
const Uint32 ff = 0xff;
const unsigned pixel_offset = rect.y * surf->w + rect.x;
surface_lock lock(surf);
for(unsigned y = 0; y < rect.h; ++y) {
const Uint32* front = &queue[0];
Uint32* back = &queue[0];
Uint32 red = 0, green = 0, blue = 0, avg = 0;
Uint32* p = lock.pixels() + pixel_offset + y * surf->w;
for(unsigned x = 0; x <= depth && x < rect.w; ++x, ++p) {
red += ((*p) >> 16)&0xFF;
green += ((*p) >> 8)&0xFF;
blue += (*p)&0xFF;
++avg;
*back++ = *p;
if(back == end_queue) {
back = &queue[0];
}
}
p = lock.pixels() + pixel_offset + y * surf->w;
for(unsigned x = 0; x < rect.w; ++x, ++p) {
*p = 0xFF000000
| (std::min(red/avg,ff) << 16)
| (std::min(green/avg,ff) << 8)
| std::min(blue/avg,ff);
if(x >= depth) {
red -= ((*front) >> 16)&0xFF;
green -= ((*front) >> 8)&0xFF;
blue -= *front&0xFF;
--avg;
++front;
if(front == end_queue) {
front = &queue[0];
}
}
if(x + depth+1 < rect.w) {
Uint32* q = p + depth+1;
red += ((*q) >> 16)&0xFF;
green += ((*q) >> 8)&0xFF;
blue += (*q)&0xFF;
++avg;
*back++ = *q;
if(back == end_queue) {
back = &queue[0];
}
}
}
}
for(unsigned x = 0; x < rect.w; ++x) {
const Uint32* front = &queue[0];
Uint32* back = &queue[0];
Uint32 red = 0, green = 0, blue = 0, avg = 0;
Uint32* p = lock.pixels() + pixel_offset + x;
for(unsigned y = 0; y <= depth && y < rect.h; ++y, p += surf->w) {
red += ((*p) >> 16)&0xFF;
green += ((*p) >> 8)&0xFF;
blue += *p&0xFF;
++avg;
*back++ = *p;
if(back == end_queue) {
back = &queue[0];
}
}
p = lock.pixels() + pixel_offset + x;
for(unsigned y = 0; y < rect.h; ++y, p += surf->w) {
*p = 0xFF000000
| (std::min(red/avg,ff) << 16)
| (std::min(green/avg,ff) << 8)
| std::min(blue/avg,ff);
if(y >= depth) {
red -= ((*front) >> 16)&0xFF;
green -= ((*front) >> 8)&0xFF;
blue -= *front&0xFF;
--avg;
++front;
if(front == end_queue) {
front = &queue[0];
}
}
if(y + depth+1 < rect.h) {
Uint32* q = p + (depth+1)*surf->w;
red += ((*q) >> 16)&0xFF;
green += ((*q) >> 8)&0xFF;
blue += (*q)&0xFF;
++avg;
*back++ = *q;
if(back == end_queue) {
back = &queue[0];
}
}
}
}
}
surface blur_alpha_surface(const surface &surf, int depth, bool optimize)
{
if(surf == NULL) {
return NULL;
}
surface res = make_neutral_surface(surf);
if(res == NULL) {
std::cerr << "could not make neutral surface...\n";
return NULL;
}
const int max_blur = 256;
if(depth > max_blur) {
depth = max_blur;
}
Uint32 queue[max_blur];
const Uint32* end_queue = queue + max_blur;
const Uint32 ff = 0xff;
surface_lock lock(res);
int x, y;
for(y = 0; y < res->h; ++y) {
const Uint32* front = &queue[0];
Uint32* back = &queue[0];
Uint32 alpha=0, red = 0, green = 0, blue = 0, avg = 0;
Uint32* p = lock.pixels() + y*res->w;
for(x = 0; x <= depth && x < res->w; ++x, ++p) {
alpha += ((*p) >> 24)&0xFF;
red += ((*p) >> 16)&0xFF;
green += ((*p) >> 8)&0xFF;
blue += (*p)&0xFF;
++avg;
*back++ = *p;
if(back == end_queue) {
back = &queue[0];
}
}
p = lock.pixels() + y*res->w;
for(x = 0; x < res->w; ++x, ++p) {
*p = (std::min(alpha/avg,ff) << 24) | (std::min(red/avg,ff) << 16) | (std::min(green/avg,ff) << 8) | std::min(blue/avg,ff);
if(x >= depth) {
alpha -= ((*front) >> 24)&0xFF;
red -= ((*front) >> 16)&0xFF;
green -= ((*front) >> 8)&0xFF;
blue -= *front&0xFF;
--avg;
++front;
if(front == end_queue) {
front = &queue[0];
}
}
if(x + depth+1 < res->w) {
Uint32* q = p + depth+1;
alpha += ((*q) >> 24)&0xFF;
red += ((*q) >> 16)&0xFF;
green += ((*q) >> 8)&0xFF;
blue += (*q)&0xFF;
++avg;
*back++ = *q;
if(back == end_queue) {
back = &queue[0];
}
}
}
}
for(x = 0; x < res->w; ++x) {
const Uint32* front = &queue[0];
Uint32* back = &queue[0];
Uint32 alpha=0, red = 0, green = 0, blue = 0, avg = 0;
Uint32* p = lock.pixels() + x;
for(y = 0; y <= depth && y < res->h; ++y, p += res->w) {
alpha += ((*p) >> 24)&0xFF;
red += ((*p) >> 16)&0xFF;
green += ((*p) >> 8)&0xFF;
blue += *p&0xFF;
++avg;
*back++ = *p;
if(back == end_queue) {
back = &queue[0];
}
}
p = lock.pixels() + x;
for(y = 0; y < res->h; ++y, p += res->w) {
assert(avg);
*p = (std::min(alpha/avg,ff) << 24) | (std::min(red/avg,ff) << 16) | (std::min(green/avg,ff) << 8) | std::min(blue/avg,ff);
if(y >= depth) {
alpha -= ((*front) >> 24)&0xFF;
red -= ((*front) >> 16)&0xFF;
green -= ((*front) >> 8)&0xFF;
blue -= *front&0xFF;
--avg;
++front;
if(front == end_queue) {
front = &queue[0];
}
}
if(y + depth+1 < res->h) {
Uint32* q = p + (depth+1)*res->w;
alpha += ((*q) >> 24)&0xFF;
red += ((*q) >> 16)&0xFF;
green += ((*q) >> 8)&0xFF;
blue += (*q)&0xFF;
++avg;
*back++ = *q;
if(back == end_queue) {
back = &queue[0];
}
}
}
}
return optimize ? create_optimized_surface(res) : res;
}
surface cut_surface(const surface &surf, SDL_Rect const &r)
{
if(surf == NULL)
return NULL;
surface res = create_compatible_surface(surf, r.w, r.h);
if(res == NULL) {
std::cerr << "Could not create a new surface in cut_surface()\n";
return NULL;
}
size_t sbpp = surf->format->BytesPerPixel;
size_t spitch = surf->pitch;
size_t rbpp = res->format->BytesPerPixel;
size_t rpitch = res->pitch;
// compute the areas to copy
SDL_Rect src_rect = r;
SDL_Rect dst_rect = { 0, 0, r.w, r.h };
if (src_rect.x < 0) {
if (src_rect.x + src_rect.w <= 0)
return res;
dst_rect.x -= src_rect.x;
dst_rect.w += src_rect.x;
src_rect.w += src_rect.x;
src_rect.x = 0;
}
if (src_rect.y < 0) {
if (src_rect.y + src_rect.h <= 0)
return res;
dst_rect.y -= src_rect.y;
dst_rect.h += src_rect.y;
src_rect.h += src_rect.y;
src_rect.y = 0;
}
if(src_rect.x >= surf->w || src_rect.y >= surf->h)
return res;
const_surface_lock slock(surf);
surface_lock rlock(res);
const Uint8* src = reinterpret_cast<const Uint8 *>(slock.pixels());
Uint8* dest = reinterpret_cast<Uint8 *>(rlock.pixels());
for(int y = 0; y < src_rect.h && (src_rect.y + y) < surf->h; ++y) {
const Uint8* line_src = src + (src_rect.y + y) * spitch + src_rect.x * sbpp;
Uint8* line_dest = dest + (dst_rect.y + y) * rpitch + dst_rect.x * rbpp;
size_t size = src_rect.w + src_rect.x <= surf->w ? src_rect.w : surf->w - src_rect.x;
assert(rpitch >= src_rect.w * rbpp);
memcpy(line_dest, line_src, size * rbpp);
}
return res;
}
surface blend_surface(
const surface &surf
, const double amount
, const Uint32 color
, const bool optimize)
{
if(surf== NULL) {
return NULL;
}
surface nsurf(make_neutral_surface(surf));
if(nsurf == NULL) {
std::cerr << "could not make neutral surface...\n";
return NULL;
}
{
surface_lock lock(nsurf);
Uint32* beg = lock.pixels();
Uint32* end = beg + nsurf->w*surf->h;
Uint16 ratio = amount * 256;
const Uint16 red = ratio * static_cast<Uint8>(color >> 16);
const Uint16 green = ratio * static_cast<Uint8>(color >> 8);
const Uint16 blue = ratio * static_cast<Uint8>(color);
ratio = 256 - ratio;
#ifdef PANDORA
/*
* Use an optimised version of the generic algorithm. The optimised
* version processes 8 pixels a time. If the number of pixels is not an
* exact multiple of 8 it falls back to the generic algorithm to handle
* the last pixels.
*/
uint16x8_t vred = vdupq_n_u16(red);
uint16x8_t vgreen = vdupq_n_u16(green);
uint16x8_t vblue = vdupq_n_u16(blue);
uint8x8_t vratio = vdup_n_u8(ratio);
const int div = (nsurf->w * surf->h) / 8;
for(int i = 0; i < div; ++i, beg += 8) {
uint8x8x4_t rgba = vld4_u8(reinterpret_cast<Uint8*>(beg));
uint16x8_t b = vmull_u8(rgba.val[0], vratio);
uint16x8_t g = vmull_u8(rgba.val[1], vratio);
uint16x8_t r = vmull_u8(rgba.val[2], vratio);
b = vaddq_u16(b, vblue);
g = vaddq_u16(g, vgreen);
r = vaddq_u16(r, vred);
rgba.val[0] = vshrn_n_u16(b, 8);
rgba.val[1] = vshrn_n_u16(g, 8);
rgba.val[2] = vshrn_n_u16(r, 8);
vst4_u8(reinterpret_cast<Uint8*>(beg), rgba);
}
#endif
while(beg != end) {
Uint8 a = static_cast<Uint8>(*beg >> 24);
Uint8 r = (ratio * static_cast<Uint8>(*beg >> 16) + red) >> 8;
Uint8 g = (ratio * static_cast<Uint8>(*beg >> 8) + green) >> 8;
Uint8 b = (ratio * static_cast<Uint8>(*beg) + blue) >> 8;
*beg = (a << 24) | (r << 16) | (g << 8) | b;
++beg;
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
/* Simplified RotSprite algorithm.
* http://en.wikipedia.org/wiki/Image_scaling#RotSprite
* Lifted from: http://github.com/salmonmoose/SpriteRotator
* 1) Zoom the source image by a certain factor.
* 2) Scan the zoomed source image at every step=offset and put it in the result. */
surface rotate_any_surface(const surface& surf, float angle, int zoom, int offset, bool optimize)
{
int src_w, src_h, dst_w, dst_h;
float min_x, max_x, min_y, max_y, sine, cosine;
{
// convert angle to radiant (angle * 2 * PI) / 360
const float radians = angle * boost::math::constants::pi<float>() / 180;
cosine = static_cast<float>(cos(radians));
sine = static_cast<float>(sin(radians));
// calculate the size of the dst image
src_w = surf->w * zoom;
src_h = surf->h * zoom;
/* See http://en.wikipedia.org/wiki/Rotation_(mathematics) */
const float point_1x = src_h * -sine;
const float point_1y = src_h * cosine;
const float point_2x = src_w * cosine - src_h * sine;
const float point_2y = src_h * cosine + src_w * sine;
const float point_3x = src_w * cosine;
const float point_3y = src_w * sine;
/* After the rotation, the new image has different dimensions.
* E.g.: The maximum height equals the former diagonal in case the angle is 45, 135, 225 or 315 degree.
* See http://en.wikipedia.org/wiki/File:Rotation_illustration2.svg to get the idea. */
min_x = std::min(0.0F, std::min(point_1x, std::min(point_2x, point_3x)));
min_y = std::min(0.0F, std::min(point_1y, std::min(point_2y, point_3y)));
max_x = (angle > 90 && angle < 180) ? 0 : std::max(point_1x, std::max(point_2x, point_3x));
max_y = (angle > 180 && angle < 270) ? 0 : std::max(point_1y, std::max(point_2y, point_3y));
dst_w = static_cast<int>(ceil(std::abs(max_x) - min_x)) / zoom;
dst_h = static_cast<int>(ceil(std::abs(max_y) - min_y)) / zoom;
}
surface dst(create_neutral_surface(dst_w, dst_h));
{
surface_lock dst_lock(dst);
const surface src = scale_surface(surf, src_w, src_h, false);
const_surface_lock src_lock(src);
const float scale = 1.f / zoom;
const int max_x = dst_w * zoom;
const int max_y = dst_h * zoom;
/* Loop through the zoomed src image,
* take every pixel in steps with offset distance and place it in the dst image. */
for (int x = 0; x < max_x; x += offset)
for (int y = 0; y < max_y; y += offset) {
// calculate the src pixel that fits in the dst
const float source_x = (x + min_x)*cosine + (y + min_y)*sine;
const float source_y = (y + min_y)*cosine - (x + min_x)*sine;
// if the pixel exists on the src surface
if (source_x >= 0 && source_x < src_w
&& source_y >= 0 && source_y < src_h)
// get it from the src surface and place it on the dst surface
put_pixel(dst, dst_lock, x*scale , y*scale, // multiply with scale
get_pixel(src, src_lock, source_x, source_y));
}
}
return optimize ? create_optimized_surface(dst) : dst;
}
void put_pixel(const surface& surf, surface_lock& surf_lock, int x, int y, Uint32 pixel)
{
const int bpp = surf->format->BytesPerPixel;
/* dst is the address to the pixel we want to set */
Uint8* const dst = reinterpret_cast<Uint8*>(surf_lock.pixels()) + y * surf->pitch + x * bpp;
switch (bpp) {
case 1:
*dst = pixel;
break;
case 2:
*reinterpret_cast<Uint16*>(dst) = pixel;
break;
case 3:
if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
dst[0] = (pixel >> 16) & 0xff;
dst[1] = (pixel >> 8) & 0xff;
dst[2] = pixel & 0xff;
} else {
dst[0] = pixel & 0xff;
dst[1] = (pixel >> 8) & 0xff;
dst[2] = (pixel >> 16) & 0xff;
}
break;
case 4:
*reinterpret_cast<Uint32*>(dst) = pixel;
break;
default:
break;
}
}
Uint32 get_pixel(const surface& surf, const const_surface_lock& surf_lock, int x, int y)
{
const int bpp = surf->format->BytesPerPixel;
/* p is the address to the pixel we want to retrieve */
const Uint8* const src = reinterpret_cast<const Uint8*>(surf_lock.pixels()) + y * surf->pitch + x * bpp;
switch (bpp) {
case 1:
return *src;
case 2:
return *reinterpret_cast<const Uint16*>(src);
case 3:
if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
return src[0] << 16 | src[1] << 8 | src[2];
else
return src[0] | src[1] << 8 | src[2] << 16;
break;
case 4:
return *reinterpret_cast<const Uint32*>(src);
}
return 0;
}
// Rotates a surface 180 degrees.
surface rotate_180_surface(const surface &surf, bool optimize)
{
if ( surf == NULL )
return NULL;
// Work with a "neutral" (unoptimized) surface.
surface nsurf(make_neutral_surface(surf));
if ( nsurf == NULL ) {
std::cerr << "could not make neutral surface...\n";
return NULL;
}
{// Code block to limit the scope of the surface lock.
surface_lock lock(nsurf);
Uint32* const pixels = lock.pixels();
// Swap pixels in the upper half of the image with
// those in the lower half.
for (int y=0; y != nsurf->h/2; ++y) {
for(int x=0; x != nsurf->w; ++x) {
const int index1 = y*nsurf->w + x;
const int index2 = (nsurf->h-y)*nsurf->w - x - 1;
std::swap(pixels[index1],pixels[index2]);
}
}
if ( is_odd(nsurf->h) ) {
// The middle row still needs to be processed.
for (int x=0; x != nsurf->w/2; ++x) {
const int index1 = (nsurf->h/2)*nsurf->w + x;
const int index2 = (nsurf->h/2)*nsurf->w + (nsurf->w - x - 1);
std::swap(pixels[index1],pixels[index2]);
}
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
// Rotates a surface 90 degrees, either clockwise or counter-clockwise.
surface rotate_90_surface(const surface &surf, bool clockwise, bool optimize)
{
if ( surf == NULL )
return NULL;
// Work with "neutral" (unoptimized) surfaces.
surface dst(create_neutral_surface(surf->h, surf->w)); // Flipped dimensions.
surface src(make_neutral_surface(surf));
if ( src == NULL || dst == NULL ) {
std::cerr << "could not make neutral surface...\n";
return NULL;
}
{// Code block to limit the scope of the surface locks.
const_surface_lock src_lock(src);
surface_lock dst_lock(dst);
const Uint32* const src_pixels = src_lock.pixels();
Uint32* const dst_pixels = dst_lock.pixels();
// Copy the pixels.
for ( int y = 0; y != src->h; ++y ) {
for ( int x = 0; x != src->w; ++x ) {
const int src_index = y*src->w + x;
const int dst_index = clockwise ?
x*dst->w + (dst->w-1-y) :
(dst->h-1-x)*dst->w + y;
dst_pixels[dst_index] = src_pixels[src_index];
}
}
}
return optimize ? create_optimized_surface(dst) : dst;
}
surface flip_surface(const surface &surf, bool optimize)
{
if(surf == NULL) {
return NULL;
}
surface nsurf(make_neutral_surface(surf));
if(nsurf == NULL) {
std::cerr << "could not make neutral surface...\n";
return NULL;
}
{
surface_lock lock(nsurf);
Uint32* const pixels = lock.pixels();
for(int y = 0; y != nsurf->h; ++y) {
for(int x = 0; x != nsurf->w/2; ++x) {
const int index1 = y*nsurf->w + x;
const int index2 = (y+1)*nsurf->w - x - 1;
std::swap(pixels[index1],pixels[index2]);
}
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
surface flop_surface(const surface &surf, bool optimize)
{
if(surf == NULL) {
return NULL;
}
surface nsurf(make_neutral_surface(surf));
if(nsurf == NULL) {
std::cerr << "could not make neutral surface...\n";
return NULL;
}
{
surface_lock lock(nsurf);
Uint32* const pixels = lock.pixels();
for(int x = 0; x != nsurf->w; ++x) {
for(int y = 0; y != nsurf->h/2; ++y) {
const int index1 = y*nsurf->w + x;
const int index2 = (nsurf->h-y-1)*surf->w + x;
std::swap(pixels[index1],pixels[index2]);
}
}
}
return optimize ? create_optimized_surface(nsurf) : nsurf;
}
surface create_compatible_surface(const surface &surf, int width, int height)
{
if(surf == NULL)
return NULL;
if(width == -1)
width = surf->w;
if(height == -1)
height = surf->h;
surface s = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, surf->format->BitsPerPixel,
surf->format->Rmask, surf->format->Gmask, surf->format->Bmask, surf->format->Amask);
if (surf->format->palette) {
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_SetPaletteColors(s->format->palette, surf->format->palette->colors, 0, surf->format->palette->ncolors);
#else
SDL_SetPalette(s, SDL_LOGPAL, surf->format->palette->colors, 0, surf->format->palette->ncolors);
#endif
}
return s;
}
void blit_surface(const surface& surf,
const SDL_Rect* srcrect, surface& dst, const SDL_Rect* dstrect)
{
assert(surf);
assert(dst);
assert(is_neutral(dst));
const surface& src = is_neutral(surf) ? surf : make_neutral_surface(surf);
// Get the areas to blit
SDL_Rect dst_rect = sdl::create_rect(0, 0, dst->w, dst->h);
if(dstrect) {
dst_rect.x = dstrect->x;
dst_rect.w -= dstrect->x;
dst_rect.y = dstrect->y;
dst_rect.h -= dstrect->y;
}
SDL_Rect src_rect = sdl::create_rect(0, 0, src->w, src->h);
if(srcrect && srcrect->w && srcrect->h) {
src_rect.x = srcrect->x;
src_rect.y = srcrect->y;
src_rect.w = srcrect->w;
src_rect.h = srcrect->h;
if (src_rect.x < 0) {
if (src_rect.x + src_rect.w <= 0 || src_rect.x + dst_rect.w <= 0 )
return;
dst_rect.x -= src_rect.x;
dst_rect.w += src_rect.x;
src_rect.w += src_rect.x;
src_rect.x = 0;
}
if (src_rect.y < 0) {
if (src_rect.y + src_rect.h <= 0 || src_rect.y + dst_rect.h <= 0 )
return;
dst_rect.y -= src_rect.y;
dst_rect.h += src_rect.y;
src_rect.h += src_rect.y;
src_rect.y = 0;
}
if (src_rect.x + src_rect.w > src->w) {
if (src_rect.x >= src->w)
return;
src_rect.w = src->w - src_rect.x;
}
if (src_rect.y + src_rect.h > src->h) {
if (src_rect.y >= src->h)
return;
src_rect.h = src->h - src_rect.y;
}
}
assert(dst_rect.x >= 0);
assert(dst_rect.y >= 0);
// Get the blit size limits.
const unsigned width = std::min(src_rect.w, dst_rect.w);
const unsigned height = std::min(src_rect.h, dst_rect.h);
{
// Extra scoping used for the surface_lock.
const_surface_lock src_lock(src);
surface_lock dst_lock(dst);
const Uint32* const src_pixels = src_lock.pixels();
Uint32* dst_pixels = dst_lock.pixels();
for(unsigned y = 0; y < height; ++y) {
for(unsigned x = 0; x < width; ++x) {
// We need to do the blitting using some optimizations
// if the src is fully transparent we can ignore this pixel
// if the src is fully opaque we can overwrite the destination with this pixel
// if the destination is fully transparent we replace us with the source
//
// We do these optimizations between the extraction of the variables
// to avoid creating variables not used (it might save us some cycles).
const int src_offset = (y + src_rect.y) * src->w + (x + src_rect.x);
assert(src_offset < src->w * src->h);
const Uint32 src_pixel = src_pixels[src_offset];
const Uint8 src_a = (src_pixel & 0xFF000000) >> 24;
if(!src_a) {
// Fully transparent source, ignore
continue;
}
const ptrdiff_t dst_offset = (y + dst_rect.y) * dst->w + (x + dst_rect.x);
assert(dst_offset < dst->w * dst->h);
if(src_a == 255) {
// Fully opaque source, copy
dst_pixels[dst_offset] = src_pixel;
continue;
}
const Uint32 dst_pixel = dst_pixels[dst_offset];
Uint8 dst_a = (dst_pixel & 0xFF000000) >> 24;
if(!dst_a) {
// Fully transparent destination, copy
dst_pixels[dst_offset] = src_pixel;
continue;
}
const Uint8 src_r = (src_pixel & 0x00FF0000) >> 16;
const Uint8 src_g = (src_pixel & 0x0000FF00) >> 8;
const Uint8 src_b = src_pixel & 0x000000FF;
Uint8 dst_r = (dst_pixel & 0x00FF0000) >> 16;
Uint8 dst_g = (dst_pixel & 0x0000FF00) >> 8;
Uint8 dst_b = dst_pixel & 0x000000FF;
if(dst_a == 255) {
// Destination fully opaque blend the source.
dst_r = (((src_r - dst_r) * src_a) >> 8 ) + dst_r;
dst_g = (((src_g - dst_g) * src_a) >> 8 ) + dst_g;
dst_b = (((src_b - dst_b) * src_a) >> 8 ) + dst_b;
} else {
// Destination and source party transparent.
// acquired the data now do the blitting
const unsigned tmp_a = 255 - src_a;
const unsigned tmp_r = 1 + (src_r * src_a) + (dst_r * tmp_a);
dst_r = (tmp_r + (tmp_r >> 8)) >> 8;
const unsigned tmp_g = 1 + (src_g * src_a) + (dst_g * tmp_a);
dst_g = (tmp_g + (tmp_g >> 8)) >> 8;
const unsigned tmp_b = 1 + (src_b * src_a) + (dst_b * tmp_a);
dst_b = (tmp_b + (tmp_b >> 8)) >> 8;
dst_a += (((255 - dst_a) * src_a) >> 8);
}
dst_pixels[dst_offset] = (dst_a << 24) | (dst_r << 16) | (dst_g << 8) | (dst_b);
}
}
}
}
surface get_surface_portion(const surface &src, SDL_Rect &area, bool optimize_format)
{
if (src == NULL) {
return NULL;
}
// Check if there is something in the portion
if(area.x >= src->w || area.y >= src->h || area.x + area.w < 0 || area.y + area.h < 0) {
return NULL;
}
if(area.x + area.w > src->w) {
area.w = src->w - area.x;
}
if(area.y + area.h > src->h) {
area.h = src->h - area.y;
}
// use same format as the source (almost always the screen)
surface dst = create_compatible_surface(src, area.w, area.h);
if(dst == NULL) {
std::cerr << "Could not create a new surface in get_surface_portion()\n";
return NULL;
}
sdl_blit(src, &area, dst, NULL);
return optimize_format ? display_format_alpha(dst) : dst;
}
namespace {
struct not_alpha
{
not_alpha() {}
// we assume neutral format
bool operator()(Uint32 pixel) const {
Uint8 alpha = pixel >> 24;
return alpha != 0x00;
}
};
}
SDL_Rect get_non_transparent_portion(const surface &surf)
{
SDL_Rect res = {0,0,0,0};
surface nsurf(make_neutral_surface(surf));
if(nsurf == NULL) {
std::cerr << "failed to make neutral surface\n";
return res;
}
const not_alpha calc;
surface_lock lock(nsurf);
const Uint32* const pixels = lock.pixels();
int n;
for(n = 0; n != nsurf->h; ++n) {
const Uint32* const start_row = pixels + n*nsurf->w;
const Uint32* const end_row = start_row + nsurf->w;
if(std::find_if(start_row,end_row,calc) != end_row)
break;
}
res.y = n;
for(n = 0; n != nsurf->h-res.y; ++n) {
const Uint32* const start_row = pixels + (nsurf->h-n-1)*surf->w;
const Uint32* const end_row = start_row + nsurf->w;
if(std::find_if(start_row,end_row,calc) != end_row)
break;
}
// The height is the height of the surface,
// minus the distance from the top and
// the distance from the bottom.
res.h = nsurf->h - res.y - n;
for(n = 0; n != nsurf->w; ++n) {
int y;
for(y = 0; y != nsurf->h; ++y) {
const Uint32 pixel = pixels[y*nsurf->w + n];
if(calc(pixel))
break;
}
if(y != nsurf->h)
break;
}
res.x = n;
for(n = 0; n != nsurf->w-res.x; ++n) {
int y;
for(y = 0; y != nsurf->h; ++y) {
const Uint32 pixel = pixels[y*nsurf->w + surf->w - n - 1];
if(calc(pixel))
break;
}
if(y != nsurf->h)
break;
}
res.w = nsurf->w - res.x - n;
return res;
}
bool operator==(const SDL_Color& a, const SDL_Color& b) {
return a.r == b.r && a.g == b.g && a.b == b.b;
}
bool operator!=(const SDL_Color& a, const SDL_Color& b) {
return !operator==(a,b);
}
SDL_Color inverse(const SDL_Color& color) {
SDL_Color inverse;
inverse.r = 255 - color.r;
inverse.g = 255 - color.g;
inverse.b = 255 - color.b;
#if SDL_VERSION_ATLEAST(2,0,0)
inverse.a = 0;
#else
inverse.unused = 0;
#endif
return inverse;
}
surface_restorer::surface_restorer() : target_(NULL), rect_(sdl::empty_rect), surface_(NULL)
{
}
surface_restorer::surface_restorer(CVideo* target, const SDL_Rect& rect)
: target_(target), rect_(rect), surface_(NULL)
{
update();
}
surface_restorer::~surface_restorer()
{
restore();
}
void surface_restorer::restore(SDL_Rect const &dst) const
{
if (surface_.null())
return;
SDL_Rect dst2 = sdl::intersect_rects(dst, rect_);
if (dst2.w == 0 || dst2.h == 0)
return;
SDL_Rect src = dst2;
src.x -= rect_.x;
src.y -= rect_.y;
sdl_blit(surface_, &src, target_->getSurface(), &dst2);
update_rect(dst2);
}
void surface_restorer::restore() const
{
if (surface_.null())
return;
SDL_Rect dst = rect_;
sdl_blit(surface_, NULL, target_->getSurface(), &dst);
update_rect(rect_);
}
void surface_restorer::update()
{
if(rect_.w == 0 || rect_.h == 0)
surface_.assign(NULL);
else
surface_.assign(::get_surface_portion(target_->getSurface(),rect_));
}
void surface_restorer::cancel()
{
surface_.assign(NULL);
}
void draw_centered_on_background(surface surf, const SDL_Rect& rect, const SDL_Color& color, surface target)
{
clip_rect_setter clip_setter(target, &rect);
#if SDL_VERSION_ATLEAST(2,0,0)
Uint32 col = SDL_MapRGBA(target->format, color.r, color.g, color.b, color.a);
#else
Uint32 col = SDL_MapRGBA(target->format, color.r, color.g, color.b, color.unused);
#endif
//TODO: only draw background outside the image
SDL_Rect r = rect;
sdl::fill_rect(target, &r, col);
if (surf != NULL) {
r.x = rect.x + (rect.w-surf->w)/2;
r.y = rect.y + (rect.h-surf->h)/2;
sdl_blit(surf, NULL, target, &r);
}
update_rect(rect);
}
std::ostream& operator<<(std::ostream& s, const SDL_Rect& rect)
{
s << rect.x << ',' << rect.y << " x " << rect.w << ',' << rect.h;
return s;
}