mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2025-02-16 21:05:15 +00:00
hsv: added support for more RGB and BGR formats to hsvdetector and hsvfilter
This commit is contained in:
parent
6b5e536ca6
commit
e9f0a3b8ac
3 changed files with 577 additions and 94 deletions
|
@ -82,6 +82,93 @@ impl ObjectSubclass for HsvDetector {
|
|||
type ParentType = gst_base::BaseTransform;
|
||||
}
|
||||
|
||||
fn video_input_formats() -> Vec<glib::SendValue> {
|
||||
let values = [
|
||||
gst_video::VideoFormat::Rgbx,
|
||||
gst_video::VideoFormat::Xrgb,
|
||||
gst_video::VideoFormat::Bgrx,
|
||||
gst_video::VideoFormat::Xbgr,
|
||||
gst_video::VideoFormat::Rgb,
|
||||
gst_video::VideoFormat::Bgr,
|
||||
];
|
||||
values.iter().map(|i| i.to_str().to_send_value()).collect()
|
||||
}
|
||||
|
||||
fn video_output_formats() -> Vec<glib::SendValue> {
|
||||
let values = [
|
||||
gst_video::VideoFormat::Rgba,
|
||||
gst_video::VideoFormat::Argb,
|
||||
gst_video::VideoFormat::Bgra,
|
||||
gst_video::VideoFormat::Abgr,
|
||||
];
|
||||
values.iter().map(|i| i.to_str().to_send_value()).collect()
|
||||
}
|
||||
|
||||
impl HsvDetector {
|
||||
#[inline]
|
||||
fn hsv_detect<CF, DF>(
|
||||
&self,
|
||||
in_frame: &gst_video::video_frame::VideoFrameRef<&gst::buffer::BufferRef>,
|
||||
out_frame: &mut gst_video::video_frame::VideoFrameRef<&mut gst::buffer::BufferRef>,
|
||||
to_hsv: CF,
|
||||
apply_alpha: DF,
|
||||
) where
|
||||
CF: Fn(&[u8]) -> [f32; 3],
|
||||
DF: Fn(&[u8], &mut [u8], u8),
|
||||
{
|
||||
let settings = self.settings.lock().unwrap();
|
||||
|
||||
// Keep the various metadata we need for working with the video frames in
|
||||
// local variables. This saves some typing below.
|
||||
let width = in_frame.width() as usize;
|
||||
let in_stride = in_frame.plane_stride()[0] as usize;
|
||||
let in_data = in_frame.plane_data(0).unwrap();
|
||||
let out_stride = out_frame.plane_stride()[0] as usize;
|
||||
let out_data = out_frame.plane_data_mut(0).unwrap();
|
||||
let nb_input_channels = in_frame.format_info().pixel_stride()[0] as usize;
|
||||
|
||||
assert_eq!(out_data.len() / out_stride, in_data.len() / in_stride);
|
||||
assert_eq!(in_data.len() % nb_input_channels, 0);
|
||||
|
||||
let in_line_bytes = width * nb_input_channels;
|
||||
let out_line_bytes = width * 4;
|
||||
|
||||
assert!(in_line_bytes <= in_stride);
|
||||
assert!(out_line_bytes <= out_stride);
|
||||
|
||||
for (in_line, out_line) in in_data
|
||||
.chunks_exact(in_stride)
|
||||
.zip(out_data.chunks_exact_mut(out_stride))
|
||||
{
|
||||
for (in_p, out_p) in in_line[..in_line_bytes]
|
||||
.chunks_exact(nb_input_channels)
|
||||
.zip(out_line[..out_line_bytes].chunks_exact_mut(4))
|
||||
{
|
||||
let hsv = to_hsv(in_p);
|
||||
|
||||
// We handle hue being circular here
|
||||
let ref_hue_offset = 180.0 - settings.hue_ref;
|
||||
let mut shifted_hue = hsv[0] + ref_hue_offset;
|
||||
|
||||
if shifted_hue < 0.0 {
|
||||
shifted_hue += 360.0;
|
||||
}
|
||||
|
||||
shifted_hue %= 360.0;
|
||||
|
||||
if (shifted_hue - 180.0).abs() <= settings.hue_var
|
||||
&& (hsv[1] - settings.saturation_ref).abs() <= settings.saturation_var
|
||||
&& (hsv[2] - settings.value_ref).abs() <= settings.value_var
|
||||
{
|
||||
apply_alpha(in_p, out_p, 255);
|
||||
} else {
|
||||
apply_alpha(in_p, out_p, 0);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for HsvDetector {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
|
@ -282,10 +369,7 @@ impl ElementImpl for HsvDetector {
|
|||
let caps = gst::Caps::new_simple(
|
||||
"video/x-raw",
|
||||
&[
|
||||
(
|
||||
"format",
|
||||
&gst::List::new(&[&gst_video::VideoFormat::Rgba.to_str()]),
|
||||
),
|
||||
("format", &gst::List::from_owned(video_output_formats())),
|
||||
("width", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
||||
("height", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
||||
(
|
||||
|
@ -310,10 +394,7 @@ impl ElementImpl for HsvDetector {
|
|||
let caps = gst::Caps::new_simple(
|
||||
"video/x-raw",
|
||||
&[
|
||||
(
|
||||
"format",
|
||||
&gst::List::new(&[&gst_video::VideoFormat::Rgbx.to_str()]),
|
||||
),
|
||||
("format", &gst::List::from_owned(video_input_formats())),
|
||||
("width", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
||||
("height", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
||||
(
|
||||
|
@ -358,7 +439,7 @@ impl BaseTransformImpl for HsvDetector {
|
|||
let mut caps = caps.clone();
|
||||
|
||||
for s in caps.make_mut().iter_mut() {
|
||||
s.set("format", &gst_video::VideoFormat::Rgbx.to_str());
|
||||
s.set("format", &gst::List::from_owned(video_input_formats()));
|
||||
}
|
||||
|
||||
caps
|
||||
|
@ -366,7 +447,7 @@ impl BaseTransformImpl for HsvDetector {
|
|||
let mut caps = caps.clone();
|
||||
|
||||
for s in caps.make_mut().iter_mut() {
|
||||
s.set("format", &gst_video::VideoFormat::Rgba.to_str());
|
||||
s.set("format", &gst::List::from_owned(video_output_formats()));
|
||||
}
|
||||
|
||||
caps
|
||||
|
@ -439,8 +520,6 @@ impl BaseTransformImpl for HsvDetector {
|
|||
inbuf: &gst::Buffer,
|
||||
outbuf: &mut gst::BufferRef,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let settings = *self.settings.lock().unwrap();
|
||||
|
||||
let mut state_guard = self.state.borrow_mut();
|
||||
let state = state_guard.as_mut().ok_or(gst::FlowError::NotNegotiated)?;
|
||||
|
||||
|
@ -468,57 +547,283 @@ impl BaseTransformImpl for HsvDetector {
|
|||
},
|
||||
)?;
|
||||
|
||||
// Keep the various metadata we need for working with the video frames in
|
||||
// local variables. This saves some typing below.
|
||||
let width = in_frame.width() as usize;
|
||||
let in_stride = in_frame.plane_stride()[0] as usize;
|
||||
let in_data = in_frame.plane_data(0).unwrap();
|
||||
let out_stride = out_frame.plane_stride()[0] as usize;
|
||||
let out_data = out_frame.plane_data_mut(0).unwrap();
|
||||
|
||||
assert_eq!(in_data.len() % 4, 0);
|
||||
assert_eq!(out_data.len() / out_stride, in_data.len() / in_stride);
|
||||
|
||||
let in_line_bytes = width * 4;
|
||||
let out_line_bytes = width * 4;
|
||||
|
||||
assert!(in_line_bytes <= in_stride);
|
||||
assert!(out_line_bytes <= out_stride);
|
||||
|
||||
for (in_line, out_line) in in_data
|
||||
.chunks_exact(in_stride)
|
||||
.zip(out_data.chunks_exact_mut(out_stride))
|
||||
{
|
||||
for (in_p, out_p) in in_line[..in_line_bytes]
|
||||
.chunks_exact(4)
|
||||
.zip(out_line[..out_line_bytes].chunks_exact_mut(4))
|
||||
{
|
||||
assert_eq!(out_p.len(), 4);
|
||||
let hsv =
|
||||
hsvutils::from_rgb(in_p[..3].try_into().expect("slice with incorrect length"));
|
||||
|
||||
// We handle hue being circular here
|
||||
let ref_hue_offset = 180.0 - settings.hue_ref;
|
||||
let mut shifted_hue = hsv[0] + ref_hue_offset;
|
||||
|
||||
if shifted_hue < 0.0 {
|
||||
shifted_hue += 360.0;
|
||||
match state.in_info.format() {
|
||||
gst_video::VideoFormat::Rgbx | gst_video::VideoFormat::Rgb => {
|
||||
match state.out_info.format() {
|
||||
gst_video::VideoFormat::Rgba => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_rgb(
|
||||
in_p[..3].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[..3].copy_from_slice(&in_p[..3]);
|
||||
out_p[3] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Argb => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_rgb(
|
||||
in_p[..3].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[1..4].copy_from_slice(&in_p[..3]);
|
||||
out_p[0] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Bgra => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_rgb(
|
||||
in_p[..3].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[0] = in_p[2];
|
||||
out_p[1] = in_p[1];
|
||||
out_p[2] = in_p[0];
|
||||
out_p[3] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Abgr => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_rgb(
|
||||
in_p[..3].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[1] = in_p[2];
|
||||
out_p[2] = in_p[1];
|
||||
out_p[3] = in_p[0];
|
||||
out_p[0] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
shifted_hue %= 360.0;
|
||||
|
||||
out_p[..3].copy_from_slice(&in_p[..3]);
|
||||
|
||||
out_p[3] = if (shifted_hue - 180.0).abs() <= settings.hue_var
|
||||
&& (hsv[1] - settings.saturation_ref).abs() <= settings.saturation_var
|
||||
&& (hsv[2] - settings.value_ref).abs() <= settings.value_var
|
||||
{
|
||||
255
|
||||
} else {
|
||||
0
|
||||
}
|
||||
gst_video::VideoFormat::Xrgb => {
|
||||
match state.out_info.format() {
|
||||
gst_video::VideoFormat::Rgba => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_rgb(
|
||||
in_p[1..4].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[..3].copy_from_slice(&in_p[1..4]);
|
||||
out_p[3] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Argb => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_rgb(
|
||||
in_p[1..4].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[1..4].copy_from_slice(&in_p[1..4]);
|
||||
out_p[0] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Bgra => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_rgb(
|
||||
in_p[1..4].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[0] = in_p[3];
|
||||
out_p[1] = in_p[2];
|
||||
out_p[2] = in_p[1];
|
||||
out_p[3] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Abgr => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_rgb(
|
||||
in_p[1..4].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[1] = in_p[3];
|
||||
out_p[2] = in_p[2];
|
||||
out_p[3] = in_p[1];
|
||||
out_p[0] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
gst_video::VideoFormat::Bgrx | gst_video::VideoFormat::Bgr => {
|
||||
match state.out_info.format() {
|
||||
gst_video::VideoFormat::Rgba => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_bgr(
|
||||
in_p[..3].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[0] = in_p[2];
|
||||
out_p[1] = in_p[1];
|
||||
out_p[2] = in_p[0];
|
||||
out_p[3] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Argb => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_bgr(
|
||||
in_p[..3].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[1] = in_p[2];
|
||||
out_p[2] = in_p[1];
|
||||
out_p[3] = in_p[0];
|
||||
out_p[0] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Bgra => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_bgr(
|
||||
in_p[..3].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[..3].copy_from_slice(&in_p[..3]);
|
||||
out_p[3] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Abgr => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_bgr(
|
||||
in_p[..3].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[1..4].copy_from_slice(&in_p[..3]);
|
||||
out_p[0] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
gst_video::VideoFormat::Xbgr => match state.out_info.format() {
|
||||
gst_video::VideoFormat::Rgba => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_bgr(
|
||||
in_p[1..4].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[0] = in_p[3];
|
||||
out_p[1] = in_p[2];
|
||||
out_p[2] = in_p[1];
|
||||
out_p[3] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Argb => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_bgr(
|
||||
in_p[1..4].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[1] = in_p[3];
|
||||
out_p[2] = in_p[2];
|
||||
out_p[3] = in_p[1];
|
||||
out_p[0] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Bgra => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_bgr(
|
||||
in_p[1..4].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[..3].copy_from_slice(&in_p[1..4]);
|
||||
out_p[3] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Abgr => {
|
||||
self.hsv_detect(
|
||||
&in_frame,
|
||||
&mut out_frame,
|
||||
|in_p| {
|
||||
hsvutils::from_bgr(
|
||||
in_p[1..4].try_into().expect("slice with incorrect length"),
|
||||
)
|
||||
},
|
||||
|in_p, out_p, val| {
|
||||
out_p[1..4].copy_from_slice(&in_p[1..4]);
|
||||
out_p[0] = val;
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
|
|
|
@ -77,6 +77,55 @@ impl ObjectSubclass for HsvFilter {
|
|||
type ParentType = gst_base::BaseTransform;
|
||||
}
|
||||
|
||||
impl HsvFilter {
|
||||
#[inline]
|
||||
fn hsv_filter<CF, FF>(
|
||||
&self,
|
||||
frame: &mut gst_video::video_frame::VideoFrameRef<&mut gst::buffer::BufferRef>,
|
||||
to_hsv: CF,
|
||||
apply_filter: FF,
|
||||
) where
|
||||
CF: Fn(&[u8]) -> [f32; 3],
|
||||
FF: Fn(&[f32; 3], &mut [u8]),
|
||||
{
|
||||
let settings = *self.settings.lock().unwrap();
|
||||
|
||||
let width = frame.width() as usize;
|
||||
let stride = frame.plane_stride()[0] as usize;
|
||||
let nb_channels = frame.format_info().pixel_stride()[0] as usize;
|
||||
let data = frame.plane_data_mut(0).unwrap();
|
||||
|
||||
assert_eq!(data.len() % nb_channels, 0);
|
||||
|
||||
let line_bytes = width * nb_channels;
|
||||
|
||||
for line in data.chunks_exact_mut(stride) {
|
||||
for p in line[..line_bytes].chunks_exact_mut(nb_channels) {
|
||||
assert_eq!(p.len(), nb_channels);
|
||||
|
||||
let mut hsv = to_hsv(p);
|
||||
|
||||
hsv[0] = (hsv[0] + settings.hue_shift) % 360.0;
|
||||
if hsv[0] < 0.0 {
|
||||
hsv[0] += 360.0;
|
||||
}
|
||||
hsv[1] = hsvutils::Clamp::clamp(
|
||||
settings.saturation_mul * hsv[1] + settings.saturation_off,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
hsv[2] = hsvutils::Clamp::clamp(
|
||||
settings.value_mul * hsv[2] + settings.value_off,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
apply_filter(&hsv, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for HsvFilter {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
|
@ -255,7 +304,18 @@ impl ElementImpl for HsvFilter {
|
|||
&[
|
||||
(
|
||||
"format",
|
||||
&gst::List::new(&[&gst_video::VideoFormat::Rgbx.to_str()]),
|
||||
&gst::List::new(&[
|
||||
&gst_video::VideoFormat::Rgbx.to_str(),
|
||||
&gst_video::VideoFormat::Xrgb.to_str(),
|
||||
&gst_video::VideoFormat::Bgrx.to_str(),
|
||||
&gst_video::VideoFormat::Xbgr.to_str(),
|
||||
&gst_video::VideoFormat::Rgba.to_str(),
|
||||
&gst_video::VideoFormat::Argb.to_str(),
|
||||
&gst_video::VideoFormat::Bgra.to_str(),
|
||||
&gst_video::VideoFormat::Abgr.to_str(),
|
||||
&gst_video::VideoFormat::Rgb.to_str(),
|
||||
&gst_video::VideoFormat::Bgr.to_str(),
|
||||
]),
|
||||
),
|
||||
("width", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
||||
("height", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
||||
|
@ -346,8 +406,6 @@ impl BaseTransformImpl for HsvFilter {
|
|||
element: &Self::Type,
|
||||
buf: &mut gst::BufferRef,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let settings = *self.settings.lock().unwrap();
|
||||
|
||||
let mut state_guard = self.state.borrow_mut();
|
||||
let state = state_guard.as_mut().ok_or(gst::FlowError::NotNegotiated)?;
|
||||
|
||||
|
@ -361,39 +419,52 @@ impl BaseTransformImpl for HsvFilter {
|
|||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
let width = frame.width() as usize;
|
||||
let stride = frame.plane_stride()[0] as usize;
|
||||
let format = frame.format();
|
||||
let data = frame.plane_data_mut(0).unwrap();
|
||||
|
||||
assert_eq!(format, gst_video::VideoFormat::Rgbx);
|
||||
assert_eq!(data.len() % 4, 0);
|
||||
|
||||
let line_bytes = width * 4;
|
||||
|
||||
for line in data.chunks_exact_mut(stride) {
|
||||
for p in line[..line_bytes].chunks_exact_mut(4) {
|
||||
assert_eq!(p.len(), 4);
|
||||
|
||||
let mut hsv =
|
||||
hsvutils::from_rgb(p[..3].try_into().expect("slice with incorrect length"));
|
||||
hsv[0] = (hsv[0] + settings.hue_shift) % 360.0;
|
||||
if hsv[0] < 0.0 {
|
||||
hsv[0] += 360.0;
|
||||
}
|
||||
hsv[1] = hsvutils::Clamp::clamp(
|
||||
settings.saturation_mul * hsv[1] + settings.saturation_off,
|
||||
0.0,
|
||||
1.0,
|
||||
match state.info.format() {
|
||||
gst_video::VideoFormat::Rgbx
|
||||
| gst_video::VideoFormat::Rgba
|
||||
| gst_video::VideoFormat::Rgb => {
|
||||
self.hsv_filter(
|
||||
&mut frame,
|
||||
|p| hsvutils::from_rgb(p[..3].try_into().expect("slice with incorrect length")),
|
||||
|hsv, p| {
|
||||
p[..3].copy_from_slice(&hsvutils::to_rgb(hsv));
|
||||
},
|
||||
);
|
||||
hsv[2] = hsvutils::Clamp::clamp(
|
||||
settings.value_mul * hsv[2] + settings.value_off,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
p[..3].copy_from_slice(&hsvutils::to_rgb(&hsv));
|
||||
}
|
||||
gst_video::VideoFormat::Xrgb | gst_video::VideoFormat::Argb => {
|
||||
self.hsv_filter(
|
||||
&mut frame,
|
||||
|p| {
|
||||
hsvutils::from_rgb(p[1..4].try_into().expect("slice with incorrect length"))
|
||||
},
|
||||
|hsv, p| {
|
||||
p[1..4].copy_from_slice(&hsvutils::to_rgb(hsv));
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Bgrx
|
||||
| gst_video::VideoFormat::Bgra
|
||||
| gst_video::VideoFormat::Bgr => {
|
||||
self.hsv_filter(
|
||||
&mut frame,
|
||||
|p| hsvutils::from_bgr(p[..3].try_into().expect("slice with incorrect length")),
|
||||
|hsv, p| {
|
||||
p[..3].copy_from_slice(&hsvutils::to_bgr(hsv));
|
||||
},
|
||||
);
|
||||
}
|
||||
gst_video::VideoFormat::Xbgr | gst_video::VideoFormat::Abgr => {
|
||||
self.hsv_filter(
|
||||
&mut frame,
|
||||
|p| {
|
||||
hsvutils::from_bgr(p[1..4].try_into().expect("slice with incorrect length"))
|
||||
},
|
||||
|hsv, p| {
|
||||
p[1..4].copy_from_slice(&hsvutils::to_bgr(hsv));
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
|
|
|
@ -83,6 +83,50 @@ pub fn from_rgb(in_p: &[u8; 3]) -> [f32; 3] {
|
|||
]
|
||||
}
|
||||
|
||||
// Converts a BGR pixel to HSV
|
||||
#[inline]
|
||||
pub fn from_bgr(in_p: &[u8; 3]) -> [f32; 3] {
|
||||
let b = in_p[0] as f32 / 255.0;
|
||||
let g = in_p[1] as f32 / 255.0;
|
||||
let r = in_p[2] as f32 / 255.0;
|
||||
|
||||
let value: f32 = *in_p
|
||||
.iter()
|
||||
.max()
|
||||
.expect("Cannot find max value from rgb input") as f32
|
||||
/ 255.0;
|
||||
let chroma: f32 = value
|
||||
- (*in_p
|
||||
.iter()
|
||||
.min()
|
||||
.expect("Cannot find min value from rgb input") as f32
|
||||
/ 255.0);
|
||||
|
||||
let mut hue: f32 = if chroma == 0.0 {
|
||||
0.0
|
||||
} else if (value - r).abs() < EPSILON {
|
||||
60.0 * ((g - b) / chroma)
|
||||
} else if (value - g).abs() < EPSILON {
|
||||
60.0 * (2.0 + ((b - r) / chroma))
|
||||
} else if (value - b).abs() < EPSILON {
|
||||
60.0 * (4.0 + ((r - g) / chroma))
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
if hue < 0.0 {
|
||||
hue += 360.0;
|
||||
}
|
||||
|
||||
let saturation: f32 = if value == 0.0 { 0.0 } else { chroma / value };
|
||||
|
||||
[
|
||||
hue % 360.0,
|
||||
saturation.clamp(0.0, 1.0),
|
||||
value.clamp(0.0, 1.0),
|
||||
]
|
||||
}
|
||||
|
||||
// Converts a HSV pixel to RGB
|
||||
#[inline]
|
||||
pub fn to_rgb(in_p: &[f32; 3]) -> [u8; 3] {
|
||||
|
@ -118,6 +162,41 @@ pub fn to_rgb(in_p: &[f32; 3]) -> [u8; 3] {
|
|||
]
|
||||
}
|
||||
|
||||
// Converts a HSV pixel to RGB
|
||||
#[inline]
|
||||
pub fn to_bgr(in_p: &[f32; 3]) -> [u8; 3] {
|
||||
let c: f32 = in_p[2] * in_p[1];
|
||||
let hue_prime: f32 = in_p[0] / 60.0;
|
||||
|
||||
let x: f32 = c * (1.0 - ((hue_prime % 2.0) - 1.0).abs());
|
||||
|
||||
let rgb_prime = if hue_prime < 0.0 {
|
||||
[0.0, 0.0, 0.0]
|
||||
} else if hue_prime <= 1.0 {
|
||||
[c, x, 0.0]
|
||||
} else if hue_prime <= 2.0 {
|
||||
[x, c, 0.0]
|
||||
} else if hue_prime <= 3.0 {
|
||||
[0.0, c, x]
|
||||
} else if hue_prime <= 4.0 {
|
||||
[0.0, x, c]
|
||||
} else if hue_prime <= 5.0 {
|
||||
[x, 0.0, c]
|
||||
} else if hue_prime <= 6.0 {
|
||||
[c, 0.0, x]
|
||||
} else {
|
||||
[0.0, 0.0, 0.0]
|
||||
};
|
||||
|
||||
let m = in_p[2] - c;
|
||||
|
||||
[
|
||||
((rgb_prime[2] + m) * 255.0).clamp(0.0, 255.0) as u8,
|
||||
((rgb_prime[1] + m) * 255.0).clamp(0.0, 255.0) as u8,
|
||||
((rgb_prime[0] + m) * 255.0).clamp(0.0, 255.0) as u8,
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
@ -143,6 +222,12 @@ mod tests {
|
|||
const RGB_GREEN: [u8; 3] = [0, 255, 0];
|
||||
const RGB_BLUE: [u8; 3] = [0, 0, 255];
|
||||
|
||||
const BGR_WHITE: [u8; 3] = [255, 255, 255];
|
||||
const BGR_BLACK: [u8; 3] = [0, 0, 0];
|
||||
const BGR_RED: [u8; 3] = [0, 0, 255];
|
||||
const BGR_GREEN: [u8; 3] = [0, 255, 0];
|
||||
const BGR_BLUE: [u8; 3] = [255, 0, 0];
|
||||
|
||||
const HSV_WHITE: [f32; 3] = [0.0, 0.0, 1.0];
|
||||
const HSV_BLACK: [f32; 3] = [0.0, 0.0, 0.0];
|
||||
const HSV_RED: [f32; 3] = [0.0, 1.0, 1.0];
|
||||
|
@ -160,6 +245,17 @@ mod tests {
|
|||
assert!(is_equivalent(&from_rgb(&RGB_BLUE), &HSV_BLUE, EPSILON));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_bgr() {
|
||||
use super::*;
|
||||
|
||||
assert!(is_equivalent(&from_bgr(&BGR_WHITE), &HSV_WHITE, EPSILON));
|
||||
assert!(is_equivalent(&from_bgr(&BGR_BLACK), &HSV_BLACK, EPSILON));
|
||||
assert!(is_equivalent(&from_bgr(&BGR_RED), &HSV_RED, EPSILON));
|
||||
assert!(is_equivalent(&from_bgr(&BGR_GREEN), &HSV_GREEN, EPSILON));
|
||||
assert!(is_equivalent(&from_bgr(&BGR_BLUE), &HSV_BLUE, EPSILON));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_rgb() {
|
||||
use super::*;
|
||||
|
@ -170,4 +266,15 @@ mod tests {
|
|||
assert!(to_rgb(&HSV_GREEN) == RGB_GREEN);
|
||||
assert!(to_rgb(&HSV_BLUE) == RGB_BLUE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_bgr() {
|
||||
use super::*;
|
||||
|
||||
assert!(to_bgr(&HSV_WHITE) == BGR_WHITE);
|
||||
assert!(to_bgr(&HSV_BLACK) == BGR_BLACK);
|
||||
assert!(to_bgr(&HSV_RED) == BGR_RED);
|
||||
assert!(to_bgr(&HSV_GREEN) == BGR_GREEN);
|
||||
assert!(to_bgr(&HSV_BLUE) == BGR_BLUE);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue