Skip to content

Instantly share code, notes, and snippets.

@Lypheo
Last active February 16, 2021 15:46
Show Gist options
  • Save Lypheo/b69b55a9ab7895cb4a15729c668914a9 to your computer and use it in GitHub Desktop.
Save Lypheo/b69b55a9ab7895cb4a15729c668914a9 to your computer and use it in GitHub Desktop.
plotting mpv/libplacebo tonemapping operators
import colour # pip install colour-science
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
L = np.linspace(0, 10000, 1000)
ops = {
"linear": []
,"hable": []
,"mobius": []
,"reinhard": []
,"2390": []
,"gamma": []
,"identity": []
}
def calc(sig_peak = 10000, target_peak = 300):
def mobius_(x, param = 0.3):
src_peak = sig_peak/target_peak
if src_peak > 1 + 1e-6:
j = param
a = -j*j * (src_peak-1) / (j**2 - 2*j + src_peak)
b = (j**2 - 2*j*src_peak + src_peak) / max(1e-6, src_peak-1)
scale = (b*b + 2*b*j + j*j) / (b-a)
return x if x <= j else scale * (x + a) / (x + b)
return x
def reinhard_(x, param= 0.5):
src_peak = sig_peak/target_peak
contrast = param
offset = (1.0 - contrast) / contrast
x /= x + offset
scale = (src_peak + offset) / src_peak
return x * scale
def hable_(x):
src_peak = sig_peak/target_peak
A = 0.15; B = 0.50; C = 0.10; D = 0.20; E = 0.02; F = 0.30
__hable = lambda x: (x*(x*A + C*B) + D*E) / (x*(x*A + B) + D*F) - E/F
return __hable(max(x, 0)) / __hable(src_peak)
def gamma_(x, param = 1.8):
src_peak = sig_peak/target_peak
gamma = param
cutoff = 0.05; gamma = 1/gamma
scale = ((cutoff / src_peak) ** gamma) / cutoff
return (x / src_peak) ** gamma if x > cutoff else scale * x
def _2390(x):
x, src_peak = (colour.models.eotf_inverse_PQ_BT2100(i) for i in (x, sig_peak))
scale = 1 / src_peak
x *= scale
maxLum = scale * colour.models.eotf_inverse_PQ_BT2100(target_peak)
ks = 1.5 * maxLum - 0.5
tb = (x - ks) / (1 - ks)
tb2 = tb ** 2
tb3 = tb ** 3
pb = (2 * tb3 - 3 * tb2 + 1) * ks + (tb3 - 2 * tb2 + tb) * (1-ks) + (-2 * tb3 + 3 * tb2) * maxLum
x = x if x < ks else pb
x *= src_peak # 1/scale
return colour.models.eotf_PQ_BT2100(x)
ops["linear"] = [target_peak*x/sig_peak for x in L]
ops["identity"] = [x for x in L]
ops["hable"] = [hable_(x/target_peak)*target_peak for x in L]
ops["mobius"] = [mobius_(x/target_peak)*target_peak for x in L]
ops["reinhard"] = [reinhard_(x/target_peak)*target_peak for x in L]
ops["gamma"] = [gamma_(x/target_peak)*target_peak for x in L]
ops["2390"] = [_2390(x) for x in L]
calc()
fig = plt.figure()
ax = fig.gca()
plt.subplots_adjust(bottom=0.2)
ls = {k: plt.plot(L, v, label=k)[0] for k,v in ops.items()}
plt.grid(True)
dst_peak_slider_ax = plt.axes([0.07, 0.03, 0.8, 0.03])
dst_peak_slider = Slider(dst_peak_slider_ax, 'dst peak', 0, 10000, valinit=300)
src_peak_slider_ax = plt.axes([0.07, 0.1, 0.8, 0.03])
src_peak_slider = Slider(src_peak_slider_ax, 'src peak', 0, 10000, valinit=10000)
def update(val):
calc(target_peak = dst_peak_slider.val, sig_peak=src_peak_slider.val)
for k,v in ls.items():
v.set_ydata(ops[k])
ax.set_ylim([0,dst_peak_slider.val + dst_peak_slider.val/30])
ax.set_xlim([0,src_peak_slider.val + src_peak_slider.val/30])
fig.canvas.draw_idle()
dst_peak_slider.on_changed(update)
src_peak_slider.on_changed(update)
update(0)
ax.legend()
plt.show()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment