Information wants to be free...

Polaroid Digital 320 Camera

While digging around in my collection of junk (consisting of different hardware and electronics gathered over time), I found an old digital camera with the label "Polaroid Digital 320", presumably made sometime in 2001.

I searched the web to see if there was any Linux driver for this camera. It seems that some people have been trying to make a driver some time ago. The effort seems to have faded away, as there was little help to get from Polaroid (the camera maker) and Xirlink (the chip maker).

I decided to try on my own to get the picture data out of the camera, and learn more about the JPEG file format in the process. I was able to get further than the other people that have tried, because I was able to figure out more about the proprietary file format used by the camera, and how it differs from the official JPEG standard. (Details on these differences can be found in the README file in the source code.) This was possible because I made a JPEG decoder from scratch, and made adjustments to it everywhere to see what produced the best picture.

However, the result is not perfect, since there is still information missing about the optimal quantization for the different components in the picture. I attempted to reverse engineer parts of the official TWAIN driver from Polaroid, but with no success.

The source code for the download and decoder tool can be downloaded here. The code is released under a MIT licence, so feel free to reuse the code wherever you want.

The tool produces pictures in the portable PPM and PGM formats. You can use Netpbm to convert them something else.

Here is an example of a picture downloaded in three formats: color, grey scale and raw components (luminance, blue chrominance, red chrominance and another luminance component):

Color.

Grey scale (luminance only).

Raw components (Y1, Cb, Cr, Y2).


Topic: Open Source, by Kjetil @ 20/12-2008, Article Link

Binary Dumping Tool

Here is a homemade tool that I use when dealing with binary files. The tool will convert binary files to its binary textual representation (like 0011010000110010) and back. It can also convert to hexadecimal representation and back.

The tool is typically used for analysis (look for patterns during cryptography, steganography, etc.) or to do transformations. By transformations I mean converting to e.g. hex, change the data with some other tool and then covert back to binary.

I find the source code to be too short to be considered an "application" (with a licence), so I will just post the code directly into the public domain:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

typedef enum {
  CONV_NONE,
  CONV_TO_HEX,
  CONV_FROM_HEX,
  CONV_TO_BIN,
  CONV_FROM_BIN,
} conv_t;

static void display_help(void)
{
  fprintf(stderr, "\nOptions:\n"
     "  -h        Display this help and exit.\n"
     "  -s        Enable strict mode:\n"
     "              Disables formated output when converting 'to' or\n"
     "              warns about unknown characters when converting 'from'.\n"
     "  -o FILE   Write output to FILE instead of standard out.\n"
     "  -b        Convert to binary representation.\n"
     "  -B        Convert from binary representation.\n"
     "  -x        Convert to hex representation.\n"
     "  -X        Convert from hex representation.\n\n");
}

static int power_of_two(int n)
{
  if (n == 0)
    return 1;
  else
    return 2 * power_of_two(n - 1);
}

static void bin_output(FILE *out, int c)
{
  int i;
  for (i = 7; i >= 0; i--) {
    if (c - power_of_two(i) >= 0) {
      fprintf(out, "1");
      c -= power_of_two(i);
    } else
      fprintf(out, "0");
  }
}

static int bin_scan(FILE *in, int strict)
{
  int c, value, n;

  value = 0; 
  n = 7;
  while ((c = fgetc(in)) != EOF) {
    if (c == '0') {
      if (n <= 0)
        return value;
      n--;

    } else if (c == '1') {
      if (n <= 0)
        return value + power_of_two(n);
      else
        value += power_of_two(n);
      n--;

    } else {
      if (strict) {
        fprintf(stderr, "%s: not a 1 or 0 in conversion: 0x%02x\n",
          __FILE__, c);
        return -2;
      }
    }
  }

  return EOF;
}

static int hex_value(int c)
{
  if (c >= 0x30 && c <= 0x39) /* '0' - '9' */
    return c - 0x30;
  else if (c >= 0x41 && c <= 0x46) /* 'A' - 'F' */
    return (c - 0x41) + 10;
  else if (c >= 0x61 && c <= 0x66) /* 'a' - 'f' */
    return (c - 0x61) + 10;
  else
    return -1;
}

