Avant l'avènement des calculatrices graphiques, c'est
Nintendo
qui lança le tout premier jeu électronique de poche muni d'un écran LCD
(cristaux liquides)
exploitant les deux dimensions,
Ball
en avril 1980. Comme indiqué par sa référence
AC-01
, c'était le premier né de la gamme
Game & Watch
, largement imitée au siècle dernier par d'autres constructeurs asiatiques pour le meilleur et pour le pire. Au lieu de simples points, les pixels ici allumés à l'écran ont des formes représentant des éléments du jeu.
avait déjà rendu un bel hommage à ces produits avec une adaptation pour ta
Graph 35/75+E
de
Helmet
, sorti en février 1981 sous la référence
CN-07
, soit le 7ème jeu de la gamme. Il est à noter que le jeu fut renommé
Headache
pour sa commercialisation dans plusieurs zones d'Europe, le mot anglais
Helmet
(casque)
se retrouvant sous à l'identique en allemand et renvoyant donc aux heures sombres de l'Europe.
Dans ce jeu te voici ouvrier sur un chantier de construction, et tu te dois de traverser la cour pour atteindre la cabane à outils pendant que l'on t'en maintient la porte ouverte. Et cela bien sûr en évitant les divers outils qui tombent du ciel sur ce genre de chantier.
sorti en 1994 initialement sur borne d'arcade, puis adapté pour consoles de jeux et
Windows
en 1995.
Taito
nous ressortait donc ses charmants dinosaures
Bub
et
Bob
introduits dans son jeu d'arcade mythique de 1983,
Bubble Bobble
. Ces derniers manœuvrent ici un canon qui lance des bulles colorées. Le but est de nettoyer intégralement chaque tableau, sachant que juxtaposer 3 bulles de la même couleur les fait éclater.
, un portage de haute facture rien qu'à regarder les graphismes.
Mais voilà, la technologie
B-System
utilisée pour les bornes d'arcade
Taito
de l'époque offrait une résolution de 320×224 pixels dont tu ne profitais pas ici sur ta calculatrice.
L'écran de ta calculatrice dispose certes d'une définition de 396×224 pixels matériellement plus que suffisante.
Mais logiciellement, le système d'exploitation ne t'offre pas d'accès à une bordure qu'il se réserve pour indiquer le bon fonctionnement du mode examen :
marge de 6 pixels à gauche
marge de 6 pixels à droite
marge de 8 pixels en bas
Par défaut les applications ne peuvent donc afficher qu'en 384×216 pixels.
Pas de redimensionnement réduisant la qualité du rendu ici, le rendu était tout simplement tronqué. Il te manquait 8 lignes de pixels en haut d'écran, ce qui pouvait notamment se remarquer sur les fonds d'écran.
est de retour aujourd'hui avec une nouvelle version
1.02
. Lorsque son application prend la main sur le système, il se donne ici la peine de reconfigurer le contrôleur écran pour autoriser l'affichage plein écran 396×224 pixels, et donc te faire profiter cette fois-ci du jeu dans sa définition originale de 320×224 pixels au complet !
Une mise à jour majeure pour continuer à bien prendre soin de toi; merci
Avant de poursuivre notre exploration des modules, voyons un petit peu où nous en sommes aujourd'hui niveau mémoire.
Attention, nous ne disposons que d'une préversion. Il est donc parfaitement possible que plusieurs points abordés dans notre critique soient améliorés d'ici la sortie.
Les interpréteurs
MicroPython
ou similaires qui tournent sur ta calculatrice font appel à 3 types de mémoires avec les rôles suivants :
la mémoire de stockage qui accueille et conserve tes scripts
Python
le
stack (pile)
qui, à l'exécution, accueille les références vers les objets
Python
créés
le
heap (tas)
qui, à l'exécution, accueille les valeurs de ces objets
Python
En gros le
stack / pile
limite donc le nombre d'objets différents pouvant exister simultanément en mémoire, alors que le
heap / tas
limite la taille globale occupée par ces objets. A moins d'avoir de gros besoins en terme de récursivité, le facteur limitant pour tes projets
def sizeenv(): s=0 import __main__ for o in dir(__main__): try:s+=size(eval(o)) except:pass return s def size(o): s,t=0,type(o) if t==str:s=49+len(o) if str(t)=="<class 'function'>":s=136 if t==int: s=24 while o: s+=4 o>>=30 if t==list: s+=64 for so in o:s+=8+size(so) return s def mem(v=1,r=1): try: l=[] try: l+=[r and 793+sizeenv()] if v*r:print(" ",l[0]) l+=[0] l+=[""] l[2]+="x" while 1: try:l[2]+=l[2][l[1]:] except: if l[1]<len(l[2])-1:l[1]=len(l[2])-1 else:raise(Exception) except: if v:print("+",size(l)) try:l[0]+=size(l) except:pass try:l[0]+=mem(v,0) except:pass return l[0] except:return 0
Les formidables nouveautés de la version
5.5
semblent hélas avoir un coût important, à vide nous trouvons désormais à peine
17,5K
de libres avec la version
5.5
, un effondrement non négligeable de plus de 2,5K.
La
TI-83 Premium CE Édition Python
fait partie des très rares calculatrices disposant du module
gc
, module qui va nous être bien pratique pour comprendre ce qui se passe. Retenons les appels suivants :
gc.mem_alloc() retourne l'espace occupé sur le tas
from gc import * a, f = mem_alloc(), mem_free() (a, f, a+f)
Nous découvrons donc que le tas
Python
de la
TI-83 Premium CE Édition Python
fait exactement
19,968K
; sa capacité totale n'a donc pas été réduite.
Mais, lorsque tu lances tes scripts
Python
plusieurs choses sont initialisées et consomment du tas avant même l'exécution de ta première ligne de code. Et c'est là qu'il y a une lourde différence :
avec la dernière version
5.4
: juste
608o
consommés pour pas moins de
19,36Ko
de libres
avec la prochaine version
5.5
: pas moins de
2912o
consommés pour juste
17,056Ko
de libres
Effectivement, on retrouve bien cette consommation supplémentaire de 2,5K sur le tas avant même que tu aies tapé la moindre ligne de
Python
.
En passant il est normal que l'on trouve ici à chaque fois un petit peu moins que l'espace libre trouvé avec
mem()
, puisqu'il y a ici l'importation obligatoire du module
gc
qui consomme également du tas.
D'où le classement en terme de capacité tas
(heap)
disponible pour tes scripts, avec la capacité totale précisée lorsque le module
pour la capacité tas, et cela ne fait donc qu'empirer.
C'est probablement largement suffisant pour les petits
algo-musements
de quelques lignes qui seront traités en Mathématiques ou Physique-Chimie.
Le problème est ailleurs, ceux qui auront suffisamment accroché pour avoir envie d'aller plus loin risquent d'être rapidement déçus par leur calculatrice... Ceux qui aborderont des projets
(
SNT
,
NSI
, ... ou même non scolaires)
risquent d'obtenir des erreurs de mémoire assez rapidement après le début du projet...
Car le
Python
ce n'est pas du
C
, les objets
Python
les plus élémentaires sont extrêmement gourmands en mémoire :
64 octets rien que pour une liste vide
plus 8 octets pour chaque élément de liste supplémentaire, sans compter la taille de l'élément en question
24 octets pour un entier nul
28 octets pour un entier court non nul
49 octets rien que pour une chaîne vide
plus 1 octet par caractère de chaîne supplémentaire
...
Alors imagine la catastrophe quand pour des projets tu multiplies le nombre de ces objets au sein de listes...
Quand on pense à tout ce que nous avons pu souffrir sur
NumWorks
depuis la rentrée 2017 avec seulement 16K de tas disponible avant la mise à jour
13.2
faisant passer cela à 32K, nous sommes en effet assez pessimistes ici devant nos même pas
17.5K
de tas.
Il est possible que l'on ne puisse pas importer simultanément l'ensemble des modules. Il risque de ne pas rester beaucoup de tas disponible pour coder une fois certains gros modules importés, et nous allons revenir là-dessus. Cela remet au passage en question notre projet déjà évoqué de développer certains scripts de compatibilité, scripts qui ne feraient alors que réduire encore davantage le peu de mémoire tas utilisable pour tes scripts si tu dois les importer en plus des modules en question.
Les erreurs de mémoire risquent donc d'arriver très rapidement. A cause de ce gros défaut, les scripts
Python
offerts en ligne risquent à notre avis d'être à terme plus nombreux et surtout beaucoup plus conséquents pour les solutions concurrentes que pour la
TI-83 Premium CE Édition Python
, et ce malgré toutes ses formidables possibilités.
Le pire était que cela fait plus de 2 ans que nous communiquons régulièrement
(sans aucune malveillance)
sur ce grave défaut aujourd'hui corrigé de la
NumWorks
, et comprenons donc assez mal que la même erreur ait pu être reproduite ici. A moins que ce ne soit en fait exprès pour pousser ceux qui veulent aller plus loin à un investissement dans les modèles haut de gamme
Voyons enfin un petit peu ce que consomme l'importation de chaque module disponible sur le tas
(heap)
.
Il suffit d'appeler les fonctions du module
gc
vues plus haut, juste avant et après l'importation d'un module.
Voici un script en ce sens, volontairement minimaliste sans définition de fonction afin de minimiser les chances de déclencher en cours d'exécution un nettoyage mémoire qui fausserait les résultats :
Voici donc les consommations de tas à l'importation des différents modules intégrés :
array
: 96 o
builtins
: 816 o
collections
: 96 o
gc
: 128 o
math
: 336 o
random
: 176 o
sys
: 224 o
ti_graphics
: 2,896 Ko
ti_hub
: 224 o
ti_plotlib
: 7,232 Ko
ti_rover
: 4,848 Ko
ti_system
: 176 o
time
: 112 o
Nos craintes évoquées plus haut étaient donc apparemment bien fondées; plusieurs modules sont très gros avec plusieurs kilooctets consommés sur le tas:
ti_graphics
,
ti_rover
et
ti_plotlib
notamment, avec quasiment la moitié du tas initialement disponible pour ce dernier.
Quand tu vas donc coder tes scripts et surtout projets
Python
sur
TI-83 Premium CE Édition Python
, contrairement à ce que tu fais peut-être sur d'autres plateformes n'importe que le strict nécessaire.
Aussi si ton projet nécessite des affichages graphiques
(interfaces de menus, jeux, ...)
, évite à tous prix le module
ti_plotlib
sauf si tu as vraiment besoin de tracer des diagrammes
(nuages de points, histogrammes...)
. Préfère-lui plutôt le module
ti_graphics
beaucoup plus économe. Plutôt que d'utiliser les coordonnées dans un repère orthogonal,
ti_graphics
te fera travailler en terme de pixels ce qui sera beaucoup plus précis; et nous allons t'en présenter les formidables possibilités très prochainement.
Nous t'avions dit en introduction que les éventuels points négatifs pourraient très bien être améliorés d'ici la sortie, vu que nous ne disposons que d'une préversion.
Toutefois, dans le cas particulier du tas
(heap)
Python
traité aujourd'hui nous sommes assez pessimistes.
Rappelons en effet un petit peu l'architecture matérielle assez particulière de la
TI-83 Premium CE Édition Python
:
un cœur secondaire 32 bits
Atmel SAMD21E18A-U
dédié à l'exécution des scripts
Python
un cœur principal
eZ80
qui est le seul à avoir accès à l'ensemble des autres composantes
(
Flash
, contrôleur écran, cœur secondaire...)
Le cœur secondaire contient notamment :
un processeur 32 bits
ARM Cortex-M0+
cadencé à
48 MHz
et agissant donc ici en tant que coprocesseur pour l'exécution de tes scripts
Python
une mémoire
Flash
de
256 Kio
une mémoire
SRAM
de
32 Kio
Commences-tu à comprendre le problème ? Le coprocesseur
Python
n'a accès qu'à
32Kio
de
RAM
,
RAM
qui sert pour différentes choses :
certes le tas
(heap)
Python
de près de 20K mais pas que
également la pile
(stack)
Python
et aussi il ne faut pas l'oublier l'exécution du
firmware
CircuitPython
modifié inclus dans la mémoire
Flash
et qui fournit l'interpréteur
Python
Près de 17.5K d'espace disponible sur un tas de près de 20K lui-même pris sur une
RAM
de
32Kio
qui sert à bien d'autres choses; la marge d'amélioration nous semble a priori extrêmement limitée.
NumWorks
a mis deux ans et demi à le comprendre; si tant est que
Texas Instruments
comprenne également en quoi 17.5K de tas sont un inconvénient majeur même dans un contexte scolaire, nous craignons que les améliorations ne puissent concerner qu'une future révision matérielle ou même un futur modèle, sans doute encore lointain pour le milieu de gamme vu que la
Un moteur qui en plus de venir avec des niveaux par défaut a le gros avantage d'accepter les niveaux perso, et également celui de la disponibilité d'un éditeur de niveaux pour
Windows / Mac
.
Nous nous attardons aujourd'hui sur un pack de niveaux perso absolument remarquable par
Ici nous renonçons aux éléments de fond d'écran, arbres, collines et nuages, bien que
Oiram CE
dispose pourtant de
sprites
qui auraient permis de les imiter. Pas de variation de couleur pour les briques sous-terraines, le
sprite
de brique sombre n'existant pas dans
Oiram CE
. Le mât de fin de niveau n'ayant lui non plus aucune existence dans
Oiram CE
est ici remplacé par un empilement de pièces. Le mini-donjon décoratif qui lui fait suite en fin de niveau est par contre absent et ce sera toujours le cas.
dans le sens où c'est visuellement très gênant; il n'y a pas de
sprite
pour les pièces utilisant le fond noir, et elles sont donc affichées comme si elles étaient en plein jour à l'air libre. Là encore quelque chose qui n'existe pas dans
Oiram CE
, les ascenseurs continus à la
pater noster
d'Europe centrale, sont remplacés par des paires verticales de deux plateformes constituées de blocs qui tombent. La salle secrète de
warp zone
est bien présente mais non fonctionnelle puisque les
warp zones
ne sont hélas pas gérées par
Oiram CE
. Au lieu de te permettre de passer au choix aux mondes 2, 3 ou 4, elle te permettra juste de passer au niveau suivant exactement comme la fin normale du niveau. C'est quand même un beau clien d'oeil de l'avoir incluse, même non inopérante.
fait comme il peut et c'est assez réussi. Dans le même style, on renonce ici aux plateformes mobiles. Celles qui se déplacent verticalement sont là encore remplacées par des paires verticales de deux plateformes de blocs qui tombent. Celles qui se déplacent horirontalement sont directement remplacées par des blocs qui tombent. Le château décoratif n'est pas illustré à la fin du niveau, et ce sera toujours le cas.
bien embêtant visuellement, les pièces placées sous l'eau n'utilisent pas le fond aquatique elles non plus. Outre les méduses, le niveau original présentait deux espèces de poissons : rouges et gris, ces derniers étant plus lents. Nous déplorons ici nos premiers ennemis manquants faute d'équivalent, tous les poissons étant donc rouges. Les algues rouges ici encore faute d'équivalent, sont remplacés par des éléments non fonctionnels de tuyaux rouges.
Ici encore un absent, les scarabés nous font défaut dans
Oiram CE
et sont remplacés par des tortues zombie. Les deux salles secrètres de
warp zone
sont là encore présentes mais non fonctionnelles, ne te permettant que de passer au niveau suivant au lieu de t'amener au choix aux mondes 5, 6, 7 ou 8.
Le château est ici difficile à reproduire car il est spécial. C'est un labyrinthe, dans le sens ou si l'on n'enchaîne pas les bonnes plateformes on est ramené en boucle au début du niveau, élément de
t'a-t-il rajouté quelques blocs infranchissables pour te forcer à enchaîner les bonnes plateformes, là encore un clin d'oeil inutile et donc absolument indispensable.
5ème monde qui reprend le découpage du 3ème monde mais cette fois-ci en plein jour :
1er + 2ème niveaux sur la terre ferme
3ème niveau de plateformes dans une forêt de champignons géants
4ème niveau avec le château
Les niveaux correspondants sont ici numérotés 17 à 20 dans
Ici encore un château-labyrinthe mais d'une autre façon. Tu dois trouver cette fois-ci le bon enchaînement de tuyaux qui t'amènera à
Bowser
, tout mauvais tuyau te ramenant en début de niveau. Dans l'original si tu avançais sans rentrer dans aucun tuyau, le château bouclait et te ramenait également au début, élément de
gameplay
impossible à reproduire ici; tu devras choisir obligatoirement un tuyau avant le mur rajouté pour t'empêcher d'aller plus loin. Dommage que
Peach
ne t'attende pas à la fin.
Bref un superbe pack de niveaux et avec 32 niveaux le plus grand jamais créé pour
Oiram CE
!
Dans la limite des nombreuses contraintes imposées par
te signe quand même une reproduction extrêmement fidèle de
Super Mario Bros
, une reproduction méticuleuse au bloc près, et tout élément ne pouvant être reproduit a été pensé et adapté avec talent toujours dans le respect du jeu original !
, nous n'osons pas imaginer combien de temps tu as pu passer là-dessus !
Au nom de tous les confinés, merci en tous cas pour ce superbe cadeau qui nous va droit au cœur en ces temps si difficiles, de quoi bien nous occuper cette semaine !
Attention, pour fonctionner correctement,
Oiram CE
a besoin des bibliothèques C téléchargeables ci-dessous. Mais rien de bien compliqué, il suffit juste de récupérer et transférer leur fichier.
def getplatform(): id=-1 try: import sys try: if sys.platform=='nspire':id=0 if sys.platform.startswith('TI-Python') or sys.platform=='Atmel SAMD21':id=4 except:id=3 except: try: import kandinsky id=1 except: try: if chr(256)==chr(0):id=5+(not ("HP" in version())) except: id=2 return id
platform=getplatform() #lines shown on screen #plines=[29,12, 7, 9,11,0,0] plines=[29,16, 7, 9,11,0,0] #max chars per line #(error or CR if exceeded) pcols =[53,99,509,32,32,0,0]
def mprint(*ls): global curline st="" for s in ls: if not(isinstance(s,str)): s=str(s) st=st+s stlines=1+int(len(st)/ncols) if curline+stlines>=nlines: input("Input to continue:") curline=0 print(st) curline+=stlines
def sstr(obj): try: s=obj.__name__ except: s=str(obj) a=s.find("'") b=s.rfind("'") if a>=0 and b!=a: s=s[a+1:b] return s
def isExplorable(obj): s=str(obj) return s.startswith("<") and s.find(" ")>=0 and not s.startswith("<module")
def explmod(pitm,pitmsl=[],reset=True): global curline if(reset): curline=0 pitmsl=[sstr(pitm)] hd="."*(len(pitmsl)-1) spath=".".join(pitmsl) c,c2=0,0 spitm=str(pitm) for itms in sorted(dir(pitm)): c,c2=c+1,c2+1 try: itm=eval(spath+"."+itms) mprint(hd+itms+"="+str(itm)) if isExplorable(itm) and itm!=pitm: pitmsl2=pitmsl.copy() pitmsl2.append(itms) c2=c2+explmod(itm,pitmsl2,False)[1] except: mprint(hd+itms) if c>0 and reset: mprint(hd+"Total: "+str(c)+" 1st level item(s)") if c2>0 and c2!=c: mprint(hd+" "+str(c2)+" item(s)") return [c,c2]
Pas mal de choses a première vue, nous découvrons déjà 30 éléments accessibles rien qu'au premier niveau, et jusqu'à 45 en comptant les sous-éléments.
Une première lecture des noms de différentes fonctions, comme
getPixel()
et
setPixel()
nous permet de deviner de quoi il s'agit.
ti_graphics
est un module
Python
permettant de dessiner sur l'écran de ta
TI-83 Premium CE Edition Python
, non pas en donnant des coordonnées approximatives dans un repère mais en accédant directement les pixels de l'écran ! Pour te donner un point de repère, nous pouvons donc a priori le rapprocher des modules
casioplot
sur
Casio Graph 90/35+E II
ou
kandinsky
sur
NumWorks
.
Nous allons bien évidemment te tester et documenter tout ça sans tarder, mais en attendant cela nous permet déjà de mettre à jour notre petit classement des
Explorons maintenant notre environnement graphique. Pour cela nous allons utiliser quelques premières fonctions élémentaires, commençons déjà par en préciser les spécifications :
getPixel(x, y)
setPixel(x, y, c)
Quelles sont donc les dimensions de la zone graphique que nous contrôlons ? Petite astuce pour la détecter automatiquement sans intervention humaine, on peut remarquer que
getPixel()
renvoie
(0,0,0)
lorsque l'on interroge un pixel hors écran.
Un simple boucle de tests suffit alors, avec un éventuel changement de couleur préalable pour les quelques pixels de l'écran qui seraient déjà noirs.
Mettons à jours notre classe de compatibilité écrans
class polyscr: w, h = 0, 0 # get_pixel(x, y) # set_pixel(x, y, color(r8, g8, b8)) show_screen = lambda self: None need_show_screen = False # color mode : # 0: (R8, G8, B8) # 1: int RGB-565 color_mode = 0
def color(self, r, g=0, b=0): if isinstance(r, tuple) or isinstance(r,list): r, g, b = r[0], r[1], r[2] return self.color_mode == 0 and (r,g,b) or r<<11 | g<<5 | b
def _can_get_pixel(x, y): c = self.get_pixel(x, y) if c == self.color(0, 0, 0): self.set_pixel(x, y, self.color(255,0,0)) c = self.get_pixel(x, y) return c is not None and c != self.color(0, 0, 0)
self.w, self.h, dw, dh = 0, 0, 1, 1 while dw or dh: if not _can_get_pixel(self.w - (dw == 0),self.h - (dh == 0)): if _can_get_pixel(self.w,self.h-1): dh = 0 elif _can_get_pixel(self.w-1,self.h): dw = 0 else: dw, dh = 0, 0 self.w += dw; self.h += dh
Appelons ensuite le code suivant pour obtenir la réponse :
scr = polyscr() print('can get {}x{} pixels at (0,0)'.format(scr.w, scr.h))
Mais c'est fantastique, nous bénéficierions donc apparemment un contrôle plein écran
320×240
pixels !
Sauf que... non, fausse joie.
On se rend vite compte que même si
Texas Instruments
nous autorise contrairement à la concurrence à lire tous les pixels de l'écran, ceux utilisés pour l'affichage de la barre d'état en haut d'écran ne peuvent apparemment pas être réécrits :
def color(self, r, g=0, b=0): if isinstance(r, tuple) or isinstance(r,list): r, g, b = r[0], r[1], r[2] return self.color_mode == 0 and (r,g,b) or r<<11 | g<<5 | b
def _can_get_pixel(x, y): c = self.get_pixel(x, y) if c == self.color(0, 0, 0): self.set_pixel(x, y, self.color(255,0,0)) c = self.get_pixel(x, y) return c is not None and c != self.color(0, 0, 0)
self.w, self.h, dw, dh = 0, 0, 1, 1 while dw or dh: if not _can_get_pixel(self.w - (dw == 0),self.h - (dh == 0)): if _can_get_pixel(self.w,self.h-1): dh = 0 elif _can_get_pixel(self.w-1,self.h): dw = 0 else: dw, dh = 0, 0 self.w += dw; self.h += dh
# detect writable pixel array # remove top status bar
def _can_set_pixel(x, y):
def _invert_color(r, g=0, b=0): if isinstance(r, tuple) or isinstance(r,list): r, g, b = r[0], r[1], r[2] return self.color(~r & 0xFF, ~g & 0xFF, ~b & 0xFF)
c = self.get_pixel(x, y) self.set_pixel(x, y, _invert_color(c)) return c != self.get_pixel(x, y)
self.w0, self.h0 = self.w, self.h while not _can_set_pixel(0, self.y0): self.y0 += 1; self.h0 -= 1
Appelons enfin le code suivant pour obtenir toutes les informations écran :
scr = polyscr() print('can get {}x{} pixels at (0,0)'.format(scr.w, scr.h)) print('can set {}x{} pixels at ({},{})'.format(scr.w0, scr.h0, scr.x0, scr.y0))
Voilà, nous bénéficions en réalité d'un accès total à seulement
320×210
pixels, les écritures sur les 30 premières lignes de l'écran étant ignorées.
A noter que l'origine restant tout en haut à gauche de l'écran, les valeurs de paramètres
y
à utiliser pour tes scripts adressant les pixels démarreront obligatoirement à 30.
Puisque notre classe
polyscr
cible la compatibilité, profitons-en pour comparer avec les modèles concurrents :
def rect(x, y, w, h, c=(0,0,0)): for k in range(w): scr.pixon(x+k, y, c) scr.pixon(x+k, y+h-1, c) for k in range(h): scr.pixon(x, y+k, c) scr.pixon(x+w-1, y+k, c)
L'appel rect(0, 0, 320, 240, 255<<16) nous affiche bien un rectangle au complet et nous confirme donc que nous contrôlons ici la totalité des 320×240 pixels de l'écran.
D'où le classement en terme de zones graphiques dédiées au
def hsv2c(h,s,v): c=v*s x,m,k=c*(1-abs((h%(2/3))*3-1)),v-c,(h*3)//1 return (round(255*(m+x*(k%3==1)+c*(k%5==0))),round(255*(m+c*(k==1 or k==2)+x*(k%3==0))),round(255*(m+x*(k%3==2)+c*(k==3 or k==4))))
def grad(x,y,w,h): for i in range(w): for j in range(h): c=hsv2c(2*j/(h-1),i>=w//2 and 1 or i/(w//2-1),i<w//2 and 1 or (w-1-i)/((w-w//2)-1)) scr.set_pixel(x+i,y+j,c)
L'appel suivant nous produit alors l'affichage ci-contre :
Fantastique, non ? Quand tu penses que dans le langage
TI-Basic
historique tu n'avais droit qu'à 15 couleurs, mais comment as-tu pu survivre avec ça ?
Déjà pour information, on peut mesurer précisément le temps d'affichage à l'aide du script suivant. L'appel timer(grad, scr.x0, scr.y0, scr.w0, scr.h0) nous renvoie alors
def color(self, r, g=0, b=0): if isinstance(r, tuple) or isinstance(r,list): r, g, b = r[0], r[1], r[2] return self.color_mode == 0 and (r,g,b) or r<<11 | g<<5 | b
def _can_get_pixel(x, y): c = self.get_pixel(x, y) if c == self.color(0, 0, 0): self.set_pixel(x, y, self.color(255,0,0)) c = self.get_pixel(x, y) return c is not None and c != self.color(0, 0, 0)
self.w, self.h, dw, dh = 0, 0, 1, 1 while dw or dh: if not _can_get_pixel(self.w - (dw == 0),self.h - (dh == 0)): if _can_get_pixel(self.w,self.h-1): dh = 0 elif _can_get_pixel(self.w-1,self.h): dw = 0 else: dw, dh = 0, 0 self.w += dw; self.h += dh
# detect writable pixel array # remove top status bar
def _can_set_pixel(x, y):
def _invert_color(r, g=0, b=0): if isinstance(r, tuple) or isinstance(r,list): r, g, b = r[0], r[1], r[2] return self.color(~r & 0xFF, ~g & 0xFF, ~b & 0xFF)
c = self.get_pixel(x, y) self.set_pixel(x, y, _invert_color(c)) return c != self.get_pixel(x, y)
self.w0, self.h0 = self.w, self.h while not _can_set_pixel(0, self.y0): self.y0 += 1; self.h0 -= 1
Modifions maintenant le script de tracer du dégradé :
def grad(x,y,w,h): for i in range(w): for j in range(h): c=scr.myscr.hsv_to_rgb(360*j/(h-1),i>=w//2 and 1 or i/(w//2-1),i<w//2 and 1 or (w-1-i)/((w-w//2)-1)) scr.set_pixel(x+i,y+j,c)
Ce n'est pas la révolution mais c'est quand même significatif, l'appel timer(grad, scr.x0, scr.y0, scr.w0, scr.h0) ne mesure plus que
24min 33,282s
. On gagne 1 minute, soit environ 4%.
Bon, revenons-en enfin à cet affichage de dégradé. Si tu regardes bien, tu remarqueras que les lignes tirant sur le vert donnent 2 fois plus de teintes intermédiaires et donc un dégradé bien plus continu, bien moins saccadé.
C'est-à-dire que même si les coordonnées sont spécifiées en RGB-888, le matériel utilise pour sa part du RGB-565 :
def grad(x,y,w,h): for i in range(w): for j in range(h): _h = 2*j/(h-1) _s = i>=w//2 and 1 or i/(w//2-1) _v = i<w//2 and 1 or (w-1-i)/((w-w//2)-1) _c=_v*_s _x,_m,_k=_c*(1-abs((_h%(2/3))*3-1)),_v-_c,(_h*3)//1 scr.pixon(x+i,y+j,round(255*(_m+_x*(_k%3==1)+_c*(_k%5==0)))*2**16 + round(255*(_m+_c*(_k==1 or _k==2)+_x*(_k%3==0)))*2**8 + round(255*(_m+_x*(_k%3==2)+_c*(_k==3 or _k==4))))
On pourrait croire à la capture d'écran ci-contre présentant des saccades régulières dans l'ensemble des teintes que l'on aurait affaire à du RGB-666, ce qui n'est pas très logique. En réalité, c'est le protocole utilisé pour les captures d'écran qui fait perdre des informations couleur, et il faut donc se référer à la photo ci-contre. Sur la photo donc aucune saccade mais un dégradé des plus lisses, c'est ici du RGB-888 :
28=256 teintes de rouge
28=256 teintes de vert
28=256 teintes de bleu
Ici encore, il serait bien de pouvoir détecter cela automatiquement sans intervention humaine.
Et bien c'est possible si on se rend compte que les valeurs effectivement réglées pour les pixels diffèrent parfois de celles spécifiées.
def color(self, r, g=0, b=0): if isinstance(r, tuple) or isinstance(r,list): r, g, b = r[0], r[1], r[2] return self.color_mode == 0 and (r,g,b) or r<<(self.col_bits[0]+self.col_bits[1]) | g<<self.col_bits[0] | b
def _can_get_pixel(x, y): c = self.get_pixel(x, y) if c == self.color(0, 0, 0): self.set_pixel(x, y, self.color(255,0,0)) c = self.get_pixel(x, y) return c is not None and c != self.color(0, 0, 0)
self.w, self.h, dw, dh = 0, 0, 1, 1 while dw or dh: if not _can_get_pixel(self.w - (dw == 0),self.h - (dh == 0)): if _can_get_pixel(self.w,self.h-1): dh = 0 elif _can_get_pixel(self.w-1,self.h): dw = 0 else: dw, dh = 0, 0 self.w += dw; self.h += dh
# detect writable pixel array # remove top status bar
def _can_set_pixel(x, y):
def _invert_color(r, g=0, b=0): if isinstance(r, tuple) or isinstance(r,list): r, g, b = r[0], r[1], r[2] return self.color(~r & 0xFF, ~g & 0xFF, ~b & 0xFF)
c = self.get_pixel(x, y) self.set_pixel(x, y, _invert_color(c)) return c != self.get_pixel(x, y)
self.w0, self.h0 = self.w, self.h while not _can_set_pixel(0, self.y0): self.y0 += 1; self.h0 -= 1
if not self.color_mode:
# test color screen self.set_pixel(self.x0, self.y0, (0, 127, 255)) col = self.get_pixel(self.x0, self.y0) self.has_color = col[0] != col[1] or col[1] != col[2]
# detect color channel bits self.set_pixel(self.x0, self.y0, (255, 255, 255)) col = list(self.get_pixel(self.x0, self.y0)) for k in range(3): while col[k]<255: col[k] += 2 ** (8 - self.col_bits[k]) self.col_bits[k] -= 1
Appelons enfin le code suivant pour obtenir l'intégralité des informations écran, et les comparer aux solutions concurrentes :
scr = polyscr() print('can get {}x{} pixels at (0,0)'.format(scr.w, scr.h)) print('can set {}x{} pixels at ({},{})'.format(scr.w0, scr.h0, scr.x0, scr.y0)) print(scr.has_color and 'color' or 'monochrome') if scr.has_color: print('internal : RGB{}{}{}'.format(scr.col_bits[0], scr.col_bits[1], scr.col_bits[2]))
Voilà, nous obtenons bien comme prévu du
RGB565
pour le format utilisé en interne pour les pixels
Testons également la concurrence en lançant le même code :
Au classement selon les meilleurs rendus de couleurs, nous avons donc :
Faisons maintenant un script allumant un par un tous les pixels de l'écran, et afin de mieux pouvoir mesurer et comparer les performances graphiques nous minimiserons les calculs en ne faisant pas appel à notre classe de compatibilité, mais en appelant directement les fonctions graphiques :
, donc il va falloir chronométrer à la main, bien évidemment avec une montre
Casio
. Autre différence, les affichages sont à la différence effectués ici sur un calque invisible et ne passent à l'écran que lorsqu'on le demande. Afin donc de tester dans des conditions aussi équivalentes que possibles, nous demanderons un affichage écran après chaque traitement de pixel.
En terme de boucle d'allumages individuels de pixels, la
TI-83 Premium CE Edition Python
ne serait donc apparemment pas très performante. Mais attends un petit peu avant de partir, nous sommes très loin d'avoir dit notre dernier mot, nous allons approfondir cela de suite dans les deux prochaines parties.
impossibilité de positionner le texte comme on voulait verticalement, nous ne pouvions que choisir une des 11 à 12 bandes horizontales de 17 pixels de hauteur, numérotées de haut en bas, même dans le contexte de
ti_plotlib
pourtant censé travailler dans un repère
impossibilité de positionner le texte comme on voulait horizontalement, nous ne pouvion que choisir entre l'aligner à gauche, au centre ou à droite
et en prime effet de bord sur l'affichage avec effacement de la partie droite non utilisée de la bande ou même de toute la bande
ti_graphics
dispose lui aussi d'une instruction d'affichage similaire drawString('texte', x, y).
Mais ici rien à voir avec les précédentes,
(x, y)
sont les coordonnées comptées cette fois-ci au pixel près à partir du coin en haut à gauche, et le tout ne produit aucun effet de bord !
Si si, tu pourras bien afficher tes textes en toute liberté !
tw = 10 s = 'Thank you TI' xmin, xmax, ymin, ymax = 0, 319, 30, 239 x, y, dx, dy = xmin, ymin, 1, 9
scr.cls() while x <= xmax - tw*len(s): scr.drawString(s, x, y) y += dy x += dx dx += 1 disp_wait()
C'est le cas de le dire,
merci TI
!
Et puis, ce n'est pas tout. Le test de la partie précédente nous a donc donné une vitesse de traitement calamiteuse de moins d'une 50aine de pixels à la seconde.
Et pourtant, aucun problème ici avec la fonction
drawString()
, l'affichage pourtant conséquent en nombre de pixels prenant moins d'une paire de secondes !
Que se passe-t-il donc ? Sans doute que c'est ici qu'il faut tenir comtpe de l'architecture matérielle très spéciale de la
TI-83 Premium CE
, avec :
un cœur secondaire
Atmel SAMD21E18A-U
intégrant un
ARM Cortex-M0+
cadencé à
48 MHz
, coprocesseur 32-bits dédié à l'exécution des scripts
Python
un cœur principal
eZ80
qui à la différence dispose d'un accès direct à l'ensemble des autres composantes matérielles
(
Flash
, contrôleur écran, cœur secondaire...)
Et justement, on peut donc imaginer que chaque appel à
setPixel()
dans le test précédent génère un événement de mise à jour écran qui doit repasser par le processeur principal avant d'atteindre ce dernier, d'où une certaine latence pour une boucle travaillant de façon individuelle sur des pixels. Alors qu'ici chaque appel à
drawString()
allume d'un coup plein de pixels que l'on peut supposer à la différence partager un seul et unique événément de mise à jour.
Un comparatif s'appuyant sur une boucle d'allumage de pixels n'est donc pas représentatif des performances graphiques moyennes de la
TI-83 Premium CE Edition Python
puisque c'est pour elle le pire des cas, et donc non pertinent. Mais en même temps, quand les modules graphiques de la concurrence sont à la différence très minimalistes, nous ne voyons pas d'autre fonction commune et identique à utiliser pour construire un test de performances...
Abordons maintenant les autres fonctions de tracer de
ti_graphics
.
Les modules équivalents chez la concurrence sont assez minimalistes, te fournissant juste de quoi choisir la couleur, allumer/éteindre des pixels, et afficher du texte. Tu devais te fabriquer tes autres fonctions graphiques
(lignes, rectangles, cercles, etc...)
. Et bien ici tu es gâté,
ti_graphics
est très riche.
Mais c'est aussi surtout un moyen de répondre au problème découvert plus haut, à savoir que la
TI-83 Premium CE Edition Python
est très lente pour les boucles appelant des
setPixel()
. Coder toi-même ces mêmes fonctions en utilisant
setPixel()
faute de mieux eut été désastreux.
Commençons déjà par de la configuration. Déjà, on eut effacer l'écran à l'aide de la fonction
cls()
. Son comportement semble identique à celui des fonctions
La fonction cursor(c=1) permet d'activer ou désactiver l'affichage du curseur de texte, comme le permettait déjà la fonction ti_system.cursor(c). A la différence ici que les effets des valeurs sont inversés :
0: curseur affiché
1: curseur masqué
Autre différence également; contrairement à
ti_system.cursor()
la fonction
cursor()
admet un comportement par défaut : si appelée sans paramètre elle masque le curseur.
On peut maintenant choisir la couleur du stylo avec setColor(r, g, b), appel équivalent à ti_plotlib.color(r, g, b). D'ailleurs si l'on importe les deux modules
, on peut appeler n'importe laquel de ces deux fonctions pour obtenir le même effet, et ce simultanément dans les deux contextes graphiques. Sans doute que ces deux fonctions partagent le même code.
On peut également régler la plume du stylo, avec setPen(taille, type), un appel donc similaire à ti_plotlib.pen('taille', 'type'). A la différence près que
setPen()
n'accepte pas d'arguments sous forme de chaînes de caractères, uniquement des entiers. On peut donc supposer que
nta, nty = 4, 4 lx = [xmin + k*(xmax-xmin)/(2*nta+1) for k in range(1, 2*nta+1)] ly = [ymin + k*(ymax-ymin)/(2*nty+1) for k in range(1, 2*nty+1)] l = (xmax-xmin+1) / (2*nta+1)
scr.cls() for i in range(nty): scr.drawString(str(i), xmin-tw, ly[i*2]) for j in range(nta): scr.drawString(str(j), lx[j*2], ymin-th) scr.setPen(j, i) scr.drawLine(lx[j*2], ly[i*2], lx[j*2 + 1], ly[i*2]) disp_wait()
Ci-contre l'affichage obtenu, ainsi que pour comparaison celui du test similaire dans le contexte de la fonction
ti_plotlib.pen()
.
Ce qui nous permet de terminer la documentation de
pen()
:
setPen(0, 0) = ti_plotlib.pen('thin', 'solid')
setPen(1, 1) = ti_plotlib.pen('medium', 'dot')
setPen(2, 2) = ti_plotlib.pen('thick', 'dash')
Quant à l'appel setPen(3, 3), il permet donc d'accéder à deux réglages secrets non offerts via
ti_plotlib
:
une taille de stylo encore plus grande que
'thick'
un tracé en pointillés qui enchaîne non pas des tirets mais des petits points
Des valeurs supérieures à 3 donnent quant à elles la même chose que la valeur 2.
Testons maintenant de façon similaire les fonctions drawPolyLine([(x1,y1), (x2,y2), ..., (xn,yn)]) et fillPolyLine([(x1,y1), (x2,y2), ..., (xn,yn)]) :
nta, nty = 4, 4 lx = [xmin + k*(xmax-xmin)/(2*nta+1) for k in range(1, 2*nta+1)] ly = [ymin + k*(ymax-ymin)/(2*nty+1) for k in range(1, 2*nty+1)] l = (xmax-xmin+1) / (2*nta+1)
scr.cls() for i in range(nty): scr.drawString(str(i), xmin-tw, ly[i*2]) for j in range(nta): scr.drawString(str(j), lx[j*2], ymin-th) scr.setPen(j, i) scr.setColor((255,0,0)) scr.fillPolygon([(lx[j*2], ly[i*2]), (lx[j*2 + 1], ly[i*2]), (lx[j*2], ly[i*2+1]), (lx[j*2 + 1], ly[i*2+1])]) scr.setColor((0,0,0)) scr.drawPolyLine([(lx[j*2], ly[i*2]), (lx[j*2 + 1], ly[i*2]), (lx[j*2], ly[i*2+1]), (lx[j*2 + 1], ly[i*2+1])]) disp_wait()
La fonction
drawPolyLine()
permet donc de tracer une ligne brisée. Elle serait donc équivalente au code suivant :
def drawPolyLine(l): for k in range(len(l) - 1): drawLine(l[k][0], l[k][1], l[k+1][0], l[k+1][1])
La fonction
fillPolygon()
permet quant à elle de colorier le polygone obtenu en fermant cette ligne brisée. Et comme tu vois c'est remarquable, elle marche même avec des polygones croisés !
Passons maintenant à drawRect(x, y, w, h) et fillRect(x, y, w, h) :
nta, nty = 4, 4 lx = [xmin + k*(xmax-xmin)/(2*nta+1) for k in range(1, 2*nta+1)] ly = [ymin + k*(ymax-ymin)/(2*nty+1) for k in range(1, 2*nty+1)] l = (xmax-xmin+1) / (2*nta+1)
scr.cls() for i in range(nty): scr.drawString(str(i), xmin-tw, ly[i*2]) for j in range(nta): scr.drawString(str(j), lx[j*2], ymin-th) scr.setPen(j, i) scr.setColor((255,0,0)) scr.fillRect(lx[j*2], ly[i*2], lx[j*2+1]-lx[j*2], ly[i*2+1]-ly[i*2]) scr.setColor((0,0,0)) scr.drawRect(lx[j*2], ly[i*2], lx[j*2+1]-lx[j*2], ly[i*2+1]-ly[i*2]) disp_wait()
drawRect(x, y, w, h) permet donc de tracer un rectangle :
de dimensions
w
et
h
données en pixels
aux côtés parallèles aux bors de l'écran
et en utilisant le point de coordonnées
(x, y)
comme sommet supérieur gauche
La fonction
fillRect()
quant à elle permet de colorier le rectangle en question.
Voici maintenant du lourd avec drawArc(x, y, w, h, t1, t2) et fillArc(x, y, w, h, t1, t2) :
nta, nty = 4, 4 lx = [xmin + k*(xmax-xmin)/(2*nta+1) for k in range(1, 2*nta+1)] ly = [ymin + k*(ymax-ymin)/(2*nty+1) for k in range(1, 2*nty+1)] l = (xmax-xmin+1) / (2*nta+1)
scr.cls() for i in range(nty): scr.drawString(str(i), xmin-tw, ly[i*2]) for j in range(nta): scr.drawString(str(j), lx[j*2], ymin-th) scr.setPen(j, i) scr.setColor((255,0,0)) scr.fillArc(lx[j*2], ly[i*2], lx[j*2+1]-lx[j*2], ly[i*2+1]-ly[i*2], 0, 3150) scr.setColor((0,0,0)) scr.drawArc(lx[j*2], ly[i*2], lx[j*2+1]-lx[j*2], ly[i*2+1]-ly[i*2], 0, 3150) disp_wait()
La fonction drawArc(x, y, dx, dy, t1, t2) permet donc de tracer un arc d'une ellipse elle-même inscrite dans un rectangle :
de dimensions
w
et
h
données en pixels
aux côtés parallèles aux bors de l'écran
et en utilisant le point de coordonnées
(x, y)
comme sommet supérieur gauche
t1
et
t2
sont les angles au centre orientés délimitant l'arc en question, exprimés en dixièmes de degrés.
Et La fonction
fillArc()
permet quant à elle de colorier le secteur d'ellipse obtenu par balayage de l'arc en question.
Petite curiosité, nous avons une fonction fillCircle(x, y, r) permettant de préciser des éléments caractéristiques différents plus naturels dans le cas particulier d'un disque. La fonction serait donc équivalente au code suivant :
def fillCircle(x, y, r): fillArc(x-r, y-r, 2*r, 2*r, 0, 3600)
Ce qui est curieux ? Et bien qu'il n'y ait apparemment pas de fonction similaire drawCircle(x, y, r) pour le tracé d'un cercle, obligeant dans ce cas à utiliser une logique complètement différente.
nta, nty = 4, 4 lx = [xmin + k*(xmax-xmin)/(2*nta+1) for k in range(1, 2*nta+1)] ly = [ymin + k*(ymax-ymin)/(2*nty+1) for k in range(1, 2*nty+1)] l = (xmax-xmin+1) / (2*nta+1)
scr.cls() for i in range(nty): scr.drawString(str(i), xmin-tw, ly[i*2]) for j in range(nta): scr.drawString(str(j), lx[j*2], ymin-th) scr.setPen(j, i) scr.setColor((255,0,0)) scr.fillCircle(lx[j*2]+(ly[i*2+1]-ly[i*2])/2, (ly[i*2]+ly[i*2+1])/2, (ly[i*2+1]-ly[i*2])/2) scr.setColor((0,0,0)) scr.drawArc(lx[j*2], ly[i*2], ly[i*2+1]-ly[i*2], ly[i*2+1]-ly[i*2], 0, 3600) disp_wait()
dispose apparemment de pas moins de 3 fonctions dédiées à l'affichage d'images, et donc entre autres de
sprites
pour tes interfaces de menus et jeux !
C'est sans doute un moyen une fois encore de répondre au problème de performances des boucles d'allumage de pixels que tu aurais utilisées par défaut
Mais ces fonctions dédiées n'en restent pas moins un formidable avantage sur la concurrence, ces boucles d'allumage n'étant sur la plupart des modèles pas assez rapides pour permettre d'animer ou déplacer un
sprite
de façon fluide dans le contexte par exemple d'un jeu.
Comme ces fonctions ne sont pas au menu il nous faut donc arriver à deviner ce qu'elles attendent comme arguments.
L'appel pushImage(x, y, w, h) semble définir une image de dimensions
w×h
qui pourra être affichée avec comme coin supérieur gauche le pixel de coordonnées
(x,y)
. La fonction
popImage()
quant à elle en déclenche ensuite l'affichage, effectivement très rapide même si par défaut cela nous affiche ici n'importe quoi.
Par exemple ci-contre, le résultat du code suivant :
drawImage(?, ?, ?) doit pour sa part servir à dessiner l'image en question avant affichage, mais nous n'avons pas réussi à en comprendre le fonctionnement.
Si l'on se réfère à notre test précédent ayant mis en évidence que nous disposions de près de 17.5K de mémoire de tas
(heap)
il semble impensable que cette fonction puisse accepter des données d'image brutes, que ce soit sous forme de liste ou de chaîne de caractères. 320×240 pixels codés sur 16 bits nécessiteraient en effet pas moins de 153.6 Ko, qui déclencheront ici une erreur de mémoire.
Nous avons deux hypothèses :
Soit la fonction attend une liste ou une chaîne de données compressées par exemple en RLE, et peut-être que
TI
a prévu un outil générant automatiquement à partir d'une image fournie la liste ou chaîne compressée à coller dans ton script. Mais en tous cas cet outil ne nous as pas été passé.
Soit la fonction attend le nom d'une ressource image à aller chercher en externe dans les variables du système de la calculatrice. En fouillant l'application
Python
à l'éditeur hexadécimal nous avons effectivement trouvé un indice en ce sens, avec un
'imgname'
qui est bien présent en clair dans le code même nous n'avons trouvé pour le moment aucun menu l'affichant.
En tous cas, faire appel aux variables images préchargées dans le calculatrice avec par exemple scr.drawImage('Image1', 50, 50) semble ne rien donner.
On pouvait quand même s'en douter, vu que ces images ont une taille fixe prévue pour un affichage en plein écran :
265×165 pixels pour les images 16 couleurs
Pic0
à
Pic9
133×83 pixels pour les images d'arrière plan
Image0
à
Image9
(agrandies d'un facteur 2 pour l'affichage)
C'est donc en total contradiction avec les quelques arguments devinés pour le moment.
Peut-être donc plutôt que
drawImage()
va chercher des
AppVars (variables d'applications)
qui contiendraient des images dédiées au
Python
et qu'un futur
TI-Connect CE 5.5
effectuera la conversion à la volée lorsque l'on lui fournira des images. Ce serait l'hypothèse la plus plausible, mais en tous cas on ne nous l'a pas passé.
comporte également quelques autres fonctions privées ou issues d'autres modules.
Déjà, il inclut le module
sys
que tu n'auras donc pas à importer simultanément.
Il inclut également une fonction
sleep()
équivalente à
time.sleep()
, autre économie de mémoire pour tes importations.
La fonction privée _write('') semble être équivalente avec ti_plotlib._write('').
Sauf qu'ici elle est accompagnée d'une fonction _read(n). Attention, si on lui demande de lire plus de données qu'il y en a, on ne sait où en passant, la calculatrice rentre dans une boucle d'attente infinie qu'il est apparemment impossible d'interrompre autrement que par un
reset
.
Egalement quelques autres mystères à découvrir et qui peut-être nous permettront des choses bien pratiques : _grcmd(''), _grcif('') et _handshake(?).
de dessin sur écran extrêmement complet, face à une concurrence à ce jour très minimaliste sur ce sujet.
Il y a clairement eu un travail très conséquent là-dessus, c'est un module conçu à partir de zéro et taillé sur-mesure pour te donner le meilleur avec le matériel choisi pour ta
TI-83 Premium CE Edition Python
.
Pixels, textes, formes géométriques diverses et images, chaque fonction te permet ici d'exploiter l'intégralité des capacités matérielles.
ti_graphics
te permet de réaliser avec de bien meilleurs performances tout ce que tu pouvais déjà faire avec le langage historique
TI-Basic
et même davantage, notamment au niveau des ellipses, couleurs et images !