Propojení Pythonu s nativními knihovnami s využitím balíčku ctypes

27. 2. 2024
Doba čtení: 54 minut

Sdílet

Již jsme se setkali s propojením Pythonu s nativními knihovnami naprogramovanými (například) v C. Použili jsme přitom balíček cffi. Ovšem někdy může být užitečné využít namísto toho standardní balíček ctypes.

Obsah

1. Propojení Pythonu s nativními knihovnami s využitím balíčku ctypes

2. Řešený problém

3. Základní informace o knihovně Pygame

4. Vykreslení jednotlivých pixelů v rastrovém obrázku typu Image v Pythonu

5. Limity předchozího řešení

6. Realizace nativní funkce, která vyplní obrázek jednoduchým testovacím vzorkem

7. Překlad nativní funkce do dynamicky linkované knihovny

8. Zavolání nativní funkce z Pythonu

9. Předání barvové palety do nativní funkce

10. Přednosti propojení Pythonu s jazykem C

11. Výpočet Mandelbrotovy a Juliovy množiny implementovaný v jazyce C

12. Zavolání nativních funkci pro výpočet Mandelbrotovy a Juliovy množiny z Pythonu

13. Mandelbrotova množina jako „mapa“ všech Juliových množin

14. Interaktivní určení konstanty c v Mandelbrotově množině

15. Výsledná podoba demonstračního příkladu

16. Buffer sdílený oběma rastrovými obrázky

17. Oprava předchozího postupu: použití dvou bufferů bez kopie pixelů

18. Přístup k pixelům obrázku po 32bitových slovech

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Propojení Pythonu s nativními knihovnami s využitím balíčku ctypes

Na stránkách Roota, konkrétně v článcích [1] [2] a [3], jsme se již zabývali problematikou propojení vysokoúrovňového programovacího jazyka Python s nativními knihovnami naprogramovanými například v jazyku C (ovšem může se jednat i o Rust, C++ a pokud si dáme pozor, tak i Go). Poměrně jednoduchá je situace, kdy se pouze z Pythonu volají nativní funkce, kterým se předávají již naalokované objekty. Pro tento účel jsme v předchozích článcích využili především knihovnu cffi (viz též Overview). Dnes si naproti tomu ukážeme použití standardního balíčku ctypes (viz též dokumentaci).

Základní postup přitom zůstává stále stejný. Zdrojový kód napsaný v jazyku C (C++, Rustu, Go, …) je nejprve přeložen do nativní dynamické knihovny, tedy konkrétně do souboru s koncovkou „.so“ na Linuxu a „.dll“ v systému Microsoft Windows. Aplikace psaná v Pythonu tuto dynamickou knihovnu načte a přes balíček ctypes umožní volání funkcí naprogramovaných v C/C++ atd. Zpočátku se může zdát, že se jedná o bezproblémové řešení, ovšem v praxi musíme vyřešit především dva problémy: vlastnictví předávaných hodnot (tedy která strana alokuje paměť a která ji může dealokovat) a taktéž korektní předání hodnot různých typů. První problém musí vyřešit programátor, ovšem druhý problém může – i když pouze částečně – řešit i balíček realizující volání nativních funkcí z Pythonu. A právě zde nalezneme největší rozdíly mezi ctypes, cffi i dalšími balíčky určenými pro stejný účel. V tomto ohledu jsou možnosti ctypes dosti omezené, ovšem jedná se o standardní balíček, který navíc může pro některé účely plně vyhovovat.

Poznámka: název ctypes a vlastně i cffi evokuje jazyk C. Ovšem jak již bylo napsáno výše, lze nativní část vytvořit i v jiných jazycích; pouze se v takovém případě dříve či později setkáme s obtížemi, které v C nenastávají (práce s objekty, správce paměti v nativní knihovně atd.).

Obrázek 1: Programovací jazyk C je prozatím ve své nice prakticky nenahraditelný, i když už vzniklo několik jeho nástupců (Zig, D, částečně Rust).

2. Řešený problém

V rámci navazujících kapitol se pokusíme o vytvoření jednoduché aplikace, která zobrazí Mandelbrotovu množinu a umožní uživateli interaktivní výběr souřadnice v této množině (což je komplexní číslo označované jako C nebo malé c), které bude ihned využito při vykreslení Juliovy množiny:

Obrázek 2: Výsledná aplikace. Uživatel může v Mandelbrotově množině (levá část) vybrat hodnotu komplexního čísla C. To se ihned projeví v pravé části na tvaru Juliovy množiny.

Obrázek 3: Juliova množina pro odlišnou hodnotu C (reálná část je nulová).

Obrázek 4: Juliova množina pro odlišnou hodnotu C.

Obrázek 5: Juliova množina pro odlišnou hodnotu C (imaginární část je nulová).

Samotné (minimalisticky pojaté) grafické uživatelské prostředí aplikace bude naprogramováno s využitím populární knihovny Pygame, výpočet Mandelbrotovy množiny a Juliovy množiny bude realizován v ANSI C a pro jednoduchost nebude algoritmus nijak optimalizován (výsledný nativní kód však je možné optimalizovat překladačem céčka, postačuje jen nastavit příslušné přepínače).

3. Základní informace o knihovně Pygame

Pygame je knihovna určená pro programovací jazyk Python, která interně volá funkce nativní knihovny SDL a několika dalších podpůrných knihoven. Myšlenka, na níž je Pygame postavena, je v mnoha ohledech podobná myšlence výše systému LÖVE (kde se ovšem používá jazyk Lua) – implementace nízkoúrovňových operací nechť je vytvořena odborníky v programovacích jazycích C a C++; pokud budou tyto operace implementovány dostatečně rychle, je již možné zbytek hry naprogramovat ve vysokoúrovňovém jazyku Python. A ukazuje se, že je tato myšlenka – a obecně systém rozdělení aplikace mezi dva programovací jazyky (kompilovaný a skriptovací) – poměrně úspěšná, neboť v Pygame již vzniklo mnoho zajímavých her i dalších aplikací.

Obrázek 6: Logo knihovny Pygame.

Pro naše účely využijeme jen několik typů objektů knihovny Pygame. Jedná se o:

  • display: objekt představující plochu okna, do něhož se provádí vykreslování celé scény
  • event: událost, která vznikne například při stisku tlačítka myši, pohybem myši, stiskem klávesy, zavřením okna atd.
  • image: objekt s rastrovým obrázkem (s paletou či plnobarevným)
  • clock: hodiny, které nám umožní například zajistit maximální frekvenci obnovování obsahu okna atd.

4. Vykreslení jednotlivých pixelů v rastrovém obrázku typu Image v Pythonu

S využitím knihovny Pygame nyní vytvoříme jednoduchou aplikaci, která po svém spuštění otevře okno se zadanými rozměry a vykreslí do něj dvojici rastrových obrázků s různými barvovými vzorky. První funkce otevře nové okno, vyplní ho zadanou barvou pozadí a vytvoří objekt typu „hodiny“ (ten použijeme v pozdějších verzích příkladu):

def initialize_ui(title, width, height):
    """Initialize Pygame display, drawing surface, and clocks."""
    # set window title
    pygame.display.set_caption(title)
 
    # initialize window
    display = pygame.display.set_mode([width, height])
    display.fill((0, 0, 0))
 
    clock = pygame.time.Clock()
 
    return display, clock

Druhá funkce je již komplikovanější. Jedná se o realizaci programové smyčky reagující na události čtené z fronty. Pokud se uživatel pokusí zavřít okno aplikace, dojde k vytvoření události typu QUIT. A pokud stiskne klávesu Escape, bude se jednat o událost typu KEYDOWN, přičemž bude nastavený atribut key na kód klávesy Escape. Po zpracování všech událostí vykreslíme na displej dvojici rastrových obrázků operací display.blit, necháme překreslit displej (resp. obsah okna) a počkáme určitý časový interval:

def event_loop(display, image1, image2, clock):
    while True:
        for event in pygame.event.get():
            if event.type == pygame.locals.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.locals.KEYDOWN:
                if event.key == pygame.locals.K_ESCAPE:
                    pygame.quit()
                    sys.exit()
 
        # all events has been processed - update scene and redraw the screen
        display.blit(image1, (30, 20))
        display.blit(image2, (60 + image1.get_width(), 20))
 
        # and update the whole display
        pygame.display.update()
        clock.tick(25)

Dále je v programu realizována funkce, která do předaného obrázku (typu Image) vykreslí testovací vzorek. Povšimněte si, že musíme vypočítat hodnotu barvy ze všech tří barvových složek RGB (posunem složek o 8 a 16 bitů) a že se hodnota pixelu nastavuje metodou Image.set_at:

def render_test_rgb_image(image, green):
    width, height = image.get_size()
    for y in range(height):
        for x in range(width):
            color = (x<<16) + (green<<8) + y
            image.set_at((x, y), color)

A nakonec následuje inicializace okna, obrázků a vstup do těla smyčky pro zpracování událostí:

def main():
    display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT)
    image1 = pygame.Surface([IMAGE_WIDTH, IMAGE_HEIGHT])
    image2 = pygame.Surface([IMAGE_WIDTH, IMAGE_HEIGHT])
 
    render_test_rgb_image(image1, 0)
    render_test_rgb_image(image2, 255)
 
    event_loop(display, image1, image2, clock)
 
 
if __name__ == "__main__":
    main()

Úplný zdrojový kód bude vypadat následovně:

import sys
 
import pygame
import pygame.locals
 
TITLE = "Raster image"
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 300
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
 
 
def initialize_ui(title, width, height):
    """Initialize Pygame display, drawing surface, and clocks."""
    # set window title
    pygame.display.set_caption(title)
 
    # initialize window
    display = pygame.display.set_mode([width, height])
    display.fill((0, 0, 0))
 
    clock = pygame.time.Clock()
 
    return display, clock
 
 
def event_loop(display, image1, image2, clock):
    while True:
        for event in pygame.event.get():
            if event.type == pygame.locals.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.locals.KEYDOWN:
                if event.key == pygame.locals.K_ESCAPE:
                    pygame.quit()
                    sys.exit()
 
        # all events has been processed - update scene and redraw the screen
        display.blit(image1, (30, 20))
        display.blit(image2, (60 + image1.get_width(), 20))
 
        # and update the whole display
        pygame.display.update()
        clock.tick(25)
 
 
def render_test_rgb_image(image, green):
    width, height = image.get_size()
    for y in range(height):
        for x in range(width):
            color = (x<<16) + (green<<8) + y
            image.set_at((x, y), color)
 
 
def main():
    display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT)
    image1 = pygame.Surface([IMAGE_WIDTH, IMAGE_HEIGHT])
    image2 = pygame.Surface([IMAGE_WIDTH, IMAGE_HEIGHT])
 
    render_test_rgb_image(image1, 0)
    render_test_rgb_image(image2, 255)
 
    event_loop(display, image1, image2, clock)
 
 
if __name__ == "__main__":
    main()
 
 
# finito

Po spuštění této funkce by se mělo na desktopu zobrazit nové okno s následujícím obsahem:

Obrázek 7: Okno s dvojicí testovacích rastrových obrázků.

5. Limity předchozího řešení

Dnešní první demonstrační příklad, který byl popsán v předchozí kapitole, je sice plně funkční, ovšem má jednu vadu – samotné vykreslování (resp. přesněji řečeno obarvování) pixelů přímo v Pythonu metodou set_at je velmi pomalé. Je tomu tak z toho důvodu, že je neustále nutné počítat adresu pixelu (i když obrázek vykreslujeme postupně po pixelech a potom po řádcích) a taktéž samotný výpočet barvy je pomalejší. A navíc je většinou (interně) nutné pole s rastrovými daty (tedy s jednotlivými pixely) zamykat. Pro naše relativně malé obrázky je sice vykreslení na moderním HW dostatečně rychlé, ale ukážeme si řešení založené na nativní funkci naprogramované v céčku. Toto řešení má výhodu v tom, že je velmi snadno rozšiřitelné i pro účely výpočtu Mandelbrotovy množiny a Juliovy množiny (množin).

6. Realizace nativní funkce, která vyplní obrázek jednoduchým testovacím vzorkem

Nyní se podívejme na to, jakým způsobem by mohla být vytvořena funkce naprogramovaná v jazyku C, které se předá ukazatel na data rastrového obrázku společně s rozměry tohoto obrázku. Funkce následně obrázek vyplní testovacím vzorkem. Budeme předpokládat, že každý pixel obrázku je uložen ve čtyřech po sobě jdoucích bajtech (viz předchozí kapitolu) a že mezi jednotlivými obrazovými řádky nejsou žádné vyplňovací bajty (což by souviselo s hodnotou stride). Za těchto podmínek je vyplnění rastrového obrázku RGB vzorkem velmi jednoduché, což ostatně můžeme vidět i z realizace této funkce. Povšimněte si především toho, že pixel je skutečně uložen ve čtyřech bajtech, jeden bajt však není použit (je při zápisu zcela přeskočen):

void render_test_rgb_image(unsigned int width, unsigned int height,
                           unsigned char *pixels, unsigned char green) {
    unsigned int i, j;
    unsigned char *p = pixels;
 
    for (j = 0; j < height; j++) {
        for (i = 0; i < width; i++) {
            *p++ = i;
            *p++ = green;
            *p++ = j;
            p++;
        }
    }
}

7. Překlad nativní funkce do dynamicky linkované knihovny

Zdrojový kód s touto funkcí musíme přeložit tak, aby vznikla dynamicky linkovaná knihovna (.so, .dll). Pro operační systém Linux bude příslušný Makefile soubor realizující překlad vypadat v té nejjednodušší podobě následovně:

.PHONY: all clean
 
CC=gcc
CFLAGS=-Wall -pedantic -ansi
 
all: renderer.so
 
clean:
        rm -f renderer.so
 
renderer.so:    renderer.c
        $(CC) $(CFLAGS) -shared -Wl,-soname,renderer -o $@ -fPIC $<

Překlad s následným vytvořením dynamicky linkované knihovny provedeme příkazem:

$ make

Překladem by měl vzniknout soubor renderer.so, v němž mj. najdeme i naši funkci render_test_rgb_image:

$ nm renderer.so 
 
0000000000004008 b completed.0
                 w __cxa_finalize@GLIBC_2.2.5
0000000000001040 t deregister_tm_clones
00000000000010b0 t __do_global_dtors_aux
0000000000003de8 d __do_global_dtors_aux_fini_array_entry
0000000000003df0 d __dso_handle
0000000000003df8 d _DYNAMIC
000000000000117c t _fini
00000000000010f0 t frame_dummy
0000000000003de0 d __frame_dummy_init_array_entry
0000000000002080 r __FRAME_END__
0000000000003fe8 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000002000 r __GNU_EH_FRAME_HDR
0000000000001000 t _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000001070 t register_tm_clones
00000000000010f9 T render_test_rgb_image
0000000000004008 d __TMC_END__

8. Zavolání nativní funkce z Pythonu

Otestujme si nyní, jak lze nativní funkci z předchozí kapitoly zavolat z Pythonu. Nejdříve musíme načíst dynamicky linkovanou knihovnu, což zajistí tyto řádky:

from ctypes import CDLL
 
renderer = CDLL("./renderer.so")

Následně si necháme (přímo v Pythonu) naalokovat buffer o takové velikosti, aby do něj bylo možné uložit všechny pixely obrázku. Každý pixel je uložen ve čtyřech bajtech, takže velikost bude rovna 4 * IMAGE_WIDTH * IMAGE_HEIGHT. Pro alokaci bufferu použijeme funkci create_string_buffer, která však (i přes své jméno) akceptuje velikost v bajtech:

from ctypes import create_string_buffer
 
buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT)

Nyní již můžeme naši nativní funkci zavolat a předat jí rozměry obrázku i právě naalokovaný buffer:

from ctypes import c_int
 
renderer.render_test_rgb_image(c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), buffer, 0)
Poznámka: povšimněte si, jak se předají celočíselné parametry přes pomocnou funkci c_int.

Nakonec musíme hodnoty pixelů z bufferu převést na obrázek, k čemuž slouží funkce pygame.image.frombytes. Ta kromě bufferu s hodnotami pixelů potřebuje znát i velikost obrázku (předává se formou dvojice) a jeho formát (pro naše účely RGBX):

def image_from_buffer(buffer, width, height, fmt):
    return pygame.image.frombytes(bytes(buffer), (width, height), fmt)
 
image1 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX")

Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:

import sys
from ctypes import CDLL, c_int, create_string_buffer
 
import pygame
import pygame.locals
 
TITLE = "Renderer"
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 300
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
 
 
def initialize_ui(title, width, height):
    """Initialize Pygame display, drawing surface, and clocks."""
    # set window title
    pygame.display.set_caption(title)
 
    # initialize window
    display = pygame.display.set_mode([width, height])
    display.fill((0, 0, 0))
 
    clock = pygame.time.Clock()
 
    return display, clock
 
 
def event_loop(display, image1, image2, clock):
    while True:
        for event in pygame.event.get():
            if event.type == pygame.locals.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.locals.KEYDOWN:
                if event.key == pygame.locals.K_ESCAPE:
                    pygame.quit()
                    sys.exit()
 
        # all events has been processed - update scene and redraw the screen
        display.blit(image1, (30, 20))
        display.blit(image2, (60 + image1.get_width(), 20))
 
        # and update the whole display
        pygame.display.update()
        clock.tick(25)
 
 
def image_from_buffer(buffer, width, height, fmt):
    return pygame.image.frombytes(bytes(buffer), (width, height), fmt)
 
 
def main():
    display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT)
 
    # try to load dynamically linked library
    renderer = CDLL("./renderer.so")
 
    # create buffer for raster image
    buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT)
 
    renderer.render_test_rgb_image(c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), buffer, 0)
    image1 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX")
 
    renderer.render_test_rgb_image(c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), buffer, 255)
    image2 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX")
 
    event_loop(display, image1, image2, clock)
 
 
if __name__ == "__main__":
    main()
 
 
# finito

9. Předání barvové palety do nativní funkce

V dalším kroku se pokusíme o předání barvové palety do nativní funkce. Barvová paleta bude obsahovat 256 barev, přičemž každá barva je reprezentována třemi bajty – každý bajt totiž obsahuje hodnotu jedné barvové složky red, green nebo blue. Celkem je tedy nutné do nativní funkce předat 256×3 = 768 bajtů. Ovšem vzhledem k tomu, že barvová paleta nebude ve funkci měněna, můžeme ji předat ukazatelem, což znamená, že namísto 768 bajtů se předají čtyři bajty nebo osm bajtů (podle šířky ukazatele).

Připomeňme si, že původní nativní funkce určená pro vykreslení testovacího vzorku do rastrového obrázku vypadala následovně:

void render_test_rgb_image(unsigned int width, unsigned int height,
                           unsigned char *pixels, unsigned char green) {
    unsigned int i, j;
    unsigned char *p = pixels;
 
    for (j = 0; j < height; j++) {
        for (i = 0; i < width; i++) {
            *p++ = i;
            *p++ = green;
            *p++ = j;
            p++;
        }
    }
}

Tato funkce nepoužívala barvovou paletu pro přepočet nějaké celočíselné hodnoty na barvu z palety. Provedeme tedy následující úpravy:

  1. Funkce bude nově akceptovat ukazatel na první barvu v barvové paletě
  2. Namísto přímého výpočtu složek R, G, B použijeme barvovou paletu ve funkci LUT (look-up table)

Pro jednoduchost odvodíme index barvy pixelu pouze z jeho x-ové složky. Výsledná funkce bude vypadat následovně:

void render_test_palette_image(unsigned int width, unsigned int height,
                               const unsigned char *palette,
                               unsigned char *pixels) {
    unsigned int i, j;
    unsigned char *p = pixels;
 
    for (j = 0; j < height; j++) {
        for (i = 0; i < width; i++) {
            int color = i * 3;
            *p++ = palette[color];
            *p++ = palette[color + 1];
            *p++ = palette[color + 2];
            p++;
        }
    }
}

Výsledkem by měl obrázek z pravé části screenshotu:

Obrázek 8: Pravý obrázek je vykreslen s využitím barvové palety.

Upravená Pythonní část, která bude volat výše uvedenou funkci, bude vypadat následovně:

import sys
from ctypes import CDLL, c_int, create_string_buffer
 
from palette_mandmap import palette
 
import pygame
import pygame.locals
 
TITLE = "Renderer"
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 300
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
 
 
def initialize_ui(title, width, height):
    """Initialize Pygame display, drawing surface, and clocks."""
    # set window title
    pygame.display.set_caption(title)
 
    # initialize window
    display = pygame.display.set_mode([width, height])
    display.fill((0, 0, 0))
 
    clock = pygame.time.Clock()
 
    return display, clock
 
 
def event_loop(display, image1, image2, clock):
    while True:
        for event in pygame.event.get():
            if event.type == pygame.locals.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.locals.KEYDOWN:
                if event.key == pygame.locals.K_ESCAPE:
                    pygame.quit()
                    sys.exit()
 
        # all events has been processed - update scene and redraw the screen
        display.blit(image1, (30, 20))
        display.blit(image2, (60 + image1.get_width(), 20))
 
        # and update the whole display
        pygame.display.update()
        clock.tick(25)
 
 
def palette_to_buffer(p):
    s = create_string_buffer(len(p) * 3)
    i = 0
    for color in p:
        s[i] = color[0]
        s[i + 1] = color[1]
        s[i + 2] = color[2]
        i += 3
    return s
 
 
def image_from_buffer(buffer, width, height, fmt):
    return pygame.image.frombytes(bytes(buffer), (width, height), fmt)
 
 
def main():
    pal = palette_to_buffer(palette)
 
    display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT)
 
    # try to load dynamically linked library
    renderer = CDLL("./renderer.so")
 
    # create buffer for raster image
    buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT)
 
    renderer.render_test_rgb_image(c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), buffer, 0)
    image1 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX")
 
    renderer.render_test_palette_image(
        c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer
    )
    image2 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX")
 
    event_loop(display, image1, image2, clock)
 
 
if __name__ == "__main__":
    main()
 
 
# finito

10. Přednosti propojení Pythonu s jazykem C

Propojení programovacího jazyka Python s jazykem C (popř. s Rustem nebo jazykem Zig) přináší zajímavé možnosti. V první řadě to umožňuje relativně snadné volání funkcí ze systémových knihoven, ale i dalších funkcí dostupných formou dynamicky sdílených knihoven. Díky tomu lze spojit snadnost tvorby aplikací v Pythonu (vysokoúrovňový jazyk s relativně velkou mírou abstrakce) s optimalizovaným nativním kódem. Dobrým příkladem takového propojení je projekt Numpy, v němž se výpočetně náročné části realizují nativními funkcemi. A příkladem propojení Pythonu s Rustem může být projekt Polars, se kterým jsme se na stránkách Roota taktéž již setkali v článcích Knihovna Polars: výkonnější alternativa ke knihovně Pandas a Knihovna Polars: výkonnější alternativa ke knihovně Pandas (datové rámce).

Již v několika předchozích článcích o Pythonu [1] [2] [3] jsme pro některé benchmarky použili výpočet Mandelbrotovy množiny. Dnes si tento výpočet, doplněný navíc o výpočet Juliovy množiny, převedeme do céčka, čímž bychom měli dosáhnout menšího času výpočtu v porovnání s řešením v Pythonu (za předpokladu, že se nevyužije více jader mikroprocesoru).

Poznámka: nejvíce bezbolestné propojení zajistíme tak, že se o alokace a (automatické) dealokace paměti bude starat interpret Pythonu. Pokud bude jak pythonní kód, tak i kód psaný v jazyce C, alokovat paměť svými prostředky bude výsledek poměrně špatně odladitelný.

11. Výpočet Mandelbrotovy a Juliovy množiny implementovaný v jazyce C

V rámci této kapitoly si přepíšeme funkce pro výpočet Mandelbrotovy množiny a Juliovy množiny z Pythonu do programovacího jazyka C (pro větší zajímavost do ANSI C, takže nepoužijeme typ komplexní číslo). V Pythonu lze oba výpočty realizovat velmi snadno:

def mandelbrot(cx, cy, maxiter):
    """Calculate number of iterations for given complex number to escape from set."""
    c = complex(cx, cy)
    z = 0
    for i in range(0, maxiter):
        if abs(z) < 2:
            return i
        z = z * z + c
    return 0
 
 
def recalc_mandelbrot(image, palette, xmin, ymin, xmax, ymax, maxiter=100):
    """Recalculate the whole fractal and render the set into given image."""
    width, height = image.get_size()  # rozmery obrazku
    stepx = (xmax - xmin) / width
    stepy = (ymax - ymin) / height
 
    y1 = ymin
    for y in range(0, height):
        x1 = xmin
        for x in range(0, width):
            i = mandelbrot(x1, y1, maxiter)
            i = 3 * i % 256
            color = palette[i][2] + (palette[i][1] << 8) + (palette[i][0] << 16)
            image.set_at((x, y), color)
            x1 += stepx
        y1 += stepy
 
 
def julia(zx0, zy0, cx, cy, maxiter):
    """Calculate number of iterations for given complex numbers Z and C to escape from set."""
    c = complex(cx, cy)
    z = complex(zx0, zy0)
    for i in range(0, maxiter):
        if abs(z) > 2:
            return i
        z = z * z + c
    return 0
 
 
def recalc_julia(image, palette, xmin, ymin, xmax, ymax, cx, cy, maxiter=1000):
    """Recalculate the whole fractal and render the set into given image."""
    width, height = image.get_size()  # rozmery obrazku
    stepx = (xmax - xmin) / width
    stepy = (ymax - ymin) / height
 
    y1 = ymin
    for y in range(0, height):
        x1 = xmin
        for x in range(0, width):
            i = julia(x1, y1, cx, cy, maxiter)
            i = 3 * i % 256
            color = palette[i][2] + (palette[i][1] << 8) + (palette[i][0] << 16)
            image.set_at((x, y), color)
            x1 += stepx
        y1 += stepy

Výše uvedené výpočty budou velmi pomalé, zejména při použití standardního CPythonu. Příslušné funkce přepsané do programovacího jazyka C současně provádí i vykreslení s využitím předané barvové palety, tedy podobně, jako tomu bylo v Pythonu:

