diff --git a/sc2/ChangeLog b/sc2/ChangeLog index 39c5241c2..9076ac58f 100644 --- a/sc2/ChangeLog +++ b/sc2/ChangeLog @@ -1,4 +1,5 @@ Changes towards version 0.8: +- Added gamma correction to the setup menu (bug #977) - Alex, Nic - Refactor Melnorme comm code to make modding easier; step 1 (bug #1128), from Scott A. Colcord - Added aspect ratio option to the setup menu - Alex diff --git a/sc2/content/base/ui/setupmenu.txt b/sc2/content/base/ui/setupmenu.txt index b4c0c3775..0783c3417 100644 --- a/sc2/content/base/ui/setupmenu.txt +++ b/sc2/content/base/ui/setupmenu.txt @@ -455,6 +455,7 @@ Currently only meaningful in OpenGL mode. Music Volume SFX Volume Speech Volume +Gamma Correction #(SLIDER_0_DESC) Sets the music volume. @@ -465,6 +466,10 @@ Sets the sound effects volume. #(SLIDER_2_DESC) Sets the speech volume. +#(SLIDER_3_DESC) +Sets the gamma correction. +May not work on all hardware. + #(BUTTONS) Quit Setup Menu Return to Main Menu diff --git a/sc2/src/libs/graphics/gfx_common.h b/sc2/src/libs/graphics/gfx_common.h index ef9cd718a..a0ffe9dd3 100644 --- a/sc2/src/libs/graphics/gfx_common.h +++ b/sc2/src/libs/graphics/gfx_common.h @@ -67,7 +67,7 @@ int TFB_InitGraphics (int driver, int flags, int width, int height); int TFB_ReInitGraphics (int driver, int flags, int width, int height); void TFB_UninitGraphics (void); void TFB_ProcessEvents (void); -void TFB_SetGamma (float gamma); +bool TFB_SetGamma (float gamma); void TFB_UploadTransitionScreen (void); // This function should not be called directly void TFB_SwapBuffers (int force_full_redraw); diff --git a/sc2/src/libs/graphics/sdl/sdl_common.c b/sc2/src/libs/graphics/sdl/sdl_common.c index 461a64831..0fd84e149 100644 --- a/sc2/src/libs/graphics/sdl/sdl_common.c +++ b/sc2/src/libs/graphics/sdl/sdl_common.c @@ -592,16 +592,8 @@ TFB_UploadTransitionScreen (void) #endif } -void +bool TFB_SetGamma (float gamma) { - if (SDL_SetGamma (gamma, gamma, gamma) == -1) - { - log_add (log_Warning, "Unable to set gamma correction."); - } - else - { - log_add (log_Info, "Gamma correction set to %1.4f.", gamma); - } + return (SDL_SetGamma (gamma, gamma, gamma) == 0); } - diff --git a/sc2/src/options.c b/sc2/src/options.c index 5b8389c36..105a8d139 100644 --- a/sc2/src/options.c +++ b/sc2/src/options.c @@ -58,6 +58,9 @@ BOOLEAN optSpeech; BOOLEAN optSubtitles; BOOLEAN optStereoSFX; BOOLEAN optKeepAspectRatio; + +float optGamma; + uio_DirHandle *contentDir; uio_DirHandle *configDir; uio_DirHandle *saveDir; @@ -584,3 +587,14 @@ prepareAddons (const char **addons) } } } + +bool +setGammaCorrection (float gamma) +{ + bool set = TFB_SetGamma (gamma); + if (set) + log_add (log_Info, "Gamma correction set to %.4f.", gamma); + else + log_add (log_Warning, "Unable to set gamma correction."); + return set; +} diff --git a/sc2/src/options.h b/sc2/src/options.h index 23c6f4c73..9fd33b1f4 100644 --- a/sc2/src/options.h +++ b/sc2/src/options.h @@ -45,6 +45,9 @@ extern BOOLEAN optSubtitles; extern BOOLEAN optStereoSFX; extern BOOLEAN optKeepAspectRatio; +#define GAMMA_SCALE 1000 +extern float optGamma; + extern uio_DirHandle *contentDir; extern uio_DirHandle *configDir; extern uio_DirHandle *saveDir; @@ -76,5 +79,7 @@ void prepareShadowAddons (const char **addons); BOOLEAN loadAddon (const char *addon); int loadIndices (uio_DirHandle *baseDir); +bool setGammaCorrection (float gamma); + #endif diff --git a/sc2/src/uqm.c b/sc2/src/uqm.c index a94e233ed..26d572866 100644 --- a/sc2/src/uqm.c +++ b/sc2/src/uqm.c @@ -243,7 +243,7 @@ main (int argc, char *argv[]) INIT_CONFIG_OPTION( scaler, 0 ), INIT_CONFIG_OPTION( showFps, false ), INIT_CONFIG_OPTION( keepAspectRatio, false ), - INIT_CONFIG_OPTION( gamma, 0.0f ), + INIT_CONFIG_OPTION( gamma, 1.0f ), INIT_CONFIG_OPTION( soundDriver, audio_DRIVER_MIXSDL ), INIT_CONFIG_OPTION( soundQuality, audio_QUALITY_MEDIUM ), INIT_CONFIG_OPTION( use3doMusic, true ), @@ -418,8 +418,11 @@ main (int argc, char *argv[]) gfxFlags |= TFB_GFXFLAGS_SHOWFPS; TFB_InitGraphics (gfxDriver, gfxFlags, options.resolution.width, options.resolution.height); - if (options.gamma.set) - TFB_SetGamma (options.gamma.value); + if (options.gamma.set && setGammaCorrection (options.gamma.value)) + optGamma = options.gamma.value; + else + optGamma = 1.0f; // failed or default + InitColorMaps (); init_communication (); /* TODO: Once threading is gone, restore initAudio here. @@ -557,6 +560,25 @@ getVolumeConfigValue (struct float_option *option, const char *config_val) option->set = true; } +static void +getGammaConfigValue (struct float_option *option, const char *config_val) +{ + int val; + + if (option->set || !res_IsInteger (config_val)) + return; + + val = res_GetInteger (config_val); + // gamma config option is a fixed-point number + // ignore ridiculously out-of-range values + if (val < (int)(0.03 * GAMMA_SCALE) || val > (int)(9.9 * GAMMA_SCALE)) + return; + option->value = val / (float)GAMMA_SCALE; + // avoid setting gamma when not necessary + if (option->value != 1.0f) + option->set = true; +} + static bool getListConfigValue (struct int_option *option, const char *config_val, const struct option_list_value *list) @@ -605,6 +627,7 @@ getUserConfigOptions (struct options_struct *options) getBoolConfigValue (&options->scanlines, "config.scanlines"); getBoolConfigValue (&options->showFps, "config.showfps"); getBoolConfigValue (&options->keepAspectRatio, "config.keepaspectratio"); + getGammaConfigValue (&options->gamma, "config.gamma"); getBoolConfigValue (&options->subtitles, "config.subtitles"); diff --git a/sc2/src/uqm/setupmenu.c b/sc2/src/uqm/setupmenu.c index 6f1bbfcdc..2c07128ee 100644 --- a/sc2/src/uqm/setupmenu.c +++ b/sc2/src/uqm/setupmenu.c @@ -37,6 +37,8 @@ #include "libs/memlib.h" #include "resinst.h" #include "nameref.h" +#include + static STRING SetupTab; @@ -74,7 +76,7 @@ static void clear_control (WIDGET_CONTROLENTRY *widget); #define MENU_COUNT 8 #define CHOICE_COUNT 24 -#define SLIDER_COUNT 3 +#define SLIDER_COUNT 4 #define BUTTON_COUNT 10 #define LABEL_COUNT 4 #define TEXTENTRY_COUNT 1 @@ -119,6 +121,7 @@ static WIDGET *graphics_widgets[] = { (WIDGET *)(&choices[0]), (WIDGET *)(&choices[23]), (WIDGET *)(&choices[10]), + (WIDGET *)(&sliders[3]), (WIDGET *)(&choices[2]), (WIDGET *)(&choices[3]), (WIDGET *)(&buttons[1]), @@ -202,6 +205,18 @@ menu_defs[] = {NULL, 0} }; +// Start with reasonable gamma bounds. These will get updated +// as we find out the actual bounds. +static float minGamma = 0.4f; +static float maxGamma = 2.5f; +// The gamma slider uses an exponential curve +// We use y = e^(2.1972*(x-1)) curve to give us a nice spread of +// gamma values 0.11 < g < 9.0 centered at g=1.0 +#define GAMMA_CURVE_B 2.1972f +static float minGammaX; +static float maxGammaX; + + static int quit_main_menu (WIDGET *self, int event) { @@ -405,6 +420,7 @@ SetDefaults (void) sliders[0].value = opts.musicvol; sliders[1].value = opts.sfxvol; sliders[2].value = opts.speechvol; + sliders[3].value = opts.gamma; } static void @@ -438,6 +454,7 @@ PropagateResults (void) opts.musicvol = sliders[0].value; opts.sfxvol = sliders[1].value; opts.speechvol = sliders[2].value; + opts.gamma = sliders[3].value; SetGlobalOptions (&opts); } @@ -594,6 +611,135 @@ OnTextEntryEvent (WIDGET_TEXTENTRY *widget) return TRUE; // event handled } +static inline float +gammaCurve (float x) +{ + // The slider uses an exponential curve + return exp ((x - 1) * GAMMA_CURVE_B); +} + +static inline float +solveGammaCurve (float y) +{ + return log (y) / GAMMA_CURVE_B + 1; +} + +static int +gammaToSlider (float gamma) +{ + const float x = solveGammaCurve (gamma); + const float step = (maxGammaX - minGammaX) / 100; + return (int) ((x - minGammaX) / step + 0.5); +} + +static float +sliderToGamma (int value) +{ + const float step = (maxGammaX - minGammaX) / 100; + const float x = minGammaX + step * value; + const float g = gammaCurve (x); + // report any value that is close enough as 1.0 + return (fabs (g - 1.0f) < 0.001f) ? 1.0f : g; +} + +static void +updateGammaBounds (bool useUpper) +{ + float g, x; + int slider; + + // The slider uses an exponential curve. + // Calculate where on the curve the min and max gamma values are + minGammaX = solveGammaCurve (minGamma); + maxGammaX = solveGammaCurve (maxGamma); + + // We have 100 discrete steps through the range, so the slider may + // skip over a 1.0 gamma. We need to ensure that there always is + // a 1.0 on the slider by tweaking the range (expanding/contracting). + slider = gammaToSlider (1.0f); + g = sliderToGamma (slider); + if (g == 1.0f) + return; // no adjustment needed + + x = solveGammaCurve (g); + if (useUpper) + { // Move the upper bound up or down to land on 1.0 + const float d = (x - 1.0f) * 100 / slider; + maxGammaX -= d; + maxGamma = gammaCurve (maxGammaX); + } + else + { // Move the lower bound up or down to land on 1.0 + const float d = (x - 1.0f) * 100 / (100 - slider); + minGammaX -= d; + minGamma = gammaCurve (minGammaX); + } +} + +static int +gamma_HandleEventSlider (WIDGET *_self, int event) +{ + WIDGET_SLIDER *self = (WIDGET_SLIDER *)_self; + int prevValue = self->value; + float gamma; + bool set; + + switch (event) + { + case WIDGET_EVENT_LEFT: + self->value -= self->step; + break; + case WIDGET_EVENT_RIGHT: + self->value += self->step; + break; + default: + return FALSE; + } + + // Limit the slider to values accepted by gfx subsys + gamma = sliderToGamma (self->value); + set = TFB_SetGamma (gamma); + if (!set) + { // revert + self->value = prevValue; + gamma = sliderToGamma (self->value); + } + + // Grow or shrink the range based on accepted values + if (gamma < minGamma || (!set && event == WIDGET_EVENT_LEFT)) + { + minGamma = gamma; + updateGammaBounds (true); + // at the lowest end + self->value = 0; + } + else if (gamma > maxGamma || (!set && event == WIDGET_EVENT_RIGHT)) + { + maxGamma = gamma; + updateGammaBounds (false); + // at the highest end + self->value = 100; + } + return TRUE; +} + +static void +gamma_DrawValue (WIDGET_SLIDER *self, int x, int y) +{ + TEXT t; + char buf[16]; + float gamma = sliderToGamma (self->value); + snprintf (buf, sizeof buf, "%.4f", gamma); + + t.baseline.x = x; + t.baseline.y = y; + t.align = ALIGN_CENTER; + t.CharCount = ~0; + t.pStr = buf; + + font_DrawText (&t); +} + static void rebind_control (WIDGET_CONTROLENTRY *widget) { @@ -804,6 +950,10 @@ init_widgets (void) sliders[i].tooltip[1] = ""; sliders[i].tooltip[2] = ""; } + // gamma is a special case + sliders[3].step = 1; + sliders[3].handleEvent = gamma_HandleEventSlider; + sliders[3].draw_value = gamma_DrawValue; for (i = 0; i < SLIDER_COUNT; i++) { @@ -1080,6 +1230,8 @@ SetupMenu (void) void GetGlobalOptions (GLOBALOPTS *opts) { + bool whichBound; + if (GfxFlags & TFB_GFXFLAGS_SCALE_BILINEAR) { opts->scaler = OPTVAL_BILINEAR_SCALE; @@ -1227,13 +1379,22 @@ GetGlobalOptions (GLOBALOPTS *opts) } } + whichBound = (optGamma < maxGamma); + // The option supplied by the user may be beyond our starting range + // but valid nonetheless. We need to account for that. + if (optGamma <= minGamma) + minGamma = optGamma - 0.03f; + else if (optGamma >= maxGamma) + maxGamma = optGamma + 0.3f; + updateGammaBounds (whichBound); + opts->gamma = gammaToSlider (optGamma); + opts->player1 = PlayerControls[0]; opts->player2 = PlayerControls[1]; opts->musicvol = (((int)(musicVolumeScale * 100.0f) + 2) / 5) * 5; opts->sfxvol = (((int)(sfxVolumeScale * 100.0f) + 2) / 5) * 5; opts->speechvol = (((int)(speechVolumeScale * 100.0f) + 2) / 5) * 5; - } void @@ -1336,6 +1497,14 @@ SetGlobalOptions (GLOBALOPTS *opts) FlushGraphics (); InitVideoPlayer (TRUE); } + + // Avoid setting gamma when it is not necessary + if (optGamma != 1.0f || sliderToGamma (opts->gamma) != 1.0f) + { + optGamma = sliderToGamma (opts->gamma); + setGammaCorrection (optGamma); + } + optSubtitles = (opts->subtitles == OPTVAL_ENABLED) ? TRUE : FALSE; // optWhichMusic = (opts->music == OPTVAL_3DO) ? OPT_3DO : OPT_PC; optWhichMenu = (opts->menu == OPTVAL_3DO) ? OPT_3DO : OPT_PC; @@ -1366,6 +1535,7 @@ SetGlobalOptions (GLOBALOPTS *opts) res_PutBoolean ("config.positionalsfx", opts->stereo == OPTVAL_ENABLED); res_PutBoolean ("config.pulseshield", opts->shield == OPTVAL_3DO); res_PutBoolean ("config.keepaspectratio", opts->keepaspect == OPTVAL_ENABLED); + res_PutInteger ("config.gamma", (int) (optGamma * GAMMA_SCALE + 0.5)); res_PutInteger ("config.player1control", opts->player1); res_PutInteger ("config.player2control", opts->player2); diff --git a/sc2/src/uqm/setupmenu.h b/sc2/src/uqm/setupmenu.h index d9f4301ee..3a4619224 100644 --- a/sc2/src/uqm/setupmenu.h +++ b/sc2/src/uqm/setupmenu.h @@ -80,6 +80,7 @@ typedef struct globalopts_struct { OPT_CONSOLETYPE menu, text, cscan, scroll, intro, meleezoom, shield; CONTROL_TEMPLATE player1, player2; int speechvol, musicvol, sfxvol; + int gamma; } GLOBALOPTS; void SetupMenu (void);