mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-06-10 10:19:23 +00:00
hsv: added support for more RGB and BGR formats to hsvdetector and hsvfilter
This commit is contained in:
parent
6b5e536ca6
commit
e9f0a3b8ac
|
@ -82,6 +82,93 @@ impl ObjectSubclass for HsvDetector {
|
||||||
type ParentType = gst_base::BaseTransform;
|
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 {
|
impl ObjectImpl for HsvDetector {
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
|
@ -282,10 +369,7 @@ impl ElementImpl for HsvDetector {
|
||||||
let caps = gst::Caps::new_simple(
|
let caps = gst::Caps::new_simple(
|
||||||
"video/x-raw",
|
"video/x-raw",
|
||||||
&[
|
&[
|
||||||
(
|
("format", &gst::List::from_owned(video_output_formats())),
|
||||||
"format",
|
|
||||||
&gst::List::new(&[&gst_video::VideoFormat::Rgba.to_str()]),
|
|
||||||
),
|
|
||||||
("width", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
("width", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
||||||
("height", &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(
|
let caps = gst::Caps::new_simple(
|
||||||
"video/x-raw",
|
"video/x-raw",
|
||||||
&[
|
&[
|
||||||
(
|
("format", &gst::List::from_owned(video_input_formats())),
|
||||||
"format",
|
|
||||||
&gst::List::new(&[&gst_video::VideoFormat::Rgbx.to_str()]),
|
|
||||||
),
|
|
||||||
("width", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
("width", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
||||||
("height", &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();
|
let mut caps = caps.clone();
|
||||||
|
|
||||||
for s in caps.make_mut().iter_mut() {
|
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
|
caps
|
||||||
|
@ -366,7 +447,7 @@ impl BaseTransformImpl for HsvDetector {
|
||||||
let mut caps = caps.clone();
|
let mut caps = caps.clone();
|
||||||
|
|
||||||
for s in caps.make_mut().iter_mut() {
|
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
|
caps
|
||||||
|
@ -439,8 +520,6 @@ impl BaseTransformImpl for HsvDetector {
|
||||||
inbuf: &gst::Buffer,
|
inbuf: &gst::Buffer,
|
||||||
outbuf: &mut gst::BufferRef,
|
outbuf: &mut gst::BufferRef,
|
||||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
let settings = *self.settings.lock().unwrap();
|
|
||||||
|
|
||||||
let mut state_guard = self.state.borrow_mut();
|
let mut state_guard = self.state.borrow_mut();
|
||||||
let state = state_guard.as_mut().ok_or(gst::FlowError::NotNegotiated)?;
|
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
|
match state.in_info.format() {
|
||||||
// local variables. This saves some typing below.
|
gst_video::VideoFormat::Rgbx | gst_video::VideoFormat::Rgb => {
|
||||||
let width = in_frame.width() as usize;
|
match state.out_info.format() {
|
||||||
let in_stride = in_frame.plane_stride()[0] as usize;
|
gst_video::VideoFormat::Rgba => {
|
||||||
let in_data = in_frame.plane_data(0).unwrap();
|
self.hsv_detect(
|
||||||
let out_stride = out_frame.plane_stride()[0] as usize;
|
&in_frame,
|
||||||
let out_data = out_frame.plane_data_mut(0).unwrap();
|
&mut out_frame,
|
||||||
|
|in_p| {
|
||||||
assert_eq!(in_data.len() % 4, 0);
|
hsvutils::from_rgb(
|
||||||
assert_eq!(out_data.len() / out_stride, in_data.len() / in_stride);
|
in_p[..3].try_into().expect("slice with incorrect length"),
|
||||||
|
)
|
||||||
let in_line_bytes = width * 4;
|
},
|
||||||
let out_line_bytes = width * 4;
|
|in_p, out_p, val| {
|
||||||
|
out_p[..3].copy_from_slice(&in_p[..3]);
|
||||||
assert!(in_line_bytes <= in_stride);
|
out_p[3] = val;
|
||||||
assert!(out_line_bytes <= out_stride);
|
},
|
||||||
|
);
|
||||||
for (in_line, out_line) in in_data
|
}
|
||||||
.chunks_exact(in_stride)
|
gst_video::VideoFormat::Argb => {
|
||||||
.zip(out_data.chunks_exact_mut(out_stride))
|
self.hsv_detect(
|
||||||
{
|
&in_frame,
|
||||||
for (in_p, out_p) in in_line[..in_line_bytes]
|
&mut out_frame,
|
||||||
.chunks_exact(4)
|
|in_p| {
|
||||||
.zip(out_line[..out_line_bytes].chunks_exact_mut(4))
|
hsvutils::from_rgb(
|
||||||
{
|
in_p[..3].try_into().expect("slice with incorrect length"),
|
||||||
assert_eq!(out_p.len(), 4);
|
)
|
||||||
let hsv =
|
},
|
||||||
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]);
|
||||||
// We handle hue being circular here
|
out_p[0] = val;
|
||||||
let ref_hue_offset = 180.0 - settings.hue_ref;
|
},
|
||||||
let mut shifted_hue = hsv[0] + ref_hue_offset;
|
);
|
||||||
|
}
|
||||||
if shifted_hue < 0.0 {
|
gst_video::VideoFormat::Bgra => {
|
||||||
shifted_hue += 360.0;
|
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;
|
gst_video::VideoFormat::Xrgb => {
|
||||||
|
match state.out_info.format() {
|
||||||
out_p[..3].copy_from_slice(&in_p[..3]);
|
gst_video::VideoFormat::Rgba => {
|
||||||
|
self.hsv_detect(
|
||||||
out_p[3] = if (shifted_hue - 180.0).abs() <= settings.hue_var
|
&in_frame,
|
||||||
&& (hsv[1] - settings.saturation_ref).abs() <= settings.saturation_var
|
&mut out_frame,
|
||||||
&& (hsv[2] - settings.value_ref).abs() <= settings.value_var
|
|in_p| {
|
||||||
{
|
hsvutils::from_rgb(
|
||||||
255
|
in_p[1..4].try_into().expect("slice with incorrect length"),
|
||||||
} else {
|
)
|
||||||
0
|
},
|
||||||
|
|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)
|
Ok(gst::FlowSuccess::Ok)
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,55 @@ impl ObjectSubclass for HsvFilter {
|
||||||
type ParentType = gst_base::BaseTransform;
|
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 {
|
impl ObjectImpl for HsvFilter {
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
|
@ -255,7 +304,18 @@ impl ElementImpl for HsvFilter {
|
||||||
&[
|
&[
|
||||||
(
|
(
|
||||||
"format",
|
"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)),
|
("width", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
||||||
("height", &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,
|
element: &Self::Type,
|
||||||
buf: &mut gst::BufferRef,
|
buf: &mut gst::BufferRef,
|
||||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
let settings = *self.settings.lock().unwrap();
|
|
||||||
|
|
||||||
let mut state_guard = self.state.borrow_mut();
|
let mut state_guard = self.state.borrow_mut();
|
||||||
let state = state_guard.as_mut().ok_or(gst::FlowError::NotNegotiated)?;
|
let state = state_guard.as_mut().ok_or(gst::FlowError::NotNegotiated)?;
|
||||||
|
|
||||||
|
@ -361,39 +419,52 @@ impl BaseTransformImpl for HsvFilter {
|
||||||
gst::FlowError::Error
|
gst::FlowError::Error
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let width = frame.width() as usize;
|
match state.info.format() {
|
||||||
let stride = frame.plane_stride()[0] as usize;
|
gst_video::VideoFormat::Rgbx
|
||||||
let format = frame.format();
|
| gst_video::VideoFormat::Rgba
|
||||||
let data = frame.plane_data_mut(0).unwrap();
|
| gst_video::VideoFormat::Rgb => {
|
||||||
|
self.hsv_filter(
|
||||||
assert_eq!(format, gst_video::VideoFormat::Rgbx);
|
&mut frame,
|
||||||
assert_eq!(data.len() % 4, 0);
|
|p| hsvutils::from_rgb(p[..3].try_into().expect("slice with incorrect length")),
|
||||||
|
|hsv, p| {
|
||||||
let line_bytes = width * 4;
|
p[..3].copy_from_slice(&hsvutils::to_rgb(hsv));
|
||||||
|
},
|
||||||
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,
|
|
||||||
);
|
);
|
||||||
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)
|
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
|
// Converts a HSV pixel to RGB
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_rgb(in_p: &[f32; 3]) -> [u8; 3] {
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
@ -143,6 +222,12 @@ mod tests {
|
||||||
const RGB_GREEN: [u8; 3] = [0, 255, 0];
|
const RGB_GREEN: [u8; 3] = [0, 255, 0];
|
||||||
const RGB_BLUE: [u8; 3] = [0, 0, 255];
|
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_WHITE: [f32; 3] = [0.0, 0.0, 1.0];
|
||||||
const HSV_BLACK: [f32; 3] = [0.0, 0.0, 0.0];
|
const HSV_BLACK: [f32; 3] = [0.0, 0.0, 0.0];
|
||||||
const HSV_RED: [f32; 3] = [0.0, 1.0, 1.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));
|
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]
|
#[test]
|
||||||
fn test_to_rgb() {
|
fn test_to_rgb() {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -170,4 +266,15 @@ mod tests {
|
||||||
assert!(to_rgb(&HSV_GREEN) == RGB_GREEN);
|
assert!(to_rgb(&HSV_GREEN) == RGB_GREEN);
|
||||||
assert!(to_rgb(&HSV_BLUE) == RGB_BLUE);
|
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