Windows XP adjusting screen contrast through C# code programmatically


Joined
Jul 21, 2011
Messages
2
Reaction score
0
my problem is the following :

I am making a program, which can manipulate brightness, gamma and contrast through C# code. For brightness and gamma it is ok I have achieved it through code I found in the net, but I can't for contrast. The only thing I have found by now is a CalculateRamp method, which has as input parameters (double level, double brightness, double gamma, double contrast), I know what input to give for brightness gamma and contrast ( the values from the sliders in the interface ) , but I don't know what level is for, the other problem with this method is that when I pass the calculated ramp with random "level" parameter as a parameter to the SetDeviceGammaRamp(IntPtr hDC,ref RAMP rmp) it actually changes the screen contrast, but when I move the brightness slider the changes made from the contrast slider are lost, it may be because of using the same method or I am not sure for what.

I will be very thankful to any help or ideas, no matter if they are changes of my current solution which is not full , or brand new solutions / which I would preffer , because I feel in some way unsure with this / . Thanks in advance to everybody.

Here is the code of the CalculateRamp method, as the function SetDeviceGammaRamp(...) is called by me to manipulate the contrast with the current calculated ramp, I am not sure if I have to use it in this way or not :

public static void CalculateRamp(double level, double gamma, double brightness, double contrast) {

ramp.Red = new ushort[256];
ramp.Green = new ushort[256];
ramp.Blue = new ushort[256];

gamma /= 10;
brightness = 1 + (((brightness - 50) / 100) * 65535);
contrast = 1 + ((contrast - 50) / 100);
level = 1 + ((level - 50) / 100);

for (int i = 0; i < 256; i++)
{
double value = i * 256;
value = (Math.Pow(value / 65535, 1 / gamma) * 65535) + 0.5;
value = ((((value / 65535) - 0.5) * contrast) + 0.5) * 65535;
value = value += brightness;
value *= level;
ramp.Red = ramp.Green = ramp.Blue = (ushort)Math.Min((double)65535, Math.Max((double)0, value));
}
SetDeviceGammaRamp(GetDC(IntPtr.Zero), ref ramp);
}
 
Ad

Advertisements

Joined
Sep 3, 2016
Messages
3
Reaction score
0
How would you calculate the input values (arguments) for this method directly from the existing Gamma Ramp? In other words, what would be the reverse method?

If I am going to provide these utilities to the user, I need to present them with their current settings prior to their making the adjustments.

I have tried for three days to come up with a solution, and must admit that this is beyond my capabilities.
 
Joined
Sep 3, 2016
Messages
3
Reaction score
0
How would you calculate the input values (arguments) for this method directly from the existing Gamma Ramp? In other words, what would be the reverse method?

If I am going to provide these utilities to the user, I need to present them with their current settings prior to their making the adjustments.

I have tried for three days to come up with a solution, and must admit that this is beyond my capabilities.

--------------------------------------------------------------------------------------------------
After unsuccessfully trying to solve for a mathematical solution for a week I had to try some other way to derive the Gamma Ramp Settings directly from the GetDeviceGammaRamp call.

One early attempt involved a simple iteration of all possible settings. This gave correct results, but it took more than 20 minutes to complete the operation. My application needs to be able to do this in under a second.

I finally found a better, more efficient way to perform the iterations, and it meets my requirements. It may be an imperfect solution; but a win is a win.

I shaved a little more time off of this operation by testing for a positive or a negative curve in the Gamma setting (or no curve at all = neutral setting 10). This saves a few iterations going in. To do this I created two additional functions: PtToPtRngBrg and GetPtFromPt.

If you like, you can skip these; and simply iterate through all possible Gamma settings (2 to 50). This would greatly simplify the code.

Here is my C++ solution...

