Files
itgmania212121/stepmania/src/SDL_dither.cpp
T
Glenn Maynard ae4cb0197c license update
2004-05-18 07:11:11 +00:00

299 lines
11 KiB
C++

#include "global.h"
#include "RageUtil.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];
/* conv is the ratio from the input to the output. */
static uint8_t 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_t((out_intensity + 1) >> 16);
}
void SM_SDL_OrderedDither(const SDL_Surface *src, SDL_Surface *dst)
{
static bool DitherMatCalc_initted = false;
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_t 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_t alpha_max = uint8_t((1 << dst_cbits[3]) - 1);
/* For each row: */
for( int row = 0; row < src->h; ++row )
{
const uint8_t *srcp = (const uint8_t *) src->pixels + row * src->pitch;
uint8_t *dstp = (uint8_t *) dst->pixels + row * dst->pitch;
/* For each pixel: */
for( int col = 0; col < src->w; ++col )
{
uint8_t 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_t((out_intensity + 1) >> 16);
/* I don't remember why this used to truncate. Doing that causes
* dithering to an image with 1-bit alpha to be transparent on all
* source alpha values except for full-opaque, which is wrong.
* Add .5, so we round. */
colors[3] = uint8_t((out_intensity + 32767) >> 16);
}
/* Raw value -> int -> pixel */
mySDL_SetRawRGBAV(dstp, dst, colors);
srcp += src->format->BytesPerPixel;
dstp += dst->format->BytesPerPixel;
}
}
}
/* Return "random" numbers in [0,2]; this is for SM_SDL_ErrorDiffusionDither (rand() is too slow). */
/* static inline int GetFastRand()
{
static int RandomNumbers[] =
{
2, 1, 0, 2, 0, 2, 2, 1, 0, 1, 1, 2, 2, 0, 1, 1, 2, 2, 1, 0, 1, 0, 1, 2, 1, 0, 2, 1, 0, 1, 1, 0,
2, 1, 1, 2, 1, 0, 0, 1, 1, 2, 0, 2, 0, 1, 0, 1, 2, 0, 0, 1, 1, 2, 0, 2, 1, 2, 2, 2, 2, 1, 1, 1,
1, 0, 2, 1, 2, 1, 1, 1, 2, 1, 0, 0, 1, 0, 0, 2, 0, 1, 0, 1, 2, 2, 0, 0, 0, 2, 2, 1, 2, 2, 2, 2,
1, 2, 1, 0, 2, 0, 1, 0, 2, 1, 2, 1, 1, 0, 1, 1, 2, 0, 0, 2, 2, 2, 1, 0, 0, 1, 2, 1, 2, 0, 0, 2,
2, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 2, 2, 2, 0, 0, 0, 1, 2, 0, 1, 1, 0, 0, 0, 2, 1, 2, 0, 0, 1,
1, 2, 0, 1, 1, 2, 2, 1, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1, 2, 0, 0, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2,
0, 1, 2, 0, 2, 2, 0, 2, 1, 0, 0, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 0, 1, 0, 1, 2, 1, 2, 1, 0, 0,
1, 0, 0, 0, 2, 2, 2, 2, 1, 2, 1, 0, 0, 2, 1, 0, 1, 2, 0, 2, 2, 0, 0, 0, 2, 2, 0, 0, 2, 0, 1, 1
};
static int iNextNumber = 0;
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_t 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_t alpha_max = uint8_t((1 << dst_cbits[3]) - 1);
/* For each row: */
for(int row = 0; row < src->h; ++row)
{
int32_t accumError[4] = { 0, 0, 0, 0 }; // accum error values are reset every row
const uint8_t *srcp = (const uint8_t *)src->pixels + row * src->pitch;
uint8_t *dstp = (uint8_t *)dst->pixels + row * dst->pitch;
/* For each pixel in row: */
for( int col = 0; col < src->w; ++col )
{
uint8_t colors[4];
mySDL_GetRawRGBAV( srcp, src, colors );
for( int c = 0; c < 3; ++c )
{
/* Convert the number to the destination range. */
int out_intensity = colors[c] * conv[c];
/* Add e to make sure a value of 14.999998 -> 15. */
++out_intensity;
/* Add bias. */
out_intensity += accumError[c];
/* 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;
/* Truncate. */
colors[c] = uint8_t(clamped_intensity >> 16);
accumError[c] = out_intensity - clamped_intensity;
// Reduce funky streaks in low-bit channels by clamping error.
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.
/* 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_t((out_intensity + 1) >> 16);
/* I don't remember why this used to truncate. Doing that causes
* dithering to an image with 1-bit alpha to be transparent on all
* source alpha values except for full-opaque, which is wrong.
* Add .5, so we round. */
colors[3] = uint8_t((out_intensity + 32767) >> 16);
}
mySDL_SetRawRGBAV( dstp, dst, colors );
srcp += src->format->BytesPerPixel;
dstp += dst->format->BytesPerPixel;
}
}
}
/*
* (c) 2002-2004 Glenn Maynard, Chris Danford
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/