PEP 368 – Standard-Bildprotokoll und -klasse
- Autor:
- Lino Mastrodomenico <l.mastrodomenico at gmail.com>
- Status:
- Verschoben
- Typ:
- Standards Track
- Erstellt:
- 28. Juni 2007
- Python-Version:
- 2.6, 3.0
- Post-History:
Zusammenfassung
Die aktuelle Situation der Bildspeicherung und -manipulation in der Python-Welt ist extrem fragmentiert: Fast jede Bibliothek, die Bildobjekte verwendet, hat ihre eigene Bildklasse implementiert, die inkompatibel mit allen anderen ist und oft nicht sehr pythonisch ist. Eine grundlegende RGB-Bildklasse existiert in der Standardbibliothek (Tkinter.PhotoImage), ist aber für alles außer Tkinter-Programmierung praktisch unbrauchbar und wird nicht verwendet.
Diese Fragmentierung nimmt nicht nur wertvollen Speicherplatz in den Köpfen der Entwickler ein, sondern macht auch den Austausch von Bildern zwischen verschiedenen Bibliotheken (was in relativ häufigen Anwendungsfällen erforderlich ist) langsamer und komplexer als nötig.
Dieses PEP schlägt vor, die Situation zu verbessern, indem ein einfaches und pythonisches Bildprotokoll/-schnittstelle definiert wird, das hoffentlich von bestehenden Bildklassen innerhalb und außerhalb der Standardbibliothek akzeptiert und implementiert werden kann, *ohne die Abwärtskompatibilität* mit ihren bestehenden Benutzerbasen zu brechen. Praktisch handelt es sich hierbei um die Definition, wie ein minimales *bildähnliches* Objekt aussehen und sich verhalten sollte (ähnlich wie die Methoden read() und write() bei *dateiähnlichen* Objekten).
Die Aufnahme einer Klasse in die Standardbibliothek, die grundlegende Bildmanipulationsfunktionen bietet und das neue Protokoll implementiert, wird ebenfalls vorgeschlagen, zusammen mit einer Mixin-Klasse, die dabei hilft, bestehenden Bildklassen Unterstützung für das Protokoll hinzuzufügen.
PEP Verschiebung
Die weitere Erforschung der in diesem PEP behandelten Konzepte wurde mangels eines aktuellen Vertreters, der an der Förderung der Ziele des PEP interessiert ist und Feedback sammelt und einarbeitet, sowie über ausreichend verfügbare Zeit zur effektiven Umsetzung, zurückgestellt.
Begründung
Ein guter Weg, um hochwertige Module für die Aufnahme in die Python-Standardbibliothek bereitzustellen, ist, einfach auf die natürliche Selektion unter konkurrierenden externen Bibliotheken zu warten, die einen klaren Gewinner mit nützlicher Funktionalität und einer großen Benutzerbasis hervorbringt. Dann kann der De-facto-Standard offiziell sanktioniert werden, indem er in die Standardbibliothek aufgenommen wird.
Leider hat dieser Ansatz bei der Schaffung einer dominanten Bildklasse in der Python-Welt nicht gut funktioniert: Fast jede Drittanbieter-Bibliothek, die ein Bildobjekt benötigt, erstellt ihre eigene Klasse, die mit denen anderer Bibliotheken inkompatibel ist. Dies ist ein echtes Problem, da es für ein Programm durchaus üblich ist, ein Bild z. B. mit PIL (der Python Imaging Library) zu erstellen und zu bearbeiten und es dann mit wxPython oder pygame anzuzeigen. Aber diese Bibliotheken haben unterschiedliche und inkompatible Bildklassen, und die übliche Lösung besteht darin, ein Bild manuell von der Quelle in ein (Breite, Höhe, Bytes-String)-Tupel zu "exportieren" und es durch Erstellen einer neuen Instanz im Zielformat zu "importieren". Dieser Ansatz *funktioniert*, ist aber sowohl hässlicher als auch langsamer als nötig.
Eine andere "Lösung", die manchmal verwendet wurde, ist die Erstellung spezifischer Adapter und/oder Konverter von einer Klasse zu einer anderen (z. B. bietet PIL das Modul ImageTk zur Konvertierung von PIL-Bildern in eine mit der Tkinter-Klasse kompatible Klasse). Aber dieser Ansatz skaliert schlecht mit der Anzahl der beteiligten Bibliotheken und ist für den Benutzer immer noch ärgerlich: Warum sollte ich ein Objekt, das ich habe, konvertieren, bevor ich es an die nächste Methode übergebe, warum kann es nicht einfach mein Bild so akzeptieren, wie es ist?
Das Problem beschränkt sich keineswegs auf die drei genannten Bibliotheken und hat wahrscheinlich mehrere Ursachen, darunter zwei, die meiner Meinung nach sehr wichtig sind, um es zu verstehen, bevor es gelöst wird
- In der heutigen Computerwelt ist ein Bild ein Basistyp, der nicht eng an eine bestimmte Domäne gebunden ist. Deshalb wird es nie einen klaren Gewinner zwischen den Bildklassen der drei genannten Bibliotheken (PIL, wxPython und pygame) geben: Sie decken unterschiedliche Domänen ab und konkurrieren nicht wirklich miteinander;
- Die Python-Standardbibliothek hat nie eine gute Bildklasse bereitgestellt, die von Drittanbietermodulen übernommen oder nachgeahmt werden kann.
Tkinter.PhotoImagebietet grundlegende RGB-Funktionalität, ist aber bei weitem die langsamste und hässlichste der Gruppe und kann nur nach der Erstellung des Tkinter-Hauptfensters instanziiert werden.
Dieses PEP versucht, diese Situation auf vier Arten zu verbessern
- Es definiert ein einfaches und pythonisches Bildprotokoll/-schnittstelle (sowohl auf Python- als auch auf C-Seite), das hoffentlich von bestehenden Bildklassen innerhalb und außerhalb der Standardbibliothek akzeptiert und implementiert werden kann, *ohne die Abwärtskompatibilität* mit ihren bestehenden Benutzerbasen zu brechen.
- Es schlägt die Aufnahme von drei neuen Klassen in die Standardbibliothek vor
ImageMixinstellt fast alles Notwendige für die Implementierung des neuen Protokolls bereit; sein Hauptzweck ist es, die Unterstützung dieser Schnittstelle für bestehende Bibliotheken so einfach wie möglich zu machen, in einigen Fällen so einfach wie das Hinzufügen zur Liste der Basisklassen und geringfügige Ergänzungen zum Konstruktor.Imageist eine Unterklasse vonImageMixinund fügt einen Konstruktor hinzu, der ein Bild zwischen verschiedenen Pixelformaten skalieren und/oder konvertieren kann. Dies soll eine schnelle und effiziente Standardimplementierung des neuen Protokolls bieten.ImageSizeist eine kleine Hilfsklasse. Details siehe unten.
Tkinter.PhotoImagewird das neue Protokoll (hauptsächlich über die KlasseImageMixin) implementieren und alle Tkinter-Methoden, die ein Bild empfangen können, werden modifiziert, um jedes Objekt zu akzeptieren, das die Schnittstelle implementiert. Nebenbei wird der Autor dieses PEP mit den Entwicklern der gängigsten externen Bibliotheken zusammenarbeiten, um das gleiche Ziel zu erreichen (Unterstützung des Protokolls in ihren Klassen und Akzeptanz jeder Klasse, die es implementiert).- Neue
PyImage_*Funktionen werden zur CPython C API hinzugefügt: Sie implementieren die C-Seite des Protokolls und akzeptieren als ersten Parameter **jedes** Objekt, das es unterstützt, auch wenn es keine Instanz der KlassenImage/ImageMixinist.
Die Haupteffekte für den Endbenutzer werden eine Vereinfachung des Austauschs von Bildern zwischen verschiedenen Bibliotheken sein (wenn alles gut geht, wird jede Python-Bibliothek Bilder von jeder anderen Bibliothek akzeptieren) und die sofortige Verfügbarkeit der neuen Klasse Image. Die neue Klasse soll einfache, aber gängige Anwendungsfälle abdecken, wie z. B. das Zuschneiden und/oder Skalieren eines Fotos auf die gewünschte Größe und dessen Übergabe an ein geeignetes Widget zur Anzeige in einem Fenster, oder das Abdunkeln einer Textur und deren Übergabe an eine 3D-Bibliothek.
Die Klasse Image ist nicht dazu gedacht, PIL, Pythonmagick oder NumPy zu ersetzen oder mit ihnen zu konkurrieren, auch wenn sie eine (sehr kleine) Teilmenge der Funktionalität dieser drei Bibliotheken bietet. Insbesondere PIL bietet sehr reiche Bildmanipulationsfunktionen mit *Dutzenden* von Klassen, Filtern, Transformationen und Dateiformaten. Die Aufnahme von PIL (oder etwas Ähnlichem) in die Standardbibliothek mag ein lohnenswertes Ziel sein oder auch nicht, aber sie liegt vollständig außerhalb des Rahmens dieses PEP.
Spezifikation
Das Modul imageop wird als *Standardort* für die neuen Klassen und Objekte verwendet, da es seit langem Funktionen beherbergt, die eine etwas ähnliche Funktionalität boten, aber ein neues Modul kann nach Belieben erstellt werden (z. B. ein neues Modul namens "image" oder "media"; letzteres könnte schließlich andere Multimedia-Klassen enthalten).
MODES ist eine neue Konstante auf Modulebene: sie ist eine Menge von Pixelformaten, die von der Klasse Image unterstützt werden. Jedes Bildobjekt, das das neue Protokoll implementiert, wird garantiert in einem dieser Modi formatiert sein, aber Bibliotheken, die Bilder akzeptieren, dürfen nur eine Teilmenge davon unterstützen.
Diese Modi sind wiederum auch als Konstanten auf Modulebene verfügbar (z. B. imageop.RGB).
Die folgende Tabelle gibt einen Überblick über die derzeit unterstützten Modi und ihre Eigenschaften
| Name | Komponenten-Namen | Bits pro Komponente | Unterabtastung | Gültige Intervalle |
|---|---|---|---|---|
| L | l (kleines L) | 8 | nein | voller Bereich |
| L16 | l | 16 | nein | voller Bereich |
| L32 | l | 32 | nein | voller Bereich |
| LA | l, a | 8 | nein | voller Bereich |
| LA32 | l, a | 16 | nein | voller Bereich |
| RGB | r, g, b | 8 | nein | voller Bereich |
| RGB48 | r, g, b | 16 | nein | voller Bereich |
| RGBA | r, g, b, a | 8 | nein | voller Bereich |
| RGBA64 | r, g, b, a | 16 | nein | voller Bereich |
| YV12 | y, cr, cb | 8 | 1, 2, 2 | 16-235, 16-240, 16-240 |
| JPEG_YV12 | y, cr, cb | 8 | 1, 2, 2 | voller Bereich |
| CMYK | c, m, y, k | 8 | nein | voller Bereich |
| CMYK64 | c, m, y, k | 16 | nein | voller Bereich |
Wenn der Name eines Modus mit einer Zahl endet, stellt er die durchschnittliche Anzahl von Bits pro Pixel dar. Alle anderen Modi verwenden einfach ein Byte pro Komponente pro Pixel.
Keine Paletten-Modi oder Modi mit weniger als 8 Bits pro Komponente werden unterstützt. Willkommen im 21. Jahrhundert.
Hier ist eine kurze Beschreibung der Modi und der Grund für ihre Aufnahme; es gibt vier Gruppen von Modi
- Graustufen (
L*-Modi): Sie werden stark in der wissenschaftlichen Datenverarbeitung verwendet (diese Leute benötigen möglicherweise auch einen sehr hohen Dynamikbereich und eine hohe Präzision, daherL32, der einzige Modus mit 32 Bits pro Komponente) und manchmal kann es nützlich sein, eine einzelne Komponente eines Farbbildes als Graustufenbild zu betrachten (dies wird von den einzelnen Ebenen der planaren Bilder verwendet, sieheYV12unten); der Name der Komponente ('l', kleiner Buchstabe L) steht für Luminanz, die zweite optionale Komponente ('a') ist der Alphawert und repräsentiert die Opazität der Pixel: Alpha = 0 bedeutet volle Transparenz, Alpha = 255/65535 repräsentiert ein vollständig opakes Pixel; - RGB* Modi: die alltäglichen Farbbilder. Die optionale Alpha-Komponente hat die gleiche Bedeutung wie bei Graustufen-Modi;
- YCbCr, auch bekannt als YUV (
*YV12-Modi). Diese Modi sind planar (d. h. die Werte aller Pixel für jede Komponente werden in einem zusammenhängenden Speicherbereich gespeichert, anstatt der üblichen Anordnung, bei der alle Komponenten eines Pixels in aufeinanderfolgenden Bytes liegen) und verwenden eine 1, 2, 2 (auch 4:2:0) Unterabtastung (d. h. jedes Pixel hat seinen eigenen Y-Wert, aber die Cb- und Cr-Komponenten werden zwischen Gruppen von 2x2 benachbarten Pixeln geteilt), da dies das Format ist, das mit Abstand am häufigsten für YCbCr-Bilder verwendet wird. Bitte beachten Sie, dass die V (Cr)-Ebene vor der U (Cb)-Ebene gespeichert wird.YV12wird häufig für MPEG2 (einschließlich DVDs), MPEG4 (sowohl ASP/DivX als auch AVC/H.264) und Theora-Videobilder verwendet. Gültige Werte für Y liegen im Bereich von 16 bis 235 (exklusive 236), und gültige Werte für Cb und Cr liegen im Bereich von 16 bis 240.JPEG_YV12ist ähnlich wieYV12, aber die drei Komponenten können den vollen Bereich von 256 Werten haben. Es ist das native Format, das von fast allen JPEG/JFIF-Dateien und von MJPEG-Videobildern verwendet wird. Die "Seltsamkeit" dieser beiden im Vergleich zu allen anderen unterstützten Modi ergibt sich daraus, dass sie so häufig von vielen bestehenden Bibliotheken und Anwendungen verwendet werden; dies ist auch der Grund für ihre Aufnahme (und die Tatsache, dass sie nicht verlustfrei in RGB konvertiert werden können, da YCbCr ein größerer Farbraum ist); die lustige 4:2:0 planare Anordnung der Pixelwerte ist relativ einfach zu unterstützen, da in den meisten Fällen die drei Ebenen als drei separate Graustufenbilder betrachtet werden können; - CMYK* Modi (Cyan, Magenta, Gelb und Schwarz) sind subtraktive Farbmodi, die für den Druck von Farbbildern auf "toten Bäumen" verwendet werden. Professionelle Designer lieben es, so zu tun, als könnten sie nicht ohne sie leben, also hier sind sie.
Python API
Siehe die Beispiele unten.
In Python 2.x sind alle hier definierten neuen Klassen neue Klassen.
Modusobjekte
Die Modusobjekte bieten eine Reihe von Attributen und Methoden, die zur Implementierung generischer Algorithmen verwendet werden können, die auf verschiedenen Bildtypen arbeiten
Komponenten
Die Anzahl der Komponenten pro Pixel (z. B. 4 für ein RGBA-Bild).
component_names
Ein Tupel von Zeichenketten; siehe die Spalte "Komponenten-Namen" in der obigen Tabelle.
bits_per_component
8, 16 oder 32; siehe "Bits pro Komponente" in der obigen Tabelle.
bytes_per_pixel
Komponenten * bits_pro_komponente // 8, nur für nicht-planare Modi verfügbar (siehe unten).
planar
Boolean;True, wenn die Bildkomponenten jeweils in einer separaten Ebene liegen. Derzeit geschieht dies, wenn und nur wenn der Modus Unterabtastung verwendet.
subsampling
Ein Tupel, das für jede Komponente im Modus ein Tupel aus zwei ganzen Zahlen enthält, die die Menge der Unterabtastung in horizontaler und vertikaler Richtung darstellen. Praktisch ist es((1, 1), (2, 2), (2, 2))fürYV12undJPEG_YV12und((1, 1),) * Komponentenfür alles andere.
x_divisor
max(x for x, y in subsampling); die Breite eines Bildes, das diesen Modus verwendet, muss durch diesen Wert teilbar sein.
y_divisor
max(y for x, y in subsampling); die Höhe eines Bildes, das diesen Modus verwendet, muss durch diesen Wert teilbar sein.
intervals
Ein Tupel, das für jede Komponente im Modus ein Tupel aus zwei ganzen Zahlen enthält: den minimalen und maximalen gültigen Wert für die Komponente. Sein Wert ist((16, 235), (16, 240), (16, 240))fürYV12und((0, 2 ** bits_pro_komponente - 1),) * Komponentenfür alles andere.
get_length(iterable[integer]) -> int
Der Parameter muss ein Iterable sein, das zwei ganze Zahlen enthält: die Breite und Höhe eines Bildes; er gibt die Anzahl der Bytes zurück, die zur Speicherung eines Bildes dieser Abmessungen mit diesem Modus benötigt werden.
Implementierungsdetail: Die Modi sind Instanzen einer Unterklasse von str und haben einen Wert, der ihrem Namen entspricht (z. B. imageop.RGB == 'RGB'), mit Ausnahme von L32, das den Wert 'I' hat. Dies dient nur der Abwärtskompatibilität mit bestehenden PIL-Benutzern; neuer Code, der das hier vorgeschlagene Bildprotokoll verwendet, sollte sich nicht auf dieses Detail verlassen.
Bildprotokoll
Jedes Objekt, das das Bildprotokoll unterstützt, muss die folgenden Methoden und Attribute bereitstellen
Modus
Das Format und die Anordnung der Pixel in diesem Bild; es ist eine der Konstanten in der MengeMODES.
size
Eine Instanz der Klasse ImageSize; es ist ein benanntes Tupel aus zwei ganzen Zahlen: die Breite und die Höhe des Bildes in Pixeln; beide müssen >= 1 sein und können auch über die Attributewidthundheightvonsizeaufgerufen werden.
buffer
Eine Sequenz von ganzen Zahlen zwischen 0 und 255; dies sind die tatsächlichen Bytes, die zur Speicherung der Bilddaten verwendet werden (d. h. die Änderung ihrer Werte beeinflusst die Bildpixel und umgekehrt); die Daten haben eine zeilenmajor/C-kontinuierliche Reihenfolge ohne Auffüllung und ohne spezielle Speicherordnung, auch wenn mehr als 8 Bits pro Komponente vorhanden sind. Die einzigen unterstützten Methoden sind__len__,__getitem__/__setitem__(sowohl mit ganzen Zahlen als auch mit Slice-Indizes) und__iter__; auf der C-Seite implementiert es das Pufferprotokoll.Dies ist eine ziemlich Low-Level-Schnittstelle zum Bild, und der Benutzer ist dafür verantwortlich, die korrekte (native) Byte-Reihenfolge für Modi mit mehr als 8 Bits pro Komponente und die korrekten Wertebereiche für
YV12-Bilder zu verwenden. Ein Puffer kann eine Referenz auf sein Bild behalten oder auch nicht, aber es ist immer noch sicher (wenn auch nutzlos), den Puffer zu verwenden, auch nachdem das entsprechende Bild vom Garbage Collector zerstört wurde (dies erfordert Änderungen an der Bildklasse von wxPython und möglicherweise anderen Bibliotheken). Implementierungsdetail: Dies kann einarray('B'), einbytes()-Objekt oder ein spezialisierter festlängiger Typ sein.
info
Eindict-Objekt, das beliebige Metadaten enthalten kann, die mit dem Bild verknüpft sind (z. B. DPI, Gamma, ICC-Profil, Belichtungszeit...); die Interpretation dieser Daten liegt außerhalb des Rahmens dieses PEP und hängt wahrscheinlich von der Bibliothek ab, die zum Erstellen und/oder Speichern des Bildes verwendet wird; wenn eine Methode des Bildes ein neues Bild zurückgibt, kann sie Metadaten aus ihrem eigeneninfo-Attribut kopieren oder anpassen (dieImageMixin-Implementierung erstellt immer ein neues Bild mit einem leereninfo-Dictionary).
bits_per_componentbytes_per_pixelcomponent_namesKomponentenintervalsplanarsubsamplingAbkürzungen für die entsprechenden Attribute vonmode.*.
map(function[, function...]) -> None
Ordnet für jedes Pixel im Bild jede Komponente durch die entsprechende Funktion zu. Wenn nur eine Funktion übergeben wird, wird diese wiederholt für jede Komponente verwendet. Diese Methode modifiziert das Bild *in-place* und ist normalerweise sehr schnell (die Funktionen werden meist nur wenige Male aufgerufen, möglicherweise nur einmal für einfache Funktionen ohne Verzweigungen), sie auferlegt jedoch eine Reihe von Einschränkungen für die übergebene(n) Funktion(en)
- sie muss ein einzelnes Ganzzahlargument akzeptieren und eine Zahl zurückgeben (
maprundet das Ergebnis auf die nächste ganze Zahl und beschneidet es bei Bedarf aufrange(0, 2 ** bits_pro_komponente));- sie darf nicht versuchen, irgendwelche
BaseException,Exceptionoder irgendeine unbekannte Unterklasse vonExceptionabzufangen, die von einer Operation auf dem Argument ausgelöst wird (Implementierungen können versuchen, die Geschwindigkeit zu optimieren, indem sie lustige Objekte übergeben, sodass sogar ein einfaches"if n == 10:"eine Ausnahme auslösen kann: ignorieren Sie sie einfach,mapkümmert sich darum); das Abfangen anderer Ausnahmen ist in Ordnung;- sie sollte nebenwirkungsfrei sein und ihr Ergebnis sollte nicht von Werten (außer dem Argument) abhängen, die sich während eines einzigen Aufrufs von
mapändern können.
rotate90() -> imagerotate180() -> imagerotate270() -> imageGibt eine Kopie des Bildes zurück, die um 90, 180 oder 270 Grad gegen den Uhrzeigersinn um ihre Mitte gedreht ist.
clip() -> None
Sättigt ungültige Komponentenwerte inYV12-Bildern auf das Minimum oder Maximum (siehemode.intervals). Für andere Bildmodi tut diese Methode nichts, sehr schnell; Bibliotheken, dieYV12-Bilder speichern/exportieren, werden ermutigt, diese Methode immer aufzurufen, da Zwischenoperationen (z. B. diemap-Methode) Pixeln Werte außerhalb der gültigen Intervalle zuweisen können.
split() -> tuple[image]
Gibt ein Tupel vonL-,L16- oderL32-Bildern zurück, die den einzelnen Komponenten im Bild entsprechen.
Planare Bilder unterstützen auch Attribute mit denselben Namen wie in component_names definiert: sie enthalten Graustufenbilder (Modus L), die eine Ansicht auf die Pixelwerte für die entsprechende Komponente bieten; jede Änderung an den Unterbildern wird sofort auf das Elternbild und umgekehrt reflektiert (ihre Puffer verweisen auf denselben Speicherort).
Nicht-planare Bilder bieten die folgenden zusätzlichen Methoden
pixels() -> iterator[pixel]
Gibt einen Iterator zurück, der über alle Pixel in der Zeile iteriert, von oben nach unten und jede Zeile von links nach rechts scannt. Siehe unten für eine Beschreibung der Pixelobjekte.
__iter__() -> iterator[line]
Gibt einen Iterator zurück, der über alle Zeilen des Bildes von oben nach unten iteriert. Siehe unten für eine Beschreibung der Zeilenobjekte.
__len__() -> int
Gibt die Anzahl der Zeilen im Bild zurück (size.height).
__getitem__(integer) -> line
Gibt die Zeile an der angegebenen (y-)Position zurück.
__getitem__(tuple[integer]) -> pixel
Der Parameter muss ein Tupel aus zwei ganzen Zahlen sein; sie werden als x- und y-Koordinaten im Bild interpretiert (0, 0 ist die obere linke Ecke) und ein Pixelobjekt wird zurückgegeben.
__getitem__(slice | tuple[integer | slice]) -> image
Der Parameter muss ein Slice oder ein Tupel sein, das zwei Slices oder eine ganze Zahl und einen Slice enthält; der ausgewählte Bereich des Bildes wird kopiert und ein neues Bild wird zurückgegeben;image[x:y:z]ist äquivalent zuimage[:, x:y:z].
__setitem__(tuple[integer], integer | iterable[integer]) -> None
Modifiziert das Pixel an der angegebenen Position;image[x, y] = integerist eine Abkürzung fürimage[x, y] = (integer,)für Bilder mit einer einzelnen Komponente.
__setitem__(slice | tuple[integer | slice], image) -> None
Wählt einen Bereich auf die gleiche Weise aus wie die entsprechende Form der Methode__getitem__und weist ihm eine Kopie der Pixel aus dem Bild im zweiten Argument zu, das genau denselben Modus wie dieses Bild und dieselbe Größe wie der angegebene Bereich haben muss; die Alpha-Komponente, falls vorhanden, wird einfach kopiert und beeinflusst die anderen Komponenten des Bildes nicht (d. h. es wird kein Alpha-Compositing durchgeführt).
Der mode, die size und der buffer (einschließlich der Speicheradresse des buffer) ändern sich nach der Erstellung eines Bildes nie.
Es wird erwartet, dass, wenn PEP 3118 akzeptiert wird, alle Bildobjekte das neue Pufferprotokoll unterstützen werden, dies liegt jedoch außerhalb des Rahmens dieses PEP.
Image und ImageMixin Klassen
Die Klasse ImageMixin implementiert alle oben beschriebenen Methoden und Attribute außer mode, size, buffer und info. Image ist eine Unterklasse von ImageMixin, die Unterstützung für diese vier Attribute hinzufügt und den folgenden Konstruktor bietet (bitte beachten Sie, dass der Konstruktor nicht Teil des Bildprotokolls ist)
__init__(mode, size, color, source)
modemuss eine der Konstanten in der MengeMODESsein,sizeist eine Sequenz aus zwei ganzen Zahlen (Breite und Höhe des neuen Bildes);colorist eine Sequenz aus ganzen Zahlen, eine für jede Komponente des Bildes, die verwendet wird, um alle Pixel auf denselben Wert zu initialisieren;sourcekann eine Sequenz von ganzen Zahlen der entsprechenden Größe und des entsprechenden Formats sein, die als solche in den Puffer des neuen Bildes oder eines vorhandenen Bildes kopiert wird, oder ein vorhandenes Bild; in Python 2.x kannsourceauch eine Instanz vonstrsein und wird als Byte-Sequenz interpretiert.colorundsourcesind gegenseitig ausschließend, und wenn sie beide weggelassen werden, wird das Bild auf transparentes Schwarz initialisiert (alle Bytes im Puffer haben den Wert 16 im ModusYV12, 255 in den ModiCMYK*und 0 für alles andere). Wennsourcevorhanden ist und ein Bild ist, könnenmodeund/odersizeweggelassen werden; wenn sie angegeben sind und sich vom Quellmodus und/oder der Quellgröße unterscheiden, wird das Quellbild konvertiert.Die genauen Algorithmen für das Skalieren und die Farbraumkonvertierung können zwischen Python-Versionen und Implementierungen variieren, aber sie liefern immer qualitativ hochwertige Ergebnisse (z. B. kann eine kubische Spline-Interpolation für die Hochskalierung und ein Antialiasing-Filter für die Herunterskalierung von Bildern verwendet werden); jede Kombination von Moduskonvertierungen wird unterstützt, aber der Algorithmus für Konvertierungen zu und von den
CMYK*-Modi ist ziemlich naiv: Wenn Sie die exakten Farbprofile Ihrer Geräte haben, möchten Sie vielleicht ein gutes Farbmanagement-Tool wie LittleCMS verwenden. Das neue Bild hat ein leeresinfodict.
Zeilenobjekte
Die Zeilenobjekte (z. B. beim Iterieren über ein Bild zurückgegeben) unterstützen die folgenden Attribute und Methoden
Modus
Der Modus des Bildes, von dem diese Zeile stammt.
__iter__() -> iterator[pixel]
Gibt einen Iterator zurück, der über alle Pixel in der Zeile von links nach rechts iteriert. Siehe unten für eine Beschreibung der Pixelobjekte.
__len__() -> int
Gibt die Anzahl der Pixel in der Zeile zurück (die Bildbreite).
__getitem__(integer) -> pixel
Gibt das Pixel an der angegebenen (x-)Position zurück.
__getitem__(slice) -> image
Der ausgewählte Teil der Zeile wird kopiert und ein neues Bild wird zurückgegeben; das neue Bild wird immer eine Höhe von 1 haben.
__setitem__(integer, integer | iterable[integer]) -> None
Modifiziert das Pixel an der angegebenen Position;line[x] = integerist eine Abkürzung fürline[x] = (integer,)für Bilder mit einer einzelnen Komponente.
__setitem__(slice, image) -> None
Wählt einen Teil der Zeile aus und weist ihm eine Kopie der Pixel aus dem Bild im zweiten Argument zu, das eine Höhe von 1, eine Breite, die dem angegebenen Slice entspricht, und denselben Modus wie diese Zeile haben muss; die Alpha-Komponente, falls vorhanden, wird einfach kopiert und beeinflusst die anderen Komponenten des Bildes nicht (d. h. es wird kein Alpha-Compositing durchgeführt).
Pixelobjekte
Die Pixelobjekte (z. B. beim Iterieren über eine Zeile zurückgegeben) unterstützen die folgenden Attribute und Methoden
Modus
Der Modus des Bildes, von dem dieses Pixel stammt.
value
Ein Tupel von ganzen Zahlen, eine für jede Komponente. Jedes Iterable mit der richtigen Länge kannvaluezugewiesen werden (es wird automatisch in ein Tupel konvertiert), aber Sie können ihm keine einzelne ganze Zahl zuweisen, selbst wenn der Modus nur eine einzige Komponente hat: verwenden Sie stattdessen z. B.pixel.l = 123.
r, g, b, a, l, c, m, y, k
Die Ganzzahlwerte jeder Komponente; nur die für den aktuellen Modus (inmode.component_names) anwendbaren sind verfügbar.
__iter__() -> iterator[int]__len__() -> int__getitem__(integer | slice) -> int | tuple[int]__setitem__(integer | slice, integer | iterable[integer]) -> NoneDiese vier Methoden emulieren eine Liste fester Länge von ganzen Zahlen, eine für jede Pixelkomponente.
ImageSize Klasse
ImageSize ist ein benanntes Tupel, eine Klasse, die mit tuple identisch ist, außer dass
- sein Konstruktor akzeptiert nur zwei ganze Zahlen, Breite und Höhe; sie werden im Konstruktor mithilfe ihrer
__index__()-Methoden konvertiert, sodass alleImageSize-Objekte garantiert nurint- (oder möglicherweiselong- in Python 2.x) Instanzen enthalten; - er hat eine
width- und eineheight-Eigenschaft, die dem ersten bzw. zweiten Wert im Tupel entsprechen; - die von seiner
__repr__-Methode zurückgegebene Zeichenkette ist'imageop.ImageSize(width=%d, height=%d)' % (width, height).
ImageSize wird normalerweise nicht von Endbenutzern instanziiert, kann aber bei der Erstellung einer neuen Klasse, die das Bildprotokoll implementiert, verwendet werden, da das Attribut size eine ImageSize-Instanz sein muss.
C API
Die verfügbaren Bildmodi sind auf C-Ebene als PyImage_*-Konstanten vom Typ PyObject * sichtbar (z.B. ist PyImage_RGB gleich imageop.RGB).
Die folgenden Funktionen bieten eine C-freundliche Schnittstelle zu Modus- und Bildobjekten (alle Funktionen geben im Fehlerfall NULL oder -1 zurück)
int PyImageMode_Check(PyObject *obj)
Gibt wahr zurück, wenn das Objektobjein gültiger Bildmodus ist.
int PyImageMode_GetComponents(PyObject *mode)PyObject* PyImageMode_GetComponentNames(PyObject *mode)int PyImageMode_GetBitsPerComponent(PyObject *mode)int PyImageMode_GetBytesPerPixel(PyObject *mode)int PyImageMode_GetPlanar(PyObject *mode)PyObject* PyImageMode_GetSubsampling(PyObject *mode)int PyImageMode_GetXDivisor(PyObject *mode)int PyImageMode_GetYDivisor(PyObject *mode)Py_ssize_t PyImageMode_GetLength(PyObject *mode, Py_ssize_t width, Py_ssize_t height)Diese Funktionen entsprechen ihren entsprechenden Python-Attributen oder -Methoden.
int PyImage_Check(PyObject *obj)
Gibt wahr zurück, wenn das ObjektobjeinImage-Objekt oder eine Instanz eines Subtyps desImage-Typs ist; siehe auchPyObject_CheckImageunten.
int PyImage_CheckExact(PyObject *obj)
Gibt wahr zurück, wenn das ObjektobjeinImage-Objekt ist, aber keine Instanz eines Subtyps desImage-Typs.
PyObject* PyImage_New(PyObject *mode, Py_ssize_t width, Py_ssize_t height)Gibt eine neueImage-Instanz zurück, die auf transparentes Schwarz initialisiert ist (Details sieheImage.__init__oben).
PyObject* PyImage_FromImage(PyObject *image, PyObject *mode, Py_ssize_t width, Py_ssize_t height)Gibt eine neueImage-Instanz zurück, die mit dem Inhalt desimage-Objekts initialisiert ist, bei Bedarf skaliert und in den angegebenenmodekonvertiert.
PyObject* PyImage_FromBuffer(PyObject *buffer, PyObject *mode, Py_ssize_t width, Py_ssize_t height)Gibt eine neueImage-Instanz zurück, die mit dem Inhalt desbuffer-Objekts initialisiert ist.
int PyObject_CheckImage(PyObject *obj)
Gibt wahr zurück, wenn das Objektobjeine ausreichende Teilmenge des Bildprotokolls implementiert, um von den unten definierten Funktionen akzeptiert zu werden, auch wenn seine Klasse keine Unterklasse vonImageMixinund/oderImageist. Derzeit wird nur die Existenz und Korrektheit der Attributemode,sizeundbuffergeprüft.
PyObject* PyImage_GetMode(PyObject *image)Py_ssize_t PyImage_GetWidth(PyObject *image)Py_ssize_t PyImage_GetHeight(PyObject *image)int PyImage_Clip(PyObject *image)PyObject* PyImage_Split(PyObject *image)PyObject* PyImage_GetBuffer(PyObject *image)int PyImage_AsBuffer(PyObject *image, const void **buffer, Py_ssize_t *buffer_len)Diese Funktionen entsprechen ihren entsprechenden Python-Attributen oder -Methoden; der Bildspeicher kann nur mit dem GIL und einer Referenz auf das Bild oder seinen Puffer zugegriffen werden, und es ist besondere Vorsicht geboten bei Modi mit mehr als 8 Bit pro Komponente: Die Daten werden in nativer Byte-Reihenfolge gespeichert und sind möglicherweise **nicht** auf 2- oder 4-Byte-Grenzen ausgerichtet.
Beispiele
Einige Beispiele für gängige Operationen mit der neuen Image-Klasse und dem Protokoll
# create a new black RGB image of 6x9 pixels
rgb_image = imageop.Image(imageop.RGB, (6, 9))
# same as above, but initialize the image to bright red
rgb_image = imageop.Image(imageop.RGB, (6, 9), color=(255, 0, 0))
# convert the image to YCbCr
yuv_image = imageop.Image(imageop.JPEG_YV12, source=rgb_image)
# read the value of a pixel and split it into three ints
r, g, b = rgb_image[x, y]
# modify the magenta component of a pixel in a CMYK image
cmyk_image[x, y].m = 13
# modify the Y (luma) component of a pixel in a *YV12 image and
# its corresponding subsampled Cr (red chroma)
yuv_image.y[x, y] = 42
yuv_image.cr[x // 2, y // 2] = 54
# iterate over an image
for line in rgb_image:
for pixel in line:
# swap red and blue, and set green to 0
pixel.value = pixel.b, 0, pixel.r
# find the maximum value of the red component in the image
max_red = max(pixel.r for pixel in rgb_image.pixels())
# count the number of colors in the image
num_of_colors = len(set(tuple(pixel) for pixel in image.pixels()))
# copy a block of 4x2 pixels near the upper right corner of an
# image and paste it into the lower left corner of the same image
image[:4, -2:] = image[-6:-2, 1:3]
# create a copy of the image, except that the new image can have a
# different (usually empty) info dict
new_image = image[:]
# create a mirrored copy of the image, with the left and right
# sides flipped
flipped_image = image[::-1, :]
# downsample an image to half its original size using a fast, low
# quality operation and a slower, high quality one:
low_quality_image = image[::2, ::2]
new_size = image.size.width // 2, image.size.height // 2
high_quality_image = imageop.Image(size=new_size, source=image)
# direct buffer access
rgb_image[0, 0] = r, g, b
assert tuple(rgb_image.buffer[:3]) == (r, g, b)
Abwärtskompatibilität
Es gibt drei Bereiche, die von diesem PEP betroffen sind und bei denen die Abwärtskompatibilität berücksichtigt werden muss
- Python 2.6: neue Klassen und Objekte werden dem Modul
imageophinzugefügt, ohne den vorhandenen Modulinhalt zu berühren; neue Methoden und Attribute werden zuTkinter.PhotoImagehinzugefügt und seine__getitem__und__setitem__Methoden werden so modifiziert, dass sie ganze Zahlen, Tupel und Slices akzeptieren (derzeit akzeptieren sie nur Strings). Alle Änderungen stellen eine Obermenge der bestehenden Funktionalität dar, sodass keine größeren Kompatibilitätsprobleme zu erwarten sind. - Python 3.0: die Legacy-Inhalte des Moduls
imageopwerden gemäß PEP 3108 gelöscht; alles, was in diesem Vorschlag definiert ist, wird wie in Python 2.x funktionieren, mit Ausnahme der üblichen 2.x/3.0-Unterschiede (z.B. wird die Unterstützung fürlong-Ganzzahlen und die Interpretation vonstr-Instanzen als Byte-Sequenzen fallen gelassen). - externe Bibliotheken: die Namen und die Semantik der Standard-Bildmethoden und -attribute werden sorgfältig ausgewählt, um es einigen externen Bibliotheken, die Bilder manipulieren (einschließlich mindestens PIL, wxPython und pygame), zu ermöglichen, das neue Protokoll in ihren Bildklassen zu implementieren, ohne die Kompatibilität mit bestehendem Code zu beeinträchtigen. Die einzigen offensichtlichen Konflikte zwischen dem Bildprotokoll und NumPy-Arrays sind der Wert des
size-Attributs und die Koordinatenreihenfolge im Ausdruckimage[x, y].
Referenzimplementierung
Wenn dieser PEP akzeptiert wird, wird der Autor eine Referenzimplementierung der neuen Klassen in reinem Python bereitstellen (die in CPython, PyPy, Jython und IronPython ausgeführt werden kann) und eine zweite, für Geschwindigkeit optimierte Implementierung in Python und C, die für die Aufnahme in die CPython-Standardbibliothek geeignet ist. Der Autor wird auch die erforderlichen Tkinter-Patches einreichen. Für den gesamten Code wird es eine Version für Python 2.x und eine Version für Python 3.0 geben (es wird erwartet, dass die beiden Versionen sehr ähnlich sein werden und die Version für Python 3.0 wahrscheinlich fast vollständig automatisch generiert wird).
Danksagungen
Die Implementierung dieses PEP wird, falls akzeptiert, von Google im Rahmen des Google Summer of Code-Programms gesponsert.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0368.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT