Manipulace s binárními datovými strukturami v Pythonu (2. část)

2. 1. 2025
Doba čtení: 31 minut

Sdílet

Autor: Root.cz s využitím DALL-E
Budeme se zabývat zarovnáním údajů v datových strukturách uložených v binární podobě (alignment) a taktéž tím, jak jsou realizovány výplně (padding) přidávané na konec serializovaných datových struktur.

Obsah

1. Manipulace s binárními datovými strukturami v Pythonu (2. část)

2. Metoda Struct.unpack pro přečtení hodnot z binární struktury

3. Korektní přečtení jedné hodnoty z binárních dat

4. Čtení vícebajtové hodnoty se specifikací pořadí bajtů

5. Binární formáty obsahující více hodnot různých typů

6. Zarovnání a výplně

7. Výpočet velikosti binární struktury se zadaným formátem prvků

8. Velikost binární struktury s výplněmi

9. Doplňkové bajty na konci binární struktury

10. Krátké zopakování z minula: zápis rastrového obrázku do formátu PNG

11. Formát PNG

12. Testovací obrázek, který budeme načítat a analyzovat

13. Čtení signatury souborů PNG

14. Načtení hlaviček jednotlivých chunků

15. Přeskok datové části chunku a přečtení kontrolního součtu

16. Úplný zdrojový kód příkladu, který přečte chunky uložené v souboru formátu PNG

17. Načtení informací uložených v chunku IHDR

18. Úplný zdrojový kód skriptu, který vypíše strukturu PNG souboru i přesné informace z hlavičky

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

20. Odkazy na Internetu

1. Manipulace s binárními datovými strukturami v Pythonu (2. část)

Na první článek o zpracování binárních dat v programovacím jazyku Python dnes navážeme. Zabývat se budeme problematikou zarovnání údajů v datových strukturách uložených v binární podobě (alignment) a taktéž tím, jak jsou realizovány výplně (padding) přidávané na konec serializovaných datových struktur. Jedná se o poměrně důležitá témata, protože se se zarovnanými strukturami popř. se strukturami s doplněnými bajty můžeme setkat relativně často, zejména při posílání dat mezi Pythonem a překládanými jazyky typu C, Go či Rust. A v závěrečné části článku si ukážeme způsob čtení informací z datového formátu PNG, do kterého již umíme data ukládat.

Poznámka: formát PNG nebyl zvolen náhodně. Jeho interní uspořádání je totiž navrženo takovým způsobem, aby bylo možné data relativně jednoduše zpracovávat v různých programovacích jazycích. Existuje i mnoho dalších formátů, v nichž se interně používají chunky, ovšem v PNG se s nimi pracuje skutečně velmi snadným způsobem (například formát FLI pro uložení animací již tak jednoduchý na zpracování není, i když myšlenka je zde podobná).

2. Metoda Struct.unpack pro přečtení hodnot z binární struktury

Pro serializaci hodnot do binárních dat jsme v předchozím článku používali funkci unpack ze standardního balíčku struct. Opačná operace, tj. získání hodnoty určitého typu z binárních dat, je nepatrně složitější, neboť namísto volání jedné funkce je nejprve nutné zkonstruovat objekt typu struct.Struct (s předáním požadovaného formátu) a následně zavolat metodu unpack, které se předají binární data (bytes, bytearray atd.). Tato metoda z binárních dat získá požadovanou hodnotu či hodnoty a vrátí je formou n-tice (tuple):

unpack(self, buffer, /) unbound _struct.Struct method
    Return a tuple containing unpacked values.
 
    Unpack according to the format string Struct.format. The buffer's size
    in bytes must be Struct.size.

Celý postup si ukážeme na jednoduchém demonstračním příkladu, v němž nejprve uložíme celočíselnou hodnotu 42 do binárních dat (typ bytes) a pro jistotu si obsah binárních dat vypíšeme. Následně se zkonstruuje objekt typu struct.Struct předáním požadovaného formátu do konstruktoru (formát „b“ – jediný bajt). Posledním krokem je zavolání metody Struct.unpack(), které předáme již dříve vytvořená binární data:

import struct
 
# uložení hodnoty bajtu do binární struktury
bytes = struct.pack("b", 42)
print("Serialized:  ", bytes.hex(" ", 1))
 
# přečtení binární struktury, která obsahuje jediný bajt
s = struct.Struct("b")
from_struct = s.unpack(bytes)
 
# vypsat přečtenou hodnotu
print("Deserialized:", from_struct)
print("Type:        ", type(from_struct))

Výsledkem deserializace z binárních dat bude n-tice obsahující jediný prvek, konkrétně prvek s celočíselnou hodnotou 42:

Serialized:   2a
Deserialized: (42,)
Type:         <class 'tuple'>

3. Korektní přečtení jedné hodnoty z binárních dat

V případě, že se z binárních dat čte pouze jediný údaj, typicky znak, celočíselná numerická hodnota, numerická hodnota s plovoucí řádovou čárkou atd., je řešení velmi jednoduché – metodou Struct.unpack si necháme vrátit n-tici, která ovšem bude obsahovat jediný údaj (specifikovaný ve formátu). Následně z n-tice tento jediný její prvek přečteme a získáme tak hodnotu korektního typu.

Celý postup je snadný a můžeme si ho ověřit na následujícím demonstračním příkladu:

import struct
 
# uložení hodnoty bajtu do binární struktury
bytes = struct.pack("b", 42)
print("Serialized:  ", bytes.hex(" ", 1))
 
# přečtení binární struktury, která obsahuje jediný bajt
s = struct.Struct("b")
 
# první prvek z přečtené struktury
from_struct = s.unpack(bytes)[0]
 
# vypsat přečtenou hodnotu
print("Deserialized:", from_struct)
print("Type:        ", type(from_struct))

Tento skript by po svém spuštění měl vypsat, že binární data obsahují jediný bajt s hodnotou 0×2a a následně se navíc vypíše, že byl tento bajt deserializován do celočíselné hodnoty 42, která má korektní (resp. přesněji řečeno očekávaný) typ int:

Serialized:   2a
Deserialized: 42
Type:         <class 'int'>

4. Čtení vícebajtové hodnoty se specifikací pořadí bajtů

