Avec Python, tracer des caractères chinois créés en SVG

Je n'ai pas tout compris , mais ça marche

a marqué ce sujet comme résolu.

Bonjour à toutes et à tous ! :)

Explication liminaire

J’ai écrit du code Python pour tracer les traits des caractères chinois.
J’utilise pour cela le fichier "graphics.txt" légèrement modifié.
Ce fichier contient, pour chaque caractère,
la description du ou des traits qui composent ce caractère.
Cette description est un path au sens de svg.

Je peux ensuite générer la code SVG qui va bien.
Pour cela, il vaut mieux avoir lu les explications où il y a un paragraphe graphics.txt keys:

Ensuite, j’utilise du code miraculeux PyGame (pompé sur le web) qui peut afficher ce que je veux.

Mais je n’ai pas tout compris le code PyGame

Après avoir généré la chaine de caractère qui décrit le SVG (elle s’appelle svg_string),
le code Pygame utilise une ligne magique :magicien:
pygame_surface = pygame.image.load(io.BytesIO(svg_string.encode())
Voila ce que je crois comprendre :
svg_string est de type class = string.
svg_string.encode() est de type class = bytes.
OK.

Mais par quel miracle io.BytesIO génère une image ?

(je le soupçonne de générer un PNG)

Je n’ai pas vu cela dans la documentation.

Je vous remercie par avance pour vos explications éventuelles et vos remarques bienveillantes :)

Mon code pas beau du tout

import json

"""
graphics.txt is derived from:
    Arphic PL KaitiM GB - https://apps.ubuntu.com/cat/applications/precise/fonts-arphic-gkai00mp/
    Arphic PL UKai - https://apps.ubuntu.com/cat/applications/fonts-arphic-ukai/

You can redistribute and/or modify graphics.txt under the terms of the Arphic
Public License as published by Arphic Technology Co., Ltd. You should have
received a copy of this license (the directory "APL") along with graphics.txt;
if not, see <http://ftp.gnu.org/non-gnu/chinese-fonts-truetype/LICENSE>.
"""

with open('graphics.txt', encoding = 'utf-8') as my_file:
    data = json.load(my_file)

hanzi = input("which character ? ")


if len(hanzi) != 1:
    print("Wrong entry. Use '一' instead")
    hanzi = "一"

notfound = True
for character in data:
    if character["character"] == hanzi:
        
        strokes = character["strokes"]
        notfound = False

if notfound:
    hanzi = "一"
    print("Sory, character ", hanzi, " not found !")
    print("Use 一 instead !")
    strokes = ['M 518 382 Q 572 385 623 389 Q 758 399 900 383 Q 928 379 935 390 Q 944 405 930 419 Q 896 452 845 475 Q 829 482 798 473 Q 723 460 480 434 Q 180 409 137 408 Q 130 408 124 408 Q 108 408 106 395 Q 105 380 127 363 Q 146 348 183 334 Q 195 330 216 338 Q 232 344 306 354 Q 400 373 518 382 Z']

    

import pygame
import io

pygame.init()
window = pygame.display.set_mode((1024, 1024))
clock = pygame.time.Clock()


svg_string = '<svg height="1024" width="1024"><g transform="scale(1, -1) translate(0, -900)"><path d="'

for stroke in strokes:
    svg_string = svg_string +  stroke

svg_string = svg_string + '" style = "stroke: #000000; fill : none"> </g></svg>'
# <g transform ... according to explanation concerning graphics.txt
# " style = "stroke: #000000; fill : none" to have black lines without any fill. By default, fill will be black.

print('See the content of svg_string : ')
print(svg_string)
print ('See the type of svg_string and svg_string.encode : ')
print(type(svg_string), type(svg_string.encode()))

pygame_surface = pygame.image.load(io.BytesIO(svg_string.encode()))


run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    window.fill((255, 255, 255)) #window initialy black filled
    window.blit(pygame_surface, pygame_surface.get_rect(center = window.get_rect().center))
    pygame.display.flip()

pygame.quit()
exit()

Pour le faire marcher, faut un fichier graphics.txt minimal, avec par exemple deux caractères :

