Quelques formats d'images des ATARI ST(E)

IL existait pour l'Atari ST quelques formats d'image assez simples, dont le format de fichier .IMG (compressé), très importants puisque mis au point par Digital Research, dont l'interface graphique GEMGraphical Environment Manager était implémentée dans le système. Les logiciels GEM Paint, GEM Scan et Ventura Publisher (MSDOS puis Windows) l'utilisaient.

IMG (GEM) - PSC (PaintShop Compressed) - BBT (Omikron Basic) - PAD

Pour les images en format écrans (Degas, NEOchrome, Tiny), voir ici.

La solution libre recoil semble difficilement compilable. Une page html permet de lire beaucoup d'images du monde Atari (et Amiga).

Les données sont parfois écrites en notation hexadécimale, qui résume des états de pixels allumés ou éteints, donc binaires. Voici un table de correspondance entre notations décimale, binaire et hexadécimale. Les chiffres binaires sont 0 ou 1 , et tout rang à gauche vaut deux fois plus. Les «chiffres» hewadécimaux valent de 0 à 15 (de 0 à 9 et de A à F) ; tout rang à gauche vaut 16 fois plus : B9 vaut 11 *16 + 9 = 185 ; 123 écrit en hexadécimal vaut 256 (1 *16 *16) + 32 (2 *16) + 3 (3 *1) = 291. Voici les correspondances décimal-binaire-hexadécimal- :

Déc.Bin.Hex.Déc.Bin.Hex.Déc.Bin.Hex.Déc.Bin.Hex.
000000401004810008121100C
100011501015910019131101D
200102601106101010A141110E
300113701117111011B151111F

Il faut deux chiffres hexadécimaux pour le composer un octet, qui vaut de 0 à 255 : 202= 12*16+10, soit CA, qui se traduit en monochrome par la suite de pixels 1100 1010

Format d'un fichier .img

Les fichiers IMG disposent d'une compression sans perte. Leur entête comprend un nombre variable de mots :

Note : L'écran monochrome SM124 de l'Atari ST a une résolution de 72dpi (points par pouce), les pixels ont donc a priori une dimension de 25,4mm /72, soit 353µ de côté (un tiers de millimètre). Les dimensions de pixels les plus communes des images IMG semblent aller de 85 à 385. Attention : un pixel n'est pas nécessairement carré : en moyenne résolution ST (640x200), il est deux fois plus haut que large, en basse résolution TT (320x480), deux fois plus large que haut.

Le reste du fichier sont les données, compressées selon les conditions ci-après.

Algorithme de compression d'un fichier .img

Les données compressées se lisent octet par octet, ligne après ligne. Chaque ligne est composée d'un nombre entier d'octets. Si la largeur en pixels du fichier n'est pas égale à un multiple de 8, le reste du dernier octet n'est pas pris en compte.

