Following this past question: How Can I Make This Python Script Work With Python 3?
I would like to make this script work in python 3. I've managed to fix some details but it still doesn't work.. now it seems throwing a error when decompressing the chunk data.
Here is the current error I have: Error -5 while decompressing data: incomplete or truncated stream
I'm using Python 3.6.2
And here is the script with things already migrated to Python 3. The script basically normalizes a PNG with custom iphone format.
import pdb
from struct import *
from zlib import *
import stat
import sys
import os
import zlib
def getNormalizedPNG(filename):
pngheader = b"\x89PNG\r\n\x1a\n"
pdb.set_trace()
file = open(filename, "rb")
oldPNG = file.read()
file.close()
if oldPNG[:8] != pngheader:
return None
newPNG = oldPNG[:8]
chunkPos = len(newPNG)
# For each chunk in the PNG file
while chunkPos < len(oldPNG):
# Reading chunk
chunkLength = oldPNG[chunkPos:chunkPos+4]
chunkLength = unpack(">L", chunkLength)[0]
chunkType = oldPNG[chunkPos+4 : chunkPos+8]
chunkData = oldPNG[chunkPos+8:chunkPos+8+chunkLength]
chunkCRC = oldPNG[chunkPos+chunkLength+8:chunkPos+chunkLength+12]
chunkCRC = unpack(">L", chunkCRC)[0]
chunkPos += chunkLength + 12
# Parsing the header chunk
if chunkType == b"IHDR":
width = unpack(">L", chunkData[0:4])[0]
height = unpack(">L", chunkData[4:8])[0]
# Parsing the image chunk
if chunkType == b"IDAT":
try:
pdb.set_trace()
# Uncompressing the image chunk
bufSize = width * height * 4 + height
chunkData = decompress(chunkData, -8, bufSize)
except Exception as e:
print("Already normalized")
print(e)
# The PNG image is normalized
return None
# Swapping red & blue bytes for each pixel
newdata = b""
for y in range(height):
i = len(newdata)
newdata += chunkData[i]
for x in range(width):
i = len(newdata)
newdata += chunkData[i+2]
newdata += chunkData[i+1]
newdata += chunkData[i+0]
newdata += chunkData[i+3]
# Compressing the image chunk
chunkData = newdata
chunkData = compress( chunkData )
chunkLength = len( chunkData )
chunkCRC = crc32(chunkType)
chunkCRC = crc32(chunkData, chunkCRC)
chunkCRC = (chunkCRC + 0x100000000) % 0x100000000
# Removing CgBI chunk
if chunkType != b"CgBI":
newPNG += pack(">L", chunkLength)
newPNG += chunkType
if chunkLength > 0:
newPNG += chunkData
newPNG += pack(">L", chunkCRC)
# Stopping the PNG file parsing
if chunkType == b"IEND":
break
return newPNG
def updatePNG(filename):
data = getNormalizedPNG(filename)
if data != None:
file = open(filename, "wb")
file.write(data)
file.close()
return True
return data
Any clue will be appreciated. Thanks! :)
The original code does not process multiple IDAT
chunks right away; it does the right thing™ and only concatenates them into a single large object before decompressing it as a whole. IDAT
chunks are not separately compressed, but your code assumes they do – and so it fails when there is more than one.
There may be multiple IDAT chunks; if so, they shall appear consecutively with no other intervening chunks. The compressed datastream is then the concatenation of the contents of the data fields of all the IDAT chunks.
11.2.4 IDAT Image data
Re-wiring your loop to first gather all IDAT
s fixes things. Only when an IEND
chunk is found, this data is decompressed, bytes are swapped, and a new IDAT
chunk gets created. The final step, appending an IEND
, closes the file.
from struct import *
from zlib import *
import stat
import sys
import os
import zlib
def getNormalizedPNG(filename):
pngheader = b"\x89PNG\r\n\x1a\n"
file = open(filename, "rb")
oldPNG = file.read()
file.close()
if oldPNG[:8] != pngheader:
return None
newPNG = oldPNG[:8]
chunkPos = len(newPNG)
chunkD = bytearray()
foundCGBi = False
# For each chunk in the PNG file
while chunkPos < len(oldPNG):
# Reading chunk
chunkLength = oldPNG[chunkPos:chunkPos+4]
chunkLength = unpack(">L", chunkLength)[0]
chunkType = oldPNG[chunkPos+4 : chunkPos+8]
chunkData = oldPNG[chunkPos+8:chunkPos+8+chunkLength]
chunkCRC = oldPNG[chunkPos+chunkLength+8:chunkPos+chunkLength+12]
chunkCRC = unpack(">L", chunkCRC)[0]
chunkPos += chunkLength + 12
# Parsing the header chunk
if chunkType == b"IHDR":
width = unpack(">L", chunkData[0:4])[0]
height = unpack(">L", chunkData[4:8])[0]
# Parsing the image chunk
if chunkType == b"IDAT":
# Concatename all image data chunks
chunkD += chunkData
continue
# Stopping the PNG file parsing
if chunkType == b"IEND":
if not foundCGBi:
print ('Already normalized')
return None
bufSize = width * height * 4 + height
chunkData = decompress(chunkD, -8, bufSize)
# Swapping red & blue bytes for each pixel
chunkData = bytearray(chunkData)
offset = 1
for y in range(height):
for x in range(width):
chunkData[offset+4*x],chunkData[offset+4*x+2] = chunkData[offset+4*x+2],chunkData[offset+4*x]
offset += 1+4*width
# Compressing the image chunk
#chunkData = newdata
chunkData = compress( chunkData )
chunkLength = len( chunkData )
chunkCRC = crc32(b'IDAT')
chunkCRC = crc32(chunkData, chunkCRC)
chunkCRC = (chunkCRC + 0x100000000) % 0x100000000
newPNG += pack(">L", chunkLength)
newPNG += b'IDAT'
newPNG += chunkData
newPNG += pack(">L", chunkCRC)
chunkCRC = crc32(chunkType)
newPNG += pack(">L", 0)
newPNG += b'IEND'
newPNG += pack(">L", chunkCRC)
break
# Removing CgBI chunk
if chunkType == b"CgBI":
foundCGBi = True
else:
newPNG += pack(">L", chunkLength)
newPNG += chunkType
if chunkLength > 0:
newPNG += chunkData
newPNG += pack(">L", chunkCRC)
return newPNG
def updatePNG(filename):
data = getNormalizedPNG(filename)
if data != None:
file = open(filename+'_fixed.png', "wb")
file.write(data)
file.close()
return True
return data
updatePNG("broken_image.png")
which results in a valid fixed file.
This code does not restore the broken CgBI
alpha channel! If you need proper alpha transparency, you need to apply the row filters to get straight-up RGB values, invert the alpha, and then apply the inverse of the row filters before compressing again.
You could use the Python wrapper for PNGDefry, which is a C program that indeed performs these missing steps.
Disclaimer: I am the writer of PNGdefry.