Create: Computing AgesDoc Brass
Bibliothèques

gfx

Dessiner en pixels sous le texte de l'écran : formes, texte, sprites et défilement.

Ordinateur à transistors et suivants

La bibliothèque gfx dessine des pixels sur l'écran de l'ordinateur : points, lignes, rectangles, cercles, triangles, texte dans une petite police en pixels, sprites écrits en chiffres hexadécimaux, et un défilement qui fait glisser toute l'image. C'est avec elle que l'on construit des cadrans, des graphiques, des plans de réseau ferré, des panneaux de commande avec de vrais boutons, et des jeux.

Le dessin se trouve sous le texte de l'écran. Ce que vous affichez avec print reste lisible par-dessus : un programme peut dessiner une image et continuer à se servir de print, term.write et read comme d'habitude.

Brass
local t = gfx.size()
gfx.clear("light_blue")                              -- le ciel
gfx.circle(262, 34, 16, "yellow", true)              -- le soleil
gfx.rect(1, 128, t.w, t.h - 127, "green", true)      -- l'herbe
gfx.rect(104, 30, 12, 30, "gray", true)              -- une cheminée
gfx.rect(36, 72, 96, 56, "brown", true)              -- l'atelier
gfx.triangle(28, 72, 84, 38, 140, 72, "red", true)   -- son toit
gfx.rect(72, 98, 24, 30, "black", true)              -- la porte
for i = 0, 7 do
  local a = i * math.pi / 4
  gfx.pixel(206 + math.cos(a) * 30, 96 + math.sin(a) * 30, "orange", 9)
end
gfx.circle(206, 96, 28, "orange", true)              -- une roue dentée
gfx.circle(206, 96, 9, "light_gray", true)
gfx.text(8, 8, "ATELIER DE LAITON", "white", 2)
term.set_cursor(2, 18)
term.write("Le texte de term.write reste dessus")
Écran
Écran
Fonctions
gfx.size()La taille du dessin en pixels : la taille de la grille de texte multipliée par 6 et par 9.
gfx.clear([color])Efface le dessin, ou le remplit entièrement d'une couleur.
gfx.get_pixel(x, y)La couleur d'un pixel du dessin, sous forme de nombre, même s'il a été dessiné avec un nom. Elle vaut nil là où rien n'est dessiné, hors de l'écran, et partout avant le premier dessin.
gfx.pixel(x, y, color [, size])Colorie un pixel, ou un carré de size × size pixels centré dessus.
gfx.line(x1, y1, x2, y2, color [, thickness])Une ligne droite de (x1, y1) à (x2, y2), les deux bouts compris.
gfx.rect(x, y, w, h, color [, fill])Un rectangle de w pixels de large et h pixels de haut, son coin en haut à gauche en (x, y).
gfx.circle(x, y, radius, color [, fill])Un cercle autour de (x, y), ou un disque, ou un anneau.
gfx.triangle(x1, y1, x2, y2, x3, y3, color [, fill])Un triangle passant par trois coins, donnés dans n'importe quel ordre.
gfx.fill(x, y, color)Le pot de peinture : repeint toute la zone de la même couleur que le pixel en (x, y).
gfx.text(x, y, text, color [, scale])Écrit du texte avec une minuscule police de 3 × 5 pixels, à n'importe quel pixel et à n'importe quelle taille. Renvoie la largeur occupée.
gfx.image(x, y, rows [, scale [, flip]])Dessine un sprite : une petite image écrite comme du texte, un caractère par pixel.
gfx.scroll(dx, dy [, x, y, w, h])Fait glisser le dessin de (dx, dy). Ce qui sort de la zone est perdu, et ce qui entre de l'autre côté est vide.

La couche de pixels

Chaque case de caractère de l'écran fait 6 pixels de large et 9 pixels de haut : la taille du dessin suit donc celle de la grille de texte.

OrdinateurTextePixelsCouleurs
Calculateur à tubes40 × 14pas de gfxtéléscripteur papier
Ordinateur à transistors51 × 19306 × 171phosphore vert
Mini-ordinateur51 × 19306 × 171phosphore ambre
Micro-ordinateur51 × 19306 × 17116 couleurs
Microcontrôleur40 × 12240 × 10816 couleurs
Ordinateur moderne64 × 24384 × 21616 couleurs

Le Calculateur à tubes imprime sur du papier et ne dessine pas : chez lui, gfx vaut nil. Un programme prévu pour tous les ordinateurs peut tester if gfx then ... end.

Coordonnées

Comme pour term.set_cursor, les coordonnées partent de (1, 1) dans le coin en haut à gauche. x augmente vers la droite, y augmente vers le bas. Les coordonnées décimales sont arrondies vers le bas : vous pouvez passer directement le résultat de math.sin ou d'une division.

Rien n'est jamais hors limites : ce qui dépasse de l'écran est simplement coupé. Un cercle à moitié sorti du bord dessine sa moitié visible, et un pixel en (-5, 400) ne dessine rien, sans erreur.

Couleurs

Une couleur est un nom ("red") ou un nombre de 0 à 15 (term.colors.red, qui vaut 14). Le nom spécial "none" (ou le nombre -1) efface : le pixel redevient vide et laisse voir ce qu'il y a sous le dessin. Les 16 noms et leurs numéros sont dans Couleurs. Un nombre doit être entier, de -1 à 15 : sinon le programme s'arrête avec bad argument #3 to 'pixel' (colour (0..15 or a name) expected, got number).

Les noms sont en anglais. Un nom inconnu arrête le programme, et l'erreur donne la liste des bons :

