491 lines
15 KiB
C++
491 lines
15 KiB
C++
/*
|
|
* Various small SDL tools.
|
|
*
|
|
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
|
|
* Glenn Maynard
|
|
*
|
|
* Portions from SDL source and documentation.
|
|
*/
|
|
|
|
#include "global.h"
|
|
|
|
#include "SDL.h"
|
|
#include "SDL_utils.h"
|
|
#include "SDL_endian.h"
|
|
|
|
#if defined(WIN32)
|
|
|
|
/* Pull in all of our SDL libraries here. */
|
|
#ifdef DEBUG
|
|
#pragma comment(lib, "SDL-1.2.5/lib/SDLd.lib")
|
|
#pragma comment(lib, "SDL_image-1.2/SDL_imaged.lib")
|
|
#else
|
|
#pragma comment(lib, "SDL-1.2.5/lib/SDL.lib")
|
|
#pragma comment(lib, "SDL_image-1.2/SDL_image.lib")
|
|
#endif
|
|
|
|
|
|
#endif
|
|
|
|
Uint32 mySDL_Swap24(Uint32 x)
|
|
{
|
|
return SDL_Swap32(x) >> 8; // xx223344 -> 443322xx -> 00443322
|
|
}
|
|
|
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
|
#define mySDL_SwapLE24(x) mySDL_Swap24(x)
|
|
#else
|
|
#define mySDL_SwapLE24(x) (x)
|
|
#endif
|
|
|
|
/* These conditionals in the inner loop are slow. Templates? */
|
|
inline Uint32 decodepixel(const Uint8 *p, int bpp)
|
|
{
|
|
switch(bpp) {
|
|
case 1: return *p;
|
|
case 2: return *(Uint16 *)p;
|
|
case 3:
|
|
if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
|
|
return p[0] << 16 | p[1] << 8 | p[2];
|
|
else
|
|
return p[0] | p[1] << 8 | p[2] << 16;
|
|
|
|
case 4: return *(Uint32 *)p;
|
|
default: return 0; /* shouldn't happen, but avoids warnings */
|
|
}
|
|
}
|
|
|
|
void encodepixel(Uint8 *p, int bpp, Uint32 pixel)
|
|
{
|
|
switch(bpp) {
|
|
case 1: *p = Uint8(pixel); break;
|
|
case 2: *(Uint16 *)p = Uint16(pixel); break;
|
|
case 3:
|
|
if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
|
|
p[0] = Uint8((pixel >> 16) & 0xff);
|
|
p[1] = Uint8((pixel >> 8) & 0xff);
|
|
p[2] = Uint8(pixel & 0xff);
|
|
} else {
|
|
p[0] = Uint8(pixel & 0xff);
|
|
p[1] = Uint8((pixel >> 8) & 0xff);
|
|
p[2] = Uint8((pixel >> 16) & 0xff);
|
|
}
|
|
break;
|
|
case 4: *(Uint32 *)p = pixel; break;
|
|
}
|
|
}
|
|
|
|
/* Get and set colors without scaling them to 0..255. Get them into
|
|
* an array, which is much easier to work with. We need the surface
|
|
* to get at flags, or we won't know if colorkey is valid. (Why isn't
|
|
* format self-contained?) Use mySDL_GetBitsPerChannel() to get the
|
|
* number of bits per channel. */
|
|
void mySDL_GetRawRGBAV(Uint32 pixel, const SDL_Surface *src, Uint8 *v)
|
|
{
|
|
const SDL_PixelFormat *fmt = src->format;
|
|
if(src->format->BytesPerPixel == 1) {
|
|
v[0] = fmt->palette->colors[pixel].r;
|
|
v[1] = fmt->palette->colors[pixel].g;
|
|
v[2] = fmt->palette->colors[pixel].b;
|
|
v[3] = 0xFF;
|
|
} else {
|
|
v[0] = Uint8((pixel & fmt->Rmask) >> fmt->Rshift);
|
|
v[1] = Uint8((pixel & fmt->Gmask) >> fmt->Gshift);
|
|
v[2] = Uint8((pixel & fmt->Bmask) >> fmt->Bshift);
|
|
v[3] = Uint8((pixel & fmt->Amask) >> fmt->Ashift);
|
|
}
|
|
|
|
if(src->flags & SDL_SRCCOLORKEY) {
|
|
if((fmt->colorkey & ~fmt->Amask) == (pixel & ~fmt->Amask))
|
|
v[3] = 0;
|
|
}
|
|
}
|
|
|
|
void mySDL_GetRawRGBAV(const Uint8 *p, const SDL_Surface *src, Uint8 *v)
|
|
{
|
|
Uint32 pixel = decodepixel(p, src->format->BytesPerPixel);
|
|
mySDL_GetRawRGBAV(pixel, src, v);
|
|
}
|
|
|
|
void mySDL_GetRGBAV(Uint32 pixel, const SDL_Surface *src, Uint8 *v)
|
|
{
|
|
mySDL_GetRawRGBAV(pixel, src, v);
|
|
const SDL_PixelFormat *fmt = src->format;
|
|
v[0] = v[0] << fmt->Rloss;
|
|
v[1] = v[1] << fmt->Gloss;
|
|
v[2] = v[2] << fmt->Bloss;
|
|
// Correct for surfaces that don't have an alpha channel.
|
|
if( fmt->Aloss == 8)
|
|
v[3] = 255;
|
|
else
|
|
v[3] = v[3] << fmt->Aloss;
|
|
}
|
|
|
|
void mySDL_GetRGBAV(const Uint8 *p, const SDL_Surface *src, Uint8 *v)
|
|
{
|
|
Uint32 pixel = decodepixel(p, src->format->BytesPerPixel);
|
|
mySDL_GetRGBAV(pixel, src, v);
|
|
}
|
|
|
|
|
|
/* Inverse of mySDL_GetRawRGBAV. */
|
|
Uint32 mySDL_SetRawRGBAV(const SDL_PixelFormat *fmt, const Uint8 *v)
|
|
{
|
|
return v[0] << fmt->Rshift |
|
|
v[1] << fmt->Gshift |
|
|
v[2] << fmt->Bshift |
|
|
v[3] << fmt->Ashift;
|
|
}
|
|
|
|
void mySDL_SetRawRGBAV(Uint8 *p, const SDL_Surface *src, const Uint8 *v)
|
|
{
|
|
Uint32 pixel = mySDL_SetRawRGBAV(src->format, v);
|
|
encodepixel(p, src->format->BytesPerPixel, pixel);
|
|
}
|
|
|
|
/* Inverse of mySDL_GetRGBAV. */
|
|
Uint32 mySDL_SetRGBAV(const SDL_PixelFormat *fmt, const Uint8 *v)
|
|
{
|
|
return (v[0] >> fmt->Rloss) << fmt->Rshift |
|
|
(v[1] >> fmt->Gloss) << fmt->Gshift |
|
|
(v[2] >> fmt->Bloss) << fmt->Bshift |
|
|
(v[3] >> fmt->Aloss) << fmt->Ashift;
|
|
}
|
|
|
|
void mySDL_SetRGBAV(Uint8 *p, const SDL_Surface *src, const Uint8 *v)
|
|
{
|
|
Uint32 pixel = mySDL_SetRGBAV(src->format, v);
|
|
encodepixel(p, src->format->BytesPerPixel, pixel);
|
|
}
|
|
|
|
|
|
/* Get the number of bits representing each color channel in fmt. */
|
|
void mySDL_GetBitsPerChannel(const SDL_PixelFormat *fmt, Uint32 bits[4])
|
|
{
|
|
if(fmt->BytesPerPixel == 1) {
|
|
/* If we're paletted, the palette is 8888. For some reason, the
|
|
* *loss values are all 8 on paletted surfaces; they should be
|
|
* 0, to represent the palette. Since they're not, we have to
|
|
* special case this. */
|
|
bits[0] = bits[1] = bits[2] = bits[3] = 8;
|
|
return;
|
|
}
|
|
|
|
/* The actual bits stored in each color is 8-loss. */
|
|
bits[0] = 8 - fmt->Rloss;
|
|
bits[1] = 8 - fmt->Gloss;
|
|
bits[2] = 8 - fmt->Bloss;
|
|
bits[3] = 8 - fmt->Aloss;
|
|
}
|
|
|
|
void ConvertSDLSurface(SDL_Surface *&image,
|
|
int width, int height, int bpp,
|
|
Uint32 R, Uint32 G, Uint32 B, Uint32 A)
|
|
{
|
|
SDL_Surface *ret_image = SDL_CreateRGBSurfaceSane(
|
|
SDL_SWSURFACE, width, height, bpp, R, G, B, A);
|
|
ASSERT(ret_image != NULL);
|
|
|
|
/* If the formats are the same, no conversion is needed. */
|
|
if(width == image->w && height == image->h && bpp == image->format->BitsPerPixel &&
|
|
image->format->Rmask == ret_image->format->Rmask &&
|
|
image->format->Gmask == ret_image->format->Gmask &&
|
|
image->format->Bmask == ret_image->format->Bmask &&
|
|
image->format->Amask == ret_image->format->Amask)
|
|
{
|
|
/* One exception: if we have a color key and we're not paletted (8-bit).
|
|
* In this case, we need to do the blit to get rid of the color key. */
|
|
if(!( image->flags & SDL_SRCCOLORKEY && image->format->BitsPerPixel != 8) )
|
|
{
|
|
SDL_FreeSurface(ret_image);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* We don't want to actually blend the alpha channel over the destination converted
|
|
* surface; we want to simply blit it, so make sure SDL_SRCALPHA is not on. */
|
|
SDL_SetAlpha( image, 0, SDL_ALPHA_OPAQUE );
|
|
|
|
/* Copy the palette, if we have one. */
|
|
if(image->format->palette)
|
|
SDL_SetPalette(ret_image, SDL_LOGPAL, image->format->palette->colors,
|
|
0, image->format->palette->ncolors);
|
|
|
|
if(image->format->BitsPerPixel == 8 && ret_image->format->BitsPerPixel == 8 &&
|
|
image->flags & SDL_SRCCOLORKEY)
|
|
{
|
|
/* The source and dest are both paletted, and we have a color key.
|
|
* First, make sure that the image we're blitting to has a default
|
|
* color of the color key, so any places we don't blit to will
|
|
* be transparent. (The default color in the image is 0, so we're
|
|
* all set if the color key is 0.) */
|
|
if(image->format->colorkey != 0)
|
|
SDL_FillRect(ret_image, NULL, image->format->colorkey);
|
|
|
|
/* Copy over the color key mode, and then turn off color keying in the
|
|
* source so the color key index gets copied like any other color. */
|
|
SDL_SetColorKey( ret_image, SDL_SRCCOLORKEY, image->format->colorkey);
|
|
SDL_SetColorKey( image, 0, 0 );
|
|
}
|
|
|
|
SDL_Rect area;
|
|
area.x = area.y = 0;
|
|
area.w = short(image->w);
|
|
area.h = short(image->h);
|
|
|
|
SDL_BlitSurface(image, &area, ret_image, &area);
|
|
SDL_FreeSurface(image);
|
|
image = ret_image;
|
|
}
|
|
|
|
/* With d3d, textures are stored little endian (local endian for x86).
|
|
*
|
|
* I'm not sure if we should store textures in big endian, little endian
|
|
* or local endian with OpenGL. It doesn't really impact anything except
|
|
* the actual code that loads the texture itself, and OpenGL does have
|
|
* byte order toggles, so maybe we can get rid of this. */
|
|
SDL_Surface *SDL_CreateRGBSurfaceSane
|
|
(Uint32 flags, int width, int height, int depth,
|
|
Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask)
|
|
{
|
|
/* This is untested on big-endian machines. */
|
|
if(depth == 16) {
|
|
Rmask = SDL_SwapLE16((Uint16)Rmask);
|
|
Gmask = SDL_SwapLE16((Uint16)Gmask);
|
|
Bmask = SDL_SwapLE16((Uint16)Bmask);
|
|
Amask = SDL_SwapLE16((Uint16)Amask);
|
|
} else if(depth == 24) { // completely untested
|
|
Rmask = mySDL_SwapLE24(Rmask);
|
|
Gmask = mySDL_SwapLE24(Gmask);
|
|
Bmask = mySDL_SwapLE24(Bmask);
|
|
Amask = mySDL_SwapLE24(Amask);
|
|
} else if(depth == 32) {
|
|
Rmask = SDL_SwapLE32(Rmask);
|
|
Gmask = SDL_SwapLE32(Gmask);
|
|
Bmask = SDL_SwapLE32(Bmask);
|
|
Amask = SDL_SwapLE32(Amask);
|
|
}
|
|
|
|
return SDL_CreateRGBSurface(flags, width, height, depth,
|
|
Rmask, Gmask, Bmask, Amask);
|
|
}
|
|
|
|
static void FindAlphaRGB(const SDL_Surface *img, Uint8 &r, Uint8 &g, Uint8 &b, bool reverse)
|
|
{
|
|
/* If we have no alpha or no color key, there's no alpha color. */
|
|
if(img->format->BitsPerPixel == 8 && !(img->flags & SDL_SRCCOLORKEY))
|
|
return;
|
|
if(img->format->BitsPerPixel > 8 && !img->format->Amask)
|
|
return;
|
|
|
|
/* Eww. Sorry. Iterate front-to-back or in reverse. */
|
|
for(int y = reverse? img->h-1:0;
|
|
reverse? (y >=0):(y < img->h); reverse? (--y):(++y))
|
|
{
|
|
Uint8 *row = (Uint8 *)img->pixels + img->pitch*y;
|
|
if(reverse)
|
|
row += img->format->BytesPerPixel * (img->w-1);
|
|
|
|
for(int x = 0; x < img->w; ++x)
|
|
{
|
|
Uint32 val = decodepixel(row, img->format->BytesPerPixel);
|
|
if((img->format->BitsPerPixel == 8 && val != img->format->colorkey) ||
|
|
(img->format->BitsPerPixel != 8 && val & img->format->Amask))
|
|
{
|
|
/* This color isn't fully transparent, so grab it. */
|
|
SDL_GetRGB(val, img->format, &r, &g, &b);
|
|
return;
|
|
}
|
|
|
|
if(reverse)
|
|
row -= img->format->BytesPerPixel;
|
|
else
|
|
row += img->format->BytesPerPixel;
|
|
}
|
|
}
|
|
|
|
/* Huh? The image is completely transparent. */
|
|
r = g = b = 0;
|
|
}
|
|
|
|
/* Set the underlying RGB values of all pixels in 'img' that are
|
|
* completely transparent. */
|
|
static void SetAlphaRGB(const SDL_Surface *img, Uint8 r, Uint8 g, Uint8 b)
|
|
{
|
|
/* If it's a paletted surface, all we have to do is change the
|
|
* colorkey, if any. */
|
|
if(img->format->BitsPerPixel == 8)
|
|
{
|
|
if(img->flags & SDL_SRCCOLORKEY)
|
|
{
|
|
img->format->palette->colors[img->format->colorkey].r = r;
|
|
img->format->palette->colors[img->format->colorkey].g = g;
|
|
img->format->palette->colors[img->format->colorkey].b = b;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* It's RGBA. If there's no alpha channel, we have nothing to do. */
|
|
if(!img->format->Amask)
|
|
return;
|
|
|
|
Uint32 trans = SDL_MapRGBA(img->format, r, g, b, 0);
|
|
for(int y = 0; y < img->h; ++y)
|
|
{
|
|
Uint8 *row = (Uint8 *)img->pixels + img->pitch*y;
|
|
|
|
for(int x = 0; x < img->w; ++x)
|
|
{
|
|
Uint32 val = decodepixel(row, img->format->BytesPerPixel);
|
|
if(val != trans && !(val&img->format->Amask))
|
|
{
|
|
encodepixel(row, img->format->BytesPerPixel, trans);
|
|
}
|
|
|
|
row += img->format->BytesPerPixel;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* When we scale up images (which we always do in high res), pixels
|
|
* that are completely transparent can be blended with opaque pixels,
|
|
* causing their RGB elements to show. This is visible in many textures
|
|
* as a pixel-wide border in the wrong color. This is tricky to fix.
|
|
* We need to set the RGB components of completely transparent pixels
|
|
* to a reasonable color.
|
|
*
|
|
* Most images have a single border color. For these, the transparent
|
|
* color is easy: search through the image top-bottom-left-right,
|
|
* find the first non-transparent pixel, and pull out its RGB.
|
|
*
|
|
* A few images don't. We can only make a guess here. What we'll do
|
|
* is, after the above search, do the same in reverse (bottom-top-right-
|
|
* left). If the color we find is different, we'll just set the border
|
|
* color to black.
|
|
*/
|
|
void FixHiddenAlpha(SDL_Surface *img)
|
|
{
|
|
Uint8 r, g, b;
|
|
FindAlphaRGB(img, r, g, b, false);
|
|
|
|
Uint8 cr, cg, cb; /* compare */
|
|
FindAlphaRGB(img, cr, cg, cb, true);
|
|
|
|
if(cr != r || cg != g || cb != b)
|
|
r = g = b = 0;
|
|
|
|
SetAlphaRGB(img, r, g, b);
|
|
}
|
|
|
|
/* XXX: currently only TRAIT_NO_TRANSPARENCY and TRAIT_BOOL_TRANSPARENCY work. */
|
|
/* Find various traits of a surface. Do these all at once, so we only have to
|
|
* iterate the surface once. */
|
|
|
|
/* We could theoretically do a test to see if an image fits in GL_ALPHA4,
|
|
* by looking at the least significant bits of each alpha value. This is
|
|
* not likely to ever find a match, though, so don't bother; only use 8alphaonly
|
|
* if it's explicitly enabled.
|
|
*
|
|
* XXX: We could do the FindAlphaRGB search here, too, but we need that information
|
|
* in a different place. */
|
|
// #define TRAIT_CONSISTENT_TRANSPARENT_COLOR 0x0008
|
|
|
|
int FindSurfaceTraits(const SDL_Surface *img)
|
|
// Uint8 AlphaColor[3])
|
|
{
|
|
// bool HaveAlphaValue = false; /* whether ar, ag, ab is valid */
|
|
// bool HaveConsistentAlphaValue = true;
|
|
const int NEEDS_NO_ALPHA=0, NEEDS_BOOL_ALPHA=1, NEEDS_FULL_ALPHA=2;
|
|
int alpha_type = NEEDS_NO_ALPHA;
|
|
// bool HaveTransparency = false;
|
|
// bool HaveTranslucensy = false;
|
|
// bool WhiteOnly = true;
|
|
|
|
for(int y = 0; y < img->h; ++y)
|
|
{
|
|
Uint8 *row = (Uint8 *)img->pixels + img->pitch*y;
|
|
// bool FirstVisible = true;
|
|
|
|
for(int x = 0; x < img->w; ++x)
|
|
{
|
|
Uint32 val = decodepixel(row, img->format->BytesPerPixel);
|
|
|
|
if( img->format->BitsPerPixel == 8 ) {
|
|
if(img->flags & SDL_SRCCOLORKEY && val == img->format->colorkey )
|
|
alpha_type = max( alpha_type, NEEDS_BOOL_ALPHA );
|
|
} else if(img->format->Amask) {
|
|
Uint32 masked_alpha = val & img->format->Amask;
|
|
if(masked_alpha == 0)
|
|
alpha_type = max( alpha_type, NEEDS_BOOL_ALPHA );
|
|
else if(masked_alpha != img->format->Amask)
|
|
alpha_type = max( alpha_type, NEEDS_FULL_ALPHA );
|
|
}
|
|
|
|
#if 0
|
|
/* Hmm. This doesn't quite work. For example, the ScreenCompany
|
|
* shadow is actually black; we load it as 8alphaonly, which discards
|
|
* the black completely (making it a white shadow), and then we make
|
|
* it black again by setting the diffuse color to black. This is hard
|
|
* to generalize. I guess we could just make the shadow white, but
|
|
* that's a little ugly to edit.
|
|
*
|
|
* Also, for some reason, the font borders are actually a combination
|
|
* of a shade of gray and some alpha value. Those need to be simplified
|
|
* to white and some alpha value (multiply the luma by alpha), but
|
|
* that's another special case.
|
|
*
|
|
* So, this doesn't actually help anything right now, and we still
|
|
* need 8alphaonly. */
|
|
if( img->format->BitsPerPixel != 8 ) {
|
|
/* If the pixel isn't transparent, and isn't completely white: */
|
|
if(!Transparent &&
|
|
(val & img->format->Rmask) != img->format->Rmask &&
|
|
(val & img->format->Gmask) != img->format->Gmask &&
|
|
(val & img->format->Bmask) != img->format->Bmask)
|
|
WhiteOnly=false;
|
|
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
/* Is this the first non-invisible pixel on this row? */
|
|
if(!Invisible && FirstVisible)
|
|
{
|
|
FirstVisible = false;
|
|
|
|
Uint8 r, g, b;
|
|
SDL_GetRGB(val, img->format, &r, &g, &b);
|
|
|
|
if(!HaveAlphaValue) {
|
|
/* We don't have the border color yet; set it. */
|
|
HaveAlphaValue = true;
|
|
AlphaColor[0] = r;
|
|
AlphaColor[1] = g;
|
|
AlphaColor[2] = b;
|
|
} else if(HaveConsistentAlphaValue) {
|
|
/* We already have an alpha color from the previous row;
|
|
* if it's not the same, it's inconsistent. */
|
|
if(r != AlphaColor[0] || g != AlphaColor[1] || b != AlphaColor[2])
|
|
HaveConsistentAlphaValue = false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
row += img->format->BytesPerPixel;
|
|
}
|
|
}
|
|
|
|
int ret = 0;
|
|
// if(HaveConsistentAlphaValue) ret |= TRAIT_CONSISTENT_TRANSPARENT_COLOR;
|
|
switch( alpha_type )
|
|
{
|
|
case NEEDS_NO_ALPHA: ret |= TRAIT_NO_TRANSPARENCY; break;
|
|
case NEEDS_BOOL_ALPHA: ret |= TRAIT_BOOL_TRANSPARENCY; break;
|
|
case NEEDS_FULL_ALPHA: break;
|
|
default: ASSERT(0);
|
|
}
|
|
// if(WhiteOnly) ret |= TRAIT_WHITE_ONLY;
|
|
return ret;
|
|
}
|