static int hex_scan(FILE *in, int strict)
{
  int c, high = -1;

  while ((c = fgetc(in)) != EOF) {
    if (hex_value(c) >= 0) {
      if (high == -1)
        high = c;
      else
        return hex_value(high) * 16 + hex_value(c);
    } else {
      if (strict) {
        fprintf(stderr, "%s: not a hex character in conversion: 0x%02x\n",
          __FILE__, c);
        return -2;
      }
    }
  }

  return EOF;
}

static void convert(conv_t conv, int strict, FILE *in, FILE *out)
{
  int c, space = 0;

  switch (conv) {
  case CONV_TO_BIN:
    while ((c = fgetc(in)) != EOF) {
      bin_output(out, c);
      if (! strict) {
        if (space % 8 == 7)
          fprintf(out, "\n");
        else if (space % 1 == 0)
          fprintf(out, " ");
      }
      space++;
    }
    if (! strict)
      fprintf(out, "\n");
    break;

  case CONV_FROM_BIN:
    while ((c = bin_scan(in, strict)) != EOF) {
      if (c >= 0)
        fprintf(out, "%c", c);
    }
    break;

  case CONV_TO_HEX:
    while ((c = fgetc(in)) != EOF) {
      fprintf(out, "%02x", c);
      if (! strict) {
        if (space % 24 == 23)
          fprintf(out, "\n");
        else if (space % 4 == 3)
          fprintf(out, "  ");
        else if (space % 1 == 0)
          fprintf(out, " ");
      }
      space++;
    }
    if (! strict)
      fprintf(out, "\n");
    break;

  case CONV_FROM_HEX:
    while ((c = hex_scan(in, strict)) != EOF) {
      if (c >= 0)
        fprintf(out, "%c", c);
    }
    break;

  default:
    break;
  }
}

int main(int argc, char *argv[])
{
  int c, i;
  FILE *in = NULL, *out = NULL;
  char *outfile = NULL;
  int strict = 0;
  conv_t conv = CONV_NONE;

  while ((c = getopt(argc, argv, "hso:bBxX")) != -1) {
    switch (c) {
    case 'h':
      display_help();
      exit(EXIT_SUCCESS);

    case 's':
      strict = 1;
      break;

    case 'o':
      outfile = optarg;
      break;

    case 'b':
    case 'B':
    case 'x':
    case 'X':
      if (conv != CONV_NONE) {
        fprintf(stderr,
          "%s: only one of the options b, B, x or X can be set.\n", __FILE__);
        display_help();
        exit(EXIT_FAILURE);
      } else {
        if (c == 'b')
          conv = CONV_TO_BIN;
        else if (c == 'B')
          conv = CONV_FROM_BIN;
        else if (c == 'x')
          conv = CONV_TO_HEX;
        else if (c == 'X')
          conv = CONV_FROM_HEX;
      }
      break;

    case '?':
    default:
      display_help();
      exit(EXIT_FAILURE);
    }
  }

  if (conv == CONV_NONE) {
    fprintf(stderr,
      "%s: one of the options b, B, x or X must be set.\n", __FILE__);
    display_help();
    exit(EXIT_FAILURE);
  }

  if (outfile == NULL)
    out = stdout; /* Default to stdout if no output is specified. */
  else {
    out = fopen(outfile, "w");
    if (out == NULL) {
      fprintf(stderr, "%s: unable to open output file for writing: %s\n",
        __FILE__, strerror(errno));
      exit(EXIT_FAILURE);
    }
  }

  if (argc == optind) /* No input files specified, use stdin. */
    convert(conv, strict, stdin, out);
  else {
    for (i = optind; i < argc; i++) {
      in = fopen(argv[i], "r");
      if (in == NULL) {
        fprintf(stderr, "%s: unable to open input file for reading: %s\n",
          __FILE__, strerror(errno));
        continue; /* Do not abort, try the next file. */
      }

      convert(conv, strict, in, out);
      fclose(in);
    }
  }

  fclose(out);
  return EXIT_SUCCESS;
}
          


Topic: Scripts and Code, by Kjetil @ 15/11-2008, Article Link

Permutations by Swapping

A while back, I discovered an algorithm to get all possible permutations (unordered) of a data set, by swapping the elements in a particular way.

I do not know if this is a widely known method, please comment if you have seen this way of finding permutations before.

The swapping sequence is a follows (1-n = swap first element with n-element.):
1-2, 1-3, 1-2, 1-3, 1-2, 1-4, 1-2, 1-3, 1-2, 1-3, 1-2, 1-4, 1-2, ...

