DirectX:DirectDraw:Tutorials:VB:DX7:Altering the Display with Gamma Controls

From GPWiki
Jump to: navigation, search

Put on your adult-diapers folks because you're about to pee your pants. I have deciphered the undocumented GammaControl object included with DirectX7, and now have the ability to alter the screen's colour output at will. I can fade out, fade in, filter only the red channel, blue channel, green channel or any combination. I can do flashes of light (such as the red flash you see when you're hit in Quake) and all manner of other funky things. Have you wet yourself yet?

There were a few hindrances that had previously stopped VB programmers from using the GammaControl, most notably the fact that Gamma Ramps are arrays of unsigned integers. Visual Basic does not support unsigned integers, so I've written two cheesy little functions to handle the conversion:

Public Function ConvToUnSignedValue(intValue As Integer) As Long

   If intValue >= 0 Then
       ConvToUnSignedValue = intValue
       Exit Function
   End If
   ConvToUnSignedValue = intValue + 65535

End Function


Public Function ConvToSignedValue(lngValue As Long) As Integer

   If lngValue <= 32767 Then
       ConvToSignedValue = CInt(lngValue)
       Exit Function
   End If
   ConvToSignedValue = CInt(lngValue - 65535)

End Function

You see, an unsigned integer can range from 0 to 65355 while a signed integer ranges from -32767 to 32767. It's the same number of values, yes, but the bits are interpreted differently. When using the Gamma Control, we receive unsigned integers as data. Visual Basic interprets them as signed integers, however, and the data is skewed. Any value below 32767 will be handled properly, but any value beyond that (ie. 32768 to 65535) will result in the negative inverse. The functions above correct for this. They could certainly have been more elegantly coded (perhaps using hex) but they'll serve their purpose well enough.

Keeping this knowledge in mind, we can continue. It is advisable to ensure that Gamma Ramp Support is in fact present within the computer's hardware before proceeding:

Dim dx As New DirectX7 Dim dd As DirectDraw7 Dim hwCaps As DDCAPS Dim helCaps As DDCAPS

   Set dd = dx.DirectDrawCreate("")
   dd.GetCaps hwCaps, helCaps
   If (hwCaps.lCaps2 And DDCAPS2_PRIMARYGAMMA) = 0 Then
       MsgBox "Your system does not have Gamma Ramp Support", vbOKOnly, "No Gamma Support"
       End
   End If

This code will create the DirectDraw object and then poll it for capabilities. If the DDSCAPS2_PRIMARYGAMMA capability is not present within the hwCaps.lCaps2 then an error message is display and execution stops.

Now, if the computer passes this test then we can set up a primary surface and use it to produce a Gamma Control. Note, Gamma Controls can ONLY be used on primary surfaces. The following code assumes a primary surface has been set up appropriately:

Dim GammaControler As DirectDrawGammaControl

   Set GammaControler = Primary.GetDirectDrawGammaControl

There are two things you can do with the Gamma Control once you've created it: GetGammaRamp and SetGammaRamp. Use GetGammaRamp to determine the current state of the screen. Use SetGammaRamp to change the current state of the screen. Simple enough... so first, lets make two DDGAMMARAMP objects. One we will use to store the screen's original data, the other we will use to update the screen as we see fit:

Dim GammaRamp As DDGAMMARAMP Dim OriginalRamp As DDGAMMARAMP

   GammaControler.GetGammaRamp DDSGR_DEFAULT, OriginalRamp

We have now stored the screen's current Gamma Ramp data into our variable OriginalRamp. It is important to maintain a copy of the original screen data since applying a nonlinear Gamma Ramp may produce unexpected results on systems that do not support Gamma Calibration (the lCaps2 DDCAPS2_CANCALIBRATEGAMMA flag). The code above uses the DDSGR_DEFAULT flag rather than the DDSGR_CALIBRATE flag since many computers will not support calibration (although you could perform a capabilities check as we did above and use it if available). For our purposes, simple linear ramps will suffice, and the easiest way to produce these is to simply use a percentage of the original ramp values. The following routine will alter the screen's Gamma Ramp data by comparative percentages of the original Gamma Ramp data:

Public Sub SetGamma(bytRed As Byte, bytGreen As Byte, bytBlue As Byte)

Dim i as Integer

   For i = 0 To 255
       GammaRamp.red(i) = ConvToSignedValue(ConvToUnSignedValue(OriginalRamp.red(i)) * bytRed / 100)
       GammaRamp.green(i) = ConvToSignedValue(ConvToUnSignedValue(OriginalRamp.green(i)) * bytGreen / 100)
       GammaRamp.blue(i) = ConvToSignedValue(ConvToUnSignedValue(OriginalRamp.blue(i)) * bytBlue / 100)
   Next
   GammaControler.SetGammaRamp DDSGR_DEFAULT, GammaRamp

End Sub

The DDGAMMARAMP object is made up of three arrays, one for each colour channel. These arrays contain unsigned integers that correspond to the output that will be produced when a given input it received. By altering these values as a function of the percentage of the original, we maintain the linearity required for consistent results. For example, by calling SetGamma 50, 50, 50 the result would be to decrease the intensity of every colour by one-half (a one-half fade). By calling SetGamma 100, 0, 0 we would remove all but the red component of the display, a fade to red.

Now the technical aspect involved in utilizing the Gamma Control may sound somewhat daunting, but I assure you it is very simple. Have a look at this sample source code, and you'll see just how easy it is.