Added smoothing control to both PCjs and PC8080 (setting it to false should probably be the new default)

It can be controlled with a new "smoothing" attribute on the <video> component and/or overridden with a "smoothing" URL parameter set to either "true" or "false"
This commit is contained in:
Jeff Parsons 2016-05-03 09:11:02 -07:00
commit c31f6222a1
12 changed files with 666 additions and 426 deletions

View file

@ -87,6 +87,10 @@ if (NODE) {
*/
function Video(parmsVideo, canvas, context, textarea, container)
{
var video = this;
this.fGecko = web.isUserAgent("Gecko/");
var i, sEvent, asWebPrefixes = ['', 'moz', 'webkit', 'ms'];
Component.call(this, "Video", parmsVideo, Video, Messages.VIDEO);
this.cxScreen = parmsVideo['screenWidth'];
@ -107,10 +111,27 @@ function Video(parmsVideo, canvas, context, textarea, container)
this.textareaScreen = textarea;
this.inputScreen = textarea || canvas || null;
this.contextScreen['webkitImageSmoothingEnabled'] = false;
this.contextScreen['mozImageSmoothingEnabled'] = false;
this.contextScreen['msImageSmoothingEnabled'] = false;
this.contextScreen['imageSmoothingEnabled'] = false;
/*
* Support for disabling (or, less commonly, enabling) image smoothing, which all browsers
* seem to support now (well, OK, I still have to test the latest MS Edge browser), despite
* it still being labelled "experimental technology". Let's hope the browsers standardize
* on this. I see other options emerging, like the CSS property "image-rendering: pixelated"
* that's apparently been added to Chrome. Sigh.
*/
var fSmoothing = parmsVideo['smoothing'];
var sSmoothing = Component.parmsURL['smoothing'];
if (sSmoothing) fSmoothing = (sSmoothing == "true")? true : false;
if (fSmoothing != null) {
for (i = 0; i < asWebPrefixes.length; i++) {
sEvent = asWebPrefixes[i];
if (!sEvent) {
sEvent = 'imageSmoothingEnabled';
} else {
sEvent += 'ImageSmoothingEnabled';
}
this.contextScreen[sEvent] = fSmoothing;
}
}
this.rotateScreen = parmsVideo['screenRotate'];
if (this.rotateScreen == 90) {
@ -135,6 +156,39 @@ function Video(parmsVideo, canvas, context, textarea, container)
this.canvasBuffer.width = cxBuffer;
this.canvasBuffer.height = cyBuffer;
this.contextBuffer = this.canvasBuffer.getContext("2d");
/*
* Here's the gross code to handle full-screen support across all supported browsers. The lack of standards
* is exasperating; browsers can't agree on 'full' or 'Full, 'request' or 'Request', 'screen' or 'Screen', and
* while some browsers honor other browser prefixes, most browsers don't.
*/
this.container = container;
if (this.container) {
this.container.doFullScreen = container['requestFullscreen'] || container['msRequestFullscreen'] || container['mozRequestFullScreen'] || container['webkitRequestFullscreen'];
if (this.container.doFullScreen) {
for (i = 0; i < asWebPrefixes.length; i++) {
sEvent = asWebPrefixes[i] + 'fullscreenchange';
if ('on' + sEvent in document) {
var onFullScreenChange = function() {
var fFullScreen = (document['fullscreenElement'] || document['mozFullScreenElement'] || document['webkitFullscreenElement'] || document['msFullscreenElement']);
video.notifyFullScreen(fFullScreen? true : false);
};
document.addEventListener(sEvent, onFullScreenChange, false);
break;
}
}
for (i = 0; i < asWebPrefixes.length; i++) {
sEvent = asWebPrefixes[i] + 'fullscreenerror';
if ('on' + sEvent in document) {
var onFullScreenError = function() {
video.notifyFullScreen(null);
};
document.addEventListener(sEvent, onFullScreenError, false);
break;
}
}
}
}
}
Component.subclass(Video);
@ -170,6 +224,137 @@ Video.prototype.initBus = function(cmp, bus, cpu, dbg)
this.setReady();
};
/**
* setBinding(sHTMLType, sBinding, control, sValue)
*
* @this {Video}
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "refresh")
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
* @param {string} [sValue] optional data value
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
Video.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
{
var video = this;
switch (sBinding) {
case "fullScreen":
this.bindings[sBinding] = control;
if (this.container && this.container.doFullScreen) {
control.onclick = function onClickFullScreen() {
if (DEBUG) video.printMessage("fullScreen()");
video.doFullScreen();
};
} else {
if (DEBUG) this.log("FullScreen API not available");
control.parentNode.removeChild(/** @type {Node} */ (control));
}
return true;
default:
break;
}
return false;
};
/**
* doFullScreen()
*
* @this {Video}
* @return {boolean} true if request successful, false if not (eg, failed OR not supported)
*/
Video.prototype.doFullScreen = function()
{
var fSuccess = false;
if (this.container) {
if (this.container.doFullScreen) {
/*
* Styling the container with a width of "100%" and a height of "auto" works great when the aspect ratio
* of our virtual screen is at least roughly equivalent to the physical screen's aspect ratio, but now that
* we support virtual VGA screens with an aspect ratio of 1.33, that's very much out of step with modern
* wide-screen monitors, which usually have an aspect ratio of 1.6 or greater.
*
* And unfortunately, none of the browsers I've tested appear to make any attempt to scale our container to
* the physical screen's dimensions, so the bottom of our screen gets clipped. To prevent that, I reduce
* the width from 100% to whatever percentage will accommodate the entire height of the virtual screen.
*
* NOTE: Mozilla recommends both a width and a height of "100%", but all my tests suggest that using "auto"
* for height works equally well, so I'm sticking with it, because "auto" is also consistent with how I've
* implemented a responsive canvas when the browser window is being resized.
*/
var sWidth = "100%";
var sHeight = "auto";
if (screen && screen.width && screen.height) {
var aspectPhys = screen.width / screen.height;
var aspectVirt = this.cxScreen / this.cyScreen;
if (aspectPhys > aspectVirt) {
sWidth = Math.round(aspectVirt / aspectPhys * 100) + '%';
}
// TODO: We may need to someday consider the case of a physical screen with an aspect ratio < 1.0....
}
if (!this.fGecko) {
this.container.style.width = sWidth;
this.container.style.height = sHeight;
} else {
/*
* Sadly, the above code doesn't work for Firefox, because as http://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Using_full_screen_mode
* explains:
*
* 'It's worth noting a key difference here between the Gecko and WebKit implementations at this time:
* Gecko automatically adds CSS rules to the element to stretch it to fill the screen: "width: 100%; height: 100%".
*
* Which would be OK if Gecko did that BEFORE we're called, but apparently it does that AFTER, effectively
* overwriting our careful calculations. So we style the inner element (canvasScreen) instead, which
* requires even more work to ensure that the canvas is properly centered. FYI, this solution is consistent
* with Mozilla's recommendation for working around their automatic CSS rules:
*
* '[I]f you're trying to emulate WebKit's behavior on Gecko, you need to place the element you want
* to present inside another element, which you'll make fullscreen instead, and use CSS rules to adjust
* the inner element to match the appearance you want.'
*/
this.canvasScreen.style.width = sWidth;
this.canvasScreen.style.width = sWidth;
this.canvasScreen.style.display = "block";
this.canvasScreen.style.margin = "auto";
}
this.container.style.backgroundColor = "black";
this.container.doFullScreen();
fSuccess = true;
}
this.setFocus();
}
return fSuccess;
};
/**
* notifyFullScreen(fFullScreen)
*
* @this {Video}
* @param {boolean|null} fFullScreen (null if there was a full-screen error)
*/
Video.prototype.notifyFullScreen = function(fFullScreen)
{
if (!fFullScreen && this.container) {
if (!this.fGecko) {
this.container.style.width = this.container.style.height = "";
} else {
this.canvasScreen.style.width = this.canvasScreen.style.height = "";
}
}
this.printMessage("notifyFullScreen(" + fFullScreen + ")", true);
};
/**
* setFocus()
*
* @this {Video}
*/
Video.prototype.setFocus = function()
{
if (this.inputScreen) this.inputScreen.focus();
};
/**
* getRefreshRate()
*

View file

@ -100,6 +100,10 @@ if (NODE) {
*/
function Video(parmsVideo, canvas, context, textarea, container)
{
var video = this;
this.fGecko = web.isUserAgent("Gecko/");
var i, sEvent, asWebPrefixes = ['', 'moz', 'webkit', 'ms'];
Component.call(this, "Video", parmsVideo, Video, Messages.VIDEO);
/*
@ -152,6 +156,28 @@ function Video(parmsVideo, canvas, context, textarea, container)
this.textareaScreen = textarea;
this.inputScreen = textarea || canvas || null;
/*
* Support for disabling (or, less commonly, enabling) image smoothing, which all browsers
* seem to support now (well, OK, I still have to test the latest MS Edge browser), despite
* it still being labelled "experimental technology". Let's hope the browsers standardize
* on this. I see other options emerging, like the CSS property "image-rendering: pixelated"
* that's apparently been added to Chrome. Sigh.
*/
var fSmoothing = parmsVideo['smoothing'];
var sSmoothing = Component.parmsURL['smoothing'];
if (sSmoothing) fSmoothing = (sSmoothing == "true")? true : false;
if (fSmoothing != null) {
for (i = 0; i < asWebPrefixes.length; i++) {
sEvent = asWebPrefixes[i];
if (!sEvent) {
sEvent = 'imageSmoothingEnabled';
} else {
sEvent += 'ImageSmoothingEnabled';
}
this.contextScreen[sEvent] = fSmoothing;
}
}
/*
* initBus() will determine touch-screen support; for now, just record values and set defaults.
*/
@ -206,16 +232,12 @@ function Video(parmsVideo, canvas, context, textarea, container)
* is exasperating; browsers can't agree on 'full' or 'Full, 'request' or 'Request', 'screen' or 'Screen', and
* while some browsers honor other browser prefixes, most browsers don't.
*/
var video = this;
this.fGecko = web.isUserAgent("Gecko/");
var i, sEvent, asPrefixes = ['', 'moz', 'webkit', 'ms'];
this.container = container;
if (this.container) {
this.container.doFullScreen = container['requestFullscreen'] || container['msRequestFullscreen'] || container['mozRequestFullScreen'] || container['webkitRequestFullscreen'];
if (this.container.doFullScreen) {
for (i = 0; i < asPrefixes.length; i++) {
sEvent = asPrefixes[i] + 'fullscreenchange';
for (i = 0; i < asWebPrefixes.length; i++) {
sEvent = asWebPrefixes[i] + 'fullscreenchange';
if ('on' + sEvent in document) {
var onFullScreenChange = function() {
var fFullScreen = (document['fullscreenElement'] || document['mozFullScreenElement'] || document['webkitFullscreenElement'] || document['msFullscreenElement']);
@ -225,8 +247,8 @@ function Video(parmsVideo, canvas, context, textarea, container)
break;
}
}
for (i = 0; i < asPrefixes.length; i++) {
sEvent = asPrefixes[i] + 'fullscreenerror';
for (i = 0; i < asWebPrefixes.length; i++) {
sEvent = asWebPrefixes[i] + 'fullscreenerror';
if ('on' + sEvent in document) {
var onFullScreenError = function() {
video.notifyFullScreen(null);
@ -241,7 +263,7 @@ function Video(parmsVideo, canvas, context, textarea, container)
/*
* More gross code to handle pointer-locking support across all supported browsers.
*
* TODO: Consider "upgrading" this code to use the same asPrefixes array as above, especially once Microsoft
* TODO: Consider "upgrading" this code to use the same asWebPrefixes array as above, especially once Microsoft
* finally releases a browser that supports pointer-locking (post-Windows 10?)
*/
if (this.inputScreen) {

View file

@ -1099,6 +1099,12 @@
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="smoothing">
<xsl:choose>
<xsl:when test="@smoothing"><xsl:value-of select="@smoothing"/></xsl:when>
<xsl:otherwise>null</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="interruptRate">
<xsl:choose>
<xsl:when test="@interruptRate"><xsl:value-of select="@interruptRate"/></xsl:when>
@ -1114,7 +1120,7 @@
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">video</xsl:with-param>
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',mode:<xsl:value-of select="$mode"/>,screenWidth:<xsl:value-of select="$screenWidth"/>,screenHeight:<xsl:value-of select="$screenHeight"/>,screenColor:'<xsl:value-of select="$screenColor"/>',screenRotate:<xsl:value-of select="$screenRotate"/>,bufferAddr:<xsl:value-of select="$bufferAddr"/>,bufferCols:<xsl:value-of select="$bufferCols"/>,bufferRows:<xsl:value-of select="$bufferRows"/>,bufferBits:<xsl:value-of select="$bufferBits"/>,bufferLeft:<xsl:value-of select="$bufferLeft"/>,bufferRotate:<xsl:value-of select="$bufferRotate"/>,memory:<xsl:value-of select="$memory"/>,switches:'<xsl:value-of select="$switches"/>',scale:<xsl:value-of select="$scale"/>,charCols:<xsl:value-of select="$charCols"/>,charRows:<xsl:value-of select="$charRows"/>,fontROM:'<xsl:value-of select="$fontROM"/>',touchScreen:'<xsl:value-of select="$touchScreen"/>',autoLock:<xsl:value-of select="$autoLock"/>,aspectRatio:<xsl:value-of select="$aspectRatio"/>,interruptRate:<xsl:value-of select="$interruptRate"/>,refreshRate:<xsl:value-of select="$refreshRate"/></xsl:with-param>
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',mode:<xsl:value-of select="$mode"/>,screenWidth:<xsl:value-of select="$screenWidth"/>,screenHeight:<xsl:value-of select="$screenHeight"/>,screenColor:'<xsl:value-of select="$screenColor"/>',screenRotate:<xsl:value-of select="$screenRotate"/>,bufferAddr:<xsl:value-of select="$bufferAddr"/>,bufferCols:<xsl:value-of select="$bufferCols"/>,bufferRows:<xsl:value-of select="$bufferRows"/>,bufferBits:<xsl:value-of select="$bufferBits"/>,bufferLeft:<xsl:value-of select="$bufferLeft"/>,bufferRotate:<xsl:value-of select="$bufferRotate"/>,memory:<xsl:value-of select="$memory"/>,switches:'<xsl:value-of select="$switches"/>',scale:<xsl:value-of select="$scale"/>,charCols:<xsl:value-of select="$charCols"/>,charRows:<xsl:value-of select="$charRows"/>,fontROM:'<xsl:value-of select="$fontROM"/>',touchScreen:'<xsl:value-of select="$touchScreen"/>',autoLock:<xsl:value-of select="$autoLock"/>,aspectRatio:<xsl:value-of select="$aspectRatio"/>,smoothing:<xsl:value-of select="$smoothing"/>,interruptRate:<xsl:value-of select="$interruptRate"/>,refreshRate:<xsl:value-of select="$refreshRate"/></xsl:with-param>
</xsl:call-template>
</xsl:template>