物理の駅 Physics station by 現役研究者

テクノロジーは共有されてこそ栄える

Python: JPG形式の画像ファイルのDPIだけバイナリで書き換える

以下、JPG形式の画像ファイルの物理サイズをA4サイズの横幅にするコード。

PNG形式の記事も参照

def parse_jpg(file_path):
    with open(file_path, 'rb') as f:
        data = f.read()
    
    i = 0
    segments = {}
    while i < len(data):
        assert(data[i] == 0xff)
        marker = data[i:i+2]
        if marker[1] == 0xD8:
            # print("SOI")
            i += 2
            continue
        elif marker[1] == 0xD9:
            # print("EOI")
            break

        segment_length = int.from_bytes(data[i+2:i+4], 'big')
        if marker[1] == 0xE0:  # APP0 segment
            segments["APP0"] = i
        elif marker[1] == 0xC0:  # SOF0 segment (Baseline DCT)
            segments["SOF0"] = i
        elif marker[1] == 0xDA:  # Start Of Scan (SOS)
            segment_length = 0
            j = i + segment_length + 2
            while j<len(data):
                if data[j] == 0xFF and data[j+1] != 0x00:break
                segment_length+=1
                j = i + segment_length + 2
        # print(f"i:{i} marker:0xff{format(marker[1], 'x')} segment_length:{segment_length}")
        i += segment_length + 2

    return segments

def modify_dpi(file_path, app0_segment, sof0_segment):
    with open(file_path, 'rb') as f:
        data = f.read()
    identifier = data[app0_segment+4:app0_segment+4+5].decode('ascii')
    version = (data[app0_segment+4+5], data[app0_segment+4+6])
    units = data[app0_segment+4+7]
    x_density = int.from_bytes(data[app0_segment+4+8:app0_segment+4+10], 'big')
    y_density = int.from_bytes(data[app0_segment+4+10:app0_segment+4+12], 'big')
    precision = data[sof0_segment+4]
    height = int.from_bytes(data[sof0_segment+4+1:sof0_segment+4+3], 'big')
    width = int.from_bytes(data[sof0_segment+4+3:sof0_segment+4+5], 'big')
    colors = data[sof0_segment+4+5]
    
    # print(f"Identifier: {identifier}")
    # print(f"Version   : {version[0]}.{version[1]}")
    # print(f"DPI_Units : {units}")
    # print(f"X_DPI     : {x_density}")
    # print(f"Y_DPI     : {y_density}")
    # print(f"Precision : {precision} bits")
    # print(f"Height    : {height} pixels")
    # print(f"Width     : {width} pixels")
    # print(f"Colors    : {colors}")

    a4_width = 210/25.4 #A4の幅 mm -> inch
    a4_dpi = round(width/a4_width)
    if x_density == a4_dpi:
        print(f"DPI: {x_density} == {a4_dpi}")
        return
    print(f"DPI: {x_density} -> {a4_dpi}")
    data = bytearray(data)
    data[app0_segment+4+7] = 1
    data[app0_segment+4+8:app0_segment+4+10] = a4_dpi.to_bytes(2, 'big')
    data[app0_segment+4+10:app0_segment+4+12] = a4_dpi.to_bytes(2, 'big')
    with open(file_path, 'wb') as f:
        f.write(data)

# ディレクトリ内のすべての jpg ファイルを変換
import glob
for file_path in glob.glob("*.jpg"):
    segments = parse_jpg(file_path)
    modify_dpi(file_path,segments["APP0"],segments["SOF0"])

色々対応させたバージョン

gitlab.com