Log in

View Full Version : Some finger exercises with image levels


Didée
18th July 2004, 18:23
Lately, the discussion about the different gamma curves of TV sets and PC monitors came up again. This reminded me of some gamma tricks I'm used to occasionally apply in image processing. And now I even found time set it up in Avisynth ;)

The most-faced problem is: video appears "too dark" on PC monitors.
The usual solution is: raise black level, or slope-up the gamma curve.

Both make the details in very dark areas better visible - but both create the usual ugly "washed-out" effect at the same time.

A more pleasing solution is, to adjust the gamma curve for dark areas more strongly than for bright areas.

Functions:
(MaskTools.dll is needed. >= v1.5.5 is recommended.)

edit 07 dec 2004: all functions updated to latest MaskTools' semantics (currently v1.5.5 (http://manao4.free.fr/MaskTools-v1.5.5.dll))


function Ylevels(clip clp, int a, float gamma, int b, int c, int d)
{ wicked="x "+string(a)+" - "+string(b)+" "+string(a)+" - / 1 "+string(gamma)+" / ^ "+string(d)+" "+string(c)+" - * "+string(c)+" +"
# Reminder: Yexpr = "x a - b a - / 1 gamma / ^ d c - * c +"
# return( clp.subtitle(wicked) )
return( clp.YV12LUT(Yexpr = wicked, U=2,V=2) )
}

function YlevelsG(clip clp, int a, float gamma, int b, int c, int d)
{ wicked = gamma > 1.0
\ ? "x "+string(a)+" - "+string(b)+" "+string(a)+" - / 1 "+string(gamma)+" / ^ "+string(d)+" "+string(c)+" - * "+string(c)+" + x * x 255 x - * + 255 /"
\ : "x "+string(a)+" - "+string(b)+" "+string(a)+" - / 1 "+string(gamma)+" / ^ "+string(d)+" "+string(c)+" - * "+string(c)+" + 255 x - * x x * + 255 /"
return( clp.YV12LUT(Yexpr = wicked, U=2,V=2) )
}

function YlevelsS(clip clp, int a, float gamma, int b, int c, int d)
{ wicked="x "+string(a)+" - "+string(b)+" "+string(a)+" - / 1 "+string(gamma)+" / ^ "+string(d)+" "+string(c)+" - * "+string(c)+" + x 162,97466 / sin 255 * * x 255 x 162,97466 / sin 255 * - * + 255 /"
return( clp.YV12LUT(Yexpr = wicked, U=2,V=2) )
}

All functions are called the same way as AviSynth's "levels()" command, except for the coring parameter. Coring is not available with the Ylevels() functions. They'll always work on the full [0,255] value range.

Ylevels() is a simple replacement for Avisynth's internal levels() command, with a few neat differences:

- processes only luma --> faster, better preservation of color saturation
- no clipping: Ylevels(40,1.0,220, 40, 220) will not clip the output to [40,220] as levels() does. Values below 40 / above 220 will be scaled accordingly to the correct vales. This means, one can use any values for IN_LOW, IN_HIGH as "control points" without getting the input clipped at those values.


But that's not the deal.


YlevelsG() and YlevelsS() apply the given levels conversion by 100% @ Luma==0, by 0% @ Luma=255, and accordingly in-between.

YlevelsG() does a linear sweep (G = Gradient)
YlevelsS() does a sine sweep (S = yes, exactly ;) )

Try those with gamma values 1.5 ~ 4.0. I suppose some will like them.

YlevelsG (not YlevelsS) does "inverse" scaling for gamma values < 1.0, but that's not that great. It's just for completeness.


But that's not all, yet.


While building the LUT expression for the sine sweep :devil:, at one point I accidentially swapped some things, and the result was ...

YlevelsC()

For this one, 2.0 < gamma < 16.0 is reasonable.

No, I won't explain it :p - just try & enjoy it ;)
function YlevelsC(clip clp, int a, float gamma, int b, int c, int d)
{ wicked="x "+string(a)+" - "+string(b)+" "+string(a)+" - / 1 "+string(gamma)+" / ^ "+string(d)+" "+string(c)+" - * "+string(c)+" + 255 x 162,97466 / cos 255 * - * x x 162,97466 / cos 255 * * + 255 /"
return( clp.YV12LUT(Yexpr = wicked, U=2,V=2) )
}