Již minule jsme se setkali s problematikou pořadí bajtů u vícebajtové hodnoty uložené do binárního bloku. Víme již, že pořadí uložení bajtů lze při serializaci (tj. při volání funkce struct.pack) ovlivnit pomocí znaků <, >, ! atd. zapisovaných do řetězce s formátem. Tyto znaky se zapisují na samotný začátek řetězce s formátem:

Znak Význam
@ podle platformy (ovlivňuje i zarovnání atd.)
= podle platformy, ovšem bez zarovnání
< little endian
> big endian
! big endian (zde se ovšem používá označení „network“)

Tytéž znaky je možné použít i při konstrukci objektu struct.Struct a zvolit tak, jak budou vícebajtové hodnoty načteny (deserializovány) z binárních dat. Ukažme si nejprve deserializaci dvoubajtové hodnoty typu celé číslo v případě, že nebudeme specifikovat pořadí bajtů:

import struct
 
# uložení hodnoty bajtu do binární struktury
bytes = struct.pack("h", 42)
print("Serialized:  ", bytes.hex(" ", 1))
 
# přečtení binární struktury, která obsahuje jediný bajt
s = struct.Struct("h")
 
# první prvek z přečtené struktury
from_struct = s.unpack(bytes)[0]
 
# vypsat přečtenou hodnotu
print("Deserialized:", from_struct)
print("Type:        ", type(from_struct))

Na platformě x86(64) by se měly po spuštění tohoto skriptu zobrazit následující hodnoty (povšimněte si, že nejdříve je uložen nižší bajt):

Serialized:   2a 00
Deserialized: 42
Type:         <class 'int'>

Samozřejmě můžeme explicitně nastavit, že budeme číst dvoubajtovou hodnotu uloženou v pořadí bajtů little endian:

import struct
 
# uložení hodnoty bajtu do binární struktury
bytes = struct.pack("h", 42)
print("Serialized:  ", bytes.hex(" ", 1))
 
# přečtení binární struktury, která obsahuje jediný bajt
s = struct.Struct("<h")
 
# první prvek z přečtené struktury
from_struct = s.unpack(bytes)[0]
 
# vypsat přečtenou hodnotu
print("Deserialized:", from_struct)
print("Type:        ", type(from_struct))

Výsledky budou na platformě x86(64) totožné:

Serialized:   2a 00
Deserialized: 42
Type:         <class 'int'>

Nebo naopak při čtení specifikujeme pořadí bajtů big endian:

import struct
 
# uložení hodnoty bajtu do binární struktury
bytes = struct.pack("h", 42)
print("Serialized:  ", bytes.hex(" ", 1))
 
# přečtení binární struktury, která obsahuje jediný bajt
s = struct.Struct(">h")
 
# první prvek z přečtené struktury
from_struct = s.unpack(bytes)[0]
 
# vypsat přečtenou hodnotu
print("Deserialized:", from_struct)
print("Type:        ", type(from_struct))

Nyní se přečte odlišná hodnota, konkrétně hodnota odpovídající 42×256:

Serialized:   2a 00
Deserialized: 10752
Type:         <class 'int'>
Poznámka: pořadí bajtů platí vždy pro celou strukturu, tj. nelze nastavit, že první dva bajty jsou uloženy jako little endian a další dva jako big endian. Pokud taková situace nastane, je nutné serializaci/deserializaci provádět po částech.

5. Binární formáty obsahující více hodnot různých typů

V praxi se velmi často dostaneme do situace, v níž je nutné serializovat nebo deserializovat rozsáhlejší datové struktury obsahující hodnoty různých typů. Připomeňme si například, jakým způsobem se zapsala hlavička IHDR do souboru ve formátu PNG:

struct.pack("!2I5B", width, height, 8, 2, 0, 0, 0)

V tomto případě se zapíše dvojice čtyřbajtových celých čísel a následně pětice bajtů.

Ovšem právě u podobných datových struktur je nutné vyřešit zarovnání hodnot a případnou existenci nebo neexistenci výplňových bajtů na konci takové struktury. V balíčku struct můžeme explicitně určovat, že se použijí výplňové bajty, s využitím specifikátoru „x“ (například „4ד značí čtyři výplňové bajty, ukládají se do nich nuly). To je sice užitečné, ale k dispozici máme ještě jednu možnost – specifikovat, že zarovnání a výplně se mají řešit přesně tak, jak to dělají céčkové překladače na dané platformě. Tím bude zajištěna možnost komunikace mezi Pythonem a céčkem. Tento režim je povolený v případě, že prvním znakem v řetězci s formátem je znak „@“, nebo se zde žádný specifikátor způsobu uložení nenachází. Navíc tento znak ovlivní i bitové (a tedy i bajtové) šířky numerických hodnot atd.

Poznámka: „@“ je nastaven jako výchozí formát, pokud není zadáno něco jiného!

6. Zarovnání a výplně

Vyzkoušejme si nyní provést serializaci trojice hodnot s formáty „dvoubytové slovo+bajt+dvoubytové slovo“ do binárního bloku, přičemž nejprve použijeme výchozí způsob uložení, dále nativní způsob bez zarovnání a konečně explicitně nastavený nativní způsob se zarovnáním:

import struct
 
bytes1 = struct.pack("hbh", 1, 2, 3)
print(type(bytes1))
print(bytes1.hex(" ", 1))
 
print()
 
bytes2 = struct.pack("=hbh", 1, 2, 3)
print(type(bytes2))
print(bytes2.hex(" ", 1))
 
print()
 
bytes3 = struct.pack("@hbh", 1, 2, 3)
print(type(bytes3))
print(bytes3.hex(" ", 1))

Výsledky serializace budou odlišné, právě kvůli zarovnání u prvního a posledního formátu:

<class 'bytes'>
01 00 02 00 03 00
 
<class 'bytes'>
01 00 02 03 00
 
<class 'bytes'>
01 00 02 00 03 00
Poznámka: zde se tedy výstup odlišuje pouze tím, zda je za prostředním bajtem uložena výplň či nikoli.

Otestujme podobný příklad, ovšem nyní s hodnotami odlišných typů. Pro větší čitelnost výsledků se zde ukládají hodnoty 1, 0×ffff (16bitů) a 3. První a poslední hodnota přitom může být uložena ve čtyřech bajtech (NEnativní serializace) nebo na některých platformách i v odlišném počtu bajtů (nativní serializace):

import struct
 
bytes1 = struct.pack("iHi", 1, 0xffff, 3)
print(type(bytes1))
print(bytes1.hex(" ", 1))
 
print()
 
bytes2 = struct.pack("=iHi", 1, 0xffff, 3)
print(type(bytes2))
print(bytes2.hex(" ", 1))
 
print()
 
bytes3 = struct.pack("@iHi", 1, 0xffff, 3)
print(type(bytes3))
print(bytes3.hex(" ", 1))

Na platformě x86(64) dostaneme tyto výsledky:

<class 'bytes'>
01 00 00 00 ff ff 00 00 03 00 00 00
 
<class 'bytes'>
01 00 00 00 ff ff 03 00 00 00
 
<class 'bytes'>
01 00 00 00 ff ff 00 00 03 00 00 00
Poznámka: opět si povšimněte, že rozdíl spočívá v tom, zda se za 16bitovou hodnotou použije zarovnání resp. zda se vlastně uloží 16bitová či 32bitová hodnota (to nemusíme rozlišovat).

U nativního způsobu zarovnání většinou platí, že kratší prvky (bajty, šestnáctibitové hodnoty) jsou zarovnány na stejnou šířku, jako delší prvky (32bitové a 64bitové hodnoty). Opět si to vyzkoušejme, nyní s krajními prvky typu „i“, což může odpovídat 32bitovým či 64bitovým hodnotám (v NEnativní serializaci jsou to 32bitové hodnoty):

import struct
 
bytes1 = struct.pack("lHl", 1, 0xffff, 3)
print(type(bytes1))
print(bytes1.hex(" ", 1))
 
print()
 
bytes2 = struct.pack("=lHl", 1, 0xffff, 3)
print(type(bytes2))
print(bytes2.hex(" ", 1))
 
print()
 
bytes3 = struct.pack("@lHl", 1, 0xffff, 3)
print(type(bytes3))
print(bytes3.hex(" ", 1))

Výsledky nyní mohou být odlišné v případě, že „i“ bude na dané platformě chápáno jako 64bitová hodnota nebo hodnota zarovnaná na 64bitů:

<class 'bytes'>
01 00 00 00 00 00 00 00 ff ff 00 00 00 00 00 00 03 00 00 00 00 00 00 00
 
<class 'bytes'>
01 00 00 00 ff ff 03 00 00 00
 
<class 'bytes'>
01 00 00 00 00 00 00 00 ff ff 00 00 00 00 00 00 03 00 00 00 00 00 00 00

7. Výpočet velikosti binární struktury se zadaným formátem prvků

V některých situacích, například při blokových přenosech dat, vytváření hlaviček chunků, alokaci paměti atd. je nutné vypočítat, jaká vlastně bude velikost binární struktury se serializovanými daty. Pochopitelně je možné si takovou strukturu vytvořit zavoláním struct.pack a následně získat její délku v bajtech, ovšem to nemusí být nejrychlejší ani paměťově nejefektivnější řešení. Ovšem samotná knihovna struct programátorům dává k dispozici pomocnou funkci nazvanou jednoduše calcsize (resp. struct.calcsize v závislosti na způsobu importu). Této funkci se předává pouze řetězec s popisem formátu použitého při serializaci nebo deserializaci binárních dat a vrací se velikost binárních dat v bajtech:

calcsize(format, /)
    Return size in bytes of the struct described by the format string.
 
Poznámka: tato funkce pochopitelně bere v úvahu i případné zarovnání nebo existenci výplňových bajtů (alignment, padding), jinak by nebyla v praxi použitelná.

8. Velikost binární struktury s výplněmi

Chování výše zmíněné funkce struct.calcsize si opět vyzkoušíme. Nejprve si vytvoříme pomocnou funkci, které se předá řetězec se specifikací formátu. Tato funkce tento řetězec vypíše a taktéž vypíše vypočtenou délku binárních dat (v bajtech) při serializaci struktury popsané formátem:

def size_for_format(format):
    size = struct.calcsize(format)
    print(f"{format:>6}:{size}")

Následně si necháme vypsat délky binárních bloků po serializaci struktury, a to například tímto způsobem:

size_for_format("b")
 
size_for_format("iBi")
size_for_format("=iBi")
size_for_format("@iBi")
Poznámka: povšimněte si, že skutečně můžeme do prvního znaku řetězce vložit informaci o požadovaném zarovnání i bitové šířce numerických datových typů (nativní, NEnativní).

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

import struct
 
def size_for_format(format):
    size = struct.calcsize(format)
    print(f"{format:>6}:{size}")
 
 
size_for_format("b")
 
print()
 
size_for_format("iBi")
size_for_format("=iBi")
size_for_format("@iBi")
 
print()
 
size_for_format("lBl")
size_for_format("=lBl")
size_for_format("@lBl")
 
print()
 
size_for_format("fBf")
size_for_format("=fBf")
size_for_format("@fBf")
 
print()
 
size_for_format("dBd")
size_for_format("=dBd")
size_for_format("@dBd")
 
print()
 
size_for_format("dBBd")
size_for_format("=dBBd")
size_for_format("@dBBd")
 
print()
 
size_for_format("dBBBd")
size_for_format("=dBBBd")
size_for_format("@dBBBd")

Ze zobrazených výsledků je patrné, jaký vliv má znak „=“ na vypočtenou velikost binární struktury a taktéž na jejím interním uspořádání. Tento znak totiž nastavuje „Pythonovský“ formát bez zarovnání a bez výplňových bajtů. Ten je kompatibilní napříč platformami, ovšem není kompatibilní například s céčkem:

     b:1
 
   iBi:12
  =iBi:9
  @iBi:12
 
   lBl:24
  =lBl:9
  @lBl:24
 
   fBf:12
  =fBf:9
  @fBf:12
 
   dBd:24
  =dBd:17
  @dBd:24
 
  dBBd:24
 =dBBd:18
 @dBBd:24
 
 dBBBd:24
=dBBBd:19
@dBBBd:24

9. Doplňkové bajty na konci binární struktury

Existují datové formáty a přenosové protokoly, které vyžadují, aby celá binární datová struktura byla uložena takovým způsobem, že její délka bude násobkem (například) dvou bajtů, čtyř bajtů atd. To v praxi znamená, že na konci takového binárního bloku mají být uloženy výplňové bajty (padding). I binární struktury s takovým formátem můžeme při použití standardního balíčku struct realizovat, a to s využitím malého triku – ve formátovacím řetězci na jeho konci specifikujeme, že se má uložit nula prvků o velikosti dvoubajtového slova, čtyřbajtového slova atd.

