View Full Version : Smooth Color Transitions (gradients)
BantamCityGames
12-07-2004, 06:26 PM
Hi, I'm working on my new game and I'm using DX6.1 16bit 800x600 full screen. The problem is, certain graphics that have a smooth gradient in them tend to have a striping effect (similar to what you would see if you saved a gif with alot of compression). Now when I open the image (BMP) up in any image editor it looks beatifully smooth, but when it displays in the actual game it look blotchy/stripey. I notice this happens more with blues than any other color. Is this just the cost of converting 24bit images to 16bit? or is there something I could or should be doing? Thanks. If example screen shots are needed, I can upload some.
Anthony Flack
12-07-2004, 06:47 PM
You should be reducing the images down to 16bit in a good quality image editor, and getting it to dither the colours when it does so.
Sergey Komar
12-07-2004, 06:48 PM
16 bit mode is like that. I know people use some kind of dithering effect (after you load the images into the memory) and then it looks quite alright. Never tried though. Make two modes - 32 & 16 and leave those with old videocards with crappy images. Time to upgrade :)
svero
12-07-2004, 07:41 PM
Yeah its the loss from 24 to 16. You can either do what Anthony suggested and pre dither the image in a graphics program, or build a little dithering algorithm into your file loader. That's what we did.
- S
BantamCityGames
12-07-2004, 08:03 PM
@Anthony - I'm using Photoshop 6, is there a way to pre-dither the images in this? I've dithered to 8-bit and I do this by going to Image->Mode->Indexed Color, but I've never done this to 16-bit... the max amount of colors allowed in the Indexed Color task is 256.
@Svero - Interesting... I'm gonna search the web for this now, but could you give me any pointers?
svero
12-07-2004, 09:45 PM
I looked around on the web for a nice explanation of error diffusion or something you could read to help you, but couldn't find a good page. Many computer graphics books will have the algorithm spelled out though.
Anthony Flack
12-07-2004, 09:47 PM
Hmm, you know, I'm not sure if Photoshop does actually allow you to go down to 16bpp. It's one of the only things photoshop doesn't seem to be interested in dealing with.
I think Paint Shop Pro might actually be the tool for the job here (for once). Or maybe there is a free alternative. Or you could go the Svero route.
svero
12-07-2004, 09:57 PM
Paint shop latest is a little weird too, but it does seem to allow dithering a 24 bit image to 65k colors. So that would probably work.
James C. Smith
12-07-2004, 11:12 PM
You defiantly need dithering if you are using a 16 bit screen. Using something like Photoshop to pre-dither is an easy way to go. Ricochet uses 16 bit full screen and has a nice gradient in the underwater background (level 1). Since I store my art in 24 bit JPEGs, the pre-dithering doesn’t work very well. At run time I load the 24 bit JPEG and down sample it to 16 bit with error diffusion. I use the “Floyd Steinberg” flavor of error diffused dithering. Here is my code but you should be able to google some better documented code.
/*
* The dither function: Floyd Steinberg Dithering.
* The value of an channel will be changed to match one of the given
* (No. of Shades) values. The error will be distributed according to the
* following table:
*
* Direction: ----->
*
* +------+------+------+
* | |curr. | |
* | |Pixel | 7/16 |
* | | | |
* +------+------+------+
* | | | |
* | 3/16 | 5/16 | 1/16 |
* | | | |
* +------+------+------+
*
* A slightly better result is reached by changing the direction every
* second row.
*/
class CErrorDifuseDither
{
public:
CErrorDifuseDither(uint Width, float DitherFactor, CPalette* pPalette, CPalette* pSearchPalette);
~CErrorDifuseDither();
uint Dither(Byte r, Byte g, Byte b) {return Dither(r / 255.f, g / 255.f, b / 255.f);} // returns an 8 bit dithered color
uint Dither(float r, float g, float b); // returns an 8 bit dithered color
//private:
uint GetHistoryindex(uint Index);
void AdjustForPreviousError(float& r, float& g, float& b);
bool CalculateNewError(float& r, float& g, float& b);
// **** DATA ****
uint m_Width;
uint m_BufferSize;
uint m_RingBufferPos;
uint m_x;
float* pRedError;
float* pGreenError;
float* pBlueError;
float LastR;
float LastG;
float LastB;
float LastRWithoutError;
float LastGWithoutError;
float LastBWithoutError;
float m_DitherFactor;
float m_MaxErrorSqrd;
bool ThisIsARetry;
CPalette* m_pPalette; // This must be a 256 color palette. It is used convert an 8 bit color index to a 24 bit color.
CPalette* m_pSearchPalette;// This can be an ICLUT or a 256 color palette. It is used to convert a 24 bit color to an 8 bit color. In other words, it is used to search for a closest color match
};
//************************************************** ************************************************** *****************
CErrorDifuseDither::CErrorDifuseDither(uint Width, float DitherFactor, CPalette* pPalette, CPalette* pSearchPalette)
{
m_pPalette = pPalette;
m_pSearchPalette = pSearchPalette;
m_DitherFactor = DitherFactor;
m_MaxErrorSqrd = m_DitherFactor * m_DitherFactor;
m_Width = Width;
m_BufferSize = m_Width + 1;
pRedError = new float[m_BufferSize];
pGreenError = new float[m_BufferSize];
pBlueError = new float[m_BufferSize];
for(uint i = 0; i < m_BufferSize; i++)
{
pRedError[i] = 0.f;
pGreenError[i] = 0.f;
pBlueError[i] = 0.f;
}
m_RingBufferPos = 0;
m_x = 0;
}
//************************************************** ************************************************** *****************
CErrorDifuseDither::~CErrorDifuseDither()
{
ArrayDeleteAndSetNull(pRedError);
ArrayDeleteAndSetNull(pGreenError);
ArrayDeleteAndSetNull(pBlueError);
}
//************************************************** ************************************************** *****************
uint CErrorDifuseDither::Dither(float r, float g, float b) // returns an 8 bit dithered color
{
AdjustForPreviousError(r, g, b);
RetryLookup:
uint ColorIndex = m_pSearchPalette->GetColorIndex(FloatToLong(r * 255), FloatToLong(g * 255), FloatToLong(b * 255), 1);
uint ResultR;
uint ResultG;
uint ResultB;
m_pPalette->GetRGB(ColorIndex, &ResultR, &ResultG, &ResultB);
r = ResultR / 255.f;
g = ResultG / 255.f;
b = ResultB / 255.f;
if(CalculateNewError(r, g, b))
{
goto RetryLookup;
}
return ColorIndex;
}
//************************************************** ************************************************** *****************
uint CErrorDifuseDither::GetHistoryindex(uint Index)
{
if(Index >= m_BufferSize)
{
Index -= m_BufferSize;
assert(Index < m_BufferSize);
}
return Index;
}
//************************************************** ************************************************** *****************
void CErrorDifuseDither::AdjustForPreviousError(float& r, float& g, float& b)
{
LastRWithoutError = r;
LastGWithoutError = g;
LastBWithoutError = b;
float RedError = pRedError[m_RingBufferPos];
float GreenError = pGreenError[m_RingBufferPos];
float BlueError = pBlueError[m_RingBufferPos];
r -= RedError;
g -= GreenError;
b -= BlueError;
pRedError[m_RingBufferPos] = 0;
pGreenError[m_RingBufferPos] = 0;
pBlueError[m_RingBufferPos] = 0;
m_RingBufferPos++;
if(m_RingBufferPos >= m_BufferSize)
{
m_RingBufferPos = 0;
}
m_x++;
if(m_x >= m_Width)
{
m_x = 0;
}
r = PinValue(r, 0.f, 1.f);
g = PinValue(g, 0.f, 1.f);
b = PinValue(b, 0.f, 1.f);
LastR = r;
LastG = g;
LastB = b;
ThisIsARetry = false;
}
//************************************************** ************************************************** *****************
bool CErrorDifuseDither::CalculateNewError(float& r, float& g, float& b)
{
if(!ThisIsARetry)
{
float RedDeltaOriginal = LastRWithoutError - r;
float GreenDeltaOriginal = LastGWithoutError - g;
float BlueDeltaOriginal = LastBWithoutError - b;
float TotalErrorSqrd = RedDeltaOriginal * RedDeltaOriginal +
GreenDeltaOriginal * GreenDeltaOriginal +
BlueDeltaOriginal * BlueDeltaOriginal;
if(TotalErrorSqrd > m_MaxErrorSqrd)
{
ThisIsARetry = true;
r = LastRWithoutError;
g = LastGWithoutError;
b = LastBWithoutError;
// if(!(CKeyboard::Instance().ModifierKeyIsDown(SC_LE FTSHIFT) || CKeyboard::Instance().ModifierKeyIsDown(SC_RIGHTSH IFT)))
{
LastR = r;
LastG = g;
LastB = b;
}
return true;
}
}
// calulate the error in this pixel
float RedError = r - LastR;
float GreenError = g - LastG;
float BlueError = b - LastB;
// m_RingBufferPos Points to next pixel (pixel aft the one we just plated and calculated error more)
// spread it out over future pixels
// next pixel gets 7/16ths
pRedError[m_RingBufferPos] += RedError / 16 * 7;
pGreenError[m_RingBufferPos] += GreenError / 16 * 7;
pBlueError[m_RingBufferPos] += BlueError / 16 * 7;
uint Index;
if(m_x > 0)
{
// pixel below and to left get 3/16
Index = GetHistoryindex(m_RingBufferPos + m_Width - 2);
pRedError[Index] += RedError / 16 * 3;
pGreenError[Index] += GreenError / 16 * 3;
pBlueError[Index] += BlueError / 16 * 3;
}
// pixel below get 5/16
Index = GetHistoryindex(m_RingBufferPos + m_Width - 1);
pRedError[Index] += RedError / 16 * 5;
pGreenError[Index] += GreenError / 16 * 5;
pBlueError[Index] += BlueError / 16 * 5;
if(m_x + 1 < m_Width)
{
// pixel below and to right get 3/16
Index = GetHistoryindex(m_RingBufferPos + m_Width - 0);
pRedError[Index] += RedError / 16 * 1;
pGreenError[Index] += GreenError / 16 * 1;
pBlueError[Index] += BlueError / 16 * 1;
}
return false;
}
Here is a simple of some code that calls my dithering class. This code is adjusting the 24 bit pixels so they will look good when they are truncated to 16 bits later
static void DitherStripTo16(Pixel32* pSourcePixel, uint Width, CErrorDifuseDither* pDither)
{
assert(Width);
while(Width--)
{
float b = pSourcePixel->Blue / 255.f;
float g = pSourcePixel->Green / 255.f;
float r = pSourcePixel->Red / 255.f;
pDither->AdjustForPreviousError(r,g,b);
do
{
r = ((int) (r*31)) / 31.f;
g = ((int) (g*63)) / 63.f;
b = ((int) (b*31)) / 31.f;
} while(pDither->CalculateNewError(r,g,b));
uint lr = FloatToLong(r * 255);
uint lg = FloatToLong(g * 255);
uint lb = FloatToLong(b * 255);
assert(lr < 256 && lg < 256 && lb < 256);
pSourcePixel->Red = (Byte)lr;
pSourcePixel->Green = (Byte)lg;
pSourcePixel->Blue = (Byte)lb;
pSourcePixel++;
}
}
Dan MacDonald
12-07-2004, 11:56 PM
See james likes open source! ;)
svero
12-08-2004, 12:03 AM
I would have posted my source but I didn't feel like getting into some long drawn out argument about my bracketing style.
Anthony Flack
12-08-2004, 12:21 AM
Oho, are you a scruffbag? James can post his code because it's all neat and tidy.
svero
12-08-2004, 01:32 AM
Actually the engine code is the opposite of scruffy. Its really really pristine. The game stuff can get a little hairy at times when I'm lazy. I just happen to use the one provably correct way of writing code. A way which nobody else seems to use. And I'll leave it at that since as I said.. this is more or less the discussion I didn't want to get into ;-)
ERoberts
12-08-2004, 02:11 AM
I just happen to use the one provably correct way of writing code.
Well, I know of two provably correct ways:
if (whatever)
{
code here;
}
else
{
more code here;
}
or
if (whatever) {
code here;
} else {
more code here;
}
svero
12-08-2004, 02:23 AM
Nice try but im not getting drawn into it.
ERoberts
12-08-2004, 02:26 AM
But I'm curious... I prefer the first one myself, but some people have argued that the second style is MORE correct, but I don't see why...
Raptisoft
12-08-2004, 02:32 AM
I'm curious too, Svero.
I used to code this way:
void FunctionCall() {
Do processing;
Do processing;
Do processing;
}
...but working at Popcap kinda forced me to go to standard (which I love now, because Visual Studio does so much edit helping).
What other way is there?
I prefer...
if (statement)
{
//blah!
}
...but only because I find it easier to read afterwards. Anyway, back to subject - what percentage are we looking at in demographical terms for 16 bit graphics hardware these days? We use a quick and dirty approach just for the sake of support, might implement something similar to what James uses now though as that looks quite interesting :)
svero
12-08-2004, 04:56 AM
Alright. To my mind...
case1:
if(something something) {
some_code
some_code
some_code
}
From my point of view this is the worse one. I find it's poor aesthetically. It also makes it difficult to match up brackets and see blocks of code. The brackets aren't associated with the code or the condition. It seems almost random. It does have one advantage though which is that it's more compact for written text, so I might use that style in a book to keep the examples vertically dense and not spread over too many pages which is arguably harder to follow.
The style...
case2:
if(something something )
{
some_code
some_code
some_code
}
Is ok, and I've used it in the past. However I would argue....
case3:
if(something something)
{
some_code
some_code
some_code
}
Is the best style because internally in the logic of the code the brackets are associated not with the condition or the loop that precedes them but with the block of code. Brackets can be used to block out scope in code without a condition or loop. And furthermore a condition and a loop can be used without brackets. For those reasons I feel the brackets should stay with the code. I also feel it's the nicest one aesthetically.
I will say though, that if you're working on a body of code that's already quite large and it uses a particular style it's probably best to just match the style that's there already. So I would drop to case 1 or 2 if that was the situation i found myself in, but when given a choice I'd use 3.
Of course people generally prefer what they're use to because the longer you've looked at one style the more clearly you can see that. You just get use to it. You might wonder why i mention aesthetics. I do look at code as kind of like a little piece of art to some degree. I don't see it as a pure set of logical instructions.
Anthony Flack
12-08-2004, 05:16 AM
If my code was a piece of art, it'd be a Jackson Pollock.
EpicBoy
12-08-2004, 06:21 AM
Coding style threads are the best. Really.
BantamCityGames
12-08-2004, 08:51 AM
If my code was a piece of art, it'd be a Jackson Pollock.HAHA
Whoa, this thread got a little off topic, but what the hell I'll play along:
I prefer:
if(condition)
{
statement;
}Now back to the point:
@James, thanks. Now that you put a Name to the technique I am able to find all kinds of stuff on the web about it.
I'm gonna try Paint Shop Pro now to see if the pre-dithering technique would work, but I can see a definite advantage to the algorithm technique and may revert to that if I get some time.
BantamCityGames
12-08-2004, 12:33 PM
Just tried the trial version of Paint Shop Pro (60 days) and it looks like it does the trick! I'm surprised photoshop doesn't have this capability. I may still implement the software dithering too. Thanks everyone! :D
Anthony Flack
12-08-2004, 06:32 PM
Photoshop is too posh I guess. It supports 64 bit colour, instead...
vBulletin v3.6.0, Copyright ©2000-2008, Jelsoft Enterprises Ltd.