Skip to content

Instantly share code, notes, and snippets.

@MrLixm
Last active November 21, 2020 17:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MrLixm/96e24bdbf643dbf24cd098be446829cc to your computer and use it in GitHub Desktop.
Save MrLixm/96e24bdbf643dbf24cd098be446829cc to your computer and use it in GitHub Desktop.
Implementation of Lottes(2016) Tonemapping.
import logging
import colour
import numpy as np
from oiio import OpenImageIO as oiio
# ---------------------------------------------------------
def lerp(x, y, a):
""" Performs a linear interpolation between x and y using a to weight between them
Args:
x : Specify the start of the range in which to interpolate.
y : Specify the end of the range in which to interpolate.
a : Specify the value to use to interpolate between x and y.
"""
return x * (1 - a) + y * a
def tonemap_lottes(rgb, contrast=1.65, shoulder=0.977, hdrMax=16.0, midIn=0.18, midOut=0.267):
"""
Args:
midOut:
midIn:
hdrMax:
shoulder:
contrast:
rgb(ndarray):
Returns:
ndarray
"""
rgb: np.ndarray
logging.debug(f"Tonemapping init with contrast={contrast}, shoulder={shoulder}, hdrMax={hdrMax}, midIn={midIn}, midOut={midOut}")
""" Constants """
cs = contrast * shoulder
# b,c anchor curves
b0 = -pow(midIn, contrast)
b1 = pow(hdrMax, cs) * pow(midIn, contrast)
b2 = pow(hdrMax, contrast) * pow(midIn, cs) * midOut
b3 = pow(hdrMax, cs) * midOut
b4 = pow(midIn, cs) * midOut
b = -((b0 + (midOut * (b1 - b2)) / (b3 - b4)) / b4)
logging.debug(f"b = {b}")
c0 = pow(hdrMax, cs) * pow(midIn, contrast)
c1 = pow(hdrMax, contrast) * pow(midIn, cs) * midOut
c2 = pow(hdrMax, cs) * midOut
c3 = pow(midIn, cs) * midOut
c = (c0 - c1) / (c2 - c3)
logging.debug(f"c = {c}")
"""
peak_max should be float
rgb_ratio should be float3 (here 3d narray)
output_scale should be float
"""
peak_max = np.amax(rgb)
logging.debug(f"peak_max = {peak_max}")
rgb_ratio = np.divide(rgb, peak_max)
""" Tonemapping """
# contrast adjustement
output_scale = pow(peak_max, contrast)
# highlight compression
output_scale = output_scale / (pow(output_scale, shoulder) * b + c)
logging.debug(f"output_scale = {output_scale}")
# ------------------------------------------------------------------------------------------------------------------
""" We could end here with output_scale * rgb_ratio but let's add crosstalk"""
crosstalk = 64.0 # (64.0, 32.0, 128.0) controls amount of channel crosstalk
saturation = contrast # (0.0, 0.0, 0.0) + contrast full tonal range saturation control
cross_saturation = 4 # (4.0, 1.0, 16.0)
c_white = 1.0
# c_white = np.array([[[1.0, 1.0, 1.0]], [[1.0, 1.0, 1.0]]])
rgb_ratio = np.power(np.abs(rgb_ratio), saturation / cross_saturation)
rgb_ratio = lerp(rgb_ratio, c_white, pow(output_scale, crosstalk)) # TODO if output_scale or max3(output_scale)
rgb_ratio = np.power(np.abs(rgb_ratio), cross_saturation)
# ------------------------------------------------------------------------------------------------------------------
output = output_scale * rgb_ratio
logging.debug("Tonemapping applied")
return output
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG, format='- %(asctime)s [%(levelname)s] %(message)s', datefmt='%H:%M:%S')
v_contrast = 5 # also a , value: 1.4
v_shoulder = 1 # also d
v_hdrMax = 16
v_midIn = 0.18
v_midOut = 0.18
image_path = r"L:\temp\gm\render_test_sRGB_v01.exr"
outfile_path = rf"L:\temp\gm\rndr_tonemap_v02_{v_contrast}_{v_shoulder}_{v_hdrMax}_{v_midIn}_{v_midOut}.jpg"
img_in = oiio.ImageBuf(image_path, 0, 0)
img_in_spec = img_in.nativespec()
img_in_roi = img_in_spec.roi
img_in = oiio.ImageBufAlgo.channels(img_in, (0, 1, 2)) # crop the image to RGB only
RGB = img_in.get_pixels()
tm_rgb = tonemap_lottes(RGB, v_contrast, v_shoulder, v_hdrMax, v_midIn, v_midOut)
tm_rgb = colour.cctf_encoding(tm_rgb, function='sRGB')
out_buf = img_in.copy()
out_buf.set_pixels(img_in_roi, tm_rgb)
out_buf.specmod().attribute("compression", "jpg:100")
if out_buf.has_error:
raise RuntimeError("Final buffer has error. {}".format(out_buf.geterror()))
out_buf.write(outfile_path)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment