Last active
July 21, 2021 08:23
-
-
Save weaming/41f15e9fdda566cd9d9f06d850bd2b79 to your computer and use it in GitHub Desktop.
CLI to convert or assign colorspace of JPEG
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# Author : weaming | |
# Mail : garden.yuen@gmail.com | |
# Created : 2021-07-12 15:07:12 | |
# 解决以下几个方面的问题:色彩空间、文件压缩、EXIF 处理、水印 | |
import argparse | |
import io | |
import os | |
from PIL import Image | |
from PIL import ImageCms | |
ADOBE_RGB = b'\x00\x00\x02Tlcms\x02\x10\x00\x00mntrRGB XYZ \x07\xd0\x00\x08\x00\x0b\x00\x13\x003\x00;acspAPPL\x00\x00\x00\x00none\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xd6\x00\x01\x00\x00\x00\x00\xd3-lcms\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ncprt\x00\x00\x00\xfc\x00\x00\x002desc\x00\x00\x010\x00\x00\x00\x90wtpt\x00\x00\x01\xc0\x00\x00\x00\x14bkpt\x00\x00\x01\xd4\x00\x00\x00\x14rTRC\x00\x00\x01\xe8\x00\x00\x00\x0egTRC\x00\x00\x01\xf8\x00\x00\x00\x0ebTRC\x00\x00\x02\x08\x00\x00\x00\x0erXYZ\x00\x00\x02\x18\x00\x00\x00\x14gXYZ\x00\x00\x02,\x00\x00\x00\x14bXYZ\x00\x00\x02@\x00\x00\x00\x14text\x00\x00\x00\x00Copyright 2000 Adobe Systems Incorporated\x00\x00\x00desc\x00\x00\x00\x00\x00\x00\x00\x11Adobe RGB (1998)\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00A\x00d\x00o\x00b\x00e\x00 \x00R\x00G\x00B\x00 \x00(\x001\x009\x009\x008\x00)\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00XYZ \x00\x00\x00\x00\x00\x00\xf3Q\x00\x01\x00\x00\x00\x01\x16\xccXYZ \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00curv\x00\x00\x00\x00\x00\x00\x00\x01\x023\x00\x00curv\x00\x00\x00\x00\x00\x00\x00\x01\x023\x00\x00curv\x00\x00\x00\x00\x00\x00\x00\x01\x023\x00\x00XYZ \x00\x00\x00\x00\x00\x00\x9c\x18\x00\x00O\xa5\x00\x00\x04\xfcXYZ \x00\x00\x00\x00\x00\x004\x8d\x00\x00\xa0,\x00\x00\x0f\x95XYZ \x00\x00\x00\x00\x00\x00&1\x00\x00\x10/\x00\x00\xbe\x9c' | |
S_RGB = b'\x00\x00\x02Tlcms\x040\x00\x00mntrRGB XYZ \x07\xe5\x00\x07\x00\x0c\x00\x04\x005\x00\x10acspAPPL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xd6\x00\x01\x00\x00\x00\x00\xd3-lcms\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0bdesc\x00\x00\x01\x08\x00\x00\x00>cprt\x00\x00\x01H\x00\x00\x00Lwtpt\x00\x00\x01\x94\x00\x00\x00\x14chad\x00\x00\x01\xa8\x00\x00\x00,rXYZ\x00\x00\x01\xd4\x00\x00\x00\x14bXYZ\x00\x00\x01\xe8\x00\x00\x00\x14gXYZ\x00\x00\x01\xfc\x00\x00\x00\x14rTRC\x00\x00\x02\x10\x00\x00\x00 gTRC\x00\x00\x02\x10\x00\x00\x00 bTRC\x00\x00\x02\x10\x00\x00\x00 chrm\x00\x00\x020\x00\x00\x00$mluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00"\x00\x00\x00\x1c\x00s\x00R\x00G\x00B\x00 \x00I\x00E\x00C\x006\x001\x009\x006\x006\x00-\x002\x00.\x001\x00\x00mluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x000\x00\x00\x00\x1c\x00N\x00o\x00 \x00c\x00o\x00p\x00y\x00r\x00i\x00g\x00h\x00t\x00,\x00 \x00u\x00s\x00e\x00 \x00f\x00r\x00e\x00e\x00l\x00yXYZ \x00\x00\x00\x00\x00\x00\xf6\xd6\x00\x01\x00\x00\x00\x00\xd3-sf32\x00\x00\x00\x00\x00\x01\x0cB\x00\x00\x05\xde\xff\xff\xf3%\x00\x00\x07\x93\x00\x00\xfd\x90\xff\xff\xfb\xa1\xff\xff\xfd\xa2\x00\x00\x03\xdc\x00\x00\xc0nXYZ \x00\x00\x00\x00\x00\x00o\xa0\x00\x008\xf5\x00\x00\x03\x90XYZ \x00\x00\x00\x00\x00\x00$\x9f\x00\x00\x0f\x84\x00\x00\xb6\xc3XYZ \x00\x00\x00\x00\x00\x00b\x97\x00\x00\xb7\x87\x00\x00\x18\xd9para\x00\x00\x00\x00\x00\x03\x00\x00\x00\x02ff\x00\x00\xf2\xa7\x00\x00\rY\x00\x00\x13\xd0\x00\x00\n[chrm\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\xa3\xd7\x00\x00T{\x00\x00L\xcd\x00\x00\x99\x9a\x00\x00&f\x00\x00\x0f\\' | |
DISPLAY_P3 = b'\x00\x00\x02$appl\x04\x00\x00\x00mntrRGB XYZ \x07\xe1\x00\x07\x00\x07\x00\r\x00\x16\x00 acspAPPL\x00\x00\x00\x00APPL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xd6\x00\x01\x00\x00\x00\x00\xd3-appl\xca\x1a\x95\x82%\x7f\x10M8\x99\x13\xd5\xd1\xea\x15\x82\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ndesc\x00\x00\x00\xfc\x00\x00\x00ecprt\x00\x00\x01d\x00\x00\x00#wtpt\x00\x00\x01\x88\x00\x00\x00\x14rXYZ\x00\x00\x01\x9c\x00\x00\x00\x14gXYZ\x00\x00\x01\xb0\x00\x00\x00\x14bXYZ\x00\x00\x01\xc4\x00\x00\x00\x14rTRC\x00\x00\x01\xd8\x00\x00\x00 chad\x00\x00\x01\xf8\x00\x00\x00,bTRC\x00\x00\x01\xd8\x00\x00\x00 gTRC\x00\x00\x01\xd8\x00\x00\x00 desc\x00\x00\x00\x00\x00\x00\x00\x0bDisplay P3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00text\x00\x00\x00\x00Copyright Apple Inc., 2017\x00\x00XYZ \x00\x00\x00\x00\x00\x00\xf3Q\x00\x01\x00\x00\x00\x01\x16\xccXYZ \x00\x00\x00\x00\x00\x00\x83\xdf\x00\x00=\xbf\xff\xff\xff\xbbXYZ \x00\x00\x00\x00\x00\x00J\xbf\x00\x00\xb17\x00\x00\n\xb9XYZ \x00\x00\x00\x00\x00\x00(8\x00\x00\x11\x0b\x00\x00\xc8\xb9para\x00\x00\x00\x00\x00\x03\x00\x00\x00\x02ff\x00\x00\xf2\xa7\x00\x00\rY\x00\x00\x13\xd0\x00\x00\n[sf32\x00\x00\x00\x00\x00\x01\x0cB\x00\x00\x05\xde\xff\xff\xf3&\x00\x00\x07\x93\x00\x00\xfd\x90\xff\xff\xfb\xa2\xff\xff\xfd\xa3\x00\x00\x03\xdc\x00\x00\xc0n' | |
CS = {'sRGB': S_RGB, 'AdobeRGB': ADOBE_RGB, 'DisplayP3': DISPLAY_P3, 'null': None} | |
# http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf | |
adobe_to_xyz = ( | |
0.57667, 0.18556, 0.18823, 0, | |
0.29734, 0.62736, 0.07529, 0, | |
0.02703, 0.07069, 0.99134, 0, | |
) | |
# http://brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html | |
displayP3_to_xyz = ( | |
0.4497288, 0.3162486, 0.1844926, 0, | |
0.2446525, 0.6720283, 0.0833192, 0, | |
0.0251848, 0.1411824, 0.9224628, 0, | |
) | |
# http://en.wikipedia.org/wiki/SRGB | |
xyz_to_srgb = ( | |
3.2406, -1.5372, -0.4986, 0, | |
-0.9689, 1.8758, 0.0415, 0, | |
0.0557, -0.2040, 1.0570, 0, | |
) | |
def adobe_to_srgb(image, force=False): | |
if is_display_p3(image): | |
return image.convert('RGB', displayP3_to_xyz).convert('RGB', xyz_to_srgb) | |
if is_adobe_rgb(image) or force: | |
return image.convert('RGB', adobe_to_xyz).convert('RGB', xyz_to_srgb) | |
print('WARN: Colorspace is not Adobe RGB, return directly') | |
return image | |
def is_adobe_rgb(image): | |
return b'Adobe RGB' in image.info.get('icc_profile', b'') | |
def is_display_p3(image): | |
return b'Apple Inc' in image.info.get('icc_profile', b'') | |
def exif_transpose(img): | |
""" | |
根据 EXIF 纠正图片方向 | |
https://medium.com/@ageitgey/the-dumb-reason-your-fancy-computer-vision-app-isnt-working-exif-orientation-73166c7d39da | |
""" | |
if not img: | |
return img | |
exif_orientation_tag = 274 | |
# Check for EXIF data (only present on some files) | |
if hasattr(img, "_getexif") and isinstance(img._getexif(), dict) and exif_orientation_tag in img._getexif(): | |
exif_data = img._getexif() | |
orientation = exif_data[exif_orientation_tag] | |
# Handle EXIF Orientation | |
if orientation == 1: | |
# Normal image - nothing to do! | |
pass | |
elif orientation == 2: | |
# Mirrored left to right | |
img = img.transpose(Image.FLIP_LEFT_RIGHT) | |
elif orientation == 3: | |
# Rotated 180 degrees | |
img = img.rotate(180) | |
elif orientation == 4: | |
# Mirrored top to bottom | |
img = img.rotate(180).transpose(Image.FLIP_LEFT_RIGHT) | |
elif orientation == 5: | |
# Mirrored along top-left diagonal | |
img = img.rotate(-90, expand=True).transpose(Image.FLIP_LEFT_RIGHT) | |
elif orientation == 6: | |
# Rotated 90 degrees | |
img = img.rotate(-90, expand=True) | |
elif orientation == 7: | |
# Mirrored along top-right diagonal | |
img = img.rotate(90, expand=True).transpose(Image.FLIP_LEFT_RIGHT) | |
elif orientation == 8: | |
# Rotated 270 degrees | |
img = img.rotate(90, expand=True) | |
return img | |
parser = argparse.ArgumentParser() | |
parser.add_argument("file", nargs='+') | |
parser.add_argument("--output", help='output path of file (single) or directory (multiple)') | |
parser.add_argument("--quality", type=int, default=85, help='quality of output') | |
parser.add_argument("-c", "--colorspace", default='sRGB', choices=CS.keys()) | |
parser.add_argument("-f", "--from-adobe-rgb", action='store_true', default=False, help='force converting from Adobe RGB colorspace') | |
args = parser.parse_args() | |
for f in args.file: | |
with Image.open(f) as im: | |
print(f) | |
print('-'*100) | |
print(im) | |
icc_profile = im.info.get('icc_profile') | |
# print(icc_profile, type(icc_profile)) | |
if icc_profile == ADOBE_RGB: | |
print('Colorspace is AdobeRGB') | |
elif icc_profile == S_RGB: | |
print('Colorspace is sRGB') | |
elif icc_profile == DISPLAY_P3: | |
print('Colorspace is Display P3') | |
else: | |
print('Unknown colorspace:', icc_profile) | |
output = args.output | |
if not output: | |
output = os.path.join('output', f) | |
elif os.path.isdir(output): | |
output = os.path.join(output, f) | |
try: | |
os.makedirs(os.path.dirname(output)) | |
except Exception as e: | |
pass | |
if output: | |
im = exif_transpose(im) | |
if args.colorspace == 'sRGB': | |
print('Convert to sRGB...') | |
im = adobe_to_srgb(im, args.from_adobe_rgb) | |
im.save(output, "JPEG", icc_profile=CS[args.colorspace], quality=args.quality, subsampling=0 if isinstance(im, Image.Image) else 'keep') | |
print('saved as', output, 'with colorspace', args.colorspace) | |
print() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment