編寫您自己的影像外掛程式

Pillow 使用外掛程式模型,讓您可以在不變更程式庫本身的情況下,將自己的解碼器和編碼器新增至程式庫。這類外掛程式通常會有類似 XxxImagePlugin.py 的名稱,其中 Xxx 是唯一的格式名稱 (通常是縮寫)。

警告

Pillow >= 2.1.0 不再自動匯入 Python 路徑中任何名稱結尾為 ImagePlugin.py 的檔案。您需要手動匯入您的影像外掛程式。

Pillow 會分兩個階段解碼檔案

  1. 它會依載入順序迴圈處理可用的影像外掛程式,並使用檔案的前 16 個位元組呼叫外掛程式的 _accept 函式。如果 _accept 函式傳回 true,則會呼叫外掛程式的 _open 方法來設定影像中繼資料和影像圖塊。_open 方法並非用於解碼實際的影像資料。

  2. 當要求影像資料時,會呼叫 ImageFile.load 方法,該方法會為每個圖塊設定解碼器並將資料饋送至其中。

影像外掛程式應包含衍生自 PIL.ImageFile.ImageFile 基底類別的格式處理常式。此類別應提供 _open 方法,該方法會讀取檔案標頭並至少設定內部 _size_mode 屬性,以便填入 modesize。為了能夠載入檔案,此方法也必須建立 tile 描述項的清單,其中包含解碼器名稱、圖塊的範圍以及任何解碼器專屬的資料。必須透過呼叫 Image 模組,明確註冊格式處理常式類別。

注意

為了效能考量,_open 方法快速拒絕不包含適當內容的檔案非常重要。

範例

以下外掛程式支援簡單的格式,該格式具有 128 位元組的標頭,其中包含「SPAM」字詞,後接寬度、高度和像素大小 (以位元為單位)。標頭欄位以空格分隔。影像資料緊接在標頭之後,可以是雙層、灰階或 24 位元真彩色。

SpamImagePlugin.py:

from PIL import Image, ImageFile


def _accept(prefix: bytes) -> bool:
    return prefix[:4] == b"SPAM"


class SpamImageFile(ImageFile.ImageFile):

    format = "SPAM"
    format_description = "Spam raster image"

    def _open(self) -> None:

        header = self.fp.read(128).split()

        # size in pixels (width, height)
        self._size = int(header[1]), int(header[2])

        # mode setting
        bits = int(header[3])
        if bits == 1:
            self._mode = "1"
        elif bits == 8:
            self._mode = "L"
        elif bits == 24:
            self._mode = "RGB"
        else:
            msg = "unknown number of bits"
            raise SyntaxError(msg)

        # data descriptor
        self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 128, (self.mode, 0, 1))]


Image.register_open(SpamImageFile.format, SpamImageFile, _accept)

Image.register_extensions(
    SpamImageFile.format,
    [
        ".spam",
        ".spa",  # DOS version
    ],
)

格式處理常式必須一律設定內部 _size_mode 屬性,以便填入 sizemode。如果未設定這些屬性,則無法開啟檔案。為了簡化外掛程式,呼叫程式碼會將 SyntaxErrorKeyErrorIndexErrorEOFErrorstruct.error 之類的例外視為無法識別檔案。

請注意,必須使用 PIL.Image.register_open() 明確註冊影像外掛程式。雖然非必要,但最好也註冊此格式使用的任何延伸檔名。

匯入外掛程式後,即可使用

from PIL import Image
import SpamImagePlugin

with Image.open("hopper.spam") as im:
    pass

tile 屬性

為了能夠讀取檔案以及只是識別檔案,也必須設定 tile 屬性。此屬性包含圖塊描述項的清單,其中每個描述項都會指定應該如何將資料載入至影像中的指定區域。

在大多數情況下,只會使用單一描述項,涵蓋整個影像。PsdImagePlugin.PsdImageFile 會使用多個圖塊來合併單一層內的通道,因為通道會分別依序儲存。

圖塊描述項是具有以下內容的 4 元組

(decoder, region, offset, parameters)

欄位的使用方式如下

解碼器

指定要使用的解碼器。此處使用的 raw 解碼器支援各種像素格式的未壓縮資料。如需此解碼器的詳細資訊,請參閱下方的說明。

可以在 _imaging.c 中函式陣列的編碼解碼器區段下查看 C 解碼器的清單。Python 解碼器會註冊在相關的外掛程式內。

區域

指定要在影像中儲存資料的位置的 4 元組。

偏移

從檔案開頭到影像資料的位元組偏移。

參數

解碼器的參數。此欄位的內容取決於圖塊描述項元組中第一個欄位指定的解碼器。如果解碼器不需要任何參數,請對此欄位使用 None

請注意,tile 屬性包含圖塊描述項的清單,而不只是單一描述項。

解碼器

原始解碼器

