Scaling and Resolution

Calculate Imperfect Resolutions for a Display

As seen in a different post (which I haven’t made available yet), we determined all the resolutions available for the monitor’s native resolution. But, what if, say, you need to determine the maximum resolution you can display on your monitor for the SNES for streaming purposes?

I needed to figure this out while setting up my capture equipment in OBS to stream my SNES on original hardware. Problem is, the Elgato HD60S that I happen to own wants to display the input it’s receiving at the native output resolution of
1920 x 1080. This is no good.

How am I able to use this device to capture a system that doesn’t have HDMI? Well, that’s for another time, but here’s a photo or two to give you an idea of the nonsense hoops I have to jump through.

Note:
All the code here was written to work in JavaScript, but I translated it from GML, or, GameMaker Language to those unfamiliar with it.

The Hoops


ss1

ss2

ss3

ss4

ss5

ss6


As you can see in the final two images, the game displays fine on the CRT, but is displaying just horribly in the recording software.

We can fix this in OBS, but let’s talk about the math behind it.

Let’s Do This.

So, we’ve got the formula already to get the Greatest Common Divisor between two values. We need to do this for 2 different aspect ratios, and that’s where it gets complicated.

First, we need to know the display area of the output area. Like in the post mentioned above, we will use the common standard of 1920 x 1080, but if yours is different, the formula will still work. So let’s calculate that again.

The Greatest Common Divisor

function gcd(a,b) {
 while b != 0 {
  var ta = b;
  var tb = a%b;
  a = ta;
  b = tb;
 }
 return a;
}

// Run and store the result.
display = {
 width: 1920,
 height: 1080
}

// Result is 120
display_gcd = gcd(display.width, display.height)

Running this, we get the result of 120. By dividing this value into the display width and height, we get the aspect ratio.

display_aspect = {
 width: display.width/display_gcd,
 height: display.height/display_gcd
}

// Logging this gives us the aspect ratio.
console.log(`${display_aspect.width} x ${display_aspect.height}`)

//>>> 16 x 9

We can then use that initial value of 120 to get all pixel perfect ratios using Wolfram Alpha, which looks like this.

{f(x) = Range[120]*16, Range[120]*9}

Let’s see what we need to do to find the next step.

Making it Fit

To do this, we need to get the native display resolution of the SNES. It happens to be 256 x 224. Plugging this in like the above we get the following.

snes = {
 width: 256,
 height: 224
}

// Result is 32
snes_gcd = gcd(snes.width, snes.height)

// Dividing the snes' width and height by its GCD gets us the
// aspect ratio we're after.
snes_aspect_ratio = {
 width: snes.width/snes_gcd,
 height: snes.height/snes_gcd
}

// Log to the console to see our results!
console.log(`${snes_aspect_ratio.width} x ${snes_aspect_ratio.height}`);

//--> 8 x 7

Thus, we now know the aspect ratio of the SNES: 8 x 7. These values will soon become of great importance to us to find out what the maximum achievable resolution is on our monitor of 1920 x 1080, whose aspect ratio is quite different at 16 x 9, and thus impossible to render without the proper scaling.

Balancing the Scales

So, the limiting element in both of the aspect ratios, i.e. the lower value is the height, with the display’s being a 9 and the SNES’s being a 7. So, to find the highest achievable resolution in our screen space, we need to create a function that iterates over and over again until it reaches a point where the limiting element of the height in our case, inevitably reaches a point where it is larger than our available resolution.

I’ve written up a little function that will solve our problem. It has some redundant code in there for the sake of clarity, but relies on knowing only 2 arguments:

  1. The resolution of the screen, the limiter, which is for us, the display variable.
  2. The aspect ratio of the material that you wish to display, the snes.
// Finds all possible resolutions that can be contained by a 
// display area with a different aspect ratio to the source
// content.
function constrain(limiter, tester){
 var limit = 0;
 var i = 1;
 var limiter_gcd = gcd(limiter.width, limiter.height);
 var tester_gcd = gcd(tester.width, tester.height);
 var widths = [];
 var heights = [];
    
 var limiter_aspect = {
  width: limiter.width/limiter_gcd,
  height: limiter.height/limiter_gcd
 }
    
 var tester_aspect = {
  width: tester.width/tester_gcd,
  height: tester.height/tester_gcd
 }

  while (i * tester_aspect.width < limiter.width &&
         i * tester_aspect.height < limiter.height) {
   // Increment i
   i++;
   widths[i] = tester_aspect.width * i
   heights[i] = tester_aspect.height * i;
   limit = i;
 }
    
 return [limit, widths, heights];
}

// Store the results in variables.
results = constrain(display, snes);
// Break them down into individual variables.
limiter = results[0];
widths = results[1];
heights = results[2];

The first result returned represents the number that we can plug in to our Mathematica formula to easily repeat this process and find all the perfect resolutions, along with their widths and heights. 

That number happens to be 155. So, let’s see!

Note:
It's extremely simple to alter this formula for your own needs, and I've written this redundantly so people of varying skill levels can easily modify it!

{f(x) = Range[155]*8, Range[155]*7}

Now, view the results below, or try clicking here to see how this works in action on Wolfram Alpha!

crr

And, if you’d like to print it all out yourself, here’s a lovely little function that will do that in a jiffy.

function print_resolutions(l1, l2) {
 for (var i = 0; i < l1.length; i++) {
  console.log(`${l1[i]} x ${l2[i]}`)
 }
}

// Logs the resolutions to the console in an easily readable format.
print_resolutions(widths, heights);

It’ll look something like this.

16 x 14
24 x 21
32 x 28
40 x 35
48 x 42
56 x 49
64 x 56
72 x 63
80 x 70
88 x 77
96 x 84
104 x 91

Closing Remarks 🕹 📺

And here’s how the math helped me fix my problem, so I could get the perfect scaling for my little stream.

The Fruits of Our Mathematical Labours

Aaaand, the final look for now.

The Final Result

Farewell

Stop by the stream sometime! It’s right here on Twitch, and I’d love to have you come by! I play From Soft games, Super Mario World and their infamous hacks, along with new games I’ve yet to try out. I’ve got 25 followers at the moment, but that just means I’m halfway to the big 50!

IT COULD BE YOU!!!!

SAW-EEEEENG BAATAAAA!
❤️ iivii