Monday, May 05, 2014

Le format image PNM / PNM easy graphic format

  Pour cette info en français, voir www.jchr.be/python/recette.htm

PNM (Portable aNy Map) is a graphic file format by Jef Poskanzer (b. 1958) in the eighties.

Such graphic files can have more specific extensions:

  • .pbm (Portable BitMap) for black/white graphics files
  • .pgm (Portable GrayMap) for grays graphics files (it can be more than 50)
  • .ppm (Portable PixMap) for coloured graphic files

It's no compressed format, and even worse (a single pixel can be encoded '243 222 123 '!), but very easy to create. A PNM begins with a tag ('P1' to 'P6'), a width, a height and (for gray files and coloured maps) a hightest value; after comes the data.

The P1, P2 and P3 data are ascii-coded and space-separated. A comment is possible between an # and an end-of-line before the dimensions; the P4, P5 and P6 data are binary-coded, with no separation.

P1: black&white ascii-coded image

A PNM Black/White file begins with 'P1 width height ' (width and height are pixel values), followed by '0' (white) or '1' (black), and - if you want - a space between each pixel.

P1 6 4
1 1 1 1 1 1
1 0 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1
"P1 6 4  111111100001100001111111 " is an equivalent (a last dummy pixels seems to be necessary).

Note: this graphic is a 6x4 pixels images, you'll have to zoom it.

A 1000x1000 pixels will be 1 or 2MB large, but it's possible to shrink it by 8 (each pixel is a bit) with the P4 binary format (see below).

P2: gray ascii-coded image

These files begins with 'P2' tag, a width and a height, followed by the highest value (until 255). The data are also ascii-typed and must have space-separations. In the following sample the values go from 0 (black) to 6 (white)

P2 4 4 6
6 5 4 3
5 4 3 2
4 3 2 1
3 2 1 0
"P2 4 4 6  6 5 4 3 5 4 3 2 4 3 2 1 3 2 1 0 " is an equivalent.

P3: coloured ascii-coded image

A coloured graphic ASCII begins with a 'P3' tag, follow by a width, a height and a maximal value. The datas are a sequence of three values which are the red, green and blue values:

P3 3 3 255
0   0   0    255 255 255    0 0    0
255 255 0    255 0   255    0 255 255
255 0   0     0   255 0       0 0   255

The number of spaces betwen each value doesn't have any importance. Red, green and blue are only coded with 0 or 255: in this example, it's possible to code in an easier way, setting the maximal value to 1 (before the '#'):

P3 # header (comment between '#' and carriage-return)
3 3 1
0 0 0  1 1 1  0 0 0
1 1 0  1 0 1  0 1 1
1 0 0  0 1 0  0 0 1
"P3 3 3 1   0 0 0 1 1 1 0 0 0  1 1 0 1 0 1 0 1 1   1 0 0 0 1 0 0 0 1 " is an equivalent

Note: you'll have to zoom this image to see these nine pixels!

P4, P5 et P6: binary formats

P4, P5 et P6 tags in the beginning of a PNM file are respectivily for black/white, gray scaled and coloured graphics where values are binary coded:

P4: black & white binary-coded:

P4 16 16
...byte-coded data

...without space nor carriage-return ('\n','\r') 

For a P4-PNM file, the first eight pixels must be translated into a value between 0 ('00000000') and 255 ('11111111').

For instance, WBBWWBBW is encoded through the binary number '01100110' = 102, (the 'f' character).

You can translate the binary number with int(number,2), and transform it into a character with char():

char(int("01100110",2)) 

The file will be:

"P4 8 4
ffff"

Some values cannot be written with ASCII-character. You can use chr(int('001100110',2) in a routine.

The best should be adding a line in your script with a command which transforms a PNM-file into PNG-one with os.system("convert image.pnm image.png") from the ImageMagick package (Unix only?).

P5 gray binary-coded image: each pixels is coded by a byte.

P6 colour binary-coded image: each pixel is coded by three bytes (red/green/blue) 

How to generate a P1-file

This script generates a black circle on a white background (you give the radius). It tests the pixels one by one: are they at the good distance from the center (x°,y°)? Pythagore answers with r²=(x-x°)²+(y-y°)². A file 'circle'+diameter+'.pnm' is created.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#! /usr/bin/python -Qnew 
 
import os
 
r=input("radius: ") # asking for a radius
d=r*2+1 # image width and height 
diam=str(d) # string  for header
fichier="P1 %s %s " %(diam,diam) # header 
 
for i in range(d): # rough way to produce a circle
  for j in range(d): 
    if round((abs((i-r)**2)+abs((j-r)**2))**.5)==r: 
      fichier+="1" 
    else: 
      fichier+="0" 
  fichier+="\n" # a last dummy pixel seems to be necessary 
 
handle=open("circle"+diam+".pnm","w") 
handle.write(fichier) 
handle.close() 
 
 
try:
   os.system("convert rond"+diam+".pnm rond"+diam+".png")
except:
   print "Sorry! no ImageMagick package found!"

[Reply]

I always wondered how PNM works. Thanks for the explanation!

Comment by Martin (05/15/2014 11:38)

Add comment

Add comment

authimage