Using PowerShell to Analyze Screen Resolutions

By Michael Flanakin @ 10:54 AM :: 3172 Views :: PowerShell :: Digg it!
PowerShell

As part of my site redesign effort, I'm taking a look at different screen resolutions users of my site have (I'll post something on that later). I noticed a few strange screen resolutions and wanted to compare the different aspect ratios. I knew a couple of them, but not all, so I figured PowerShell should be able to solve this problem fairly easily to ultimately give me the following information. Note that the chart was created in Excel. I could've used WPF, but since this is a rat-hole of a rat-hole off my actual task at hand, the redesign, I didn't want to get into WPF generation in PowerShell.

Screen aspect ratios of michaelflanakin.com users

Breaking the problem down, we need to be able to go from 1024x768 to 4:3. In order to do this, we'll have to whip out some elementary school math to figure out the greatest common divisor. Luckily, this is pretty simple to determine. First, let's start with getting a list of all divisors for one number.

function Get-Divisors($n)
{
    $div = @();
    foreach ($i in 1 .. ($n/3))
    {
        $d = $n/$i;
        if (($d -eq [System.Math]::Floor($d)) -and -not ($div -contains $i))
        {
            $div += $i;
            $div += $d;
        }
    };
    $div | Sort-Object;
}

This is pretty simple, but I should probably cover a few non-obvious things. First, @() is an empty array, which will be used to store all of the divisors. Looking at the loop, you'll notice that I only loop thru one third of the possible values. I hope I remember this correctly from school, but you do not need to loop thru all numbers between 1 and the target value to identify all possible divisors. This is simply a way to speed up the calculation. I'll leave it to you to explore the algorithm on your own, since that's not my focus here. Within the loop, the function checks to see if the divisor is a whole number and whether the value has already been saved to avoid duplicates then adds each divisor to the array. Pay attention to this because what's happening here is that PowerShell sees [array] + [object] and automatically determins that you must want to add a new item to the array. This was a very nice surprise. Finally, Sort-Object puts the numbers in order for us humans. Now, we can get all divisors for a specific number.

PS C:\> Get-Divisors 1024;
1
2
4
8
16
32
32
64
128
256
512
1024

Next, we need to get the common divisors for both the height and width of the screen resolutions.

function Get-CommonDivisors($x, $y)
{
    $xd = Get-Divisors $x;
    $yd = Get-Divisors $y;
    $div = @();
    foreach ($i in $xd) { if ($yd -contains $i) { $div += $i; } }
    $div | Sort-Object;
}

This is pretty much more of the same. Get all divisors, create a new array to hold the common divisors, loop thru the divisors to find the commonalities, and finally sort the array. The array shouldn't need to be sorted, since the previous function did it, but I figured it's probably good to be sure.

PS C:\> Get-CommonDivisors 1024 768;
1
2
4
8
16
32
32
64
128
256

Next, we'll grab the greatest common divisor.

function Get-GreatestCommonDivisor($x, $y)
{
    $d = Get-CommonDivisors $x $y;
    $d[$d.Length-1];
}

This isn't even worth explaining. Arguably, we could've simply returned the greatest common divisor in the last function, but this is a good way to create composable, reusable scripts. After all, we need to retrieve a list of divisors all the time, right? :-P I won't bother showing what the output would be. I'm sure you can figure this one out ;-) We'll move right on to the last step, which will get the actual aspect ratio.

function Get-Ratio($x, $y)
{
    $d = Get-GreatestCommonDivisor $x $y;
    New-Object PSObject -Property @{
        X = $x;
        Y = $y;
        Divisor = $d;
        XRatio = $x/$d;
        YRatio = $y/$d;
        Ratio = "$($x/$d):$($y/$d)";
    };
}

This will most likely throw you for a loop, if you're a PowerShell beginner and possibly even some intermediate users. You most likely expected the function to simply return a string, like "4:3". We could absolutely do this, but with the richness of PowerShell, using a string is somewhat wasteful. This all comes back to a goal of composable scripts that can be reused in the future. In the future, we may want more than just a string value. Since PowerShell is so good at passing around objects, let's create a new object that has all the properties that make sense for this context, namely the two numbers, greatest common divisor, individual ratio portions, and the string representation of that ratio.

PS C:> Get-Ratio 1024 768;

Divisor : 256
Y       : 768
XRatio  : 4
X       : 1024
YRatio  : 3
Ratio   : 4:3

Now, we have what we sought out to get: the aspect ratio for a 1024x768 screen resolution. Let's face it, tho, we didn't want one, we wanted a bunch of them. To be exact, I was curious about 10 different resolutions. We've gone this far to automate this process, we might as well finish up with a function to get a group of aspect ratios.

function Get-CommonRatios($res)
{
    $ratios = @{};
    foreach ($r in $res)
    {
        $rat = (Get-Ratio $r[0] $r[1]);
        if (-not $ratios.Contains($rat.Ratio))
        {
            $ratios.Add($rat.Ratio, 
                (New-Object PSObject -Property @{
                    XRatio = $rat.XRatio;
                    YRatio = $rat.YRatio;
                    Ratio = $rat.Ratio;
                    Count = 1;
                }));
        }
        else
        {
            $ratios[$rat.Ratio].Count += 1;
        }
    }
    $ratios.Values | Sort-Object -Property XRatio;
}

Again, this is all pretty normal. I'm using a hashtable (@{}) instead of an array to avoid duplicates and also created a new object to hold the metadata instead of the original ratio object because not all of the properties on the old object are applicable anymore. I also added a property to count the number of times the aspect ratio is used.

PS C:\> Get-CommonRatios @((1024,768), (1280,800), (1280,1024), (1366,768), (1440,900), (1600,900), (1600,1200), (1680,1050), (1920,1080), (1920,1200)) | Format-Table Ratio, Count;

Ratio                           Count
-----                           -----
4:3                                 2
5:4                                 1
8:5                                 4
16:9                                2
683:384                             1

There ya have it. The only special thing I did was format the results as a table with only the ratio and count properties. Hopefully, you were able to pick up a few new things for me, I was glad to explore the dynamic array handling and runtime object creation.

Ratings