diff --git a/Changes.txt b/Changes.txt
index 88ddd7afe..0a08e446b 100644
--- a/Changes.txt
+++ b/Changes.txt
@@ -16,14 +16,16 @@
* Added basic (entire and single line only) text cut/copy and paste.
- * Fixed bug with aspect correction and fullscreen mode; snapshots from
- such a mode are now pixel-exact.
+ * Added color parameters to 'Custom' palette
* Some improvements to AVox-USB adaptor functionality:
- Made serial port used for an AtariVox-USB adaptor editable.
- Autodetection of serial ports no longer messes up devices plugged
into other serial ports.
+ * Fixed bug with aspect correction and fullscreen mode; snapshots from
+ such a mode are now pixel-exact.
+
* Fixed crash with missing or incorrectly sized SaveKey data file, and
with certain functions not working (erase pages, erase entire EEPROM).
diff --git a/docs/graphics/options_video_palettes.png b/docs/graphics/options_video_palettes.png
index cb8597bbe..d965b45b3 100644
Binary files a/docs/graphics/options_video_palettes.png and b/docs/graphics/options_video_palettes.png differ
diff --git a/docs/index.html b/docs/index.html
index 169bc0bc6..53f5b9e0f 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -2130,7 +2130,7 @@
-palette <standard|z26|user|custom> |
Set the palette to either normal Stella, the one used in the z26
emulator, a user-defined palette, or a custom palette generated
- from user-defined phase shifts. |
+ from user-defined parameters.
@@ -2143,6 +2143,36 @@
Adjust phase shift of 'custom' PAL palette. |
+
+ -pal.red_scale <number> |
+ Adjust red scale of 'custom' palette (range -1.0 to 1.0). |
+
+
+
+ -pal.red_shift <number> |
+ Adjust red shift of 'custom' palette (range -22.5 to 22.5). |
+
+
+
+ -pal.green_scale <number> |
+ Adjust green scale of 'custom' palette (range -1.0 to 1.0). |
+
+
+
+ -pal.green_shift <number> |
+ Adjust green shift of 'custom' palette (range -22.5 to 22.5). |
+
+
+
+ -pal.blue_scale <number> |
+ Adjust blue scale of 'custom' palette (range -1.0 to 1.0). |
+
+
+
+ -pal.blue_shift <number> |
+ Adjust blue shift of 'custom' palette (range -22.5 to 22.5). |
+
+
-pal.hue <number> |
Adjust hue of current palette (range -1.0 to 1.0). |
@@ -3078,8 +3108,11 @@
Item | Brief description | For more information, see Command Line |
Palette | Palette used for emulation mode | -palette |
- NTSC phase | Adjust phase shift for 'Custom' NTSC palette | -pal.phase_ntsc |
- PAL phase | Adjust phase shift for 'Custom' PAL palette | -pal.phase_pal |
+ NTSC phase | Adjust phase shift of 'Custom' NTSC palette | -pal.phase_ntsc |
+ PAL phase | Adjust phase shift of 'Custom' PAL palette | -pal.phase_pal |
+ R | Adjust red scale and shift of 'Custom' palette | -pal.red_scale, -pal.red_shift |
+ G | Adjust green scale and shift of 'Custom' palette | -pal.green_scale, -pal.green_shift |
+ B | Adjust blue scale and shift of 'Custom' palette | -pal.blue_scale, -pal.blue_shift |
Hue | Adjust hue of currently selected palette | -pal.hue |
Saturation | Adjust saturation of currently selected palette | -pal.saturation |
Contrast | Adjust contrast of currently selected palette | -pal.contrast |
diff --git a/src/common/PaletteHandler.cxx b/src/common/PaletteHandler.cxx
index 030ff4d9e..db6961af8 100644
--- a/src/common/PaletteHandler.cxx
+++ b/src/common/PaletteHandler.cxx
@@ -74,14 +74,38 @@ void PaletteHandler::cyclePalette(int direction)
setPalette(palette);
}
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool PaletteHandler::isCustomAdjustable() const
+{
+ return myCurrentAdjustable >= CUSTOM_START
+ && myCurrentAdjustable <= CUSTOM_END;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool PaletteHandler::isPhaseShift() const
+{
+ return myCurrentAdjustable == PHASE_SHIFT;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool PaletteHandler::isRGBScale() const
+{
+ return myCurrentAdjustable >= RED_SCALE && myCurrentAdjustable <= BLUE_SCALE;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool PaletteHandler::isRGBShift() const
+{
+ return myCurrentAdjustable >= RED_SHIFT && myCurrentAdjustable <= BLUE_SHIFT;
+}
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::showAdjustableMessage()
{
- const bool isPhaseShift = myAdjustables[myCurrentAdjustable].value == nullptr;
ostringstream msg, buf;
msg << "Palette " << myAdjustables[myCurrentAdjustable].name;
- if(isPhaseShift)
+ if(isPhaseShift())
{
const ConsoleTiming timing = myOSystem.console().timing();
const bool isNTSC = timing == ConsoleTiming::ntsc;
@@ -90,12 +114,22 @@ void PaletteHandler::showAdjustableMessage()
buf << std::fixed << std::setprecision(1) << value << DEGREE;
myOSystem.frameBuffer().showMessage(
"Palette phase shift", buf.str(), value,
- (isNTSC ? DEF_NTSC_SHIFT : DEF_PAL_SHIFT) - MAX_SHIFT,
- (isNTSC ? DEF_NTSC_SHIFT : DEF_PAL_SHIFT) + MAX_SHIFT);
+ (isNTSC ? DEF_NTSC_SHIFT : DEF_PAL_SHIFT) - MAX_PHASE_SHIFT,
+ (isNTSC ? DEF_NTSC_SHIFT : DEF_PAL_SHIFT) + MAX_PHASE_SHIFT);
+ }
+ else if(isRGBShift())
+ {
+ const float value = *myAdjustables[myCurrentAdjustable].value;
+
+ buf << std::fixed << std::setprecision(1) << value << DEGREE;
+ myOSystem.frameBuffer().showMessage(
+ msg.str(), buf.str(), value, -MAX_RGB_SHIFT, +MAX_RGB_SHIFT);
}
else
{
- const int value = scaleTo100(*myAdjustables[myCurrentAdjustable].value);
+ const int value = isRGBScale()
+ ? scaleRGBTo100(*myAdjustables[myCurrentAdjustable].value)
+ : scaleTo100(*myAdjustables[myCurrentAdjustable].value);
buf << value << "%";
myOSystem.frameBuffer().showMessage(
msg.str(), buf.str(), value);
@@ -106,15 +140,15 @@ void PaletteHandler::showAdjustableMessage()
void PaletteHandler::cycleAdjustable(int direction)
{
const bool isCustomPalette = SETTING_CUSTOM == myOSystem.settings().getString("palette");
- bool isPhaseShift;
+ bool isCustomAdj;
do {
myCurrentAdjustable = BSPF::clampw(int(myCurrentAdjustable + direction), 0, NUM_ADJUSTABLES - 1);
- isPhaseShift = myAdjustables[myCurrentAdjustable].value == nullptr;
+ isCustomAdj = isCustomAdjustable();
// skip phase shift when 'Custom' palette is not selected
- if(!direction && isPhaseShift && !isCustomPalette)
+ if(!direction && isCustomAdj && !isCustomPalette)
myCurrentAdjustable++;
- } while(isPhaseShift && !isCustomPalette);
+ } while(isCustomAdj && !isCustomPalette);
showAdjustableMessage();
}
@@ -122,29 +156,38 @@ void PaletteHandler::cycleAdjustable(int direction)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::changeAdjustable(int adjustable, int direction)
{
- const bool isCustomPalette = SETTING_CUSTOM == myOSystem.settings().getString("palette");
- const bool isPhaseShift = myAdjustables[adjustable].value == nullptr;
-
myCurrentAdjustable = adjustable;
- if(isPhaseShift && !isCustomPalette)
- myCurrentAdjustable++;
-
changeCurrentAdjustable(direction);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::changeCurrentAdjustable(int direction)
{
- if(myAdjustables[myCurrentAdjustable].value == nullptr)
+ if(isPhaseShift())
changeColorPhaseShift(direction);
else
{
- int newVal = scaleTo100(*myAdjustables[myCurrentAdjustable].value);
+ if(isRGBScale())
+ {
+ int newVal = scaleRGBTo100(*myAdjustables[myCurrentAdjustable].value);
- newVal = BSPF::clamp(newVal + direction * 1, 0, 100);
+ newVal = BSPF::clamp(newVal + direction * 1, 0, 100);
+ *myAdjustables[myCurrentAdjustable].value = scaleRGBFrom100(newVal);
+ }
+ else if(isRGBShift())
+ {
+ float newShift = *myAdjustables[myCurrentAdjustable].value;
- *myAdjustables[myCurrentAdjustable].value = scaleFrom100(newVal);
+ newShift = BSPF::clamp(newShift + direction * 0.5F, -MAX_RGB_SHIFT, MAX_RGB_SHIFT);
+ *myAdjustables[myCurrentAdjustable].value = newShift;
+ }
+ else
+ {
+ int newVal = scaleTo100(*myAdjustables[myCurrentAdjustable].value);
+ newVal = BSPF::clamp(newVal + direction * 1, 0, 100);
+ *myAdjustables[myCurrentAdjustable].value = scaleFrom100(newVal);
+ }
showAdjustableMessage();
setPalette();
}
@@ -162,7 +205,7 @@ void PaletteHandler::changeColorPhaseShift(int direction)
const float shift = isNTSC ? DEF_NTSC_SHIFT : DEF_PAL_SHIFT;
float newPhase = isNTSC ? myPhaseNTSC : myPhasePAL;
- newPhase = BSPF::clamp(newPhase + direction * 0.3F, shift - MAX_SHIFT, shift + MAX_SHIFT);
+ newPhase = BSPF::clamp(newPhase + direction * 0.3F, shift - MAX_PHASE_SHIFT, shift + MAX_PHASE_SHIFT);
if(isNTSC)
myPhaseNTSC = newPhase;
@@ -181,15 +224,21 @@ void PaletteHandler::loadConfig(const Settings& settings)
{
// Load adjustables
myPhaseNTSC = BSPF::clamp(settings.getFloat("pal.phase_ntsc"),
- DEF_NTSC_SHIFT - MAX_SHIFT, DEF_NTSC_SHIFT + MAX_SHIFT);
+ DEF_NTSC_SHIFT - MAX_PHASE_SHIFT, DEF_NTSC_SHIFT + MAX_PHASE_SHIFT);
myPhasePAL = BSPF::clamp(settings.getFloat("pal.phase_pal"),
- DEF_PAL_SHIFT - MAX_SHIFT, DEF_PAL_SHIFT + MAX_SHIFT);
+ DEF_PAL_SHIFT - MAX_PHASE_SHIFT, DEF_PAL_SHIFT + MAX_PHASE_SHIFT);
+ myRedScale = BSPF::clamp(settings.getFloat("pal.red_scale"), -1.0F, 1.0F) + 1.F;
+ myGreenScale = BSPF::clamp(settings.getFloat("pal.green_scale"), -1.0F, 1.0F) + 1.F;
+ myBlueScale = BSPF::clamp(settings.getFloat("pal.blue_scale"), -1.0F, 1.0F) + 1.F;
+ myRedShift = BSPF::clamp(settings.getFloat("pal.red_shift"), -MAX_RGB_SHIFT, MAX_RGB_SHIFT);
+ myGreenShift = BSPF::clamp(settings.getFloat("pal.green_shift"), -MAX_RGB_SHIFT, MAX_RGB_SHIFT);
+ myBlueShift = BSPF::clamp(settings.getFloat("pal.blue_shift"), -MAX_RGB_SHIFT, MAX_RGB_SHIFT);
- myHue = BSPF::clamp(settings.getFloat("pal.hue"), -1.0F, 1.0F);
- mySaturation = BSPF::clamp(settings.getFloat("pal.saturation"), -1.0F, 1.0F);
- myContrast = BSPF::clamp(settings.getFloat("pal.contrast"), -1.0F, 1.0F);
- myBrightness = BSPF::clamp(settings.getFloat("pal.brightness"), -1.0F, 1.0F);
- myGamma = BSPF::clamp(settings.getFloat("pal.gamma"), -1.0F, 1.0F);
+ myHue = BSPF::clamp(settings.getFloat("pal.hue"), -1.0F, 1.0F);
+ mySaturation = BSPF::clamp(settings.getFloat("pal.saturation"), -1.0F, 1.0F);
+ myContrast = BSPF::clamp(settings.getFloat("pal.contrast"), -1.0F, 1.0F);
+ myBrightness = BSPF::clamp(settings.getFloat("pal.brightness"), -1.0F, 1.0F);
+ myGamma = BSPF::clamp(settings.getFloat("pal.gamma"), -1.0F, 1.0F);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -198,6 +247,12 @@ void PaletteHandler::saveConfig(Settings& settings) const
// Save adjustables
settings.setValue("pal.phase_ntsc", myPhaseNTSC);
settings.setValue("pal.phase_pal", myPhasePAL);
+ settings.setValue("pal.red_scale", myRedScale - 1.F);
+ settings.setValue("pal.green_scale", myGreenScale - 1.F);
+ settings.setValue("pal.blue_scale", myBlueScale - 1.F);
+ settings.setValue("pal.red_shift", myRedShift);
+ settings.setValue("pal.green_shift", myGreenShift);
+ settings.setValue("pal.blue_shift", myBlueShift);
settings.setValue("pal.hue", myHue);
settings.setValue("pal.saturation", mySaturation);
@@ -209,8 +264,14 @@ void PaletteHandler::saveConfig(Settings& settings) const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::setAdjustables(const Adjustable& adjustable)
{
- myPhaseNTSC = adjustable.phaseNtsc / 10.F;
- myPhasePAL = adjustable.phasePal / 10.F;
+ myPhaseNTSC = scaleFromAngles(adjustable.phaseNtsc);
+ myPhasePAL = scaleFromAngles(adjustable.phasePal);
+ myRedScale = scaleRGBFrom100(adjustable.redScale);
+ myGreenScale = scaleRGBFrom100(adjustable.greenScale);
+ myBlueScale = scaleRGBFrom100(adjustable.blueScale);
+ myRedShift = scaleFromAngles(adjustable.redShift);
+ myGreenShift = scaleFromAngles(adjustable.greenShift);
+ myBlueShift = scaleFromAngles(adjustable.blueShift);
myHue = scaleFrom100(adjustable.hue);
mySaturation = scaleFrom100(adjustable.saturation);
@@ -222,8 +283,14 @@ void PaletteHandler::setAdjustables(const Adjustable& adjustable)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::getAdjustables(Adjustable& adjustable) const
{
- adjustable.phaseNtsc = myPhaseNTSC * 10.F;
- adjustable.phasePal = myPhasePAL * 10.F;
+ adjustable.phaseNtsc = scaleToAngles(myPhaseNTSC);
+ adjustable.phasePal = scaleToAngles(myPhasePAL);
+ adjustable.redScale = scaleRGBTo100(myRedScale);
+ adjustable.greenScale = scaleRGBTo100(myGreenScale);
+ adjustable.blueScale = scaleRGBTo100(myBlueScale);
+ adjustable.redShift = scaleToAngles(myRedShift);
+ adjustable.greenShift = scaleToAngles(myGreenShift);
+ adjustable.blueShift = scaleToAngles(myBlueShift);
adjustable.hue = scaleTo100(myHue);
adjustable.saturation = scaleTo100(mySaturation);
@@ -371,10 +438,9 @@ void PaletteHandler::generateCustomPalette(ConsoleTiming timing)
constexpr int NUM_LUMA = 8;
constexpr float SATURATION = 0.25F; // default saturation
- float color[NUM_CHROMA][2] = {{0.0F}};
-
if(timing == ConsoleTiming::ntsc)
{
+ vector2d IQ[NUM_CHROMA];
// YIQ is YUV shifted by 33°
constexpr float offset = 33 * BSPF::PI_f / 180;
const float shift = myPhaseNTSC * BSPF::PI_f / 180;
@@ -382,22 +448,23 @@ void PaletteHandler::generateCustomPalette(ConsoleTiming timing)
// color 0 is grayscale
for(int chroma = 1; chroma < NUM_CHROMA; chroma++)
{
- color[chroma][0] = SATURATION * sinf(offset + shift * (chroma - 1));
- color[chroma][1] = SATURATION * cosf(offset + shift * (chroma - 1) - BSPF::PI_f);
+ IQ[chroma] = vector2d(SATURATION * sinf(offset + shift * (chroma - 1)),
+ SATURATION * cosf(offset + shift * (chroma - 1) - BSPF::PI_f));
}
+ const vector2d IQR = scale(rotate(vector2d(+0.956F, +0.621F), myRedShift), myRedScale);
+ const vector2d IQG = scale(rotate(vector2d(-0.272F, -0.647F), myGreenShift), myGreenScale);
+ const vector2d IQB = scale(rotate(vector2d(-1.106F, +1.703F), myBlueShift), myBlueScale);
for(int chroma = 0; chroma < NUM_CHROMA; chroma++)
{
- const float I = color[chroma][0];
- const float Q = color[chroma][1];
-
for(int luma = 0; luma < NUM_LUMA; luma++)
{
const float Y = 0.05F + luma / 8.24F; // 0.05..~0.90
- float R = Y + 0.956F * I + 0.621F * Q;
- float G = Y - 0.272F * I - 0.647F * Q;
- float B = Y - 1.106F * I + 1.703F * Q;
+ float R = Y + dotProduct(IQ[chroma], IQR);
+ float G = Y + dotProduct(IQ[chroma], IQG);
+ float B = Y + dotProduct(IQ[chroma], IQB);
+
if(R < 0) R = 0;
if(G < 0) G = 0;
@@ -420,35 +487,37 @@ void PaletteHandler::generateCustomPalette(ConsoleTiming timing)
constexpr float offset = BSPF::PI_f;
const float shift = myPhasePAL * BSPF::PI_f / 180;
constexpr float fixedShift = 22.5F * BSPF::PI_f / 180;
+ vector2d UV[NUM_CHROMA];
// colors 0, 1, 14 and 15 are grayscale
for(int chroma = 2; chroma < NUM_CHROMA - 2; chroma++)
{
int idx = NUM_CHROMA - 1 - chroma;
- color[idx][0] = SATURATION * sinf(offset - fixedShift * chroma);
+
+ UV[idx].x = SATURATION * sinf(offset - fixedShift * chroma);
if ((idx & 1) == 0)
- color[idx][1] = SATURATION * sinf(offset - shift * (chroma - 3.5F) / 2.F);
+ UV[idx].y = SATURATION * sinf(offset - shift * (chroma - 3.5F) / 2.F);
else
- color[idx][1] = SATURATION * -sinf(offset - shift * chroma / 2.F);
+ UV[idx].y = SATURATION * -sinf(offset - shift * chroma / 2.F);
}
+ // Most sources
+ const vector2d UVR = scale(rotate(vector2d( 0.000F, +1.403F), myRedShift), myRedScale);
+ const vector2d UVG = scale(rotate(vector2d(-0.344F, -0.714F), myGreenShift), myGreenScale);
+ const vector2d UVB = scale(rotate(vector2d(+0.714F, 0.000F), myBlueShift), myBlueScale);
+ // German Wikipedia, huh???
+ //float R = Y + 1 / 0.877 * V;
+ //float B = Y + 1 / 0.493 * U;
+ //float G = 1.704 * Y - 0.590 * R - 0.194 * B;
for(int chroma = 0; chroma < NUM_CHROMA; chroma++)
{
- const float U = color[chroma][0];
- const float V = color[chroma][1];
-
for(int luma = 0; luma < NUM_LUMA; luma++)
{
const float Y = 0.05F + luma / 8.24F; // 0.05..~0.90
- // Most sources
- float R = Y + 1.403F * V;
- float G = Y - 0.344F * U - 0.714F * V;
- float B = Y + 1.770F * U;
- // German Wikipedia, huh???
- //float B = Y + 1 / 0.493 * U;
- //float R = Y + 1 / 0.877 * V;
- //float G = 1.704 * Y - 0.590 * R - 0.194 * B;
+ float R = Y + dotProduct(UV[chroma], UVR);
+ float G = Y + dotProduct(UV[chroma], UVG);
+ float B = Y + dotProduct(UV[chroma], UVB);
if(R < 0) R = 0.0;
if(G < 0) G = 0.0;
@@ -491,6 +560,28 @@ void PaletteHandler::adjustHueSaturation(int& R, int& G, int& B, float H, float
B = BSPF::clamp(b, 0.F, 255.F);
}
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+PaletteHandler::vector2d PaletteHandler::rotate(const PaletteHandler::vector2d& vec, float angle) const
+{
+ const float r = angle * BSPF::PI_f / 180;
+
+ return PaletteHandler::vector2d(vec.x * cosf(r) - vec.y * sinf(r),
+ vec.x * sinf(r) + vec.y * cosf(r));
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+PaletteHandler::vector2d PaletteHandler::scale(const PaletteHandler::vector2d& vec, float factor) const
+{
+ return PaletteHandler::vector2d(vec.x * factor, vec.y * factor);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+float PaletteHandler::dotProduct(const PaletteHandler::vector2d& vec1,
+ const PaletteHandler::vector2d& vec2) const
+{
+ return vec1.x * vec2.x + vec1.y * vec2.y;
+}
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const PaletteArray PaletteHandler::ourNTSCPalette = {
0x000000, 0, 0x4a4a4a, 0, 0x6f6f6f, 0, 0x8e8e8e, 0,
diff --git a/src/common/PaletteHandler.hxx b/src/common/PaletteHandler.hxx
index 956fcf9b3..66da36715 100644
--- a/src/common/PaletteHandler.hxx
+++ b/src/common/PaletteHandler.hxx
@@ -35,20 +35,32 @@ class PaletteHandler
// Phase shift default and limits
static constexpr float DEF_NTSC_SHIFT = 26.2F;
static constexpr float DEF_PAL_SHIFT = 31.3F; // ~= 360 / 11.5
- static constexpr float MAX_SHIFT = 4.5F;
+ static constexpr float MAX_PHASE_SHIFT = 4.5F;
+ static constexpr float DEF_RGB_SHIFT = 0.0F;
+ static constexpr float MAX_RGB_SHIFT = 22.5F;
enum Adjustables {
PHASE_SHIFT,
+ RED_SCALE,
+ GREEN_SCALE,
+ BLUE_SCALE,
+ RED_SHIFT,
+ GREEN_SHIFT,
+ BLUE_SHIFT,
HUE,
SATURATION,
CONTRAST,
BRIGHTNESS,
- GAMMA
+ GAMMA,
+ CUSTOM_START = PHASE_SHIFT,
+ CUSTOM_END = BLUE_SHIFT,
};
// Externally used adjustment parameters
struct Adjustable {
- float phaseNtsc{0.F}, phasePal{0.F};
+ float phaseNtsc{0.F}, phasePal{0.F},
+ redScale{0.F}, greenScale{0.F}, blueScale{0.F},
+ redShift{0.F}, greenShift{0.F}, blueShift{0.F};
uInt32 hue{0}, saturation{0}, contrast{0}, brightness{0}, gamma{0};
};
@@ -108,6 +120,7 @@ class PaletteHandler
*/
void setPalette();
+
private:
static constexpr char DEGREE = 0x1c;
@@ -121,12 +134,45 @@ class PaletteHandler
MaxType = Custom
};
+ struct vector2d {
+ float x;
+ float y;
+
+ explicit vector2d()
+ : x(0.F), y(0.F) { }
+ explicit vector2d(float _x, float _y)
+ : x(_x), y(_y) { }
+ };
+
+ /**
+ Convert RGB adjustables from/to 100% scale
+ */
+ static constexpr float scaleRGBFrom100(float x) { return x / 50.F; }
+ static constexpr uInt32 scaleRGBTo100(float x) { return uInt32(50.0001F * (x - 0.F)); }
+
+ /**
+ Convert angles
+ */
+ static constexpr float scaleFromAngles(float x) { return x / 10.F; }
+ static constexpr Int32 scaleToAngles(float x) { return uInt32(10.F * x); }
+
/**
Convert adjustables from/to 100% scale
*/
static constexpr float scaleFrom100(float x) { return (x / 50.F) - 1.F; }
static constexpr uInt32 scaleTo100(float x) { return uInt32(50.0001F * (x + 1.F)); }
+ /**
+ Check for 'Custom' palette only adjustables
+ */
+ bool isCustomAdjustable() const;
+
+ bool isPhaseShift() const;
+
+ bool isRGBScale() const;
+
+ bool isRGBShift() const;
+
/**
Convert palette settings name to enumeration.
@@ -186,6 +232,21 @@ class PaletteHandler
*/
void adjustHueSaturation(int& R, int& G, int& B, float H, float S);
+ /**
+ Rotate a 2D vector.
+ */
+ vector2d rotate(const vector2d& vec, float angle) const;
+
+ /**
+ Scale a 2D vector.
+ */
+ vector2d scale(const vector2d& vec, float factor) const;
+
+ /**
+ Get the dot product of two 2D vectors.
+ */
+ float dotProduct(const vector2d& vec1, const vector2d& vec2) const;
+
/**
Loads a user-defined palette file (from OSystem::paletteFile), filling the
appropriate user-defined palette arrays.
@@ -193,7 +254,7 @@ class PaletteHandler
void loadUserPalette();
private:
- static constexpr int NUM_ADJUSTABLES = 6;
+ static constexpr int NUM_ADJUSTABLES = 12;
OSystem& myOSystem;
@@ -207,6 +268,12 @@ class PaletteHandler
const std::array myAdjustables =
{ {
{ "phase shift", nullptr },
+ { "red scale", &myRedScale },
+ { "green scale", &myGreenScale },
+ { "blue scale", &myBlueScale },
+ { "red shift", &myRedShift },
+ { "green shift", &myGreenShift },
+ { "blue shift", &myBlueShift },
{ "hue", &myHue },
{ "saturation", &mySaturation },
{ "contrast", &myContrast },
@@ -217,6 +284,14 @@ class PaletteHandler
// NTSC and PAL color phase shifts
float myPhaseNTSC{DEF_NTSC_SHIFT};
float myPhasePAL{DEF_PAL_SHIFT};
+ // Color intensities
+ float myRedScale{1.0F};
+ float myGreenScale{1.0F};
+ float myBlueScale{1.0F};
+ // Color shifts
+ float myRedShift{0.0F};
+ float myGreenShift{0.0F};
+ float myBlueShift{0.0F};
// range -1.0 to +1.0 (as in AtariNTSC)
// Basic parameters
float myHue{0.0F}; // -1 = -180 degrees +1 = +180 degrees
diff --git a/src/emucore/EventHandler.cxx b/src/emucore/EventHandler.cxx
index a5b0d5b1f..f0da1226c 100644
--- a/src/emucore/EventHandler.cxx
+++ b/src/emucore/EventHandler.cxx
@@ -377,7 +377,9 @@ AdjustFunction EventHandler::cycleAdjustSetting(int direction)
#ifdef ADAPTABLE_REFRESH_SUPPORT
|| (myAdjustSetting == AdjustSetting::ADAPT_REFRESH && !isFullScreen)
#endif
- || (myAdjustSetting == AdjustSetting::PALETTE_PHASE && !isCustomPalette)
+ || (myAdjustSetting >= AdjustSetting::PALETTE_PHASE
+ && myAdjustSetting <= AdjustSetting::PALETTE_BLUE_SHIFT
+ && !isCustomPalette)
|| (myAdjustSetting >= AdjustSetting::NTSC_SHARPNESS
&& myAdjustSetting <= AdjustSetting::NTSC_BLEEDING
&& !isCustomFilter);
@@ -425,6 +427,18 @@ AdjustFunction EventHandler::getAdjustSetting(AdjustSetting setting)
std::bind(&PaletteHandler::cyclePalette, &myOSystem.frameBuffer().tiaSurface().paletteHandler(), _1),
std::bind(&PaletteHandler::changeAdjustable, &myOSystem.frameBuffer().tiaSurface().paletteHandler(),
PaletteHandler::PHASE_SHIFT, _1),
+ std::bind(&PaletteHandler::changeAdjustable, &myOSystem.frameBuffer().tiaSurface().paletteHandler(),
+ PaletteHandler::RED_SCALE, _1),
+ std::bind(&PaletteHandler::changeAdjustable, &myOSystem.frameBuffer().tiaSurface().paletteHandler(),
+ PaletteHandler::RED_SHIFT, _1),
+ std::bind(&PaletteHandler::changeAdjustable, &myOSystem.frameBuffer().tiaSurface().paletteHandler(),
+ PaletteHandler::GREEN_SCALE, _1),
+ std::bind(&PaletteHandler::changeAdjustable, &myOSystem.frameBuffer().tiaSurface().paletteHandler(),
+ PaletteHandler::GREEN_SHIFT, _1),
+ std::bind(&PaletteHandler::changeAdjustable, &myOSystem.frameBuffer().tiaSurface().paletteHandler(),
+ PaletteHandler::BLUE_SCALE, _1),
+ std::bind(&PaletteHandler::changeAdjustable, &myOSystem.frameBuffer().tiaSurface().paletteHandler(),
+ PaletteHandler::BLUE_SHIFT, _1),
std::bind(&PaletteHandler::changeAdjustable, &myOSystem.frameBuffer().tiaSurface().paletteHandler(),
PaletteHandler::HUE, _1),
std::bind(&PaletteHandler::changeAdjustable, &myOSystem.frameBuffer().tiaSurface().paletteHandler(),
diff --git a/src/emucore/EventHandler.hxx b/src/emucore/EventHandler.hxx
index e437de7b4..4e591b869 100644
--- a/src/emucore/EventHandler.hxx
+++ b/src/emucore/EventHandler.hxx
@@ -431,6 +431,12 @@ class EventHandler
// Palette adjustables
PALETTE,
PALETTE_PHASE,
+ PALETTE_RED_SCALE,
+ PALETTE_RED_SHIFT,
+ PALETTE_GREEN_SCALE,
+ PALETTE_GREEN_SHIFT,
+ PALETTE_BLUE_SCALE,
+ PALETTE_BLUE_SHIFT,
PALETTE_HUE,
PALETTE_SATURATION,
PALETTE_CONTRAST,
diff --git a/src/emucore/PointingDevice.cxx b/src/emucore/PointingDevice.cxx
index accf00a04..cbd7f89c9 100644
--- a/src/emucore/PointingDevice.cxx
+++ b/src/emucore/PointingDevice.cxx
@@ -81,6 +81,7 @@ void PointingDevice::update()
return;
// Update horizontal direction
+ cerr << myEvent.get(Event::MouseAxisXMove) << ", " << myHCounterRemainder << endl;
updateDirection( myEvent.get(Event::MouseAxisXMove), myHCounterRemainder,
myTrackBallLeft, myTrackBallLinesH, myScanCountH, myFirstScanOffsetH);
diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx
index 08898c428..f7fa27ae8 100644
--- a/src/emucore/Settings.cxx
+++ b/src/emucore/Settings.cxx
@@ -62,6 +62,13 @@ Settings::Settings()
setPermanent("palette", PaletteHandler::SETTING_STANDARD);
setPermanent("pal.phase_ntsc", "26.2");
setPermanent("pal.phase_pal", "31.3");
+ setPermanent("pal.red_scale", "0.0");
+ setPermanent("pal.green_scale", "0.0");
+ setPermanent("pal.blue_scale", "0.0");
+ setPermanent("pal.red_shift", "0.0");
+ setPermanent("pal.green_shift", "0.0");
+ setPermanent("pal.blue_shift", "0.0");
+
setPermanent("pal.contrast", "0.0");
setPermanent("pal.brightness", "0.0");
setPermanent("pal.hue", "0.0");
@@ -415,16 +422,24 @@ void Settings::usage() const
<< " -center <1|0> Centers game window in windowed modes\n"
<< " -windowedpos Sets the window position in windowed emulator mode\n"
<< " -display Sets the display for Stella's emulator\n"
- << " -palette \n"
- << " -pal.phase_ntsc Phase shift for NTSC 'custom' palette\n"
- << " -pal.phase_pal Phase shift for PAL 'custom' palette\n"
- << " -pal.hue <-1.0 - 1.0> Adjust hue for current palette\n"
- << " -pal.saturation <-1.0 - 1.0> Adjust saturation of current palette\n"
- << " -pal.contrast <-1.0 - 1.0> Adjust contrast of current palette\n"
- << " -pal.brightness <-1.0 - 1.0> Adjust brightness of current palette\n"
- << " -pal.gamma <-1.0 - 1.0> Adjust gamma of current palette\n"
+ << endl
+ << " -palette \n"
+ << " -pal.phase_ntsc Phase shift for NTSC 'custom' palette\n"
+ << " -pal.phase_pal Phase shift for PAL 'custom' palette\n"
+ << " -pal.red_scale <-1.0 - 1.0> Adjust red scale for 'custom' palette\n"
+ << " -pal.red_shift <-1.0 - 1.0> Adjust red shift for 'custom' palette\n"
+ << " -pal.green_scale <-1.0 - 1.0> Adjust green scale for 'custom' palette\n"
+ << " -pal.green_shift <-1.0 - 1.0> Adjust green shift for 'custom' palette\n"
+ << " -pal.blue_scale <-1.0 - 1.0> Adjust blue scale for 'custom' palette\n"
+ << " -pal.blue_shift <-1.0 - 1.0> Adjust blue shift for 'custom' palette\n"
+ << " -pal.hue <-1.0 - 1.0> Adjust hue for current palette\n"
+ << " -pal.saturation <-1.0 - 1.0> Adjust saturation of current palette\n"
+ << " -pal.contrast <-1.0 - 1.0> Adjust contrast of current palette\n"
+ << " -pal.brightness <-1.0 - 1.0> Adjust brightness of current palette\n"
+ << " -pal.gamma <-1.0 - 1.0> Adjust gamma of current palette\n"
+ << endl
<< " -speed Run emulation at the given speed\n"
<< " -turbo <1|0> Enable 'Turbo' mode for maximum emulation speed\n"
<< " -uimessages <1|0> Show onscreen UI messages for different events\n"
diff --git a/src/gui/VideoAudioDialog.cxx b/src/gui/VideoAudioDialog.cxx
index be08f83f8..01297b62b 100644
--- a/src/gui/VideoAudioDialog.cxx
+++ b/src/gui/VideoAudioDialog.cxx
@@ -223,25 +223,82 @@ void VideoAudioDialog::addPaletteTab()
const int swidth = myTIAPalette->getWidth() - lwidth;
const int plWidth = _font.getStringWidth("NTSC phase ");
const int pswidth = swidth - INDENT + lwidth - plWidth;
+ xpos += INDENT;
myPhaseShiftNtsc =
- new SliderWidget(myTab, _font, xpos + INDENT, ypos-1, pswidth, lineHeight,
+ new SliderWidget(myTab, _font, xpos, ypos - 1, pswidth, lineHeight,
"NTSC phase", plWidth, kNtscShiftChanged, fontWidth * 5);
- myPhaseShiftNtsc->setMinValue((PaletteHandler::DEF_NTSC_SHIFT - PaletteHandler::MAX_SHIFT) * 10);
- myPhaseShiftNtsc->setMaxValue((PaletteHandler::DEF_NTSC_SHIFT + PaletteHandler::MAX_SHIFT) * 10);
+ myPhaseShiftNtsc->setMinValue((PaletteHandler::DEF_NTSC_SHIFT - PaletteHandler::MAX_PHASE_SHIFT) * 10);
+ myPhaseShiftNtsc->setMaxValue((PaletteHandler::DEF_NTSC_SHIFT + PaletteHandler::MAX_PHASE_SHIFT) * 10);
myPhaseShiftNtsc->setTickmarkIntervals(4);
wid.push_back(myPhaseShiftNtsc);
ypos += lineHeight + VGAP;
myPhaseShiftPal =
- new SliderWidget(myTab, _font, xpos + INDENT, ypos-1, pswidth, lineHeight,
+ new SliderWidget(myTab, _font, xpos, ypos - 1, pswidth, lineHeight,
"PAL phase", plWidth, kPalShiftChanged, fontWidth * 5);
- myPhaseShiftPal->setMinValue((PaletteHandler::DEF_PAL_SHIFT - PaletteHandler::MAX_SHIFT) * 10);
- myPhaseShiftPal->setMaxValue((PaletteHandler::DEF_PAL_SHIFT + PaletteHandler::MAX_SHIFT) * 10);
+ myPhaseShiftPal->setMinValue((PaletteHandler::DEF_PAL_SHIFT - PaletteHandler::MAX_PHASE_SHIFT) * 10);
+ myPhaseShiftPal->setMaxValue((PaletteHandler::DEF_PAL_SHIFT + PaletteHandler::MAX_PHASE_SHIFT) * 10);
myPhaseShiftPal->setTickmarkIntervals(4);
wid.push_back(myPhaseShiftPal);
ypos += lineHeight + VGAP;
+ const int rgblWidth = _font.getStringWidth("R ");
+ const int rgbsWidth = (myTIAPalette->getWidth() - INDENT - rgblWidth - fontWidth * 5) / 2;
+
+ myTVRedScale =
+ new SliderWidget(myTab, _font, xpos, ypos - 1, rgbsWidth, lineHeight,
+ "R", rgblWidth, kPaletteUpdated, fontWidth * 4, "%");
+ myTVRedScale->setMinValue(0);
+ myTVRedScale->setMaxValue(100);
+ myTVRedScale->setTickmarkIntervals(2);
+ wid.push_back(myTVRedScale);
+
+ const int xposr = myTIAPalette->getRight() - rgbsWidth;
+ myTVRedShift =
+ new SliderWidget(myTab, _font, xposr, ypos - 1, rgbsWidth, lineHeight,
+ "", 0, kRedShiftChanged, fontWidth * 6);
+ myTVRedShift->setMinValue((PaletteHandler::DEF_RGB_SHIFT - PaletteHandler::MAX_RGB_SHIFT) * 10);
+ myTVRedShift->setMaxValue((PaletteHandler::DEF_RGB_SHIFT + PaletteHandler::MAX_RGB_SHIFT) * 10);
+ myTVRedShift->setTickmarkIntervals(2);
+ wid.push_back(myTVRedShift);
+ ypos += lineHeight + VGAP;
+
+ myTVGreenScale =
+ new SliderWidget(myTab, _font, xpos, ypos - 1, rgbsWidth, lineHeight,
+ "G", rgblWidth, kPaletteUpdated, fontWidth * 4, "%");
+ myTVGreenScale->setMinValue(0);
+ myTVGreenScale->setMaxValue(100);
+ myTVGreenScale->setTickmarkIntervals(2);
+ wid.push_back(myTVGreenScale);
+
+ myTVGreenShift =
+ new SliderWidget(myTab, _font, xposr, ypos - 1, rgbsWidth, lineHeight,
+ "", 0, kGreenShiftChanged, fontWidth * 6);
+ myTVGreenShift->setMinValue((PaletteHandler::DEF_RGB_SHIFT - PaletteHandler::MAX_RGB_SHIFT) * 10);
+ myTVGreenShift->setMaxValue((PaletteHandler::DEF_RGB_SHIFT + PaletteHandler::MAX_RGB_SHIFT) * 10);
+ myTVGreenShift->setTickmarkIntervals(2);
+ wid.push_back(myTVGreenShift);
+ ypos += lineHeight + VGAP;
+
+ myTVBlueScale =
+ new SliderWidget(myTab, _font, xpos, ypos - 1, rgbsWidth, lineHeight,
+ "B", rgblWidth, kPaletteUpdated, fontWidth * 4, "%");
+ myTVBlueScale->setMinValue(0);
+ myTVBlueScale->setMaxValue(100);
+ myTVBlueScale->setTickmarkIntervals(2);
+ wid.push_back(myTVBlueScale);
+
+ myTVBlueShift =
+ new SliderWidget(myTab, _font, xposr, ypos - 1, rgbsWidth, lineHeight,
+ "", 0, kBlueShiftChanged, fontWidth * 6);
+ myTVBlueShift->setMinValue((PaletteHandler::DEF_RGB_SHIFT - PaletteHandler::MAX_RGB_SHIFT) * 10);
+ myTVBlueShift->setMaxValue((PaletteHandler::DEF_RGB_SHIFT + PaletteHandler::MAX_RGB_SHIFT) * 10);
+ myTVBlueShift->setTickmarkIntervals(2);
+ wid.push_back(myTVBlueShift);
+ ypos += lineHeight + VGAP;
+ xpos -= INDENT;
+
CREATE_CUSTOM_SLIDERS(Hue, "Hue ", kPaletteUpdated)
CREATE_CUSTOM_SLIDERS(Satur, "Saturation ", kPaletteUpdated)
CREATE_CUSTOM_SLIDERS(Contrast, "Contrast ", kPaletteUpdated)
@@ -524,6 +581,12 @@ void VideoAudioDialog::loadConfig()
instance().frameBuffer().tiaSurface().paletteHandler().getAdjustables(myPaletteAdj);
myPhaseShiftNtsc->setValue(myPaletteAdj.phaseNtsc);
myPhaseShiftPal->setValue(myPaletteAdj.phasePal);
+ myTVRedScale->setValue(myPaletteAdj.redScale);
+ myTVRedShift->setValue(myPaletteAdj.redShift);
+ myTVGreenScale->setValue(myPaletteAdj.greenScale);
+ myTVGreenShift->setValue(myPaletteAdj.greenShift);
+ myTVBlueScale->setValue(myPaletteAdj.blueScale);
+ myTVBlueShift->setValue(myPaletteAdj.blueShift);
myTVHue->setValue(myPaletteAdj.hue);
myTVBright->setValue(myPaletteAdj.brightness);
myTVContrast->setValue(myPaletteAdj.contrast);
@@ -760,6 +823,12 @@ void VideoAudioDialog::setDefaults()
myTIAPalette->setSelected(PaletteHandler::SETTING_STANDARD);
myPhaseShiftNtsc->setValue(PaletteHandler::DEF_NTSC_SHIFT * 10);
myPhaseShiftPal->setValue(PaletteHandler::DEF_PAL_SHIFT * 10);
+ myTVRedScale->setValue(50);
+ myTVRedShift->setValue(PaletteHandler::DEF_RGB_SHIFT);
+ myTVGreenScale->setValue(50);
+ myTVGreenShift->setValue(PaletteHandler::DEF_RGB_SHIFT);
+ myTVBlueScale->setValue(50);
+ myTVBlueShift->setValue(PaletteHandler::DEF_RGB_SHIFT);
myTVHue->setValue(50);
myTVSatur->setValue(50);
myTVContrast->setValue(50);
@@ -847,6 +916,23 @@ void VideoAudioDialog::handlePaletteChange()
myPhaseShiftNtsc->setEnabled(enable);
myPhaseShiftPal->setEnabled(enable);
+ myTVRedScale->setEnabled(enable);
+ myTVRedShift->setEnabled(enable);
+ myTVGreenScale->setEnabled(enable);
+ myTVGreenShift->setEnabled(enable);
+ myTVBlueScale->setEnabled(enable);
+ myTVBlueShift->setEnabled(enable);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void VideoAudioDialog::handleShiftChanged(SliderWidget* widget)
+{
+ std::ostringstream ss;
+
+ ss << std::setw(4) << std::fixed << std::setprecision(1)
+ << (0.1 * (widget->getValue())) << DEGREE;
+ widget->setValueLabel(ss.str());
+ handlePaletteUpdate();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -859,6 +945,12 @@ void VideoAudioDialog::handlePaletteUpdate()
PaletteHandler::Adjustable paletteAdj;
paletteAdj.phaseNtsc = myPhaseShiftNtsc->getValue();
paletteAdj.phasePal = myPhaseShiftPal->getValue();
+ paletteAdj.redScale = myTVRedScale->getValue();
+ paletteAdj.redShift = myTVRedShift->getValue();
+ paletteAdj.greenScale = myTVGreenScale->getValue();
+ paletteAdj.greenShift = myTVGreenShift->getValue();
+ paletteAdj.blueScale = myTVBlueScale->getValue();
+ paletteAdj.blueShift = myTVBlueShift->getValue();
paletteAdj.hue = myTVHue->getValue();
paletteAdj.saturation = myTVSatur->getValue();
paletteAdj.contrast = myTVContrast->getValue();
@@ -931,25 +1023,25 @@ void VideoAudioDialog::handleCommand(CommandSender* sender, int cmd,
break;
case kNtscShiftChanged:
- {
- std::ostringstream ss;
-
- ss << std::setw(4) << std::fixed << std::setprecision(1)
- << (0.1 * abs(myPhaseShiftNtsc->getValue())) << DEGREE;
- myPhaseShiftNtsc->setValueLabel(ss.str());
- handlePaletteUpdate();
+ handleShiftChanged(myPhaseShiftNtsc);
break;
- }
+
case kPalShiftChanged:
- {
- std::ostringstream ss;
-
- ss << std::setw(4) << std::fixed << std::setprecision(1)
- << (0.1 * abs(myPhaseShiftPal->getValue())) << DEGREE;
- myPhaseShiftPal->setValueLabel(ss.str());
- handlePaletteUpdate();
+ handleShiftChanged(myPhaseShiftPal);
break;
- }
+
+ case kRedShiftChanged:
+ handleShiftChanged(myTVRedShift);
+ break;
+
+ case kGreenShiftChanged:
+ handleShiftChanged(myTVGreenShift);
+ break;
+
+ case kBlueShiftChanged:
+ handleShiftChanged(myTVBlueShift);
+ break;
+
case kVSizeChanged:
{
int adjust = myVSizeAdjust->getValue();
diff --git a/src/gui/VideoAudioDialog.hxx b/src/gui/VideoAudioDialog.hxx
index 3581635b4..1d0e8a637 100644
--- a/src/gui/VideoAudioDialog.hxx
+++ b/src/gui/VideoAudioDialog.hxx
@@ -53,6 +53,7 @@ class VideoAudioDialog : public Dialog
void handleTVModeChange(NTSCFilter::Preset);
void loadTVAdjustables(NTSCFilter::Preset preset);
void handlePaletteChange();
+ void handleShiftChanged(SliderWidget* widget);
void handlePaletteUpdate();
void handleFullScreenChange();
void handleOverscanChange();
@@ -105,6 +106,12 @@ class VideoAudioDialog : public Dialog
PopUpWidget* myTIAPalette{nullptr};
SliderWidget* myPhaseShiftNtsc{nullptr};
SliderWidget* myPhaseShiftPal{nullptr};
+ SliderWidget* myTVRedScale{nullptr};
+ SliderWidget* myTVRedShift{nullptr};
+ SliderWidget* myTVGreenScale{nullptr};
+ SliderWidget* myTVGreenShift{nullptr};
+ SliderWidget* myTVBlueScale{nullptr};
+ SliderWidget* myTVBlueShift{nullptr};
SliderWidget* myTVHue{nullptr};
SliderWidget* myTVSatur{nullptr};
SliderWidget* myTVBright{nullptr};
@@ -138,6 +145,9 @@ class VideoAudioDialog : public Dialog
kPaletteChanged = 'VDpl',
kNtscShiftChanged = 'VDns',
kPalShiftChanged = 'VDps',
+ kRedShiftChanged = 'VDrs',
+ kGreenShiftChanged = 'VDgs',
+ kBlueShiftChanged = 'VDbs',
kPaletteUpdated = 'VDpu',
kTVModeChanged = 'VDtv',
diff --git a/src/gui/WhatsNewDialog.cxx b/src/gui/WhatsNewDialog.cxx
index 9c37701d2..e40d8e622 100644
--- a/src/gui/WhatsNewDialog.cxx
+++ b/src/gui/WhatsNewDialog.cxx
@@ -47,8 +47,9 @@ WhatsNewDialog::WhatsNewDialog(OSystem& osystem, DialogContainer& parent, const
add(ypos, "fixed bug with launcher not remembering last selected ROM");
#else
add(ypos, "added basic text cut/copy/paste to UI");
- add(ypos, "fixed fullscreen mode, aspect correction and pixel-exact snapshots");
+ add(ypos, "added color parameters to 'Custom' palette");
add(ypos, "improved AVox-USB adaptor autodetection");
+ add(ypos, "fixed fullscreen mode, aspect correction and pixel-exact snapshots");
add(ypos, "fixed crash with SaveKey ROMs (EEPROM file issues)");
add(ypos, "fixed bug with launcher not remembering last selected ROM");
add(ypos, ELLIPSIS + " (for a complete list see 'docs/Changes.txt')");