File talk:Yuv420.svg

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search

Below is the source for the program I wrote to generate this thing. I got a bit carried away because I wanted to learn some more Cairo, so it probably would have been quicker to do this by hand, but at least the grids are nice and regular. --Qef (talk) 08:14, 24 June 2008 (UTC)[reply]

#!/usr/bin/perl
use warnings;
use strict;
use Cairo;
use Math::Trig; # for deg2rad

my $TEST_MODE = 0;

my @TEXT_COLOUR = (0, 0, 0);
my @GRID_COLOUR = (0, 0, 0);
my @ARROW_COLOUR = (0, 0, 0);
my %BYTE_COLOUR_SPEC = (
    blue => [ 0.6, 0.8, 1 ],
    green => [ 0.36, 0.52, 0.15 ],
    yellow => [ 1, 1, 0.6 ],
    red => [ 1, 0.4, 0.2 ],
    purple => [ 0.6, 0.6, 1 ],
    orange => [ 1, 0.71, 0.08 ],
);

my $FONT = 'sans';
my $BYTE_STREAM_FONT_SIZE = 9;
my $MAIN_BOX_FONT_SIZE = 16;
my $HEADINGS_FONT_SIZE = 20;

my $MARGIN = 20.5;
my $MAIN_BOX_INDENT = 5 * $MARGIN;

my $MAIN_BOX_GRID_LINE_WD = 3;
my $MAIN_BOX_GRID_LINE_WD_THIN = 1;
my $MAIN_BOX_GRID_MARGIN_X = 5;
my $MAIN_BOX_GRID_MARGIN_Y = 10;

my $ARROW_LINE_WD = 1;
my ($ARROW_CURVINESS_X, $ARROW_CURVINESS_Y) = (0.5, 0.1);
my ($ARROW_RADIUS, $ARROW_SWEEP) = (15, 20);

my $BYTE_STREAM_GRID_LINE_WD = 1;
my $BYTE_STREAM_GRID_MARGIN_X = 1.5;
my $BYTE_STREAM_GRID_MARGIN_Y = 4;
my @BYTE_STREAM = ((map "Y$_", 1 .. 24),
                   (map "U$_", 1 .. 6),
                   (map "V$_", 1 .. 6));
my %BYTE_COLOUR = qw(
    Y1 blue Y2 blue Y7 blue Y8 blue U1 blue V1 blue
    Y3 green Y4 green Y9 green Y10 green U2 green V2 green
    Y5 yellow Y6 yellow Y11 yellow Y12 yellow U3 yellow V3 yellow
    Y13 red Y14 red Y19 red Y20 red U4 red V4 red
    Y15 purple Y16 purple Y21 purple Y22 purple U5 purple V5 purple
    Y17 orange Y18 orange Y23 orange Y24 orange U6 orange V6 orange
);
die unless @BYTE_STREAM == scalar(keys %BYTE_COLOUR);

my ($IMG_WD, $IMG_HT, $BYTE_STREAM_BOX_WD, $BYTE_STREAM_BOX_HT,
    $MAIN_BOX_WD, $MAIN_BOX_HT) = calculate_image_width();

sub make_surface {
    my ($wd, $ht) = @_;
    return $TEST_MODE ? Cairo::ImageSurface->create('rgb24', $wd, $ht)
                      : Cairo::SvgSurface->create('Yuv420.svg', $wd, $ht);
}

my $surface = make_surface($IMG_WD, $IMG_HT);
my $cr = Cairo::Context->create($surface);

# white background
if ($TEST_MODE) {
    $cr->set_source_rgb(1, 1, 1);
    $cr->rectangle(0, 0, $IMG_WD, $IMG_HT);
    $cr->fill;
}

my $cur_y = $MARGIN;

