Прочитайте IDAT файла PNG с Python - PullRequest
0 голосов
/ 18 января 2020

моя картинка PNG 10x10 для теста

Привет всем, я пытаюсь создать Python скрипт, который читает / записывает файл PNG. Мне не нужен полный вариант скрипта. В этом тесте нет сжатия, нет фильтра, нет чересстрочной развертки, я использую палитру RGB и альфа-палитру (цветовой тип 3) с глубиной 8 бит.

Я просто не понимаю блок IDAT ... Я ожидал список индексных цветов, таких как: 10px Ширина x 10px Высота x 8 Битовая глубина -> 100 байт данных в IDAT, но вместо этого у меня есть 206 байт. (Пожалуйста, исправьте меня, если я ошибаюсь) И диапазон от 0 до 66 для цветов индекса, но он полностью выходит за пределы диапазона.

Если кто-то может объяснить мне, как я должен читать эти данные или что я сделал неправильно, я буду признателен.

это мой код (строка 50 для IDAT):

#!/usr/bin/env python3
with open("smile.png", 'rb') as f:
    hexData = f.read().hex()

#Init cursor_0
cursor_0 = 0

#check signature (8 bytes)
start = cursor_0
stop = cursor_0+(8*2)
cursor_0 = stop
if hexData[start:stop] != "89504e470d0a1a0a":
    print("signature fail")

#Read each Chunk
read = True
while read:
    #NEW CHUNK

    #read length of the chunk (4 bytes)
    start = cursor_0
    stop = cursor_0+(4*2)
    cursor_0 = stop
    chunkDataLength = int(hexData[start:stop],16)

    #read type of the chunk (4 bytes)
    start = cursor_0
    stop = cursor_0+(4*2)
    cursor_0 = stop
    chunkTypeHex = hexData[start:stop]
    chunkType = bytes.fromhex(hexData[start:stop]).decode()

    #read the data of the chunk (variable)
    start = cursor_0
    stop = cursor_0+(chunkDataLength*2)
    cursor_0 = stop
    chunkDataHex = hexData[start:stop]

    #read the CRC of the chunk (4 bytes)
    start = cursor_0
    stop = cursor_0+(4*2)
    cursor_0 = stop
    chunkCrcHex = hexData[start:stop]

    #Decode

    #Init cursor_1
    cursor_1 = 0

    if chunkType == "IHDR":
        print(chunkType)

        #check the pDataLength
        if chunkDataLength != 13:
            print("unexpected pDataLength: "+ chunkDataLength)

        #Width (4 bytes)
        start = cursor_1
        stop = cursor_1+(4*2)
        cursor_1 = stop
        width = int(chunkDataHex[start:stop])
        print("Width: "+str(width))

        #Height (4 bytes)
        start = cursor_1
        stop = cursor_1+(4*2)
        cursor_1 = stop
        height = int(chunkDataHex[start:stop])
        print("Height: "+str(height))

        #Bit Depth (1 byte)
        start = cursor_1
        stop = cursor_1+(1*2)
        cursor_1 = stop
        bitDepth = int(chunkDataHex[start:stop])
        print("Bit Depth: "+str(bitDepth))

        #Color Type (1 byte)
        start = cursor_1
        stop = cursor_1+(1*2)
        cursor_1 = stop
        colorType = int(chunkDataHex[start:stop])
        print("ColorType: "+str(colorType))

        #Compression Method (1 byte)
        start = cursor_1
        stop = cursor_1+(1*2)
        cursor_1 = stop
        compressionMethod = int(chunkDataHex[start:stop])
        print("Compression Method: "+str(compressionMethod))

        #Filter Method (1 byte)
        start = cursor_1
        stop = cursor_1+(1*2)
        cursor_1 = stop
        filterMethod = int(chunkDataHex[start:stop])
        print("Filter Method: "+str(filterMethod))

        #Interlace Method (1 byte)
        start = cursor_1
        stop = cursor_1+(1*2)
        cursor_1 = stop
        interlaceMethod = int(chunkDataHex[start:stop])
        print("Interlace Method: "+str(interlaceMethod))

    elif chunkType == "PLTE":
        print(chunkType)

        print(str(int(chunkDataLength/3)) + " Colors")

        while cursor_1 < chunkDataLength*2:
            #RED (1 byte)
            start = cursor_1
            stop = cursor_1+(1*2)
            cursor_1 = stop
            red = chunkDataHex[start:stop]

            #GREEN (1 byte)
            start = cursor_1
            stop = cursor_1+(1*2)
            cursor_1 = stop
            green = chunkDataHex[start:stop]

            #BLUE (1 byte)
            start = cursor_1
            stop = cursor_1+(1*2)
            cursor_1 = stop
            blue = chunkDataHex[start:stop]

            color = red+green+blue
            #print("Color: "+ color)

    elif chunkType == "tRNS":
        print(chunkType)

        print(str(int(chunkDataLength)) + " Transparent Colors")

        while cursor_1 < chunkDataLength*2:
            #Transparent Color (1 byte)
            start = cursor_1
            stop = cursor_1+(1*2)
            cursor_1 = stop
            transparent = chunkDataHex[start:stop]

            #print("Transparent Color: "+ transparent)

    elif chunkType == "IDAT":
        print(chunkType)

        #>>>1ST TRY
        while cursor_1 < chunkDataLength*bitDepth/8*2:
            start = int(cursor_1)
            stop = int(cursor_1 + bitDepth/8*2)
            cursor_1 = stop
            colorIndex = int(chunkDataHex[start:stop],16)
            print("ColorIndex: "+str(colorIndex))


        #>>>2ND TRY
        #translate Hexadecimal to Binary
        chunkDataBin = bin(int(chunkDataHex,16))
        #print("len(chunkDataBin)/8="+str(len(chunkDataBin)/8))
        #print("chunkDataLength="+str(chunkDataLength))

        #start at 2 for jumping the 0b prefixe
        cursor_1 = 2

        while cursor_1 < chunkDataLength*bitDepth:
            start = cursor_1
            stop = cursor_1 + bitDepth
            cursor_1 = stop
            colorIndex = int(chunkDataBin[start:stop],2)
            #print("ColorIndex: "+str(colorIndex))

    elif chunkType == "IEND":
        print(chunkType)
        #If END OF FILE detected, break the loop
        read = False

    else:
        print("PyPng script can't handle " + chunkType + " chunk type")