Brass
gfx.rect(10, 10, 40, 20, "gris", true)
Écran
snippet:1: bad argument #5 to 'rect' (unknown colou
r 'gris': white, orange, magenta, light_blue, yello
w, lime, pink, gray, light_gray, cyan, purple, blue
, brown, green, red, black, none)

Les couches : fond, dessin, texte

L'écran est dessiné en trois couches, de bas en haut :

  1. la couleur de fond de chaque case de caractère (term.set_bg puis term.clear, ou write) ;
  2. le dessin de gfx ;
  3. les caractères du texte.

Le texte s'affiche donc toujours par-dessus le dessin, mais la couleur de fond d'une case est cachée partout où quelque chose est dessiné. Les pixels vides laissent voir les fonds des cases.

Le dessin reste à l'écran quand le programme se termine. term.clear n'efface que le texte : pour retirer le dessin, appelez gfx.clear(). La commande clear du shell, l'arrêt d'un programme avec Ctrl+T et un redémarrage effacent les deux.

Coût

Dessiner n'est pas gratuit. Chaque appel qui change des pixels compte au programme une instruction par tranche de 64 pixels touchés, en plus de l'appel lui-même. Un Micro-ordinateur exécute 1 200 instructions par tick à pleine vitesse, et remplir tout son écran (306 × 171 = 52 326 pixels) en coûte 817. Le texte coûte une instruction par caractère, multipliée par le carré de l'échelle ; un sprite, une par ligne, plus ses pixels.

AppelInstructions
gfx.pixel, gfx.line, gfx.rect, gfx.circle, gfx.triangle, gfx.fill, gfx.scroll, gfx.clear(couleur)1 pour 64 pixels
gfx.textnombre de caractères × échelle²
gfx.image1 par ligne + 1 pour 64 pixels
gfx.clear(), gfx.size, gfx.get_pixelrien de plus que l'appel

L'écran est envoyé aux joueurs qui le regardent au plus tous les 2 ticks (un réglage du serveur), et seule la bande de lignes de pixels qui a changé depuis le dernier envoi passe sur le réseau : du changement le plus haut au plus bas. Un petit cadran animé ne coûte presque rien ; redessiner tout l'écran à chaque tick renvoie toute l'image à chaque fois. Ne dessinez que ce qui change (voir les recettes à la fin de cette page).

Écrans monochromes

L'Ordinateur à transistors (vert) et le Mini-ordinateur (ambre) n'ont qu'une couleur de phosphore. Sur eux, toutes les couleurs sauf le noir s'allument dans la couleur du phosphore, et le noir est l'écran éteint. Un dessin prévu pour eux utilise le noir pour « éteint » et n'importe quelle autre couleur pour « allumé ». (Un Moniteur LCD branché sur eux montre les vraies couleurs : voir Écrans et moniteurs.)

Brass
local noms = {"white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "gray",
  "light_gray", "cyan", "purple", "blue", "brown", "green", "red", "black"}
gfx.clear("black")
gfx.text(8, 8, "LES 16 COULEURS", "white", 2)
for i = 1, 16 do
  local x = 8 + (i - 1) * 18
  gfx.rect(x, 40, 16, 80, noms[i], true)
  gfx.text(x + 2, 126, tostring(i - 1), "white")
end
gfx.rect(277, 39, 18, 82, "white")   -- un cadre autour du noir
gfx.text(8, 150, "LE NOIR (15) EST LE SEUL ETEINT", "white")
Écran
Écran

Le même programme sur un Micro-ordinateur :

Brass
local noms = {"white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "gray",
  "light_gray", "cyan", "purple", "blue", "brown", "green", "red", "black"}
gfx.clear("black")
gfx.text(8, 8, "LES 16 COULEURS", "white", 2)
for i = 1, 16 do
  local x = 8 + (i - 1) * 18
  gfx.rect(x, 40, 16, 80, noms[i], true)
  gfx.text(x + 2, 126, tostring(i - 1), "white")
end
gfx.rect(277, 39, 18, 82, "white")   -- un cadre autour du noir
gfx.text(8, 150, "LE NOIR (15) EST LE SEUL ETEINT", "white")
Écran
Écran
#

gfx.size()

→ table

La taille du dessin en pixels : la taille de la grille de texte multipliée par 6 et par 9.

Renvoie
table
{w = largeur, h = hauteur} en pixels
Brass
local t = gfx.size()
print(t.w, t.h)
local grille = term.get_size()
print(grille.w * 6, grille.h * 9)
Écran
Écran

Utilisez-la plutôt que des nombres fixes, et le même programme s'adapte à tous les écrans. Ce cadre suit les bords de l'écran, quel que soit l'ordinateur :

Brass
local t = gfx.size()
gfx.clear("black")
gfx.line(1, 1, t.w, t.h, "gray")
gfx.line(t.w, 1, 1, t.h, "gray")
gfx.rect(1, 1, t.w, t.h, "yellow", 2)
gfx.text(5, 5, "1,1", "white")
local coin = t.w .. "," .. t.h
gfx.text(t.w - #coin * 4 - 3, t.h - 9, coin, "white")
Écran
Écran

Le même code sur un Microcontrôleur, dont l'écran fait 240 × 108 :

Brass
local t = gfx.size()
gfx.clear("black")
gfx.line(1, 1, t.w, t.h, "gray")
gfx.line(t.w, 1, 1, t.h, "gray")
gfx.rect(1, 1, t.w, t.h, "yellow", 2)
gfx.text(5, 5, "1,1", "white")
local coin = t.w .. "," .. t.h
gfx.text(t.w - #coin * 4 - 3, t.h - 9, coin, "white")
Écran
Écran

Voir aussi term.get_size()

#

gfx.clear([color])

⚙ coût 1 pour 64 px

Efface le dessin, ou le remplit entièrement d'une couleur.

Paramètres
color string|number facultatif
une couleur qui remplit toute l'image ; sans elle (ou avec "none"), le dessin est effacé

Sans couleur, tous les pixels redeviennent vides et les fonds des cases du texte réapparaissent. Cela ne coûte rien. Avec une couleur, toute l'image est peinte, ce qui coûte une instruction pour 64 pixels (817 sur un Micro-ordinateur).

gfx.clear ne touche pas au texte, et term.clear ne touche pas au dessin. Ici, les cases sont mises en gris avec term.set_bg, le dessin les recouvre de bleu, et un rectangle de "none" ouvre une fenêtre où le gris réapparaît :

Brass
term.set_bg(term.colors.gray)
term.clear()                                -- la grille de texte : cases grises
gfx.clear("blue")                           -- le dessin les recouvre
gfx.rect(84, 50, 144, 72, "none", true)     -- pixels effacés : le gris revient
term.set_cursor(16, 9)
term.write("un trou dans le dessin")
term.set_cursor(2, 2)
term.write("Le texte est toujours au-dessus")
Écran
Écran

Un programme qui redessine son écran depuis zéro commence en général par les deux :

Brass
term.set_bg(term.colors.black)
term.clear()
gfx.clear()

Voir aussi term.clear()

#

gfx.get_pixel(x, y)

→ number|nil

La couleur d'un pixel du dessin, sous forme de nombre, même s'il a été dessiné avec un nom. Elle vaut nil là où rien n'est dessiné, hors de l'écran, et partout avant le premier dessin.

Paramètres
x number
colonne du pixel, à partir de 1
y number
ligne du pixel, à partir de 1
Renvoie
number|nil
le numéro de la couleur (0 à 15), ou nil là où rien n'est dessiné
Brass
gfx.rect(10, 10, 20, 20, "red", true)
print(gfx.get_pixel(15, 15))
print(gfx.get_pixel(15, 15) == term.colors.red)
print(gfx.get_pixel(100, 100))
Écran
14
true
nil
Attention

Comparez avec un nombre : gfx.get_pixel(x, y) == "red" est toujours faux. Utilisez term.colors.red ou 14.

Elle ne lit que le dessin, jamais le texte. Elle rend service dans les petits jeux, pour savoir ce qu'une pièce en mouvement va toucher : un mur dessiné en gris, une pièce d'or dessinée en jaune.

Points et lignes

#

gfx.pixel(x, y, color [, size])

⚙ coût 1 pour 64 px

Colorie un pixel, ou un carré de size × size pixels centré dessus.

Paramètres
x number
colonne, à partir de 1
y number
ligne, à partir de 1
color string|number
un nom ou un numéro de couleur, "none" pour effacer
size number facultatif
le côté d'un carré centré sur (x, y), de 1 à 64 (1 par défaut)

Avec une taille impaire, (x, y) est pile au milieu. Avec une taille paire, la ligne et la colonne en trop vont à droite et en bas. Au-delà de 64, la taille est ramenée à 64. Les points rouges marquent le point donné :

Brass
gfx.clear("black")
local tailles = {1, 2, 3, 5, 9, 15, 25}
local x = 16
for i = 1, #tailles do
  gfx.pixel(x, 60, "yellow", tailles[i])
  gfx.pixel(x, 60, "red")
  gfx.text(x - 2, 90, tostring(tailles[i]), "white")
  x = x + 22 + tailles[i]
end
gfx.text(8, 8, "GFX.PIXEL(X, Y, COLOR, SIZE)", "white", 2)
gfx.text(8, 130, "LE POINT ROUGE EST LE POINT (X, Y)", "red")
Écran
Écran

Les points sont l'outil idéal pour les relevés : une mesure, un point. Ici une journée d'une courbe d'ensoleillement, un point tous les 4 pixels :

Brass
local t = gfx.size()
gfx.clear("black")
gfx.line(1, 150, t.w, 150, "gray")
for x = 1, t.w, 4 do
  local y = 150 - math.max(0, math.sin((x - 20) / 270 * math.pi)) * 120
  gfx.pixel(x, y, "yellow", 3)
end
gfx.text(4, 158, "6:00", "white")
gfx.text(t.w - 22, 158, "18:00", "white")
Écran
Écran

Voir aussi gfx.rect() gfx.get_pixel()

#

gfx.line(x1, y1, x2, y2, color [, thickness])

⚙ coût 1 pour 64 px

Une ligne droite de (x1, y1) à (x2, y2), les deux bouts compris.

Paramètres
x1 number
colonne du départ
y1 number
ligne du départ
x2 number
colonne de l'arrivée
y2 number
ligne de l'arrivée
color string|number
un nom ou un numéro de couleur, "none" pour effacer
thickness number facultatif
épaisseur de 1 à 64 pixels (1 par défaut)

Une ligne fine fait un pixel de large et n'a pas de trous, même quand elle monte en pente raide. Une ligne épaisse tamponne un carré de thickness pixels à chaque point : ses bouts sont carrés, et elle coûte plus cher. Une ligne de 100 pixels épaisse de 8 touche 6 400 pixels.

Brass
gfx.clear("black")
for i = 1, 4 do
  local e = 2 ^ (i - 1)
  local y = 10 + i * 30
  gfx.line(16, y + 12, 136, y - 8, "orange", e)
  gfx.text(146, y, "EPAISSEUR " .. e, "white")
end
for degres = 0, 330, 30 do
  local a = degres * math.pi / 180
  gfx.line(254, 86, 254 + math.cos(a) * 44, 86 + math.sin(a) * 44, "yellow")
end
gfx.circle(254, 86, 10, "yellow", true)
Écran
Écran

Des lignes entre les mesures d'un capteur font un graphique. Chaque segment relie une valeur à la suivante :

Brass
local niveaux = {40, 52, 47, 70, 95, 88, 120, 112, 90, 64, 58, 75}
gfx.clear("black")
gfx.rect(10, 10, 286, 150, "gray")
for i = 1, #niveaux - 1 do
  local x1 = 12 + (i - 1) * 25
  local x2 = 12 + i * 25
  gfx.line(x1, 158 - niveaux[i], x2, 158 - niveaux[i + 1], "lime", 2)
end
gfx.text(16, 16, "NIVEAU DU RESERVOIR, 12 DERNIERES HEURES", "white")
Écran
Écran

Voir aussi gfx.pixel() gfx.triangle()

Formes

Les formes prennent leur taille et leur couleur, puis un dernier argument facultatif qui dit comment les dessiner : sans lui, un contour d'un pixel ; true, pleine ; pour les rectangles et les cercles, un nombre donne l'épaisseur du bord.

#

gfx.rect(x, y, w, h, color [, fill])

⚙ coût 1 pour 64 px

Un rectangle de w pixels de large et h pixels de haut, son coin en haut à gauche en (x, y).

Paramètres
x number
colonne du coin en haut à gauche
y number
ligne du coin en haut à gauche
w number
largeur en pixels
h number
hauteur en pixels
color string|number
un nom ou un numéro de couleur, "none" pour effacer
fill boolean|number facultatif
true pour le remplir, ou l'épaisseur du bord (1 à 64) ; un contour d'un pixel sans lui

Le bord est dessiné à l'intérieur du rectangle : gfx.rect(10, 10, 50, 20, "red", 3) reste dans les mêmes 50 × 20 pixels que le rectangle plein, avec un trou au milieu. Quand le bord fait au moins la moitié du plus petit côté, le rectangle est tout simplement plein. Une largeur ou une hauteur nulle ou négative ne dessine rien.

Brass
gfx.clear("black")
gfx.rect(10, 20, 86, 50, "white")
gfx.rect(110, 20, 86, 50, "lime", true)
gfx.rect(210, 20, 86, 50, "orange", 6)
gfx.text(10, 76, "CONTOUR", "white")
gfx.text(110, 76, "TRUE : PLEIN", "white")
gfx.text(210, 76, "6 : BORD", "white")
-- une barre de progression : un cadre, puis la partie remplie
local fait = 0.7
gfx.rect(10, 110, 200, 22, "white")
gfx.rect(12, 112, math.floor(196 * fait), 18, "green", true)
gfx.text(220, 116, math.round(fait * 100) .. "% BROYE", "white")
Écran
Écran

Les rectangles pleins sont aussi le moyen le plus rapide d'effacer une partie de l'image avant de la redessiner : peignez-la de la couleur du fond, ou avec "none".

Voir aussi gfx.pixel() gfx.clear()

#

gfx.circle(x, y, radius, color [, fill])

⚙ coût 1 pour 64 px

Un cercle autour de (x, y), ou un disque, ou un anneau.

Paramètres
x number
colonne du centre
y number
ligne du centre
radius number
le rayon en pixels ; 0 dessine un seul pixel
color string|number
un nom ou un numéro de couleur, "none" pour effacer
fill boolean|number facultatif
true pour un disque, ou l'épaisseur de l'anneau (1 à 64) ; un contour d'un pixel sans lui

Le cercle fait 2 × radius + 1 pixels de diamètre. L'épaisseur du bord pousse vers l'intérieur, comme le bord d'un rectangle. Un rayon négatif ne dessine rien.

Brass
gfx.clear("black")
gfx.circle(40, 50, 28, "white")
gfx.circle(40, 120, 28, "light_blue", true)
gfx.circle(110, 85, 34, "orange", 6)
gfx.text(14, 160, "CONTOUR, DISQUE, ANNEAU", "white")
-- une Roue dentée de Create : d'abord les dents, puis la roue par-dessus
local cx, cy = 226, 85
for i = 0, 11 do
  local a = i * math.pi / 6
  gfx.pixel(cx + math.cos(a) * 52, cy + math.sin(a) * 52, "brown", 13)
end
gfx.circle(cx, cy, 50, "brown", true)
gfx.circle(cx, cy, 40, "orange", 3)
gfx.circle(cx, cy, 14, "light_gray", true)
gfx.circle(cx, cy, 5, "black", true)
Écran
Écran

Avec math.cos et math.sin, les cercles deviennent des voyants, des cadrans et des écrans radar. Une couronne de 8 voyants, allumés selon un motif :

Brass
local allumes = {true, true, false, true, false, false, true, true}
gfx.clear("black")
for i = 1, 8 do
  local a = (i - 1) * math.pi / 4 - math.pi / 2
  local x = 153 + math.cos(a) * 60
  local y = 86 + math.sin(a) * 60
  local couleur = "gray"
  if allumes[i] then couleur = "lime" end
  gfx.circle(x, y, 12, couleur, true)
  gfx.circle(x, y, 12, "white")
  gfx.text(x - 1, y - 2, tostring(i), "black")
end
Écran
Écran

Voir aussi gfx.rect() math.cos()

#

gfx.triangle(x1, y1, x2, y2, x3, y3, color [, fill])

⚙ coût 1 pour 64 px

Un triangle passant par trois coins, donnés dans n'importe quel ordre.

Paramètres
x1 number
colonne du premier coin
y1 number
ligne du premier coin
x2 number
colonne du deuxième coin
y2 number
ligne du deuxième coin
x3 number
colonne du troisième coin
y3 number
ligne du troisième coin
color string|number
un nom ou un numéro de couleur, "none" pour effacer
fill boolean facultatif
true pour le remplir ; un contour d'un pixel sinon

Contrairement aux rectangles et aux cercles, le triangle n'a pas d'épaisseur de bord : le dernier argument vaut true pour un triangle plein, et tout le reste dessine le contour. Pour un contour épais, tracez trois lignes avec une épaisseur.

Les triangles font des toits, des montagnes, et surtout des flèches :

Brass
gfx.clear("black")
gfx.triangle(20, 80, 60, 16, 100, 80, "green", true)
gfx.triangle(120, 80, 160, 16, 200, 80, "white")
gfx.text(20, 88, "PLEIN", "white")
gfx.text(120, 88, "CONTOUR", "white")
-- des flèches de tendance à côté de deux stocks
gfx.triangle(20, 140, 30, 124, 40, 140, "lime", true)
gfx.text(48, 130, "FER  +120/H", "lime")
gfx.triangle(150, 124, 160, 140, 170, 124, "red", true)
gfx.text(178, 130, "CHARBON  -45/H", "red")
Écran
Écran

Voir aussi gfx.line()

#

gfx.fill(x, y, color)

⚙ coût 1 pour 64 px

Le pot de peinture : repeint toute la zone de la même couleur que le pixel en (x, y).

Paramètres
x number
colonne du pixel de départ
y number
ligne du pixel de départ
color string|number
la nouvelle couleur, ou "none" pour effacer la zone

La zone s'étend depuis (x, y) aux voisins du dessus, du dessous, de gauche et de droite qui ont exactement la même couleur que le pixel de départ. Elle s'arrête sur toute autre couleur. Les contours tracés par gfx.line, gfx.circle et gfx.triangle sont fermés pour elle, même là où ils passent en diagonale : l'intérieur d'une forme se remplit sans fuir.

Les pixels vides comptent comme une couleur à part : sur une image effacée avec gfx.clear("black"), un trou fait avec "none" est un mur pour un remplissage qui part du noir.

Rien ne se passe quand le pixel a déjà la nouvelle couleur, ou quand (x, y) est hors de l'écran. Le remplissage s'arrête après 1 048 576 pixels, bien plus que le plus grand écran (82 944 pixels sur un Ordinateur moderne) : en pratique, il remplit toujours toute la zone, à une instruction pour 64 pixels.

Une forme fermée se remplit seulement à l'intérieur. Ouvrez une brèche d'un pixel, et la peinture s'échappe :

Brass
gfx.clear("black")
gfx.rect(4, 20, 146, 120, "gray")
gfx.rect(156, 20, 146, 120, "gray")
gfx.circle(77, 80, 40, "white")
gfx.circle(229, 80, 40, "white")
gfx.rect(266, 76, 6, 9, "black", true)     -- une brèche dans le cercle de droite
gfx.fill(77, 80, "orange")
gfx.fill(229, 80, "orange")
gfx.text(30, 148, "FERME", "white")
gfx.text(170, 148, "UNE BRECHE : CA FUIT", "white")
Écran
Écran

L'exemple shapes de l'ordinateur s'en sert pour les clics : chaque clic peint la zone sous la souris (voir shapes).

Voir aussi gfx.get_pixel()

Texte et sprites

#

gfx.text(x, y, text, color [, scale])

→ number⚙ coût caractères × échelle²

Écrit du texte avec une minuscule police de 3 × 5 pixels, à n'importe quel pixel et à n'importe quelle taille. Renvoie la largeur occupée.

Paramètres
x number
colonne du bord gauche de la première lettre
y number
ligne du haut des lettres
text string
le texte (un nombre est changé en texte)
color string|number
un nom ou un numéro de couleur, "none" pour effacer
scale number facultatif
de 1 à 64, la taille de chaque point de la police (1 par défaut)
Renvoie
number
la largeur du texte dessiné, en pixels

Chaque lettre fait 3 points de large et 5 de haut, suivie d'une colonne vide : 4 pixels par caractère, si bien qu'une ligne de 76 caractères tient sur un Micro-ordinateur à l'échelle 1. À l'échelle 2, chaque point devient un carré de 2 × 2 pixels, et ainsi de suite.

La police n'a que des majuscules (les minuscules sont dessinées en majuscules), des chiffres et ces signes : espace . , : ; ! ? ' " - + = / ( ) < > % * _ # [ ]. Les accents sont retirés (É est dessiné E), et tout autre caractère devient ?.

Brass
gfx.clear("black")
gfx.text(4, 4, "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789", "white")
gfx.text(4, 12, ".,:;!?'\"-+=/()<>%*_#[]", "white")
gfx.text(4, 22, "les minuscules deviennent des capitales", "yellow")
gfx.text(4, 30, "Déjà vu : les accents sont retirés", "yellow")
gfx.text(4, 38, "Sans dessin : @ & $ { } ~", "red")
gfx.text(4, 54, "ECHELLE 2", "lime", 2)
gfx.text(4, 72, "ECHELLE 3", "lime", 3)
gfx.text(4, 98, "ECHELLE 6", "orange", 6)
Écran
Écran

La largeur renvoyée vaut (4 × caractères - 1) × échelle : la dernière colonne vide n'est pas comptée.

Brass
print(gfx.text(1, 1, "SALUT", "white"))
print(gfx.text(1, 10, "SALUT", "white", 2))
print(gfx.text(1, 30, "", "white"))
Écran
19
38
0

Comme la largeur est connue d'avance, centrer n'est qu'une soustraction. Gardez une petite fonction comme celle-ci en haut de vos programmes :

Brass
local t = gfx.size()
local function centrer(y, texte, couleur, echelle)
  local largeur = (#texte * 4 - 1) * echelle
  gfx.text((t.w - largeur) // 2 + 1, y, texte, couleur, echelle)
end
gfx.clear("blue")
gfx.rect(1, 1, t.w, 30, "black", true)
centrer(9, "GARE DU NORD", "yellow", 3)
centrer(60, "PROCHAIN TRAIN", "white", 2)
centrer(84, "2 MIN", "lime", 5)
centrer(140, "VOIE 3, DIRECTION LA MINE", "white", 1)
Écran
Écran

La largeur renvoyée sert aussi à placer le morceau de texte suivant, pour une étiquette suivie d'une valeur d'une autre couleur :

Brass
local etat = {
  {"PRESSION", "OK", "lime"},
  {"CHAUDIERE", "CHAUFFE", "yellow"},
  {"STRESS", "SURCHARGE", "red"},
}
gfx.clear("black")
for i, ligne in ipairs(etat) do
  local y = 12 + (i - 1) * 24
  local x = 10 + gfx.text(10, y, ligne[1], "white", 2)
  gfx.text(x + 8, y, ligne[2], ligne[3], 2)
end
Écran
Écran
Note

Le texte de gfx.text fait partie du dessin : il est sous les caractères de print, il s'efface avec "none" et il se déplace avec gfx.scroll. Chaque caractère coûte des instructions (échelle² chacun) : un grand titre dessiné une fois ne coûte presque rien, mais redessiner à chaque tick une page entière de texte à l'échelle 3, si.

Voir aussi gfx.image() term.write()

#

gfx.image(x, y, rows [, scale [, flip]])

⚙ coût lignes + 1 pour 64 px

Dessine un sprite : une petite image écrite comme du texte, un caractère par pixel.

Paramètres
x number
colonne du coin en haut à gauche
y number
ligne du coin en haut à gauche
rows table
une liste de chaînes, une par ligne de pixels, un chiffre hexadécimal par pixel
scale number facultatif
de 1 à 64, chaque pixel du sprite devient un carré de cette taille (1 par défaut)
flip boolean facultatif
true retourne le sprite de gauche à droite, comme dans un miroir

Chaque ligne est une chaîne. Chaque caractère est un chiffre hexadécimal, le numéro d'une couleur : 0 blanc, 1 orange... 9 cyan, a violet, b bleu, c marron, d vert, e rouge, f noir (les majuscules marchent aussi). Tout autre caractère, comme . ou une espace, est transparent : le pixel garde ce qu'il y avait avant. Le tableau complet est dans Couleurs.

Brass
gfx.clear("light_blue")
gfx.rect(1, 110, 306, 62, "green", true)
local creeper = {
  "5d5dd5d5",
  "d5ddd5dd",
  "dffd5ffd",
  "5ffddff5",
  "dd5ffd5d",
  "d5ffffdd",
  "5dffffd5",
  "ddfd5fdd",
}
gfx.image(10, 10, creeper)
gfx.image(30, 10, creeper, 4)
gfx.image(76, 10, creeper, 8)
gfx.text(10, 84, "ECHELLES 1, 4 ET 8", "black")
local fleche = {
  "....4....",
  "....44...",
  "44444444.",
  "444444444",
  "44444444.",
  "....44...",
  "....4....",
}
gfx.image(170, 20, fleche, 4)
gfx.text(214, 32, "NORMAL", "black")
gfx.image(170, 96, fleche, 4, true)      -- en miroir, à moitié sur l'herbe
gfx.text(214, 108, "MIROIR", "black")
Écran
Écran

scale fait de chaque pixel un carré, et flip retourne le sprite : un personnage qui marche à gauche et à droite n'a besoin que d'une image. Chaque ligne est retournée sur sa propre longueur : donnez la même longueur à toutes les lignes (complétez avec des .), sinon le sprite retourné se disloque.

Une ligne qui n'est pas une chaîne arrête le programme : bad argument #3 to 'image' (row 2 is not a string). Il n'y a pas de chiffre pour « effacer » : pour retirer un sprite, dessinez par-dessus un rectangle de la couleur du fond (ou de "none").

Voir aussi gfx.text() Couleurs

Défilement

#

gfx.scroll(dx, dy [, x, y, w, h])

⚙ coût 1 pour 64 px de la zone

Fait glisser le dessin de (dx, dy). Ce qui sort de la zone est perdu, et ce qui entre de l'autre côté est vide.

Paramètres
dx number
pixels vers la droite (négatif : vers la gauche)
dy number
pixels vers le bas (négatif : vers le haut) ; 0 par défaut
x number facultatif
colonne du coin en haut à gauche de la zone à déplacer
y number facultatif
ligne du coin en haut à gauche de la zone
w number facultatif
largeur de la zone
h number facultatif
hauteur de la zone

Avec quatre nombres de plus, seul ce rectangle bouge et le reste de l'image ne change pas : une barre d'état peut rester en haut pendant que le paysage défile dessous. Donnez les quatre ou aucun. Le texte de l'écran ne bouge pas (c'est le rôle de term.scroll), et avant tout dessin, l'appel ne fait rien.

Un défilement coûte autant que de remplir sa zone une fois (une instruction pour 64 pixels), quel que soit ce qui y est dessiné : un paysage fait de centaines de formes se déplace pour le prix d'un rectangle, et seule la nouvelle bande au bord reste à dessiner. C'est ainsi que marchent les jeux à défilement horizontal et les graphiques en direct. Ici, les collines arrivent par la droite, 2 pixels par tick, sous une barre de titre qui ne bouge pas :

Brass
local t = gfx.size()
local HAUT = 20
gfx.clear("light_blue")
gfx.rect(1, 1, t.w, HAUT - 1, "gray", true)
gfx.text(4, 7, "COLLINES QUI DEFILENT", "white")
local pas = 0
while true do
  pas = pas + 1
  gfx.scroll(-2, 0, 1, HAUT, t.w, t.h - HAUT + 1)  -- tout ce qui est sous la barre, 2 pixels à gauche
  local sol = 120 + math.floor(18 * math.sin(pas / 9) + 7 * math.sin(pas / 3.7))
  gfx.rect(t.w - 1, HAUT, 2, sol - HAUT, "light_blue", true)
  gfx.rect(t.w - 1, sol, 2, 4, "lime", true)
  gfx.rect(t.w - 1, sol + 4, 2, t.h - sol - 3, "brown", true)
  sleep(0.05)
end
Écran
Écran

Voir aussi term.scroll()

Recettes

De petits programmes complets qui combinent les fonctions ci-dessus. Ils utilisent des valeurs d'exemple pour tourner partout : dans votre monde, les valeurs viennent de peripheral.wrap(), inventory.count(), kinetic.speed() et des autres appareils.

Un graphique en barres

Le stock d'un entrepôt, une barre par objet, à l'échelle pour que le plus grand remplisse le graphique :

Brass
local stock = {
  {nom = "FER", nombre = 1450, couleur = "light_gray"},
  {nom = "CUIVRE", nombre = 820, couleur = "orange"},
  {nom = "ZINC", nombre = 610, couleur = "light_blue"},
  {nom = "LAITON", nombre = 1980, couleur = "yellow"},
  {nom = "ANDESITE", nombre = 2400, couleur = "gray"},
}
local t = gfx.size()
local BASE = t.h - 20       -- le bas des barres
local HAUTEUR_MAX = 110
local plus_grand = 0
for _, objet in ipairs(stock) do
  plus_grand = math.max(plus_grand, objet.nombre)
end

local function centrer(x, l, y, texte, couleur)
  gfx.text(x + (l - (#texte * 4 - 1)) // 2, y, texte, couleur)
end

gfx.clear("black")
gfx.text(6, 6, "STOCK DE L'ENTREPOT", "white", 2)
gfx.line(4, BASE + 1, t.w - 4, BASE + 1, "gray")
for i, objet in ipairs(stock) do
  local x = 14 + (i - 1) * 58
  local h = math.max(1, math.floor(objet.nombre / plus_grand * HAUTEUR_MAX))
  gfx.rect(x, BASE - h + 1, 40, h, objet.couleur, true)
  centrer(x, 40, BASE - h - 7, tostring(objet.nombre), "white")
  centrer(x, 40, BASE + 6, objet.nom, objet.couleur)
end
Écran
Écran

Un cadran

Un compte-tours pour un arbre de Create : un demi-cercle de points colorés, une graduation tous les 32 tr/min, et une aiguille. L'angle va de π (à gauche, 0 tr/min) à 2π (à droite, 256 tr/min). Le sinus de ces angles est négatif et y augmente vers le bas de l'écran : l'arc monte donc au-dessus de son centre.

Brass
local CX, CY, R = 153, 124, 84
local vitesse = 192
gfx.clear("black")
for degres = 0, 180, 3 do
  local valeur = degres / 180 * 256
  local couleur = "lime"
  if valeur > 224 then
    couleur = "red"
  elseif valeur > 160 then
    couleur = "yellow"
  end
  local a = math.pi + degres * math.pi / 180
  gfx.pixel(CX + math.cos(a) * R, CY + math.sin(a) * R, couleur, 5)
end
for v = 0, 256, 32 do
  local a = math.pi + v / 256 * math.pi
  gfx.line(CX + math.cos(a) * (R - 16), CY + math.sin(a) * (R - 16),
    CX + math.cos(a) * (R - 8), CY + math.sin(a) * (R - 8), "white")
end
local a = math.pi + vitesse / 256 * math.pi
gfx.line(CX, CY, CX + math.cos(a) * (R - 22), CY + math.sin(a) * (R - 22), "red", 3)
gfx.circle(CX, CY, 7, "light_gray", true)
local texte = vitesse .. " TR/MIN"
gfx.text(CX - (#texte * 4 - 1), CY + 18, texte, "white", 2)
Écran
Écran

Quand la vitesse change, inutile de redessiner le cadran : redessinez l'ancienne aiguille en noir, puis la nouvelle.

Animer sans scintillement

L'écran parvient aux joueurs tous les 2 ticks. Si une image demande plus d'un tick d'instructions (un effacement complet suivi d'un redessin y arrive facilement), le programme se met en pause au milieu et les joueurs peuvent voir une image à moitié dessinée : l'écran scintille. Deux habitudes l'évitent :

  • N'effacez pas tout l'écran à chaque image. Effacez seulement ce qui bouge (un rectangle de la couleur du fond sur son ancienne place), puis dessinez-le à sa nouvelle place.
  • **Faites glisser ce qui est déjà dessiné avec gfx.scroll**, puis ne dessinez que la nouvelle partie. L'image n'est jamais vide, et chaque étape coûte une fraction d'un redessin.

Un graphique en direct du stress d'une usine fonctionne ainsi : à chaque tick, le graphique glisse de 2 pixels vers la gauche et un seul nouveau segment est tracé au bord droit.

Brass
local X, Y, L, H = 10, 30, 286, 120      -- la zone du graphique
gfx.clear("black")
gfx.text(10, 10, "STRESS", "white", 2)
gfx.rect(X - 1, Y - 1, L + 2, H + 2, "gray")
local avant = Y + H // 2
local n = 0
while true do
  n = n + 1
  -- une mesure d'exemple de 0 à 1 ; dans un monde : stress / capacité d'un appareil cinétique
  local charge = 0.5 + 0.35 * math.sin(n / 8) + 0.1 * math.sin(n / 2.3)
  local y = Y + H - 1 - math.floor(charge * (H - 1))
  gfx.scroll(-2, 0, X, Y, L, H)
  local couleur = "lime"
  if charge > 0.8 then couleur = "red" end
  gfx.line(X + L - 3, avant, X + L - 1, y, couleur)
  avant = y
  sleep(0.05)
end
Écran
Écran

Une animation de sprite

Une petite locomotive roule sur ses rails. Elle a deux images dont les roues diffèrent, montrées tour à tour, et elle est retournée quand elle fait demi-tour. Chaque étape efface l'ancienne place avec un rectangle couleur ciel : le reste de l'image n'est jamais redessiné.

Brass
local IMAGES = {
  {
    ".........7777...",
    ".cccc.....77....",
    ".c33c.....77....",
    ".cccceeeeeeeeeee",
    "4cccceeeeeeeeeee",
    "4cccceeeeeeeeeee",
    "ffffffffffffffff",
    "..f.f..f.f..f.f.",
    "...8....8....8..",
    "..f.f..f.f..f.f.",
  },
  {
    ".........7777...",
    ".cccc.....77....",
    ".c33c.....77....",
    ".cccceeeeeeeeeee",
    "4cccceeeeeeeeeee",
    "4cccceeeeeeeeeee",
    "ffffffffffffffff",
    "...f....f....f..",
    "..f8f..f8f..f8f.",
    "...f....f....f..",
  },
}
local ECHELLE = 4
local Y = 80
local t = gfx.size()
gfx.clear("light_blue")
gfx.rect(1, Y + 40, t.w, 4, "gray", true)            -- les rails
gfx.rect(1, Y + 44, t.w, t.h - Y - 43, "green", true)
gfx.text(8, 8, "LA NAVETTE DE LA MINE", "white", 2)
local x, dx, image = 10, 4, 1
while true do
  gfx.rect(x, Y, 16 * ECHELLE, 10 * ECHELLE, "light_blue", true)   -- efface l'ancienne place
  x = x + dx
  if x + 16 * ECHELLE > t.w or x < 1 then
    dx = -dx                                           -- demi-tour
    x = x + 2 * dx
  end
  image = 3 - image                                    -- 1, 2, 1, 2...
  gfx.image(x, Y, IMAGES[image], ECHELLE, dx < 0)      -- en miroir vers la gauche
  sleep(0.1)
end
Écran
Écran

Des boutons en pixels

Des boutons de n'importe quelle taille, n'importe où, avec une étiquette dans la police en pixels. Un évènement de clic porte e.px et e.py, le pixel sous la souris (ou sous le doigt, sur un moniteur) : le bouton touché est celui dont le rectangle contient ce point. Voir Écrans et moniteurs pour les clics sur les moniteurs.

Brass
local boutons = {
  {x = 12, y = 44, l = 88, h = 64, texte = "PRESSE", couleur = "orange", face = "left", actif = true},
  {x = 109, y = 44, l = 88, h = 64, texte = "MIXEUR", couleur = "light_blue", face = "right", actif = false},
  {x = 206, y = 44, l = 88, h = 64, texte = "VENTILO", couleur = "red", face = "top", actif = false},
}

local function dessiner(b)
  local fond, encre = "gray", "white"
  if b.actif then
    fond, encre = b.couleur, "black"
  end
  gfx.rect(b.x, b.y, b.l, b.h, fond, true)
  gfx.rect(b.x, b.y, b.l, b.h, "white", 2)
  local largeur = (#b.texte * 4 - 1) * 2
  gfx.text(b.x + (b.l - largeur) // 2, b.y + b.h // 2 - 4, b.texte, encre, 2)
end

local function dedans(b, px, py)
  return px >= b.x and px < b.x + b.l and py >= b.y and py < b.y + b.h
end

gfx.clear("black")
gfx.text(12, 12, "COMMANDES LIGNE 2", "white", 2)
gfx.text(12, 130, "CLIQUEZ UN BOUTON POUR SA MACHINE", "light_gray")
for _, b in ipairs(boutons) do
  dessiner(b)
end
while true do
  local e = os.pull_event("click")
  for _, b in ipairs(boutons) do
    if dedans(b, e.px, e.py) then
      b.actif = not b.actif
      rs.set(b.face, b.actif)
      dessiner(b)               -- ne redessine que le bouton qui a changé
    end
  end
end
Écran
Écran

Le test lui-même n'est que de l'arithmétique : vous pouvez le vérifier sans cliquer.

Brass
local presse = {x = 12, y = 40, l = 88, h = 40}
local function dedans(b, px, py)
  return px >= b.x and px < b.x + b.l and py >= b.y and py < b.y + b.h
end
print(dedans(presse, 50, 60))
print(dedans(presse, 100, 60))
Écran
true
false

px < b.x + b.l, avec un < strict, parce qu'un bouton de 88 pixels de large qui commence en 12 finit au pixel 99.