Formátovací řetězec by tedy mohl končit znaky „0h“, „0i“, „0l“ atd. V takovém případě Python automaticky do binárních dat doplní n nulových bajtů. Kolik těchto bajtů bude, záleží na délce struktury – výsledná délka však bude dělitelná dvěma, čtyřmi, osmi atd. – podle nastavení.

import struct
 
bytes1 = struct.pack("lB", 1, 0xff)
print(type(bytes1))
print(bytes1.hex(" ", 1))
 
print()
 
bytes2 = struct.pack("lB0h", 1, 0xff)
print(type(bytes2))
print(bytes2.hex(" ", 1))
 
print()
 
bytes3 = struct.pack("lB0i", 1, 0xff)
print(type(bytes3))
print(bytes3.hex(" ", 1))
 
print()
 
bytes4 = struct.pack("lB0l", 1, 0xff)
print(type(bytes4))
print(bytes4.hex(" ", 1))

Povšimněte si výplňových bajtů doplněných do serializovaných dat. Tyto bajty jsou podtrženy:

<class 'bytes'>
01 00 00 00 00 00 00 00 ff
 
<class 'bytes'>
01 00 00 00 00 00 00 00 ff 00
 
<class 'bytes'>
01 00 00 00 00 00 00 00 ff 00 00 00
 
<class 'bytes'>
01 00 00 00 00 00 00 00 ff 00 00 00 00 00 00 00
Poznámka: v případě, že je datová struktura zarovnána, žádné výplňové bajty se nebudou muset přidávat.

10. Krátké zopakování z minula: zápis rastrového obrázku do formátu PNG

Ve druhé části dnešního článku navážeme na demonstrační příklad uvedený minule. Připomeňme si, že se jednalo o skript, na jehož vstupu byly barvy pixelů rastrového obrázku (bitmapy). Skript tento obrázek uložil do formátu PNG, a to včetně korektní hlavičky, rastrových dat kódovaných s využitím algoritmu DEFLATE, korektně vypočtených kontrolních součtů atd. Využili jsme přitom dvojici standardních knihoven, konkrétně knihovny struct (tu si stále popisujeme) a taktéž knihovnu zlib. Celý zdrojový kód skriptu, který nejdříve vypočte barvy pixelů v bitmapě a následně tuto bitmapu uloží do PNG, vypadal následovně:

""" Zápis rastrového obrázku do formátu PNG."""
 
# Inspirace:
# https://stackoverflow.com/questions/902761/saving-a-numpy-array-as-an-image
 
import struct
import zlib
 
PNG_SIGNATURE = b'\x89PNG\r\n\x1a\n'
FILTER_TYPE = b'\x00'
 
def prepare_raw_data(buffer, width, height):
    """Konverze barev pixelů z bufferu do podoby se specifikací filtru na
    každém řádku."""
    raw_data = bytearray()
    offset = 0
    for _ in range(height):
        # nastavit filtr + zkopirovat jeden radek (scanline)
        raw_data += FILTER_TYPE + buffer[offset:offset+width*3]
        # na dalsi radek ve zdrojovem bufferu
        offset += width*3
    return raw_data
 
 
def png_chunk(png_tag, chunk_data):
    """Konstrukce jednoho PNG chunku s tagem i závěrečným kontrolním kódem."""
    chunk_header = png_tag + chunk_data
    return (struct.pack("!I", len(chunk_data)) +
            chunk_header +
            struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_header)))
 
 
def write_png(buffer, width, height):
    """Uložení rastrového obrázku z bufferu do PNG."""
    raw_data = prepare_raw_data(buffer, width, height)
 
    return b''.join([
        PNG_SIGNATURE,
        png_chunk(b'IHDR', struct.pack("!2I5B", width, height, 8, 2, 0, 0, 0)),
        png_chunk(b'IDAT', zlib.compress(raw_data, level=9, wbits=15)),
        png_chunk(b'IEND', b'')])
 
 
WIDTH = 256
HEIGHT = 256
 
# buffer pro rastrová data
pixels = bytearray(WIDTH*HEIGHT*3)
 
# vybarvení testovacího obrázku
index = 0
for i in range(HEIGHT):
    for j in range(WIDTH):
        pixels[index] = 0xff
        index+=1
        pixels[index] = i
        index+=1
        pixels[index] = j
        index+=1
 
data = write_png(pixels, WIDTH, HEIGHT)
with open("test.png", 'wb') as fout:
    fout.write(data)

11. Formát PNG

Formát PNG je interně relativně jednoduchý. Začíná osmibajtovou signaturou s přesně specifikovanými hodnotami bajtů (nesmí se lišit) a poté následuje série takzvaných chunků, tj. bloků proměnné délky. Každý chunk se přitom skládá ze čtyř částí:

  1. První část má konstantní velikost čtyř bajtů a obsahuje celkovou délku datové části chunku. Teoreticky je tedy maximální délka datové části rovna 232-1 bytům; avšak pro snazší implementaci, například v těch programovacích jazycích, které nemají implementovaný beznaménkový datový typ (v té době se jednalo například o Javu), je maximální hodnota délky rovna „pouze“ 231-1 bytům. V praxi jsou však délky chunků pochopitelně o několik řádů menší.
  2. Druhá část chunku má opět velikost čtyři byty. Obsahuje jméno (typ) chunku ve formátu čtyř ASCII znaků malé i velké anglické abecedy. Jedná se o ASCII kódy v rozsahu 65–90 a 97–122 decimálně. Jedná se o velmi dobře navržený způsob pojmenování, protože jméno chunku může být načteno a následně rozpoznáno pouhou jednou operací porovnání (32 bitových integerů) bez nutnosti implementace řetězcového porovnání a současně je typ chunku velmi dobře čitelný i pro člověka. Velikost znaků ve jménu (minusky/verzálky) dále určuje, zda je daný chunk pro zpracování obrázku volitelný či povinný.
  3. Třetí část chunku je tvořena vlastními daty. Tato část může v některých případech mít nulovou délku.
  4. Poslední čtvrtá část chunku má délku čtyři byty a obsahuje kontrolní součet (CRC) druhé a třetí části, tj. jména (typu) chunku a uložených dat. Vzhledem k tomu, že délka chunku není do kontrolního součtu zahrnuta, je možné CRC generovat přímo při zápisu dat bez nutnosti druhého průchodu v případě, že délka chunku není dopředu známá (například při komprimaci). Použitý polynom má tvar:
    x32+x26+x23+x22+x16+x12+x­11+x10+x8+x7+x5+x4+x2+x+1

