From 5e4eb9e001fe77a4f7b0852fdfb34c8a649ec8da Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Sun, 11 Apr 2004 01:41:35 +0000 Subject: [PATCH] optimize SM_SDL_ErrorDiffusionDither like SM_SDL_OrderedDither; now it's only about 50% slower this also fixes banding after dithering gradients --- stepmania/src/SDL_dither.cpp | 102 +++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/stepmania/src/SDL_dither.cpp b/stepmania/src/SDL_dither.cpp index 94ed29aae2..c43028930e 100644 --- a/stepmania/src/SDL_dither.cpp +++ b/stepmania/src/SDL_dither.cpp @@ -138,10 +138,8 @@ void SM_SDL_OrderedDither(const SDL_Surface *src, SDL_Surface *dst) } -#define CLAMP(x, l, h) {if (x > h) x = h; else if (x < l) x = l;} - /* Return "random" numbers in [0,2]; this is for SM_SDL_ErrorDiffusionDither (rand() is too slow). */ -static inline int GetFastRand() +/* static inline int GetFastRand() { static int RandomNumbers[] = { @@ -158,13 +156,36 @@ static inline int GetFastRand() if( iNextNumber == ARRAYSIZE(RandomNumbers) ) iNextNumber = 0; return RandomNumbers[ iNextNumber++ ]; -} +} */ +/* This is very similar to SM_SDL_OrderedDither, except instead of using a matrix + * containing rounding values, we truncate and then add the resulting error for + * each pixel to the next pixel on the same line. (Maybe we could do both?) */ void SM_SDL_ErrorDiffusionDither(const SDL_Surface *src, SDL_Surface *dst) { /* 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) { @@ -176,50 +197,63 @@ void SM_SDL_ErrorDiffusionDither(const SDL_Surface *src, SDL_Surface *dst) /* For each pixel in row: */ for( int col = 0; col < src->w; ++col ) { - Uint8 originalColors[4]; - mySDL_GetRGBAV( srcp, src, originalColors ); + Uint8 colors[4]; + mySDL_GetRawRGBAV( srcp, src, colors ); - Uint8 colorsPlusError[4]; - int c; - for( c = 0; c < 4; ++c ) + for( int c = 0; c < 3; ++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] ); + /* Convert the number to the destination range. */ + int out_intensity = colors[c] * conv[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) ); - } + /* Add e to make sure a value of 14.999998 -> 15. */ + ++out_intensity; + + /* Add bias. */ + out_intensity += accumError[c]; - mySDL_SetRGBAV( dstp, dst, colorsPlusError ); + /* out_intensity is now what we actually want this component to be. + * To store it, we have to clamp it (prevent overflow) and shift it + * from fixed-point to [0,255]. The error introduced in that calculation + * becomes the new accumError. */ + int clamped_intensity = clamp( out_intensity, 0, 0xFFFFFF ); + clamped_intensity &= 0xFF0000; - Uint8 ditheredColors[4]; - mySDL_GetRGBAV( dstp, dst, ditheredColors ); + /* Truncate. */ + colors[c] = Uint8(clamped_intensity >> 16); - for(c = 0; c < 4; ++c) - accumError[c] += originalColors[c] - ditheredColors[c]; + accumError[c] = out_intensity - clamped_intensity; - /* 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 ); + CLAMP( accumError[c], -128 * 65536, +128 * 65536 ); // Keep only a fraction of the error to make the effect more subtle. // This used to divide by [1,4]; shift right by [0,2] to get a similar // (but much faster) effect. - accumError[c] >>= (GetFastRand())+1; + /* This resulted in banding in gradients, and doesn't work quite the + * same way with this calculation; without it, gradients look correct. + * Unfortunately, I don't remember any of the problem cases we had + * originally; if we see problems with the dithering in the future, let's + * archive them somewhere for future testing. */ +// accumError[c] >>= (GetFastRand())+1; } + /* 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); + } + + mySDL_SetRawRGBAV( dstp, dst, colors ); + srcp += src->format->BytesPerPixel; dstp += dst->format->BytesPerPixel; }