JPG形式の記事 と基本的には同じである。
違いは物理サイズ指定の単位が JPGではDPI (dots per inch) であるのに対して、PNGではPPM (pixels per meter) であること、チャンク(セグメント)ごとにCRC-32の巡回冗長検査があるのでそれを書き換える必要があることである。
以下、PNG形式の画像ファイルの物理サイズをA4の横幅にするコード。
def parse_png(file_path): with open(file_path, 'rb') as file: data = file.read() i = 0 signature = data[i:i+8] assert("PNG" in str(signature)) i+=8 chunks = {} while True: length = int.from_bytes(data[i:i+4], 'big') chunk_type = data[i+4:i+8].decode('ascii') if chunk_type == 'IHDR': chunks[chunk_type] = (i,length) elif chunk_type == 'pHYs': chunks[chunk_type] = (i,length) if chunk_type == 'IEND': break i += (12+length) return chunks import zlib def modify_ppm(file_path, chunk_IHDR, chunk_pHYs): with open(file_path, 'rb') as f: data = f.read() width = int.from_bytes(data[chunk_IHDR[0]+8:chunk_IHDR[0]+12], 'big') height = int.from_bytes(data[chunk_IHDR[0]+12:chunk_IHDR[0]+16], 'big') precision = data[chunk_IHDR[0]+16] colors = data[chunk_IHDR[0]+17] x_density = int.from_bytes(data[chunk_pHYs[0]+8:chunk_pHYs[0]+12], 'big') y_density = int.from_bytes(data[chunk_pHYs[0]+12:chunk_pHYs[0]+16], 'big') units = data[chunk_pHYs[0]+16] #CRC-32検証 # chunk_data = data[chunk_pHYs[0]+4:chunk_pHYs[0]+chunk_pHYs[1]+8] # crc_calculated = zlib.crc32(chunk_data) & 0xffffffff # crc_from_file = int.from_bytes(data[chunk_pHYs[0]+chunk_pHYs[1]+8:chunk_pHYs[0]+chunk_pHYs[1]+12],'big') # print(crc_calculated,"vs",crc_from_file) # print(f"PPM_Units : {units}") # print(f"X_PPM : {x_density}") # print(f"Y_PPM : {y_density}") # print(f"Precision : {precision} bits") # print(f"Height : {height} pixels") # print(f"Width : {width} pixels") # print(f"Colors : {colors}") a4_width = 210 a4_ppm = round(width/a4_width*1000) # PPMを計算 if x_density == a4_ppm and units == 1: print(f"DPI: {x_density} == {a4_ppm}") return print(f"PPM: {x_density} -> {a4_ppm}") data = bytearray(data) data[chunk_pHYs[0]+8:chunk_pHYs[0]+12] = a4_ppm.to_bytes(4, 'big') data[chunk_pHYs[0]+12:chunk_pHYs[0]+16] = a4_ppm.to_bytes(4, 'big') data[chunk_pHYs[0]+16] = 1 #新CRC-32書き込み chunk_data = data[chunk_pHYs[0]+4:chunk_pHYs[0]+chunk_pHYs[1]+8] crc_new = zlib.crc32(chunk_data) & 0xffffffff data[chunk_pHYs[0]+chunk_pHYs[1]+8:chunk_pHYs[0]+chunk_pHYs[1]+12] = crc_new.to_bytes(4, 'big') with open(file_path, 'wb') as f: f.write(data) # ディレクトリ内のすべての png ファイルを変換 import glob for file_path in glob.glob("*.png"): chunks = parse_png(file_path) modify_ppm(file_path,chunks["IHDR"],chunks["pHYs"])
色々対応させたバージョン