mercredi 29 juillet 2015

ZPL - Impression d'image sur l'étiquette avec une Zebra LP2824 Plus

Erratum
L'article mentionne à plusieurs reprises que l'instruction Field Origin ^FO ne fonctionne pas avec l'affichage des images.
Ne tenez pas compte de cette remarque erronée. Il ne faut pas confondre la lettre O avec le chiffre zéro. En fonction de la font utilisée, cette distinction est parfois impossible et l'erreur de frappe si vite arrivée :-/ ! Voila pourquoi j'aime fonts avec le zéro barré ;-)

Introduction

Cette article est relativement technique mais passe en revue un certain nombre d'informations nécessaires permettant d'imprimer des images sur une étiquette Zebra.
J'y suis arrivé, vous pourrez donc le faire aussi.

Pour ma part, je dois réaliser cela à partir d'un programme en Clipper (Harbour Project) et n'ai donc pas d'autres choix que de me taper les spécifications.

Vous pouvez également vous référer à ces quelques articles d'introduction:
L'exemple selon Zebra à revoir!
Pour imprimer une image, il faut utiliser la commande ~DG pour définir l'image et stocker l'image en RAM.
Ensuite, l'image est imprimée à l'aide de la commande ^XG

L'exemple Zebra se base sur un petit damier codé assez simplement.
Cet exemple à deux défauts:
  • L'image est petite. Si vous utilisez une étiquette de 1", il est fort probable que vous ne voyez jamais rien. L'image est simplement imprimée hors de l'étiquette!
  • L'image est toujours imprimée depuis la coordonnée 0,0.
    Lu ailleurs dans la doc technique de 500 pages, la coordonnée par défaut de l'image est 0,0.
    Malgré les commandes de positionnement ^FO (field origin), l'origine d'impression sur ma Zebra est toujours 0,0!!!
    Il y a même l'exemple ^XG prétendant imprimer une image a différentes positions sur l'étiquette Zebra LP2824... cet exemple ne fonctionne simplement pas. L'origine est et reste 0,0 et rien d'autre. Il faudra donc prévoir son image en fonction de cette caractéristique.
Pour contourner les défauts de cet exemple, j'ai utilisé un "magnification factor" (agrandissement) de 3 sur une étiquette Zebra de 2" de large.

~DGR:SAMPLE.GRF,00080,010,
FFFFFFFFFFFFFFFFFFFF
8000FFFF0000FFFF0001
8000FFFF0000FFFF0001
8000FFFF0000FFFF0001
FFFF0000FFFF0000FFFF
FFFF0000FFFF0000FFFF
FFFF0000FFFF0000FFFF
FFFFFFFFFFFFFFFFFFFF
^XA
^F020,20^XGR:SAMPLE.GRF,3,3^FS
^XZ

Vous pouvez clairement voir que sur notre Zebra LP2824 la partie supérieure gauche du damier est imprimé "hors" de l'étiquette (depuis le point d'origine 0,0).

Il est donc très important de tenir compte de cette caractéristique lors de la création de notre image.

Encodage de l'image et notes de calculs

Encodage Hexadécimal
Ce point est probablement l'un des plus simple (cfr la doc).
Chaque point/pixel est représenté par un bit dans un octet/byte (il y a 8 bits dans un octet).

Et chaque octet est codé en Hexadécimal pour le définition de l'image.
 
FF en Hexadécimal --> 255 en décimal --> 11111111 en binaire --> donc 8 points nous d'affilés
80 en Hexadécimal --> 128 en décimal --> 10000000 en binaire --> un point noir + 7 point blancs.

Une ligne contenant FF FF FF représente donc une ligne d'une image avec 24 points noir. 

Nous avons donc un ligne ZPL par ligne dans l'image ET chaque ligne doit contenir un multiple de 8 points.

Si l'image fait 203 pixels (lignes) de haut, le bloc ZPL de l'image fera 203 lignes.
Si l'image fait 120 pixels de large, il faudra 120 / 8bits = 15 octets pour coder chaque ligne.

Comme la représentation d'un octet se fait avec 2 caractères Hexadécimal (FF = 255 en décimal), chaque ligne du bloc ZPL définissant l'image contiendra 30 caractères alphanumériques.

Question de point de vue
Là aussi je pense que la doc Zebra est un peu confuse.
Je ne sais pas pour vous mais moi je ne pars pas d'une image en mm pour ensuite savoir comment la plaquer sur une étiquette.
Je pars plutôt de l'espace disponible sur mon étiquette... puis détermine la taille de l'image à y placer... et je forge l'image en PIXELS en fonction de ce que j'ai besoin!
Pas besoin de se prendre la tête avec le nombre de dpi de l'imprimante et les mm disponibles.

La définition du bloc ~DG ci-dessous reprend donc les mensuration et caclul à partir de la taille en pixels.

Définition de l'image

Le bloc ~DG est défini comme suit:

~DGd:o.x,t,w,data

  • d = l'endroit où l'on stocke l'image... c'est en R (la ram)
  • o.x = Nom de l'image et son extension. Nom court, sans fioriture, moins de 8 caractères. optez pour un truc come LOGO.GRF
  • t = Taille de l'image en byte/octet. hauteur_en_pixels * largeur_en_pixels / 8.
    N'oubliez pas de prendre une image dont la largeur est un multiple de 8.
    Pour une image 120 x 203 --> 120 x 203 / 8 = 3045 octets
  • w = Nombre de bytes/octets par ligne. Dépend uniquement de la largeur de l'image.
    Largeur_en_Pixel / 8 --> 120 / 8 = 15 octets sur chaque ligne.
  • data = donnée de l'image au format hexadécimal (comme décrit ci-dessus).
Voici donc l'entête (et une partie du corps) ZPL de mon étiquette contenant une image (120 x 203 pixels) en ZPL.
Cette étiquette étant destiné à une imprimante Zebra LP2824 Plus avec une étiquette Z-Select 2000D de 50.8 x 25.4mm  (2" x 1").

~DGR:GGLOGO.GRF,03045,015,
800000000000000000000000000000
000000000000000000000000000000
...
...
000000000000000000000000000000
000000000000000000000000000000
^XA
^F00,0^XGR:GGLOGO.GRF,1,1^FS
^FX--FAIRE ETIQUETTE LARGE--^FS
^FX--Print Qty--^FS
^PQ@@QTY@@,0,0,Y
^FX--FR LABEL--^FS
^FO20,25^AR,40,30^FB405,1,0,C,0^FD@@LIBEEN@@^FS
^FX--PRODUCT CODE--^FS
^FO120,75^AT,40,30^FB300,1,0,C,0^FD@@ARTI@@^FS
^FX--BOX LOGO--^FS
^FO20,65^GB100,130,2^FS
^FX--EAN - CODE128--^FS
^FO230,130^BY1^AD,18,10
^BCN,40,Y,N,
^FD@@EAN@@^FS
^XZ

Au final, nous obtenons ceci.

Cas pratique
Voici comment faire pour créer l'image à afficher sur une étiquette.
Pour commencer, j'ai essayer de produire le code ZPL de l'étiquette avec un cadre à l'emplacement où je voulais placer l'image.
Cela permet:
  1. De connaître les coordonnées utiles.
  2. d'avoir la dimension de l'image à produire.
Je me suis donc efforcé d'écrire un tel fichier ZPL (voyez nos autres articles repris dans l'intro pour l'impression).
Au final, j'ai produit l'étiquette suivante.

^XA
^FX--FAIRE ETIQUETTE LARGE--^FS
^FX--Print Qty--^FS
^PQ1,0,0,Y
^FX--FR LABEL--^FS
^FO20,25^AR,40,30^FB405,1,0,C,0^FD@@LIBEEN@@^FS
^FX--PRODUCT CODE--^FS
^FO120,75^AT,40,30^FB300,1,0,C,0^FD@@ARTI@@^FS
^FX--BOX LOGO--^FS
^FO20,65^GB100,130,2^FS
^FX--EAN - CODE128--^FS
^FO230,130^BY1^AD,18,10
^BCN,40,Y,N,
^FD@@EAN@@^FS
^XZ

Les valeurs avec @@code@@ sont destinés à être remplacés au vol par un programme.
Ce qui nous intéresse, c'est la BOX pour la future position du logo.

^FO20,65^GB100,130,2^FS

Cette boite se trouve à l'origine 20,65 (gauche,top) avec une largeur de 100 et hauteur de 130 points.
Mais souvenez vous, une image est toujours affichée depuis l'origine 0,0.
Pour remplir le cadre, il faudra une image de:
  • 20 + 100 = 120 point de large.
    On veillera à ne pas placer de graphique sur une bande de 20 pixels sur la gauche de l'image).
    La largeur est aussi un multiple de 8 (pour le codage Hexadécimal) --> 15 octets par ligne.
  • 65 + 103 = 195 Pixels de haut.
    On veillera à ne pas placer de graphique sur la bande supérieure de 65 pixels (afin de ne dessiner que dans le cadre).
Nous avons donc la dimension idéale de notre image 120 x 195 pixels (avec une partie de celle-ci sur laquelle il ne faut pas dessiner).

Créer l'image
J'ai utiliser la méthode décrite dans le précédent article Transformer une image en BMP 2 couleurs pour une impression PCL pour créer l'image ci-dessous.

 Bon, j'ai fait un petit excès de zèle sur la hauteur de l'image, elle fait 203 pixels de haut (au lieu des 195 prévu).
La taille en octet de mon image sera donc de Hauteur_en_pixel x Largeur_en_Pixel / 8 = 3045 octets!
Le nombre d'octet par ligne pour l'image est de largeur_en_pixel / 8 = 15 octets!

Voici l'image pour ceux qui voudraient répéter l'opération (l'image doit être en 2 couleurs, 120x203 pixels)


Blogger n'aime pas les BMP 2 couleurs
du coup c'est un PNG qu'il vous faudra certainement
la retransformé en BMP 2 couleurs

Comme indiqué à la fin de l'article Transformer une image en BMP 2 couleurs pour une impression PCL , j'ai sauvé ce fichier en format SUNRAS 1 bit (im1).

Cela permet d'obtenir un fichier brute qui peut être rechargé en Python (sous format binaire).

Il faut sauter les 32 premiers octets de l'entête et chaque ligne est codée sur 16 octets de large au lieu de 15 octets (il faut donc se débarrasser du dernier octet de chaque ligne qui est toujours à 00).

Voici un petit script python (iPython) sans prétention qui permet de faire les manipulations nécessaires à la création du bloc de donnée de l'image en HexaDécimal.
file = open( 'GGLOGO.im1', 'rb' ) # Read BINARY file
fileContent = file.read()
file.close()
withoutHeader = fileContent[32:]

# Get the first byte as Hex
# "{:02x}".format( withoutHeader[0] )

# Image Size in Pixels (or dots). Look for Image property in Gimp
#    Width must be a multiple of 8
image_width = 120 
image_height = 203

byte_per_line = int( image_width / 8 )
byte_to_read  = int( (image_width * image_height)/8 )

# Test the size of array of data with image size
len( withoutHeader ) # 3248 (inclus le 16ieme octet 00 à virer sur chaque ligne!)
byte_to_read         # 3045 (n'inclus pas le CR dans le calcul)

# Result = List with one line per image line.
#          Each image line is a list of 15 byte under HexaDecimal
#            representation (we throw the 16th byte)
#
# result = [ [ '00', '00', '00', '00', '00', '00', '00' , '00' , '00' , '00' , '00' , '00', '00', '00' ,'00' ],
#            [ '00', '00', '00', '00', '00', '00', '00' , '00' , '00' , '00' , '00' , '00', '00', '00' ,'00' ],
#            ...
#          ] 
iCount = 0
result = []
sublist = []

for c in withoutHeader:
    iCount += 1
    # Garder que les 15 premiers bytes 
    if iCount < 16:
        sublist.append( "{:02x}".format( c ) )
    # Tous les 16 bytes c est une nouvelle ligne
    if iCount == 16:
        # skip the 16th byte
        # Start a new sublist
        result.append( sublist )
        sublist = []
        iCount = 0
if len( sublist )>0:
    result.append( sublist )

# View the result of the resultLines transformation done here under
#for l in result
#  print( ''.join(l) )
  
# Joindre les représentations Hexa Decimal en ligne (une string par ligne de l'image) )
#
# resultLines = [ '000000000000000000000000000000' ,
#                 '000000000000000000000000000000' ,
#                 ...
#               ] 
resultLines = [ ''.join(l) for l in result ]

outFile = open( 'GGLOGO.GRF' , 'w', encoding='utf8' )
for l in resultLines:
    outFile.write( l+'\n' )
outfile.close()

Je vous laisse décortiquer le script qui génère un fichier 'GGLOGO.GRF'. C'est un peu brute de décoffrage mais vous devriez pouvoir reproduire toutes ces étapes par vous même.

Le fichier GGLogo.GRF contient uniquement les données Hexadécimales de l'image à placer dans le bloc data de la commande ZPL ~DG .
Comme déjà calculé précédement, le paramètre T faut 3045 octets et le paramètre w vaut 15 octets.
Au final, nous avons une belle étiquette en ZPL dont voici le contenu complet.

~DGR:GGLOGO.GRF,03045,015,
800000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000020
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000017fffff8000020
0000000000000000e0000007000010
000000000000000300000000400010
000000000000000c00000000300020
00000000000000101e0fff400c0010
0000000000000060e230003f030020
000000000000004304200000e08010
000000000000010c002000001c4010
000000000000023004200000039010
00000000000004400420000000d820
000000000000088004400000003410
000000000000130004400000000b10
000000000000220004400000000490
000000000000cc0004400000000360
000000000001900004600000000130
000000000006600004400000000110
000000000018800004400000000120
00000003c0e3000000400000000190
00000007f90600000a640000005e20
0000001ffe01f017f8017ffffd0010
0000003ffe000de800000000000020
0000007fde000003002000e0000010
000000fc0300fc0f007fc1e000f020
000000fc0103ff0f503ff1ff0fff10
000001f80807ff8ffc7ff1ff9fffe0
000001f80fe7878ffffff9ffffeff0
000001f807ff1f0fe3f1f9f87f01f0
000001f003feff0f878079f07c0070
000001f003ee000f078039e07c0070
000001f0e3ce001e07003de0781830
000001f1b3ce001f0f003de0783c38
000001f363cf000e07001de0786438
000001f3e3cf01df07803de0f83c38
000001f9c7cf83df07c07de07c183c
000001fc07cfffdf03fffde0fc007c
000000fc0fc7ff9f03fffdf07e00fe
000000ffffc1ff3f00ffbff07f01fe
000000ffffc0700b002f3ff87ffffe
0000007fffc00000000004007fffff
0000003fff800000000000003fffff
0000000fff000000000000001fff9c
00000003fc000000000000000fff00
000000103c0000000000000003fc00
000000103c00000000000000000000
000000103c00000000000000000000
000000103800000000000000000000
000000103c00000000000000000000
000000187c00000000000000000000
0000001cfc00000000000000000000
0000001ffe00000000000000000000
0000000ff000000000000000000000
00000007e000000000000000000000
000000010400000000000000000000
000000001c00000000000000000000
000000301c00000000000000000000
000000383c00000000000000000000
000000381c00000000000000000000
000000383c00000000000000000000
000000383c00000000000000000000
0000003c3c00000000000000000000
0000003cfc00000000000000000000
0000001ffc00000000000000000000
0000001ffc00000000000000000000
0000001ffc00000000000000000000
0000000ffc00000000000000000000
000000073c00000000000000000000
000000003c00000000000000000000
000000003c00000000000000000000
000000603c00000000000000000000
000000203c00000000000000000000
000000307c00000000000000000000
000000707c00000000000000000000
00000039f800000000000000000000
0000003ff800000000000000000000
0000001ff000000000000000000000
0000001fe000000000000000000000
00000007c000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
^XA
^F00,0^XGR:GGLOGO.GRF,1,1^FS
^FX--FAIRE ETIQUETTE LARGE--^FS
^FX--Print Qty--^FS
^PQ1,0,0,Y
^FX--FR LABEL--^FS
^FO20,25^AR,40,30^FB405,1,0,C,0^FD@@LIBEEN@@^FS
^FX--PRODUCT CODE--^FS
^FO120,75^AT,40,30^FB300,1,0,C,0^FD@@ARTI@@^FS
^FX--BOX LOGO--^FS
^FO20,65^GB100,130,2^FS
^FX--EAN - CODE128--^FS
^FO230,130^BY1^AD,18,10
^BCN,40,Y,N,
^FD@@EAN@@^FS
^XZ
N'oubliez pas de remplacer les @@code@@ par des valeurs pertinentes... et surtout de sauver votre fichier en UTF8. En espérant que cela puisse vous être utile.

4 commentaires:

Georges a dit…

bonjour

Je trouve votre article très intéressant mais je suis confronté à un souci je n'arrive pas avec mon clavier à générer le caret (^). Vous utilisez quel éditeur pour pouvoir écrire un (^)X ?
Je suis Mageia par avance merci de votre aide.

ensuite je teste tout ça sur une 105LS

Georges

Dominique Meurisse (MCHobby) a dit…

bonjour Goerge. il s agit d un caractere européen utilisé principalement en langue française. il est egalement disponible depuis la nuit des temps dans les tables ASCII... c dst dire si cela date. le "caret" est aussi utilisé pour identifier le caractere Controle Ctrl.
A vous lire, j imagine que vous utikiser un clavier qwerty... il faudra chercher sur le net pour savoir comment produire le caractère au clavier (Sinon faite un copier coller).
Sur un clavier azerty, il suffit d utiliser la touche ^ suivie d un espace :-)