# Main box of byte names.
$cur_y = heading($cr, $cur_y, 'Single Frame YUV420:');
{
    set_font($cr, $FONT, $MAIN_BOX_FONT_SIZE);
    my $n = 0;
    for my $byte (@BYTE_STREAM) {
        my ($x, $y) = ($n % 6, int($n / 6));
        my ($boxx, $boxy) = ($MAIN_BOX_INDENT + $x * $MAIN_BOX_WD,
                             $cur_y + $y * $MAIN_BOX_HT);

        # background colour
        $cr->set_source_rgb(@{$BYTE_COLOUR_SPEC{$BYTE_COLOUR{$byte}}});
        $cr->rectangle($boxx, $boxy, $MAIN_BOX_WD, $MAIN_BOX_HT);
        $cr->fill;

        # label
        my $extents = $cr->text_extents($byte);
        $cr->move_to($boxx + $MAIN_BOX_WD / 2 - $extents->{width} / 2 - $extents->{x_bearing},
                     $boxy + $MAIN_BOX_HT / 2 - $extents->{height} / 2 - $extents->{y_bearing});
        $cr->set_source_rgb(@TEXT_COLOUR);
        $cr->show_text($byte);
        ++$n;
    }

    # grid of lines inside box
    $cr->set_source_rgb(@GRID_COLOUR);
    for my $x (1 .. 5) {
        for my $y (1 .. 5) {
            # top vertical grid line (sometimes thick, sometimes thin)
            $cr->set_line_width(($x % 2 == 0) ? $MAIN_BOX_GRID_LINE_WD
                                              : $MAIN_BOX_GRID_LINE_WD_THIN);
            $cr->move_to($MAIN_BOX_INDENT + $x * $MAIN_BOX_WD, $cur_y);
            $cr->line_to($MAIN_BOX_INDENT + $x * $MAIN_BOX_WD,
                         $cur_y + 4 * $MAIN_BOX_HT);
            $cr->stroke;

            # bottom vertical grid line (always thick)
            $cr->set_line_width($MAIN_BOX_GRID_LINE_WD);
            $cr->move_to($MAIN_BOX_INDENT + $x * $MAIN_BOX_WD,
                         $cur_y + 4 * $MAIN_BOX_HT);
            $cr->line_to($MAIN_BOX_INDENT + $x * $MAIN_BOX_WD,
                         $cur_y + 6 * $MAIN_BOX_HT);
            $cr->stroke;

            # horizontal grid line
            $cr->set_line_width(($y == 2 || $y == 4 || $y == 5)
                    ? $MAIN_BOX_GRID_LINE_WD : $MAIN_BOX_GRID_LINE_WD_THIN);
            $cr->move_to($MAIN_BOX_INDENT, $cur_y + $y * $MAIN_BOX_HT);
            $cr->line_to($MAIN_BOX_INDENT + 6 * $MAIN_BOX_WD,
                         $cur_y + $y * $MAIN_BOX_HT);
            $cr->stroke;
        }
    }

    # outside of box
    $cr->set_source_rgb(@GRID_COLOUR);
    $cr->set_line_width($MAIN_BOX_GRID_LINE_WD);
    $cr->rectangle($MAIN_BOX_INDENT, $cur_y,
                   6 * $MAIN_BOX_WD, 6 * $MAIN_BOX_HT);
    $cr->stroke;

    # curvy lines with arrows on
    arrow_line($cr, $MAIN_BOX_INDENT, $cur_y + $MAIN_BOX_HT,
               $cur_y + 4.5 * $MAIN_BOX_HT, -1, 160, 200);
    arrow_line($cr, $MAIN_BOX_INDENT, $cur_y + $MAIN_BOX_HT,
               $cur_y + 5.5 * $MAIN_BOX_HT, -1, undef, 200);
    arrow_line($cr, $MAIN_BOX_INDENT + 6 * $MAIN_BOX_WD,
               $cur_y + 3 * $MAIN_BOX_HT, $cur_y + 4.5 * $MAIN_BOX_HT, +1,
               30, 325);
    arrow_line($cr, $MAIN_BOX_INDENT + 6 * $MAIN_BOX_WD,
               $cur_y + 3 * $MAIN_BOX_HT, $cur_y + 5.5 * $MAIN_BOX_HT, +1,
               undef, 335);

    $cur_y += $MAIN_BOX_HT * 6 + $MARGIN;
}

# order of byte stream at bottom of picture.
$cur_y = heading($cr, $cur_y, 'Position in byte stream:');
{
    set_font($cr, $FONT, $BYTE_STREAM_FONT_SIZE);
    my $x = $MARGIN;
    my $y = $cur_y;
    for my $byte (@BYTE_STREAM) {
        # background
        $cr->set_source_rgb(@{$BYTE_COLOUR_SPEC{$BYTE_COLOUR{$byte}}});
        $cr->rectangle($x, $y, $BYTE_STREAM_BOX_WD, $BYTE_STREAM_BOX_HT);
        $cr->fill;

        # label
        my $extents = $cr->text_extents($byte);
        $cr->move_to($x + $BYTE_STREAM_BOX_WD / 2 - $extents->{width} / 2 - $extents->{x_bearing},
                     $y + $BYTE_STREAM_BOX_HT / 2 - $extents->{height} / 2 - $extents->{y_bearing});
        $cr->set_source_rgb(@TEXT_COLOUR);
        $cr->show_text($byte);

        # line to left
        if ($byte ne 'Y1') {
            $cr->move_to($x, $y);
            $cr->line_to($x, $y + $BYTE_STREAM_BOX_HT);
            $cr->set_source_rgb(@GRID_COLOUR);
            $cr->set_line_width($BYTE_STREAM_GRID_LINE_WD);
            $cr->stroke;
        }

        $x += $BYTE_STREAM_BOX_WD;
    }

    # box round outside
    $cr->rectangle($MARGIN, $y, $IMG_WD - 2 * $MARGIN, $BYTE_STREAM_BOX_HT);
    $cr->set_source_rgb(@GRID_COLOUR);
    $cr->set_line_width($BYTE_STREAM_GRID_LINE_WD);
    $cr->stroke;
}

$cr->show_page;
$surface->write_to_png('Yuv420.png') if $TEST_MODE;

sub set_font {
    my ($cr, $face, $size) = @_;
    $cr->select_font_face($face, 'normal', 'normal');
    $cr->set_font_size($size);
}

sub heading {
    my ($cr, $y, $text) = @_;
    set_font($cr, $FONT, $HEADINGS_FONT_SIZE);
    my $extents = $cr->text_extents($text);
    $cr->move_to($MARGIN + $extents->{x_bearing}, $y - $extents->{y_bearing});
    $cr->set_source_rgb(@TEXT_COLOUR);
    $cr->show_text($text);
    return $y + $extents->{height} + $MARGIN / 2;
}

sub draw_arrow {
    my ($cr, $x, $y, $a) = @_;
    $cr->move_to($x, $y);
    $cr->line_to($x + $ARROW_RADIUS * cos(deg2rad($a + $ARROW_SWEEP)),
                 $y + $ARROW_RADIUS * sin(deg2rad($a + $ARROW_SWEEP)));
    $cr->line_to($x + $ARROW_RADIUS * cos(deg2rad($a - $ARROW_SWEEP)),
                 $y + $ARROW_RADIUS * sin(deg2rad($a - $ARROW_SWEEP)));
    $cr->close_path;
    $cr->set_source_rgb(@ARROW_COLOUR);
    $cr->fill;
}

sub arrow_line {
    my ($cr, $x, $y1, $y2, $dir, $ang1, $ang2) = @_;
    my $dist = $y2 - $y1;
    $cr->move_to($x, $y1);
    $cr->curve_to($x + $ARROW_CURVINESS_X * $dir * $dist,
                  $y1 + $ARROW_CURVINESS_Y * $dist,
                  $x + $ARROW_CURVINESS_X * $dir * $dist,
                  $y2 - $ARROW_CURVINESS_Y * $dist,
                  $x, $y2);
    $cr->set_source_rgb(@ARROW_COLOUR);
    $cr->set_line_width($ARROW_LINE_WD);
    $cr->stroke;

    draw_arrow($cr, $x, $y1, $ang1) if defined $ang1;
    draw_arrow($cr, $x, $y2, $ang2) if defined $ang2;
}

sub calculate_image_width {
    # create a temporary context for getting text widths.
    my $surface = make_surface(100, 100);
    my $cr = Cairo::Context->create($surface);

    # find size of widest and highest of the byte names.
    my ($maxwd, $maxht) = (0, 0);
    my ($mainmaxwd, $mainmaxht) = (0, 0);
    for (@BYTE_STREAM) {
        set_font($cr, $FONT, $BYTE_STREAM_FONT_SIZE);
        my $extents = $cr->text_extents($_);
        $maxwd = $extents->{width}  if $extents->{width} > $maxwd;
        $maxht = $extents->{height} if $extents->{height} > $maxht;
        set_font($cr, $FONT, $MAIN_BOX_FONT_SIZE);
        $extents = $cr->text_extents($_);
        $mainmaxwd = $extents->{width}  if $extents->{width} > $mainmaxwd;
        $mainmaxht = $extents->{height} if $extents->{height} > $mainmaxht;
    }

    my $byte_box_wd = $maxwd + $BYTE_STREAM_GRID_LINE_WD +
                      2 * $BYTE_STREAM_GRID_MARGIN_X;
    my $byte_box_ht = $maxht + 2 * $BYTE_STREAM_GRID_MARGIN_Y +
                      $BYTE_STREAM_GRID_LINE_WD;
    my $main_box_wd = $mainmaxwd + $MAIN_BOX_GRID_LINE_WD +
                      2 * $MAIN_BOX_GRID_MARGIN_X;
    my $main_box_ht = $mainmaxht + 2 * $MAIN_BOX_GRID_MARGIN_Y +
                      $MAIN_BOX_GRID_LINE_WD;
    my $imgwd = 2 * $MARGIN + $byte_box_wd * @BYTE_STREAM;
    return ($imgwd, 4 * $MARGIN + 6 * $main_box_ht + $byte_box_ht + 2 * $HEADINGS_FONT_SIZE,
            $byte_box_wd, $byte_box_ht, $main_box_wd, $main_box_ht);
}