Welcome to Doom9's Forum, THE in-place to be for everyone interested in DVD conversion. Before you start posting please read the forum rules. By posting to this forum you agree to abide by the rules. |
![]() |
#1 | Link |
Registered User
Join Date: Mar 2011
Posts: 5,017
|
Specifying color (for borders or a blank clip)
For anyone who might want it, here's the latest version of the function discussed in this thread (for specifying color in an Avisynth-like manner).
I've removed all the old links from the thread (as some pointed to an incomplete version) and changed the function name to RGBColor. Even though the default color is "black" for VapourSynth's AddBorders & BlankClip functions, the added borders are always full range (for 8 bit video the black level is 0 rather than 16 as it should be for limited range video). The RGBColor function will ensure the black level is correct. YUV and RGB are both supported at any bitdepth (except for half float). Changes for RGBColor version 2024-10-02: - I hadn't realised the VapourSynth _ColorRange frame property uses 0 for full range and 1 for limited, while the resizers use 0 for limited and 1 for full, so the color range is now specified correctly when RGBColor creates the blank clip it uses for determining the color. - The previous versions required the user to specify a color. This version defaults to "black". - "lightgray" wasn't included as a color, only "lightgrey". Both are now included (as the same color). RGBColor 2025-03-25.zip There's a help file included. import vapoursynth as vs core = vs.core import RGBColor as rgb Specifying a border color for the AddBorders function Adding black borders clip = core.ffms2.Source("Video.mkv") clip = core.std.AddBorders(clip, 20,20,0,0, color=rgb.RGBColor(clip)) Adding dark blue borders clip = core.ffms2.Source("Video.mkv") clip = core.std.AddBorders(clip, 20,20,0,0, color=rgb.RGBColor(clip, color="darkblue")) Specifying the color of a blank clip with the same properties as a previous clip A blank clip with black frames clip = core.ffms2.Source("Video.mkv") BClip = core.std.BlankClip(clip, color=rgb.RGBColor(clip)) A blank clip with red frames clip = core.ffms2.Source("Video.mkv") BClip = core.std.BlankClip(clip, color=rgb.RGBColor(clip, color="red")) Specifying the color of a blank clip when no previous clip exists A blank clip with black frames BClip = core.std.BlankClip(format=vs.YUV420P8) BClip = core.std.BlankClip(BClip, color=rgb.RGBColor(BClip)) A blank clip with red frames BClip = core.std.BlankClip(format=vs.YUV420P8) BClip = core.std.BlankClip(BClip, color=rgb.RGBColor(BClip, color="red")) Last edited by hello_hello; 27th March 2025 at 13:39. |
![]() |
![]() |
![]() |
#2 | Link |
Registered User
Join Date: May 2011
Posts: 372
|
something similar to colors_rgb.avsi not sure, but for data like that python can use dictionaries, enums or dataclasses to give values to named objects, so you just create one of those.
To change these data from that colors_rgb.avsi to python's dictionary is in a code below. Then getting a color from a string: yellow = colors["yellow"] So yellow is color for RGB24 (8bit). To automatize that and get color based on your used clip's format, bitdepth and matrix you can use custom made function that changes color based on clips properties: Code:
import vapoursynth as vs from vapoursynth import core clip = core.ffms2.Source(r'video.mp4') colors = { "color_aliceblue" : (240, 248, 255), "color_antiquewhite" : (250, 235, 215), "color_aqua" : (0, 255, 255), #etc. "yellow" : (255, 255, 0), } def get_clip_color(clip, rgb_color: tuple=None, string_color: str=None): """ pass rgb_color (8bit tuple) or string_color """ if rgb_color is None: color = colors[string_color] else: color = rgb_color blank_rgb = core.std.BlankClip(color=color) matrix_prop = clip.get_frame(0).props.get("_Matrix", None) if matrix_prop in [0,2,3]: matrix_prop = None matrix_size = None if not clip.format.color_family==vs.RGB: if clip.height <= 576: matrix_size = 5 #470bg or 170m elif clip.height < 1090: matrix_size = 1 #709 else: matrix_size = 9 #2020ncl my_color_clip = blank_rgb.resize.Point(format=clip.format.id, matrix=matrix_prop or matrix_size) #getting color values from 0,0 pixel p0, p1, p2 = (None, None, None) f = my_color_clip.get_frame(0) planes =[f[i] for i in range(f.format.num_planes)] p0 = planes[0][0,0] try: p1 = planes[1][0,0] except IndexError: pass try: p2 = planes[2][0,0] except IndexError: pass return (p0,p1,p2) if p1 is not None else p0 color = get_clip_color(clip, string_color="yellow") #color = get_clip_color(clip, rgb_color=(255, 255, 0)) print(color) Last edited by _Al_; 11th August 2023 at 08:08. |
![]() |
![]() |
![]() |
#3 | Link |
Registered User
Join Date: May 2011
Posts: 372
|
or those dictionaries could be left with those hex strings and let code to change it to rgb tuple:
Code:
def hex_to_rgb(hex): return tuple(int(hex[i:i+2], 16) for i in (0, 2, 4)) hexstring = "FFFF00" rgb_color = hex_to_rgb(hexstring)) # (255, 255, 0) |
![]() |
![]() |
![]() |
#4 | Link |
Registered User
Join Date: Mar 2011
Posts: 5,017
|
I couldn't get RGB values to work correctly for YUV, which is why my example contains YUV values too, but maybe I was doing something silly.
Thanks for the info. I've been on the computer all day and my brain has just about shut down, so I'll return tomorrow when I can take it in. Cheers. |
![]() |
![]() |
![]() |
#5 | Link |
Registered User
Join Date: Mar 2011
Posts: 5,017
|
I'm on my way to a colors_rgb type function for Vapoursynth. It's working perfectly if the video is RGB, but not so much for YUV.
Any suggestions welcome. I'm still very new to Python-speak. <link removed> To specify a border color for AddBorders (RGB clip) (should be okay for any bitdepth). Code:
import vapoursynth as vs vc = vs.core import colors clip = <SomethingRGB> BorderColor = colors.RGBColor(clip, color='aqua') clip = clip.std.AddBorders(32,32,32,32, color=BorderColor) Code:
import vapoursynth as vs vc = vs.core import colors clip = vc.std.BlankClip(format=vs.RGBS) ClipColor = colors.RGBColor(clip, color='aqua') clip = vc.std.BlankClip(clip, color=ClipColor) Last edited by hello_hello; 2nd October 2023 at 19:22. |
![]() |
![]() |
![]() |
#6 | Link | |
Registered User
Join Date: Mar 2011
Posts: 5,017
|
Quote:
PS. Is the matrix a factor when adding borders to an RGB clip or just YUV? I'm still trying to get my head around your original function. Thanks. Last edited by hello_hello; 24th August 2023 at 17:06. |
|
![]() |
![]() |
![]() |
#7 | Link |
Registered User
Join Date: Mar 2011
Posts: 5,017
|
_Al_ ,
I used your method above instead of the way I was originally doing it, so now the color can be specified either way, similar to Avisynth. Still only works for RGB. <link removed> BColor = colors.RGBColor(clip, 'FFFF00') or BColor = colors.RGBColor(clip, 'yellow') clip = clip.std.AddBorders(32,32,32,32, color=BColor) Last edited by hello_hello; 26th August 2023 at 19:05. |
![]() |
![]() |
![]() |
#8 | Link | |
Registered User
Join Date: May 2011
Posts: 372
|
Quote:
That get_clip_color function can accept rgb or yuv clip and returns rgb or yuv tuple, or just a single value if t is a gray clip the principle of that function is making blankclip of desired rgb color, then using resize function to change it into clips format and color, in rgb or yuv format, then simply getting color for 0,0 pixel values, (could be any pixel, but with 0,0 pixel we do not need to deal with subsampling shifted positions for chroma), gives you desired color in particular bitdepth either for rgb or yuv, that code could be improved a bit so it is more readable to something like this: Code:
#getting color tuple from 0,0 pixel from yuv, rgb or gray clip: f = clip.get_frame(0) #getting a first frame for a clip that gives us an access to clips planes - one plane (gray clip) or three planes (rgb or yuv clip) #f[0] is first plane, first slice of frame f, it is a two dimensional array of values, same as numpy array would be, values are accesed like this: value=f[0][y,x] for x,y pixel coordinates p0 = f[0][0,0] #this is R value for rgb clip or Y value for yuv clip, for 0,0 pixel of the first plane if f.format.name.startswith('Gray'): return p0 #only Y value if clip is gray (there is one plane only for gray clip) return p0, f[1][0,0], f[2][0,0] #R, G, B values for rgb clip or Y, U, V values for yuv clip no calculations are needed or to get yuv values from rgb etc., let vapoursynth resize function to come up with proper values Last edited by _Al_; 24th August 2023 at 22:43. |
|
![]() |
![]() |
![]() |
#9 | Link |
Registered User
Join Date: May 2011
Posts: 372
|
I include it here as well, just if someone wants to get a pixel value of yuv clip, where subsampling is important, if wanting any pixel values, not just 0,0 pixel, chroma planes could be smaller than first luma plane (YUV420, YUV422, YUV411), so in that case getting a pixel value for x, y coordinates for frame 0, would be:
Code:
f = clip.get_frame(0) p0 = f[0][y,x] if f.format.name.startswith('Gray'): return p0 ys = y >> f.format.subsampling_h xs = x >> f.format.subsampling_w return p0, f[1][ys,xs], f[2][ys,xs] Last edited by _Al_; 24th August 2023 at 23:35. |
![]() |
![]() |
![]() |
#10 | Link |
Registered User
Join Date: May 2011
Posts: 372
|
Also, as using python now, you can use thousands of its modules that come with python as well, like color picker, so you can implement to select color from a GUI interface:
Code:
import tkinter from tkinter import colorchooser root = tkinter.Tk() root.withdraw() color = colorchooser.askcolor(title ="Choose color") if not color: root.mainloop() else: rgb_color, hex_color = color root.destroy() print(rgb_color) Last edited by _Al_; 25th August 2023 at 05:14. |
![]() |
![]() |
![]() |
#11 | Link |
Registered User
Join Date: Mar 2011
Posts: 5,017
|
_Al_,
I've only skimmed over your last posts as I've got to spend some time in the real world, but I've been playing around with the colors function and it now converts the RGB values to YUV. I'm not sure if the formula is always the same, and obviously the primaries differ for rec.601, 709, and 2020. The difference between the first two probably isn't enough to matter, but I assume the sRGB primaries should be converted to rec.2020 primaries when adding YUV borders, but if that's correct I'm not sure how to do it yet. The function scales YUV correctly for integer bit depths, but it's obviously wrong for float. Anyway, I'll be back at some stage to re-read your posts and try to take them in, but in the mean time I've replaced the version of colors.py I uploaded earlier with the new version, which works for YUV of integer bitdepths now. Cheers. |
![]() |
![]() |
![]() |
#12 | Link |
Registered User
Join Date: May 2011
Posts: 372
|
I got correct results for float formats as well.
Using "yellow" color for YUV444PS clip I got: (0.9277999997138977, -0.5, 0.045847088098526) Using "yellow" color for RGBS clip I got : (1.0, 1.0, 0) previewer shows the same yellow rgb 255,255,0 for borders if I use those colors for particular clip in AddBorders(), as for any integer "yelow" argument As for get_clip_color() from above, if matrix is in clip's props, than correct matrix is used for resize conversion and we get correct colors, if not it defaults depending on size(that can be changed). You can include the same code for primaries and transfer, because if you have a in value and out value is not specified, you might get resize error. I do the same thing when automatizing these things. Checking props if _Matrix, _Primaries, _Transfer is set and then get out arguments for resize, even updating props afterwords, like in that example but that could get complicated, I use even mediainfo in that example, where I should use rather vapoursynth props only. For your purpose, you might just copy your clip and delete those particular props and just be dealing with matrix maybe. It is just a color picker, it might not be that important. |
![]() |
![]() |
![]() |
#13 | Link |
Registered User
Join Date: Mar 2011
Posts: 5,017
|
I haven't looked back over your earlier posts yet (short on time), but after reading your last post and looking at your numbers I thought maybe after scaling I needed to subtract 0.5 from the UV values for float, so zero becomes the equivalent of 128 in 8 bit. It's still not quite right though, and my calculation for 8 bit YUV yellow isn't quite RGB yellow. I'm wondering if it's the matrix being used to convert the video to RGB for display. How exactly did you calculate your numbers?
What I'm currently getting using the formula in the script (the output of a blank clip). For yellow: ![]() ![]() ![]() ![]() Your numbers: ![]() PS I found an online RGB to YUV converter and the numbers it calculates for 8 bit yellow are not quite, but very close to the numbers I'm getting. https://www.calculatormix.com/conver...oogle_vignette I don't know why there's a small difference. It turns out that using the same numbers for 8 bit I get pure yellow after a conversion from 601 to 709 (excluding the primaries), so the difference is probably there somewhere, at least for integer. ![]() Last edited by hello_hello; 25th August 2023 at 12:43. |
![]() |
![]() |
![]() |
#14 | Link |
Registered User
Join Date: May 2011
Posts: 372
|
I do not calculate anything, I use zimg resizer (default in vapoursynth) to do the math. Making sure matrix is correct (and possibly primaries or transfer if they are set in clip as well). Having simple one color yellow (255,255,0) RGB blank clip and resizing that clip to clips format. Then reading its color values from its planes. That is what get_clip_color() does. All possible format values loading into previewer then give 255,255,0 yellow rgb.
|
![]() |
![]() |
![]() |
#15 | Link |
Registered User
Join Date: Mar 2011
Posts: 5,017
|
Well I think I have the formulas worked out now. I assume YUV float is always supposed to be full-scale, because I'm getting values very similar to yours now, with matrix="709".
I added a matrix argument for specifying "601", "709" or "2020". If no matrix is specified it uses the matrix from frame properties if it exists, otherwise the default is "601". I'll try your method sometime soon anyway, to check I've got the three matrices correct, but I think it's okay. I wish that someone who knows for sure would come along. Obviously the matrix argument only applies to YUV. <link removed> "None" in the subtitle was the check for a matrix in frame properties I forgot to remove. Color = colors.RGBColor(clip, color='yellow', matrix='601') ![]() ![]() Color = colors.RGBColor(clip, color='yellow', matrix='709') ![]() ![]() Last edited by hello_hello; 26th August 2023 at 19:06. |
![]() |
![]() |
![]() |
#16 | Link |
Registered User
Join Date: May 2011
Posts: 372
|
Those values do not look correct. Values like 0.99 float looks wrong (too close to 1) but more visibly -0.49 (no blue in yellow, should be U=-0.5?), similar like would be in red, 255,0,0 there would be positive 0.5 for V (full red). And matrix should not matter because color is not there (blue in yellow) or full amount (red in red only), would that be correct?
For real video footage, not our color case, for float limited range, all illegal Y values should be above 1.0. Previewer should show those values. But for our task, getting color, it should never be above 1.0, no illegal colors, because we start with full rgb 255 value, it cannot be illegal from that point., unless using faulty equations. I checked that colors.py. You you are not using values from a zimg conversion but values are calculated using equations, :-) but how about ye different matrixes alone? Not mentioning color ranges, etc. You just need more and more equations. Simply using vapoursynth conversions and then pulling out pixel values seams like way to go. Someone already worked his a** off :-) , to get proper conversions for all scenarios, why not to use it. I might post that function again considering it all, range , primaries, transfer.... |
![]() |
![]() |
![]() |
#17 | Link | |
Registered User
Join Date: Mar 2011
Posts: 5,017
|
Quote:
I think I read that according to the specs the results are supposed to be rounded for integer (I didn't bother as Vapoursynth accepts float) and the float result could simply be due to floating point math inaccuracies. Your luma value for yellow was 0.9277999997138977 rather than 1.0 too, but that's probably correct because I assume adding blue must increase the luma so only pure white would have a value of 1.0. If I use a color picker on those images though, I get (255, 255, 7) for the 8 bit yellow image and (255, 254, 6) for the 32 bit one, so maybe they're not 100% correct. Then again, they were converted back to 8 bit RGB for display by VS edit and I've no idea what it's doing. I might start another thread, or ask over at video help, to see if I can find someone who knows what the equations should be, given I've come this far. Thinking about it, I'm not sure any difference in primaries would be a factor, given the hex values used are for specifying RGB primaries, so RGB's green would be the same green for each colorspace (well maybe a tad off for SD). If 2020's green was specified instead, it'd have to be converted to RGB's green for rec.709 as the RGB color space doesn't include rec.2020's green, but the rec.2020 color space includes RGB green. Does that make sense or is it faulty logic? https://en.wikipedia.org/wiki/File:C...31_Rec_601.svg https://en.wikipedia.org/wiki/File:C...31_Rec_709.svg https://en.wikipedia.org/wiki/File:C...1_Rec_2020.svg |
|
![]() |
![]() |
![]() |
#18 | Link |
Registered User
Join Date: Mar 2011
Posts: 5,017
|
Well it looks like you talked me into using your method.
If a matrix is specified it's used (YUV only). If it's not specified any matrix in frame properties is used. If there's no frame property it's based on resolution, similar to your example. (Edit: Re-uploaded to fix an error with choosing the matrix). <link removed> The new script works the same way as the original one. Code:
import vapoursynth as vs vc = vs.core import colors clip = <SomeVideo> BorderColor = colors.RGBColor(clip, color='aqua', matrix='709') # or (color='00FFFF', matrix=1) clip = clip.std.AddBorders(32,32,32,32, color=BorderColor) Code:
import vapoursynth as vs vc = vs.core import colors clip = vc.std.BlankClip(format=vs.RGBS) # or format=vs.GRAY8 or format=vs.YUV420P10 etc. ClipColor = colors.RGBColor(clip, color='aqua') clip = vc.std.BlankClip(clip, color=ClipColor) Last edited by hello_hello; 27th August 2023 at 23:40. |
![]() |
![]() |
![]() |
#19 | Link |
Registered User
Join Date: May 2011
Posts: 372
|
ad colors.py,
I would not pass matrix, because matrix could be different than clip's matrix, and all weirdness breaks. But if you prefer to pass matrix, like you did, and to have two types for one argument, then you could deal with kwargs instead of values (because key argument changes depending on type value). Also vapoursynth would just raise error if matrix would be wrong. Exactly the same what you do, so not trying to troubleshoot matrix validity. I included range check as well. It would be like this: Code:
import vapoursynth as vs from vapoursynth import core from typing import Union colors = {'aliceblue' : 'F0F8FF', 'antiquewhite' : 'FAEBD7', #etc. dictionary is not complete} def input_rgb24_color(color:str): ''' returns rgb color as a tuple''' if color in colors.keys(): color = colors[color] #color is hex string only now try: rgb_color = tuple(int(color[i:i+2], 16) for i in (0, 2, 4)) core.std.BlankClip(color=rgb_color) except (ValueError, vs.Error): print('RGBColor: color must be a proper hex string that represents 8bit rgb color, example: "00008B"') raise return rgb_color def RGBColor(clip, color:str, matrix:Union[int,str]=None): if not isinstance(clip, vs.VideoNode): raise ValueError('RGBColor: clip must be a video') if not isinstance(color, str): raise ValueError('RGBColor: color must be a string, for example "darkblue" or "00008B"') else: rgb_color = input_rgb24_color(color) if clip.format.color_family==vs.RGB: matrix_kwarg_out = {} else: #yuv, gray if matrix is None: MatrixPassed_kwarg = {} elif isinstance(matrix, str): MatrixPassed_kwarg = {'matrix_s':matrix} elif isinstance(matrix, int): MatrixPassed_kwarg = {'matrix':matrix} else: raise ValueError('RGBColor: matrix must be an integer or string') MatrixProp = clip.get_frame(0).props.get("_Matrix", None) MatrixProp_kwarg = {} if MatrixProp in [None,0,2,3] else {'matrix':MatrixProp} if clip.width <= 1024 and clip.height <= 576: MatrixSize_kwarg = {'matrix':5} elif clip.width < 3840 and clip.height < 2160: MatrixSize_kwarg = {'matrix':1} else: MatrixSize_kwarg = {'matrix':9} matrix_kwarg_out = MatrixPassed_kwarg or MatrixProp_kwarg or MatrixSize_kwarg RangeProp = clip.get_frame(0).props.get('_ColorRange', None) range = None if RangeProp is None else {0:1,1:0}[RangeProp] #values needs to be swapped for zimg BlankRGBClip = core.std.BlankClip(color=rgb_color) ColorClip = BlankRGBClip.resize.Point(format=clip.format.id, **matrix_kwarg_out, range=range) f = ColorClip.get_frame(0) p0 = f[0][0,0] if clip.format.color_family==vs.GRAY: return p0 return p0, f[1][0,0], f[2][0,0] Last edited by _Al_; 27th August 2023 at 07:39. |
![]() |
![]() |
![]() |
#20 | Link |
Registered User
Join Date: Mar 2011
Posts: 5,017
|
I was experiencing this problem yesterday and in the end I put it down to user error when it went away, but as it was happening today I managed to track down the cause.... eventually.
Code:
clip = YUV420_Video # here clip.get_frame(0).props.get("_Matrix", None) returns 1 clip = vc.std.BlankClip(clip) # here it returns None clip = clip.resize.Spline36(1280,720) # here it returns 2 RGBClip = vc.std.BlankClip(color=(255, 255, 0)) MatrixProp = clip.get_frame(0).props.get("_Matrix", None) clip = RGBClip.resize.Point(format=clip.format.id, matrix=MatrixProp) # here the error is "no path between colorspaces" Code:
clip = YUV420_Video # here clip.get_frame(0).props.get("_Matrix", None) returns 1 clip = vc.std.BlankClip(clip) # here it returns None RGBClip = vc.std.BlankClip(color=(255, 255, 0)) MatrixProp = clip.get_frame(0).props.get("_Matrix", None) clip = RGBClip.resize.Point(format=clip.format.id, matrix=MatrixProp) # here the error is "a matrix must be specified" Why would the Vapoursynth resizers add the frame property and give it an unspecified value instead of just not adding it? My bad, I guess, for not considering the possibility that _Matrix might have a value of 2, but because a value of 2 and no value at all produce different error messages, when they're effectively the same thing, I banged my head on the desk way too many times before I tracked that one down. Last edited by hello_hello; 27th August 2023 at 22:49. |
![]() |
![]() |
![]() |
Thread Tools | Search this Thread |
Display Modes | |
|
|