Merge branch 'dev' into numworks

This commit is contained in:
Sylvain PILLOT 2024-02-08 08:02:21 +01:00
commit 263b9e83c8
20 changed files with 1460 additions and 39 deletions

255
docs/sh/modgint-en.md Normal file
View File

@ -0,0 +1,255 @@
# `gint`: Wrapper for the gint library
PythonExtra is developed with the [fxSDK](https://gitea.planet-casio.com/Lephenixnoir/fxsdk) and uses the [gint kernel](https://gitea.planet-casio.com/Lephenixnoir/gint) as a runtime. The Python module `gint` provides access to gint's internal functions for rendering, keyboard, and more. Since gint has many versatile functions with good performance, it is beneficial to use it instead of e.g. `casioplot` or `turtle`.
The `gint` module tries to match its API with the original API of the C library, which is why few functions use keyword arguments or overloading. There are a few differences documented at the end of this document. For details not described in this document, one can refer to [gint's header files](https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint) which are always applicable unless this document explicitly says otherwise.
All constants, functions, etc. discussed here are in the `gint` module.
```py
import gint
# or:
from gint import *
```
## Keyboard input
Reference headers: [`<gint/keyboard.h>`](https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint/keyboard.h) and [`<gint/keycodes.h>`](https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint/keycodes.h).
The module provides integer constants to refer to keyboard keys, with the following names:
| | | | | | |
|------------|------------|------------|------------|------------|-------------|
|`KEY_F1` |`KEY_F2` |`KEY_F3` |`KEY_F4` |`KEY_F5` |`KEY_F6` |
|`KEY_SHIFT` |`KEY_OPTN` |`KEY_VARS` |`KEY_MENU` |`KEY_LEFT` |`KEY_UP` |
|`KEY_ALPHA` |`KEY_SQUARE`|`KEY_POWER` |`KEY_EXIT` |`KEY_DOWN` |`KEY_RIGHT` |
|`KEY_XOT` |`KEY_LOG` |`KEY_LN` |`KEY_SIN` |`KEY_COS` |`KEY_TAN` |
|`KEY_FRAC` |`KEY_FD` |`KEY_LEFTP` |`KEY_RIGHTP`|`KEY_COMMA` |`KEY_ARROW` |
|`KEY_7` |`KEY_8` |`KEY_9` |`KEY_DEL` |`KEY_ACON` | |
|`KEY_4` |`KEY_5` |`KEY_6` |`KEY_MUL` |`KEY_DIV` | |
|`KEY_1` |`KEY_2` |`KEY_3` |`KEY_ADD` |`KEY_SUB` | |
|`KEY_0` |`KEY_DOT` |`KEY_EXP` |`KEY_NEG` |`KEY_EXE` | |
### Keyboard events
```
key_event:
.time -> int
.mod -> bool
.shift -> bool
.alpha -> bool
.type -> KEYEV_NONE | KEYEV_DOWN | KEYEV_UP | KEYEV_HOLD
.key -> KEY_*
```
gint communicates information about keyboard activity through _events_. Events indicate when a key (`.key` field) is pressed, repeated or released (`.type` field equal to `KEYEV_DOWN`, `KEYEV_HOLD` and `KEYEV_UP` respectively), when (`.time` field), and whether modifiers (SHIFT, `.shift` field, and ALPHA, `.alpha` field) where active at that time.
(The `.mod` field isn't very interesting, and the `KEYEV_NONE` value for the `.type` field is discussed later with `pollevent()`.)
The functions `getkey()`, `getkey_opt()`, `pollevent()` and `waitevent()` all return events.
### Waiting for a key press
```py
getkey() -> key_event
```
The function `getkey()` pauses the program until a key is pressed or repeated, and returns the associated event (which is always of type `KEYEV_DOWN` or `KEYEV_HOLD`). By default, only arrow keys are repeated, once after 400 ms, then every 40 ms.
A few things can happen while `getkey()` is waiting. The user can press SHIFT or ALPHA which will trigger modifiers and affect the `.shift` and `.alpha` fields of the returned event. The user can also go to the main menu by pressing MENU or turn the calculator off with SHIFT+AC/ON.
_Example._ In a selection menu with N possible items, one could navigate with the up and down arrow keys, jump to the top or bottom with SHIFT up and SHIFT down, and validate with EXE.
```py
ev = getkey()
if ev.key == KEY_EXE:
pass # Validate
elif ev.key == KEY_UP and ev.shift:
pos = 0 # Jump to top
elif ev.key == KEY_DOWN and ev.shift:
pos = N-1 # Jump to bottom
elif ev.key == KEY_UP and pos > 0:
pos -= 1 # Move one place up
elif ev.key == KEY_DOWN and pos < N-1:
pos += 1 # Move one place down
```
TODO: Mention `getkey_opt()`
### Reading keyboard events in real time
```py
pollevent() -> key_event
waitevent() -> key_event
clearevents() -> None
```
gint records keyboard activity in the background while the program is running. Events are placed in a queue until the program reads them. This is how `getkey()` learns about keyboard activity, for example.
The `pollevent()` function provides direct access to events. `pollevent()` returns the oldest event that hasn't yet been read by the program. If there are no events waiting to be read, `pollevent()` returns a "fake" event with type `KEYEV_NONE` to indicate that the queue is empty.
Since `pollevent()` returns instantly, it can be used to read keyboard activity without pausing the program.
_Example._ A game loop could, at every frame, read all pending events to determine when the player pressed the SHIFT key (in this example the "action" key) to perform an action.
```py
# Render game...
while True:
ev = pollevent()
if ev.type == KEYEV_NONE:
break # We're done reading events
if ev.type == KEYEV_DOWN and ev.key == KEY_SHIFT:
pass # The SHIFT key was just pressed!
# Implicitly ignores other keys
# Simulate game...
```
The `waitevent()` function operates similarly, but if there are no pending events it waits for something to happen before returning. It is used quite rarely because in waiting situations one usually uses `getkey()` instead.
The function `clearevents()` reads and ignores all events, i.e. it "throws away" all the information about recent keyboard activity. It is useful to know the immediate state of the keyboard with `keydown()'` (see below). `clearevents()` is equivalent to the following definition:
```py
def clearevents():
ev = pollevent()
while ev.type != KEYEV_NONE:
ev = pollevent()
```
### Reading the immediate state of the keyboard
```py
keydown(key: int) -> bool
keydown_all(*keys: [int]) -> bool
keydown_any(*keys: [int]) -> bool
```
After events have been read and the event queue is empty, one can query the immediate state of keys with the `keydown()` function. `keydown(k)` returns `True` if key `k` is currently pressed, `False` otherwise. This function only works **after events have been read**, which is usually done either with `pollevent()` or with `clearevents()`.
_Example._ A game loop could check the state of the left/right keys at every frame to move the player.
```py
while True:
ev = pollevent()
# ... same thing as the pollevent() example
if keydown(KEY_LEFT):
player_x -= 1
if keydown(KEY_RIGHT):
player_x += 1
```
`keydown_all()` takes a series of keys as parameters and returns `True` if they are all pressed. `keydown_any()` is similar and returns `True` if at least one of the listed keys is pressed.
### Quickly querying key state changes
```py
cleareventflips() -> None
keypressed(key: int) -> bool
keyreleased(key: int) -> bool
```
`keydown()` only tells whether keys are pressed at a given time; it cannot be used to check when keys change from the released state to the pressed state or the other way around. To do this, one must either read individual events (which can be annoying) or use the functions described below.
`keypressed(k)` and `keyreleased(k)` indicate whether key `k` was pressed/released since the last call to `cleareventflips()`. Be careful, here "pressed/released" should be interpreted as "indicated pressed/released by events read" not as a real-time state change.
_Example._ A game loop could test both the immediate state of some keys and state changes forother keys by using immediate functions after `cleareventflips()` followed by `clearevents()`.
```py
# Render game...
cleareventflips()
clearevents()
if keypressed(KEY_SHIFT):
pass # Action !
if keydown(KEY_LEFT):
player_x -= 1
if keydown(KEY_RIGHT):
player_x += 1
# Simulate game...
```
### Miscellaneous keyboard functions
```py
keycode_function(key: int) -> int
keycode_digit(key: int) -> int
```
`keycode_function(k)` returns the F-key number if `k` (i.e. 1 for `KEY_F1`, 2 for `KEY_F2`, etc.) and -1 for other keys.
`keycode_digit(k)` returns the digit associated with `k` (i.e. 0 for `KEY_0`, 1 for `KEY_1`, etc.) and -1 for other keys.
## Drawing and rendering
Reference headers: [`<gint/display.h>`](https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint/display.h), and for some details [`<gint/display-fx.h>`](https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint/display-fx.h) and [`<gint/display-cg.h>`](https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint/display-cg.h).
### Color manipulation
```py
C_WHITE: int # White
C_BLACK: int # Black
C_LIGHT: int # Light gray (on B&W: gray engine)
C_DARK: int # Dark gray (on B&W: gray engine)
C_NONE: int # Transparent
C_INVERT: int # Function: inverse
# Black-and-white (B&W) models only:
C_LIGHTEN: int # Function: lighten (gray engine)
C_DARKEN: int # Function: darken (gray engine)
# fx-CG models only:
C_RED: int # Pure red
C_GREEN: int # Pure green
C_BLUE: int # Pure blue
C_RGB(r: int, g: int, b: int) -> int
```
Colors are all integers (manipulating `(r,g,b)` tuples is excruciatingly slow and requires memory allocations all over the place). A few default colors are provided.
On the fx-CG series, the `C_RGB()` function can be used to create colors from three components ranging from 0 to 31.
TODO: Explain the gray engine.
### Basic rendering functions
```py
DWIDTH: int
DHEIGHT: int
dupdate() -> None
dclear(color: int) -> None
dpixel(x: int, y: int, color: int) -> None
dgetpixel(x: int, y: int) -> int
```
The integers `DWIDTH` and `DHEIGHT` indicate the screen's dimensions. The screen is 128x64 on black-and-white models (like the G-III) and 396x224 on the fx-CG series (the full screen is available).
All rendering functions draw to an internal image called the "VRAM"; rendering calls are thus not immediate visible on the screen. For the result to be visible one must call the `dupdate()` function, which transfers the contents of the VRAM to the real display. Usually, this is done after rendering everything we need on one frame instead of after each drawing function call.
In PythonExtra, `dupdate()` also indicates a "switch to graphics mode". Due to certain optimizations any call to `print()` is considered a "switch to text mode", and while in text mode the shell might redraw at any time. In order to draw after using text mode, one must call `dupdate()` to force a switch to graphics mode before starting rendering. Otherwise the shell and program might render at the same time and produce incoherent results.
`dclear()` fills the screen with a uniform color.
`dpixel()` changes a pixel's color. Coordinates are universally (x,y) where `x` is in the range 0 to DWIDTH-1 inclusive (0 being left) and `y` is in the range 0 to DHEIGHT-1 inclusive (0 being top).
`dgetpixel()` returns the color of a pixel. Note that `dgetpixel()` reads from VRAM, not from the display.
### Geometric shape rendering functions
TODO
### Image rendering functions
TODO
## Differences with gint's C API
- `dsubimage()` doesn't have its final parameter `int flags`. The flags are only minor optimizations and could be removed in future gint versions.
- Image constructors`image()` and `image_<format>()` don't exist in the C API.
- Asynchronous volatile-flag-based timeouts are replaced with synchronous millisecond delays (integer value or `None`).
TODO: There are more.

280
docs/sh/modgint-fr.md Normal file
View File

@ -0,0 +1,280 @@
# `gint`: Module d'accès aux fonctionnalités de gint
PythonExtra est écrit à l'aide du [fxSDK](https://gitea.planet-casio.com/Lephenixnoir/fxsdk) et utilise [gint](https://gitea.planet-casio.com/Lephenixnoir/gint) pour exécuter l'add-in. Le module Python `gint` permet d'accéder aux fonctions internes de gint en Python pour le dessin, le clavier, etc. Comme gint possède beaucoup de fonctions utiles avec de bonnes performances, il est intéressant de s'en servir au lieu d'utiliser e.g. `casioplot` ou `turtle`.
Le module `gint` essaie de garder en Python la même API que dans la version originale de la bibliothèque en C, c'est pourquoi peu de fonctions utilisent des arguments nommés ou autres fonctions surchargées. Il y a quelques différences, documentées à la fin de cette page. En cas de doute, la documentation fournie par les [fichiers d'en-tête de gint](https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint) (les `.h`) est tout à fait applicable pour comprendre les comportements que cette page n'explique pas.
Tous les noms de constantes, fonctions, etc. discutés dans cet article sont dans le module `gint`.
```py
import gint
# ou:
from gint import *
```
## Saisie au clavier
Les en-têtes de référence sont [`<gint/keyboard.h>`](https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint/keyboard.h) et [`<gint/keycodes.h>`](https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint/keycodes.h).
### Noms des touches
Le module fournit des constantes entières désignant toutes les touches du clavier. Les noms sont les suivants :
| | | | | | |
|------------|------------|------------|------------|------------|-------------|
|`KEY_F1` |`KEY_F2` |`KEY_F3` |`KEY_F4` |`KEY_F5` |`KEY_F6` |
|`KEY_SHIFT` |`KEY_OPTN` |`KEY_VARS` |`KEY_MENU` |`KEY_LEFT` |`KEY_UP` |
|`KEY_ALPHA` |`KEY_SQUARE`|`KEY_POWER` |`KEY_EXIT` |`KEY_DOWN` |`KEY_RIGHT` |
|`KEY_XOT` |`KEY_LOG` |`KEY_LN` |`KEY_SIN` |`KEY_COS` |`KEY_TAN` |
|`KEY_FRAC` |`KEY_FD` |`KEY_LEFTP` |`KEY_RIGHTP`|`KEY_COMMA` |`KEY_ARROW` |
|`KEY_7` |`KEY_8` |`KEY_9` |`KEY_DEL` |`KEY_ACON` | |
|`KEY_4` |`KEY_5` |`KEY_6` |`KEY_MUL` |`KEY_DIV` | |
|`KEY_1` |`KEY_2` |`KEY_3` |`KEY_ADD` |`KEY_SUB` | |
|`KEY_0` |`KEY_DOT` |`KEY_EXP` |`KEY_NEG` |`KEY_EXE` | |
### Événements clavier
```
key_event:
.time -> int
.mod -> bool
.shift -> bool
.alpha -> bool
.type -> KEYEV_NONE | KEYEV_DOWN | KEYEV_UP | KEYEV_HOLD
.key -> KEY_*
```
gint communique les informations sur ce qu'il se passe au clavier via des _événements_. Les événements indiquent quand une touche (champ `.key`) a été pressée, maintenue, ou relâchée (champ `.type` égal à `KEYEV_DOWN`, `KEYEV_HOLD` et `KEYEV_UP` respectivement), quand (champ `.time`) et si des modifieurs (SHIFT ou ALPHA, champs `.shift` et `.alpha`) étaient actifs à ce moment-là.
(Le champ `.mod` n'est pas très intéressant, et la valeur `KEYEV_NONE` de `.type` est discutée dans `pollevent()`.)
Les fonctions `getkey()`, `getekey_opt()`, `pollevent()` et `waitevent()` renvoient toutes des événements.
### Saisie d'une touche avec attente
```py
getkey() -> key_event
```
La fonction `getkey()` met le programme en pause jusqu'à ce qu'une touche soit pressée ou répétée, et renvoie l'événement associé (qui est forcément de type `KEYEV_DOWN` ou `KEYEV_HOLD`). Par défaut, les seules touches qui sont répétées sont les touches directionnelles, une première fois après 400 ms, et ensuite toutes les 40 ms.
Pas mal de choses peuvent se produire pendant l'exécution de `getkey()`. L'utilisateur peut appuyer sur SHIFT ou ALPHA, ce qui affecte les champs `.shift` et `.alpha` de l'événement renvoyé. L'utilisateur peut également se rendre au menu principal avec MENU et éteindre la calculatrice avec SHIFT+AC/ON.
_Exemple._ Dans un menu de sélection de N éléments, on pourrait naviguer avec les touches haut et bas, sauter directement au début ou à la fin avec SHIFT haut et SHIFT bas, et valider avec EXE.
```py
ev = getkey()
if ev.key == KEY_EXE:
pass # Valider
elif ev.key == KEY_UP and ev.shift:
pos = 0 # Revenir au début
elif ev.key == KEY_DOWN and ev.shift:
pos = N-1 # Aller à la fin
elif ev.key == KEY_UP and pos > 0:
pos -= 1 # Monter d'une position
elif ev.key == KEY_DOWN and pos < N-1:
pos += 1 # Descendre d'une position
```
TODO: Parler de `getkey_opt()`
### Lecture des événements en temps réel
```py
pollevent() -> key_event
waitevent() -> key_event
clearevents() -> None
```
gint enregistre l'activité du clavier en tâche de fond pendant que le programme s'exécute. Les événements sont mis dans une file d'attente jusqu'à ce que le programme les lise. C'est comme ça par exemple que `getkey()` détermine quoi renvoyer.
Il est possible d'accéder aux événements directement à l'aide de la fonction `pollevent()`. `pollevent()` renvoie l'événement le plus ancien qui n'a pas encore été lu par le programme. Si le programme a lu tous les événements et qu'il n'y a plus rien en attente, `pollevent()` renvoie un "faux" évenement de type `KEYEV_NONE` pour indiquer qu'il n'y a plus rien à lire.
Comme `pollevent()` retourne instanténement, on peut s'en servir pour lire l'activité du clavier sans mettre le programme en pause.
_Exemple._ Une boucle de jeu pourrait, à chaque frame, lire tous les événements en attente pour déterminer quand le joueur appuie sur la touche SHIFT ("action" dans cet exemple) pour déclencher une action.
```py
# Dessiner le jeu...
while True:
ev = pollevent()
if ev.type == KEYEV_NONE:
break # Fin de la lecture des événements
if ev.type == KEYEV_DOWN and ev.key == KEY_SHIFT:
pass # La touche SHIFT vient d'être pressée !
# Ignore implicitement les autres touches
# Simuler le jeu...
```
La fonction `waitevent()` est similaire, mais si tous les événements ont été lus elle attend qu'un événement se produise avant de retourner. Elle est plus rarement utilisée parce qu'en général quand on veut attendre on utilise `getkey()`.
La fonction `clearevents()` lit et ignore tous les événements, i.e. elle "jette" toutes les informations sur ce qu'il s'est passé au clavier. Elle est utile pour connaître l'état instantané du clavier avec `keydown()` (voir ci-dessous). `clearevents()` est équivalente à la définition suivante :
```py
def clearevents():
ev = pollevent()
while ev.type != KEYEV_NONE:
ev = pollevent()
```
### Lecture de l'état instantané du clavier
```py
keydown(key: int) -> bool
keydown_all(*keys: [int]) -> bool
keydown_any(*keys: [int]) -> bool
```
Une fois les événements lus, on peut tester l'état individuellement si les touches sont pressées ou pas à l'aide de la fonction `keydown()`. `keydown(k)` renvoie `True` si la touche `k` est pressée, `False` sinon. Cette fonction ne marche **que si les événements ont été lus**, ce qu'on fait souvent soit avec `pollevent()` soit avec `clearevents()`.
_Exemple._ Une boucle de jeu pourrait tester si les touches gauche/droite sont pressées à chaque frame pour déplacer le joueur.
```py
while True:
ev = pollevent()
# ... comme dans l'exemple pollevent()
if keydown(KEY_LEFT):
player_x -= 1
if keydown(KEY_RIGHT):
player_x += 1
```
La fonction `keydown_all()` prent une série de touches en paramètre et renvoie `True` si elles sout toutes pressées. `keydown_any()` est similaire et renvoie `True` si au moins une des touches listées est pressée.
### Lecture rapide des changements de position des touches
```py
cleareventflips() -> None
keypressed(key: int) -> bool
keyreleased(key: int) -> bool
```
`keydown()` indique uniquement l'état instantané des touches. Elle ne permet pas de déterminer à quel moment une touche passe de l'état relâché à l'état pressé ou l'inverse. Pour ça, il faut soit utiliser les événements (ce qui est un peu lourd), soit utiliser les fonctions ci-dessous.
Les fonctions `keypressed(k)` et `keyreleased(k)` indiquent si la touche a été pressée/relâchée depuis le dernier appel à `cleareventflips()`. Attention la notion de "pressée/relâchée" ici n'est pas le temps réel mais la lecture des événements.
_Exemple._ Une boucle de jeu peut tester à la fois l'état immédiat et les changements d'état des touches en utilisant les fonctions instantanée après `cleareventflips()` suivi de `clearevents()`.
```py
# Dessiner le jeu...
cleareventflips()
clearevents()
if keypressed(KEY_SHIFT):
pass # Action !
if keydown(KEY_LEFT):
player_x -= 1
if keydown(KEY_RIGHT):
player_x += 1
# Simuler le jeu...
```
### Fonctions diverses concernant le clavier
```py
keycode_function(key: int) -> int
keycode_digit(key: int) -> int
```
`keycode_function(k)` renvoie le numéro de F-touche de `k` (i.e. 1 pour `KEY_F1`, 2 pour `KEY_F2`, etc.) et -1 pour les autres touches.
`keycode_digit(k)` renvoie le chiffre associé à `k` (i.e. 0 pour `KEY_0`, 1 pour `KEY_1`, etc.) et -1 pour les autres touches.
## Dessin à l'écran
Les en-têtes de référence sont [`<gint/display.h>`](https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint/display.h), et pour certains détails techniques [`<gint/display-fx.h>`](https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint/display-fx.h) et [`<gint/display-cg.h>`](https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint/display-cg.h).
### Manipulation de couleurs
```py
C_WHITE: int # Blanc
C_BLACK: int # Noir
C_LIGHT: int # Gris clair (sur mono: moteur de gris)
C_DARK: int # Gris foncé (sur mono: moteur de gris)
C_NONE: int # Transparent
C_INVERT: int # Inverseur de couleur
# Graph mono uniquement :
C_LIGHTEN: int # Éclaircisseur de couleur (moteur de gris)
C_DARKEN: int # Assombrisseur de couleur (moteur de gris)
# Graph 90+E uniquement :
C_RED: int # Rouge pur
C_GREEN: int # Vert pur
C_BLUE: int # Bleu pur
C_RGB(r: int, g: int, b: int) -> int
```
Les couleurs sont toutes des nombres entiers (manipuler des tuples `(r,g,b)` est atrocement lent par comparaison et requiert des allocations mémoire dans tous les sens). Une poignée de couleurs est fournie par défaut.
Sur Graph 90+E, la fonction `C_RGB()` peut être utilisée pour créer des couleurs à partir de trois composantes de valeur 0 à 31.
TODO: Expliquer le moteur de gris.
### Fonctions de dessin basiques
```py
DWIDTH: int
DHEIGHT: int
dupdate() -> None
dclear(color: int) -> None
dpixel(x: int, y: int, color: int) -> None
dgetpixel(x: int, y: int) -> int
```
Les entiers `DWIDTH` et `DHEIGHT` indiquent la taille de l'écran. C'est 128x64 sur les Graph mono (type Graph 35+E II), 396x224 sur la Prizm et Graph 90+E (le plein écran est disponible).
Toutes les fonctions de dessin opèrent sur une image interne appellée "VRAM" ; l'effet du dessin n'est donc pas visible immédiatement à l'écran. Pour que le dessin se voie il faut appeler la fonction `dupdate()` qui transfère les contenus de la VRAM à l'écran réel. Généralement, on fait ça après avoir affiché tout ce dont on a besoin et surtout pas après chaque appel de fonction de dessin.
Dans PythonExtra, la fonction `dupdate()` indique aussi implicitement qu'on « passe en mode graphique ». À cause de certaines optimisations tout appel à `print()` est considéré comme un « passage en mode texte » et pendant qu'on est en mode texte le shell peut redessiner à tout moment. Si on veut dessiner après avoir utilisé le mode texte, il faut appeler `dupdate()` pour forcer un passage en mode graphique avant de commencer à dessiner. Sinon le dessin du shell pourrait interférer avec le dessin du programme.
La fonction `dclear()` remplit l'écran d'une couleur unie.
La fonction `dpixel()` modifie la couleur d'un pixel. Les coordonnées sont universellement (x,y) où `x` varie entre 0 et DWIDTH-1 inclus (0 étant à gauche), et `y` varie entre 0 et DHEIGHT-1 inclus (0 étant en haut).
La fonction `dgetpixel()` renvoie la couleur d'un pixel. Attention, `dgetpixel()` lit dans la VRAM, pas sur l'écran.
_Exemple ([`ex_draw1.py`](../../sh/examples/ex_draw1.py))._
```py
from gint import *
dclear(C_WHITE)
for y in range(10):
for x in range(10):
if (x^y) & 1:
dpixel(x, y, C_BLACK)
dupdate()
```
### Fonctions de dessin de formes géométriques
```py
drect(x1: int, y1: int, x2: int, y2: int, color: int) -> None
drect_border(x1: int, y1: int, x2: int, y2: int, fill_color: int,
border_width: int, border_color: int) -> None
dline(x1: int, y1: int, x2: int, y2: int, color: int) -> None
dhline(y: int, color: int) -> None
dvline(x: int, color: int) -> None
dcircle(x: int, y: int, radius: int, fill_color: int,
border_color: int) -> None
dellipse(x1: int, y1: int, x2: int, y2: int, fill_color: int,
border_color: int) -> None
```
TODO
### Fonctions de dessin d'images
TODO
## Différences avec l'API C de gint
- `dsubimage()` n'a pas de paramètre `int flags`. Les flags en question ne ont que des optimisations mineures et pourraient disparaître dans une version future de gint.
- Les constructeurs d'image `image()` et `image_<format>()` n'existent pas dans l'API C.
- Les timeouts asynchrones à base d'entiers volatiles sont remplacés par des timeouts synchrones avec des durées optionnelles en millisecondes (entier ou `None`).
TODO : Il y en a d'autres.

View File

@ -20,6 +20,7 @@ SRC_C = \
ports/sh/numworks/time.c \
ports/sh/modgint.c \
ports/sh/mphalport.c \
ports/sh/objgintimage.c \
ports/sh/pyexec.c \
ports/sh/widget_shell.c \
shared/runtime/gchelper_generic.c \
@ -33,6 +34,7 @@ SRC_QSTR += \
ports/sh/numworks/ion.c \
ports/sh/numworks/time.c \
ports/sh/modgint.c \
ports/sh/objgintimage.c \
ports/sh/pyexec.c \
ASSETS_O := $(SH_ASSETS:%=$(BUILD)/sh_assets/%.o)

View File

@ -6,6 +6,7 @@
#include <gint/keyboard.h>
#include <gint/display.h>
#include <gint/kmalloc.h>
#include <gint/defs/util.h>
#include <stdlib.h>
#include <string.h>
@ -59,7 +60,12 @@ bool console_line_init(console_line_t *line, int prealloc_size)
void console_line_deinit(console_line_t *line)
{
free(line->data);
memset(line, 0, sizeof *line);
/* Manual memset to allow for PRAM0 storage */
line->data = NULL;
line->size = 0;
line->alloc_size = 0;
line->render_lines = 0;
line->prefix = 0;
}
bool console_line_alloc(console_line_t *line, int n)
@ -86,7 +92,7 @@ int console_line_capacity(console_line_t *line)
void console_line_set_prefix(console_line_t *line, int prefix_size)
{
line->prefix = min(max(0, prefix_size), line->size);
line->prefix = min(max(0, prefix_size), (int)line->size);
}
bool console_line_insert(console_line_t *line, int p, char const *str, int n)
@ -175,7 +181,7 @@ bool linebuf_init(linebuf_t *buf, int capacity, int backlog_size)
if(capacity <= 0)
return false;
buf->lines = malloc(capacity * sizeof *buf->lines);
buf->lines = kmalloc(capacity * sizeof *buf->lines, PE_CONSOLE_LINE_ALLOC);
if(!buf->lines)
return false;
@ -192,7 +198,7 @@ bool linebuf_init(linebuf_t *buf, int capacity, int backlog_size)
void linebuf_deinit(linebuf_t *buf)
{
free(buf->lines);
kfree((void *)buf->lines);
memset(buf, 0, sizeof *buf);
}

View File

@ -24,26 +24,34 @@
#include <gint/keyboard.h>
#include <gint/display.h>
#include <gint/defs/attributes.h>
#include <stdbool.h>
/* Maximum line length, to ensure the console can threshold its memory usage
while cleaning only entire lines. Lines longer than this get split. */
#define PE_CONSOLE_LINE_MAX_LENGTH 1024
/* Allocation arena for arrays of lines. */
#ifdef FX9860G
#define PE_CONSOLE_LINE_ALLOC "pram0"
#else
#define PE_CONSOLE_LINE_ALLOC NULL
#endif
//=== Dynamic console lines ===//
typedef struct
typedef volatile struct
{
/* Line contents, NUL-terminated. The buffer might be larger. */
char *data;
/* Size of contents (not counting the NUL). */
int16_t size;
int32_t size :16;
/* Allocated size (always ≥ size+1). */
int16_t alloc_size;
int32_t alloc_size :16;
/* Number or render lines used (updated on-demand). */
int16_t render_lines;
int32_t render_lines :16;
/* Number of initial characters that can't be edited. */
int16_t prefix;
int32_t prefix :16;
} console_line_t;
@ -93,7 +101,10 @@ typedef struct
- 0 <= size <= capacity
- 0 <= start < capacity
- When size is 0, start is undefined. */
int capacity, start, size;
int16_t capacity, start, size;
/* Total number of rendered lines for the buffer. */
int16_t total_rendered;
/* To keep track of lines' identity, the rotating array includes an extra
numbering system. Each line is assigned an *absolute* line number which
@ -117,8 +128,6 @@ typedef struct
for edition (ie. followed by another line). Lazy rendering can always
start at `absolute_rendered+1`. */
int absolute_rendered;
/* Total number of rendered lines for the buffer. */
int total_rendered;
} linebuf_t;
@ -167,15 +176,15 @@ typedef struct
linebuf_t lines;
/* Cursor position within the last line. */
int cursor;
int16_t cursor;
/* Whether new data has been added and a frame should be rendered. */
bool render_needed;
/* View geometry parameters from last console_compute_view(). */
font_t const *render_font;
int render_width;
int render_lines;
int16_t render_width;
int16_t render_lines;
} console_t;

View File

@ -49,6 +49,8 @@ static bool timeout_popup(void)
}
#endif
static bool videocapture = false;
void pe_debug_init(void)
{
usb_interface_t const *intf[] = { &usb_ff_bulk, NULL };
@ -82,17 +84,26 @@ static void print_strn(void *env, const char *str, size_t len) {
mp_print_t const mp_debug_print = { NULL, print_strn };
void pe_debug_kmalloc(void)
void pe_debug_kmalloc(char const *prefix)
{
kmalloc_gint_stats_t *s;
kmalloc_gint_stats_t *s1, *s2;
s1 = kmalloc_get_gint_stats(kmalloc_get_arena("_uram"));
s = kmalloc_get_gint_stats(kmalloc_get_arena("_uram"));
pe_debug_printf("[_uram] used=%d free=%d\n",
s->used_memory, s->free_memory);
#ifdef FX9860G
s2 = kmalloc_get_gint_stats(kmalloc_get_arena("pram0"));
pe_debug_printf("%s: _uram[used=%d free=%d] pram0[used=%d free=%d]\n",
prefix,
s1->used_memory, s1->free_memory,
s2->used_memory, s2->free_memory);
#endif
s = kmalloc_get_gint_stats(kmalloc_get_arena("_ostk"));
pe_debug_printf("[_ostk] used=%d free=%d\n",
s->used_memory, s->free_memory);
#ifdef FXCG50
s2 = kmalloc_get_gint_stats(kmalloc_get_arena("_ostk"));
pe_debug_printf("%s: _uram[used=%d free=%d] _ostk[used=%d free=%d]\n",
prefix,
s1->used_memory, s1->free_memory,
s2->used_memory, s2->free_memory);
#endif
}
void pe_debug_screenshot(void)
@ -101,4 +112,17 @@ void pe_debug_screenshot(void)
usb_fxlink_screenshot(true);
}
void pe_debug_toggle_videocapture(void)
{
videocapture = !videocapture;
}
void pe_debug_run_videocapture(void)
{
if(videocapture) {
usb_open_wait();
usb_fxlink_videocapture(true);
}
}
#endif /* PE_DEBUG */

View File

@ -28,17 +28,25 @@ __attribute__((noreturn));
int pe_debug_printf(char const *fmt, ...);
/* Print information about allocation status. */
void pe_debug_kmalloc(void);
void pe_debug_kmalloc(char const *prefix);
/* Take a screenshot. */
void pe_debug_screenshot(void);
/* Toggle video capture. */
void pe_debug_toggle_videocapture(void);
/* Send a video capture frame if video capture is enabled. */
void pe_debug_run_videocapture(void);
#if !PE_DEBUG
#define PE_DEBUG_NOOP do {} while(0)
#define pe_debug_init(...) PE_DEBUG_NOOP
#define pe_debug_printf(...) PE_DEBUG_NOOP
#define pe_debug_kmalloc(...) PE_DEBUG_NOOP
#define pe_debug_screenshot(...) PE_DEBUG_NOOP
#define pe_debug_init(...) PE_DEBUG_NOOP
#define pe_debug_printf(...) PE_DEBUG_NOOP
#define pe_debug_kmalloc(...) PE_DEBUG_NOOP
#define pe_debug_screenshot(...) PE_DEBUG_NOOP
#define pe_debug_toggle_videocapture(...) PE_DEBUG_NOOP
#define pe_debug_run_videocapture(...) PE_DEBUG_NOOP
#endif
#endif /* __PYTHONEXTRA_DEBUG_H */

View File

@ -0,0 +1,73 @@
from gint import *
import math
palette = b'\x00\x00\xf8\x00\xff\xff'
data = b'\x80\x81\x80\x81\x82\x81\x80\x81\x80'
img = image(IMAGE_P8_RGB565, color_count=3, width=3, height=3,
stride=3, data=data, palette=palette)
print(img)
img_alpha = image_p8_rgb565a(3, 3, data, palette)
print(img_alpha)
# h ∈ [0,360), s ∈ [0,1], l ∈ [0,1]
def hsl2rgb(H, S, L):
C = (1 - abs(2*L - 1)) * S
Hp = H / 60
X = C * (1 - abs(Hp % 2 - 1))
R, G, B = [(C,X,0), (X,C,0), (0,C,X), (0,X,C), (X,0,C), (C,0,X)][int(Hp)]
m = L - C / 2
return (R + m, G + m, B + m)
gradient_data = bytearray(32*32*2)
for y in range(32):
for x in range(32):
i = (32 * y + x) * 2
lx, ly = x-15.5, y-15.5
radius = (lx*lx + ly*ly) / (16**2)
if radius > 1:
gradient_data[i] = 0x00
gradient_data[i+1] = 0x01
continue
h = math.atan2(-ly, lx) * 180 / math.pi
h = h if h >= 0 else h + 360
s = radius
l = 0.5
r, g, b = hsl2rgb(h, s, l)
color = C_RGB(int(r*32), int(g*32), int(b*32))
gradient_data[i] = color >> 8
gradient_data[i+1] = color & 0xff
gradient = image_rgb565a(32, 32, gradient_data)
print(gradient)
from cg_image_puzzle import *
print(puzzle)
# Take graphics control
dupdate()
h = DWIDTH // 2
dclear(C_WHITE)
drect(h, 0, DWIDTH-1, DHEIGHT-1, C_RGB(28,28,28))
dimage(10, 10, img)
dimage(15, 10, img)
dimage(20, 10, img)
dimage(h+10, 10, img_alpha)
dimage(h+15, 10, img_alpha)
dimage(h+20, 10, img_alpha)
dimage(h-16, 20, gradient)
dimage(40, 100, puzzle)
x, y = 110, 100
hw = puzzle.width // 2
hh = puzzle.height // 2
dsubimage(x-1, y-1, puzzle, 0, 0, hw, hh)
dsubimage(x+hw+1, y-1, puzzle, hw, 0, hw, hh)
dsubimage(x-1, y+hh+1, puzzle, 0, hh, hw, hh)
dsubimage(x+hw+1, y+hh+1, puzzle, hw, hh, hw, hh)
dupdate()
getkey()

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

View File

@ -0,0 +1,8 @@
from gint import *
dclear(C_WHITE)
for y in range(10):
for x in range(10):
if (x^y) & 1:
dpixel(x, y, C_BLACK)
dupdate()
getkey()

View File

@ -0,0 +1,10 @@
import gint
while True:
ev = gint.getkey_opt(gint.GETKEY_DEFAULT, 5000)
if ev.type == gint.KEYEV_NONE:
print("no press after 5000 ms")
else:
print(ev)
if ev.key == gint.KEY_EXIT and not ev.shift and not ev.alpha:
break

View File

@ -0,0 +1,44 @@
from gint import *
img = image(IMAGE_MONO, width=3, height=3, data=b'\xa0\x00\x00\x00\x50\x00\x00\x00\xa0\x00\x00\x00')
print(img)
print(img.format, img.width, img.height)
print(img.data)
segments = image(IMAGE_MONO, 79, 12, bytearray(b'|\x00||\x00|||||\x00\x00\xba\x02::\x82\xb8\xb8:\xba\xba\x00\x00\xc6\x06\x06\x06\xc6\xc0\xc0\x06\xc6\xc6\x00\x00\xc6\x06\x06\x06\xc6\xc0\xc0\x06\xc6\xc6\x00\x00\x82\x02\x02\x02\x82\x80\x80\x02\x82\x82\x00\x00\x00\x00|||||\x00||\x00\x00\x82\x02\xb8:::\xba\x02\xba:\x00\x00\xc6\x06\xc0\x06\x06\x06\xc6\x06\xc6\x06\x00\x00\xc6\x06\xc0\x06\x06\x06\xc6\x06\xc6\x06\x00\x00\xc6\x06\xc0\x06\x06\x06\xc6\x06\xc6\x06\x00\x00\xba\x02\xb8:\x02:\xba\x02\xba:\x00\x00|\x00||\x00||\x00||\x00\x00'))
print(segments)
# ..xx x.. | ..xx x..
# .x.. .x. | .xxx xx.
# x..x ..x | xxxx xxx
# x.xx x.x | xxxx xxx
# x..x ..x | xxxx xxx
# .x.. .x. | .xxx xx.
# ..xx x.. | ..xx x..
alpha = image(IMAGE_MONO_ALPHA, 7, 7, b'\x38\x00\x00\x00\x38\x00\x00\x00\x7c\x00\x00\x00\x44\x00\x00\x00\xfe\x00\x00\x00\x92\x00\x00\x00\xfe\x00\x00\x00\xba\x00\x00\x00\xfe\x00\x00\x00\x92\x00\x00\x00\x7c\x00\x00\x00\x44\x00\x00\x00\x38\x00\x00\x00\x38\x00\x00\x00')
# Take graphics control again after the print()
dupdate()
dclear(C_WHITE)
drect(63, 0, 127, 63, C_BLACK)
dimage(10, 5, img)
dimage(10, 15, segments)
def digit(x, y, num):
dsubimage(x, y, segments, 8*num, 0, 7, segments.height)
digit(15+0*8, 45, 4)
digit(15+1*8, 45, 2)
digit(15+2*8, 45, 7)
digit(15+3*8, 45, 3)
d = segments.data
for i in range(len(d) / 2):
d[i] = ~d[i]
dimage(10, 30, segments)
dimage(60, 5, alpha)
dupdate()
getkey()

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

View File

@ -75,7 +75,7 @@ static ssize_t stdouterr_write(void *data, void const *buf, size_t size)
return size;
}
fs_descriptor_type_t stdouterr_type = {
static fs_descriptor_type_t const stdouterr_type = {
.read = NULL,
.write = stdouterr_write,
.lseek = NULL,
@ -139,6 +139,14 @@ static bool async_filter(key_event_t ev)
return false;
}
#if PE_DEBUG
if(ev.key == KEY_SQUARE) {
if(ev.type == KEYEV_DOWN)
pe_debug_toggle_videocapture();
return false;
}
#endif
return true;
}
@ -190,6 +198,7 @@ void pe_draw(void)
DIMAGE_NONE);
#endif
dupdate();
pe_debug_run_videocapture();
}
//=== Application control functions ===//
@ -244,10 +253,40 @@ static void pe_print_prompt(int which)
else
prompt = mp_repl_get_ps1();
char str[32];
kmalloc_gint_stats_t *s;
s = kmalloc_get_gint_stats(kmalloc_get_arena("_uram"));
sprintf(str, "%lu", s->free_memory);
console_write(PE.console, str, -1);
console_write(PE.console, prompt, -1);
console_lock_prefix(PE.console);
}
static void pe_update_title(void)
{
char const *folder = jfileselect_current_folder(PE.fileselect);
#ifdef FX9860G
/* Use a static variable to ensure the title shows even if OoM */
static char title[22];
if(!folder)
jlabel_set_text(PE.title, "Python");
else {
sprintf(title, "Python[%-13.13s]", folder);
jlabel_set_text(PE.title, title);
}
#endif
#ifdef FXCG50
if(!folder)
jlabel_set_text(PE.title, "PythonExtra");
else
jlabel_asprintf(PE.title, "PythonExtra (%s)", folder);
#endif
}
/* Handle a GUI event. If `shell_bound` is true, only actions that have an
effect on the shell are allowed and the return value is any full line that
is entered in the shell. Otherwise, the full GUI is available and the return
@ -280,6 +319,7 @@ static char *pe_handle_event(jevent e, bool shell_bound)
jwidget_set_visible(PE.title, PE.show_title_in_shell);
pe_reset_micropython();
pe_draw();
char *str = malloc(8 + strlen(module) + 1);
if(str) {
@ -295,15 +335,19 @@ static char *pe_handle_event(jevent e, bool shell_bound)
pe_print_prompt(1);
}
}
if(!shell_bound && e.type == JFILESELECT_LOADED)
pe_update_title();
if(e.type != JWIDGET_KEY || e.key.type == KEYEV_UP)
return NULL;
int key = e.key.key;
pe_debug_kmalloc("key");
if(key == KEY_SQUARE && !e.key.shift && e.key.alpha)
pe_debug_screenshot();
if(key == KEY_TAN)
pe_debug_kmalloc();
pe_debug_kmalloc("tan");
if(!shell_bound && key == KEY_F1) {
jscene_show_and_focus(PE.scene, PE.fileselect);
@ -340,8 +384,20 @@ int pe_readline(vstr_t *line, char const *prompt)
int main(int argc, char **argv)
{
#ifdef FX9860G
/* Use PRAM0 as an arena for special allocs to save memory elsewhere */
kmalloc_arena_t arena_pram0 = { 0 };
arena_pram0.name = "pram0";
arena_pram0.is_default = false;
arena_pram0.start = (void *)0xfe200000;
arena_pram0.end = (void *)0xfe228000; /* 160 kB! */
kmalloc_init_arena(&arena_pram0, true);
kmalloc_add_arena(&arena_pram0);
#endif
pe_debug_init();
pe_debug_kmalloc();
pe_debug_printf("---\n");
pe_debug_kmalloc("main");
//=== Init sequence ===//
@ -356,6 +412,8 @@ int main(int argc, char **argv)
PE.console = console_create(8192, 200);
pe_debug_kmalloc("console");
/* Set up standard streams */
close(STDOUT_FILENO);
close(STDERR_FILENO);
@ -388,9 +446,9 @@ int main(int argc, char **argv)
#if PE_DEBUG
/* Add some Python ram */
// https://www.planet-casio.com/Fr/forums/topic15269-10-khicas-add-in-calcul-formel-pour-graph-90e-et-35eii.html#189284
void *py_ram_start = (void*)0x88053800;
/* void *py_ram_start = (void*)0x88053800;
void *py_ram_end = (void*)0x8807f000;
gc_add(py_ram_start, py_ram_end);
gc_add(py_ram_start, py_ram_end); */
#endif
#else
/* Get everything from the OS stack (~ 350 ko) */
@ -422,13 +480,17 @@ int main(int argc, char **argv)
MP_OBJ_NEW_QSTR(qstr_from_str("."));
#endif
pe_debug_kmalloc("upy");
pyexec_event_repl_init();
pe_print_prompt(1);
pe_debug_kmalloc("prompt");
//=== GUI setup ===//
PE.scene = jscene_create_fullscreen(NULL);
PE.title = jlabel_create("PythonExtra", PE.scene);
PE.title = jlabel_create("<temp>", PE.scene);
jwidget *stack = jwidget_create(PE.scene);
jfkeys *fkeys = jfkeys_create2(&img_fkeys_main, "/FILES;/SHELL", PE.scene);
(void)fkeys;
@ -462,6 +524,8 @@ int main(int argc, char **argv)
jwidget_set_padding(stack, 0, 6, 0, 6);
#endif
pe_debug_kmalloc("ui");
/* Initial state */
jfileselect_browse(PE.fileselect, "/");
jscene_show_and_focus(PE.scene, PE.fileselect);

View File

@ -7,6 +7,7 @@
#include "py/runtime.h"
#include "py/obj.h"
#include "debug.h"
#include <gint/display.h>
#include <stdlib.h>
#include <string.h>
@ -72,6 +73,7 @@ static mp_obj_t show_screen(void)
void pe_enter_graphics_mode(void);
pe_enter_graphics_mode();
dupdate();
pe_debug_run_videocapture();
return mp_const_none;
}

View File

@ -9,10 +9,14 @@
// considered relevant for high-level Python development).
//---
#include "debug.h"
#include "py/runtime.h"
#include "py/objtuple.h"
#include "objgintimage.h"
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/timer.h>
#include <gint/drivers/keydev.h>
void pe_enter_graphics_mode(void);
@ -117,17 +121,64 @@ STATIC mp_obj_t modgint_keyreleased(mp_obj_t arg1)
return mp_obj_new_bool(keyreleased(key) != 0);
}
/* Version of getkey_opt() that includes a VM hook */
STATIC key_event_t getkey_opt_internal(int opt, int timeout_ms)
{
/* Preset keydev transforms so they stay between calls */
keydev_t *d = keydev_std();
keydev_transform_t tr = keydev_transform(d);
key_event_t ev;
int o = KEYDEV_TR_REPEATS +
KEYDEV_TR_DELETE_MODIFIERS +
KEYDEV_TR_DELETE_RELEASES +
(opt & (GETKEY_MOD_SHIFT + GETKEY_MOD_ALPHA));
keydev_set_transform(d, (keydev_transform_t){ o, tr.repeater });
bool has_timeout = (timeout_ms >= 0);
while(!has_timeout || timeout_ms > 0) {
/* Program a delay of whatever's left or 20 ms, whichever is smaller.
It's not easy to reload a timer currently so just reconfigure. */
volatile int flag = 0;
int round_ms = has_timeout ? min(timeout_ms, 20) : 20;
int t = timer_configure(TIMER_ETMU, round_ms * 1000,
GINT_CALL_SET(&flag));
timer_start(t);
/* Run getkey_opt() for that short period */
ev = getkey_opt(opt, &flag);
timer_stop(t);
if(ev.type != KEYEV_NONE)
break;
/* The whole reason this function exists -- run the VM hook */
MICROPY_VM_HOOK_LOOP;
if(has_timeout)
timeout_ms -= round_ms;
}
keydev_set_transform(d, tr);
return ev;
}
STATIC mp_obj_t modgint_getkey(void)
{
key_event_t ev = getkey();
key_event_t ev = getkey_opt_internal(GETKEY_DEFAULT, -1);
return mk_key_event(ev);
}
// TODO: getkey_opt: timeout parameter?
STATIC mp_obj_t modgint_getkey_opt(mp_obj_t arg1)
STATIC mp_obj_t modgint_getkey_opt(mp_obj_t arg1, mp_obj_t arg2)
{
int options = mp_obj_get_int(arg1);
key_event_t ev = getkey_opt(options, NULL);
int timeout_ms = -1;
if(arg2 != mp_const_none)
timeout_ms = mp_obj_get_int(arg2);
key_event_t ev = getkey_opt_internal(options, timeout_ms);
return mk_key_event(ev);
}
@ -152,7 +203,7 @@ FUN_VAR(keydown_any, 0);
FUN_1(keypressed);
FUN_1(keyreleased);
FUN_0(getkey);
FUN_1/*2*/(getkey_opt);
FUN_2(getkey_opt);
FUN_1(keycode_function);
FUN_1(keycode_digit);
@ -179,6 +230,7 @@ STATIC mp_obj_t modgint_dupdate(void)
{
pe_enter_graphics_mode();
dupdate();
pe_debug_run_videocapture();
return mp_const_none;
}
@ -300,6 +352,108 @@ STATIC mp_obj_t modgint_dtext(size_t n, mp_obj_t const *args)
return mp_const_none;
}
/* fx-CG-specific image constructors */
#ifdef FXCG50
STATIC mp_obj_t modgint_image_rgb565(mp_obj_t arg1, mp_obj_t arg2,
mp_obj_t arg3)
{
int width = mp_obj_get_int(arg1);
int height = mp_obj_get_int(arg2);
return objgintimage_make(&mp_type_gintimage, IMAGE_RGB565, 0, width,
height, width * 2, arg3, mp_const_none);
}
STATIC mp_obj_t modgint_image_rgb565a(mp_obj_t arg1, mp_obj_t arg2,
mp_obj_t arg3)
{
int width = mp_obj_get_int(arg1);
int height = mp_obj_get_int(arg2);
return objgintimage_make(&mp_type_gintimage, IMAGE_RGB565A, 0, width,
height, width * 2, arg3, mp_const_none);
}
STATIC mp_obj_t modgint_image_p8_rgb565(size_t n, mp_obj_t const *args)
{
int width = mp_obj_get_int(args[0]);
int height = mp_obj_get_int(args[1]);
mp_obj_t data = args[2];
mp_obj_t palette = args[3];
int color_count = mp_obj_get_int(mp_obj_len(palette)) / 2;
int stride = width;
return objgintimage_make(&mp_type_gintimage, IMAGE_P8_RGB565,
color_count, width, height, stride, data, palette);
}
STATIC mp_obj_t modgint_image_p8_rgb565a(size_t n, mp_obj_t const *args)
{
int width = mp_obj_get_int(args[0]);
int height = mp_obj_get_int(args[1]);
mp_obj_t data = args[2];
mp_obj_t palette = args[3];
int color_count = mp_obj_get_int(mp_obj_len(palette)) / 2;
int stride = width;
return objgintimage_make(&mp_type_gintimage, IMAGE_P8_RGB565A,
color_count, width, height, stride, data, palette);
}
STATIC mp_obj_t modgint_image_p4_rgb565(size_t n, mp_obj_t const *args)
{
int width = mp_obj_get_int(args[0]);
int height = mp_obj_get_int(args[1]);
mp_obj_t data = args[2];
mp_obj_t palette = args[3];
int stride = (width + 1) / 2;
return objgintimage_make(&mp_type_gintimage, IMAGE_P4_RGB565, 16,
width, height, stride, data, palette);
}
STATIC mp_obj_t modgint_image_p4_rgb565a(size_t n, mp_obj_t const *args)
{
int width = mp_obj_get_int(args[0]);
int height = mp_obj_get_int(args[1]);
mp_obj_t data = args[2];
mp_obj_t palette = args[3];
int stride = (width + 1) / 2;
return objgintimage_make(&mp_type_gintimage, IMAGE_P4_RGB565A, 16,
width, height, stride, data, palette);
}
#endif /* FXCG50 */
STATIC mp_obj_t modgint_dimage(mp_obj_t arg1, mp_obj_t arg2, mp_obj_t arg3)
{
mp_int_t x = mp_obj_get_int(arg1);
mp_int_t y = mp_obj_get_int(arg2);
bopti_image_t img;
objgintimage_get(arg3, &img);
dimage(x, y, &img);
return mp_const_none;
}
STATIC mp_obj_t modgint_dsubimage(size_t n_args, const mp_obj_t *args)
{
mp_int_t x = mp_obj_get_int(args[0]);
mp_int_t y = mp_obj_get_int(args[1]);
// args[2] is the image
mp_int_t left = mp_obj_get_int(args[3]);
mp_int_t top = mp_obj_get_int(args[4]);
mp_int_t width = mp_obj_get_int(args[5]);
mp_int_t height = mp_obj_get_int(args[6]);
bopti_image_t img;
objgintimage_get(args[2], &img);
dsubimage(x, y, &img, left, top, width, height, DIMAGE_NONE);
return mp_const_none;
}
FUN_0(__init__);
#ifdef FXCG50
@ -318,6 +472,16 @@ FUN_BETWEEN(dcircle, 5, 5);
FUN_BETWEEN(dellipse, 6, 6);
FUN_BETWEEN(dtext_opt, 8, 8);
FUN_BETWEEN(dtext, 4, 4);
#ifdef FXCG50
FUN_3(image_rgb565);
FUN_3(image_rgb565a);
FUN_BETWEEN(image_p8_rgb565, 4, 4);
FUN_BETWEEN(image_p8_rgb565a, 4, 4);
FUN_BETWEEN(image_p4_rgb565, 4, 4);
FUN_BETWEEN(image_p4_rgb565a, 4, 4);
#endif
FUN_3(dimage);
FUN_BETWEEN(dsubimage, 7, 7);
/* Module definition */
@ -452,7 +616,12 @@ STATIC const mp_rom_map_elem_t modgint_module_globals_table[] = {
INT(C_LIGHT),
INT(C_DARK),
INT(C_BLACK),
INT(C_INVERT),
INT(C_NONE),
#ifdef FX9860G
INT(C_LIGHTEN),
INT(C_DARKEN),
#endif
#ifdef FXCG50
INT(C_RED),
INT(C_GREEN),
@ -473,6 +642,39 @@ STATIC const mp_rom_map_elem_t modgint_module_globals_table[] = {
OBJ(dellipse),
OBJ(dtext_opt),
OBJ(dtext),
{ MP_ROM_QSTR(MP_QSTR_image), MP_ROM_PTR(&mp_type_gintimage) },
#ifdef FXCG50
OBJ(image_rgb565),
OBJ(image_rgb565a),
OBJ(image_p8_rgb565),
OBJ(image_p8_rgb565a),
OBJ(image_p4_rgb565),
OBJ(image_p4_rgb565a),
#endif
OBJ(dimage),
OBJ(dsubimage),
/* <gint/image.h> */
#ifdef FX9860G
INT(IMAGE_MONO),
INT(IMAGE_MONO_ALPHA),
INT(IMAGE_GRAY),
INT(IMAGE_GRAY_ALPHA),
#endif
#ifdef FXCG50
INT(IMAGE_RGB565),
INT(IMAGE_RGB565A),
INT(IMAGE_P8_RGB565),
INT(IMAGE_P8_RGB565A),
INT(IMAGE_P4_RGB565),
INT(IMAGE_P4_RGB565A),
INT(IMAGE_FLAGS_DATA_RO),
INT(IMAGE_FLAGS_PALETTE_RO),
INT(IMAGE_FLAGS_DATA_ALLOC),
INT(IMAGE_FLAGS_PALETTE_ALLOC),
#endif
};
STATIC MP_DEFINE_CONST_DICT(
modgint_module_globals, modgint_module_globals_table);

7
ports/sh/objgintfont.c Normal file
View File

@ -0,0 +1,7 @@
/* gint.font(name, flags, line_heignt, data_height, block_count, glyph_count,
char_spacing, blocks, glyphs, ...)
Fixed-width fonts:
... width, storage_size
Proportional fonts:
... glyph_index, glyph_width */

372
ports/sh/objgintimage.c Normal file
View File

@ -0,0 +1,372 @@
//---------------------------------------------------------------------------//
// ____ PythonExtra //
//.-'`_ o `;__, A community port of MicroPython for CASIO calculators. //
//.-'` `---` ' License: MIT (except some files; see LICENSE) //
//---------------------------------------------------------------------------//
#include "objgintimage.h"
#include "py/runtime.h"
#include "py/objarray.h"
#include <string.h>
STATIC mp_obj_t ptr_to_memoryview(void *ptr, int size, int typecode, bool rw)
{
if(ptr == NULL)
return mp_const_none;
if(rw)
typecode |= MP_OBJ_ARRAY_TYPECODE_FLAG_RW;
return mp_obj_new_memoryview(typecode, size, ptr);
}
#ifdef FX9860G
/* Heuristic to check if the image is read-only or not */
STATIC bool pointer_is_ro(void *data)
{
uintptr_t addr = (uintptr_t)data;
return !addr || (addr >= 0x00300000 && addr <= 0x00500000);
}
STATIC int image_data_size(int profile, int width, int height)
{
int layers = image_layer_count(profile);
int longwords = (width + 31) >> 5;
return layers * longwords * height * 4;
}
/* gint.image(profile, width, height, data)
Keyword labels are allowed but the order must remain the same. */
STATIC mp_obj_t image_make_new(const mp_obj_type_t *type, size_t n_args,
size_t n_kw, const mp_obj_t *args)
{
enum { ARG_profile, ARG_width, ARG_height, ARG_data };
static mp_arg_t const allowed_args[] = {
{ MP_QSTR_profile, MP_ARG_INT | MP_ARG_REQUIRED,
{.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_width, MP_ARG_INT | MP_ARG_REQUIRED,
{.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_height, MP_ARG_INT | MP_ARG_REQUIRED,
{.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_data, MP_ARG_OBJ | MP_ARG_REQUIRED,
{.u_rom_obj = MP_ROM_NONE} },
};
mp_arg_val_t vals[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args),
allowed_args, vals);
int profile = vals[ARG_profile].u_int;
int width = vals[ARG_width].u_int;
int height = vals[ARG_height].u_int;
mp_obj_t data = vals[ARG_data].u_obj;
return objgintimage_make(type, profile, width, height, data);
}
mp_obj_t objgintimage_make(const mp_obj_type_t *type, int profile, int width,
int height, mp_obj_t data)
{
/* The supplied object must implement the buffer protocol */
mp_buffer_info_t buf;
if(!mp_get_buffer(data, &buf, MP_BUFFER_READ))
mp_raise_TypeError("data must be a buffer object");
/* Size and bounds checks */
int data_len = MP_OBJ_SMALL_INT_VALUE(mp_obj_len(data));
if(width <= 0 || height <= 0)
mp_raise_ValueError("image width/height must be >0");
if(profile < 0 || profile >= 8)
mp_raise_ValueError("invalid image profile");
if(data_len < image_data_size(profile, width, height))
mp_raise_ValueError("data len() should be >= 4*ceil(w/32)*h*layers");
/* Construct image! */
mp_obj_gintimage_t *self = mp_obj_malloc(mp_obj_gintimage_t, type);
self->img.profile = profile;
self->img.width = width;
self->img.height = height;
self->img.data = NULL;
self->data = data;
return MP_OBJ_FROM_PTR(self);
}
mp_obj_t objgintimage_make_from_gint_image(bopti_image_t const *img)
{
/* The original image is assumed to be valid. */
mp_obj_gintimage_t *self = mp_obj_malloc(mp_obj_gintimage_t,
&mp_type_gintimage);
memcpy(&self->img, img, sizeof *img);
int data_size = image_data_size(img->profile, img->width, img->height);
bool rw = !pointer_is_ro(img->data);
self->data = ptr_to_memoryview(img->data, data_size, 'B', rw);
return MP_OBJ_FROM_PTR(self);
}
STATIC void image_print(mp_print_t const *print, mp_obj_t self_in,
mp_print_kind_t kind)
{
(void)kind;
mp_obj_gintimage_t *self = MP_OBJ_TO_PTR(self_in);
char const *data_str =
self->data != mp_const_none ? "py" :
pointer_is_ro(self->img.data) ? "ro" : "rw";
static char const * const fmt_names[] = {
"mono", "mono_alpha", "gray", "gray_alpha"
};
char const *format_str =
(self->img.profile < 4) ? fmt_names[self->img.profile] : "?";
mp_printf(print, "<%s image (%d layers), %dx%d (%s, %d bytes)>",
format_str, image_layer_count(self->img.profile), self->img.width,
self->img.height, data_str,
image_data_size(self->img.profile, self->img.width, self->img.height));
}
STATIC void image_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest)
{
if(dest[0] == MP_OBJ_NULL) {
mp_obj_gintimage_t *self = MP_OBJ_TO_PTR(self_in);
if(attr == MP_QSTR_format)
dest[0] = MP_OBJ_NEW_SMALL_INT(self->img.profile);
else if(attr == MP_QSTR_width)
dest[0] = MP_OBJ_NEW_SMALL_INT(self->img.width);
else if(attr == MP_QSTR_height)
dest[0] = MP_OBJ_NEW_SMALL_INT(self->img.height);
else if(attr == MP_QSTR_data)
dest[0] = self->data;
}
else {
mp_raise_msg(&mp_type_AttributeError,
MP_ERROR_TEXT("gint.image doesn't support changing attributes"));
}
}
#endif /* FX9860G */
#ifdef FXCG50
/* gint.image(format, color_count, width, height, stride, data, palette)
Keyword labels are allowed but the order must remain the same. */
STATIC mp_obj_t image_make_new(const mp_obj_type_t *type, size_t n_args,
size_t n_kw, const mp_obj_t *args)
{
enum { ARG_format, ARG_color_count, ARG_width, ARG_height, ARG_stride,
ARG_data, ARG_palette };
static mp_arg_t const allowed_args[] = {
{ MP_QSTR_format, MP_ARG_INT | MP_ARG_REQUIRED,
{.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_color_count, MP_ARG_INT | MP_ARG_REQUIRED,
{.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_width, MP_ARG_INT | MP_ARG_REQUIRED,
{.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_height, MP_ARG_INT | MP_ARG_REQUIRED,
{.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_stride, MP_ARG_INT | MP_ARG_REQUIRED,
{.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_data, MP_ARG_OBJ | MP_ARG_REQUIRED,
{.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_palette, MP_ARG_OBJ,
{.u_rom_obj = MP_ROM_NONE} },
};
mp_arg_val_t vals[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args),
allowed_args, vals);
int format = vals[ARG_format].u_int;
int color_count = vals[ARG_color_count].u_int;
int width = vals[ARG_width].u_int;
int height = vals[ARG_height].u_int;
int stride = vals[ARG_stride].u_int;
mp_obj_t data = vals[ARG_data].u_obj;
mp_obj_t palette = vals[ARG_palette].u_obj;
return objgintimage_make(type, format, color_count, width, height, stride,
data, palette);
}
mp_obj_t objgintimage_make(const mp_obj_type_t *type, int format,
int color_count, int width, int height, int stride, mp_obj_t data,
mp_obj_t palette)
{
bool has_palette = palette != mp_const_none;
/* Type checks */
mp_buffer_info_t buf;
if(!mp_get_buffer(data, &buf, MP_BUFFER_READ))
mp_raise_TypeError("data must be a buffer object");
if(palette != mp_const_none && !mp_get_buffer(palette,&buf,MP_BUFFER_READ))
mp_raise_TypeError("palette must be None or a buffer object");
/* Size and bounds checks */
int data_len = MP_OBJ_SMALL_INT_VALUE(mp_obj_len(data));
int palette_len =
has_palette ? MP_OBJ_SMALL_INT_VALUE(mp_obj_len(palette)) : 0;
if(width <= 0 || height <= 0)
mp_raise_ValueError("image width/height must be >0");
if(format < 0 || format >= 7 || format == IMAGE_DEPRECATED_P8)
mp_raise_ValueError("invalid image format");
if(data_len < stride * height)
mp_raise_ValueError("data len() should be >= stride * height");
if(IMAGE_IS_RGB16(format) && (color_count > 0 || has_palette))
mp_raise_ValueError("RGB format should have 0 colors and no palette");
if(IMAGE_IS_P8(format) &&
(color_count < 1 || color_count > 256 || !has_palette))
mp_raise_ValueError("P8 format should have palette and 1..256 colors");
if(IMAGE_IS_P4(format) && (color_count != 16 || !has_palette))
mp_raise_ValueError("P4 format should have palette and 16 colors");
if(has_palette && palette_len < 2 * color_count)
mp_raise_ValueError("palette len() should be >= 2*color_count");
/* Construct image! */
mp_obj_gintimage_t *self = mp_obj_malloc(mp_obj_gintimage_t, type);
self->img.format = format;
self->img.color_count = color_count;
self->img.width = width;
self->img.height = height;
self->img.stride = stride;
self->img.data = NULL;
self->img.palette = NULL;
self->data = data;
self->palette = palette;
return MP_OBJ_FROM_PTR(self);
}
mp_obj_t objgintimage_make_from_gint_image(bopti_image_t const *img)
{
/* The original image is assumed to be valid. */
mp_obj_gintimage_t *self = mp_obj_malloc(mp_obj_gintimage_t,
&mp_type_gintimage);
memcpy(&self->img, img, sizeof *img);
int data_size = img->stride * img->height;
int typecode = 'B';
if(IMAGE_IS_RGB16(img->format)) {
// TODO: assert stride even
data_size >>= 1;
typecode = 'H';
}
self->data = ptr_to_memoryview(img->data, data_size, typecode,
(img->flags & IMAGE_FLAGS_DATA_ALLOC) != 0);
self->palette = ptr_to_memoryview((void *)img->palette,
img->color_count, 'H',
(img->flags & IMAGE_FLAGS_PALETTE_ALLOC) != 0);
return MP_OBJ_FROM_PTR(self);
}
STATIC char const *flag_string(int ro, int alloc)
{
static char const *flag_names[] = {
"rw", "ro", "alloc-rw", "alloc-ro",
};
return flag_names[!!ro + 2 * !!alloc];
}
STATIC void image_print(mp_print_t const *print, mp_obj_t self_in,
mp_print_kind_t kind)
{
(void)kind;
mp_obj_gintimage_t *self = MP_OBJ_TO_PTR(self_in);
int f = self->img.flags;
char const *data_str = self->data != mp_const_none ? "py" :
flag_string(f & IMAGE_FLAGS_DATA_RO, f & IMAGE_FLAGS_DATA_ALLOC);
char const *palette_str = self->palette != mp_const_none ? "py" :
flag_string(f & IMAGE_FLAGS_PALETTE_RO, f & IMAGE_FLAGS_PALETTE_ALLOC);
static char const * const fmt_names[] = {
"RGB565", "RGB565A", "LEGACY_P8",
"P4_RGB565A", "P8_RGB565", "P8_RGB565A", "P4_RGB565",
};
char const *format_str =
(self->img.format < 7) ? fmt_names[self->img.format] : "?";
mp_printf(print, "<%s image, %dx%d (%s)",
format_str, self->img.width, self->img.height, data_str);
if(IMAGE_IS_INDEXED(self->img.format))
mp_printf(print, ", %d colors (%s)",
self->img.color_count, palette_str);
mp_printf(print, ">");
}
STATIC void image_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest)
{
if(dest[0] == MP_OBJ_NULL) {
mp_obj_gintimage_t *self = MP_OBJ_TO_PTR(self_in);
if(attr == MP_QSTR_format)
dest[0] = MP_OBJ_NEW_SMALL_INT(self->img.format);
else if(attr == MP_QSTR_flags)
dest[0] = MP_OBJ_NEW_SMALL_INT(self->img.flags);
else if(attr == MP_QSTR_color_count)
dest[0] = MP_OBJ_NEW_SMALL_INT(self->img.color_count);
else if(attr == MP_QSTR_width)
dest[0] = MP_OBJ_NEW_SMALL_INT(self->img.width);
else if(attr == MP_QSTR_height)
dest[0] = MP_OBJ_NEW_SMALL_INT(self->img.height);
else if(attr == MP_QSTR_stride)
dest[0] = MP_OBJ_NEW_SMALL_INT(self->img.stride);
else if(attr == MP_QSTR_data)
dest[0] = self->data;
else if(attr == MP_QSTR_palette)
dest[0] = self->palette;
}
else {
mp_raise_msg(&mp_type_AttributeError,
MP_ERROR_TEXT("gint.image doesn't support changing attributes"));
}
}
#endif /* FXCG50 */
void objgintimage_get(mp_obj_t self_in, bopti_image_t *img)
{
if(!mp_obj_is_type(self_in, &mp_type_gintimage))
mp_raise_TypeError(MP_ERROR_TEXT("image must be a gint.image"));
mp_obj_gintimage_t *self = MP_OBJ_TO_PTR(self_in);
*img = self->img;
img->data = NULL;
if(self->data != mp_const_none) {
mp_buffer_info_t buf;
if(!mp_get_buffer(self->data, &buf, MP_BUFFER_READ))
mp_raise_TypeError("data not a buffer object?!");
img->data = buf.buf;
}
#ifdef FXCG50
img->palette = NULL;
if(self->palette != mp_const_none) {
mp_buffer_info_t buf;
if(!mp_get_buffer(self->palette, &buf, MP_BUFFER_READ))
mp_raise_TypeError("palette not a buffer object?!");
img->palette = buf.buf;
}
#endif
}
MP_DEFINE_CONST_OBJ_TYPE(
mp_type_gintimage,
MP_QSTR_image,
MP_TYPE_FLAG_NONE,
make_new, image_make_new,
print, image_print,
attr, image_attr
);

55
ports/sh/objgintimage.h Normal file
View File

@ -0,0 +1,55 @@
//---------------------------------------------------------------------------//
// ____ PythonExtra //
//.-'`_ o `;__, A community port of MicroPython for CASIO calculators. //
//.-'` `---` ' License: MIT (except some files; see LICENSE) //
//---------------------------------------------------------------------------//
// pe.objgintimage: Type of gint images for rendering and editing
#ifndef __PYTHONEXTRA_OBJGINTIMAGE_H
#define __PYTHONEXTRA_OBJGINTIMAGE_H
#include "py/obj.h"
#include <gint/display.h>
#ifdef FXCG50
#include <gint/image.h>
#endif
extern const mp_obj_type_t mp_type_gintimage;
/* A raw gint image with its pointers extracted into Python objects, allowing
manipulation through bytes() and bytearray() methods. The base image is
[img]. The members [data] and [palette] (which must be bytes, bytearray or
None) act as overrides for the corresponding fields of [img], which are
considered garbage/scratch and is constantly updated from the Python objects
before using the image.
Particular care should be given to not manipulating bytes and bytearrays in
ways that cause reallocation, especially when memory is scarce. */
typedef struct _mp_obj_gintimage_t {
mp_obj_base_t base;
bopti_image_t img;
mp_obj_t data;
#ifdef FXCG50
mp_obj_t palette;
#endif
} mp_obj_gintimage_t;
/* Project a gint image object into a standard bopti image structure for use in
C-API image functions. */
void objgintimage_get(mp_obj_t self_in, bopti_image_t *img);
/* Build a gint image object from a valid bopti image structure. */
mp_obj_t objgintimage_make_from_gint_image(bopti_image_t const *img);
/* Lower-level image object constructor. */
#if defined(FX9860G)
mp_obj_t objgintimage_make(const mp_obj_type_t *type, int profile, int width,
int height, mp_obj_t data);
#elif defined(FXCG50)
mp_obj_t objgintimage_make(const mp_obj_type_t *type, int format,
int color_count, int width, int height, int stride, mp_obj_t data,
mp_obj_t palette);
#endif
#endif /* __PYTHONEXTRA_OBJGINTIMAGE_H */