added bezel borders

This commit is contained in:
thrust26 2023-08-23 17:47:58 +02:00
parent 62b3ef38d8
commit 6b1191a16e
7 changed files with 158 additions and 83 deletions

View File

@ -33,7 +33,7 @@ void VideoModeHandler::setDisplaySize(const Common::Size& display, Int32 fsIndex
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const VideoModeHandler::Mode& const VideoModeHandler::Mode&
VideoModeHandler::buildMode(const Settings& settings, bool inTIAMode, bool showBezel) VideoModeHandler::buildMode(const Settings& settings, bool inTIAMode, Mode::BezelInfo bezelInfo)
{ {
const bool windowedRequested = myFSIndex == -1; const bool windowedRequested = myFSIndex == -1;
@ -49,7 +49,7 @@ const VideoModeHandler::Mode&
// Image and screen (aka window) dimensions are the same // Image and screen (aka window) dimensions are the same
// Overscan is not applicable in this mode // Overscan is not applicable in this mode
myMode = Mode(myImage.w * zoom, myImage.h * zoom, Mode::Stretch::Fill, myMode = Mode(myImage.w * zoom, myImage.h * zoom, Mode::Stretch::Fill,
myFSIndex, desc.str(), zoom, showBezel); myFSIndex, desc.str(), zoom, bezelInfo);
} }
else else
{ {
@ -57,7 +57,7 @@ const VideoModeHandler::Mode&
// First calculate maximum zoom that keeps aspect ratio // First calculate maximum zoom that keeps aspect ratio
// Note: We are assuming a 16:9 bezel image here // Note: We are assuming a 16:9 bezel image here
const float bezelScaleW = showBezel ? (16.F / 9.F) / (4.F / 3.F) : 1; const float bezelScaleW = bezelInfo.enabled ? bezelInfo.scaleW() : 1;
const float scaleX = myImage.w / (myDisplay.w / bezelScaleW), const float scaleX = myImage.w / (myDisplay.w / bezelScaleW),
scaleY = static_cast<float>(myImage.h) / myDisplay.h; scaleY = static_cast<float>(myImage.h) / myDisplay.h;
float zoom = 1.F / std::max(scaleX, scaleY); float zoom = 1.F / std::max(scaleX, scaleY);
@ -73,7 +73,7 @@ const VideoModeHandler::Mode&
myDisplay.w, myDisplay.h, myDisplay.w, myDisplay.h,
Mode::Stretch::Preserve, myFSIndex, Mode::Stretch::Preserve, myFSIndex,
"Fullscreen: Preserve aspect, no stretch", "Fullscreen: Preserve aspect, no stretch",
zoom, overscan, showBezel); zoom, overscan, bezelInfo);
} }
else // ignore aspect, use all space else // ignore aspect, use all space
{ {
@ -81,7 +81,7 @@ const VideoModeHandler::Mode&
myDisplay.w, myDisplay.h, myDisplay.w, myDisplay.h,
Mode::Stretch::Fill, myFSIndex, Mode::Stretch::Fill, myFSIndex,
"Fullscreen: Ignore aspect, full stretch", "Fullscreen: Ignore aspect, full stretch",
zoom, overscan, showBezel); zoom, overscan, bezelInfo);
} }
} }
} }
@ -100,15 +100,15 @@ const VideoModeHandler::Mode&
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, Stretch smode, VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, Stretch smode,
Int32 fsindex, string_view desc, Int32 fsindex, string_view desc,
float zoomLevel, bool showBezel) float zoomLevel, BezelInfo bezelInfo)
: Mode(iw, ih, iw, ih, smode, fsindex, desc, zoomLevel, 1.F, showBezel) : Mode(iw, ih, iw, ih, smode, fsindex, desc, zoomLevel, 1.F, bezelInfo)
{ {
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh,
Stretch smode, Int32 fsindex, string_view desc, Stretch smode, Int32 fsindex, string_view desc,
float zoomLevel, float overscan, bool showBezel) float zoomLevel, float overscan, BezelInfo bezelInfo)
: screenS{sw, sh}, : screenS{sw, sh},
stretch{smode}, stretch{smode},
description{desc}, description{desc},
@ -116,15 +116,17 @@ VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh,
fsIndex{fsindex} fsIndex{fsindex}
{ {
// Note: We are assuming a 16:9 bezel image here // Note: We are assuming a 16:9 bezel image here
const float bezelScaleW = showBezel ? (16.F / 9.F) / (4.F / 3.F) : 1; const float bezelScaleW = bezelInfo.enabled ? bezelInfo.scaleW() : 1;
// Now resize based on windowed/fullscreen mode and stretch factor // Now resize based on windowed/fullscreen mode and stretch factor
if(fsIndex != -1) // fullscreen mode if(fsIndex != -1) // fullscreen mode
{ {
switch(stretch) switch(stretch)
{ {
case Stretch::Preserve: case Stretch::Preserve:
iw *= overscan; iw = (iw - bezelInfo.hBorder() * zoomLevel) * overscan;
ih *= overscan; ih = (ih - bezelInfo.vBorder() * zoomLevel) * overscan;
//iw *= overscan;
//ih *= overscan;
break; break;
case Stretch::Fill: case Stretch::Fill:

View File

@ -38,6 +38,23 @@ class VideoModeHandler
Fill, // Stretch to fill all available space Fill, // Stretch to fill all available space
None // No stretching (1x zoom) None // No stretching (1x zoom)
}; };
struct BezelInfo
{
bool enabled{false};
bool windowedMode{false};
uInt32 topBorder{0};
uInt32 bottomBorder{0};
BezelInfo() = default;
BezelInfo(bool _enabled, bool _windowedMode, uInt32 _topBorder, uInt32 _bottomBorder)
: enabled{_enabled}, windowedMode{_windowedMode},
topBorder{_topBorder}, bottomBorder(_bottomBorder) { }
uInt32 vBorder() { return topBorder + bottomBorder; }
uInt32 hBorder() { return (topBorder + bottomBorder) * 4.F / 3.F; }
// Scale width from 4:3 into 16:9
float scaleW(float width = 1.F) { return width * (16.F / 9.F) / (4.F / 3.F); }
};
Common::Rect imageR; Common::Rect imageR;
Common::Rect screenR; Common::Rect screenR;
@ -50,9 +67,13 @@ class VideoModeHandler
Mode() = default; Mode() = default;
Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, Stretch smode, Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, Stretch smode,
Int32 fsindex = -1, string_view desc = "", Int32 fsindex = -1, string_view desc = "",
float zoomLevel = 1.F, float overscan = 1.F, bool showBezel = false); float zoomLevel = 1.F, float overscan = 1.F,
BezelInfo bezelInfo = BezelInfo());
//bool showBezel = false, Int32 bezelBorder = 0);
Mode(uInt32 iw, uInt32 ih, Stretch smode, Int32 fsindex = -1, Mode(uInt32 iw, uInt32 ih, Stretch smode, Int32 fsindex = -1,
string_view desc = "", float zoomLevel = 1.F, bool showBezel = false); string_view desc = "", float zoomLevel = 1.F,
BezelInfo bezelInfo = BezelInfo());
//bool showBezel = false, Int32 bezelBorder = 0);
friend ostream& operator<<(ostream& os, const Mode& vm) friend ostream& operator<<(ostream& os, const Mode& vm)
{ {
@ -94,8 +115,8 @@ class VideoModeHandler
@return A video mode based on the given criteria @return A video mode based on the given criteria
*/ */
const VideoModeHandler::Mode& buildMode(const Settings& settings, const VideoModeHandler::Mode& buildMode(const Settings& settings, bool inTIAMode,
bool inTIAMode, bool showBezel); Mode::BezelInfo bezelInfo = Mode::BezelInfo());
private: private:
Common::Size myImage, myDisplay; Common::Size myImage, myDisplay;

View File

@ -1279,9 +1279,13 @@ FBInitStatus FrameBuffer::applyVideoMode()
#else #else
const bool showBezel = false; const bool showBezel = false;
#endif #endif
VideoModeHandler::Mode::BezelInfo bezelInfo(showBezel,
myOSystem.settings().getBool("bezel.windowed"),
myOSystem.settings().getInt("bezel.topborder"),
myOSystem.settings().getInt("bezel.bottomborder"));
// Build the new mode based on current settings // Build the new mode based on current settings
const VideoModeHandler::Mode& mode = myVidModeHandler.buildMode(s, inTIAMode, showBezel); const VideoModeHandler::Mode& mode = myVidModeHandler.buildMode(s, inTIAMode, bezelInfo);
if(mode.imageR.size() > mode.screenS) if(mode.imageR.size() > mode.screenS)
return FBInitStatus::FailTooLarge; return FBInitStatus::FailTooLarge;
@ -1306,14 +1310,9 @@ FBInitStatus FrameBuffer::applyVideoMode()
if(inTIAMode) if(inTIAMode)
{ {
#ifdef IMAGE_SUPPORT #ifdef IMAGE_SUPPORT
if(myBezelSurface) loadBezel(bezelInfo);
deallocateSurface(myBezelSurface);
myBezelSurface = nullptr;
if(showBezel)
loadBezel();
#endif #endif
myTIASurface->initialize(myOSystem.console(), myActiveVidMode);
myTIASurface->initialize(myOSystem.console(), myActiveVidMode);
if(fullScreen()) if(fullScreen())
myOSystem.settings().setValue("tia.fs_stretch", myOSystem.settings().setValue("tia.fs_stretch",
myActiveVidMode.stretch == VideoModeHandler::Mode::Stretch::Fill); myActiveVidMode.stretch == VideoModeHandler::Mode::Stretch::Fill);
@ -1385,63 +1384,87 @@ bool FrameBuffer::checkBezel()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FrameBuffer::loadBezel() bool FrameBuffer::loadBezel(VideoModeHandler::Mode::BezelInfo& info)
{ {
bool isValid = false; bool isValid = false;
double aspectRatio = 1;
myBezelSurface = allocateSurface(myActiveVidMode.screenS.w, myActiveVidMode.screenS.h);
try
{
const string& path = myOSystem.bezelDir().getPath();
string imageName;
VariantList metaData;
int index = 0;
do
{
const string& name = getBezelName(index);
if(name != EmptyString)
{
imageName = path + name + ".png";
FSNode node(imageName);
if(node.exists())
{
isValid = true;
break;
}
}
} while (index != -1);
if(isValid)
myOSystem.png().loadImage(imageName, *myBezelSurface, &aspectRatio, metaData);
}
catch(const runtime_error&)
{
isValid = false;
}
if(isValid)
{
// Scale bezel to fullscreen (preserve or stretch) or window size
const uInt32 bezelW = std::min(
myActiveVidMode.screenS.w,
static_cast<uInt32>(myActiveVidMode.imageR.w() * (16.F / 9.F) / (4.F / 3.F)));
const uInt32 bezelH = std::min(
myActiveVidMode.screenS.h,
myActiveVidMode.imageR.h());
//cerr << bezelW << " x " << bezelH << endl;
myBezelSurface->setDstSize(bezelW, bezelH);
myBezelSurface->setDstPos((myActiveVidMode.screenS.w - bezelW) / 2,
(myActiveVidMode.screenS.h - bezelH) / 2); // center
myBezelSurface->setScalingInterpolation(ScalingInterpolation::sharp);
// Enable blending to allow overlaying the bezel over the TIA output
myBezelSurface->attributes().blending = true;
myBezelSurface->attributes().blendalpha = 100;
myBezelSurface->applyAttributes();
}
if(myBezelSurface) if(myBezelSurface)
myBezelSurface->setVisible(isValid); deallocateSurface(myBezelSurface);
myBezelSurface = nullptr;
if(info.enabled)
{
double aspectRatio = 1;
myBezelSurface = allocateSurface(myActiveVidMode.screenS.w, myActiveVidMode.screenS.h);
try
{
const string& path = myOSystem.bezelDir().getPath();
string imageName;
VariantList metaData;
int index = 0;
do
{
const string& name = getBezelName(index);
if(name != EmptyString)
{
imageName = path + name + ".png";
FSNode node(imageName);
if(node.exists())
{
isValid = true;
break;
}
}
} while(index != -1);
if(isValid)
myOSystem.png().loadImage(imageName, *myBezelSurface, &aspectRatio, metaData);
}
catch(const runtime_error&)
{
isValid = false;
}
if(isValid)
{
const float overscan = 1 - myOSystem.settings().getInt("tia.fs_overscan") / 100.0;
uInt32 imageW, imageH;
if(fullScreen())
{
const float hBorder = info.hBorder() * overscan * myActiveVidMode.zoom;
const float vBorder = info.vBorder() * overscan * myActiveVidMode.zoom;
imageW = info.scaleW(myActiveVidMode.imageR.w() + hBorder);
imageH = myActiveVidMode.imageR.h() + vBorder;
}
else
{
imageW = info.scaleW(myActiveVidMode.imageR.w());
imageH = myActiveVidMode.imageR.h();
}
// Scale bezel to fullscreen (preserve or stretch) or window size
const uInt32 bezelW = std::min(
myActiveVidMode.screenS.w, imageW);
//static_cast<uInt32>(myActiveVidMode.imageR.w() * (16.F / 9.F) / (4.F / 3.F)) + static_cast<int>(40 * myActiveVidMode.zoom));
const uInt32 bezelH = std::min(
myActiveVidMode.screenS.h, imageH);
//myActiveVidMode.imageR.h() + static_cast<int>(30 * myActiveVidMode.zoom));
//cerr << bezelW << " x " << bezelH << endl;
myBezelSurface->setDstSize(bezelW, bezelH);
myBezelSurface->setDstPos((myActiveVidMode.screenS.w - bezelW) / 2,
(myActiveVidMode.screenS.h - bezelH) / 2); // center
myBezelSurface->setScalingInterpolation(ScalingInterpolation::sharp);
// Enable blending to allow overlaying the bezel over the TIA output
myBezelSurface->attributes().blending = true;
myBezelSurface->attributes().blendalpha = 100;
myBezelSurface->applyAttributes();
}
if(myBezelSurface)
myBezelSurface->setVisible(isValid);
}
return isValid; return isValid;
} }
#endif #endif

View File

@ -485,7 +485,7 @@ class FrameBuffer
@return Whether the bezel was loaded or not @return Whether the bezel was loaded or not
*/ */
bool loadBezel(); bool loadBezel(VideoModeHandler::Mode::BezelInfo& info);
#endif #endif
/** /**

View File

@ -64,6 +64,8 @@ Settings::Settings()
setPermanent("pausedim", "true"); setPermanent("pausedim", "true");
setPermanent("bezel.show", "true"); setPermanent("bezel.show", "true");
setPermanent("bezel.windowed", "false"); setPermanent("bezel.windowed", "false");
setPermanent("bezel.topborder", "0");
setPermanent("bezel.bottomborder", "0");
// TIA specific options // TIA specific options
setPermanent("tia.inter", "false"); setPermanent("tia.inter", "false");
setPermanent("tia.zoom", "3"); setPermanent("tia.zoom", "3");

View File

@ -481,6 +481,22 @@ void VideoAudioDialog::addBezelTab()
//myBezelEnableCheckbox->setToolTip(Event::BezelToggle); //myBezelEnableCheckbox->setToolTip(Event::BezelToggle);
wid.push_back(myBezelShowWindowed); wid.push_back(myBezelShowWindowed);
ypos += lineHeight + VGAP * 1;
myTopBorderSlider = new SliderWidget(myTab, _font, xpos, ypos,
"Top border ", 0, 0, 6 * fontWidth, "px");
myTopBorderSlider->setMinValue(0); myTopBorderSlider->setMaxValue(50);
myTopBorderSlider->setTickmarkIntervals(5);
//myTopBorderSlider->setToolTip(Event::VolumeDecrease, Event::VolumeIncrease);
wid.push_back(myTopBorderSlider);
ypos += lineHeight + VGAP;
myBtmBorderSlider = new SliderWidget(myTab, _font, xpos, ypos,
"Bottom border ", 0, 0, 6 * fontWidth, "px");
myBtmBorderSlider->setMinValue(0); myBtmBorderSlider->setMaxValue(50);
myBtmBorderSlider->setTickmarkIntervals(5);
//myBtmBorderSlider->setToolTip(Event::VolumeDecrease, Event::VolumeIncrease);
wid.push_back(myBtmBorderSlider);
// Add items for tab 4 // Add items for tab 4
addToFocusList(wid, myTab, tabID); addToFocusList(wid, myTab, tabID);
@ -679,7 +695,6 @@ void VideoAudioDialog::loadConfig()
myPhaseShift->setTickmarkIntervals(4); myPhaseShift->setTickmarkIntervals(4);
myPhaseShift->setToolTip("Adjust PAL phase shift of 'Custom' palette."); myPhaseShift->setToolTip("Adjust PAL phase shift of 'Custom' palette.");
myPhaseShift->setValue(myPaletteAdj.phasePal); myPhaseShift->setValue(myPaletteAdj.phasePal);
} }
else else
{ {
@ -733,6 +748,8 @@ void VideoAudioDialog::loadConfig()
myBezelEnableCheckbox->setState(settings.getBool("bezel.show")); myBezelEnableCheckbox->setState(settings.getBool("bezel.show"));
myBezelPath->setText(settings.getString("bezel.dir")); myBezelPath->setText(settings.getString("bezel.dir"));
myBezelShowWindowed->setState(settings.getBool("bezel.windowed")); myBezelShowWindowed->setState(settings.getBool("bezel.windowed"));
myTopBorderSlider->setValue(settings.getInt("bezel.topborder"));
myBtmBorderSlider->setValue(settings.getInt("bezel.bottomborder"));
handleBezelChange(); handleBezelChange();
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
@ -850,8 +867,7 @@ void VideoAudioDialog::saveConfig()
NTSCFilter::saveConfig(settings); NTSCFilter::saveConfig(settings);
// TV phosphor mode & blend // TV phosphor mode & blend
settings.setValue("tv.phosphor", settings.setValue("tv.phosphor", myTVPhosphor->getState() ? "always" : "byrom");
myTVPhosphor->getState() ? "always" : "byrom");
settings.setValue("tv.phosblend", myTVPhosLevel->getValueLabel() == "Off" settings.setValue("tv.phosblend", myTVPhosLevel->getValueLabel() == "Off"
? "0" : myTVPhosLevel->getValueLabel()); ? "0" : myTVPhosLevel->getValueLabel());
@ -864,6 +880,9 @@ void VideoAudioDialog::saveConfig()
settings.setValue("bezel.show", myBezelEnableCheckbox->getState()); settings.setValue("bezel.show", myBezelEnableCheckbox->getState());
settings.setValue("bezel.dir", myBezelPath->getText()); settings.setValue("bezel.dir", myBezelPath->getText());
settings.setValue("bezel.windowed", myBezelShowWindowed->getState()); settings.setValue("bezel.windowed", myBezelShowWindowed->getState());
settings.setValue("bezel.topborder", myTopBorderSlider->getValueLabel());
settings.setValue("bezel.bottomborder", myBtmBorderSlider->getValueLabel());
cerr << myTopBorderSlider << endl;
// Note: The following has to happen after all video related setting have been saved // Note: The following has to happen after all video related setting have been saved
if(instance().hasConsole()) if(instance().hasConsole())
@ -1003,6 +1022,8 @@ void VideoAudioDialog::setDefaults()
myBezelEnableCheckbox->setState(true); myBezelEnableCheckbox->setState(true);
myBezelPath->setText(instance().userDir().getShortPath()); myBezelPath->setText(instance().userDir().getShortPath());
myBezelShowWindowed->setState(false); myBezelShowWindowed->setState(false);
myTopBorderSlider->setValue(0);
myBtmBorderSlider->setValue(0);
handleBezelChange(); handleBezelChange();
break; break;
@ -1171,9 +1192,13 @@ void VideoAudioDialog::handlePhosphorChange()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void VideoAudioDialog::handleBezelChange() void VideoAudioDialog::handleBezelChange()
{ {
myOpenBrowserButton->setEnabled(myBezelEnableCheckbox->getState()); const bool enable = myBezelEnableCheckbox->getState();
myBezelPath->setEnabled(myBezelEnableCheckbox->getState());
myBezelShowWindowed->setEnabled(myBezelEnableCheckbox->getState()); myOpenBrowserButton->setEnabled(enable);
myBezelPath->setEnabled(enable);
myBezelShowWindowed->setEnabled(enable);
myTopBorderSlider->setEnabled(enable);
myBtmBorderSlider->setEnabled(enable);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -135,6 +135,8 @@ class VideoAudioDialog : public Dialog
ButtonWidget* myOpenBrowserButton{nullptr}; ButtonWidget* myOpenBrowserButton{nullptr};
EditTextWidget* myBezelPath{nullptr}; EditTextWidget* myBezelPath{nullptr};
CheckboxWidget* myBezelShowWindowed{nullptr}; CheckboxWidget* myBezelShowWindowed{nullptr};
SliderWidget* myTopBorderSlider{nullptr};
SliderWidget* myBtmBorderSlider{nullptr};
// Audio // Audio
CheckboxWidget* mySoundEnableCheckbox{nullptr}; CheckboxWidget* mySoundEnableCheckbox{nullptr};