[
{"character":"中","strokes":["M 254 596 Q 244 603 202 608 Q 189 611 185 605 Q 178 598 189 580 Q 228 507 254 384 Q 260 347 282 324 Q 303 300 307 318 Q 310 328 310 343 L 306 381 Q 305 390 303 399 Q 276 513 267 556 C 261 585 259 594 254 596 Z","M 752 425 Q 765 462 777 498 Q 793 546 828 578 Q 853 603 821 624 Q 793 648 762 666 Q 743 679 717 668 Q 677 655 615 646 Q 575 642 535 636 L 476 628 Q 427 622 380 613 Q 307 603 254 596 C 224 592 237 551 267 556 Q 274 556 286 559 Q 400 587 476 593 L 534 600 Q 604 609 698 615 Q 717 616 724 607 Q 739 583 693 435 C 684 406 742 397 752 425 Z","M 530 370 Q 630 380 764 386 Q 776 387 778 397 Q 779 406 752 425 C 730 441 723 440 693 435 Q 606 420 530 407 L 475 399 Q 385 389 306 381 C 276 378 280 341 310 343 Q 314 343 321 343 Q 382 356 475 365 L 530 370 Z","M 475 365 Q 472 17 485 -11 Q 486 -12 488 -16 Q 503 -32 512 -10 Q 530 32 530 370 L 530 407 Q 531 519 534 600 L 535 636 Q 538 742 559 778 Q 566 794 545 811 Q 520 830 475 843 Q 454 847 441 833 Q 431 826 444 812 Q 472 787 475 753 Q 475 704 476 628 L 476 593 Q 476 517 476 419 Q 475 409 475 399 L 475 365 Z"],"medians":[[[194,598],[229,572],[236,553],[295,323]],[[262,591],[281,579],[471,611],[719,641],[741,638],[777,596],[732,460],[702,446]],[[316,350],[330,365],[689,409],[749,405],[768,398]],[[449,823],[476,817],[513,781],[505,656],[499,-10]]]},
{"character":"国","strokes":["M 282 720 Q 270 741 233 755 Q 221 762 208 757 Q 198 751 205 736 Q 229 648 231 611 Q 230 608 231 602 Q 238 308 189 137 Q 170 68 211 19 Q 223 3 236 17 Q 246 30 250 44 L 255 86 Q 259 123 261 178 Q 280 626 285 676 C 287 706 288 714 282 720 Z","M 751 76 Q 772 55 799 4 Q 809 -20 825 -18 Q 838 -17 847 5 Q 877 62 852 247 Q 822 661 841 685 Q 844 692 849 697 Q 861 712 860 720 Q 859 733 825 756 Q 767 795 724 781 Q 703 777 668 770 Q 517 754 335 723 Q 307 720 282 720 C 252 719 256 670 285 676 Q 297 676 314 682 Q 327 686 450 706 Q 601 730 718 737 Q 743 738 749 726 Q 774 701 782 401 Q 789 220 792 177 Q 796 117 780 98 C 771 69 735 96 751 76 Z","M 514 554 Q 664 588 667 590 Q 674 597 671 605 Q 664 615 638 623 Q 614 627 516 598 Q 438 580 376 574 Q 343 568 367 553 Q 400 532 459 544 Q 463 545 470 545 L 514 554 Z","M 530 382 Q 545 386 562 388 Q 602 392 634 396 Q 644 395 653 407 Q 654 417 632 428 Q 607 447 531 427 L 483 416 Q 450 410 422 406 Q 394 403 364 399 Q 337 396 358 379 Q 392 355 416 362 Q 447 369 483 374 L 530 382 Z","M 525 254 Q 526 323 530 382 L 531 427 Q 534 533 535 534 Q 523 546 514 554 C 492 574 459 573 470 545 Q 482 527 483 416 L 483 374 Q 483 323 483 246 C 483 216 525 224 525 254 Z","M 483 246 Q 407 233 319 218 Q 300 215 315 199 Q 328 186 346 182 Q 367 178 382 182 Q 499 215 672 217 Q 687 216 701 217 Q 720 217 724 226 Q 728 238 713 250 Q 656 290 590 268 Q 562 262 525 254 L 483 246 Z","M 672 345 Q 697 324 722 299 Q 732 289 745 290 Q 754 290 757 301 Q 761 311 754 335 Q 750 351 724 364 Q 667 388 652 383 Q 648 379 649 369 Q 652 362 672 345 Z","M 250 44 Q 256 44 264 45 Q 420 70 751 76 C 781 77 799 77 780 98 Q 773 108 753 124 Q 734 137 697 131 Q 451 94 255 86 C 225 85 220 41 250 44 Z"],"medians":[[[216,745],[248,707],[256,623],[246,321],[219,96],[224,25]],[[291,684],[303,700],[346,708],[722,759],[764,754],[798,723],[804,714],[803,582],[828,151],[825,102],[815,68],[823,1]],[[369,565],[390,559],[430,560],[524,576],[618,601],[663,599]],[[359,390],[407,382],[581,414],[643,409]],[[478,546],[506,521],[509,442],[505,277],[489,254]],[[318,208],[373,204],[632,246],[668,244],[713,231]],[[657,375],[719,338],[745,302]],[[257,50],[273,67],[320,72],[709,104],[772,98]]]}
]
+0 -0

