Alimonster
05-11-2002, 10:28 PM
There seems to be a dearth of info about how to use the gamma control properly. I'm suggesting this method - if anyone has additional information, correction, or insights, please drop 'em here.
First of all, we have to know about the relevant interface, IDirectDrawGammaControl (http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/ddraw7/directdraw7/ddref_0myf.asp).
This lets us control the gamma (in effect fading to black/back again easily, or removing specific parts from the gamma ramp to change the screen's colour, maybe giving a yellow tint, for example).
The first step is to work on assumption that we have a class encapsulating your basic DDraw screen (with flip, draw, and so on). I'm assuming this solely because I have such a class, making this post easier via copy-and-paste :).
Our basic class might look like this:
type
TDXScreen = class
private
FSupportsGamma: Boolean;
FOldGamma: TDDGammaRamp;
procedure CheckForGamma;
// other stuff
public
// stuff
property GammaAvailable: Boolean read FSupportsGamma;
procedure SetGamma(RedPercent, GreenPercent, BluePercent: Double);
procedure RestoreGamma;
end;
First of all, notice the TDDGammaRamp structure. A gamma ramp record stores values (256 of 'em) for the gamma ramp. Each part of this record is split into 3 sections for red, green and blue. If you alter any of those, you can change that colour in the gamma ramp. If we reduce the colours for the entire ramp, we can lighten or darken the gamma.
Here's my CheckForGamma procedure, which tries to figure out whether the device supports gamma controls (which isn't a given!):
//
// CheckForGamma
//
// Ensure that the gamma ramp is available for use by checking the IDirectDraw7
// interface's caps
//
procedure TDXScreen.CheckForGamma;
var
Caps: TDDCaps;
Gamma: IDirectDrawGammaControl;
begin
FSupportsGamma := False;
// init our capabilities record to zero
FillChar(Caps, SizeOf(Caps), 0);
Caps.dwSize := SizeOf(CapS);
// is it supported?
if FDD7.GetCaps(@Caps, nil) = DD_OK then
begin
FSupportsGamma := Caps.dwCaps2 and DDCAPS2_PRIMARYGAMMA <> 0;
// if it is supported, grab the old value as a reference for other gammas
if FSupportsGamma then
begin
FPrimary.QueryInterface(IID_IDirectDrawGammaContro l, Gamma);
Gamma.GetGammaRamp(0, FOldGamma);
end;
end;
{$IFDEF LOG_FILE}
if FSupportsGamma then WriteLog('SUPPORTS gamma fades!')
else WriteLog('DOES NOT SUPPORT gamma fades');
{$ENDIF}
end;
This looks complicated but it's not really. First of all, as with all DDraw things, we need to declare a record to store some info. The DDraw interfaces allow us to retrieve info by querying capabilities - we're asking "does it support feature X" by using the GetCaps function. DDraw fills out the passed info with the wanted info (i.e., it does or it doesn't support gamma, in this case).
We can only use gamma on the primary surface. Thus, after the GetCaps call, we check the returned capabilities. If the bit-mask for DDCAPS2_PRIMARYGAMMA is set (you can learn about bit-fiddling over at my site here (http://www.alistairkeys.co.uk/bits.shtml)) then we're in luck!
Next, we store the current gamma value. It's no use fiddling with the gamma if we end up finishing the program in darkness - the user won't be very pleased, that's for sure! We grab the old value for reference later on.
It's possible to get the gamma interface using QueryInterface. I believe (and please correct me if I'm wrong) that you don't have to clear up the interface returned, since it will die when it goes out of scope).
The returned value is an IDirectDrawGammaControl. This, handily enough, gives us access to the current gamma ramp! We then call its GetGammaRamp function.
At this point, assuming everything's worked, we know that the device supports gamma and what its starting value is. How do we use this in practise?
I came up with this cunning plan - any gamma we set will be in reference to the original gamma. We can deal with percent (100% will be the original gamma value, 0% will be total darkness). This makes sense to me - after all, there's no reason override the user's settings here :). We would also use overbright values (> 100%), I suppose, though that's pushing it a little.
Here are the two functions for us now. Note that RestoreGamma is simply a helper to set the gamma back to the default (100%):
//
// Clamp
//
// Ensures the value is between the min and max passed in
//
procedure Clamp(var Value: Double; const Min, Max: Double);
begin
if Value < Min then Value := Min;
if Value > Max then Value := Max;
end;
//
// SetGamma
//
// Sets the gamma ramp to the specified amounts, which are percents (0 - 100)
// for each colour. This effects the overall brightness of the screen (note:
// 100 percent is fully on, 0 percent is off)
//
procedure TDXScreen.SetGamma(RedPercent, GreenPercent, BluePercent: Double);
var
NewGamma: TDDGammaRamp;
i: Integer;
Gamma: IDirectDrawGammaControl;
begin
// you can remove this if you want...
Clamp(RedPercent, 0, 100);
Clamp(GreenPercent, 0, 100);
Clamp(BluePercent, 0, 100);
if FSupportsGamma then
begin
for i := 0 to 255 do
begin
// todo speed this up?
NewGamma.red[i] := Trunc((FOldGamma.Red[i] * RedPercent) / 100);
NewGamma.green[i] := Trunc((FOldGamma.Green[i] * GreenPercent) / 100);
NewGamma.blue[i] := Trunc((FOldGamma.Blue[i] * BluePercent) / 100);
end;
FPrimary.QueryInterface(IID_IDirectDrawGammaContro l, Gamma);
Gamma.SetGammaRamp(0, NewGamma);
end;
end;
//
// RestoreGamma
//
// Resets the gamma ramp to the default
//
procedure TDXScreen.RestoreGamma;
begin
SetGamma(100, 100, 100);
end;
The above is fast enough. It won't be your bottleneck, that's for sure (well, that's the case for me since I like l33t pixel manipulation code, which runs over millions of pixels ;)).
Speed tip 1: Trunc is *very very slow*. If possible, use round instead!
Speed tip 2: You could remove those divisions outside of the loop (calculate RedPercent / 100 outside the loop, for example).
Corrections? Clarifications? Miscellaneous threats? Post them here, please.
If anyone wants to investigate this for DelphiX, please do. I'm not sure if it supports gamma out-of-the-zip. If it doesn't, this should come in handy. If nobody takes up the challenge then I may just do so later on.
First of all, we have to know about the relevant interface, IDirectDrawGammaControl (http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/ddraw7/directdraw7/ddref_0myf.asp).
This lets us control the gamma (in effect fading to black/back again easily, or removing specific parts from the gamma ramp to change the screen's colour, maybe giving a yellow tint, for example).
The first step is to work on assumption that we have a class encapsulating your basic DDraw screen (with flip, draw, and so on). I'm assuming this solely because I have such a class, making this post easier via copy-and-paste :).
Our basic class might look like this:
type
TDXScreen = class
private
FSupportsGamma: Boolean;
FOldGamma: TDDGammaRamp;
procedure CheckForGamma;
// other stuff
public
// stuff
property GammaAvailable: Boolean read FSupportsGamma;
procedure SetGamma(RedPercent, GreenPercent, BluePercent: Double);
procedure RestoreGamma;
end;
First of all, notice the TDDGammaRamp structure. A gamma ramp record stores values (256 of 'em) for the gamma ramp. Each part of this record is split into 3 sections for red, green and blue. If you alter any of those, you can change that colour in the gamma ramp. If we reduce the colours for the entire ramp, we can lighten or darken the gamma.
Here's my CheckForGamma procedure, which tries to figure out whether the device supports gamma controls (which isn't a given!):
//
// CheckForGamma
//
// Ensure that the gamma ramp is available for use by checking the IDirectDraw7
// interface's caps
//
procedure TDXScreen.CheckForGamma;
var
Caps: TDDCaps;
Gamma: IDirectDrawGammaControl;
begin
FSupportsGamma := False;
// init our capabilities record to zero
FillChar(Caps, SizeOf(Caps), 0);
Caps.dwSize := SizeOf(CapS);
// is it supported?
if FDD7.GetCaps(@Caps, nil) = DD_OK then
begin
FSupportsGamma := Caps.dwCaps2 and DDCAPS2_PRIMARYGAMMA <> 0;
// if it is supported, grab the old value as a reference for other gammas
if FSupportsGamma then
begin
FPrimary.QueryInterface(IID_IDirectDrawGammaContro l, Gamma);
Gamma.GetGammaRamp(0, FOldGamma);
end;
end;
{$IFDEF LOG_FILE}
if FSupportsGamma then WriteLog('SUPPORTS gamma fades!')
else WriteLog('DOES NOT SUPPORT gamma fades');
{$ENDIF}
end;
This looks complicated but it's not really. First of all, as with all DDraw things, we need to declare a record to store some info. The DDraw interfaces allow us to retrieve info by querying capabilities - we're asking "does it support feature X" by using the GetCaps function. DDraw fills out the passed info with the wanted info (i.e., it does or it doesn't support gamma, in this case).
We can only use gamma on the primary surface. Thus, after the GetCaps call, we check the returned capabilities. If the bit-mask for DDCAPS2_PRIMARYGAMMA is set (you can learn about bit-fiddling over at my site here (http://www.alistairkeys.co.uk/bits.shtml)) then we're in luck!
Next, we store the current gamma value. It's no use fiddling with the gamma if we end up finishing the program in darkness - the user won't be very pleased, that's for sure! We grab the old value for reference later on.
It's possible to get the gamma interface using QueryInterface. I believe (and please correct me if I'm wrong) that you don't have to clear up the interface returned, since it will die when it goes out of scope).
The returned value is an IDirectDrawGammaControl. This, handily enough, gives us access to the current gamma ramp! We then call its GetGammaRamp function.
At this point, assuming everything's worked, we know that the device supports gamma and what its starting value is. How do we use this in practise?
I came up with this cunning plan - any gamma we set will be in reference to the original gamma. We can deal with percent (100% will be the original gamma value, 0% will be total darkness). This makes sense to me - after all, there's no reason override the user's settings here :). We would also use overbright values (> 100%), I suppose, though that's pushing it a little.
Here are the two functions for us now. Note that RestoreGamma is simply a helper to set the gamma back to the default (100%):
//
// Clamp
//
// Ensures the value is between the min and max passed in
//
procedure Clamp(var Value: Double; const Min, Max: Double);
begin
if Value < Min then Value := Min;
if Value > Max then Value := Max;
end;
//
// SetGamma
//
// Sets the gamma ramp to the specified amounts, which are percents (0 - 100)
// for each colour. This effects the overall brightness of the screen (note:
// 100 percent is fully on, 0 percent is off)
//
procedure TDXScreen.SetGamma(RedPercent, GreenPercent, BluePercent: Double);
var
NewGamma: TDDGammaRamp;
i: Integer;
Gamma: IDirectDrawGammaControl;
begin
// you can remove this if you want...
Clamp(RedPercent, 0, 100);
Clamp(GreenPercent, 0, 100);
Clamp(BluePercent, 0, 100);
if FSupportsGamma then
begin
for i := 0 to 255 do
begin
// todo speed this up?
NewGamma.red[i] := Trunc((FOldGamma.Red[i] * RedPercent) / 100);
NewGamma.green[i] := Trunc((FOldGamma.Green[i] * GreenPercent) / 100);
NewGamma.blue[i] := Trunc((FOldGamma.Blue[i] * BluePercent) / 100);
end;
FPrimary.QueryInterface(IID_IDirectDrawGammaContro l, Gamma);
Gamma.SetGammaRamp(0, NewGamma);
end;
end;
//
// RestoreGamma
//
// Resets the gamma ramp to the default
//
procedure TDXScreen.RestoreGamma;
begin
SetGamma(100, 100, 100);
end;
The above is fast enough. It won't be your bottleneck, that's for sure (well, that's the case for me since I like l33t pixel manipulation code, which runs over millions of pixels ;)).
Speed tip 1: Trunc is *very very slow*. If possible, use round instead!
Speed tip 2: You could remove those divisions outside of the loop (calculate RedPercent / 100 outside the loop, for example).
Corrections? Clarifications? Miscellaneous threats? Post them here, please.
If anyone wants to investigate this for DelphiX, please do. I'm not sure if it supports gamma out-of-the-zip. If it doesn't, this should come in handy. If nobody takes up the challenge then I may just do so later on.