Pour un octet n suivi d'éventuels octets p et q :

  1. 0 < n < 128 : n détermine le nombre d'une séquence d'octets vides (0)
  2. 128 < n < 256 : n -128 détermine le nombre d'une séquence d'octets pleins (255)
  3. n ==128 : p détermine un nombre d'octets à considérer tels quels. L'octet 80 est donc assez fréquent sauf si l'image contient surtout de grandes plages régulières (blancs, noirs ou de succession régulière de pixels noirs et de pixels blancs).
  4. n ==0 : deux cas possibles pour l'octet suivant p
    1. p > 0 : p fois un octet ou une séquence d'octets ; le nombre défini au quatrième mot de l'entête donne le nombre d'octets à considérer :
      • s'il vaut 2 : 00 04 FC A0 code FC A0 FC A0 FC A0 FC A0
      • s'il vaut 1 : 00 04 FC A0 code FC FC FC FC, A0 code alors une séquence suivante : 160 -128 octets FF (point 2).
    2. p ==0 et q ==255 : celui qui suit encore donne le nombre de fois que la ligne qui suit sera à répéter : 00 00 FF 12 répétera 18 fois (1 *16 +2) la ligne qui sera composée (souvent un ensemble d'octets blancs si non nul et inférieur à 128, ou noir si supérieur à 128.

Si l'image est très complexe (ensemble de points au hasard), chaque ligne doit être préfixée de 80 nn (point 3) et ajoutera donc deux octets à chaque ligne de données. Il en résulterait un fichier légèrement plus lourd que l'original non compressé. Pour que la compression soit efficace, l'image doit comporter des lignes d'octets blancs 00 (point 1) ou noir FF (point 2) répétables (point 4B), soit les motifs 10001000 10001000 ou 10101010 10101010 (point 4A) typiques des images monochromes tentant de révéler des valeurs de gris.

Couleurs

En monochrome, chaque octet définit 8 pixels (0 est blanc, 1 est noir).

Pour la couleur, il faut prévoir autant de lignes qu'il y a de plans annoncés au mot 3 de l'entête. C'est la combinaison de bits au même rang de chaque plan qui détermine la couleur des pixels (voir ici).

11001100 11001100 premier plan
10101010 10100… second plan: *2
31203120 3…   détermine une des 4 couleurs que 2 plans permettent (moyenne résolution ST)
11111111 00000000 premier plan
11110000 11110000 second plan: *2
11001100 11001100 troisième plan: *4
10101010 10101010 quatrième plan: *8
FEDCBA98 76543210 détermine une des 16 couleurs pour 4 plans (basse résolution ST ou moyenne TT)

Les images en quatre plans (16 couleurs) ont une entête de x3B mots (118 octets) si elles définissent les 16 couleurs. Chaque composante (respectivement rouge, vert et bleu) est codée de 0 à x3E4 (996), ce qui fait 6 mots par couleur.

00 01   entête GEM raster
00 3B   entête élargi pour la définition de couleur
00 04   quatre plans (16 couleurs)
00 02   schéma de répétitions d'octet(s): par deux
01 74 01 74   dimensions en microns d'un pixel
01 40 01 80   dimension de l'image en pixels
58 49 4D 47   "XIMG" au décalage 16
00 00         ?
00 00 00 00 00 00   trois composante à 0: noir
03 E4 03 E4 03 E4   = 996 996 996: blanc
03 E4 00 00 00 00   = 996 0 0: rouge
etc.
94 80 02 F0   début des données au décalage 118

On retrouve la valeur de chaque composante avec couleur*256 //1000 (arrondi par défaut), ou mieux int(round(float(couleur) *256 /1000)) (en python2, la division est a priori entière)

Script en python3 pour transformer un fichier .IMG monochrome en .pnm

#! /usr/bin/python3

# From GEM IMG Atari monochrome image to PNM / PNG
# Jean-Christophe Beumier - 2015.02.06 - GPLv3
# Translated into python3 - 2023.04.14
# https://www.gnu.org/licenses/gpl-3.0.en.html

import sys, os  # os for listdir() ; sys for exit()

repertoire =os.listdir(".") #
for i in range(len(repertoire) -1, -1, -1):
  print(repertoire[i])
  if repertoire[i][-4:].upper() !=(".IMG"):
    repertoire.pop(i)
repertoire =sorted(repertoire)

for i in range(len(repertoire)):
  print("%3d %s" %(i, repertoire[i]))
q =input("numéro de fichier à transformer: ")
nom =repertoire[int(q)]
print(nom)

with open(nom, "rb") as fd :
  img =fd.read()
img =list(img)

def word(x) : # transform two bytes into a 16bits word (up to 65535)
  return x[0] *256 +x[1]

def getcar(n) : # transform a character into a byte (up to 255)
  global data, ofs
  octet =data[n] ; ofs +=1
  return octet

def getbunch(nbr) :
  global ofs, data
  bunch =data[ofs:ofs +nbr] ; ofs +=nbr
  return bunch

magic =word(img[0:2])
if magic !=1: input("not a GEM img ! Strike a key") ; sys.exit()
plans =word(img[4:6]) ; print("plans: %s" %plans)
if plans !=1: input("not a monochrom img ! Strike a key") ; sys.exit()
entete =word(img[2:4]) ; print("header: %s" %entete)
clusters =word(img[6:8]) ; print("clusters: %s" %clusters) # number of repeated bytes
# [8:12] pixel dimensions (screen resolution, 72 dpi?)
largeur =word(img[12:14]) ; print("width: %s" %largeur)
hauteur =word(img[14:16]) ; print("height: %s" %hauteur)

larg, reste =divmod(largeur, 8)
if reste : # byte number by ligne (>= line width)
  larg +1
data =img[entete*2:]
ligne =[] ; ofs =0 ; new =[]
nbl =1 # line repetition number: 1 by default
while ofs < len(data):
  octet =getcar(ofs)
  if octet ==0 : # multiplication:
    octet =getcar(ofs)
    if octet : # 1st: sequence multiplication
      nbc =octet # identical byte number to consider
      stock =[]
      for i in range(clusters):
        octet =getcar(ofs)
        stock +=[octet]
      ligne +=stock*nbc # product
    else: # line multiplication
      octet =getcar(ofs)
      if octet ==255: # arbitrary number
        nbl =getcar(ofs) # line multiplication number
  else:
    if octet ==128: #
      octet =getcar(ofs)
      ligne +=getbunch(octet)
    elif octet < 128: #
      ligne +=[0] *octet
    else:
      ligne +=[255] *(octet -128)

  if len(ligne) >= larg :
    newline =[]
    for z in range(len(ligne)) :
      char =ligne[z]
      for j in range(7, -1, -1) :
        if z *8 +(8 -j) > largeur :
          continue
        p =2 **j
        bit =(1 -(char & p) //p) *7
        newline +=[bit] *3
    new +=newline *nbl
    nbl=1 ; ligne =[]

header =[ord(x) for x in "P6 %d %d 7\n" %(largeur, hauteur *plans)]
with open(nom +".pnm", "wb") as fd :
  fd.write(bytes(header +new))

try :
  os.system("convert %s.pnm %s.png" %(nom, nom))
  os.system("rm %s.pnm" %nom)
  input("\n  %s.pnm converted into %s.png\n" %(nom, nom))
except :
  input("\n  no .png conversion ; on UNIX, IMageMagic 'convert' should be present\n")

Script pour transformer un fichier IMG 16 couleurs en PNM

Script (python3) à améliorer : certaines images ne passent pas. Il est également possible que les couleurs par défaut ne soient pas les bonnes (en cas d'entête sans définition de couleurs) : j'ai mis l'ordre des couleurs «hardware», peut-être l'ordre «VDI» convient-il mieux (voir ici).

#! /usr/bin/python3

# From GEM IMG Atari color image to PNM / PNG
# Jean-Christophe Beumier - 2015.02.18 - GPLv3
# Translated into python3 - 2023.04.14 - GPLv3
# https://www.gnu.org/licenses/gpl-3.0.en.html

# Not completely functional !

import sys, os

repertoire=os.listdir(".")
for i in range(len(repertoire) -1, -1, -1):
  print (repertoire[i])
  if repertoire[i][-4:].upper() !=(".IMG"):
    repertoire.pop(i)
repertoire=sorted(repertoire)

for i in range(len(repertoire)):
  print("%3d %s" %(i, repertoire[i]))
q =input("numéro de fichier à transformer: ")
nom =repertoire[int(q)]
print(nom)

ofs =0

with open(nom, "rb") as fd :
  img =list(fd.read())

def word(x): # transform a two characters string into a 16bits word (up to 65535)
  return x[0] *256 +x[1]

def getcar(n):
  global data, ofs
  ofs +=1
  return data[n]

def getbunch(nbr):
  global ofs, data
  bunch =data[ofs:ofs +nbr] ; ofs +=nbr
  return bunch

magic =word(img[0:2])
if magic !=1 : print("not a GEM img") ; sys.exit()
plans =word(img[4:6]) ; print("plans: %s" %plans)
if plans !=4 : print("not a four-planes img") ; sys.exit()
entete =word(img[2:4]) ; print("header: %s" %entete)
clusters =word(img[6:8]) ; print("clusters: %s" % clusters) # number of repeated bytes
# [8:12] pixel dimensions (screen resolution, 72 dpi?)
largeur =word(img[12:14]) ; print("width: %s" %largeur)
hauteur =word(img[14:16]) ; print("height: %s" %hauteur)

# hardware colour definition
clrs=[[15,15,15], [0,0,0], [15,0,0], [0,15,0], [0,0,15], [8,0,0], [8,4,0], [0,8,0], [10,10,10], [4,4,4], [0,15,15], [0,8,8], [15,0,15], [8,0,8], [15,15,0], [8,8,0]]
# VDI order to add

ximg =img[16:20]
if ximg !="XIMG":
  print ("not a 'XIMG' - IMG raster couleur")
  mx =15
else:
  clrs =[] ; mx =255
  for i in range(16):
    deb =22
    red =word(img[deb +i *6:deb +2 +i *6]) *256 //1000
    green =word(img[deb +2 +i*6:deb+ 4+ i *6])* 256 //1000
    blue =word(img[deb +4 +i*6:deb +6 +i*6]) *256 //1000
    print (red, green, blue)
    clrs +=[[red, green, blue]]

larg, reste =divmod(largeur, 8)
if reste: larg +1 # byte number by line (>= line width)

data =img[entete*2:]
ligne =[] ; ofs =0 ; new =[]
nbl =1 # line repetition number: 1 by default
newline =[[] ,[], [], []] ; plan =0
tout =""
while ofs <len(data):
  octet =getcar(ofs)
  if octet ==0: # multiplication:
    octet =getcar(ofs)
    if octet: # 1st: sequence multiplication
      nbc =octet # identical byte number to consider
      stock =[]
      for i in range(clusters):
        octet =getcar(ofs)
        stock +=[octet]
      ligne +=stock*nbc # product
    else: # line multiplication
      octet =getcar(ofs)
      if octet ==255: # arbitrary number
        nbl =getcar(ofs) # line multiplication number
  else:
    if octet ==128: # for a bytes sequence
      octet =getcar(ofs) # how many bytes to consider
      ligne +=getbunch(octet)
    elif octet<128: # how many white (0) bytes
      ligne =ligne +[0] *octet
    else: # how many black (255) bytes
      ligne =ligne +[255] *(octet -128)

  if len(ligne) >=larg:
    newline[plan] =ligne ; ligne=[]
    if plan ==3:
      total =[]
      for z in range(len(newline[0])):
        char0 =newline[0][z]
        char1 =newline[1][z]
        char2 =newline[2][z]
        char3 =newline[3][z]
        for j in range(7, -1, -1):
          if z *8 +(8 -j) >largeur:
            continue
          p =2 **j
          bit0 =(char0 & p) //p ; bit1 =(char1 & p) //p
          bit2 =(char2 & p) //p ; bit3 =(char3 & p) //p
          clr =bit0 +bit1*2 +bit2 *4 +bit3 *8
          total +=[clrs[clr][0], clrs[clr][1], clrs[clr][2]]
      new +=total *nbl
      nbl =1 ; newline =[[], [], [], []]
    plan =(plan +1) %4

header =[ord(x) for x in "P6 %d %d %d\n" %(largeur, hauteur, mx)]
with open(nom+".pnm", "wb") as fd :
  fd.write(bytes(header +new))

PSC PaintShop Compressed

Je ne sais pas si ce format compressé sans perte, uniquement monochrome a eu énormément de succès. Il a été inventé par Thomas Much pour son application PaintShop écrite pour Atari en GFA Basic. La compression ne considère que des lignes, le header fait normalement 14 octets (une extension était prévue) :

Les données commencent à l'offset 14 (si l'octet 9 =1). Elle sont constituées d'un RLE (run-length encoding). Ce format est très performant si les lignes sont uniformes (même octet ou groupe de deux octets ou si elles se répètent :

Ci-dessous, un script en python pour décoder une image PSC. Il n'a pu être testé que pour quatre images seulement. Veuillez m'envoyer toute autre image qui ne se décompressent pas bien.

#! /usr/bin/python3

# https://github.com/thmuch/tosgem-image-reader/blob/main/docs/PaintShopCompressed.md (specs)
# https://telparia.com/fileFormatSamples/image/paintShop/ pour quelques images
# script python3 ppour transformer une image PaintShop en PNM
# Jean-Christophe Beumier - 2023.05.30 - GPLv3
# Aucune garantie - utilisation sous votre propres responsabilité

import sys, os
liste =os.listdir(".")

lon =len(liste)   # filtering
for i in range(lon-1, -1, -1):
  if liste[i][-4:].upper() not in (".PSC"):
    liste.pop(i)

liste.sort()      # showing directory TNY and consort
lon =len(liste)
for i in range(lon):
  print(f"{i:3d} {liste[i]:<14}", end="")
  if (i+1) % 4 ==0:
    print()
print()

nr =input("\n  Number(s), space-separated if several (* for all): ")

li =nr.split() ; dic ={}    # choix des numéros
if "*" not in li :
  li =[int(x) for x in li]
if nr !="*" :
  dic ={x:liste[x] for x in li}
else :
  dic =dict(zip(range(len(liste)), liste))

for num in dic :  # loop for all choosen TNY / TN3 files
  nom =dic[num]
  with open(nom, "rb") as han :
    byt =han.read()

  if byt[0:4] !=b"tm89" :
    print("Ce n'est pas un fichier PSC d'Atari PaintShop de Thomas Much")
  # print(byt[4:8]) signature du logiciel générateur
  # print(byt[8]) toujours 2
  ofs =8 +(int(byt[9]) +1) *2 + 2

  larg =int(byt[10])* 256 + int(byt[11]) +1
  loct =(larg -1) //8 +1
  haut =int(byt[12])* 256 + int(byt[13]) +1

  image =bytearray()
  data =byt[ofs:]

  lon =len(data) ; i =0 ; y =0
  while i < len(data) :
    n =data[i]
    if n ==99 : # si textuel, rien de compressé
      i +=1 ; ajout =data[i:-1] ; i +=len(ajout)
      image +=ajout
      continue
    if n ==0 : # une ligne de blanc
      ajout =b"\x00" *loct
      image +=ajout
      i +=1
      continue
    if n ==200 : # une ligne de noir
      ajout =b"\xff" *loct ;
      i +=1 ;
      image +=ajout
      continue
    if n ==10 : # répéter la dernière ligne m +1 fois
      i +=1 ; m =data[i] +1 ;
      rajout =ajout *m
      image +=rajout
      i +=1
      continue
    if n ==12 : # répéter la dernière ligne m +256 fois
      i +=1 ; m =data[i] +256
      rajout =ajout *m
      image +=rajout
      i +=1
      continue
    if n ==100 : # une ligne du même octet
      i +=1 ; m =data[i]
      ajout =bytes([m]) *loct
      image +=ajout
      i +=1
      continue
    if n ==102 : # une ligne du même mot
      i +=1 ; m = data[i]
      i +=1 ; p = data[i] ;
      ajout =bytes([m, p]) *(loct //2)
      image +=ajout
      i +=1
      continue
    if n ==110 : # une ligne non compressée
      i +=1 ; ajout =data[i:i +loct] ; i +=loct
      image +=ajout
      continue
    if n ==255 : # fin des données
      i +=1

  header =bytes(f"P4 {larg} {haut}\n", "ascii")
  with open(nom+".pnm", "wb") as han :
    han.write(header +image)
  print(f"  {nom}.pnm sauvegardé")
  try :
    os.system(f"convert {nom}.pnm {nom}.png")
    os.system(f"rm {nom}.pnm")
    print(f"  {nom}.pnm converted into {nom}.png\n")
  except :
    print("  no .png conversion ; on UNIX, IMageMagic 'convert' should be present\n")

BITBLT sur Omikron BASIC

Le BASIC Omikron 3.01 connaissait un format de BIT BLock Transfert (BITBLT) non compressé

Suivent les données non compressées. Les bits des octets non complets sont ignorés. Les lignes de données doivent en outre être codées sur un nombre pair d'octets : 16 bits tiennent sur 2 octets, mais 17 sur 4. Une façon de déterminer un nombre pair d'octets par excès à partir d'un nombre de bits :

npox =((nb -1) //16) *2 +2

Voici le script en BASIC Omikron (sur l'émulateur Hatari) ayant fabriqué et sauvegardé le bloc-image OMBAS301.BBT :

CLIP 0, 0, 639, 399
TEXT STYLE =16: TEXT HEIGHT =48 ' style =16 pour outline
TEXT 100, 100, "Omikron Basic"
TEXT STYLE =0: TEXT HEIGHT =36 ' style=0 pour normal
TEXT 109, 130, "...on Atari ST"
Adr%L =MEMORY(6 +30 *65) ' reservation de memoire
BITBLT 98, 72, 235, 65 TO Adr%L
BSAVE "OMBAS301.BBT", Adr%L, 30 *65 +6 ' sauvegarde de la memoire vers le disque

Voici le script python qui transforme le BBT monochrome en fichier image .pnm

#! /usr/bin/python3

# Atari Omikron3.01 BBT image to pnm
# Jean-Christophe Beumier - 2015.02.09 - GPLv3
# python3 adaptation: 2023.04.15 - GPLv3
# https://www.gnu.org/licenses/gpl-3.0.en.html

import os, sys

repertoire =os.listdir(".") #
for i in range(len(repertoire) -1, -1, -1):
  if repertoire[i][-4:].upper() !=(".BBT"):
    repertoire.pop(i)

if len(repertoire) ==0 :
  input("No BBT file in this directory") ; sys.exit()
repertoire =sorted(repertoire)

for i in range(len(repertoire)):
  print("%3d %s" %(i, repertoire[i]))
q =input("numéro de fichier à transformer: ")
nom =repertoire[int(q)]
print(nom)

with open(nom, "rb") as fd :
  img =list(fd.read())

def word(w):
  return w[0] *256 +w[1]

rez =word(img[0:2])
if rez !=2 : input("Not an Omikron3.01 BITBLT High Rez (black / white)") ; sys.exit()
larg =word(img[2:4]) ; largo =((larg -1) //16) *2 +2
haut =word(img[4:6])
data =img[6:]
print(nom +" - length: %s - height: %s - width: %s px - %s words / line\n" %(len(data), haut, larg, largo))
harvest =""
for j in range(haut):
  ofs =j *largo
  for i in range(largo):
    mot =data[ofs +i]
    for k in range(7, -1, -1):
      if i *8 +7 -k < larg:
        if mot & 2**k:
          harvest +="1"
        else:
          harvest +="0"

header ="P1 %s %s\n" %(larg, haut)

with open(nom+".pnm", "wb") as fd :
  fd.write(bytes(header +harvest, encoding="ascii"))
input("%s.pnm saved" %nom)

Bloc de bits au format .PAD

Il s'agit d'une image en format non compressé proche du précédent, trouvé avec le logiciel PICWORKS fonctionnant sur l'ATARI ST.

Suivent les données non compressées. Les bits des octets non complets sont ignorés. Les lignes de données doivent en outre être codées sur un nombre pair d'octets : 16 bits tiennent sur 2 octets, mais 17 sur 4. Une façon de déterminer un nombre pair d'octets par excès à partir d'un nombre de bits : npox=((nb-1)//16)*2 +2

00 0D 00 0C 00 01 0B 43 0B 40 0B 40 0B 40 1B 60
1B 60 3B 70 73 3B F3 3E E3 1C E3 1C C3 0C 83 04

premier mot : D=13, +1 = 14 pixels de largeur
second mot : C=12, +1 = 13 pixels de hauteur
troisième mot : 1 = monochrome

La largeur en pixels étant de 14, deux octets sont nécessaires par ligne. Le deux derniers bits du dernier octet de chaque ligne peut être marqués (représentés par + ou -), mais on n'en tient pas compte. Le graphique qui suit marque la séparation entre chiffres hexadécimaux, plus la dernière colonne cachée.

0B 43 = .... x.xx .x.. ..++
0B 40 = .... x.xx .x.. ..--
0B 40 = .... x.xx .x.. ..--
0B 40 = .... x.xx .x.. ..--
1B 60 = ...x x.xx .xx. ..--
1B 60 = ...x x.xx .xx. ..--
3B 70 = ..xx x.xx .xxx ..--
73 3B = .xxx ..xx ..xx x.-+
F3 3E = xxxx ..xx ..xx xx+-
E3 1C = xxx. ..xx ...x xx--
E3 1C = xxx. ..xx ...x xx--
C3 0C = xx.. ..xx .... xx--
83 04 = x... ..xx .... .x--

Voici un script de transformation de ces images monochromes :

#! /usr/bin/python3

# Atari PAD image to PNM
# Jean-Christophe Beumier - 2015.02.09 - GPLv3
# python3 adaptation: 2023.06.01 - GPLv3
# https://www.gnu.org/licenses/gpl-3.0.en.html

import sys, os
liste =os.listdir(".")

lon =len(liste)   # filtering
for i in range(lon-1, -1, -1):
  if liste[i].rsplit(".", 1)[-1].upper() not in (".PAD"):
    liste.pop(i)

liste.sort()      # showing directory TNY and consort
lon =len(liste)
for i in range(lon):
  print(f"{i:3d} {liste[i]:<14}", end="")
  if (i +1) % 4 ==0 :
    print()
print()

if not liste :
  input("\n  No .PAD file in this directory.") ; os.exit()

nr =input("\n  Number(s), space-separated if several (* for all): ")

li =nr.split() ; dic ={}    # choix des numéros
if "*" not in li :
  li =[int(x) for x in li]
if nr !="*" :
  dic ={x:liste[x] for x in li}
else :
  dic =dict(zip(range(len(liste)), liste))

for num in dic :
  nom =dic[num]
  with open(nom, "rb") as fd :
    img =list(fd.read())

  def word(w):
    return w[0] *256 +w[1]

  larg=word(img[0:2]) +1; largo =((larg -1) //16) *2 +2
  haut=word(img[2:4]) +1
  plans=word(img[4:6])
  data=img[6:]
  print(nom +f" - length: {len(data)} - height: {haut}px - width: {larg}px - {largo} words/line")
  harvest=""
  for j in range(haut):
    ofs =j *largo
    for i in range(largo):
      mot =data[ofs+i]
      for k in range(7, -1, -1):
        if i *8 +7 - k < larg:
          if mot & 2**k:
            harvest+="1"
          else:
            harvest+="0"

  header="P1 %s %s\n" %(larg, haut)
  with open(nom+".pnm","wb") as fd :
    fd.write(bytes(header +harvest, encoding="ascii"))
  try :
    os.system("convert %s.pnm %s.png" %(nom, nom))
    os.system("rm %s.pnm" %nom)
    print(f"  {nom}.pnm converted into {nom}.png\n", end="")
  except :
    print("  no .png conversion ; on UNIX, IMageMagic 'convert' should be present\n", end="")
  print()