http://qs321.pair.com?node_id=667277
Category: Utility Scripts
Author/Contact Info
Description: Found some of my old drawings I did on my Tandy 1000 using Tandy Deskmate circa 1989, nothing existed to view them or convert them. I was able to figure out the format (for my version of Deskmate anyway), this will generate a BMP from the PNT file.
#!/usr/bin/perl

=head1 NAME

pnt2bmp - Converts "Tandy Personal Deskmate P(ai)NT files" to BMP

=head1 SYNOPSIS

B<pnt2bmp.pl> I<file.pnt>

=head1 DESCRIPTION

Converts "Tandy 1000 Personal Deskmate P(ai)NT files" to BMP. AFAIK, t
+hey are always 312x176. Did this without docs, so may not work on all
+. 
Pixel data is stored in byte pairs, the first byte, two 4-bit pixels, 
+and the second, a repeat count of the two pixel values
http://www.leeland.net/pnt2bmp.html

=head1 AUTHOR

Lee Pumphret
=cut

use strict;
use warnings;

my @tga_palette = map { pack( "CCC", reverse @$_ ) } (
  [ 0x00, 0x00, 0x00 ],    # Black
  [ 0x00, 0x00, 0x99 ],    # Dark Blue
  [ 0x00, 0x99, 0x00 ],    # Dark Green
  [ 0x33, 0x99, 0x99 ],    # Dark Cyan
  [ 0x99, 0x00, 0x00 ],    # Dark Red
  [ 0xCC, 0x33, 0xCC ],    # Dark Magenta
  [ 0xCC, 0x66, 0x00 ],    # Orange
  [ 0x99, 0x99, 0x99 ],    # Light Gray
  [ 0x99, 0x66, 0x33 ],    # Dark Gray
  [ 0x66, 0x33, 0xFF ],    # Light Blue
  [ 0x33, 0xCC, 0x00 ],    # Light Green
  [ 0x66, 0xCC, 0xCC ],    # Light Cyan
  [ 0xFF, 0xCC, 0xCC ],    # Light Red
  [ 0xFF, 0x99, 0xFF ],    #  Light Magenta
  [ 0xFF, 0xFF, 0x00 ],    # Yellow
  [ 0xFF, 0xFF, 0xFF ],    #White
);

my @files = glob( shift @ARGV );