raw 解碼器用於讀取影像檔案中的未壓縮資料。它可以用於大多數未壓縮檔案格式,例如 PPM、BMP、未壓縮 TIFF 和許多其他格式。若要搭配 PIL.Image.frombytes() 函式使用原始解碼器,請使用以下語法

image = Image.frombytes(
    mode, size, data, "raw",
    raw_mode, stride, orientation
    )

在圖塊描述項中使用時,參數欄位應該看起來像這樣

(raw_mode, stride, orientation)

欄位的使用方式如下

raw_mode

檔案中使用的像素配置,用於將資料正確轉換為 PIL 的內部配置。如需可用格式的摘要,請參閱下表。

跨距

影像中兩個連續行之間的位元組距離。如果為 0,則假設影像為緊密排列(行之間沒有填充)。如果省略,則步幅預設為 0。

方向

影像中的第一行是否為螢幕上的頂行 (1) 或底行 (-1)。 如果省略,則方向預設為 1。

原始模式欄位用於決定如何解壓縮資料以符合 PIL 的內部像素佈局。PIL 支援大量原始模式;如需完整清單,請參閱 Unpack.c 模組中的表格。下表描述了一些常用的 原始模式

模式

描述

1

1 位元雙色調,儲存時最左邊的像素位於最高
有效位。0 表示黑色,1 表示白色。

1;I

1 位元反轉雙色調,儲存時最左邊的像素位於
最高有效位。0 表示白色,1 表示黑色。

1;R

1 位元反向雙色調,儲存時最左邊的像素位於
最低有效位。0 表示黑色,1 表示白色。

L

8 位元灰階。0 表示黑色,255 表示白色。

L;I

8 位元反轉灰階。0 表示白色,255 表示黑色。

P

8 位元調色盤對應影像。

RGB

24 位元真彩色,儲存為 (紅色、綠色、藍色)。

BGR

24 位元真彩色,儲存為 (藍色、綠色、紅色)。

RGBX

24 位元真彩色,儲存為 (紅色、綠色、藍色、填充)。 填充
像素可能有所不同。

RGB;L

24 位元真彩色,行交錯 (首先是所有紅色像素,然後是
所有綠色像素,最後是所有藍色像素)。

請注意,對於最常見的情況,原始模式與模式相同。

Python 影像函式庫支援許多其他解碼器,包括 JPEG、PNG 和 PackBits。如需詳細資訊,請參閱 decode.c 原始碼檔案和函式庫隨附的標準外掛程式實作。

解碼浮點數資料

PIL 提供一些特殊的機制,允許您將各種格式載入到模式 F (浮點數) 影像記憶體中。

您可以使用 raw 解碼器讀取資料以任何標準機器資料類型封裝的影像,使用下列原始模式之一

模式

描述

F

32 位元原生浮點數。

F;8

8 位元無號整數。

F;8S

8 位元帶號整數。

F;16

16 位元小端無號整數。

F;16S

16 位元小端帶號整數。

F;16B

16 位元大端無號整數。

F;16BS

16 位元大端帶號整數。

F;16N

16 位元原生無號整數。

F;16NS

16 位元原生帶號整數。

F;32

32 位元小端無號整數。

F;32S

32 位元小端帶號整數。

F;32B

32 位元大端無號整數。

F;32BS

32 位元大端帶號整數。

F;32N

32 位元原生無號整數。

F;32NS

32 位元原生帶號整數。

F;32F

32 位元小端浮點數。

F;32BF

32 位元大端浮點數。

F;32NF

32 位元原生浮點數。

F;64F

64 位元小端浮點數。

F;64BF

64 位元大端浮點數。

F;64NF

64 位元原生浮點數。

位元解碼器

如果原始解碼器無法處理您的格式,PIL 也提供特殊的「位元」解碼器,可用於將各種封裝格式讀入浮點數影像記憶體中。

若要將位元解碼器與 PIL.Image.frombytes() 函式搭配使用,請使用下列語法

image = Image.frombytes(
    mode, size, data, "bit",
    bits, pad, fill, sign, orientation
    )

在圖塊描述項中使用時,參數欄位應該看起來像這樣

(bits, pad, fill, sign, orientation)

欄位的使用方式如下

位元

每個像素的位元數 (2-32)。沒有預設值。

填充

行之間的填充,以位元為單位。 如果沒有填充,則為 0,如果行填充至完整位元組,則為 8。如果省略,則填充值預設為 8。

填滿

控制如何將資料新增至解碼器位元緩衝區,以及如何從中儲存資料。

填滿=0

將位元組新增至解碼器緩衝區的 LSB 端;從 MSB 端儲存像素。

填滿=1

將位元組新增至解碼器緩衝區的 MSB 端;從 MSB 端儲存像素。

填滿=2

將位元組新增至解碼器緩衝區的 LSB 端;從 LSB 端儲存像素。

填滿=3

將位元組新增至解碼器緩衝區的 MSB 端;從 LSB 端儲存像素。

如果省略,則填滿順序預設為 0。

符號

如果非零,則位元欄位會進行符號擴充。如果為零或省略,則位元欄位為無號。

方向

影像中的第一行是否為螢幕上的頂行 (1) 或底行 (-1)。 如果省略,則方向預設為 1。

以 C 撰寫您自己的檔案編解碼器

檔案編解碼器的生命週期分為 3 個階段

  1. 設定:Pillow 會在解碼器或編碼器登錄中尋找函式,如果找不到則會回到內部核心影像物件上的名為 [編解碼器名稱]_decoder[編解碼器名稱]_encoder 的函式。將使用 tile 中的 args 元組呼叫該函式。

  2. 轉換:會重複使用影像資料區塊呼叫編解碼器的 decodeencode 函式。

  3. 清除:如果編解碼器已登錄清除函式,則會在轉換程序結束時呼叫該函式,即使發生例外狀況亦然。

設定

目前的慣例是,編解碼器設定函式名為 PyImaging_[編解碼器名稱]DecoderNewPyImaging_[編解碼器名稱]EncoderNew,且定義於 decode.cencode.c 中。其 Python 繫結名為 [編解碼器名稱]_decoder[編解碼器名稱]_encoder,並在函式陣列的編解碼器區段中,從 _imaging.c 檔案中設定。

設定函式需要呼叫 PyImaging_DecoderNewPyImaging_EncoderNew,而且至少要設定 decodeencode 函式指標。此物件中感興趣的欄位包括

decode/encode

解碼或編碼函式的函式指標,其具有存取 imstate 和要轉換的資料緩衝區的權限。

cleanup

清除函式的函式指標,具有存取 state 的權限。

im

目標影像,將由 Pillow 設定。

state

ImagingCodecStateInstance,將由 Pillow 設定。context 成員是不透明的結構,編解碼器可以使用此結構來儲存任何格式特定的狀態或選項。

pulls_fd/pushes_fd

如果解碼器的 pulls_fd 或編碼器的 pushes_fd 設定為 1,則 state->fd 將是指向類似 Python 檔案物件的指標。編解碼器可以使用 codec_fd.c 中的函式,直接使用類似檔案的物件讀取或寫入,而不是透過緩衝區推送資料。

在 3.3.0 版中新增。

轉換

使用目標 (核心) 影像、編解碼器狀態結構和要轉換的資料緩衝區呼叫解碼或編碼函式。

編解碼器有責任從緩衝區中提取盡可能多的資料,並傳回已使用的位元組數。下一次呼叫編解碼器將包含先前未使用的尾部。將在處理資料時多次呼叫編解碼器函式。

或者,如果設定 pulls_fdpushes_fd,則會使用空的緩衝區呼叫解碼或編碼函式一次。編解碼器有責任在該一次呼叫中轉換整個圖磚。使用此方法將提供編解碼器更大的自由度,但如果編解碼器一次將整個圖磚保留在記憶體中,則該自由度可能意味著記憶體使用量增加。

如果發生錯誤,請設定 state->errcode 並傳回 -1。

在成功時傳回 -1,而不設定錯誤碼。

清除

在編解碼器傳回負值或發生錯誤後,會呼叫清除函式。此函式應釋放任何已配置的記憶體,並釋放來自外部程式庫的任何資源。

以 Python 撰寫您自己的檔案編解碼器

Python 檔案解碼器和編碼器應分別衍生自 PIL.ImageFile.PyDecoderPIL.ImageFile.PyEncoder,並且至少應覆寫解碼或編碼方法。應使用 PIL.Image.register_decoder()PIL.Image.register_encoder() 進行登錄。如同檔案編解碼器的 C 實作,Python 型檔案編解碼器的生命週期分為三個階段

  1. 設定:Pillow 會在解碼器或編碼器登錄中尋找編解碼器,然後將類別具現化。

  2. 轉換:會重複使用要解譯的資料緩衝區呼叫執行個體的 decode 方法,或者重複使用要輸出的資料大小呼叫 encode 方法。

    或者,如果解碼器的 _pulls_fd 屬性(或編碼器的 _pushes_fd 屬性)設定為 True,則 decodeencode 將只會被呼叫一次。在解碼器中,可以使用 self.fd 來存取類似檔案的物件。使用此方法將會為編解碼器提供更多自由,但如果整個檔案一次性被編解碼器載入記憶體,則這種自由可能會導致記憶體使用量增加。

    decode 中,一旦資料被解譯,就可以使用 set_as_raw 來填充影像。

  3. 清理:當轉換完成後,會呼叫實例的 cleanup 方法。這可以用來清理編解碼器使用的任何資源。

    但是,如果您將 _pulls_fd_pushes_fd 設定為 True,那麼您可能選擇在 decodeencode 的末尾執行任何清理工作。

有關 PIL.ImageFile.PyDecoder 的範例,請參閱 DdsImagePlugin。有關同時使用 PIL.ImageFile.PyDecoderPIL.ImageFile.PyEncoder 的外掛程式,請參閱 BlpImagePlugin