Aspect Ratio Calculator

Aspect ratios can be a confusing business. There's the Sample Aspect Ratio (SAR), also called Pixel Aspect Ratio (PAR), which specifies the dimensions of each pixel, and there is the Display Aspect Ratio, which specifies the aspect ratio of the whole picture. Not to speak of all the little details that are not quite what one'd think.

Well, basically this is simply a calculator used to determine the correct AR data for certain types of input video under certain transformations.

If you read and understood the linked page, know what you are doing, and think you found a bug in the calculations, please report it to: ar-calc a t ps - auxw d o t de

Calculate

Scale transforms scale the actual video resolution to the specified size. Crop transforms crop the actual video resolution to the specified size. The target size for crop must be lower than former actual video size. Pad transforms pad the actual video resolution to the specified size. The target size for pad must be greater than the former actual video size.

The source AR is given with respect to the active picture region. This means that without transforms (like cropping to the active picture region), you won't get a DAR equal to the source AR. Do not worry. Since the vast majority of professionally mastered SD material should conform to ITU-R B.601, this is the way it's supposed to be done, using correct ITU-R B.601 PAR values.

Also note, that the correct source AR is not necessarily the AR of the movie. For example, if you have a source where a widescreen movie was encoded as letterboxed fullscreen, you'll have to choose full screen as the source AR.

About HD: It is assumed that, in the case of full HD, 4:3 is encoded pillarboxed. This way the PAR for both fullscreen and widescreen video is 1:1. For "anamorphic" HD at 1440x1080 it is assumed that 4:3 is encoded without black bars, if you select fullscreen mode. Use widescreen mode for calculations with 1440x1080 stretched to 1920x1080. In any case, for HD there do not seem to be tricky issues involving active picture regions and ITU-R B.601 PAR values, so things are pretty straightforward.

Source:
Source AR:
1. Transform:
2. Transform:
3. Transform:
4. Transform:
5. Transform:
6. Transform:
7. Transform:
8. Transform:
Keep input.

Source

#!/usr/bin/perl
# ar-calc.pl - Web aspect ratio calculator
# Copyright (C) 2008-2011 Arne Bochem
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

use strict;
use lib '.';
use Math::Fraction;
use CGI qw(:param);

my $source_map = {
                  'pal'     => {'fullscreen' => [128,   117], 'widescreen' => [1024,  702], 'resolution' => [720,   576]},
                  'ntsc'    => {'fullscreen' => [4320, 4739], 'widescreen' => [5760, 4739], 'resolution' => [720,   480]},
                  'hd_full' => {'fullscreen' => [1,       1], 'widescreen' => [1,       1], 'resolution' => [1920, 1080]},
                  'hd_ana'  => {'fullscreen' => [1,       1], 'widescreen' => [4,       3], 'resolution' => [1440, 1080]},
                 };
my $error;

if (param("source") eq "download")
{
	seek(DATA, 0, 0);
	my $source = join '', <DATA>;
	print "Content-Type: application/octet-stream\n\n$source";
	exit(0);
}

print <<HTML;
Content-Type: text/html; charset=utf-8

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de">
	<head>
		<meta name="author" content="Arne Bochem" />
		<meta name="robots" content="index, follow" />
		<meta name="generator" content="VIM, knowledge and some time to write the code." />
		<title>Aspect Ratio Calculator</title>
	</head>
	<body>
		<h1>Aspect Ratio Calculator</h1>
		<p>Aspect ratios can be a confusing business. There's the Sample Aspect Ratio (SAR), also called Pixel Aspect Ratio (PAR), which specifies the dimensions of each pixel, and there is the Display Aspect Ratio, which specifies the aspect ratio of the whole picture. Not to speak of <a href="http://web.archive.org/web/20140218044518/http://lipas.uwasa.fi/~f76998/video/conversion/">all the little details</a> that are not quite what one'd think.</p>
		<p>Well, basically this is simply a calculator used to determine the correct AR data for certain types of input video under certain transformations.</p>
		<p>If you read and understood the linked page, know what you are doing, and think you found a bug in the calculations, please report it to: ar-calc a t ps - auxw d o t de</p>
		<h2>Calculate</h2>
		<p>Scale transforms scale the actual video resolution to the specified size. Crop transforms crop the actual video resolution to the specified size. The target size for crop must be lower than former actual video size. Pad transforms pad the actual video resolution to the specified size. The target size for pad must be greater than the former actual video size.</p>
		<p>The source AR is given with respect to the active picture region. This means that without transforms (like cropping to the active picture region), you won't get a DAR equal to the source AR. Do not worry. Since the vast majority of professionally mastered SD material should conform to ITU-R B.601, this is the way it's supposed to be done, using correct ITU-R B.601 PAR values.</p>
		<p>Also note, that the correct source AR is not necessarily the AR of the movie. For example, if you have a source where a widescreen movie was encoded as letterboxed fullscreen, you'll have to choose full screen as the source AR.</p>
		<p>About HD: It is assumed that, in the case of full HD, 4:3 is encoded pillarboxed. This way the PAR for both fullscreen and widescreen video is 1:1. For "anamorphic" HD at 1440x1080 it is assumed that 4:3 is encoded without black bars, if you select fullscreen mode. Use widescreen mode for calculations with 1440x1080 stretched to 1920x1080. In any case, for HD there do not seem to be tricky issues involving active picture regions and ITU-R B.601 PAR values, so things are pretty straightforward.</p>
		<div>
			<form method="get" action="ar-calc.pl">