foreach my $file (@files) {

    next and warn "Mismatched extension in filename [$file]... skippin
+g\n"
      unless $file =~ /\.pnt$/i;
    print "processing $file...\n";
    open( IN, "<$file" ) or die "Couldn't open $file :$!";
    binmode(IN);
    my @imdata;
    my $buffer;

    my $PNT_HEADER_SIZE = 22;    # .pnt file header size (I think)

    # Read the header
    ( read( IN, $buffer, $PNT_HEADER_SIZE ) == $PNT_HEADER_SIZE )
      or die "Error reading header data!";
    warn unpack( "h*", $buffer ) . " $file\n";
    my ( $ident, $other ) = unpack( "A4", $buffer );

    unless ( $ident =~ m/PNT$/ ) {
        die "Doesn't look like an PNT file! [$ident]";
    }

    my $count;

    while ( read( IN, $buffer, 2 ) ) {
        my ( $pindex, $run ) = unpack( "CC", $buffer );
        my $p1 = ( $pindex >> 4 ) & 0x0f;
        my $p2 = $pindex & 0x0f;
        push @imdata, ( ( $tga_palette[$p1], $tga_palette[$p2] ) x ($r
+un) );

    }
    my $width = 312;

    unless ( @imdata % $width == 0 ) {

        # If short, pad it out, but probably damaged.
        warn "Padding $file, may be damaged\n";
        push @imdata,
          ( $tga_palette[0] ) x ( $width - ( @imdata % $width ) );
    }
    my ( $x, $y ) = ( $width, @imdata / $width );

    # Write out the bmp file
    ( my $outfile = $file ) =~ s/pnt$/bmp/i;

    my $written = $#imdata;
    open( OUT, ">$outfile" ) or die "Couldn't open output!";
    binmode(OUT);

    # BMP 3.1 Header
    print OUT pack "C C l a a a a l l l l s s l l l l l l", 0x42, 0x4D
+,
      54 + ( $x * 3 ) * $y, 'l', 'e', 'e', 'p', 54, 0x28, $x, $y, 1, 2
+4, 0, 0,
      0, 0, 0, 0;

    # Write out the image data, bottom to top

    while ( $written > 0 ) {

        print OUT @imdata[ $written - ( $x - 1 ) .. $written ];

        $written -= $x;
    }

    close OUT;

}
Replies are listed 'Best First'.
Re: pnt2bmp
by Limbic~Region (Chancellor) on Feb 11, 2008 at 13:36 UTC
    shotgunefx,
    This sounds like a neat exercise. Perhaps you would be inclined to write about how you were able to figure out the format and develop this code. To be honest, I would rather see that then the end result anyway ;-)

    Cheers - L~R

      Well, some of it was just good guessing based on what I remembered.

      The Tandy 1000 had a "TGA" adapter, basically EGA with a fixed 4-bit/16 color palette, and the display was 320x200. So I knew I was more or less looking at 32K of pixel data uncompressed.

      Looking through a bunch of old files, I could tell where the header was and that it was mostly blank. Given the range in size, I had an idea of what the minimum run length could be. The smallest image was something like 297 bytes.

      My initial thought was perhaps that pixels were packed, two to a byte unless it was some special value, then that would indicate run length encoding. I tried to do some statistical analysis of the data in the images, but never my strong suit. So I decided to try something different. Instead of writing a proper bitmap, I encoded each value as a pixel (R=byte value, G=B=0) to visualize it and pretended the bitmap was a width of a power of 2.

      Once I could see it, it was a lot more obvious. I could definitely see patterns (though a corrupted image or two almost threw me off), lot's of vertical lines. The lines made me think that pixels were encoded in pairs, but that didn't quite make sense seeing it was compressed. It seemed wasteful to use a byte for a 4-bit pixel and another byte for the runlength, I thought maybe when a byte was some special value, X bits of it and the next were used for the encoding, but the "lines" in the data didn't seem to support that.

      Next I visualized it but used 3 bytes for each pixel, and it was just noise, so I knew the byte pair was the right track.

      I thought, maybe it's always Odd_byte=2px, Even_byte=run_length. Seemed funny, but then I thought, well, with a 16 color fixed palette, you dither like crazy, so maybe not so unreasonable to compress in pixel pairs.

      I gave it a try and while some pics looked like something (thought nothing you'd expect), some seemed tantalizingly close (harder to tell as I hadn't dug up the palette values yet). Though it's been 17 yrs, some of the file names, rang some bells, in particular a 3D mesh cube I drew, with all it's horizontal lines, was very close.

      Then I thought, maybe they are smaller than 320 due to the real estate used by the UI. A took it back a few pixels and viola!

      Here's a pic of the visualizations of one of the files.
      http://www.leeland.net/lib/leeland/decode-progress.gif

      And the image decoded. (Terminator 1991).
      http://www.leeland.net/lib/leeland/tandy-terminator-1.gif

      Keep in mind, 16 color palette, no mouse, lot's of hand dithering. I think they are pretty good for the time. A couple more for those nostalgic for the PCjr days.

      Arnold/Terminator unfinished '91 (whose idea was it for no fleshy color in the palette!)
      http://www.leeland.net/lib/leeland/tandy-terminator-2.gif

      Some art from a game I was working on in 89.
      http://www.leeland.net/lib/leeland/tandy-war-1.gif
      http://www.leeland.net/lib/leeland/tandy-war-2.gif

      -Lee
      "To be civilized is to deny one's nature."