Anonyme a dit…

Bonjour Dominique

J'avance et pour ce faire j'ai installé python que je découvre en mme tps.
J'ai un autre souci; j'ai fait un 'copier-coller' de votre script et l'interpréteur m'indique une erreur en ligne 37 :

File "trsf_im1_pour_zebra.py", line 37, in
sublist.append( "{:02x}".format( c ) )
ValueError: Unknown format code 'x' for object of type 'str'

Un peu confus et honteux de vous poser la question mais savez vous à quoi c'est dû; Docteur?
est ce grave? et auriez vous revu votre scrip dernierement..... dès fois que...

par avance merci beaucoup de votre aide et soutien
Georges

Dominique Meurisse (MCHobby) a dit…

Bonjour,
ce code utilise Python 2.7 (pour info utile, cela est important car la gestion des string est différente entre 2.7 et python 3.5).

L'erreur dans

"{:02x}".format( c )

provient du fait que "c" ne contient pas un entier mais une chaine de caractère.
Vous pouvez tester cette ligne directement dans mode interactif en affectant une valeur à "c"... comme par exemple

c = 29
"{:02x}".format( c )

Si vous testez le code ci-dessous alors vous obtenez la meme erreur.

c = "35"
"{:02x}".format( c )

La question est donc... pourquoi avez vous une chaine de caractère dans "c" plutot qu'une valeur entière.

Bonne journée,
Dominique