Log in

View Full Version : Brightness - restore dark and bright zones


Arx1meD
18th February 2021, 15:33
This script will be useful for restoring dark and bright zones of a video or for smoothly changing luma.
Thanks to Didée for his Ylevels (https://forum.doom9.org/showthread.php?t=79898). This inspired me to create other functions.


# Brightness v1.2
#
# v1.0 - First release.
# v1.1 - Adapted for use in all versions of AviSynth.
# v1.2 - Changed the function for CurveL.
# Now if 'light' > 0 bright areas will be brighter.
# Changed the default value of 'light' to -20.

/*
Needed plugins: MaskTools2 v2.2.16 or greater.
https://github.com/pinterf/masktools/releases
http://avisynth.nl/index.php/MaskTools2/mt_lut

Brightness(mode, dark, light, bit, chroma, DarkLightZonesOnly, SaveMinMaxLuma)

mode:
-----
"Line" - linear brightness variation.
"Cos" - changes brightness by Cosine function.
"Cube" - changes brightness by a cubic power function.
Default: "Cube"

dark:
-----
The strength of changing dark zones.
The recommended range is -30 ... 30. The range is not limited, in theory.
Default: 20

light:
------
The strength of changing light zones.
The recommended range is -30...30.
Default: -20

bit:
----
Bit depth per channel in input clip.
-1 - for autoscale in MaskTools2 (slower).
0 - for autodetection (faster).
8, 10, 12, 14, 16.
Default: 0

chroma:
-------
true - it means "process"; set u = v = 3 in mt_lut() MaskTools2.
false - it means "copy" or "copy first"; set u = v = 2.
Default: false

DarkLightZonesOnly:
-------------------
Changes only dark and light zones.
true or false.
If true, then "SaveMinMaxLuma" is ignored.
Default: false

SaveMinMaxLuma:
---------------
Smooth brightness change for dark and bright zones. Keep minimum (0) and maximum (255) value of luma range.
true or false.
Default: true

Usage Default:
Brightness(mode="Cube", dark=20, light=-20, bit=0, chroma=false, DarkLightZonesOnly=false, SaveMinMaxLuma=true)
or
Brightness("Cube", 20, -20, 0, false, false, true)
*/

function Brightness(clip input, string "mode", float "dark", float "light", int "bit", bool "chroma", bool "DarkLightZonesOnly", bool "SaveMinMaxLuma") {
mode = Default(mode, "Cube")
dark = Default(dark, 20)
light = Default(light, -20)
bit = Default(bit, 0)
chroma = Default(chroma, false)
DLZO = Default(DarkLightZonesOnly, false)
SMML = Default(SaveMinMaxLuma, true)

Assert(mode == "Line" || mode == "Cos" || mode == "Cube", "Brightness: Mode must be: Line, Cos, Cube")
Assert(bit == -1 || bit == 0 || bit == 8 || bit == 10 || bit == 12 || bit == 14 || bit == 16, \
"Brightness: Bit depth per channel must be only: 8, 10, 12, 14, 16 or -1, 0 for autodetection")

Try { bpc = bit == 0 || bit == -1 ? input.BitsPerComponent : bit } Catch (msg) { bpc=8 } #BitsPerComponent only in AVS+
scale_inputs = bit >= 0 ? "none" : bpc == 8 ? "none" : "allf" #"all" range in 8 bits Y=16...235; "allf" range in 8 bits Y=0...255
use_expr = bit >= 0 ? 0 : bpc == 8 ? 0 : 2
chrom = chroma ? "process" : "copy"

LUMA = bit >= 0 ? Pow(2.0, bpc) - 1 : 255.0
maxY = String(LUMA)
midY = String(LUMA/2.0)

stD = bit >= 0 ? String(dark * Pow(2.0, bpc - 8)) : String(dark)
stL = bit >= 0 ? String(light * Pow(2.0, bpc - 8)) : String(light)

k = String(mode == "Line" || mode == "Cube" ? 0 : 1)
n = String(mode == "Cube" ? 3 : 1) # odd numbers only
j = String(30) # even numbers only
l = string(21) # odd numbers only
m = String(SMML == true ? 1 : 0)
f = String(mode == "Line" ? 1.25 : mode == "Cos" ? 1.15 : 1.05)

curveD = dark == 0 ? "x" : !DLZO ? "x "+stD+" "+k+" 1 + 1 x "+midY+" / - "+n+" ^ * pi x * "+maxY+" / cos "+k+" * - * 1 1 x "+midY+" / - "+j+" ^ "+m+" * - * +"
\ : "x "+stD+" 1 x "+midY+" / - "+l+" ^ pi x * "+maxY+" / cos "+l+" ^ - * "+f+" * -"
curveL = light == 0 ? "x" : !DLZO ? "x "+stL+" "+k+" 1 + 1 x "+midY+" / - "+n+" ^ * pi x * "+maxY+" / cos "+k+" * - * 1 1 x "+midY+" / - "+j+" ^ "+m+" * - * -"
\ : "x "+stL+" 1 x "+midY+" / - "+l+" ^ pi x * "+maxY+" / cos "+l+" ^ - * "+f+" * +"

# function 1: x + 'st'*((1+'k')*(1 - x/127.5)^'n' - 'k'*cos(x*pi/255))*(1 - 'm'*(1 - x/127.5)^'j')
# function 2: x - 'f'*'st'*((1 - x/127.5)^'l' - cos(x*pi/255)^'l')

out = "x "+midY+" > "+curveL+" "+curveD+" ?"

mt_lut(input, expr=out, Y=3, chroma=chrom, scale_inputs=scale_inputs, use_expr=use_expr)
# mt_lut(input, expr=out, Y=3, chroma=chrom) # for MaskTools v2.0a48
}


Explanation of what the script does.
1. Basic functions for smoothly changing luma:
https://thumbs2.imgbox.com/10/e3/rUx0HEtK_t.png (https://imgbox.com/rUx0HEtK) https://thumbs2.imgbox.com/43/0f/VKMnnMMd_t.png (https://imgbox.com/VKMnnMMd)
Backup copy formulas 1 (https://disk.yandex.ua/i/x-5PIfHKnBzX6Q) graphs 1 (https://disk.yandex.ua/i/sIKcTU6CYogTrg)
2. Functions for changes only dark and light zones:
https://thumbs2.imgbox.com/04/a9/3mTUl2pN_t.png (https://imgbox.com/3mTUl2pN) https://thumbs2.imgbox.com/31/74/bKNgVGsu_t.png (https://imgbox.com/bKNgVGsu)
Backup copy formulas 2 (https://disk.yandex.ua/i/2GkfDWNxXsw_Ew) graphs 2 (https://disk.yandex.ua/i/ZRGY-QZumTgagw)

Examples:
Brightness(mode="Cube", dark=20, light=0, bit=0, chroma=false, DarkLightZonesOnly=true, SaveMinMaxLuma=true)
https://thumbs2.imgbox.com/c7/db/LXETYUft_t.jpg (https://imgbox.com/LXETYUft) https://thumbs2.imgbox.com/fe/3c/bOo5bxp2_t.jpg (https://imgbox.com/bOo5bxp2)
Backup copy Source frame (https://disk.yandex.ua/i/OaTFYjmsukVF5Q) Restored frame (https://disk.yandex.ua/i/GlwG9XQrdQH9HQ)
Comparison of original and restored clip (https://mega.nz/file/QChyRb7b#8KfhRWU8hEjJHcXpsqBOMTeeeXUpVeoBw8Kh0n8K24c)

anton_foy
12th February 2023, 21:23
Looks great! Would be interesting to try for prefiltering dark footage for better low light details after mdegrain. I will test.

Selur
14th February 2023, 18:26
Looks interesting, btw. to see what's in the dark is Retinex
https://i.ibb.co/Kq3sQ9D/grafik.png (https://ibb.co/fpPNKC9)
(AutoAdjust is also a good method to just brighten dark areas.)

Arx1meD
14th February 2023, 18:46
Retinex? Is there one for Avisynth?

Selur
14th February 2023, 19:08
yup: https://github.com/Asd-g/AviSynth-Retinex

coolgit
16th February 2023, 09:25
When I brighten something, I would add a certain value, and subtract a certain value when less bright, i.e. darker.

However in your script when I brighten bright zones by adding a certain value, it becomes darker, subtract a certain value and it becomes brighter. This makes no sense. Looking at the code I changed one thing, in red.

curveL = light == 0 ? "x" : !DLZO ? "x "+stL+" "+k+" 1 + 1 x "+midY+" / - "+n+" ^ * pi x * "+maxY+" / cos "+k+" * - * 1 1 x "+midY+" / - "+j+" ^ "+m+" * - * +"
\ : "x "+stL+" 1 x "+midY+" / - "+l+" ^ pi x * "+maxY+" / cos "+l+" ^ - * "+f+" * +"

Now 20 makes whites brighter and -20 makes whites darker, the same as black colours.

Arx1meD
16th February 2023, 12:34
coolgit,
dark > 0 - Dark areas will be brighter.
light > 0 - Light areas will be darker.
Do you need to have vice versa?

Julek
16th February 2023, 13:21
CLAHE is also a very good filter for this: https://github.com/dnjulek/VapourSynth-EqualizeHist
(no AVS ver)

coolgit
16th February 2023, 13:44
coolgit,
dark > 0 - Dark areas will be brighter.
light > 0 - Light areas will be darker.
Do you need to have vice versa?

That isn't logical.

dark + v = brighter
light + v = brighter

dark - v = less brighter (darker)
light - v = less brighter (darker)

logical with all existing software dealing with brightness.

Arx1meD
16th February 2023, 14:43
coolgit, I think you are right.
Formulas do not need to be changed. They are right. Need to invert part of the luma curve 127.5-255.
Change only:
light = Default(light, 20)*(-1.0)
Or change:

curveL = light == 0 ? "x" : !DLZO ? "x "+stL+" "+k+" 1 + 1 x "+midY+" / - "+n+" ^ * pi x * "+maxY+" / cos "+k+" * - * 1 1 x "+midY+" / - "+j+" ^ "+m+" * - * -"
\ : "x "+stL+" 1 x "+midY+" / - "+l+" ^ pi x * "+maxY+" / cos "+l+" ^ - * "+f+" * +"

out = "x "+midY+" > "+curveL+" "+curveD+" ?"

kedautinh12
16th February 2023, 15:12
You need edit script in first post, i will download it, thanks

Arx1meD
16th February 2023, 17:00
Change post #1.

coolgit
17th February 2023, 01:00
That worked nicely, cheers.

coolgit
18th March 2023, 15:52
Using dark=0, light=-10 is excellent at reducing over sharpen video without the need to blur.

Selur
15th November 2023, 05:02
btw. if anyone got the time and motivation, a Vapoursynth port of this would be nice. :)
Also shouldn't there be some adjustemnts depending whether the source if 'limited' or 'full' luma? (to use 16-235 instead of 0-255)

butterw2
17th November 2023, 12:29
Using dark=0, light=-10 is excellent at reducing over sharpen video without the need to blur.

thanks, I'll try it out in a shader.

The save luma line variant would requires dark=-light in order to have continuity on contrast at x=0.5.
it's possible to use lower values of n (ex n=2 or n=4 as in the plot linked below):
f(x, k) = x + k*(1-2*x)(1-(1-2*x)^n)
with n even, >0
and k in [-0.12, 0.12]
https://raw.githubusercontent.com/butterw/bShaders/5911dcd493297e459c7497675d0864f2a15f3fae/img/deContrast.png

Arx1meD
17th November 2023, 17:55
btw. if anyone got the time and motivation, a Vapoursynth port of this would be nice. :)
Also shouldn't there be some adjustemnts depending whether the source if 'limited' or 'full' luma? (to use 16-235 instead of 0-255)

I never had to use a video source with a limited level of luma. If you have such a video, then try this:
ColorYUV(levels="TV->PC")
Brightness(...)
ColorYUV(levels="PC->TV")

Selur
17th November 2023, 18:52
sure, but wouldn't it be better to just change the limits depending on whether the input is pc or tv scale?
255.0 -> 235.0
127.5 -> 167.5
255 -> 235