201 lines
5.9 KiB
C++
201 lines
5.9 KiB
C++
#include "global.h"
|
|
|
|
#include "SDL.h"
|
|
#include "SDL_dither.h"
|
|
#include "SDL_utils.h"
|
|
|
|
#define DitherMatDim 4
|
|
|
|
|
|
/*
|
|
Added error-diffusion algorithm. (SM_SDL_ErrorDiffusionDither)
|
|
Error distributed per-row, left to right.
|
|
http://www.gamasutra.com/features/19990521/pixel_conversion_03.htm
|
|
*/
|
|
|
|
/* Fractions, 0/16 to 15/16: */
|
|
static const int DitherMat[DitherMatDim][DitherMatDim] =
|
|
{
|
|
{ 0, 8, 2, 10 },
|
|
{ 12, 4, 14, 6 },
|
|
{ 3, 11, 1, 9 },
|
|
{ 15, 7, 13, 5 }
|
|
};
|
|
|
|
static int DitherMatCalc[DitherMatDim][DitherMatDim];
|
|
static bool DitherMatCalc_initted = false;
|
|
|
|
/* conv is the ratio from the input to the output. */
|
|
static Uint8 DitherPixel(int x, int y, int intensity, int conv)
|
|
{
|
|
/* The intensity matrix wraps. This assumes the matrix dims are a power of 2. */
|
|
x &= DitherMatDim-1;
|
|
y &= DitherMatDim-1;
|
|
|
|
/* Ordered dithering is scaling the old intensity range to the new, with
|
|
* the matrix values biasing to rounding down or up. Matrix values are in
|
|
* the range [0..1). For example, converting the 8-bit value 100 to 4
|
|
* bits directly gives 6.25. A matrix value of 0 means the pixel is not
|
|
* biased at all, which would cause it to be truncated to 6. A value
|
|
* of 5/16 means that the value is biased to 6.5625, which is also truncated
|
|
* to 6. A value of 15/16 biases to 6.1875, which causes it to be rounded
|
|
* up to 6. So, a proportion of pixels gets rounded up based on how close
|
|
* the number is to the next value. */
|
|
|
|
/* Convert the number to the destination range. */
|
|
int out_intensity = intensity * conv;
|
|
|
|
/* Add bias. */
|
|
out_intensity += DitherMatCalc[y][x];
|
|
|
|
/* Truncate, and add e to make sure a value of 14.999998 -> 15. */
|
|
return Uint8((out_intensity + 1) >> 16);
|
|
}
|
|
|
|
void SM_SDL_OrderedDither(const SDL_Surface *src, SDL_Surface *dst)
|
|
{
|
|
if(!DitherMatCalc_initted) {
|
|
for(int i = 0; i < DitherMatDim; ++i) {
|
|
for(int j = 0; j < DitherMatDim; ++j) {
|
|
/* Each value is 0..15. They represent 0/16 through 15/16.
|
|
* Set DitherMatCalc to that value * 65536, so we can do it
|
|
* with integer calcs. */
|
|
DitherMatCalc[i][j] = DitherMat[i][j] * 65536 / 16;
|
|
}
|
|
}
|
|
|
|
DitherMatCalc_initted = true;
|
|
}
|
|
|
|
|
|
/* We can't dither to paletted surfaces. */
|
|
ASSERT(dst->format->BytesPerPixel > 1);
|
|
|
|
Uint32 src_cbits[4], dst_cbits[4];
|
|
mySDL_GetBitsPerChannel(src->format, src_cbits);
|
|
mySDL_GetBitsPerChannel(dst->format, dst_cbits);
|
|
|
|
/* Calculate the ratio from the old bit depth to the new for each color channel. */
|
|
int conv[4];
|
|
for(int i = 0; i < 4; ++i)
|
|
{
|
|
int MaxInputIntensity = (1 << src_cbits[i])-1;
|
|
int MaxOutputIntensity = (1 << dst_cbits[i])-1;
|
|
/* If the source is missing the channel, avoid div/0. */
|
|
if(MaxInputIntensity == 0)
|
|
conv[i] = 0;
|
|
else
|
|
conv[i] = MaxOutputIntensity * 65536 / MaxInputIntensity;
|
|
}
|
|
|
|
/* Max alpha value; used when there's no alpha source. */
|
|
const Uint8 alpha_max = Uint8((1 << dst_cbits[3]) - 1);
|
|
|
|
/* For each row: */
|
|
for(int row = 0; row < src->h; ++row) {
|
|
const Uint8 *srcp = (const Uint8 *)src->pixels + row * src->pitch;
|
|
Uint8 *dstp = (Uint8 *)dst->pixels + row * dst->pitch;
|
|
|
|
/* For each pixel: */
|
|
for(int col = 0; col < src->w; ++col) {
|
|
Uint8 colors[4];
|
|
mySDL_GetRawRGBAV(srcp, src, colors);
|
|
|
|
/* Note that we don't dither the alpha channel. */
|
|
for(int c = 0; c < 3; ++c) {
|
|
/* If the destination has less bits, dither: */
|
|
colors[c] = DitherPixel(col, row, colors[c], conv[c]);
|
|
}
|
|
|
|
/* If the source has no alpha, the conversion formula will end up
|
|
* with 0; that's fine for color channels, but for alpha we need to
|
|
* be opaque. */
|
|
if(src_cbits[3] == 0) {
|
|
colors[3] = alpha_max;
|
|
} else {
|
|
/* Same as DitherPixel, except it doesn't actually dither; dithering
|
|
* looks bad on the alpha channel. */
|
|
int out_intensity = colors[3] * conv[3];
|
|
|
|
/* Truncate, and add e to make sure a value of 14.999998 -> 15. */
|
|
colors[3] = Uint8((out_intensity + 1) >> 16);
|
|
}
|
|
|
|
/* Raw value -> int -> pixel */
|
|
mySDL_SetRawRGBAV(dstp, dst, colors);
|
|
|
|
srcp += src->format->BytesPerPixel;
|
|
dstp += dst->format->BytesPerPixel;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#define CLAMP(x, l, h) {if (x > h) x = h; else if (x < l) x = l;}
|
|
|
|
void SM_SDL_ErrorDiffusionDither(const SDL_Surface *src, SDL_Surface *dst)
|
|
{
|
|
/* We can't dither to paletted surfaces. */
|
|
ASSERT(dst->format->BytesPerPixel > 1);
|
|
|
|
/* For each row: */
|
|
for(int row = 0; row < src->h; ++row)
|
|
{
|
|
Sint32 accumError[4] = { 0, 0, 0, 0 }; // accum error values are reset every row
|
|
|
|
const Uint8 *srcp = (const Uint8 *)src->pixels + row * src->pitch;
|
|
Uint8 *dstp = (Uint8 *)dst->pixels + row * dst->pitch;
|
|
|
|
/* For each pixel in row: */
|
|
for(int col = 0; col < src->w; ++col)
|
|
{
|
|
Uint8 originalColors[4];
|
|
mySDL_GetRGBAV(srcp, src, originalColors);
|
|
|
|
Uint8 colorsPlusError[4];
|
|
int c;
|
|
for(c = 0; c < 4; ++c)
|
|
{
|
|
// move some error to the new pixel (without overflowing)
|
|
Sint32 errorToAdd;
|
|
if( accumError[c] >= 0 )
|
|
errorToAdd = min( accumError[c], (Sint32)255 - originalColors[c] );
|
|
else
|
|
errorToAdd = max( accumError[c], (Sint32)-originalColors[c] );
|
|
|
|
colorsPlusError[c] = (Uint8)(originalColors[c] + errorToAdd);
|
|
accumError[c] -= errorToAdd;
|
|
|
|
// make sure we didn't overflow
|
|
ASSERT( (Uint8)(originalColors[c] + errorToAdd) == (Sint32)(originalColors[c] + errorToAdd) );
|
|
|
|
}
|
|
mySDL_SetRGBAV(dstp, dst, colorsPlusError);
|
|
|
|
|
|
Uint8 ditheredColors[4];
|
|
mySDL_GetRGBAV(dstp, dst, ditheredColors);
|
|
|
|
for(c = 0; c < 4; ++c)
|
|
accumError[c] += originalColors[c] - ditheredColors[c];
|
|
|
|
/* Blank the alpha accumulated error.
|
|
* This has the effect of not dithering the alpha channel. */
|
|
accumError[3] = 0;
|
|
|
|
for(c = 0; c < 4; ++c)
|
|
{
|
|
// Reduce funky streaks in low-bit channels by clamping error.
|
|
CLAMP( accumError[c], -128, +128 );
|
|
|
|
// Keep only a fraction of the error to make the effect more subtle.
|
|
accumError[c] /= (rand()%4)+1;
|
|
}
|
|
|
|
srcp += src->format->BytesPerPixel;
|
|
dstp += dst->format->BytesPerPixel;
|
|
}
|
|
}
|
|
}
|
|
|