12. Testovací obrázek, který budeme načítat a analyzovat

Pro otestování dále popsaného programového kódu určeného pro analýzu dat uložených ve formátu PNG byl vytvořen jednoduchý testovací obrázek dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/test.png, jenž má délku 132 bajtů. Tento obrázek má rozlišení 1×1 pixel a obsahuje barvovou paletu, informaci o průhledné barvě (transparency) a taktéž vložený informační text. Podívejme se na hexadecimální výpis obsahu tohoto souboru. Na pravé straně jsou zobrazeny tisknutelné znaky nalezené v souboru, z nichž je při podrobnějším zkoumání patrné, kde se nacházejí jednotlivé chunky nazvané IHDR, PLTE, tRNS, IDAT, iTXt a IEND:

00000000: 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52  .PNG........IHDR
00000010: 00 00 00 01 00 00 00 01 08 03 00 00 00 28 cb 34  .............(.4
00000020: bb 00 00 00 03 50 4c 54 45 00 00 00 a7 7a 3d da  .....PLTE....z=.
00000030: 00 00 00 01 74 52 4e 53 00 40 e6 d8 66 00 00 00  ....tRNS.@..f...
00000040: 0a 49 44 41 54 08 5b 63 60 00 00 00 02 00 01 62  .IDAT.[c`......b
00000050: 40 4f 68 00 00 00 19 69 54 58 74 6c 69 6e 6b 00  @Oh....iTXtlink.
00000060: 00 00 77 77 77 2e 72 6f 6f 74 2e 63 7a 00 2d 67  ..www.root.cz.-g
00000070: 00 31 2e 30 10 df f9 79 00 00 00 00 49 45 4e 44  .1.0...y....IEND
00000080: ae 42 60 82                                      .B`.

Právě z tohoto binárního souboru se budeme pokoušet číst jednotlivé údaje.

13. Čtení signatury souborů PNG

Celé čtení a interpretaci informací ze souboru, který obsahuje (nebo by alespoň měl obsahovat) rastrový obrázek uložený ve formátu PNG, pro jednoduchost provedeme v bloku with, ve kterém příslušný soubor otevřeme v režimu binárního čtení. Tím zajistíme, že znaky pro konce řádku atd. nebudou interpretovány tak, jakoby se jednalo o textový soubor (ano, ani po 45 letech od vydání první verze DOSu, jsme se tohoto problému nezbavili):

with open("test.png", "rb") as fin:
     # v tomto bloku budeme postupně číst jednotlivé části PNG

Na začátku souborů ve formátu PNG se musí nacházet takzvaná signatura, což je sekvence osmi bajtů s konstantními hodnotami:

89  50  4e  47  0d  0a  1a  0a

V Pythonu můžeme signaturu zapsat následujícím způsobem:

PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
Poznámka: povšimněte si bajtů se znaky \r a \n, které slouží pro zjištění, zda nedošlo k nějakému pokusu o převod na textová data. První bajt má hodnotu větší než 128 a měl by zajistit, že textové editory tento soubor detekují jako binární a nikoli textový. Současně první dva bajty představují magic number pro ty operační systémy, které zjišťují typ souboru právě na základě prvních bajtů. Znak \x slouží k tomu, aby se v DOSu pozastavil výpis binárních dat na terminál po zadání příkazu type. A konečně poslední znak \n opět slouží k detekci, jestli se se souborem pracuje v binárním nebo textovém formátu (opět se vracíme k prvnímu DOSu z roku 1980 :-).

Signaturu načteme snadno:

# nacteni signatury
signature = fin.read(len(PNG_SIGNATURE))

Následně zkontrolujeme její délku, tedy vlastně to, jestli soubor obsahuje alespoň osm bajtů. i obsah signatury:

# kontrola signatury
assert len(signature) == len(PNG_SIGNATURE)
assert signature == PNG_SIGNATURE

14. Načtení hlaviček jednotlivých chunků

Ihned za signaturou PNG následují jednotlivé chunky (začínat by se konkrétně mělo chunkem IHDR). Každý chunk začíná hlavičkou reprezentovanou jednoduchou strukturou:

Typ dat Význam
4 bajty délka chunku (network byte order)
4 znaky jméno chunku

Příslušný formát tedy můžeme popsat řetězcem:

"!I4s"

Realizace programové smyčky, ve které se postupně načítají jednotlivé chunky, tedy může v první variantě vypadat následovně:

while True:
    # nacteni hlavicky chunku
    chunk_header = fin.read(8)
    if len(chunk_header) < 8:
        print(f"End of file with remaining {len(chunk_header)} bytes")
        break
    # hlavicka obsahuje delku (4 bajty) a ctyri znaky se jmenem
    s = struct.Struct("!I4s")
    length, png_tag = s.unpack(chunk_header)
    ...
    ...
    ...
Poznámka: povšimněte si způsobu detekce konce souboru. V případě, že se nenačte celá hlavička chunku (osm bajtů), znamená to, že jsme dosáhli konce souboru. U korektních souborů PNG by měla poslední zpráva vypsaná skriptem vypadat přesně takto:
End of file with remaining 0 bytes

Pokud se vypíše jiné číslo 1–7, je soubor nějakým způsobem poškozen.

15. Přeskok datové části chunku a přečtení kontrolního součtu

Za hlavičkou chunku následuje datová část, která má proměnlivou délku. Tuto část přeskočíme velmi snadno (bez čtení dat):

# preskocit data chunku
fin.seek(length, 1)

V posledních čtyřech bajtech chunku je uložen kontrolní součet (CRC32). Tuto hodnotu načteme následovně:

# nacteni CRC32 chunku
c = struct.Struct("!I")
crc_block = fin.read(4)
if len(crc_block) < 4:
    print("Error: not correct CRC block!")
    break
crc = c.unpack(crc_block)[0]
Poznámka: opět si povšimněte toho, jak se kontrolují případné chyby v souboru – chunk v korektním PNG musí obsahovat kontrolní součet.

Nyní již umíme přečíst celý obsah PNG a vypsat si základní informace o jednotlivých chuncích. Přitom se nesnažíme číst data, která nepotřebujeme (přeskakujeme je). Realizace může vypadat následovně:

while True:
    # nacteni hlavicky chunku
    chunk_header = fin.read(8)
    if len(chunk_header) < 8:
        print(f"End of file with remaining {len(chunk_header)} bytes")
        break
    # hlavicka obsahuje delku (4 bajty) a ctyri znaky se jmenem
    s = struct.Struct("!I4s")
    length, png_tag = s.unpack(chunk_header)
 
    # preskocit data chunku
    fin.seek(length, 1)
 
    # nacteni CRC32 chunku
    c = struct.Struct("!I")
    crc_block = fin.read(4)
    if len(crc_block) < 4:
        print("Error: not correct CRC block!")
        break
    crc = c.unpack(crc_block)[0]
    print(f"{png_tag.decode("ASCII")}  {length:5}   {crc:04x}")

16. Úplný zdrojový kód příkladu, který přečte chunky uložené v souboru formátu PNG

Podívejme se nyní na úplný zdrojový kód příkladu, po jehož spuštění se postupně načte a analyzuje soubor „test.png“, který obsahuje obrázek ve formátu PNG. Skript postupně vypíše všechny chunky, které se v souboru nachází, jejich velikost i kontrolní součet:

"""Informace o obrázku uloženého ve formátu PNG."""
 
import struct
 
PNG_SIGNATURE = b'\x89PNG\r\n\x1a\n'
 
with open("test.png", "rb") as fin:
    # nacteni signatury
    signature = fin.read(len(PNG_SIGNATURE))
 
    # kontrola signatury
    assert len(signature) == len(PNG_SIGNATURE)
    assert signature == PNG_SIGNATURE
 
    # postupne nacteni jednotlivych chunku
    print("Chunk  Length   CRC")
    while True:
        # nacteni hlavicky chunku
        chunk_header = fin.read(8)
        if len(chunk_header) < 8:
            print(f"End of file with remaining {len(chunk_header)} bytes")
            break
        # hlavicka obsahuje delku (4 bajty) a ctyri znaky se jmenem
        s = struct.Struct("!I4s")
        length, png_tag = s.unpack(chunk_header)
 
        # preskocit data chunku
        fin.seek(length, 1)
 
        # nacteni CRC32 chunku
        c = struct.Struct("!I")
        crc_block = fin.read(4)
        if len(crc_block) < 4:
            print("Error: not correct CRC block!")
            break
        crc = c.unpack(crc_block)[0]
        print(f"{png_tag.decode("ASCII")}  {length:5}   {crc:04x}")

Zkusme si nyní tento skript spustit, přičemž se v aktuálním adresáři nachází i soubor „test.png“. Měli bychom získat tento výstup:

Chunk  Length   CRC
IHDR     13   28cb34bb
PLTE      3   a77a3dda
tRNS      1   40e6d866
IDAT     10   62404f68
iTXt     25   10dff979
IEND      0   ae426082
End of file with remaining 0 bytes
Poznámka: povšimněte si, že se zdá, že soubor je korektní, protože na konci nám nezbyly žádné bajty naví – celý obsah souboru je tvořen jen hlavičkou PNG, která je následována sérií chunků.

17. Načtení informací uložených v chunku IHDR

Pro zajímavost se podívejme na způsob přečtení informací, které jsou uloženy v hlavičce, tedy konkrétně v chunku nazvaném IHDR. Tento chunk má konstantní délku a nepoužívá se zde žádné zarovnání ani doplnění bajtů, tj. ani alignment ani padding. Vícebajtové prvky používají network byte order, tj. big endian. Datová část chunku IHDR obsahuje následující položky:

Offset Počet byte Význam
00 4 šířka obrázku uvedená v pixelech
04 4 výška obrázku uvedená v pixelech
08 1 bitová hloubka pixelů v barvovém kanálu (povolené hodnoty jsou 1, 2, 4, 8 a 16)
09 1 typ kódování barev (viz další tabulka)
0a 1 použitá metoda komprimace (musí zde být 0-deflate)
0b 1 použitá metoda filtrace (musí zde být 0-adaptivní filtrace)
0c 1 prokládání obrázku (0-bez prokládání, 1-prokládání)

Jak by tedy vypadal řetězec s popisem formátu této binární struktury? Můžeme použít delší zápis:

"!IIBBB"

nebo kratší zápis se specifikací opakování prvků stejného typu:

"!2I5B"
Poznámka: první znak opět značí network byte order, tj. vlastně big endian.

Do programové smyčky se čtením chunků můžeme zařadit větev, která přečte informace z hlavičky:

if png_tag == b"IHDR":
    # hlavicku nacist celou - ocekava se tento format:
    # Width:              4 bytes
    # Height:             4 bytes
    # Bit depth:          1 byte
    # Color type:         1 byte
    # Compression method: 1 byte
    # Filter method:      1 byte
    # Interlace method:   1 byte
    h = struct.Struct("!2I5B")
    chunk_data = fin.read(length)
    width, height, bit_depth, color_type, compression, filter, interlace = (
        h.unpack(chunk_data)
    )
else:
    # preskocit data chunku
    fin.seek(length, 1)
Poznámka: v praxi by pravděpodobně bylo lepší, kdyby existovala mapa (asociativní pole) mezi jmény chunků a funkcemi, které se zavolají pro zpracování příslušného chunku. Alternativně by taktéž bylo možné využít pattern matching, který se již pomalu stává součástí zdrojových kódů reálných projektů.

Na konci skriptu informace přečtené z hlavičky vypíšeme. Pro výpis informací o barvovém prostoru použijeme pomocnou mapu color_type_desc. Formát PNG totiž povoluje skutečně pouze hodnoty 0, 2, 3, 4 a 6:

Typ kódování barev Bitová hloubka Popis
0 1,2,4,8,16 Obrázek ve stupních šedi (popř. černobílá pérovka)
2 8,16 Plnobarevný (true color) obrázek typu RGB
3 1,2,4,8 Obrázek s barvovou paletou (v souboru musí existovat chunk PLTE)
4 8,16 Obrázek ve stupních šedi a průhledností (alfa kanálem) – nepříliš používaná kombinace
6 8,16 Obrázek, kde každý pixel obsahuje všechny čtyři hodnoty RGBA (tj. kromě tří barvových složek i alfa kanál)

Realizace výpisu informací z hlavičky PNG:

color_type_desc = [
    "grayscale",
    "unknown",
    "RGB",
    "color palette",
    "grayscale+alpha",
    "unknown",
    "RGBA",
]
 
 
print(f"Resolution:  {width}x{height}")
print(f"Bit depth:   {bit_depth} bpp")
print(f"Color type:  {color_type} = {color_type_desc[color_type]}")
print(f"Compression: {compression}")
print(f"Filter type: {filter}")
print(f"Interlace:   {interlace}")

18. Úplný zdrojový kód skriptu, který vypíše strukturu PNG souboru i přesné informace z hlavičky

V samotném závěru dnešního článku je ukázán úplný zdrojový kód skriptu, který po svém spuštění vypíše nejenom všechny chunky uložené ve formátu PNG, ale i obsah hlavičky, tj. dat v chunku IHDR:

linux_sprava_tip

"""Informace o obrázku uloženého ve formátu PNG."""
 
import struct
 
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
 
with open("test.png", "rb") as fin:
    # nacteni signatury
    signature = fin.read(len(PNG_SIGNATURE))
 
    # kontrola signatury
    assert len(signature) == len(PNG_SIGNATURE)
    assert signature == PNG_SIGNATURE
 
    # postupne nacteni jednotlivych chunku
    print("Chunk  Length   CRC")
    while True:
        # nacteni hlavicky chunku
        chunk_header = fin.read(8)
        if len(chunk_header) < 8:
            print(f"End of file with remaining {len(chunk_header)} bytes")
            break
        # hlavicka obsahuje delku (4 bajty) a ctyri znaky se jmenem
        s = struct.Struct("!I4s")
        length, png_tag = s.unpack(chunk_header)
 
        if png_tag == b"IHDR":
            # hlavicku nacist celou - ocekava se tento format:
            # Width:              4 bytes
            # Height:             4 bytes
            # Bit depth:          1 byte
            # Color type:         1 byte
            # Compression method: 1 byte
            # Filter method:      1 byte
            # Interlace method:   1 byte
            h = struct.Struct("!2I5B")
            chunk_data = fin.read(length)
            width, height, bit_depth, color_type, compression, filter, interlace = (
                h.unpack(chunk_data)
            )
        else:
            # preskocit data chunku
            fin.seek(length, 1)
 
        # nacteni CRC32 chunku
        c = struct.Struct("!I")
        crc_block = fin.read(4)
        if len(crc_block) < 4:
            print("Error: not correct CRC block!")
            break
        crc = c.unpack(crc_block)[0]
        print(f"{png_tag.decode("ASCII")}  {length:5}   {crc:04x}")
 
 
color_type_desc = [
    "grayscale",
    "unknown",
    "RGB",
    "color palette",
    "grayscale+alpha",
    "RGBA",
]
 
 
print(f"Resolution:  {width}x{height}")
print(f"Bit depth:   {bit_depth} bpp")
print(f"Color type:  {color_type} = {color_type_desc[color_type]}")
print(f"Compression: {compression}")
print(f"Filter type: {filter}")
print(f"Interlace:   {interlace}")

Výsledky, které získáme po spuštění tohoto skriptu, pokud se použije obrázek „test.png“ z repositáře:

Chunk  Length   CRC
IHDR     13   28cb34bb
PLTE      3   a77a3dda
tRNS      1   40e6d866
IDAT     10   62404f68
iTXt     25   10dff979
IEND      0   ae426082
End of file with remaining 0 bytes
Resolution:  1x1
Bit depth:   8 bpp
Color type:  3 = color palette
Compression: 0
Filter type: 0
Interlace:   0

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

Všechny demonstrační příklady využívající standardní knihovnu struct lze nalézt v repositáři https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady:

# Příklad Stručný popis Adresa příkladu
1 bytes_type.py konstrukce hodnoty typu bytes https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytes_type.py
2 bytes_literal.py literál popisující hodnotu typu bytes https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytes_literal.py
3 bytes_literal_no_ascii.py literál s neplatnými znaky https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytes_literal_no_ascii­.py
4 bytes_hex.py převod hodnoty typu bytes na řetězec s hexadecimálními hodnotami https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytes_hex.py
5 bytes_fromhex.py převod řetězce s hexadecimálními hodnotami na sekvenci bajtů https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytes_fromhex.py
6 bytes_fromhex_err.py převod řetězce s nezarovnanými hexadecimálními hodnotami na sekvenci bajtů https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytes_fromhex_err.py
7 bytes_are_immutable.py test, zda jsou hodnoty typu bytes skutečně neměnitelné https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytes_are_immutable.py
8 bytes_encode.py převod řetězce do zadaného kódování https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytes_encode.py
       
9 bytearray_type.py konstrukce hodnoty typu bytearray https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytearray_type.py
10 bytearray_is_mutable.py test, zda jsou hodnoty typu bytearray měnitelné https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytearray_is_mutable.py
11 bytearray_hex.py převod hodnoty typu bytearray na řetězec s hexadecimálními hodnotami https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytearray_hex.py
12 bytearray_fromhex.py převod řetězce s hexadecimálními hodnotami na měnitelné pole bajtů https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytearray_fromhex.py
13 bytearray_encode.py převod ASCII řetězce do zadaného kódování https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytearray_encode.py
14 bytearray_encode2.py převod řetězce s nabodeníčky do zadaného kódování https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/bytearray_encode2.py
       
14 string_to_bytes.py převod řetězce na sekvenci bajtů https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/string_to_bytes.py
       
15 struct_help.py nápověda k balíčku struct https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/struct_help.py
16 pack_byte.py uložení hodnoty typu byte do sekvence bajtů https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/pack_byte.py
17 pack_byte_negative.py pokus o uložení záporné hodnoty jakoby se jednalo o bajt bez znaménka https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/pack_byte_negative.py
18 pack_words.py uložení celočíselné numerické hodnoty ve formě slova s různou bitovou šířkou https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/pack_words.py
19 pack_words_endianess.py řízení little/big endian při ukládání slov https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/pack_words_endianess.py
20 pack_floats.py uložení hodnoty typu float do sekvence bajtů https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/pack_floats.py
21 pack_png_header1.py uložení části hlavičky grafického formátu PNG, první verze https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/pack_png_header1.py
22 pack_png_header2.py uložení části hlavičky grafického formátu PNG, druhá verze https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/pack_png_header2.py
23 write_png.py zápis bufferu s rastrovým obrázkem do formátu PNG https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/write_png.py
       
24 unpack_method.py základní použití metody struct.unpack pro načtení hodnot z binárních dat https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/unpack_method.py
25 unpack_byte.py načtení jediného bajtu z binárních dat https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/unpack_byte.py
26 unpack_word1.py vliv endianessu na načtenou vícebajtovou hodnotu, první příklad https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/unpack_word1.py
27 unpack_word2.py vliv endianessu na načtenou vícebajtovou hodnotu, druhý příklad https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/unpack_word2.py
28 unpack_word3.py vliv endianessu na načtenou vícebajtovou hodnotu, třetí příklad https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/unpack_word3.py
       
29 pack_alignment1.py zarovnání hodnot v binárních datech, první příklad https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/pack_alignment1.py
30 pack_alignment2.py zarovnání hodnot v binárních datech, druhý příklad https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/pack_alignment2.py
31 pack_alignment3.py zarovnání hodnot v binárních datech, třetí příklad https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/pack_alignment3.py
32 calcsize.py výpočet velikosti binární struktury na základě zadaného formátu https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/calcsize.py
33 pack_padding.py výplň struktury bajty přidanými na konec https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/pack_padding.py
       
34 png_info1.py přečtení základních informací ze souborů typu PNG https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/png_info1.py
35 png_info2.py přečtení celé hlavičky souborů typu PNG https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/png_info2.py
36 png_info3.py kontrola CRC jednotlivých chunků https://github.com/tisnik/most-popular-python-libs/blob/master/binary_struc­ts/png_info3.py

20. Odkazy na Internetu

  1. Python standard types: bytes
    https://docs.python.org/3­.8/library/stdtypes.html#by­tes
  2. Python standard types: bytearray
    https://docs.python.org/3­.8/library/stdtypes.html#by­tearray-objects
  3. Bytes and Bytearray Operations
    https://docs.python.org/3­.8/library/stdtypes.html#by­tes-methods
  4. Standard encodings
    https://docs.python.org/3­.8/library/codecs.html#stan­dard-encodings
  5. class memoryview
    https://docs.python.org/3­.8/library/stdtypes.html#me­moryview
  6. struct – Interpret bytes as packed binary data
    https://docs.python.org/3/li­brary/struct.html
  7. C-like structures in Python
    https://stackoverflow.com/qu­estions/35988/c-like-structures-in-python
  8. python3: bytes vs bytearray, and converting to and from strings
    https://stackoverflow.com/qu­estions/62903377/python3-bytes-vs-bytearray-and-converting-to-and-from-strings
  9. Základní informace o MessagePacku
    https://msgpack.org/
  10. Balíček msgpack na PyPi
    https://pypi.org/project/msgpack/
  11. MessagePack na Wikipedii
    https://en.wikipedia.org/wi­ki/MessagePack
  12. Comparison of data-serialization formats (Wikipedia)
    https://en.wikipedia.org/wi­ki/Comparison_of_data-serialization_formats
  13. Repositáře msgpacku
    https://github.com/msgpack
  14. Specifikace ukládání různých typů dat
    https://github.com/msgpac­k/msgpack/blob/master/spec­.md
  15. Podpora MessagePacku v různých programovacích jazycích
    https://msgpack.org/#languages
  16. Základní implementace formátu msgpack pro programovací jazyk Go
    https://github.com/msgpack/msgpack-go
  17. go-codec
    https://github.com/ugorji/go
  18. Gobs of data (odlišný serializační formát)
    https://blog.golang.org/gobs-of-data
  19. Formát BSON (odlišný serializační formát)
    http://bsonspec.org/
  20. Problematika nulových hodnot v Go, aneb proč nil != nil
    https://www.root.cz/clanky/pro­blematika-nulovych-hodnot-v-go-aneb-proc-nil-nil/
  21. IEEE-754 Floating Point Converter
    https://www.h-schmidt.net/FloatConverter/I­EEE754.html
  22. Base Convert: IEEE 754 Floating Point
    https://baseconvert.com/ieee-754-floating-point
  23. Brain Floating Point – nový formát uložení čísel pro strojové učení a chytrá čidla
    https://www.root.cz/clanky/brain-floating-point-ndash-novy-format-ulozeni-cisel-pro-strojove-uceni-a-chytra-cidla/
  24. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  25. Protocol Buffers
    https://protobuf.dev/
  26. Protocol Buffers
    https://en.wikipedia.org/wi­ki/Protocol_Buffers
  27. What is the difference between Serialization and Marshaling?
    https://stackoverflow.com/qu­estions/770474/what-is-the-difference-between-serialization-and-marshaling
  28. Comparison of data-serialization formats
    https://en.wikipedia.org/wi­ki/Comparison_of_data-serialization_formats
  29. PNG (Portable Network Graphics) Specification, Version 1.2
    http://www.libpng.org/pub/png/spec/1­.2/PNG-Structure.html
  30. Data structure alignment
    https://en.wikipedia.org/wi­ki/Data_structure_alignment
  31. Byte alignment and ordering
    https://www.eventhelix.com/em­bedded/byte-alignment-and-ordering/
  32. The Lost Art of Structure Packing
    http://www.catb.org/esr/structure-packing/
  33. Padding is hard
    https://dave.cheney.net/2015/10/09/pad­ding-is-hard
  34. Structure Member Alignment, Padding and Data Packing
    https://www.geeksforgeeks­.org/structure-member-alignment-padding-and-data-packing/
  35. C Alignment Cheatsheet
    https://github.com/Q1CHENL/c-alignment-cheatsheet
  36. Struct padding rules in Rust
    https://stackoverflow.com/qu­estions/70587534/struct-padding-rules-in-rust
  37. Deflate
    https://en.wikipedia.org/wiki/Deflate
Neutrální ikona do widgetu na odběr článků ze seriálů

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.