Ответы [ 2 ]

0 голосов
/ 19 января 2020

Если вы планируете «извлекать» данные PNG вручную, рекомендуется оставить браузер открытым в соответствии с официальными спецификациями . Пиксельные данные изображений PNG сжимаются, как объяснено в 10. Сжатие . После распаковки вы получите поток двоичных данных, состоящий из высота прогонов, каждый из которых имеет filter_type + ширина × pixel_data (где ширина и высота прямо определены в блоке IHDR, и точный формат pixel_data - битовая глубина и альфа - должен быть получен из Цветового типа флаг IHDR.

Таким образом, шаг 1 должен использовать zlib для распаковки двоичного фрагмента. После распаковки вы получите 16 × (1 + 16) = 272 байта.

Следующим шагом является итерация по строкам и применение метода фильтрации каждой строки, чтобы (наконец!) Получить список фактических значений в формате, определяемом Color Type.

Каждый из типов фильтров использует в качестве входных данных ранее декодированную строку (номер строки -1 считается всеми нулями), к которой применяется функция с новыми байтами. К счастью, ваше примерное изображение использует только фильтр строк № 0: нет, что самый простой в реализации, поскольку он заменяет только старое значение новым. Для более полного PNG-декодера вам потребуется реализовать все 4 фильтра.

Это приводит к следующему:

elif chunkType == "IDAT":
    print(chunkType)

    chunkDataBin=zlib.decompress(bytes.fromhex(chunkDataHex))
    print (len(chunkDataBin))

    # create the initial (empty) row
    processRow = [0] * width
    for y in range(height):
        rowFilter = chunkDataBin[y*(width+1)]
        print ("Row Filter: %d; pixel data " % rowFilter, end='')
        for x in range(width):
            colorIndex = chunkDataBin[y*(width+1)+1+x]
            # process filters

            # Filter: None
            if rowFilter == 0:
                processRow[x] = colorIndex
            else:
                # raise an error for not implemented filters
                raise ValueError("Filter type %d is not implemented" % rowFilter)
        for x in range(width):
            print("%02X " % processRow[x], end='')
        print ()

Обработка блока IDAT, как только вы столкнетесь с ним работает для вашего тестового файла, но в соответствии со спецификациями может быть несколько IDAT кусков. Правильный способ справиться с этим - сохранить глобальный объект, к которому вы объединяете все встречающиеся куски IDAT (они также должны быть последовательными). Только после того, как вы объедините их все в один поток, вы можете использовать zlib для распаковки.

0 голосов
/ 18 января 2020

Хорошо, мой плохой ... я думаю, что я забыл сжатие zlib ... Этот блок выглядит лучше, но у меня все еще 273 пикселя из 100 ...

   elif chunkType == "IDAT":
        print(chunkType)

        chunkDataBin=zlib.decompress(bytes.fromhex(chunkDataHex))

        while cursor_1 < len(chunkDataBin):         
            colorIndex= chunkDataBin[cursor_1]
            cursor_1 += 1
            print("colorIndex: "+str(colorIndex))

Итак, у меня больше нет данных. Каждая строка выглядит как начало с индекса 0.

Это изображение имеет ширину 16 пикселей на высоту 16 пикселей (а не ширину 10 пикселей на высоту 10 пикселей, как я уже сказал).

Я все еще работал с шестнадцатеричным числом вместо десятичной:

        #Width (4 octets)
        start = cursor_1
        stop = cursor_1+(4*2)
        cursor_1 = stop
        width = int(chunkDataHex[start:stop],16)
        print("Width: "+str(width))

        #Height (4 octets)
        start = cursor_1
        stop = cursor_1+(4*2)
        cursor_1 = stop
        height = int(chunkDataHex[start:stop],16)
        print("Height: "+str(height))

        #Bit Depth (1 octets)
        start = cursor_1
        stop = cursor_1+(1*2)
        cursor_1 = stop
        bitDepth = int(chunkDataHex[start:stop],16)
        print("Bit Depth: "+str(bitDepth))

        #Color Type (1 octets)
        start = cursor_1
        stop = cursor_1+(1*2)
        cursor_1 = stop
        colorType = int(chunkDataHex[start:stop],16)
        print("ColorType: "+str(colorType))

        #Compression Method (1 octets)
        start = cursor_1
        stop = cursor_1+(1*2)
        cursor_1 = stop
        compressionMethod = int(chunkDataHex[start:stop],16)
        print("Compression Method: "+str(compressionMethod))

        #Filter Method (1 octets)
        start = cursor_1
        stop = cursor_1+(1*2)
        cursor_1 = stop
        filterMethod = int(chunkDataHex[start:stop],16)
        print("Filter Method: "+str(filterMethod))

        #Interlace Method (1 octets)
        start = cursor_1
        stop = cursor_1+(1*2)
        cursor_1 = stop
        interlaceMethod = int(chunkDataHex[start:stop],16)
        print("Interlace Method: "+str(interlaceMethod))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...