| """PNG image manipulation helpers.""" |
| |
| from __future__ import annotations |
| |
| import binascii |
| import struct |
| |
| LEN_IEND = 12 |
| LEN_DEPTH = 22 |
| |
| DEPTH_CHUNK_LEN = struct.pack('!i', 10) |
| DEPTH_CHUNK_START = b'tEXtDepth\x00' |
| IEND_CHUNK = b'\x00\x00\x00\x00IEND\xAE\x42\x60\x82' |
| |
| |
| def read_png_depth(filename: str) -> int | None: |
| """Read the special tEXt chunk indicating the depth from a PNG file.""" |
| with open(filename, 'rb') as f: |
| f.seek(- (LEN_IEND + LEN_DEPTH), 2) |
| depthchunk = f.read(LEN_DEPTH) |
| if not depthchunk.startswith(DEPTH_CHUNK_LEN + DEPTH_CHUNK_START): |
| # either not a PNG file or not containing the depth chunk |
| return None |
| else: |
| return struct.unpack('!i', depthchunk[14:18])[0] |
| |
| |
| def write_png_depth(filename: str, depth: int) -> None: |
| """Write the special tEXt chunk indicating the depth to a PNG file. |
| |
| The chunk is placed immediately before the special IEND chunk. |
| """ |
| data = struct.pack('!i', depth) |
| with open(filename, 'r+b') as f: |
| # seek to the beginning of the IEND chunk |
| f.seek(-LEN_IEND, 2) |
| # overwrite it with the depth chunk |
| f.write(DEPTH_CHUNK_LEN + DEPTH_CHUNK_START + data) |
| # calculate the checksum over chunk name and data |
| crc = binascii.crc32(DEPTH_CHUNK_START + data) & 0xffffffff |
| f.write(struct.pack('!I', crc)) |
| # replace the IEND chunk |
| f.write(IEND_CHUNK) |