//---------------------------------------------------------------------------
#define DEG_RAD 0.017453292519943295769236907684886
//---------------------------------------------------------------------------
struct RngBrg{ //Struct for return values in the
double rng; //call to PtToPtRngBrg
double brg;
};
//---------------------------------------------------------------------------
void __fastcall TForm1::GetGammaRampSettings(void){
// level should be between 2 and 100
// gamma should be between 2 and 50
// brigntess should be between 0 and 100
// contrast should be between 0 and 100

double i,j,k,m,m4;
double s1,s2,s3,s4;
double gamma,bright,cntrst,level;
double value;
int d;
int x1,x2,x3,x4;
bool isFound=false;
RngBrg rb; //Range and Bearing struct for distance and vector

WORD GammaArray[3][256]; //Gamma Ramp Array buffer
WORD CompArray[256]; //Array buffer for comparison
double DArray[256]; //buffer used to hold gamma values

HDC GammaDC = GetDC( NULL ); //fetch the gamma ramp array
GetDeviceGammaRamp( GammaDC, GammaArray );
ReleaseDC( NULL, GammaDC );

//Calculate the Angle:

//Find two endpoints for the GammaArray
for( x1 = 0; x1 < 256; x1++ ){
if( GammaArray[0][x1] > 0){break;}
}

for( x2 = 0; x2 < 256; x2++ ){
if( GammaArray[0][x2] == 65535){isFound=true; break;}
}
if( !isFound ){ x2--; }

//normalize the gama ramp numbers
//so that a distance and vector can be calculated
s1 = ( (double) GammaArray[0][x1] + 1 ) / 256.0;
s2 = ( (double) GammaArray[0][x2] + 1 ) / 256.0;

//find the mid point array element from the line described above...
x3 = x1 + ( ( x2 - x1 ) / 2 );
s3 = ( (double) GammaArray[0][x3] + 1 ) / 256.0;

Memo1->Lines->Add( "x1 = " + IntToStr(x1) + " s1 = " + AnsiString(s1));
Memo1->Lines->Add( "x2 = " + IntToStr(x2) + " s2 = " + AnsiString(s2));
Memo1->Lines->Add( "x3 = " + IntToStr(x3) + " s3 = " + AnsiString(s3));

//caculate the strait line between
//the two endpoints discovered above
PtToPtRngBrg( &rb, x1, s1, x2, s2 );

//neutral settings are as follows...
//Bright = 50
//Contrast = 50
//Level = 50
//Gamma = 10
//rng = 359.210244842766 and brg = 45 with all neutral settings
Memo1->Lines->Add( "rng = " + AnsiString( rb.rng ) +
" brg = " + AnsiString( rb.brg ));

//what would be the value of the midpoint
//if it were a strait line (neutral gamma setting)?

GetPtFromPt(x1, s1, rb.rng / 2.0, rb.brg, &m4, &s4);

Memo1->Lines->Add("m4 = " + IntToStr( Round(m4) ) + " s4=" + AnsiString(s4));
Memo1->Lines->Add("s3 - s4 = " + AnsiString( s3-s4 ));

x4 = Round(m4); //Rounding function call

if(( s4 - s3 ) > 1 ){//we have a negative gamma curve (G<10)
Memo1->Lines->Add("Negative Gamma Curve");
for( i = 9; i > 1; i-- ){
gamma=i/10;
for( d = 0; d < 256; d++ ){
DArray[d] = ( pow( (double)(d * 256) / 65535, 1 / gamma) * 65535) + 0.5;
}
for( j = 2; j < 101 ; j++ ){//level //980,000 possible iterations
level = 1 + ((j - 50) / 100);
for( k = 0; k < 101; k++ ){//bright
bright = 1 + (((k - 50) / 100) * 65535);
for( m = 0; m < 101; m++ ){//contrast
cntrst = 1 + ((m - 50) / 100);
//Test only the mid-line value at first
//To see if a complete comparison is warranted
value = (((( DArray[x4] / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
value = value += bright;
value *= level;
if( value > 65535){ value = 65535; }
if( value < 0 ){ value = 0; }
if( (WORD) value == GammaArray[0][x4] ){
for( d = 0; d < 256; d++ ){
value = ((( (DArray[d] / 65535) - 0.5) * cntrst) + 0.5) * 65535;
value = value += bright;
value *= level;
if( value > 65535 ){ value = 65535;}
if( value < 0 ){ value = 0;}
CompArray[d] = (WORD) value;
}
if(memcmp( &CompArray[0], &GammaArray[0][0], 2*256) == 0){
goto ENDIT;
}
}
}
}
}
}
}else if((s3-s4)>1){//we have a positive gamma curve(G>10)
Memo1->Lines->Add("Positive Gamma Curve");
for( i = 11; i < 51; i++ ){
gamma=i/10;
for( d = 0; d < 256; d++ ){
DArray[d] = ( pow( (double)(d * 256) / 65535, 1 / gamma) * 65535) + 0.5;
}
for( j = 2; j < 101 ; j++ ){//level //980,000 possible iterations
level = 1 + ((j - 50) / 100);
for( k = 0; k < 101; k++ ){//bright
bright = 1 + (((k - 50) / 100) * 65535);
for( m = 0; m < 101; m++ ){//contrast
cntrst = 1 + ((m - 50) / 100);
//Test only the mid-line value at first
//To see if a complete comparison is warranted
value = (((( DArray[x4] / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
value = value += bright;
value *= level;
if( value > 65535){ value = 65535; }
if( value < 0 ){ value = 0; }
if( (WORD) value == GammaArray[0][x4] ){
for( d = 0; d < 256; d++ ){
value = ((( (DArray[d] / 65535) - 0.5) * cntrst) + 0.5) * 65535;
value = value += bright;
value *= level;
if( value > 65535 ){ value = 65535;}
if( value < 0 ){ value = 0;}
CompArray[d] = (WORD) value;
}
if(memcmp( &CompArray[0], &GammaArray[0][0], 2*256) == 0){
goto ENDIT;
}
}
}
}
}
}

}else{//no gamma curve (G=10)
Memo1->Lines->Add("No Gamma Curve");
i=10;
gamma=1; //same as 10 / 10
for( d = 0; d < 256; d++ ){
DArray[d] = ( pow( (double)(d * 256) / 65535, 1 / gamma) * 65535) + 0.5;
}
for( j = 2; j < 101 ; j++ ){//level //980,000 possible iterations
level = 1 + ((j - 50) / 100);
for( k = 0; k < 101; k++ ){//bright
bright = 1 + (((k - 50) / 100) * 65535);
for( m = 0; m < 101; m++ ){//contrast
cntrst = 1 + ((m - 50) / 100);
//Test only the mid-line value at first
//To see if a complete comparison is warranted
value = (((( DArray[x4] / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
value = value += bright;
value *= level;
if( value > 65535){ value = 65535; }
if( value < 0 ){ value = 0; }
if( (WORD) value == GammaArray[0][x4] ){
for( d = 0; d < 256; d++ ){
value = ((( (DArray[d] / 65535) - 0.5) * cntrst) + 0.5) * 65535;
value = value += bright;
value *= level;
if( value > 65535 ){ value = 65535;}
if( value < 0 ){ value = 0;}
CompArray[d] = (WORD)value;
}
if(memcmp( &CompArray[0], &GammaArray[0][0], 2*256) == 0){
goto ENDIT;
}
}
}
}
}
}

Memo1->Lines->Add("Values NOT Discovered - Checking All Values...");

//if for any reason we did not find a match, reiterate through
//all possible gamma settings...

for(i=2;i<51;i++){
gamma=i/10;
for( d = 0; d < 256; d++ ){
DArray[d] = ( pow( (double)(d * 256) / 65535, 1 / gamma) * 65535) + 0.5;
}
for( j = 2; j < 101 ; j++ ){//level //980,000 possible iterations
level = 1 + ((j - 50) / 100);
for( k = 0; k < 101; k++ ){//bright
bright = 1 + (((k - 50) / 100) * 65535);
for( m = 0; m < 101; m++ ){//contrast
cntrst = 1 + ((m - 50) / 100);
//Test only the mid-line value at first
//To see if a complete comparison is warranted
value = (((( DArray[x4] / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
value = value += bright;
value *= level;
if( value > 65535){ value = 65535; }
if( value < 0 ){ value = 0; }
if( (WORD) value == GammaArray[0][x4] ){
for( d = 0; d < 256; d++ ){
value = ((( (DArray[d] / 65535) - 0.5) * cntrst) + 0.5) * 65535;
value = value += bright;
value *= level;
if( value > 65535 ){ value = 65535;}
if( value < 0 ){ value = 0;}
CompArray[d] = (WORD) value;
}
if(memcmp( &CompArray[0], &GammaArray[0][0], 2*256) == 0){
goto ENDIT;
}
}
}
}
}
}

Memo1->Lines->Add("Values Still NOT Discovered!!!");
Memo1->Lines->Add("Discovery Operation Failed.");
return;

ENDIT:

Memo1->Lines->Add("Bright = " + IntToStr((int)k));
Memo1->Lines->Add("Cntrst = " + IntToStr((int)m));
Memo1->Lines->Add("Level = " + IntToStr((int)j));
Memo1->Lines->Add("Gamma = " + IntToStr((int)i));
}
//---------------------------------------------------------------------------
short __fastcall TForm1::Round(double flt){
short num=(short)flt;
if(flt>=0){
if(flt-num>=.5){num++;}
}else{
if(flt-num<=-.5){num--;}
}
return(num);
}
//----------------------------------------------------------------------------
bool __fastcall TForm1::ptToPtRngBrg(RngBrg *rb,double x1,double y1,double x2,double y2){//was PixToPixRngBrg
rb->rng=0;rb->brg=0;
//range is in pixels
double diffx,diffy,dangle;
if(x1>=x2){
if(x1>=0&&x2<0){diffx=x1+fabs(x2);}else{diffx=x1-x2;}
}else{
if(x2>=0&&x1<0){diffx=x2+fabs(x1);}else{diffx=x2-x1;}
}
if(y1>=y2){
if(y1>=0&&y2<0){diffy=y1+fabs(y2);}else{diffy=y1-y2;}
}else{
if(y2>=0&&y1<0){diffy=y2+fabs(y1);}else{diffy=y2-y1;}
}

if(diffx==0){
rb->brg=0;rb->rng=y2-y1;
return true;
}
if(diffy==0){
rb->brg=90;rb->rng=x2-x1;
return true;
}
dangle=atan(diffy/diffx);
rb->rng=diffx/cos(dangle);//hyp
dangle=dangle/DEG_RAD;
rb->brg=90-dangle;
return true;
}
//-----------------------------------------------------------------------
void __fastcall TForm1::GetPtFromPt(double x, double y,double rng,double brg,double *x2,double *y2){
//Gets a pix(coord) from a pix (coord)
//range is in absolute pixels
if(brg>=360){brg-=360;}
if(brg<0){brg+=360;}
if(brg==360||brg==0){*x2=x;*y2=y+rng;return;}
if(brg==270){*y2=y;*x2=x-rng;return;}
if(brg==90){*y2=y;*x2=x+rng;return;}
if(brg==180){*x2=x;*y2=y-rng;return;}

if(brg<90){
*x2=x+(rng*cos((90-brg)*DEG_RAD));
*y2=y+(rng*sin((90-brg)*DEG_RAD));
return;
}
}
//---------------------------------------------------------------------------
 
Ad

Advertisements

Joined
Sep 3, 2016
Messages
3
Reaction score
0
How would you calculate the input values (arguments) for this method directly from the existing Gamma Ramp? In other words, what would be the reverse method?

If I am going to provide these utilities to the user, I need to present them with their current settings prior to their making the adjustments.

I have tried for three days to come up with a solution, and must admit that this is beyond my capabilities.

Update Sept 8, 2016 - My best solution...

While I worked long and hard looking for a mathematical solution, I finally went back and played around with the iteration method.

A blunt force iteration that calculates each and every possible setting and then checks for a match will run for more than 20 minutes; which is unacceptable.

I found that I could take a single sample point in the middle of the array and quickly iterate through all possible settings to find a match for that one sample; and then perform all 256 of the calculations for the array to see if we had a complete match. This operation generally runs in less than a second.

But I realized that there was yet another problem. If the Gamma Ramp was not programmed using the CalculateRamp algorithm (such as factory setting) then it is unlikely that we will find an exact match for our settings; and our solution will fail.

So I devised a backup plan that uses four sample points from across the gamma ramp array; and then set up a "span" of acceptable values that would provide a close approximation for our settings. The criteria for "span" would broaden after each failure, and re-enter the search. This scenario will produce an approximation of the Gamma Ramp Settings usually within about three seconds. This is acceptable since my main program can perform this operation during the program startup sequence.

Here is my C++ solution...

//---------------------------------------------------------------------------
int __fastcall TForm1::Round( double flt ){
int num = (int) flt;
if( flt >= 0 ){
if( flt - num >= .5 ){ num++;}
}else{
if( flt - num <= -.5 ){ num--;}
}
return( num );
}
//----------------------------------------------------------------------------
void __fastcall TForm1::GetGammaRampSettings(void){
// level should be between 2 and 100
// gamma should be between 2 and 50
// brigntess should be between 0 and 100
// contrast should be between 0 and 100

double i,j,k,m;
double gamma,bright,cntrst,level;
double v1,v2,v3,v4;
double c1,c2,c3,c4;
double a1,a2,a3,a4,b1,b2,b3,b4; //for best estimate criteria
int x1,x2,x3,x4,x5;
int d,n;
WORD span = 8;
bool tog = false;

TDateTime strt, end;

WORD GammaArray[3][256]; //Gamma Ramp Array buffer
WORD CompArray[256]; //Array buffer for comparison

HDC GammaDC = GetDC( NULL ); //fetch the gamma ramp array
GetDeviceGammaRamp( GammaDC, GammaArray );
ReleaseDC( NULL, GammaDC );

strt=Now();

//Find two endpoints for the GammaArray
for( x1 = 0; x1 < 256; x1++ ){
if( GammaArray[0][x1] > 0 ){ break;}
}

for( x2 = 0; x2 < 256; x2++ ){
if( GammaArray[0][x2] == 65535 ){ break;}
}
if( x2 == 256 ){ x2 = 255; }

x5 = x1 + Round( (double)( x2 - x1 ) / 2.0 );

Memo1->Lines->Add( "x1 = " + IntToStr( x1 )); //start
Memo1->Lines->Add( "x2 = " + IntToStr( x2 )); //end
Memo1->Lines->Add( "x5 = " + IntToStr( x5 )); //mid

//neutral settings are as follows...
//Bright = 50
//Contrast = 50
//Level = 50
//Gamma = 10

//The following code will look for an exact match to the settings
//but it may fail if the gamma ramp was not programmed using the
//original CalculateRamp code. ie: A factory setting, or other

for(n=0;n<=40;){//gamma
if( n < 9 ){ //gamma begins with a neutral setting of 10 and
if( tog ){ //then increments high and low depending on tog
i = ( 10 - n );
tog = false; n++;
if( i == 10 ){ continue;}
}else{
i = ( 10 + n );
tog = true;
}
}else{
i = ( 10 + n++ );
}
gamma = i / 10;
v1 = ( pow( (double)(x5 * 256) / 65535, 1 / gamma) * 65535) + 0.5;
for( j = 2; j < 101 ; j++ ){//level //980,000 possible iterations
level = 1 + ((j - 50) / 100);
for( k = 0; k < 101; k++ ){//bright
bright = 1 + (((k - 50) / 100) * 65535);
for( m = 0; m < 101; m++ ){//contrast
cntrst = 1 + ((m - 50) / 100);
//Test only the mid-line value at first
//To see if a complete comparison is warranted
c1 = (((( v1 / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
c1 = c1 += bright;
c1 *= level;
if( c1 > 65535){ c1 = 65535; }
if( c1 < 0 ){ c1 = 0; }
if( (WORD) c1 == GammaArray[0][x5] ){
for( d = 0; d < 256; d++ ){
c1 = ( pow( (double)(d * 256) / 65535, 1 / gamma) * 65535) + 0.5;
c1 = ((( (c1 / 65535) - 0.5) * cntrst) + 0.5) * 65535;
c1 = c1 += bright;
c1 *= level;
if( c1 > 65535 ){ c1 = 65535;}
if( c1 < 0 ){ c1 = 0;}
CompArray[d] = (WORD) c1;
}
if(memcmp( &CompArray[0], &GammaArray[0][0], 2*256) == 0){
goto ENDIT;
}
}
}
}
}
}

//Since an exact match could not be found for the current
//gamma ramp settings, we will broaden our criteria and
//search for the best match for our gamma ramp settings.

//Here our search is based on the start and end values,
//plus two additional values that are 1/4 of the overall distance
//above the start point; and 1/4 the distance below the end point.

Memo1->Lines->Add("Values NOT Discovered!!!");
Memo1->Lines->Add("Widening the Search....");

x3 = x1 + Round( (double)( x2 - x1 ) / 4.0 );
x4 = x1 + Round(( (double)( x2 - x1 ) / 4.0 )*3);

Memo1->Lines->Add( "x1 = " + IntToStr( x1 )); //start
Memo1->Lines->Add( "x2 = " + IntToStr( x2 )); //end
Memo1->Lines->Add( "x3 = " + IntToStr( x3 )); // 1/4
Memo1->Lines->Add( "x4 = " + IntToStr( x4 )); // 3/4

BROADEN:

a1 = GammaArray[0][x1]+span; //these are our high and low
b1 = GammaArray[0][x1]-span; //values that the ramp values must
//fall within to be accepted
a2 = GammaArray[0][x2]+span;
b2 = GammaArray[0][x2]-span; //span begins at 8 and increments upwards
//with each loop, ever widening the
a3 = GammaArray[0][x3]+span; //the search criteria
b3 = GammaArray[0][x3]-span;

a4 = GammaArray[0][x4]+span;
b4 = GammaArray[0][x4]-span;

tog=false;
for(n=0;n<=40;){//gamma
if( n < 9 ){ //gamma begins with a neutral setting of 10 and
if( tog ){ //then increments high and low depending on tog
i = ( 10 - n );
tog = false; n++;
if( i == 10 ){ continue;} //prevents two instances of i == 10
}else{
i = ( 10 + n );
tog = true;
}
}else{
i = ( 10 + n++ );
}
gamma = i / 10;
v1 = ( pow( (double)(x1 * 256) / 65535, 1 / gamma) * 65535) + 0.5;
v2 = ( pow( (double)(x2 * 256) / 65535, 1 / gamma) * 65535) + 0.5;
v3 = ( pow( (double)(x3 * 256) / 65535, 1 / gamma) * 65535) + 0.5;
v4 = ( pow( (double)(x4 * 256) / 65535, 1 / gamma) * 65535) + 0.5;
for( j = 2; j < 101 ; j++ ){//level //980,000 possible iterations
level = 1 + ((j - 50) / 100);
for( k = 0; k < 101; k++ ){//bright
bright = 1 + (((k - 50) / 100) * 65535);
for( m = 0; m < 101; m++ ){//contrast
cntrst = 1 + ((m - 50) / 100);
//Test the four sample values to see if the
//results falls within our acceptance criteria.
c1 = (((( v1 / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
c1 = c1 += bright;
c1 *= level;
c2 = (((( v2 / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
c2 = c2 += bright;
c2 *= level;
c3 = (((( v3 / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
c3 = c3 += bright;
c3 *= level;
c4 = (((( v4 / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
c4 = c4 += bright;
c4 *= level;
if( c1 <= a1 && c1 >= b1 && c2 <= a2 && c2 >= b2 &&
c3 <= a3 && c3 >= b3 && c4 <= a4 && c4 >= b4 ){
Memo1->Lines->Add("Best Estimate +/- " + IntToStr( span ));
goto ENDIT;
}
}
}
}
}

if( span < 256){
span *= 2;
goto BROADEN;
}else if( span <= 4096 ){ //set the maximum allowance for "span"
span += 128; //which is the basis for the allowable
goto BROADEN; //search criteria
}

//if we get to this point then we have utterly failed.
Memo1->Lines->Add("Values STILL NOT Discovered.");
Memo1->Lines->Add("GetGammaRampSettings Failed.");
return;

ENDIT:
end=Now();
Memo1->Lines->Add("Execution duration was "+
FormatDateTime("nn:ss:zzz",end-strt));

//Report on findings...
Memo1->Lines->Add("Bright = " + IntToStr( (int) k ));
Memo1->Lines->Add("Cntrst = " + IntToStr( (int) m ));
Memo1->Lines->Add("Level = " + IntToStr( (int) j ));
Memo1->Lines->Add("Gamma = " + IntToStr( (int) i ));

}
//---------------------------------------------------------------------------
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top