blob: 66f7c3cb15ae461ada5fcd2480aa23e73c05cfe9 [file] [log] [blame] [edit]
#!/usr/bin/env python
# Make ICC Profile
# References
#
# [ICC 2001] ICC Specification ICC.1:2001-04 (Profile version 2.4.0)
# [ICC 2004] ICC Specification ICC.1:2004-10 (Profile version 4.2.0.0)
"""
Make an ICC Profile.
The default is a grey `mntr` profile.
Specifying any red, green, or blue colorant will
change it to RGB and add default rTRC, gTRC, and bTRC tags.
"""
# Command line examples:
# Make a grey profile that sets codes 0 to 0.07 to black:
# mkiccp.py -kTRC 0.07,0
# An older version of this code was used to make an ICC profile
# suitable for Messenger space probe images, and
# was used to prepare my ancient blog post
# https://drj11.wordpress.com/2009/05/06/what-good-is-pypng-and-some-mdis-pictures-of-earth/
# Make gbr.icc a colour swapper:
# mkiccp.py --rXYZ=0.385,0.717,0.097 --gXYZ=0.143,0.061,0.714 --bXYZ=0.436,0.222,0.014
import argparse
# https://docs.python.org/3.8/library/bisect.html
import bisect
# Local module.
import iccp
import png
# For RGB input the required tags are (See [ICC 2001] 6.3.1.2):
# profileDescription [ICC 2001] 6.4.32
# mediaWhitePoint [ICC 2001] 6.4.25
# copyright [ICC 2001] 6.4.13
def rgb_profile(out, **kwargs):
prof = iccp.Profile().rgb()
prof.addTags(rTRC=2.2, gTRC=2.2, bTRC=2.2)
prof.addTags(**kwargs)
prof.write(out)
# The required tags are (See [ICC 2001] 6.3.1.1):
# - profileDescription [ICC 2001] 6.4.32
# - mediaWhitePoint [ICC 2001] 6.4.25
# - copyright [ICC 2001] 6.4.13
# and for grey profiles
# - grayTRC [ICC 2001] 6.4.19
# and for rgb profiles
# - TRC for red,green,blue
# - XYZ (Colorant) for red,green,blue
def icc_profile(out, **kwargs):
prof = iccp.Profile().greyInput()
if "rXYZ" in kwargs or "gXYZ" in kwargs or "bXYZ" in kwargs:
prof.rgb()
prof.addTags(rTRC=2.2, gTRC=2.2, bTRC=2.2)
if "kTRC" in kwargs:
kwargs["kTRC"] = piecewise_trc(kwargs["kTRC"])
prof.addTags(**kwargs)
prof.write(out)
def piecewise_trc(ps):
"""Make a piecewise linear TRC (Tone Reproduction Curve) and
return a function that implements it.
The TRC is assumed to start at [0 0] and finish at [1 1];
the list `ps` is a list [x1 y1 x2 y2 ... ] of intermediate
points that the curve goes through. Note that the list is
"flat", it consists of x- and y-coordinates alternating.
The xs in the list of points (elements at even index) should
be sorted (in increasing order).
"""
ps = [0, 0] + ps + [1, 1]
xs = [float(x) for x in ps[::2]]
assert sorted(xs) == xs
ys = [float(y) for y in ps[1::2]]
def f(x):
i = bisect.bisect_left(xs, x)
if i == 0:
return ys[0]
e = x - xs[i - 1]
# t is between 0 and 1, the fraction between adjacent nodes
t = e / (xs[i] - xs[i - 1])
return ys[i] * t + ys[i - 1] * (1 - t)
return f
# for argparse argument type
def float_list(v):
"""Convert to list of float."""
return [float(x) for x in v.split(",")]
def main(argv=None):
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(description=__doc__)
# Defaults for colour primaries come from the
# `sRGB Profile.icc` i found as a system colour profile on my mac.
parser.add_argument(
"--rXYZ",
# default=(0.436, 0.222, 0.014),
type=float_list,
help="XYZ coordinate for red",
)
parser.add_argument(
"--gXYZ",
# default=(0.385, 0.717, 0.097),
type=float_list,
help="XYZ coordinate for green",
)
parser.add_argument(
"--bXYZ",
# default=(0.143, 0.061, 0.714),
type=float_list,
metavar="bXYZ",
help="XYZ coordinate for blue",
)
parser.add_argument(
"--kTRC",
type=float_list,
metavar="kTRC",
help="Tone Reproduction Curve for gray",
)
args = parser.parse_args()
d = vars(args)
d = {k: v for k,v in d.items() if v is not None}
icc_profile(png.binary_stdout(), **d)
if __name__ == "__main__":
main()