View Full Version : Drawing diagonal lines with Expr/mt_lutspa
Reel.Deel
7th January 2023, 22:59
Hi folks,
Does anyone know how to draw a diagonal line with Expr/mt_lutspa? I want to draw the skin tone line over the Color2 vectorscope similar to the image below. I can draw the line with Subtitle's "font_angle" parameter but it's only for Windows. The angle is 34°.
https://i.ibb.co/6bn3nwX/colortools.png
Dogway
8th January 2023, 21:56
I don't think you can express a diagonal line other than 45º on a 3x3 window. The easiest method is to draw a line and a rotate plugin with some AA.
The subtitle trick is a good one, you can also use (https://forum.doom9.org/showthread.php?p=1537385#post1537385) assrender to draw shapes but never used it myself.
Reel.Deel
8th January 2023, 22:31
Yeah, I though about assrender too and like you I also have never used it for that purpose. I'm working on a Histogram script mainly for RGB and wanted to keep it from needing other plugins or external files. I was hoping it could be done with expr. Maybe I'll just stick with the waveform and audio phase scope for now.
https://i.ibb.co/vz7KxR9/rgbparade.png
https://i.ibb.co/tJLWXPV/stereomode.png
Dogway
8th January 2023, 22:39
What's wrong with subtitle? Looks like a good option.
Reel.Deel
8th January 2023, 22:46
What's wrong with subtitle? Looks like a good option.
Nothing, it's just limited to Windows because of GDI. On other OSes only the Text filter is available. But I do have to say, Subtitle's x/y coordinates are a PITA to get right when using font angle. I still need to figure it out for other dimensions since it changes when bits>8.
wonkey_monkey
8th January 2023, 23:01
Planar RGB only (defaults to 8-bit white but that default won't work for other bit depths - might make more flexible later if StainlessS doesn't beat me to it):
function ExprLine(clip c, float x1, float y1, float x2, float y2, float "r", float "g", float "b", float "a") {
r = Default(r, 255)
g = Default(g, 255)
b = Default(b, 255)
a = Default(a, 1)
string = Format("
x 2 ^ V^
{{0}} 2 ^ L^
sx {x1} - A^
sy {y1} - B^
{x2} {x1} - C^
{y2} {y1} - D^
A C * B D * +
C C * D D * + / P^
P 0 < {x1} P 1 > {x2} {x1} P C * + ? ? X^
P 0 < {y1} P 1 > {y2} {y1} P D * + ? ? Y^
sx X - 2 ^
sy Y - 2 ^
+
sqrt 1 swap - 0 max {a} *
L V - *
V +
sqrt
")
r = Format(string, r)
g = Format(string, g)
b = Format(string, b)
return c.Expr(r,g,b)
}
Expr isn't the most efficient way to do this but it makes up for it by being pretty fast anyway.
Fjord
8th January 2023, 23:44
I haven't tried this in Expr expression, but it should be possible to draw an arbitrary line segment given:
1) linear formula for the line, in frame coordinates: y = a*x + b
2) start and end points (x1, y1) and (x2, y2)
and following algorithm.
In the Expr expression, use absolute pixel coordinates sx and sy as current pixel coordinates.
3) test if sx is between x1 and x2 (min(x1,x2) max(x1,x2))
4) test if sy is between y1 and y2 (min(y1,y2) max(y1,y2))
5) test if pixel is on the line, ie. sy = a + sx * b
6) using ?, if all above are true (and), assign pixel the desired color value, else assign x (no change).
Actually 5) needs to be a bit more sophisticated: test if pixel is near the line, where tolerance t will determine the thickness of the line drawn: abs(sy - (a + sx * b)) <= t
The remaining work is the linear math to obtain the a and b parameters for the line, given any 2 of: start point, end point, angle. And then formulate the RPN.
I have never tried this, and I'm just a beginner with Expr. But this should give you some ideas.
EDIT - Wonkey_monkey replied while I was thinking this over... Anyway, maybe my description helps to understand what is going on in his script.
Reel.Deel
9th January 2023, 00:14
Very nice wonkey_monkey, thank you very much. It produces a nice anti-aliased line :) ... kudos. Regarding speed, single threaded I get ~150fps on a 512x512 clip, it might be faster to just render 1 frame and use it as a mask. Something like Overlay(src, Blankclip(color=$ffffff), mask=ExprLine(Blankclip(length=1), ...))
img = FFImageSource("colortools.png").Spline36Resize(512,512).ConvertToPlanarRGB()
Blankclip(pixel_type="YV24", length=1)
Histogram("Color2", keepsource=false, bits=9)
ConvertToPlanarRGB()
FlipVertical()
ExprLine(117, 42, 256, 256) #138 // 131 64
https://i.ibb.co/7GTCW4J/vectorscope.png
@Fjord
Thank you for the explanation. Maybe one day I will understand, math is not my strong suit :o
StainlessS
9th January 2023, 03:41
It produces a nice anti-aliased line ... kudos.
Yeah, Flash git.
might make more flexible later if StainlessS doesn't beat me to it
Well this is part way there maybe,
#img = FFImageSource("colortools.png").Spline36Resize(512,512).ConvertToPlanarRGB()
Blankclip(pixel_type="YV24", length=1)
Histogram("Color2", keepsource=false, bits=9)
ConvertToPlanarRGB()
FlipVertical()
ConvertBits(16)
ExprLine(117, 42, 256, 256) #138 // 131 64
Return Info()
return last
function ExprLine(clip c, float x1, float y1, float x2, float y2, float "r", float "g", float "b", float "a") {
defVal = Bitlshift(1,c.BitsPerComponent) - 1 # channel max val : 8 -> 16 bit only, not float
r = Default(r, defVal)
g = Default(g, defVal)
b = Default(b, defVal)
a = Default(a, 1) # Not sure what to do with this Alpha thingy
string = Format("
x 2 ^ V^
{{0}} 2 ^ L^
sx {x1} - A^
sy {y1} - B^
{x2} {x1} - C^
{y2} {y1} - D^
A C * B D * +
C C * D D * + / P^
P 0 < {x1} P 1 > {x2} {x1} P C * + ? ? X^
P 0 < {y1} P 1 > {y2} {y1} P D * + ? ? Y^
sx X - 2 ^
sy Y - 2 ^
+
sqrt 1 swap - 0 max {a} *
L V - *
V +
sqrt
")
r = Format(string, r)
g = Format(string, g)
b = Format(string, b)
return c.Expr(r,g,b)
}
Cant help thinking that I'm 'being played' though.
Sir Clive Sinclair was chaiman of MENSA {high I.Q. club} for about 20+ years, with an I.Q. of 150 {he just made it through the door}.
It's my theory that the other clever guys in there said to each other,
"lot of work that, let the stupid guy do it".
https://i.postimg.cc/t4TCsRFx/rd-00.jpg (https://postimages.org/)
Reel.Deel
9th January 2023, 08:33
defVal = Bitlshift(1,c.BitsPerComponent) - 1 # channel max val : 8 -> 16 bit only, not float
r = Default(r, defVal)
g = Default(g, defVal)
b = Default(b, defVal)
a = Default(a, 1) # Not sure what to do with this Alpha thingy
But that only scales the default, if you defined those parameters it wont be scaled. Maybe something like this:
function ExprLine(clip c, float x1, float y1, float x2, float y2, int "r", int "g", int "b", float "a") {
bits = BitsPerComponent(c)
r = scale8_el(Default(r, 255), bits)
g = scale8_el(Default(g, 255), bits)
b = scale8_el(Default(b, 255), bits)
a = Default(a, 1)
...
function scale8_el(int n, int bits)
{
Return (bits==8 || n<=0) ? n : (bits==32) ? n/255.0 : (n>=255) ? BitLShift(1, bits) - 1 : BitLShift(n, bits - 8)
}
I'm still confused about what is the proper/recommended formula to convert 8-bit values to other bitdepths. Avs' ConvertBits, avsresize, fmtconv and others use this formula: (color8 * max_pixel_value / 255). When you scale 128 to 16-bit, it becomes 32896 instead of 32768. I'm sure there's a reason as to why it's done that way, but I don't know why :)
Edit:
Not sure what to do with this Alpha thingy
I think that is like an opacity parameter? Sane values from 0 to 1.0. I increased it to 128 and the line is almost binarized. Even at 255 there's still some tints of grey so I don't think it's intended to be used with values over 1.
Cant help thinking that I'm 'being played' though.
This made me chuckle.
StainlessS
9th January 2023, 14:53
Planar RGB only (defaults to 8-bit white but that default won't work for other bit depths)
But that only scales the default, if you defined those parameters it wont be scaled.
It is the default that does not scale, by WM's comment I assume that user is intended to provide valid n-bit values for R,G,B, rather than assume values provided as 8-bit.
(obviously could easily be changed, that's Wonkies job)
I'm still confused about what is the proper/recommended formula to convert 8-bit values to other bitdepths.
In this case, requirement is for R,G,B, so I thought should be should be max possible. (seems a bit daft but there you go, I though the weird stuff only affect YUV, not RGB, brain fart!)
When you scale 128 to 16-bit, it becomes 32896 instead of 32768.
Mid point for 16 bit.
nBits = 16
nBits_Max_pixel_value = $FF << (nBits-8) = $FF00
nBits_Max_pixel_value = 255 << (nBits-8) = 65280
color8 * nBits_max_pixel_value / 255
color16 = color8 * nBits_max_pixel_value / 255
color16 = 128 * 65280 / 255 = 32768
Whereas color16 = 128 * 65535 / 255 = 32896 ($8080 : Dont quite work) [EDIT: Fixed, was 32986]
EDIT: Whereas color16 = 128 * 65535 / 256 = 32768.5 (but with integer math 32768)
Fixed-ish (wonky can mod)
#img = FFImageSource("colortools.png").Spline36Resize(512,512).ConvertToPlanarRGB()
Blankclip(pixel_type="YV24", length=1)
Histogram("Color2", keepsource=false, bits=9)
ConvertToPlanarRGB()
FlipVertical()
ConvertBits(16)
ExprLine(117, 42, 256, 256) #138 // 131 64
Return Info()
return last
function ExprLine(clip c, float x1, float y1, float x2, float y2, float "r", float "g", float "b", float "a") {
nBits_Max = Bitlshift(255, c.BitsPerComponent-8) # channel max val : 8 -> 16 bit only, not float
r = Default(r, 255 * nBits_Max / 255)
g = Default(g, 255 * nBits_Max / 255)
b = Default(b, 255 * nBits_Max / 255)
a = Default(a, 1) # Not sure what to do with this Alpha thingy (for 16 bit, should this be 1 * nBits_Max / 255 ie $100)
#RT_DebugF("R=$%X G=$%X B=$%X",r,g,b,name="ExprLine_RGB: ")
string = Format("
x 2 ^ V^
{{0}} 2 ^ L^
sx {x1} - A^
sy {y1} - B^
{x2} {x1} - C^
{y2} {y1} - D^
A C * B D * +
C C * D D * + / P^
P 0 < {x1} P 1 > {x2} {x1} P C * + ? ? X^
P 0 < {y1} P 1 > {y2} {y1} P D * + ? ? Y^
sx X - 2 ^
sy Y - 2 ^
+
sqrt 1 swap - 0 max {a} *
L V - *
V +
sqrt
")
r = Format(string, r)
g = Format(string, g)
b = Format(string, b)
return c.Expr(r,g,b)
}
EDIT: I'm not sure that the function handles Float RGB clip.
wonkey_monkey
9th January 2023, 14:57
The max value for 16-bit is FFFF, not FF00.
128 is not exactly midway between 0 and 255, so the corresponding 16-bit value shouldn't be expected to be midway between 0 and 65535.
StainlessS
9th January 2023, 16:16
The max value for 16-bit is FFFF, not FF00.
As you seem to know what you are talkin' bout, maybe you can have the honour of fixing it :)
(Dont think I care anymore. Life, dont talk to me about life.)
EDIT: To Wonkey with love :) :- https://www.youtube.com/watch?v=PPvRsLWlDXw
wonkey_monkey
9th January 2023, 17:21
function ExprLine(clip c, float x1, float y1, float x2, float y2, float "r", float "g", float "b", float "a") {
pixel_Max = c.BitsPerComponent == 32 ? 1.0 : BitLShift(1, c.BitsPerComponent) - 1
r = Default(r, pixel_Max)
g = Default(g, pixel_Max)
b = Default(b, pixel_Max)
a = Default(a, 1)
string = Format("
x 2 ^ V^
{{0}} 2 ^ L^
sx {x1} - A^
sy {y1} - B^
{x2} {x1} - C^
{y2} {y1} - D^
A C * B D * +
C C * D D * + / P^
P 0 < {x1} P 1 > {x2} {x1} P C * + ? ? X^
P 0 < {y1} P 1 > {y2} {y1} P D * + ? ? Y^
sx X - 2 ^
sy Y - 2 ^
+
sqrt 1 swap - 0 max {a} *
L V - *
V +
sqrt
")
r = Format(string, r)
g = Format(string, g)
b = Format(string, b)
return c.Expr(r,g,b)
}
Defaults to white for all bit depths (including float). It's still up to the user to provide properly ranged RGB values for the current bit depth.
wonkey_monkey
9th January 2023, 17:41
it might be faster to just render 1 frame and use it as a mask.
I thought about doing that but it got complicated - you only need to make a Y32 mask but Expr wouldn't let me use it as a secondary clip with the RGB clip. I have plans one day to make a better Expr (not that it isn't already pretty great) - what this kind of thing really needs is per-pixel or per-line calculations before it iterates over each channel.
Dogway
9th January 2023, 18:33
A C * B D * +
C C * D D * + / P^
Wow, so great, is that the eigenvalue? I still have to digest all these matrices things.
Also one thing I keep stumbling onto in Expr is the need to reference a different channel. Typically I have to extract then recombine.
wonkey_monkey
9th January 2023, 20:31
Wow, so great, is that the eigenvalue?
Err... maybe. I just rewrote some Javascript code on StackOverflow.
Also one thing I keep stumbling onto in Expr is the need to reference a different channel. Typically I have to extract then recombine.
I've got an RPN plugin that lets you do that, but development stalled a bit since Expr came out. Expr is generally faster, since it uses SSE, but my plugin does real conditional execution/loops, multithreading, and lets you access any channel as well as past/future frames (fixed offsets), and works in double (or higher, up to 80-bit using the x87 FPU) precision.
Now I'm in two minds about whether to rewrite it using SSE (which would made conditional execution and loops more difficult) or to write a wrapper around Expr to add some of its functionality (like other channels and past/future frames).
Reel.Deel
9th January 2023, 21:15
I thought about doing that but it got complicated - you only need to make a Y32 mask but Expr wouldn't let me use it as a secondary clip with the RGB clip.
Not too complicated. I modified the script to do just that and added a mask parameter to output a Y mask. With the original script and a 1280x720 clip I got ~60fps, with this mod I know get ~460 fps. It also supports interleaved RGB now, although a little slower than planar input.
I deleted the script since it is not working as it should, guess wonkey_monkey was right about being more complicated.
Err... maybe. I just rewrote some Javascript code on StackOverflow.
Do you still have the link?
wonkey_monkey
9th January 2023, 21:44
I think it was this one: https://stackoverflow.com/a/6853926
StainlessS
10th January 2023, 02:17
This is my attempt at 8 bit input args,
#img = FFImageSource("colortools.png").Spline36Resize(512,512).ConvertToPlanarRGB()
Blankclip(pixel_type="YV24", length=1)
Histogram("Color2", keepsource=false, bits=9)
ConvertToPlanarRGB()
FlipVertical()
ConvertBits(16)
#ConvertBits(32)
A = Undefined
#A = 0.0
#A = 0.5
#A = 1.0
#A = 10.0
#A = 255.0
#A = 1000000.0
ExprLine8(117, 42, 256, 256, a=A) #138 // 131 64
#PointResize(width*2,height*2)
Return Info()
return last
##########################################
# 8 bit R,G,B, args INPUT Ver$
# Changed r,g,b to Type INT from FLOAT.
# Takes R,G,B, type int, 8bit range 0->255. A = type Float; range = who knows the secret of the black magic box?
function ExprLine8(clip c, float x1, float y1, float x2, float y2, int "r", int "g", int "b", Float "a") {
Function ___ExprLineScaleRGB8(int n,int bpc) {
n = n.Min(255).Max(0) [* Silent Limit to valid 8 bit input *]
targetMax = (bpc==32) ? 1.0 : BitLShift(1,bpc)-1 [* bits==32 then float 1.0 : Else Int Max value that can be contained in bpc bits (lowest bpc bits all set) *]
Return n * targetMax / 255 [* For RGB, ALWAYS use, x_new = x_old * target_max / source_max (Shift scale is just for YUV) *]
}
bpc = c.BitsPerComponent
r = Default(r,255).___ExprLineScaleRGB8(bpc)
g = Default(g,255).___ExprLineScaleRGB8(bpc)
b = Default(b,255).___ExprLineScaleRGB8(bpc)
a = Default(a, 1) # a would seem to be 0.0 -> 1.0-ish{anti-aliased} or a bit more{full jaggy white}.
#RT_DebugF((bpc==32) ? "R=%f : G=%f : B=%f : A=%f" : "R=$%04X : G=$%04X : B=$%04X : A=%f" ,r,g,b,a,name="ExprLine8: ")
string = Format("
x 2 ^ V^
{{0}} 2 ^ L^
sx {x1} - A^
sy {y1} - B^
{x2} {x1} - C^
{y2} {y1} - D^
A C * B D * +
C C * D D * + / P^
P 0 < {x1} P 1 > {x2} {x1} P C * + ? ? X^
P 0 < {y1} P 1 > {y2} {y1} P D * + ? ? Y^
sx X - 2 ^
sy Y - 2 ^
+
sqrt 1 swap - 0 max {a} *
L V - *
V +
sqrt
")
r = Format(string, r)
g = Format(string, g)
b = Format(string, b)
return c.Expr(r,g,b)
}
But that only scales the default, if you defined those parameters it wont be scaled.
As mod posted above.
function scale8_el(int n, int bits)
{
Return (bits==8 || n<=0) ? n : (bits==32) ? n/255.0 : (n>=255) ? BitLShift(1, bits) - 1 : BitLShift(n, bits - 8)
}
Above would seem to be just for YUV-Y. (which uses shifted 8bit)
I'm still confused about what is the proper/recommended formula to convert 8-bit values to other bitdepths. Avs' ConvertBits, avsresize, fmtconv and others use this formula: (color8 * max_pixel_value / 255). When you scale 128 to 16-bit, it becomes 32896 instead of 32768. I'm sure there's a reason as to why it's done that way, but I don't know why
I thought I had originally cocked up in 1st post script, but did not, RGB is never shift conversion.
by Pinterf:
RGB is always full scale, the ranges should be stretched like
x_new = x_old * target_max / source_max
https://forum.doom9.org/showthread.php?p=1903442#post1903442
Maybe see prior post for context.
Reel.Deel
10th January 2023, 04:24
It seems someone is lurking :p - https://github.com/FFmpeg/FFmpeg/commit/5d3f0226ade2fc6d655d0e3dfc08035b4e70df24#diff-1aeeaa88dd710ccae4244531179820a73c379adb6a4227a4437def4834032927R166
FFmpeg's audio vector scope will look much better now...
vBulletin® v3.8.11, Copyright ©2000-2026, vBulletin Solutions Inc.