void render_mandelbrot(unsigned int width, unsigned int height,
                       const unsigned char *palette, unsigned char *pixels) {
    int x, y;
    double cx, cy;
    double xmin = -2.0, ymin = -1.5, xmax = 1.0, ymax = 1.5;
    unsigned char *p = pixels;
 
    cy = ymin;
    for (y = 0; y < height; y++) {
        cx = xmin;
        for (x = 0; x < width; x++) {
            double zx = 0.0;
            double zy = 0.0;
            unsigned int i = 0;
            while (i < 150) {
                double zx2 = zx * zx;
                double zy2 = zy * zy;
                if (zx2 + zy2 > 4.0) {
                    break;
                }
                zy = 2.0 * zx * zy + cy;
                zx = zx2 - zy2 + cx;
                i++;
            }
            {
                unsigned char * pal = (unsigned char *)palette + (unsigned char)(i * 3);
 
                *p++ = *pal++;
                *p++ = *pal++;
                *p++ = *pal;
                p++;
            }
            cx += (xmax - xmin) / width;
        }
        cy += (ymax - ymin) / height;
    }
}

void render_julia(unsigned int width, unsigned int height,
                  const unsigned char *palette, unsigned char *pixels,
                  double cx, double cy) {
    int x, y;
    double zx0, zy0;
    double xmin = -1.5, ymin = -1.5, xmax = 1.5, ymax = 1.5;
    unsigned char *p = pixels;
 
    zy0 = ymin;
    for (y = 0; y < height; y++) {
        zx0 = xmin;
        for (x = 0; x < width; x++) {
            double zx = zx0;
            double zy = zy0;
            unsigned int i = 0;
            while (i < 150) {
                double zx2 = zx * zx;
                double zy2 = zy * zy;
                if (zx2 + zy2 > 4.0) {
                    break;
                }
                zy = 2.0 * zx * zy + cy;
                zx = zx2 - zy2 + cx;
                i++;
            }
            {
                unsigned char * pal = (unsigned char *)palette + (unsigned char)(i * 3);
 
                *p++ = *pal++;
                *p++ = *pal++;
                *p++ = *pal;
                p++;
            }
            zx0 += (xmax - xmin) / width;
        }
        zy0 += (ymax - ymin) / height;
    }
}
Poznámka: tento kód je nepatrně delší než obdobný kód v Pythonu, protože výpočty s komplexními čísly byly přepsány do explicitních výpočtů se složkami komplexních čísel (reálná, imaginární). Nicméně rychlost výpočtů je pochopitelně mnohem vyšší.

12. Zavolání nativních funkci pro výpočet Mandelbrotovy a Juliovy množiny z Pythonu

Volání funkcí pro výpočet Mandelbrotovy a Juliovy množiny, které byly vypsány v předchozí kapitole, lze v Pythonu realizovat poměrně snadno. Je zde ovšem novinka – předání komplexní konstanty c do funkce pro výpočet Juliovy množiny vyžaduje použití c_double, což je opět funkce definovaná v knihovně ctypes. Načtení dynamicky linkované knihovny, vytvoření pomocného bufferu a zavolání funkcí pro výpočet obou fraktálů může vypadat následovně:

# try to load dynamically linked library
renderer = CDLL("./renderer.so")
 
# create buffer for raster image
buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT)
 
renderer.render_mandelbrot(c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer)
image1 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX")
 
renderer.render_julia(
    c_int(IMAGE_WIDTH),
    c_int(IMAGE_HEIGHT),
    pal,
    buffer,
    c_double(0.285),
    c_double(0.01),
)
image2 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX")

Obrázek 9: Výsledek výpočtu Mandelbrotovy a Juliovy množiny.

A takto bude vypadat úplný zdrojový kód upraveného demonstračního příkladu (resp. přesněji řečeno jeho Pythonovská část):

import sys
from ctypes import CDLL, c_double, c_int, create_string_buffer
 
from palette_mandmap import palette
 
import pygame
import pygame.locals
 
TITLE = "Renderer"
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 300
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
 
 
def initialize_ui(title, width, height):
    """Initialize Pygame display, drawing surface, and clocks."""
    # set window title
    pygame.display.set_caption(title)
 
    # initialize window
    display = pygame.display.set_mode([width, height])
    display.fill((0, 0, 0))
 
    clock = pygame.time.Clock()
 
    return display, clock
 
 
def event_loop(display, image1, image2, clock):
    while True:
        for event in pygame.event.get():
            if event.type == pygame.locals.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.locals.KEYDOWN:
                if event.key == pygame.locals.K_ESCAPE:
                    pygame.quit()
                    sys.exit()
 
        # all events has been processed - update scene and redraw the screen
        display.blit(image1, (30, 20))
        display.blit(image2, (60 + image1.get_width(), 20))
 
        # and update the whole display
        pygame.display.update()
        clock.tick(25)
 
 
def palette_to_buffer(p):
    s = create_string_buffer(len(p) * 3)
    i = 0
    for color in p:
        s[i] = color[0]
        s[i + 1] = color[1]
        s[i + 2] = color[2]
        i += 3
    return s
 
 
def image_from_buffer(buffer, width, height, fmt):
    return pygame.image.frombytes(bytes(buffer), (width, height), fmt)
 
 
def main():
    pal = palette_to_buffer(palette)
 
    display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT)
 
    # try to load dynamically linked library
    renderer = CDLL("./renderer.so")
 
    # create buffer for raster image
    buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT)
 
    renderer.render_mandelbrot(c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer)
    image1 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX")
 
    renderer.render_julia(
        c_int(IMAGE_WIDTH),
        c_int(IMAGE_HEIGHT),
        pal,
        buffer,
        c_double(0.285),
        c_double(0.01),
    )
    image2 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX")
 
    event_loop(display, image1, image2, clock)
 
 
if __name__ == "__main__":
    main()
 
 
# finito

13. Mandelbrotova množina jako „mapa“ všech Juliových množin

Juliových množin existuje nekonečné množství. Liší se od sebe hodnotou c, která je použita při výpočtu bodů ležících uvnitř množiny (do nativní funkce jsme předávali reálnou a imaginární složku odděleně, a to v parametrech cx a cy). Juliovy množiny lze v závislosti na hodnotě c rozdělit na tři typy:

  1. Pro některé hodnoty c tvoří body ležící v Juliově množině spojitou oblast. To znamená, že každé dva body v takovéto Juliově množině je možné navzájem spojit určitou křivkou tak, že celá křivka leží uvnitř Juliovy množiny. Tato oblast je vždy jedna, neexistují tedy například navzájem izolované „ostrovy“.
  2. Pro jiné hodnoty c jsou jednotlivé body tvořící Juliovu množinu zcela izolovány, tj. v jejich okolí neexistuje další bod, který by ležel v Juliově množině. Taková „rozložená“ Juliova množina se nazývá Fatouův prach (Fatou dust) nebo také Cantorův prach (Cantor dust).
  3. Třetí možnost leží na hranici obou předchozích. Body ležící uvnitř Juliovy množiny sice nejsou vzájemně izolovány, ale současně netvoří žádnou plochu (body na úsečce také nejsou izolovány, ale úsečka má nulovou plochu).

Vztah mezi hodnotou komplexní konstanty c a typem Juliovy množiny je velmi složitý a při jeho detailním zkoumání byla nalezena „mapa“ všech Juliových množin – je jí již dnes několikrát zmíněná Mandelbrotova množina. Body ležící uvnitř Mandelbrotovy množiny představují komplexní hodnoty c, pro které je příslušná Juliova množina spojitá (a naopak). To tedy vlastně znamená, že můžeme interaktivně měnit bod uvnitř Mandelbrotovy množiny a vykreslit pro něj odpovídající Juliovu množinu. A právě tento interaktivní postup je uplatněn v dalším demonstračním příkladu.

Obrázek 10: Spojitá Juliova množina (pravý obrázek).

Obrázek 11: Nespojitá Juliova množina.

Obrázek 12: Hraniční případ.

14. Interaktivní určení konstanty c v Mandelbrotově množině

Jak ale vůbec zajistíme interaktivní nastavení a změnu konstanty c? Můžeme například v Mandelbrotově množině zobrazit „záměrný kříž“ představující hodnotu této konstanty (horizontální osa odpovídá reálné složce a vertikální osa pak složce imaginární) a s využitím kurzorových kláves (nebo myší) umožnit uživateli volbu konstanty.

Konstanty pro výchozí výpočet měřítka (přepočet ze souřadnic obrazovky na hodnotu v komplexní rovině) se získají takto:

scale_x = (XMAX - XMIN) / image1.get_width()
scale_y = (YMAX - YMIN) / image1.get_height()

Výchozí hodnota konstanty c:

cx = cx_scr * scale_x + XMIN
cy = cy_scr * scale_x + YMIN

Takto lze přepočítat pozici záměrného kříže na obrazovce v závislosti na hodnotách cx_scr_delta a cy_scr_delta, což jsou hodnoty –1, 0 či 1 (posun kříže):

cx_scr += cx_scr_delta
cy_scr += cy_scr_delta

Samotný posun kříže je realizován kurzorovými klávesami. Při stisku některé kurzorové klávesy se nastaví příslušný posun do proměnných cx_scr_delta a cy_scr_delta tak, aby se při držení klávesy kříž stále posunoval (vlastně si v těchto konstantách zapamatujeme, které kurzorové klávesy jsou v daný okamžik stisknuty):

if event.type == pygame.locals.KEYDOWN:
    if event.key == pygame.locals.K_LEFT:
        cx_scr_delta = -1
    if event.key == pygame.locals.K_RIGHT:
        cx_scr_delta = 1
    if event.key == pygame.locals.K_UP:
        cy_scr_delta = -1
    if event.key == pygame.locals.K_DOWN:
        cy_scr_delta = 1

Po puštění klávesy pochopitelně musíme pohyb zastavit. Opět vše naprogramujeme takovým způsobem, aby byl možný i diagonální pohyb a zastavení tohoto pohybu. Budeme tedy každou kurzorovou klávesu řešit samostatně:

if event.type == pygame.locals.KEYUP:
    if event.key == pygame.locals.K_LEFT:
        cx_scr_delta = 0
    if event.key == pygame.locals.K_RIGHT:
        cx_scr_delta = 0
    if event.key == pygame.locals.K_UP:
        cy_scr_delta = 0
    if event.key == pygame.locals.K_DOWN:
        cy_scr_delta = 0

V posledním kroku do plochy prvního obrázku (což je Mandelbrotova množina) vykreslíme „záměrný kříž“, který ukazuje pozici hodnoty c v komplexní rovině:

pygame.draw.line(
    display,
    WHITE,
    (XSTART + cx_scr, YSTART),
    (XSTART + cx_scr, YSTART + image1.get_height() - 1),
)
pygame.draw.line(
    display,
    WHITE,
    (XSTART, YSTART + cy_scr),
    (XSTART + image1.get_width() - 1, YSTART + cy_scr),
)

Na závěr ve smyčce pro obsluhu událostí překreslíme okno a zajistíme, aby se kříž pohyboval maximální rychlostí 25 pixelů za sekundu:

pygame.display.update()
clock.tick(25)

15. Výsledná podoba demonstračního příkladu

Demonstrační příklad upravený tak, že umožňuje interaktivní změnu konstanty c, vypadá následovně (resp. přesněji řečeno je uveden kód v Pythonu, zatímco výpočet obou množin v céčkové funkci zůstal zcela nezměněn):

import sys
from ctypes import CDLL, c_double, c_int, create_string_buffer
 
from palette_mandmap import palette
 
import pygame
import pygame.locals
 
TITLE = "Renderer"
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 300
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
 
# Mandelbrot fractal parameters
XMIN = -2.0
XMAX = 1.0
YMIN = -1.5
YMAX = 1.5
MAXITER = 100
 
XSTART = 30
YSTART = 20
 
 
def initialize_ui(title, width, height):
    """Initialize Pygame display, drawing surface, and clocks."""
    # set window title
    pygame.display.set_caption(title)
 
    # initialize window
    display = pygame.display.set_mode([width, height])
    display.fill((0, 0, 0))
 
    clock = pygame.time.Clock()
 
    return display, clock
 
 
def event_loop(display, image1, image2, clock, pal, renderer, buffer):
    cx_scr = image1.get_width() / 2 - 1 + 32
    cy_scr = image1.get_width() / 2 - 1 - 42 * 2
    cx_scr_delta = 0
    cy_scr_delta = 0
    first_draw = True
 
    while True:
        for event in pygame.event.get():
            if event.type == pygame.locals.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.locals.KEYDOWN:
                if event.key == pygame.locals.K_ESCAPE:
                    pygame.quit()
                    sys.exit()
                if event.key == pygame.locals.K_RETURN:
                    pygame.quit()
                    sys.exit()
                if event.key == pygame.locals.K_LEFT:
                    cx_scr_delta = -1
                if event.key == pygame.locals.K_RIGHT:
                    cx_scr_delta = 1
                if event.key == pygame.locals.K_UP:
                    cy_scr_delta = -1
                if event.key == pygame.locals.K_DOWN:
                    cy_scr_delta = 1
            if event.type == pygame.locals.KEYUP:
                if event.key == pygame.locals.K_LEFT:
                    cx_scr_delta = 0
                if event.key == pygame.locals.K_RIGHT:
                    cx_scr_delta = 0
                if event.key == pygame.locals.K_UP:
                    cy_scr_delta = 0
                if event.key == pygame.locals.K_DOWN:
                    cy_scr_delta = 0
 
        # all events has been processed - update scene and redraw the screen
 
        # keep moving C
        cx_scr += cx_scr_delta
        cy_scr += cy_scr_delta
 
        # check for limits
        if cx_scr < 0:
            cx_scr = 0
        if cx_scr > image1.get_width() - 1:
            cx_scr = image1.get_width() - 1
        if cy_scr < 0:
            cy_scr = 0
        if cy_scr > image1.get_height() - 1:
            cy_scr = image1.get_height() - 1
 
        # recalculate Julia set if needed
        if cx_scr_delta != 0 or cy_scr_delta != 0 or first_draw:
            first_draw = False
            scale_x = (XMAX - XMIN) / image1.get_width()
            scale_y = (YMAX - YMIN) / image1.get_height()
 
            cx = cx_scr * scale_x + XMIN
            cy = cy_scr * scale_x + YMIN
 
            renderer.render_julia(
                c_int(IMAGE_WIDTH),
                c_int(IMAGE_HEIGHT),
                pal,
                buffer,
                c_double(cx),
                c_double(cy),
            )
            image2 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX")
 
        # display Mandelbrot set and Julia se
        display.blit(image1, (XSTART, YSTART))
        display.blit(image2, (60 + image1.get_width(), YSTART))
 
        # display C coordinates
        WHITE = (255, 255, 255)
        pygame.draw.line(
            display,
            WHITE,
            (XSTART + cx_scr, YSTART),
            (XSTART + cx_scr, YSTART + image1.get_height() - 1),
        )
        pygame.draw.line(
            display,
            WHITE,
            (XSTART, YSTART + cy_scr),
            (XSTART + image1.get_width() - 1, YSTART + cy_scr),
        )
 
        # and update the whole display
        pygame.display.update()
        clock.tick(25)
 
 
def palette_to_buffer(p):
    s = create_string_buffer(len(p) * 3)
    i = 0
    for color in p:
        s[i] = color[0]
        s[i + 1] = color[1]
        s[i + 2] = color[2]
        i += 3
    return s
 
 
def image_from_buffer(buffer, width, height, fmt):
    return pygame.image.frombytes(bytes(buffer), (width, height), fmt)
 
 
def main():
    pal = palette_to_buffer(palette)
 
    display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT)
 
    renderer = CDLL("./renderer.so")
 
    # create buffer for raster image
    buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT)
 
    renderer.render_mandelbrot(c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer)
    image1 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX")
 
    image2 = pygame.Surface([IMAGE_WIDTH, IMAGE_HEIGHT])
 
    event_loop(display, image1, image2, clock, pal, renderer, buffer)
 
 
if __name__ == "__main__":
    main()
 
 
# finito

16. Buffer sdílený oběma rastrovými obrázky

V předchozích demonstračních příkladech jsme buffer s pixely vybarvenými nativní funkcí přenesli do rastrového obrázku s využitím této pomocné funkce:

def image_from_buffer(buffer, width, height, fmt):
    return pygame.image.frombytes(bytes(buffer), (width, height), fmt)

Tato funkce ve skutečnosti provedla kopii všech pixelů, což může být časově náročnější operace (u našich malých obrázků nikoli, ale u větších scén již ano). Proto se nabízí otázka, zda namísto toho nevyužít funkci pygame.image.frombytes, která kopii pixelů neprovádí. Upravme tedy zdrojový kód našeho příkladu do následující podoby (ukazuji jen změněné části):

import pygame
import pygame.image as image
import pygame.locals
 
 
def event_loop(display, image1, image2, clock, pal, renderer, buffer):
 
    ...
    ...
    ...
            renderer.render_julia(
                c_int(IMAGE_WIDTH),
                c_int(IMAGE_HEIGHT),
                pal,
                buffer,
                c_double(cx),
                c_double(cy),
            )
            image2 = image.frombuffer(buffer, (IMAGE_WIDTH, IMAGE_HEIGHT), "RGBX")
    ...
    ...
    ...
 
 
def main():
    pal = palette_to_buffer(palette)
 
    display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT)
 
    renderer = CDLL("./renderer.so")
 
    # create buffer for raster image
    buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT)
 
    renderer.render_mandelbrot(c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer)
    image1 = image.frombuffer(buffer, (IMAGE_WIDTH, IMAGE_HEIGHT), "RGBX")
 
    image2 = pygame.Surface([IMAGE_WIDTH, IMAGE_HEIGHT])
 
    event_loop(display, image1, image2, clock, pal, renderer, buffer)
 
 
if __name__ == "__main__":
    main()

Po spuštění takto upraveného příkladu je patrné, že je buffer skutečně sdílen mezi oběma obrázky a že tedy původní Mandelbrotova množina byla již v prvním snímku překreslena Juliovou množinou:

Obrázek 13: Výsledek použití sdíleného bufferu pro oba rastrové obrázky.

Obrázek 14: Výsledek použití sdíleného bufferu pro oba rastrové obrázky.

Obrázek 15: Výsledek použití sdíleného bufferu pro oba rastrové obrázky.

17. Oprava předchozího postupu: použití dvou bufferů bez kopie pixelů

Ve skutečnosti pochopitelně stále můžeme využít rychlejší vykreslování bez opakované kopie pixelů. Musíme provést jen jedinou změnu – pro každý obrázek (tedy v našem případě pro obrázek Mandelbrotovy množiny a pro obrázek množiny Juliovy) je nutné alokovat a následně i použít vlastní buffer. Změn ve zdrojovém kódu je zapotřebí provést jen několik, což je ostatně patrné i z následujícího výpisu (změny jsou zde zvýrazněny):

def main():
    pal = palette_to_buffer(palette)
 
    display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT)
 
    renderer = CDLL("./renderer.so")
 
    # create buffers for raster image
    buffer1 = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT)
    buffer2 = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT)
 
    renderer.render_mandelbrot(c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer1)
    image1 = image.frombuffer(buffer1, (IMAGE_WIDTH, IMAGE_HEIGHT), "RGBX")
 
    image2 = pygame.Surface([IMAGE_WIDTH, IMAGE_HEIGHT])
 
    event_loop(display, image1, image2, clock, pal, renderer, buffer2)
 
 
if __name__ == "__main__":
    main()

Výsledky získané po spuštění takto upraveného příkladu:

Obrázek 16: Výsledek použití unikátního bufferu pro každý rastrový obrázek.

Obrázek 17: Výsledek použití unikátního bufferu pro každý rastrový obrázek.

Obrázek 18: Výsledek použití unikátního bufferu pro každý rastrový obrázek.

Výsledná podoba zdrojového kódu:

import sys
from ctypes import CDLL, c_double, c_int, create_string_buffer
 
from palette_mandmap import palette
 
import pygame
import pygame.image as image
import pygame.locals
 
TITLE = "Renderer"
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 300
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
 
# Mandelbrot fractal parameters
XMIN = -2.0
XMAX = 1.0
YMIN = -1.5
YMAX = 1.5
MAXITER = 100
 
XSTART = 30
YSTART = 20
 
 
def initialize_ui(title, width, height):
    """Initialize Pygame display, drawing surface, and clocks."""
    # set window title
    pygame.display.set_caption(title)
 
    # initialize window
    display = pygame.display.set_mode([width, height])
    display.fill((0, 0, 0))
 
    clock = pygame.time.Clock()
 
    return display, clock
 
 
def event_loop(display, image1, image2, clock, pal, renderer, buffer):
    cx_scr = image1.get_width() / 2 - 1 + 32
    cy_scr = image1.get_width() / 2 - 1 - 42 * 2
    cx_scr_delta = 0
    cy_scr_delta = 0
    first_draw = True
 
    while True:
        for event in pygame.event.get():
            if event.type == pygame.locals.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.locals.KEYDOWN:
                if event.key == pygame.locals.K_ESCAPE:
                    pygame.quit()
                    sys.exit()
                if event.key == pygame.locals.K_RETURN:
                    pygame.quit()
                    sys.exit()
                if event.key == pygame.locals.K_LEFT:
                    cx_scr_delta = -1
                if event.key == pygame.locals.K_RIGHT:
                    cx_scr_delta = 1
                if event.key == pygame.locals.K_UP:
                    cy_scr_delta = -1
                if event.key == pygame.locals.K_DOWN:
                    cy_scr_delta = 1
            if event.type == pygame.locals.KEYUP:
                if event.key == pygame.locals.K_LEFT:
                    cx_scr_delta = 0
                if event.key == pygame.locals.K_RIGHT:
                    cx_scr_delta = 0
                if event.key == pygame.locals.K_UP:
                    cy_scr_delta = 0
                if event.key == pygame.locals.K_DOWN:
                    cy_scr_delta = 0
 
        # all events has been processed - update scene and redraw the screen
 
        # keep moving C
        cx_scr += cx_scr_delta
        cy_scr += cy_scr_delta
 
        # check for limits
        if cx_scr < 0:
            cx_scr = 0
        if cx_scr > image1.get_width() - 1:
            cx_scr = image1.get_width() - 1
        if cy_scr < 0:
            cy_scr = 0
        if cy_scr > image1.get_height() - 1:
            cy_scr = image1.get_height() - 1
 
        # recalculate Julia set if needed
        if cx_scr_delta != 0 or cy_scr_delta != 0 or first_draw:
            first_draw = False
            scale_x = (XMAX - XMIN) / image1.get_width()
            scale_y = (YMAX - YMIN) / image1.get_height()
 
            cx = cx_scr * scale_x + XMIN
            cy = cy_scr * scale_x + YMIN
 
            renderer.render_julia(
                c_int(IMAGE_WIDTH),
                c_int(IMAGE_HEIGHT),
                pal,
                buffer,
                c_double(cx),
                c_double(cy),
            )
            image2 = image.frombuffer(buffer, (IMAGE_WIDTH, IMAGE_HEIGHT), "RGBX")
 
        # display Mandelbrot set and Julia se
        display.blit(image1, (XSTART, YSTART))
        display.blit(image2, (60 + image1.get_width(), YSTART))
 
        # display C coordinates
        WHITE = (255, 255, 255)
        pygame.draw.line(
            display,
            WHITE,
            (XSTART + cx_scr, YSTART),
            (XSTART + cx_scr, YSTART + image1.get_height() - 1),
        )
        pygame.draw.line(
            display,
            WHITE,
            (XSTART, YSTART + cy_scr),
            (XSTART + image1.get_width() - 1, YSTART + cy_scr),
        )
 
        # and update the whole display
        pygame.display.update()
        clock.tick(25)
 
 
def palette_to_buffer(p):
    s = create_string_buffer(len(p) * 3)
    i = 0
    for color in p:
        s[i] = color[0]
        s[i + 1] = color[1]
        s[i + 2] = color[2]
        i += 3
    return s
 
 
def image_from_buffer(buffer, width, height, fmt):
    return pygame.image.frombytes(bytes(buffer), (width, height), fmt)
 
 
def main():
    pal = palette_to_buffer(palette)
 
    display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT)
 
    renderer = CDLL("./renderer.so")
 
    # create buffers for raster image
    buffer1 = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT)
    buffer2 = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT)
 
    renderer.render_mandelbrot(c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer1)
    image1 = image.frombuffer(buffer1, (IMAGE_WIDTH, IMAGE_HEIGHT), "RGBX")
 
    image2 = pygame.Surface([IMAGE_WIDTH, IMAGE_HEIGHT])
 
    event_loop(display, image1, image2, clock, pal, renderer, buffer2)
 
 
if __name__ == "__main__":
    main()
 
 
# finito

18. Přístup k pixelům obrázku po 32bitových slovech

Prozatím jsme ve všech nativních funkcích k barvám pixelů přistupovali po bajtech, což znamenalo, že se každá barvová složka měnila jedním zápisem (bajtu). Ovšem kromě toho pochopitelně můžeme k pixelům bez problémů přistupovat i po 32bitových slovech, přičemž horních osm bitů tohoto slova nebude využito (což zajišťuje formát „RGBX“). Z pohledu Pythonu k žádné změně nedojde, protože se do nativní funkce stále předává ukazatel na buffer, ovšem v nativní funkci bude tento ukazatel typu uint32_t * a nikoli unsigned char *. A zápis barvy se v tomto případě provede jedinou operací. Na druhou stranu musíme skládat barvy pixelů s využitím bitových posunů, takže reálné rychlosti zápisu se nebudou příliš odlišovat (jiné to bude ve chvíli, kdy budeme mít korektně připravenou barvovou paletu – to je relativně jednoduchá změna, kterou ponechám jako úkol pro vážené čtenáře):

#include <stdint.h>
 
void render_test_rgb_image(unsigned int width, unsigned int height,
                           uint32_t *pixels, unsigned char green) {
    unsigned int i, j;
    unsigned int *p = pixels;
 
    for (j = 0; j < height; j++) {
        for (i = 0; i < width; i++) {
            unsigned int color = (i << 16) + (green << 8) + j;
            *p++ = color;
        }
    }
}

Výsledné obrázky by měly vypadat stejně, jako tomu bylo v dnešním prvním demonstračním příkladu:

Obrázek 19: Obrázky s testovacím vzorkem vyplněné zde popsanou nativní funkcí.

bitcoin_skoleni

19. Repositář s demonstračními příklady

V dnešním článku popsané demonstrační příklady naleznete na GitHubu:

# Příklad Stručný popis Adresa
  1 Vyplnění rastrových obrázků vzorkem přímo z Pythonu  
1 show.py Vyplnění rastrových obrázků vzorkem přímo z Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/show.py
       
  2 Zavolání nativní funkce pro vyplnění obrázku vzorkem  
1 Makefile Makefile pro překlad kódu renderer.c do dynamicky sdílené knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample1/Makefile
2 renderer.c https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample1/renderer.c
3 show.py zobrazení dvojice testovacích obrázků https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample1/show.py
       
  3 Vyplnění rastrového obrázku s využitím barvové palety  
5 Makefile Makefile pro překlad kódu renderer.c do dynamicky sdílené knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample2/Makefile
5 palette_mandmap.py definice barvové palety s 256 barvami https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample2/palette_mandmap.py
6 renderer.c vyplnění rastrového obrázku s využitím barvové palety https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample2/renderer.c
7 show.py zobrazení testovacího obrázku s paletou https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample2/show.py
       
  4 Výpočet Mandelbrotovy a Juliovy množiny implementovaný v jazyku C  
8 Makefile Makefile pro překlad kódu renderer.c do dynamicky sdílené knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample3/Makefile
9 palette_mandmap.py definice barvové palety s 256 barvami https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample3/palette_mandmap.py
10 renderer.c výpočet Mandelbrotovy a Juliovy množiny implementovaný v C https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample3/renderer.c
11 show.py realizace výpočtu a zobrazení Mandelbrotovy a Juliovy množiny https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample3/show.py
       
  5 Interaktivní změna tvaru Juliovy množiny  
12 Makefile Makefile pro překlad kódu renderer.c do dynamicky sdílené knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample4/Makefile
13 palette_mandmap.py definice barvové palety s 256 barvami https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample4/palette_mandmap.py
14 renderer.c výpočet Mandelbrotovy a Juliovy množiny implementovaný v C https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample4/renderer.c
15 show.py interaktivní změna hodnoty C při výpočtu Juliovy množiny https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample4/show.py
       
  6 Využití jednoho (sdíleného) bufferu pro oba obrázky  
16 Makefile Makefile pro překlad kódu renderer.c do dynamicky sdílené knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample5/Makefile
17 palette_mandmap.py definice barvové palety s 256 barvami https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample5/palette_mandmap.py
18 renderer.c výpočet Mandelbrotovy a Juliovy množiny implementovaný v C https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample5/renderer.c
19 show.py využití jednoho (sdíleného) bufferu https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample5/show.py
       
  7 Využití dvojice bufferů, jeden buffer pro každý obrázek  
20 Makefile Makefile pro překlad kódu renderer.c do dynamicky sdílené knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample6/Makefile
21 palette_mandmap.py definice barvové palety s 256 barvami https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample6/palette_mandmap.py
22 renderer.c výpočet Mandelbrotovy a Juliovy množiny implementovaný v C https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample6/renderer.c
23 show.py využití dvojice rozličných bufferů https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample6/show.py
       
  8 Přístup k pixelům obrázku po 32bitových slovech  
24 Makefile Makefile pro překlad kódu renderer.c do dynamicky sdílené knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample7/Makefile
25 palette_mandmap.py definice barvové palety s 256 barvami https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample7/palette_mandmap.py
26 renderer.c výpočet Mandelbrotovy a Juliovy množiny implementovaný v C https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample7/renderer.c
27 show.py využití dvojice rozličných bufferů https://github.com/tisnik/most-popular-python-libs/blob/master/ctypes/e­xample7/show.py

Navíc si pro úplnost uveďme demonstrační příklady použité v článcích o knihovně cffi. I v těchto článcích jsme se totiž o ctypes zmiňovali:

# Příklad Stručný popis Adresa
1 adder/adder.c funkce psaná v C, která sečte své dva celočíselné parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/adder.c
2 adder/call_via_cffi1.py zavolání céčkovské funkce přes cffi s korektními parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_cffi1.py
3 adder/call_via_cffi2.py zavolání céčkovské funkce přes cffi s nekorektními parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_cffi2.py
4 adder/call_via_cffi3.py zavolání céčkovské funkce přes cffi s nekorektními parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_cffi3.py
5 adder/call_via_cffi.sh nastavení cest a spuštění všech tří předchozích Pythonovských skriptů https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_cffi.sh
6 adder/call_via_ctypes1.py zavolání céčkovské funkce přes ctypes s korektními parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_ctypes1.py
7 adder/call_via_ctypes2.py zavolání céčkovské funkce přes ctypes s nekorektními parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_ctypes2.py
8 adder/call_via_ctypes3.py zavolání céčkovské funkce přes ctypes s nekorektními parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_ctypes3.py
9 adder/call_via_ctypes.sh nastavení cest a spuštění všech tří předchozích Pythonovských skriptů https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_ctypes.sh
10 adder/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ma­ke_library.sh
11 adder/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/clean.sh
       
12 greeter/greeter.c funkce psaná v C, která na standardní výstup vytiskne řetězec https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/greeter.c
13 greeter/call_via_cffi1.py zavolání céčkovské funkce přes cffi s nekorektním parametrem https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_cffi1.py
14 greeter/call_via_cffi2.py zavolání céčkovské funkce přes cffi s korektním parametrem https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_cffi2.py
15 greeter/call_via_cffi3.py zavolání céčkovské funkce přes cffi s korektním parametrem https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_cffi3.py
16 greeter/call_via_cffi.sh nastavení cest a spuštění všech tří předchozích Pythonovských skriptů https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_cffi.sh
17 greeter/call_via_ctypes1.py zavolání céčkovské funkce přes ctypes s nekorektním parametrem https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_ctypes1.py
18 greeter/call_via_ctypes2.py zavolání céčkovské funkce přes ctypes s korektním parametrem https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_ctypes2.py
19 greeter/call_via_ctypes3.py zavolání céčkovské funkce přes ctypes s korektním parametrem https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_ctypes3.py
20 greeter/call_via_ctypes.sh nastavení cest a spuštění všech tří předchozích Pythonovských skriptů https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_ctypes.sh
21 greeter/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/make_library.sh
22 greeter/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter/clean.sh
       
23 swapper/swapper.c céčkovská funkce prohazující obsah svých dvou parametrů předávaných referencí https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/swap­per/swapper.c
24 swapper/call_via_cffi1.py zavolání céčkovské knihovny z jazyka Python (korektní varianta) https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/swap­per/call_via_cffi1.py
25 swapper/call_via_cffi2.py zavolání céčkovské knihovny z jazyka Python (nekorektní varianta) https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/swap­per/call_via_cffi2.py
26 swapper/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/swap­per/call_via_cffi.sh
27 swapper/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/swap­per/make_library.sh
28 swapper/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/swapper/clean.sh
       
29 filler/filler.c céčkovská funkce pro vyplnění části pole zadanou hodnotou https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/filler/filler.c
30 filler/call_via_cffi.py zavolání céčkovské knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/filler/ca­ll_via_cffi.py
31 filler/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/filler/ca­ll_via_cffi.sh
32 filler/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/filler/ma­ke_library.sh
32 filler/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/filler/clean.sh
       
33 greeter_h/greeter.c funkce psaná v C, která na standardní výstup vytiskne řetězec https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h/greeter.c
34 greeter_h/greeter.h prototyp (předběžná deklarace) funkce greeter https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h/greeter.h
35 greeter_h/call_via_cffi4.py zavolání céčkovské knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h/call_via_cffi4.py
36 greeter_h/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h/call_via_cffi.sh
37 greeter_h/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h/make_library.sh
38 greeter_h/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h/clean.sh
       
39 greeter_h2/greeter.c funkce psaná v C, která na standardní výstup vytiskne řetězec https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h2/greeter.c
40 greeter_h2/greeter.h prototyp (předběžná deklarace) funkce greeter obalená v testu na existenci symbolu/makra https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h2/greeter.h
41 greeter_h2/call_via_cffi5.py zavolání céčkovské knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h2/call_via_cffi5.py
42 greeter_h2/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h2/call_via_cffi.sh
43 greeter_h2/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h2/make_library.sh
44 greeter_h2/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h2/clean.sh
       
45 greeter_h3/greeter.c funkce psaná v C, která na standardní výstup vytiskne řetězec https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h3/greeter.c
46 greeter_h3/greeter.h test na existenci symbolu/makra, pokud makro neexistuje, provede se vložení dalšího souboru https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h3/greeter.h
47 greeter_h3/_greeter.h prototyp (předběžná deklarace) funkce greeter bez dalších informací https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h3/_greeter.h
48 greeter_h3/call_via_cffi5.py zavolání céčkovské funkce z knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h3/call_via_cffi5.py
49 greeter_h3/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h3/call_via_cffi.sh
50 greeter_h3/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h3/make_library.sh
51 greeter_h3/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_h3/clean.sh
       