Closing comment:

If these functions should be used for encoding or not, depends on the source. I would be carefully, at least ...

But:

These functions are pretty fast! (Manao: thankyouthankyouthank...)
- So they can be nicely used in the "Avisynth" section of ffdshow ...


Have fun


- Didée

malkion
18th July 2004, 20:16
Thanks Didee!

(now about that msu-sharpening script u developed, heh, just send it)

Didée
18th July 2004, 20:25
I'll post it when it's ready to be posted ;)

- Didée

malkion
18th July 2004, 20:51
Hey Didee, I like YlevelsG very much, except my CRT is kinda old, and the gamma is skewed. I set fddshow's luma offset from 50 to 60 just to watch vids. I even tried YlevelsG and Ylevels together, and I still get real time decoding. Not bad at all. But the full effect of YlevelsG is missed that way. Anyways, I can still use fddshow luma offset and YlevelsG together for a more brilliant picture. I'm about to try Ylevels first then YlevelsG to see if this option works...

There's so many options... hehe

jorel
21st July 2004, 15:23
Didée, thank you very much :)
i'm caming from http://forum.doom9.org/showthread.php?threadid=78220&perpage=20&pagenumber=3 (picture too dark with avisynth script thread),i'm testing the results.

for who need the link to download masktools:
http://jourdan.madism.org/~manao/

jorel
23rd July 2004, 20:40
@ Didée and all :
no luck here, the image still dark and changing the matizes(more green) comparing with the dvd(cartoon) source. anyone had good results? Didée, suggestions or differents adjusts in the scrip? thanks!....i lost my will to encode, we're loosing quality! :( ...waiting good solutions or hints.

Didée
24th July 2004, 14:05
Speaking of above functions, perhaps you'd need it to "bite" more in the darks only. Technically no problem, the distribution can be made at free will.

However, the following is YlevelS() with still careful settings:

http://img48.exs.cx/img48/901/YlevelsS.jpg

To my eyes, that's effective.


- Didée

morsa
24th July 2004, 17:57
Amazing!!!!!!!!!!!!!!!!!!!!!!!!!!!
Wouldn't be better to turn this directly into a plug-in??

Manao
24th July 2004, 18:03
No, it wouldn't be faster, and it would lose all the tweakability of YV12LUT. Didee's functions are wrappers to make the use of YV12LUT easier in these kind of levels filtering, and for that, avs scripts are perfectly fit.

LigH
24th July 2004, 18:06
By now, I was quite satisfied with the results of
Levels(0,1.4,255,8,248)
Tweak(sat=1.2,cont=1.1)
I'll be interrestend in comparing the results! :sly:

jorel
24th July 2004, 19:01
thank you boys....i did more tests.
differents results and reasons:

from vob in vdubmod:
http://img20.exs.cx/img20/6633/a105.th.jpg (http://img20.exs.cx/my.php?loc=img20&image=a105.jpg)
http://img20.exs.cx/img20/6633/a105.jpg

using .avs script (source only)from dvd2avi project:
http://img20.exs.cx/img20/802/b98.th.jpg (http://img20.exs.cx/my.php?loc=img20&image=b98.jpg)
http://img20.exs.cx/img20/802/b98.jpg

little differences cos here was with xvid decompressor in vdub mod!

now here,big differences:
from vobs in vdubmod:
http://img34.imageshack.us/img34/5236/vobs.th.jpg
http://img34.imageshack.us/img34/5236/vobs.jpg

using .avs script (source only)from dvd2avi project:
http://img39.imageshack.us/img39/7083/pcscale.th.jpg
http://img39.imageshack.us/img39/7083/pcscale.jpg

great differences cos was using ati decompressor in vdubmod(file information).

then,(correct me please) the differences in vdub depend of the decompressor used to show the pictures (like in vdub file informations).
now my doubt: if i'm using the ati the image is dark,if using xvid "seems" equal ..... what is working when encoding in CCE(D2S)? :confused:

Didée
24th July 2004, 19:52
Originally posted by LigH
By now, I was quite satisfied with the results of
Levels(0,1.4,255,8,248)
Tweak(sat=1.2,cont=1.1)
I'll be interrestend in comparing the results! :sly:

Okay.

http://img45.exs.cx/img45/448/YlevelsS_vs_Levels.jpg

What I see here, is:

One picture where detail in dark areas is clearly visible, but that suffers from contrast over the full range, making the overall appearance somewhat dull,
and one picture where detail in dark areas is clearly visible, and that has good contrast over the full range, making the overall appearance vivid.

q.e.d. ;)

- Didee


[edit]

*damnit*

Sorry for the misspelling, LigH. :o

LigH
25th July 2004, 12:16
Well - a little tweaking of the Levels(gamma) and Tweak(sat,cont) parameters may lead to even better results than my default ones. But finally: If your Ylevels or the Levels/Tweak combo are more or less successful, is beyond my ability of recognition. Other people may be more trained in spotting differences. :o :cool:

Didée
25th July 2004, 18:57
It gets clearer with some curves. The following curves are not mathematically exact - they're quickly free-handed, but give the idea.

A plain gamma correction looks like this:
http://img42.exs.cx/img42/451/gamma_normal.png

We get a strong brightening effect over the full range. But from about 35% of the available range, we start loosing contrast (steepyness of curve gets < 1).

Ylevels simply blends the normal gamma curve, weightened with the inverse of the input's brightness. The result is similar to this:
http://img42.exs.cx/img42/7656/gamma_lf.png

By this weightening, the brightening of the dark areas is still achieved. But here, the cost of the additional levels that were assigned to the darks is distributed more equally over the remaining ranges: the curve's steepyness goes only a little below '1', plus the curve's overall deviation from y=x is much smaller.

You can tweak your levels().tweak() - combo until you get blue in the face: you'll never get the same effect. Starting with normal levels(), contrast in the upper 65% is lost. Then, you can get it back
- if at all, only with loss. Expanding the compressed ranges again will lead to posterization, because actually values got lost during gamma correction
- not at all with tweak() - (not really), since its contrast parameter works symetrically to Y=127.


Hey, I don't want to sell anything here ;)

But the difference is really easy to spot, in my opinion:

interleave( levels(...).tweak(...), Ylevels(...) )

Flip this back 'n forth, and don't say you see no difference ;)


- Didée

scharfis_brain
25th July 2004, 19:24
hehe, I was using Photoshops curves to build a curve, that improved the image, and then used RGBLut :)

(the curves mostly seem to be similar to your one)

Didée
25th July 2004, 19:52
RGBLUT ony a YUV souce ... didn't you get some banding effects in areas of low luma? You're getting gaps through the YUV->RGB conversion (look at the histogram in Photoshop before-after), are spreading these gaps through gamma adjustment, and are left with the gaps after backconversion (again look at the histogram).

I don't pretend this springs to the eye obviously, but generally it's just like that.

- Didée

scharfis_brain
25th July 2004, 19:56
hehe, but since TMPEGenc only accepts RGB input, that isn't a big problem, isn't it?

Didée
7th December 2004, 12:13
All Ylevels()-functions updated to the latest MaskTools' semantics (needs to specify "U=2,V=2" to carry the original color planes through).

Jeremy Duncan
6th November 2006, 05:55
Didée,

I notified the FFdshow developers that the FFdshow Levels tab uses your code wrong. That it doesn't work as you've shown in this thread.

Here's the link:
Link (http://forum.doom9.org/showthread.php?p=896704#post896704)

I asked them to look at this thread and implement the Levels you made in this thread into FFdshow properly.

Alain2
6th November 2006, 13:53
Talking about ylevels, Didee I sent you a pm on this subject back in June, but you never replied..

Didée
6th November 2006, 14:49
Sorry for not replying ... with an all-time-bursting PM box, some PMs might slip through.

Worse is that YlevelsG/S/C indeed is wrongly implemented, and has been like that since day one.
Consequently, ffdshow "correctly" incorporates the faulty Ylevels variants ... so my complaints always were unjustified.

I am standing there with egg on my face. :(

Jeremy Duncan
20th November 2006, 09:59
Didée,

The FFdshow developer h_yamagata updated the YlevelsG/S/C using Alain2's patch.

Check it out. Rev 567
Link (http://svn.sourceforge.net/viewvc/ffdshow-tryout/?view=log)

FFDShow (http://easynews.dl.sourceforge.net/sourceforge/ffdshow-tryout/ffdshow_rev568_20061119_clsid.exe)

:D

bananacreamandpeca
13th December 2006, 06:06
Ok. this is interesting. I was looking for a quick way just to
control the gamma for my video.
But how do I use this? I only need the masktools.dll for avisynth,
that's all?

digitalone
27th January 2007, 13:24
thx for this great function Didée...but is it possible to update it using MVTools v2 semantics? thx again.

Manao
27th January 2007, 13:27
Just replace yv12lut by mt_lut

digitalone
27th January 2007, 13:43
thx a lot manao..it's working now... :)

FlimsyFeet
30th October 2007, 12:09
I need some help understanding this.

I have a DVD that's too "bright" and looks a bit washed out. I can get more of a "film-ish" look if I use:

Levels(0, 0.85, 255, 0, 255)

But this pulls the brightest bits down a bit too much and gives the illusion of reduced contrast. It sounds like the adjusted curves in this script are what I need to make the dark bits darker and not touch the bright bits too much, but reading the first post it looks as if this filter was not desinged for gamma values less than 1.0? (Where it says "YlevelsG (not YlevelsS) does "inverse" scaling for gamma values < 1.0, but that's not that great. It's just for completeness.") Of course I could just try it and see what it looks like, but if someone could clarify whether I should be using this or if something else would do a better job?

LigH
30th October 2007, 12:23
You should use a limited luminance (and chrominance) range when encoding to MPEG2 for DVD. MPEG2 encoders may expect "TV range" material (due to the fact that TV sets expect a limited signal voltage range from the receiver), and if you give them "PC range" instead, you will get overbright results especially on CRT TV sets.

You should apply the filter ColorYUV(levels="PC->TV") on full range material before encoding it.

http://avisynth.org/mediawiki/ColorYUV

FlimsyFeet
30th October 2007, 13:36
I don't think that's going to help, because the MPEG2 source should already be in TV range, I'm not doing any colourpsace conversions.

FlimsyFeet
31st October 2007, 19:28
My quick test:
a=ImageReader("greyscale.png").converttoyv12().histogram()
b=ImageReader("greyscale.png").converttoyv12().levels(0, 0.6, 255, 0 , 255).histogram()
c=ImageReader("greyscale.png").converttoyv12().ylevels(0, 0.6, 255, 0 , 255).histogram()
d=ImageReader("greyscale.png").converttoyv12().ylevelsg(0, 0.6, 255, 0 , 255).histogram()
e=ImageReader("greyscale.png").converttoyv12().ylevelss(0, 0.6, 255, 0 , 255).histogram()
stackvertical(a, b, c, d, e)

http://img132.imageshack.us/img132/6849/testxp9.png

YLevels and YLevelsG seems to clip the black too much. YLevelsS reduces the peak white.

Using gamma values >1.0, I see similar clipping in the white.

IanB
1st November 2007, 07:02
You might also test the coring=false option to Levels(). This option changes the range scaling from 16..235 to 0..255

...ImageReader("greyscale.png").converttoyv12().levels(0, 0.6, 255, 0 , 255, coring=false)

This should be the same as the default coring=true...ImageReader("greyscale.png").converttoyv12().levels(16, 0.6, 235, 16 , 235, coring=false)

FlimsyFeet
1st November 2007, 22:19
I should have used the 16-235 levels in my YLevels script above!
e.g.
ImageReader("greyscale.png").converttoyv12().ylevelsg(16, 0.6, 235, 16 , 235).histogram()
is correct with no clipping.

But, is there any reason why I shouldn't do this:
ImageReader("greyscale.png").ConvertToYV12().FlimsYLevels(15).Histogram()

function FlimsYlevels(clip clp, float amp)
{ wicked="x x 16 - 34,85493 / sin "+string(amp)+" * -"
return( clp.mt_lut(Yexpr = wicked) )
}

zee944
18th February 2008, 11:30
Didée, this filter is a great idea. I hope you'll continue refining it. Very useful.

My problem with it that I can't call forth the effect I want. I'd like to define which range I want to boost up a little. I've played around a lot with the values, but I just can't do it.

What I want is to only affect pixels that have luma value between (say) 50 and 200. I don't want to touch pixels under and above a certain brightness. So probably the gamma curve should be shrinked to fit between 50 and 200. Is it possible?

Didée
28th February 2008, 14:17
Answered zee944's question here (http://forum.doom9.org/showthread.php?p=1106374#post1106374).

Summary: Possible, but not feasable to do that with MaskTools' lookup tables.

Kumo
29th February 2008, 13:13
i'm trying to use this filter on a jap dvd r2 anime.i'm calling ylevels in my script like : "ylevelsS(16, 1.0, 235, 0 , 255).
i can adjust input_low and output_low settings to reach a 0-255 full range (checking it with histogram)only with gamma=1.
if i try with i.e. gamma =1.6(or any other value), i get dark levels too bright with i.e. input_low=34 and too dark with next value(i.e. input_low=35, there are no mid-ways).where am i wrong?

Vesi
6th March 2008, 14:17
I tried to test ylevels, but the problem I got is, when I try to work on source with AvsP everythink looks ok,but when rip finish I don't see any effect of ylevels in my rip...any Idea why.
I am using this script and Virtualdub for rip.

Script:
LoadPlugin("C:\Program Files\AviSynth 2.5\plugins\DGDecode.dll")
MPEG2Source("C:\Documents and Settings\Vesi\My Documents\Test\Test.d2v")
Deblock_QED()
tfm(order=1).tdecimate()
Crop(0, 44, -0, -48)

Spline36Resize(624,304) # Spline36 (Neutral)
YlevelsS(0,1.6,255,0,255)
FFT3DFILTER(sigma=1.0,bw=32,bh=32,ow=16,oh=16,bt=3)
dull = last
sharp = dull.LimitedSharpenfaster( ss_x=1.25, ss_y=1.25,smode=4, strength=150, overshoot=1 )
Soothe( sharp, dull, 40 )
Tweak(hue=0.0, sat=1.2)

Before
http://maxupload.com/img/E86AA5C0.jpg
With AvsP
http://maxupload.com/img/08010E72.jpg
Output
http://maxupload.com/img/E86AA5C0.jpg

WorBry
22nd April 2010, 15:47
FlimsyFeet,

I know this is quite an old thread, but I'm experimenting with routines for simulating 'film-looks', ideally keeping the process in YV12.
Since there seems to be no easy way to import Photoshop/Gimp curves without color space conversion (and I'm not sure if the Levels > Curves control in FFDSHow avoids that), I've been using a Masktools (mt_lutxy) function (128-centred multiply) that achieves the same result as the Overlay blend mode, found in After Effects etc.

http://forum.doom9.org/showthread.php?p=1387067#post1387067

This effectively creates an S-shaped luma curve, the 'strength' of which can be somewhat adjusted by opacity.

I also came across your FlimsYLevels function, which produces a sinusoidal S-curve.



function FlimsYlevels(clip clp, float amp)
{ wicked="x x 16 - 34,85493 / sin "+string(amp)+" * -"
return( clp.mt_lut(Yexpr = wicked) )
}[/CODE]


I'm interested to know how you derived the calc for the the 'wicked' mt_lut expression; I want to stay in full 0 - 255 range, not 16 -235.

Cheers.

Mounir
23rd June 2011, 04:38
I'm beginning to experiment and appreciate the benefits of this filter; sadly enough, like most of the filters it is YV12 only so i have to make an official request for YUY2 support , if that's not asking the moon.

LaTo
23rd June 2011, 10:56
I'm beginning to experiment and appreciate the benefits of this filter; sadly enough, like most of the filters it is YV12 only so i have to make an official request for YUY2 support , if that's not asking the moon.

You can do the same with SmoothAdjust which support YV16 (and so YUY2) in avisynth 2.6.x


Or you can add YUY2 indirectly in these functions:
clip = last
ConvertToYV12()

xxxlevels(...)

ConvertToYUY2()
MergeChroma(clip)

hello_hello
27th October 2019, 14:49
Would anyone with a wiki account care to fix the version of Ylevels on the wiki (http://avisynth.nl/index.php/Ylevels)?

For the version on the wiki, the "show_function" argument is integer, when it should be bool.
function Ylevels(clip clp,
\ int "input_low", float "gamma", int "input_high",
\ int "output_low", int "output_high", int "show_function")

And the return line has been changed to display the subtitle over the source video when show_function=true. I'm not sure if that was deliberate, but I'm also not sure why it would've been changed.

Wiki version:
return( show_function ? clp.subtitle(wicked) : clp.mt_lut(Yexpr = wicked, U=2,V=2) )

The version I've been using:
########
# Levels transforms by Didée
# Requires MaskTools v2.0 or greater
# last revision 2004-12-07; update to MaskTools v2.0 semantics 2008-03-09
#
# PARAMETERS:
# "input_low" : (Default: 0)
# "gamma" : (Default: 1.0)
# "input_high" : (Default: 255)
# "output_low" : (Default: 0)
# "output_high" : (Default: 255)
# "show_function" : (Default: false) - the function to be made into a lookup-table
# transform, shown in postfix notation
#
# USAGE:
# YLevels(0, 1.2, 255, 0, 255, false)
# YLevels(0, 1.2, 255)
# YLevels(gamma=1.2)

# Constant
function Ylevels(clip clp,
\ int "input_low", float "gamma", int "input_high",
\ int "output_low", int "output_high", bool "show_function")
{
input_low = Default(input_low, 0)
gamma = Default(gamma, 1.0)
input_high = Default(input_high, 255)
output_low = Default(output_low, 0)
output_high = Default(output_high, 255)
show_function = Default(show_function, false)

wicked = "x " +string(input_low)+ " - " +string(input_high)+ " " +string(input_low)+ " - / 1 " +string(gamma)+
\ " / ^ " +string(output_high)+ " " +string(output_low)+ " - * " +string(output_low)+ " +"

return show_function ? \
clp.mt_lut(Yexpr = wicked, U=2,V=2).subtitle(wicked) : \
clp.mt_lut(Yexpr = wicked, U=2,V=2)
}

# Gradient
function YlevelsG(clip clp,
\ int "input_low", float "gamma", int "input_high",
\ int "output_low", int "output_high", bool "show_function")
{
input_low = Default(input_low, 0)
gamma = Default(gamma, 1.0)
input_high = Default(input_high, 255)
output_low = Default(output_low, 0)
output_high = Default(output_high, 255)
show_function = Default(show_function, false)

wicked = gamma > 1.0
\ ? "x " +string(input_low)+ " - " +string(input_high)+ " " +string(input_low)+ " - / 1 " +string(gamma)+
\ " / ^ " +string(output_high)+ " " +string(output_low)+ " - * " +string(output_low)+ " + x * x 255 x - * + 255 /"
\ : "x " +string(input_low)+ " - " +string(input_high)+ " " +string(input_low)+ " - / 1 " +string(gamma)+
\ " / ^ " +string(output_high)+ " " +string(output_low)+ " - * " +string(output_low)+ " + 255 x - * x x * + 255 /"

return show_function ? \
clp.mt_lut(Yexpr = wicked, U=2,V=2).subtitle(wicked) : \
clp.mt_lut(Yexpr = wicked, U=2,V=2)
}

# Sine
function YlevelsS(clip clp,
\ int "input_low", float "gamma", int "input_high",
\ int "output_low", int "output_high", bool "show_function")
{
input_low = Default(input_low, 0)
gamma = Default(gamma, 1.0)
input_high = Default(input_high, 255)
output_low = Default(output_low, 0)
output_high = Default(output_high, 255)
show_function = Default(show_function, false)

wicked="x " +string(input_low)+ " - " +string(input_high)+ " " +string(input_low)+ " - / 1 " +string(gamma)+
\ " / ^ " +string(output_high)+ " " +string(output_low)+ " - * " +string(output_low)+ " + x 162,97466 /" +
\ " sin 255 * * x 255 x 162,97466 / sin 255 * - * + 255 /"

return show_function ? \
clp.mt_lut(Yexpr = wicked, U=2,V=2).subtitle(wicked) : \
clp.mt_lut(Yexpr = wicked, U=2,V=2)
}

# Cosine
function YlevelsC(clip clp,
\ int "input_low", float "gamma", int "input_high",
\ int "output_low", int "output_high", bool "show_function")
{
input_low = Default(input_low, 0)
gamma = Default(gamma, 1.0)
input_high = Default(input_high, 255)
output_low = Default(output_low, 0)
output_high = Default(output_high, 255)
show_function = Default(show_function, false)

wicked="x " +string(input_low)+ " - "+ string(input_high)+ " " +string(input_low)+ " - / 1 "+string(gamma)+
\ " / ^ " +string(output_high)+ " " +string(output_low)+ " - * " +string(output_low)+ " + 255 x 162,97466" +
\ " / cos 255 * - * x x 162,97466 / cos 255 * * + 255 /"

return show_function ? \
clp.mt_lut(Yexpr = wicked, U=2,V=2).subtitle(wicked) : \
clp.mt_lut(Yexpr = wicked, U=2,V=2)
}

real.finder
28th April 2020, 17:03
HBD update https://github.com/realfinder/AVS-Stuff/raw/Community/avs%202.5%20and%20up/YLevels_mt.avsi

special thanks for pinterf and StainlessS for float clip support

Arx1meD
6th March 2021, 12:27
real.finder, thanks for updating the script.
I am working on the restoration of cartoons. And this script helped me a lot. Didée wrote 4 different scripts: Ylevels - the main one and YlevelsG, YlevelsS, YlevelsC based on it. I collected 4 scripts in 1 and simplified them a bit. Perhaps it will be useful to someone other than me.


/*
NEEDED PLUGINS:
MaskTools2 (v2.2.5 or above)
https://github.com/pinterf/masktools/releases
http://avisynth.nl/index.php/MaskTools2/mt_lut

PARAMETERS:
"input_low" : (Default: 0) - The range is not limited.
"gamma" : (Default: 1.0) - The range 0.1 ... 10.
"input_high" : (Default: 255) - The range is not limited.
"output_low" : (Default: 0) - The range is not limited.
"output_high" : (Default: 255) - The range is not limited.
"mode" : (Default: "Line") - The function to change of luma.
Mode must be only: "Line", "Grad", "Sin", "Cos"
"chroma" : (Default: false) - Use chroma.

USAGE:
YLevels(0, 1.2, 255, 0, 255, "Line", false)
YLevels(0, 1.2, 255)
YLevels(gamma=1.2)
*/


function YLevels(clip clp, \
float "input_low", float "gamma", float "input_high", float "output_low", float "output_high", \
string "mode", bool "chroma") {
il = String(Default(input_low, 0))
gamma = Default(gamma, 1.0)
ih = String(Default(input_high, 255))
ol = String(Default(output_low, 0))
oh = String(Default(output_high, 255))
mode = Default(mode, "Line")
chroma = Default(chroma, false)

try { bpc = clp.BitsPerComponent } catch(msg) { bpc = 8 }

chrom = chroma ? "process" : "copy"

gm = String(Min(Max(0.1, gamma), 10.0))

Line = "x "+il+" - "+ih+" "+il+" - / 1 "+gm+" / ^ "+oh+" "+ol+" - * "+ol+" +"
Grad = gamma > 1.0 ? Line+" x * x 255 x - * + 255 /" : Line+" 255 x - * x x * + 255 /"
Sin = "x x pi * 2 255 * / sin "+Line+" x - * +"
Cos = Line+" x pi * 2 255 * / cos "+Line+" x - * -"

# Old function
# Sin = Line+" x 162.33804 / sin 255 * * x 255 x 162.33804 / sin 255 * - * + 255 /"
# Cos = Line+" 255 x 162.33804 / cos 255 * - * x x 162.33804 / cos 255 * * + 255 /"

# function:
# Line = (((X-'input_low')/('input_high'-'input_low'))^(1/'gamma'))*('output_high'-'output_low')+'output_low'
# Gradient = (gamma>1.0) ? ('Line'*X+X*(255-X))/255 : ('Line'*(255-X)+X*X)/255
# Sine = ('Line'*sin(X/162.97466)*255+X*(255-sin(X/162.97466)*255))/255 simplified X+('Line'-X)*sin(X*pi/(2*255))
# Cosine = ('Line'*(255-cos(X/162.97466)*255)+X*cos(X/162.97466)*255)/255 simplified 'Line'-('Line'-X)*cos(X*pi/(2*255))
# 2*256/pi = 162.97466 must be 2*255/pi = 162.33804

Ymode = mode == "Line" ? Line : \
mode == "Grad" ? Grad : \
mode == "Sin" ? Sin : Cos

mt_lut(clp, expr=Ymode, Y=3, chroma=chrom, scale_inputs=bpc==8?"none":"allf", use_expr=bpc==8?0:2)
}


/*
FlimsYlevels is a modified version of Didee's Ylevels.
It's designed to give washed-out looking video more dynamic range,
by making the dark bits darker and boosting the bright bits, but
without clipping either end too much.

FlimsYlevels(amp, prot, chroma)
amp - sets the strength, or amplitude, of the effect.
prot - protection from the clipping (changes the range 0 ... 255).
chroma - use chroma.
*/

Function FlimsYLevels(clip clp, int "amp", int "prot", bool "chroma") {
amp = String(Default(amp, 10))
prot = String(Default(prot, 3))
chroma = Default(chroma, false)

try { bpc = clp.BitsPerComponent } catch(msg) { bpc = 8 }

chrom = chroma ? "process" : "copy"

Flims = "x x "+prot+" - pi * 127.5 "+prot+" - / sin "+amp+" * -"
#function x - 'amp'*sin((x-'prot')*pi/(127.5-'prot'))

mt_lut(clp, expr=Flims, Y=3, chroma=chrom, scale_inputs=bpc==8?"none":"allf", use_expr=bpc==8?0:2)
}


I did a comparison of Levels, Yevels_mt and Yevels. Yevels_mt and Yevels gave the same result. And I got a question:
1. Why do you need "divisor"? I understand that you cannot divide by zero.
divisor = (input_high == input_low)? 1: input_high-input_low
2. Why use the "clip" function built into MaskTools2?
0 1 clip
3. Why use "scalef" when there is "scale_inputs" in mt_lut?
\? "x" + string (input_low) + "scalef -" + string (divisor) + "scalef /"
Examples:
https://thumbs2.imgbox.com/c9/9a/qeh0ZS3l_t.png (https://imgbox.com/qeh0ZS3l) https://thumbs2.imgbox.com/09/80/enkQxMYA_t.png (https://imgbox.com/enkQxMYA) https://thumbs2.imgbox.com/07/70/SsaIYgJl_t.png (https://imgbox.com/SsaIYgJl)
https://thumbs2.imgbox.com/5e/bc/ts8y5QsG_t.png (https://imgbox.com/ts8y5QsG) https://thumbs2.imgbox.com/44/fd/ClKQH58B_t.png (https://imgbox.com/ClKQH58B) https://thumbs2.imgbox.com/ee/11/NVvD4Smi_t.png (https://imgbox.com/NVvD4Smi)

And another question about the YlevelsS and YlevelsC functions.
YlevelsS and YlevelsC ignore the "input_low" parameter. Is this a mistake or not?
https://thumbs2.imgbox.com/2c/83/eF1ZLaFk_t.png (https://imgbox.com/eF1ZLaFk) https://thumbs2.imgbox.com/ff/bb/1HWqAgSj_t.png (https://imgbox.com/1HWqAgSj)

real.finder
6th March 2021, 14:23
well, I try not to change how the original function work, divisor if it not in the original then we (me and pinterf and StainlessS) added later since there were some problem in float input scaling

Why use "scalef" when there is "scale_inputs" in mt_lut?

scalef should be faster than using scale_inputs

Arx1meD
6th March 2021, 14:44
scalef should be faster than using scale_inputs

I thought the same and checked the speed of the script with "scalef" and "scale_inputs". There was no time difference.
If use use_expr = 1, then the speed of the script is very slow. If use_expr = 0 or 2 there is no difference.

real.finder
6th March 2021, 16:24
I thought the same and checked the speed of the script with "scalef" and "scale_inputs". There was no time difference.
If use use_expr = 1, then the speed of the script is very slow. If use_expr = 0 or 2 there is no difference.

well, I am speaking in theory, theoretically scalef/scaleb should be faster than scale_inputs, Of course it must be one of them used not both in same time