Skip to content

Instantly share code, notes, and snippets.

@weaming
Last active July 21, 2021 08:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save weaming/41f15e9fdda566cd9d9f06d850bd2b79 to your computer and use it in GitHub Desktop.
Save weaming/41f15e9fdda566cd9d9f06d850bd2b79 to your computer and use it in GitHub Desktop.
CLI to convert or assign colorspace of JPEG
#!/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