HTML

my $pal        = param("keep") && param("source_type") =~ /pal/  ? ' selected="selected"' : '';
my $ntsc       = param("keep") && param("source_type") =~ /ntsc/ ? ' selected="selected"' : '';
my $hd_full    = param("keep") && param("source_type") eq 'hd_full'  ? ' selected="selected"' : '';
my $hd_ana     = param("keep") && param("source_type") eq 'hd_ana'   ? ' selected="selected"' : '';
my $fullscreen = param("keep") && param("source_ar") eq 'fullscreen' ? ' selected="selected"' : '';
my $widescreen = param("keep") && param("source_ar") eq 'widescreen' ? ' selected="selected"' : '';

print <<HTML;
				<div>Source: <select name="source_type"><option value="pal"$pal>720x576 PAL (Active picture: 702x576)</option><option value="ntsc"$ntsc>720x480 NTSC (Active picture: 710.85x486)</option><option value="hd_full"$hd_full>1920x1080 Full HD</option><option value="hd_ana"$hd_ana>1440x1080 "anamorphic" HD (see note)</option></select></div>
				<div>Source AR: <select name="source_ar"><option value="widescreen"$widescreen>Widescreen (16:9)</option><option value="fullscreen"$fullscreen>Fullscreen (4:3)</option></select></div>
HTML

# What a mess...
foreach my $i (1..8)
{
	print "				<div>$i. Transform: <select name=\"transform_type_$i\">";
	foreach ('none', 'crop', 'scale', 'pad')
	{
		my $kind = $_;
		print "<option value=\"$kind\"".(param("keep") && param("transform_type_$i") eq $_ ? ' selected="selected"' : "").">";
		$kind =~ s/(.)/\u$1/;
		print "$kind</option>";
	}
	print "</select>";
	foreach my $d ('x', 'y')
	{
		print "<input name=\"transform_${d}_$i\" type=\"text\" size=\"5\"".(param("keep") && param("transform_${d}_$i") ? ' value="'.param("transform_${d}_$i").'"' : '')." />";
	}
	print "</div>\n";
}

my $keep = !param() || param("keep") ? ' checked="checked"' : '';

print <<HTML;
				<!--div><input name="make_mod_sixteen" type="checkbox" value="true" checked="checked" /> Generate additional resolution, divisible by 16. Horitontal: <select name="horizontal_method"><option value="crop" selected="selected">Crop</option><option value="upscale">Upscale</option><option value="downscale">Downscale</option><option value="nearest">Scale to nearest</option></select> Vertical: <select name="vertical_method"><option value="crop" selected="selected">Crop</option><option value="upscale">Upscale</option><option value="downscale">Downscale</option><option value="nearest">Scale to nearest</option></select></div-->
				<div><input name="keep" type="checkbox" value="true"$keep /> Keep input.</div>
				<div><input type="submit" name="calculate" value="Calculate" /></div>
			</form>
		</div>
HTML

sub fail ($)
{
	die("ARCALC: $_[0]\n");
}

sub crop_transform ($$$$$)
{
	my ($res, $par, $x, $y, $transform_id) = @_;
	if ($res->[0] < $x)
	{
		fail("Horizontal target resolution bigger than video resolution in crop transform $transform_id: $x");
	}
	if ($res->[1] < $y)
	{
		fail("Vertical target resolution bigger than video resolution in crop transform $transform_id: $y");
	}
	$res->[0] = $x;
	$res->[1] = $y;
}

sub scale_transform ($$$$$)
{
	my ($res, $par, $x, $y, $transform_id) = @_;

	# Horizontal scale
	$par->[0] *= $res->[0];
	$par->[1] *= $x;

	# Vertical scale
	$par->[0] *= $y;
	$par->[1] *= $res->[1];

	$res->[0] = $x;
	$res->[1] = $y;
}

sub pad_transform ($$$$$)
{
	my ($res, $par, $x, $y, $transform_id) = @_;
	if ($res->[0] > $x)
	{
		fail("Horizontal target resolution smaller than video resolution in pad transform $transform_id: $x");
	}
	if ($res->[1] > $y)
	{
		fail("Vertical target resolution smaller than video resolution in pad transform $transform_id: $y");
	}
	$res->[0] = $x;
	$res->[1] = $y;
}

my %transform_map = ('crop'  => \&crop_transform,
                     'scale' => \&scale_transform,
                     'pad'   => \&pad_transform);

eval
{
	if (param("calculate"))
	{
		my $source_type = param("source_type");
		$source_type =~ s/^([^h].*)_.*/$1/;
		my $source_ar   = param("source_ar");
		my $source_res = $source_map->{$source_type}{'resolution'} || fail("Bad source type: $source_type");
		my $source_par = $source_map->{$source_type}{$source_ar} || fail("Bad source ar: $source_ar");
		my $res = [@{$source_res}];
		my $par = [@{$source_par}];
		my $transforms;

		for (my $i = 1; my $transform_type = param("transform_type_$i"); $i++)
		{
			($transform_type eq 'none') && next;
			my $x = int(param("transform_x_$i")) || fail("Bad horizontal dimension in transform $i: ".param("transform_x_$i"));
			my $y = int(param("transform_y_$i")) || fail("Bad vertical dimension in transform $i: ".param("transform_y_$i"));
			(defined($transform_map{$transform_type}) && $transform_map{$transform_type}->($res, $par, $x, $y, $i)) || fail("Bad transform type in transform $i: $transform_type");
			$transform_type =~ s/^(.)/\u$1/;
			$transforms .= "\t\t\t\t<li>$transform_type: $x, $y</li>\n";
		}

		my @display_res;
		if ($par->[0] / $par->[1] > 1)
		{
			$display_res[0] = int(0.5 + $res->[0] * $par->[0] / $par->[1]);
			$display_res[1] = $res->[1];
		}
		else
		{
			$display_res[0] = $res->[0];
			$display_res[1] = int(0.5 + $res->[1] * $par->[1] / $par->[0]);
		}

		$source_ar =~ s/^(.)/\u$1/;
		print "\t\t\t<h2>Results</h2>\n\t\t\t<h3>Numbers</h3>\n\t\t\t<div>Source resolution: $source_res->[0]x$source_res->[1]<br />Source AR: $source_ar<br />Final resolution: $res->[0]x$res->[1]<br />Final PAR: ".frac($par->[0], $par->[1])." = ".($par->[0] / $par->[1])."<br />Final display resolution: $display_res[0]x$display_res[1]<br />Final DAR (square pixels): ".frac($res->[0] * $par->[0], $res->[1] * $par->[1])." = ".($res->[0] * $par->[0] / ($res->[1] * $par->[1]))."</div>\n\t\t\t<h3>Transforms</h3>\n\t\t\t<ol>\n$transforms\t\t\t</ol>\n";
	}
};

if ($@)
{
	my $error = $@;
	if ($error =~ s/^ARCALC: //)
	{
		chomp($error);
		print "\t\t\t<h2>Error</h2>\n\t\t\t<p>An error occured during AR calculation: $error</p>\n";
	}
	else
	{
		die($error);
	}
}

if (param("source"))
{
	seek(DATA, 0, 0);
	my $source = join '', <DATA>;
	$source =~ s/&/&amp;/g;
	$source =~ s/</&lt;/g;
	$source =~ s/>/&gt;/g;
	print "\t\t\t<h2>Source</h2>\n\t\t\t<pre>$source</pre>\n";
}

print <<HTML;
		<p><a href="ar-calc.pl?source=show">Show source</a> (<a href="ar-calc.pl?source=download">Download</a>)</p>
	</body>
</html>
HTML

__DATA__

Show source (Download)