Après avoir généré la chaine de caractère qui décrit le SVG (elle s’appelle svg_string),
le code Pygame utilise une ligne magique :magicien:
pygame_surface = pygame.image.load(io.BytesIO(svg_string.encode())
Voila ce que je crois comprendre :
svg_string est de type class = string.
svg_string.encode() est de class = bytes.
OK.

Mais par quel miracle io.BytesIO génère une image ? (je le soupçonne de générer un PNG)

Je n’ai pas vu cela dans la documentation.

etherpin

En fait, pygame.image.load prend normalement un fichier binaire en paramètre (ouvert avec open("nom-du-fichier", "rb").

Le type io.BytesIO est simplement un enrobage autour du type bytes pour lui donner l’apparence d’un fichier binaire.

+0 -0

Salut,

C’est bizarre comme façon de procéder, tout de même. Transformer des glyphes en leur représentation graphique, c’est le boulot des polices d’écriture (ou typeface en anglais, ou abusivement font). J’ai l’impression que tu essaies de recréer ce que font déjà très bien les polices dans ton coin…


Par ailleurs, toute la première partie avant l’import de pygame :

import json

with open('graphics.txt', encoding = 'utf-8') as my_file:
    data = json.load(my_file)

hanzi = input("which character ? ")


if len(hanzi) != 1:
    print("Wrong entry. Use '一' instead")
    hanzi = "一"

notfound = True
for character in data:
    if character["character"] == hanzi:
        
        strokes = character["strokes"]
        notfound = False

if notfound:
    hanzi = "一"
    print("Sory, character ", hanzi, " not found !")
    print("Use 一 instead !")
    strokes = ['M 518 382 Q 572 385 623 389 Q 758 399 900 383 Q 928 379 935 390 Q 944 405 930 419 Q 896 452 845 475 Q 829 482 798 473 Q 723 460 480 434 Q 180 409 137 408 Q 130 408 124 408 Q 108 408 106 395 Q 105 380 127 363 Q 146 348 183 334 Q 195 330 216 338 Q 232 344 306 354 Q 400 373 518 382 Z']

peut être extrêmement simplifiée si tu construis un mapping entre les caractères et la séquence de "strokes" :

import json

DEFAULT = '一', [
    'M 518 382 Q 572 385 623 389 Q 758 399 900 383 Q 928 379 935 390 Q 944'
    ' 405 930 419 Q 896 452 845 475 Q 829 482 798 473 Q 723 460 480 434 Q '
    '180 409 137 408 Q 130 408 124 408 Q 108 408 106 395 Q 105 380 127 363'
    ' Q 146 348 183 334 Q 195 330 216 338 Q 232 344 306 354 Q 400 373 518 '
    '382 Z'
]

with open('graphics.txt', encoding='utf-8') as my_file:
    data = json.load(my_file)
hanzi_to_strokes = {char["character"]: char["strokes"] for char in data}

hanzi = input("which character? ")

if hanzi not in hanzi_to_strokes:
    print(f"Sorry, '{hanzi}' not found. Using '{DEFAULT[0]}' instead.")

strokes = hanzi_to_strokes.get(hanzi, DEFAULT[1])
+0 -0

Après avoir généré la chaine de caractère qui décrit le SVG (elle s’appelle svg_string),
le code Pygame utilise une ligne magique :magicien:
pygame_surface = pygame.image.load(io.BytesIO(svg_string.encode())
Voila ce que je crois comprendre :
svg_string est de type class = string.
svg_string.encode() est de class = bytes.
OK.

Mais par quel miracle io.BytesIO génère une image ? (je le soupçonne de générer un PNG)

Je n’ai pas vu cela dans la documentation.

etherpin

En fait, pygame.image.load prend normalement un fichier binaire en paramètre (ouvert avec open("nom-du-fichier", "rb").

Le type io.BytesIO est simplement un enrobage autour du type bytes pour lui donner l’apparence d’un fichier binaire.

amael

Oui, c’est logique. L’explication est du côté de PyGame.

+0 -0

Salut,

C’est bizarre comme façon de procéder, tout de même. Transformer des glyphes en leur représentation graphique, c’est le boulot des polices d’écriture (ou typeface en anglais, ou abusivement font). J’ai l’impression que tu essaies de recréer ce que font déjà très bien les polices dans ton coin…

En fait, il s’agit ici de mettre en évidence les constituants graphiques de caractères chinois, et non pas d’afficher ces caractères. Si tu entre 中 ou 国 dans le code, tu verras ces constituants affichés par PyGame. Avec le caractère par défaut, ce n’est pas très intéressant car il n’y a qu’un seul constituant.


Par ailleurs, toute la première partie avant l’import de pygame :

import json

with open('graphics.txt', encoding = 'utf-8') as my_file:
    data = json.load(my_file)

hanzi = input("which character ? ")


if len(hanzi) != 1:
    print("Wrong entry. Use '一' instead")
    hanzi = "一"

notfound = True
for character in data:
    if character["character"] == hanzi:
        
        strokes = character["strokes"]
        notfound = False

if notfound:
    hanzi = "一"
    print("Sory, character ", hanzi, " not found !")
    print("Use 一 instead !")
    strokes = ['M 518 382 Q 572 385 623 389 Q 758 399 900 383 Q 928 379 935 390 Q 944 405 930 419 Q 896 452 845 475 Q 829 482 798 473 Q 723 460 480 434 Q 180 409 137 408 Q 130 408 124 408 Q 108 408 106 395 Q 105 380 127 363 Q 146 348 183 334 Q 195 330 216 338 Q 232 344 306 354 Q 400 373 518 382 Z']

peut être extrêmement simplifiée si tu construis un mapping entre les caractères et la séquence de "strokes" :

import json

DEFAULT = '一', [
    'M 518 382 Q 572 385 623 389 Q 758 399 900 383 Q 928 379 935 390 Q 944'
    ' 405 930 419 Q 896 452 845 475 Q 829 482 798 473 Q 723 460 480 434 Q '
    '180 409 137 408 Q 130 408 124 408 Q 108 408 106 395 Q 105 380 127 363'
    ' Q 146 348 183 334 Q 195 330 216 338 Q 232 344 306 354 Q 400 373 518 '
    '382 Z'
]

with open('graphics.txt', encoding='utf-8') as my_file:
    data = json.load(my_file)
hanzi_to_strokes = {char["character"]: char["strokes"] for char in data}

hanzi = input("which character? ")

if hanzi not in hanzi_to_strokes:
    print(f"Sorry, '{hanzi}' not found. Using '{DEFAULT[0]}' instead.")

strokes = hanzi_to_strokes.get(hanzi, DEFAULT[1])

adri1

Merci pour cette amélioration. La ligne :
hanzi_to_strokes = {char["character"]: char["strokes"] for char in data} n’est pas très facile à comprendre du premier coup d’œil !

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte