Generating MODE 2 screens on a PC

handy tools that can assist in the development of new software
Post Reply
julie_m
Posts: 244
Joined: Wed Jul 24, 2019 9:53 pm
Location: Derby, UK
Contact:

Generating MODE 2 screens on a PC

Post by julie_m » Sat Jul 18, 2020 3:26 pm

I'm not sure this qualifies as a full-on development tool; but it worked first time when I tried it and somebody might find it useful, so I thought I would tidy it up a little and share it.

It uses the PerlMagick interface to the ImageMagick library to read in an image file, up to 320 pixels by 256, and build up an array of the nearest colour values using a simple threshhold based system. if the red channel is above a certain threshhold, its corresponding array entry will have bit 0 set. If the green channel is above a certain threshhold, the array entry will have bit 1 set; and if the blue channel is above a certain threshhold, the array entry will have bit 2 set. This will result in the closest matching colour chosen from the BBC's MODE 2 palette.

Having created this array, we then work through it in the same order as the bytes are arranged in the frame buffer RAM. As our array is 320*256 but the MODE 2 screen is only 160*256, we need to discard half the data. Each byte in the BBC frame buffer data corresponds to two physical pixels; we will use the first and third logical pixels as our left and right pixels on an even row, and the second and fourth logical pixels on an odd row.

Code: Select all

#!/usr/bin/perl -w

# NO RIGHTS RESERVED
# GIFTED TO THE PUBLIC DOMAIN BY JULIE MONTOYA 2020

use strict;
use Image::Magick;
use Data::Dumper;
use Getopt::Std;

my %OPTIONS;
getopts "b:g:i:o:r:", \%OPTIONS;
my $R_THRESH    = $OPTIONS{"r"} // .5;
my $G_THRESH    = $OPTIONS{"g"} // .5;
my $B_THRESH    = $OPTIONS{"b"} // .5;
my $INPUT_FILE  = $OPTIONS{"i"} // "-";
my $OUTPUT_FILE = $OPTIONS{"o"};

my $image = new Image::Magick;
my ($width, $height, $x, $y, $r, $g, $b, $c, $c1, $c2, $char_row, $byte,
    $scanline);
my @beeb_screen;
my $screen_bytes = "";

$image->Read($INPUT_FILE);

$width = $image->Get("width");
$height = $image->Get("height");

$x = $y = 0;

#  Build up an image of the screen on the Beeb

for ( $y = 0; $y < 256; ++$y ) {
    for ( $x = 0; $x < 320; ++$x ) {
        $c = 0;
        if ($x < $width && $y < $height) {
            ($r, $g, $b) = $image->GetPixel("x" => $x, "y" => $y);
            $c |= 1 if ($r > $R_THRESH);
            $c |= 2 if ($g > $G_THRESH);
            $c |= 4 if ($b > $B_THRESH);
        };
        $beeb_screen[$y]->[$x] = $c;
    };
};

#  Now @beeb_screen is a 320 by 256 array of pixel colours in range 0-7
#  We need to de-linearise this into BBC order

for ( $char_row = 0; $char_row < 32; ++$char_row ) {
    for ( $byte = 0; $byte < 80; ++$byte ) {
        for ( $scanline = 0; $scanline < 8; ++$scanline ) {
            $y = 8 * $char_row + $scanline;
            $x = 4 * $byte;
            
            #  use pixel columns 0 and 2 on even rows, 1 and 3 on odd
            $c1 = $beeb_screen[$y][$x + 2 + $y % 2];
            $c2 = $beeb_screen[$y][$x + $y % 2];

            #  interleave the bits for the BBC's frame buffer
            $c = 0;
            $c |= 1 if ($c1 & 1);
            $c |= 2 if ($c2 & 1);
            $c |= 4 if ($c1 & 2);
            $c |= 8 if ($c2 & 2);
            $c |= 16 if ($c1 & 4);
            $c |= 32 if ($c2 & 4);

            $screen_bytes .= pack "C", $c;
        };
    };
};

if ($OUTPUT_FILE) {
    open OFH, ">", $OUTPUT_FILE or die "Could not open $OUTPUT_FILE: $!";
    print OFH $screen_bytes;
    close OFH;
}
else {
    print $screen_bytes;
}

exit;
Command line options are -r, -g, -b for the colour threshholds (between 0 and 1, default 0.5); -i for the input file; and -o for the output file. If no output file is specified, the output will be sent to STDOUT.

Here's what the output looks like:
brighton2_beeb.png
Corrected program output
brighton2_beeb.png (8.21 KiB) Viewed 509 times
And here's the original picture:
brighton1.png
Original image
brighton1.png (194.99 KiB) Viewed 520 times
This is the output from an earlier version with a mistake in it. Can you spot what I did wrong?
brighton1_beeb.png
Program output displayed on BeebEm
brighton1_beeb.png (8.8 KiB) Viewed 520 times

User avatar
scruss
Posts: 293
Joined: Sun Jul 01, 2018 4:12 pm
Location: Toronto
Contact:

Re: Generating MODE 2 screens on a PC

Post by scruss » Sat Jul 25, 2020 9:52 pm

Neat! I just found out about Dithertron, which I tried out on your image in BBC Mode 2. Here's an image uncorrected for aspect ratio:
brighton1-bbcmicro.mode2.png
brighton from Dithertron
brighton1-bbcmicro.mode2.png (16.58 KiB) Viewed 379 times
I wrote about Dithertron and its associated IDE here: viewtopic.php?f=55&t=20047

julie_m
Posts: 244
Joined: Wed Jul 24, 2019 9:53 pm
Location: Derby, UK
Contact:

Re: Generating MODE 2 screens on a PC

Post by julie_m » Sat Jul 25, 2020 10:32 pm

That's pretty good!

I actually had a go at dithering colours myself. My first attempt suggested "you'll have to try harder than that", but then the day job got in the way of my adventures with the ImageMagick documentation (no point importing a dog from a library and barking yourself, and all that; it has methods to replicate anything you can do with the command line).

User avatar
scruss
Posts: 293
Joined: Sun Jul 01, 2018 4:12 pm
Location: Toronto
Contact:

Re: Generating MODE 2 screens on a PC

Post by scruss » Sun Jul 26, 2020 2:22 am

I've always found ImageMagick to be unnecessarily hard to use. Far too many options, and sometimes not great output. Netpbm is what I use to dither to a limited palette. It's best to build it from source so you're not stuck with the 20+ year old version that comes packaged with most Linux distributions.

Post Reply

Return to “development tools”