To find the element that should be swapped with the first element, the sequence counter/state is divided by the highest possible factorial that will not produce a remainder. Then the basis for the factorial is incremented by one to find the element to swap with.

Example: If the sequence counter is 12, and 12 divided by 3! (3 factorial = 6), there is no remainder. Then the first element needs to be swapped with element 4 (3 + 1) in this iteration of the sequence. If the sequence counter is 13, this is only divisible by 1! and then needs to be swapped with element 2 (1 + 1).

Here is some proof of concept code in C (Note that this algorithm is not recursive, unlike most other permutation algorithms.):

#include <stdio.h>

#define SIZE 5
static int numbers[SIZE] = {1, 2, 3, 4, 5};

static int factorial(int n)
{
  if (n == 0)
    return 1;
  else
    return n * factorial(n - 1);
}

int main(void)
{
  int i, j, k, temp;
  int permute_state;

  /* Print for visualization. (Initial values.) */
  for (j = 0; j < SIZE; j++)
    printf("%d", numbers[j]);
  printf("\n");

  permute_state = 1;
  for (i = 0; i < factorial(SIZE) - 1; i++) {
    for (j = SIZE; j > 0; j--) {
      if (permute_state % factorial(j) == 0) {
        for (k = 0; k < (j / 2) + 1; k++) {
          temp = numbers[k];
          numbers[k] = numbers[j-k];
          numbers[j-k] = temp;
        }
        break;
      }
    }
    permute_state++;

    /* Print for visualization. */
    for (j = 0; j < SIZE; j++)
      printf("%d", numbers[j]);
    printf("\n");
  }

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 26/10-2008, Article Link

TinyBasic in Lex/Yacc

Lex and Yacc are two wonderful Unix tools that can create lexical analyzers and parsers for interpreters or compilers of programming languages. I used the modern counterparts from the GNU project; Flex and Bison to create an interpreter for TinyBasic, a simple dialect of BASIC.

The reference implementation for TinyBasic can be found in the first issue of the computer magazine Doctor Dobb's Journal. The implementation I have made differs a little, and has some extensions. More information can be found in the README file in the source tarball.

Although using BASIC is considered harmful, it is very easy to learn, and therefore easy to write an interpreter for.

Here is some sample BASIC code that will work with the interpreter:
(Same program that I provided for the Attoscript interpreter earlier.)

10 LET C = 10
20 PRINT "There are ", C, " bottles of prune juice on the wall."
30 LET C = C - 1
40 IF C <> 0 THEN GOTO 20
          


The source code can be downloaded here. (Licenced under GNU GPL version 3.)

Topic: Open Source, by Kjetil @ 20/09-2008, Article Link

Amiga 500 Advertisement Leaflet

A colleague at work approached me yesterday, presenting a leaflet. It was a Norwegian advertisement for the Commodore Amiga 500, presumably printed during its heyday in the late 1980s.

I have scanned the leaflet and put it up here for you to enjoy. Some readers may notice the similarity compared to other home computer advertisements at the time: Everybody is happy, because computers are fun! :-)

Higher resolution TIFF images can be downloaded here and here.

Amiga 500 leaflet side 1.

Amiga 500 leaflet side 2.


Topic: Mundane, by Kjetil @ 13/09-2008, Article Link

Temperature Monitor in Perl/Tk

This is a simple script that fetches the current temperature for a geographical location and displays it. It is similar to the alarm clock script I made earlier.

I attempted to see if I could get the weather information from Storm Weather Center, but I was disappointed to see that they want money to provide this information. It is even more disappointing that they actually threaten to sue anyone who gets their information without approval.

Luckily, we have weather.com that provides weather information for free, if it is for personal use. This information can be fetched with RSS, if you know the weather.com specific location code.

Note that you need the Perk/Tk module and the XML-RSS-Parser module to use the script.

#!/usr/bin/perl -w
use strict;
use LWP::Simple;
use XML::RSS::Parser;
use Tk;

# Location code examples: NOXX0035 (Stavanger), NOXX0029 (Oslo).
# Find more at: http://www.weather.com/weather/rss/subscription

my ($update_rate, $location);
$location    = shift @ARGV or die "Usage: $0 <location code> [update rate]\n";
$update_rate = shift @ARGV or $update_rate = 15; # Default is 15 minutes.

my $url = "http://rss.weather.com/weather/rss/local/". $location . 
          "?cm_ven=LWO&cm_cat=rss&par=LWO_rss";

my $parser = XML::RSS::Parser->new;
my $window = new Tk::MainWindow;
$window->repeat($update_rate * 1000 * 60, \&get_temperature);
my $label = $window->Label(-foreground => "black",
                           -background => "white",
                           -font => 'Helvetica 36 bold')->pack();

$label->configure(-text => "Regex error!"); # If regex fails, this is displayed.
&get_temperature;
Tk::MainLoop;

sub get_temperature {
  my $data = get($url);
  unless (defined $data) {
    $label->configure(-text => "No contact!");
    return;
  }

  my $feed = $parser->parse_string($data);
  unless (defined $feed) {
    $label->configure(-text => "Parse error!");
    return;
  }

  for ($feed->query("/channel/item")) {
    my $node = $_->query("title");
    if ($node->text_content =~ m/current/i) {
      $node = $_->query("description");
      if ($node->text_content =~ m/(\d+)\s*&deg/) {
        my $deg = (($1 - 32) / 9) * 5; # Fahrenheit to Celsius.
        $label->configure(-text => sprintf("%.1f\x{B0}C", $deg));
      }
    }
  }
}
          


Topic: Scripts and Code, by Kjetil @ 10/08-2008, Article Link

Xlib Calculator

This program is only an experiment, and is not intended for practical use. It has many shortcomings, and there is already a better calculator named xcalc that is present in most X distributions.

The purpose was to try out programming directly against the low-level Xlib library. I have released the source code under a MIT License here. Feel free to look around in the code.

Here is a screenshot of the simple calculator:

Xlib calculator screenshot


Topic: Open Source, by Kjetil @ 25/07-2008, Article Link

Alarm Clock in Perl/Tk

I had a metallic egg-timer that met a gruesome fate, after I discovered that it was in fact made of plastic inside. Instead of buying a new physical timer/alarm clock, I decided to write a virtual one.

Below is a Perl script that will display a countdown, starting from a specified amount of minutes. When the counter reaches zero, the text in the window will blink red, in an attempt to get your attention. To make sure you see this, it is recommended to use the "Always on top" feature of your window manager.

If required, it would probably be easy to add sound support, by just hacking the script to include something like: system("aplay bell.wav");

Note that you need the Perl/Tk module to use the script.

#!/usr/bin/perl -w
use strict;
use Tk;

my $minutes = shift @ARGV
  or die "Usage: $0 <minutes>\n";

my $target = time + ($minutes * 60);

my $window = new Tk::MainWindow;
$window->repeat(50, \&update_display);
my $label = $window->Label(-foreground => "black",
                           -background => "white",
                           -font => 'Helvetica 72 bold')->pack();

Tk::MainLoop;

sub update_display {
  my $left = $target - time; # Seconds left.
  if ($left > 0) {
    $label->configure(-text => sprintf("%02d:%02d:%02d",
      ($left / 3600) % 60, ($left / 60) % 60, $left % 60));
  } else {
    if ($left % 2 == 0) {
      $label->configure(-text => "Alarm!", -foreground => "red");
    } else {
      $label->configure(-text => "Alarm!", -foreground => "black");
    }
  }
}
          


Topic: Scripts and Code, by Kjetil @ 11/07-2008, Article Link

Simple TO-DO Application

I have made yet another curses-based application. This time it's a simple tracker of things to do that are stored in a list, and managed from a terminal user interface.

Entries are actually edited by calling an external text editor (like vim). In order to do this, a temporary file is created. This file contains the editable fields, and the information is read back when the editor is closed. This may be considered unsafe, so think twice about putting sensitive information in, if the application is used in a multi-user environment.

I have released the source under the GNU GPL version 3, and it can be downloaded here. I have managed to compile this application against the PDCurses-3.3 library instead of the more common (n)curses library. This makes it possible to make Windows binaries as well.

Here is a screenshot of a PDCurses (using SDL) compilated version:

Todo application with PDCurses screenshot

(The entries are taken from this guide, in case you're interested.)

Topic: Open Source, by Kjetil @ 29/06-2008, Article Link

File Shuffler

This is a small Perl script to shuffle files. By that, I mean take a directory of files and put a unique random number before (as a prefix to) each file. When the files in the directory are listed alphabetically afterwards, they will actually be listed randomly.

I had need of this because the MP3 player function in my car stereo plays the files it find on the CD alphabetically. I wanted to randomize/shuffle the songs, and the workaround was to use this script on the files before burning the CD.

Here is the code:

#!/usr/bin/perl -w
use strict;
use List::Util qw/shuffle/;

my $directory = shift
  or die "Usage: $0 <directory>\n";

chdir $directory
  or die "Error: Could not change to directory.\n";

my @files = glob "*";

# Find digits in amount of files.
my $digits = 0;
for (split //, scalar(@files)) {
  $digits++;
}

for my $number (&shuffle(1..scalar(@files))) {
  my $file = shift @files;
  my $new = sprintf "%0${digits}d--%s", $number, $file;
  rename $file, $new;
}
          


Topic: Scripts and Code, by Kjetil @ 18/05-2008, Article Link

Copenhagen Airport uses VLC.

It seems they are using the VLC media player to run some of their advertisements in Copenhagen "Kastrup" airport. Unfortunately, they have some stability issues.

I took this snapshot as proof on my way home from a business trip:

Kastrup uses VLC


Here is a closer look of the error message (in danish):

Zoom of error message


Topic: Mundane, by Kjetil @ 03/05-2008, Article Link

Perl YouTube Fetcher

I have created a small Perl script to fetch (download) videos from YouTube. Simply supply the video URL as an argument to the script, and the video file will be downloaded to the current directory.

In order to run the script, you need to have the LWP::UserAgent module from libwww-perl installed. It will hopefully function as long as they do not decide to do any major changes to the YouTube website.

The final video file should be viewable with media players like MPlayer.

#!/usr/bin/perl -w
use strict;
use LWP::UserAgent;

my $url = shift @ARGV
  or die "Usage: $0 <youtube URL>\n";

my $browser = LWP::UserAgent->new;

my $response = $browser->get($url);
die "Failed to get URL: $url [" . $response->status_line . "]\n"
  unless $response->is_success;

(my $id = $url) =~ s/^.*?v=(.*)$/$1/; # Get the youtube ID.

# Attempt to locate the flash arguments in the content.
my $forged_url;
if ($response->content =~ m/swfArgs\s*=\s*{([^}]*)}/) {
  # Convert arguments from JSON format to a new forged URL.
  $forged_url = $1;
  $forged_url =~ s/"//g;
  $forged_url =~ s/\s*:\s*/=/g;
  $forged_url =~ s/\s*,\s*/&/g;
  $forged_url = "http://youtube.com/get_video?" . $forged_url;
} else {
  die "Did not find flash arguments.\n";
}

# Do a HEAD request to find the total size first.
$response = $browser->head($forged_url);
die "Failed to get headers of forged URL: $forged_url [" . 
  $response->status_line . "]\n" unless $response->is_success;
my $size = $response->headers->content_length;

# Fetch all of the video data with a callback to display progress.
my $video;
$response = $browser->get($forged_url, ":content_cb" => \&content_callback);
die "Failed to get forged URL: $forged_url [" . $response->status_line . "]\n"
  unless $response->is_success;

# Write the video data to file.
open WRITEVIDEO, ">$id.flv"
  or die "Could not open file '$id.flv' for writing: $!\n";
print WRITEVIDEO $video;
close WRITEVIDEO;

print STDERR "Saved video to file: $id.flv\n";

sub content_callback
{
  my($data, $response, $protocol) = @_;
  $video .= $data;
  printf STDERR "%.1f%%\r", (length($video) / $size) * 100;
}
          


Topic: Scripts and Code, by Kjetil @ 24/04-2008, Article Link

BMP Header Info

Following the tradition of analysing image headers, I have made another small program in C to read header information from BMP (BitMaP) images. There does not seem to be a complete specification of BMP images, so the program is based on various information found on the Internet.

This program works the same way as the other one; by reading the image as standard input, and presenting information in human readable form on standard out.

#include <stdio.h>

typedef struct bmp_infoheader_s {
   unsigned long int header_size;
   signed long int width, height;
   unsigned short int planes;
   unsigned short int bits;
   unsigned long int compression;
   unsigned long int image_size;
   signed long int xres, yres;
   unsigned long int colors;
   unsigned long int imp_colors;
} bmp_infoheader_t;

int main(void)
{
  bmp_infoheader_t infoheader;
  char *p;

  /* First header cannot be defined as a structure, because of aligment
     problems with the compiler. */
  unsigned short int bmp_header_type;
  unsigned long int bmp_header_size;
  unsigned short int bmp_header_reserved_1, bmp_header_reserved_2;
  unsigned long int bmp_header_offset;

  /* Read first header (in many steps). */
  fread(&bmp_header_type, sizeof(unsigned short int), 1, stdin);
  fread(&bmp_header_size, sizeof(unsigned long int), 1, stdin);
  fread(&bmp_header_reserved_1, sizeof(unsigned short int), 1, stdin);
  fread(&bmp_header_reserved_2, sizeof(unsigned short int), 1, stdin);
  fread(&bmp_header_offset, sizeof(unsigned long int), 1, stdin);
  if (feof(stdin)) {
    fprintf(stderr, "Error: Could not read first BMP header.\n");
    return 1;
  }

  if (bmp_header_type != 19778) { /* (77[M] * 256) + 66[B] = 19778 */
    fprintf(stderr, "Error: Not a BMP image. (%d)\n", bmp_header_type);
    return 1;
  }
  printf("--- First Header ---\n");
  printf("File size        : %lu bytes\n", bmp_header_size);
  printf("Reserved 1       : %hd\n", bmp_header_reserved_1);
  printf("Reserved 2       : %hd\n", bmp_header_reserved_2);
  printf("Image data offset: 0x%lx\n", bmp_header_offset);

  /* Read second header (infoheader). */
  fread(&infoheader, sizeof(bmp_infoheader_t), 1, stdin);
  if (feof(stdin)) {
    fprintf(stderr, "Error: Could not read second BMP header.\n");
    return 1;
  }
  printf("--- Second Header ---\n");
  printf("Header size (2nd): %lu bytes\n", infoheader.header_size);
  printf("Image height     : %ld pixels\n", infoheader.height);
  printf("Image width      : %ld pixels\n", infoheader.width);
  printf("Bits per pixel   : %hu\n", infoheader.bits);
  switch (infoheader.compression) {
  case 0:
    p = "None/RGB";
    break;
  case 1:
    p = "8-bit Run-length encoding";
    break;
  case 2:
    p = "4-bit Run-length encoding";
    break;
  case 3:
    p = "Bit fields";
    break;
  case 4:
    p = "Embedded JPEG";
    break;
  case 5:
    p = "Embedded PNG";
    break;
  default:
    p = "Unknown";
    break;
  }
  printf("Compression type : %lu (%s)\n", infoheader.compression, p);
  printf("Image size       : %lu bytes\n", infoheader.image_size);
  printf("Pixels/Meter (X) : %ld\n", infoheader.xres);
  printf("Pixels/Meter (Y) : %ld\n", infoheader.yres);
  printf("Color planes     : %hu\n", infoheader.planes);
  printf("Colors in palette: %lu\n", infoheader.colors);
  printf("Important colors : %lu\n", infoheader.imp_colors);

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 21/03-2008, Article Link

PNG Header Info

If you want to analyze a PNG (Portable Network Graphics) image, it is valuable to extract the header information from it. I made a small program in C to do exactly that, and represent the information in a human readable form. The program is based on information from RFC 2083 which is the PNG image specification.

Once compiled, the PNG image file should be piped into the program as standard in.

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h> /* ntohl() */

typedef struct png_image_header_s {
   unsigned long int width;
   unsigned long int height;
   unsigned char bit_depth;
   unsigned char colour_type;
   unsigned char compression_method;
   unsigned char filter_method;
   unsigned char interlace_method;
} png_image_header_t;

int main(void)
{
  int c;
  char chunk[5] = {'\0'};
  char *p;
  png_image_header_t header;

  /* Read until IHDR image header is found in file. */
  while ((c = fgetc(stdin)) != EOF) {
    chunk[0] = chunk[1];
    chunk[1] = chunk[2];
    chunk[2] = chunk[3];
    chunk[3] = c;
    if (strcmp(chunk, "IHDR") == 0)
      break;
  }

  if (feof(stdin)) {
    fprintf(stderr, "Error: Did not find PNG image header.\n");
    return 1;
  }

  fread(&header, sizeof(png_image_header_t), 1, stdin);
  if (feof(stdin)) {
    fprintf(stderr, "Error: PNG image header too short.\n");
    return 1;
  }

  /* Convert from network byte order. */
  header.width = ntohl(header.width);
  header.height = ntohl(header.height);

  printf("Width             : %lu\n", header.width);
  printf("Height            : %lu\n", header.height);
  printf("Bit depth         : %d\n", header.bit_depth);
  switch (header.colour_type) {
  case 0:
    p = "Greyscale";
    break;
  case 2:
    p = "Truecolour";
    break;
  case 3:
    p = "Indexed-colour";
    break;
  case 4:
    p = "Greyscale with alpha";
    break;
  case 6:
    p = "Truecolour with alpha";
    break;
  default:
    p = "Unknown";
    break;
  }
  printf("Colour type       : %d (%s)\n", header.colour_type, p);
  printf("Compression method: %d (%s)\n", header.compression_method, 
    (header.compression_method == 0) ? "Deflate" : "Unknown");
  printf("Filter method     : %d (%s)\n", header.filter_method,
    (header.filter_method == 0) ? "Adaptive" : "Unknown");
  switch (header.interlace_method) {
  case 0:
    p = "No interlace";
    break;
  case 1:
    p = "Adam7 interlace";
    break;
  default:
    p = "Unknown";
    break;
  }
  printf("Interlace method  : %d (%s)\n", header.interlace_method, p);

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 09/03-2008, Article Link

SDL-Man

I have made a portable (as in compilable on several platforms) Pac-Man clone using the SDL library. Since it is of course an "unofficial" version, I named it SDL-Man after the library.

It is written in C, and released under the GNU GPL version 3 license. The source code can be downloaded here, and a Windows binary with SDL runtime libraries can be downloaded here.

If you compile it for yourself on a Unix platform; remember to have both the main SDL library and the supplemental SDL_Mixer library installed first.

Here is a screen-shot of the start menu:

SDL-Man menu screen-shot


And here is an ingame screen-shot:

SDL-Man ingame screen-shot


Enjoy!

Topic: Open Source, by Kjetil @ 23/02-2008, Article Link

Steam Status on LCD

I recently provided code to drive a certain LCD under Linux. Currently, I use it to monitor my friends activites on the Steam network, that are used for games like Team Fortress 2. The steam status information is gathered with Perl script that downloads a public steam webpage every third minute. Afterwards the information is parsed and the formatted output is passed to the LCD display through a userland program.

The final output looks like this:

LCD displaying steam status.


The Perl code:

#!/usr/bin/perl -w
use strict;
use LWP::Simple;

my $steam_id = "your_steam_id_here";
my $url = "http://steamcommunity.com/id/$steam_id";

while (1) {
  my $content = get($url);

  if (defined $content) {

    my (@friend, @status);
    while ($content =~ m/<p><a class="linkFriend_([^"]+)"[^>]+?>([^<]+)</g) {
      push @status, ucfirst($1);
      push @friend, $2;
    }

    for (my $i = 0; $i < 4; $i++) { 
      if (defined $friend[$i] and defined $status[$i]) {
        # Status should not be longer than 7 characters. Could fit a name
        # of 13 characters, but keep an extra space for clarity.
        system("./lcd-putrow " . ($i + 1) . " \'" .
          (sprintf "%-13s", substr($friend[$i], 0, 12)) .
          (sprintf "%7s", $status[$i]) . "\'");
      }
    }

  } else {
    system("./lcd-putrow 1 \' \'");
    system("./lcd-putrow 2 \'       SERVER\'");
    system("./lcd-putrow 3 \'     UNAVAILABLE\'");
    system("./lcd-putrow 4 \' \'");
  }

  sleep(180);
}
          


Topic: Scripts and Code, by Kjetil @ 01/02-2008, Article Link

LCD Controller Program

I recently bought a LCD display (Warning: RAS syndrome present :-P) from an online hardware store. It has 20x4 characters and uses the (common) HD44780 controller. A standard parallel cable is used to communicate with it.

The store and the supplier recommends using a program named LCD Smartie to control the display. Although this program is open source, it is unfortunately made specifically for Windows. I tried to use another program (for Linux) named lcdproc, but I could not get it to display anything at all on the LCD. The solution is of course: Make your own driver and program(s).

Yes, I hacked together some code to successfully drive it under Linux, and the result can be downloaded here. The code is released under a MIT License, so you can do (almost) whatever you want with it.

If you want to use it on another platform you almost certainly have to change some something, since I used Linux specific port programming functions.

Here is a photo of the LCD displaying a quote from Linus Torvalds:

LCD displaying Linus Torvalds quote.


Topic: Open Source, by Kjetil @ 20/01-2008, Article Link