52 greeter_build/greeter.c funkce psaná v C, která na standardní výstup vytiskne řetězec https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_build/greeter.c
53 greeter_build/greeter.h prototyp (předběžná deklarace) funkce greeter bez dalších informací https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_build/greeter.h
54 greeter_build/call_via_cffi7.py skript pro překlad céčkovské funkce, vytvoření dynamicky linkované knihovny a zavolání funkce z této knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_build/call_via_cffi7­.py
55 greeter_build/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter_build/clean.sh
       
56 vector_printer/vector_printer.c funkce psaná v C, která akceptuje jako svůj parametr strukturu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vec­tor_printer/vector_printer­.c
57 vector_printer/vector_printer.h prototyp (předběžná deklarace) funkce print_vector bez dalších informací https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vec­tor_printer/vector_printer­.h
58 vector_printer/call_via_cffi.sh zavolání céčkovské funkce z knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vec­tor_printer/call_via_cffi­.sh
59 vector_printer/call_via_cffi.py nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vec­tor_printer/call_via_cffi­.py
60 vector_printer/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vec­tor_printer/make_library.sh
61 vector_printer/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vec­tor_printer/clean.sh
       
62 vector_printer2/vector_printer.c funkce psaná v C, která akceptuje jako svůj parametr ukazatel na strukturu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vec­tor_printer2/vector_printer­.c
63 vector_printer2/vector_printer.h prototyp (předběžná deklarace) funkce print_vector bez dalších informací https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vec­tor_printer2/vector_printer­.h
64 vector_printer2/call_via_cffi.sh zavolání céčkovské funkce z knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vec­tor_printer2/call_via_cffi­.sh
65 vector_printer2/call_via_cffi.py nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vec­tor_printer2/call_via_cffi­.py
66 vector_printer2/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vec­tor_printer2/make_library­.sh
67 vector_printer2/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vec­tor_printer2/clean.sh
       
68 array_printer1/array_printer.c funkce naprogramovaná v C, která akceptuje pole s prvky typu float https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer1/array_printer.c
69 array_printer1/array_printer.h prototyp (předběžná deklarace) funkce print_array bez dalších informací https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer1/array_printer.h
70 array_printer1/call_via_cffi.py zavolání céčkovské funkce z knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer1/call_via_cffi.sh
71 array_printer1/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer1/call_via_cffi.py
72 array_printer1/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer1/make_library.sh
73 array_printer1/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer1/clean.sh
       
74 array_printer2/array_printer.c funkce naprogramovaná v C, která akceptuje pole s prvky typu float https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer2/array_printer.c
75 array_printer2/array_printer.h prototyp (předběžná deklarace) funkce print_array bez dalších informací https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer2/array_printer.h
76 array_printer2/call_via_cffi.py zavolání céčkovské funkce z knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer2/call_via_cffi.sh
77 array_printer2/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer2/call_via_cffi.py
78 array_printer2/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer2/make_library.sh
79 array_printer2/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer2/clean.sh
       
80 array_printer3/array_printer.c funkce naprogramovaná v C, která akceptuje pole s prvky typu float https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer3/array_printer.c
81 array_printer3/array_printer.h prototyp (předběžná deklarace) funkce print_array bez dalších informací https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer3/array_printer.h
82 array_printer3/call_via_cffi.py zavolání céčkovské funkce z knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer3/call_via_cffi.sh
83 array_printer3/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer3/call_via_cffi.py
84 array_printer3/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer3/make_library.sh
85 array_printer3/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer3/clean.sh
       
86 array_printer4/array_printer.c funkce naprogramovaná v C, která akceptuje pole s prvky typu float https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer4/array_printer.c
87 array_printer4/array_printer.h prototyp (předběžná deklarace) funkce print_array bez dalších informací https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer4/array_printer.h
88 array_printer4/call_via_cffi.py zavolání céčkovské funkce z knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer4/call_via_cffi.sh
89 array_printer4/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer4/call_via_cffi.py
90 array_printer4/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer4/make_library.sh
91 array_printer4/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer4/clean.sh
       
92 array_printer5/array_printer.c funkce naprogramovaná v C, která akceptuje pole s prvky typu vector_t https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer5/array_printer.c
93 array_printer5/array_printer.h prototyp (předběžná deklarace) funkce print_array bez dalších informací https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer5/array_printer.h
94 array_printer5/call_via_cffi.py zavolání céčkovské funkce z knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer5/call_via_cffi.sh
95 array_printer5/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer5/call_via_cffi.py
96 array_printer5/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer5/make_library.sh
97 array_printer5/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer5/clean.sh
       
92 array_printer6/array_printer.c funkce naprogramovaná v C, která akceptuje dvourozměrné pole s prvky typu float https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer6/array_printer.c
93 array_printer6/array_printer.h prototyp (předběžná deklarace) funkce print_array bez dalších informací https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer6/array_printer.h
94 array_printer6/call_via_cffi.py zavolání céčkovské funkce z knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer6/call_via_cffi.sh
95 array_printer6/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer6/call_via_cffi.py
96 array_printer6/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer6/make_library.sh
97 array_printer6/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer6/clean.sh
       
98 array_printer7/array_printer.c funkce naprogramovaná v C, která akceptuje dvourozměrné pole s prvky typu float https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer7/array_printer.c
99 array_printer7/array_printer.h prototyp (předběžná deklarace) funkce print_array bez dalších informací https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer7/array_printer.h
100 array_printer7/call_via_cffi.py zavolání céčkovské funkce z knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer7/call_via_cffi.sh
101 array_printer7/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer7/call_via_cffi.py
102 array_printer7/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer7/make_library.sh
103 array_printer7/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/arra­y_printer7/clean.sh

20. Odkazy na Internetu

  1. ctypes – A foreign function library for Python
    https://docs.python.org/3/li­brary/ctypes.html
  2. Pygame: display
    https://www.pygame.org/doc­s/ref/display.html
  3. Pygame: event
    https://www.pygame.org/doc­s/ref/event.html
  4. Pygame: image
    https://www.pygame.org/doc­s/ref/image.html
  5. Pygame: clock
    https://www.pygame.org/doc­s/ref/time.html#pygame.ti­me.Clock
  6. Fraktály v počítačové grafice XII
    https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-xii/
  7. Fraktály v počítačové grafice XIII
    https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-xiii/
  8. Fraktály v počítačové grafice XIV
    https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-xiv/
  9. CFFI documentation
    https://cffi.readthedocs.i­o/en/latest/
  10. cffi 1.15.1 na PyPi
    https://pypi.org/project/cffi/
  11. Python Bindings: Calling C or C++ From Python
    https://realpython.com/python-bindings-overview/
  12. Interfacing with C/C++ Libraries
    https://docs.python-guide.org/scenarios/clibs/
  13. Cython, pybind11, cffi – which tool should you choose?
    http://blog.behnel.de/posts/cython-pybind11-cffi-which-tool-to-choose.html
  14. Python FFI with ctypes and cffi
    https://eli.thegreenplace­.net/2013/03/09/python-ffi-with-ctypes-and-cffi
  15. Propojení Go s Pythonem s využitím cgo a ctypes
    https://www.root.cz/clanky/propojeni-go-s-pythonem-s-vyuzitim-cgo-a-ctypes/
  16. Propojení Go s Pythonem s využitím cgo a ctypes (2. část)
    https://www.root.cz/clanky/propojeni-go-s-pythonem-s-vyuzitim-cgo-a-ctypes-2-cast/
  17. Programovací jazyk Rust: použití FFI pro volání funkcí z nativních knihoven
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-pouziti-ffi-pro-volani-funkci-z-nativnich-knihoven/
  18. Programovací jazyk Rust: použití FFI při předávání struktur
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-pouziti-ffi-pri-predavani-struktur/
  19. Programovací jazyk Rust: použití FFI pro volání funkcí z nativních knihoven (2. část)
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-pouziti-ffi-pro-volani-funkci-z-nativnich-knihoven-2-cast/
  20. Dynamic-link library
    https://en.wikipedia.org/wiki/Dynamic-link_library
  21. Úvod do jazyka C: Deklarace funkcí
    https://www.fi.muni.cz/us­r/jkucera/pb071/sl5.htm
  22. Using standard library headers with CFFI
    https://stackoverflow.com/qu­estions/57481873/using-standard-library-headers-with-cffi
  23. Preparing and Distributing modules
    https://cffi.readthedocs.i­o/en/latest/cdef.html
  24. C Arrays
    https://www.programiz.com/c-programming/c-arrays
  25. C Arrays
    https://www.w3schools.com/c/c_a­rrays.php
  26. Array of Structures in C
    https://overiq.com/c-programming-101/array-of-structures-in-c/#google_vignette
ikonka

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.