Add support for returning Vec<u8> instead of String when needed
MODIFY
binding-generator/src/func.rs
binding-generator/src/func.rs
@@ -392,6 +392,8 @@ impl<'tu, 'ge> Func<'tu, 'ge> {
out.set_type_hint(TypeRefTypeHint::Specialized(spec_type));
}
}
+ } else if let Some(&over) = settings::ARGUMENT_OVERRIDE.get(&self.func_id()).and_then(|x| x.get("return")) {
+ out.set_type_hint(TypeRefTypeHint::ArgOverride(over))
}
if let Some(type_ref) = out.as_reference() {
type_ref

MODIFY
binding-generator/src/settings.rs
binding-generator/src/settings.rs
@@ -472,6 +472,8 @@ pub static FUNC_RENAME: Lazy<HashMap<&str, &str>> = Lazy::new(|| hashmap! {
"cv_groupRectangles_vector_Rect_R_vector_int_R_int_double" => "+_weights",
"cv_groupRectangles_vector_Rect_R_int_double_vector_int_X_vector_double_X" => "+_levelweights",
+ "cv_QRCodeDetector_detectAndDecodeMulti_const_const__InputArrayR_vector_string_R_const__OutputArrayR_const__OutputArrayR" => "-", // fixme: stores data to Vector<String>, that doesn't work yet
+
// ### optflow ###
"cv_optflow_GPCTrainingSamples_operator_cv_optflow_GPCSamplesVector" => "-", // support of "operator &" missing
@@ -990,6 +992,21 @@ pub static ARGUMENT_OVERRIDE: Lazy<HashMap<FuncId, HashMap<&str, ArgOverride>>>
"minIdx" => ArgOverride::Nullable,
"maxIdx" => ArgOverride::Nullable,
},
+ FuncId::new("cv::decodeQRCode", ["in", "points", "decoded_info", "straight_qrcode"]) => hashmap! {
+ "decoded_info" => ArgOverride::StringAsBytes,
+ },
+ FuncId::new("cv::QRCodeDetector::decode", ["img", "points", "straight_qrcode"]) => hashmap! {
+ "return" => ArgOverride::StringAsBytes,
+ },
+ FuncId::new("cv::QRCodeDetector::decodeCurved", ["img", "points", "straight_qrcode"]) => hashmap! {
+ "return" => ArgOverride::StringAsBytes,
+ },
+ FuncId::new("cv::QRCodeDetector::detectAndDecode", ["img", "points", "straight_qrcode"]) => hashmap! {
+ "return" => ArgOverride::StringAsBytes,
+ },
+ FuncId::new("cv::QRCodeDetector::detectAndDecodeCurved", ["img", "points", "straight_qrcode"]) => hashmap! {
+ "return" => ArgOverride::StringAsBytes,
+ },
});
pub static NO_SKIP_NAMESPACE_IN_LOCALNAME: Lazy<HashMap<&str, HashMap<&str, &str>>> = Lazy::new(|| hashmap! {

MODIFY
binding-generator/src/type_ref.rs
binding-generator/src/type_ref.rs
@@ -54,11 +54,18 @@ pub enum DependentTypeMode {
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum StrType {
- StdString,
- CvString,
+ StdString(StrEnc),
+ CvString(StrEnc),
CharPtr,
}
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum StrEnc {
+ Text,
+ /// string with binary data, e.g. can contain 0 byte
+ Binary,
+}
+
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Dir<T> {
In(T),
@@ -499,16 +506,24 @@ impl<'tu, 'ge> TypeRef<'tu, 'ge> {
}
pub fn as_string(&self) -> Option<Dir<StrType>> {
- fn class_string_type(cls: Class) -> Option<StrType> {
+ let class_string_type = |cls: Class| -> Option<StrType> {
let cpp_fullname = cls.cpp_fullname();
if cpp_fullname.starts_with("std::") && cpp_fullname.ends_with("::string") {
- Some(StrType::StdString)
+ Some(StrType::StdString(if matches!(self.type_hint, TypeRefTypeHint::ArgOverride(ArgOverride::StringAsBytes)) {
+ StrEnc::Binary
+ } else {
+ StrEnc::Text
+ }))
} else if cpp_fullname == "cv::String" {
- Some(StrType::CvString)
+ Some(StrType::CvString(if matches!(self.type_hint, TypeRefTypeHint::ArgOverride(ArgOverride::StringAsBytes)) {
+ StrEnc::Binary
+ } else {
+ StrEnc::Text
+ }))
} else {
None
}
- }
+ };
match self.canonical().kind() {
Kind::Class(cls) => {
@@ -555,11 +570,11 @@ impl<'tu, 'ge> TypeRef<'tu, 'ge> {
}
pub fn is_std_string(&self) -> bool {
- matches!(self.as_string(), Some(Dir::In(StrType::StdString)))
+ matches!(self.as_string(), Some(Dir::In(StrType::StdString(_))))
}
pub fn is_cv_string(&self) -> bool {
- matches!(self.as_string(), Some(Dir::In(StrType::CvString)))
+ matches!(self.as_string(), Some(Dir::In(StrType::CvString(_))))
}
pub fn is_char_ptr_string(&self) -> bool {
@@ -1049,8 +1064,10 @@ impl<'tu, 'ge> TypeRef<'tu, 'ge> {
let typ = 'decl_type: loop {
if let Some(dir) = self.as_string() {
break 'decl_type match dir {
- Dir::In(_) => "&str".into(),
- Dir::Out(_) => "&mut String".into(),
+ Dir::In(StrType::StdString(StrEnc::Text) | StrType::CvString(StrEnc::Text) | StrType::CharPtr) => "&str".into(),
+ Dir::In(StrType::StdString(StrEnc::Binary) | StrType::CvString(StrEnc::Binary)) => "&[u8]".into(),
+ Dir::Out(StrType::StdString(StrEnc::Text) | StrType::CvString(StrEnc::Text) | StrType::CharPtr) => "&mut String".into(),
+ Dir::Out(StrType::StdString(StrEnc::Binary) | StrType::CvString(StrEnc::Binary)) => "&mut Vec<u8>".into(),
};
} else if self.is_input_array() {
break 'decl_type "&dyn core::ToInputArray".into();
@@ -1289,12 +1306,18 @@ impl<'tu, 'ge> TypeRef<'tu, 'ge> {
}
pub fn rust_arg_post_call(&self, name: &str, _is_function_infallible: bool) -> String {
- if let Some(Dir::Out(_)) = self.as_string() {
+ match self.as_string() {
+ Some(Dir::Out(StrType::StdString(StrEnc::Text) | StrType::CvString(StrEnc::Text) | StrType::CharPtr)) => {
format!("string_arg_output_receive!({name}_via => {name})", name = name)
- } else {
+ }
+ Some(Dir::Out(StrType::StdString(StrEnc::Binary) | StrType::CvString(StrEnc::Binary))) => {
+ format!("byte_string_arg_output_receive!({name}_via => {name})", name = name)
+ }
+ _ => {
"".to_string()
}
}
+ }
pub fn dependent_types(&self, mode: DependentTypeMode) -> Vec<DependentType<'tu, 'ge>> {
match self.source().kind() {
@@ -1327,7 +1350,7 @@ impl<'tu, 'ge> TypeRef<'tu, 'ge> {
}
// implement workaround for race when type with std::string gets generated first
// we only want vector<cv::String> because it's more compatible across OpenCV versions
- if str_type == StrType::StdString {
+ if matches!(str_type, StrType::StdString(_)) {
let tref = TypeRef::new(vec_cv_string, self.gen_env);
out.push(DependentType::from_vector(tref.as_vector().expect("Not possible unless something is terribly broken")));
} else {
@@ -1471,19 +1494,20 @@ impl<'tu, 'ge> TypeRef<'tu, 'ge> {
pub fn cpp_arg_pre_call(&self, name: &str) -> String {
match self.as_string() {
- Some(Dir::Out(StrType::StdString)) => {
- return format!("std::string {name}_out", name=name);
+ Some(Dir::Out(StrType::StdString(_))) => {
+ format!("std::string {name}_out", name=name)
}
- Some(Dir::Out(StrType::CvString)) => {
- return format!("cv::String {name}_out", name=name);
+ Some(Dir::Out(StrType::CvString(_))) => {
+ format!("cv::String {name}_out", name=name)
}
Some(Dir::Out(StrType::CharPtr)) => {
- return format!("char* {name}_out = new char[1024]()", name=name);
- }
- Some(_) | None => {}
+ format!("char* {name}_out = new char[1024]()", name=name)
}
+ Some(Dir::In(_)) | None => {
"".to_string()
}
+ }
+ }
pub fn cpp_arg_func_call<'a>(&self, name: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
let name = name.into();
@@ -1493,10 +1517,10 @@ impl<'tu, 'ge> TypeRef<'tu, 'ge> {
let ptr = if str_type != StrType::CharPtr && self.as_pointer().is_some() { "&" } else { "" };
return format!("{ptr}{name}_out", ptr=ptr, name=name).into();
}
- Some(Dir::In(StrType::StdString)) => {
+ Some(Dir::In(StrType::StdString(_))) => {
return format!("std::string({name})", name=name).into();
},
- Some(Dir::In(StrType::CvString)) => {
+ Some(Dir::In(StrType::CvString(_))) => {
return format!("cv::String({name})", name=name).into();
},
Some(Dir::In(StrType::CharPtr)) | None => {}
@@ -1528,16 +1552,23 @@ impl<'tu, 'ge> TypeRef<'tu, 'ge> {
pub fn cpp_arg_post_call(&self, name: &str) -> String {
match self.as_string() {
- Some(Dir::Out(StrType::StdString)) | Some(Dir::Out(StrType::CvString)) => {
- return format!("*{name} = ocvrs_create_string({name}_out.c_str())", name=name);
+ Some(Dir::Out(StrType::StdString(StrEnc::Text) | StrType::CvString(StrEnc::Text))) => {
+ format!("*{name} = ocvrs_create_string({name}_out.c_str())", name=name)
}
Some(Dir::Out(StrType::CharPtr)) => {
- return format!("*{name} = ocvrs_create_string({name}_out)", name=name);
+ format!("*{name} = ocvrs_create_string({name}_out)", name=name)
}
- _ => {}
+ Some(Dir::Out(StrType::StdString(StrEnc::Binary))) => {
+ format!("*{name} = ocvrs_create_byte_string({name}_out.data(), {name}_out.size())", name=name)
}
+ Some(Dir::Out(StrType::CvString(StrEnc::Binary))) => {
+ format!("*{name} = ocvrs_create_byte_string({name}_out.begin(), {name}_out.size())", name=name)
+ }
+ Some(Dir::In(_)) | None => {
"".to_string()
}
+ }
+ }
// we need cleanup as a separate step from post_call because in cv_ocl_convertTypeStr_int_int_int_charX the
// return value is actually one of the arguments and if we free it (in post_call phase) before converting
@@ -1586,15 +1617,21 @@ impl fmt::Debug for TypeRef<'_, '_> {
},
};
match str_type {
- StrType::StdString => {
+ StrType::StdString(StrEnc::Text) => {
props.push("std_string");
},
- StrType::CvString => {
+ StrType::CvString(StrEnc::Text) => {
props.push("cv_string");
},
StrType::CharPtr => {
props.push("char_ptr_string");
},
+ StrType::StdString(StrEnc::Binary) => {
+ props.push("byte_std_string");
+ },
+ StrType::CvString(StrEnc::Binary) => {
+ props.push("byte_cv_string");
+ },
}
}
if self.is_void() {

MODIFY
binding-generator/src/type_ref/renderer.rs
binding-generator/src/type_ref/renderer.rs
@@ -3,7 +3,7 @@ use std::{
fmt::{self, Write},
};
-use crate::{Element, settings, StringExt};
+use crate::{Element, settings, StringExt, type_ref::{Dir, StrEnc, StrType}};
use super::{Kind, TemplateArg, TypeRef};
@@ -341,9 +341,15 @@ impl TypeRefRenderer<'_> for RustRenderer {
type Recursed = Self;
fn render<'t>(self, type_ref: &'t TypeRef) -> Cow<'t, str> {
- if type_ref.as_string().is_some() {
+ if let Some(str_type) = type_ref.as_string() {
#[allow(clippy::if_same_then_else)]
- return if type_ref.constness().is_const() {
+ return if matches!(str_type, Dir::In(StrType::StdString(StrEnc::Binary) | StrType::CvString(StrEnc::Binary)) | Dir::Out(StrType::StdString(StrEnc::Binary) | StrType::CvString(StrEnc::Binary))) {
+ if self.name_style.turbo_fish_style().is_turbo() {
+ "Vec::<u8>"
+ } else {
+ "Vec<u8>"
+ }
+ } else if type_ref.constness().is_const() {
"String" // todo implement receiving const str's
} else {
"String"

MODIFY
binding-generator/src/writer/rust_native/func.rs
binding-generator/src/writer/rust_native/func.rs
@@ -18,7 +18,7 @@ use crate::{
settings,
StrExt,
StringExt,
- type_ref::{Dir, FishStyle, StrType},
+ type_ref::{Dir, FishStyle, StrEnc, StrType},
};
use super::RustNativeGeneratedElement;
@@ -271,12 +271,18 @@ fn cpp_method_return<'f>(f: &'f Func, is_infallible: bool) -> Cow<'f, str> {
out.unwrap_or_else(|| format!("new {typ}(ret)", typ=return_type.cpp_full()).into())
} else if let Some(Dir::In(string_type)) | Some(Dir::Out(string_type)) = return_type.as_string() {
match string_type {
- StrType::StdString | StrType::CvString => {
+ StrType::StdString(StrEnc::Text) | StrType::CvString(StrEnc::Text) => {
"ocvrs_create_string(ret.c_str())".into()
},
StrType::CharPtr => {
"ocvrs_create_string(ret)".into()
},
+ StrType::StdString(StrEnc::Binary) => {
+ "ocvrs_create_byte_string(ret.data(), ret.size())".into()
+ }
+ StrType::CvString(StrEnc::Binary) => {
+ "ocvrs_create_byte_string(ret.begin(), ret.size())".into()
+ }
}
} else {
// fixme

MODIFY
src/templ.rs
src/templ.rs
use std::{
ffi::CStr,
os::raw::c_char,
+ slice,
};
+use crate::platform_types::size_t;
+
macro_rules! extern_container_arg {
(nofail mut $name: ident) => {
let mut $name = $name.opencv_into_extern_container_nofail();
@@ -30,6 +33,14 @@ macro_rules! string_arg_output_receive {
};
}
+// currently only used in objdetect::decodeQRCode function in OpenCV 3.4
+#[allow(unused_macros)]
+macro_rules! byte_string_arg_output_receive {
+ ($name_via: ident => $name: ident) => {
+ *$name = unsafe { $crate::templ::receive_byte_string($name_via as *mut Vec<u8>) };
+ };
+}
+
macro_rules! callback_arg {
($tr_name: ident($($tr_arg_name: ident: $tr_arg_type: ty),*) -> $tr_ret: ty => $tr_userdata_name: ident in $callbacks_name: ident => $callback_name: ident($($fw_arg_name: ident: $fw_arg_type: ty),*) -> $fw_ret: ty) => {
unsafe extern "C" fn trampoline($($tr_arg_name: $tr_arg_type),*) -> $tr_ret {
@@ -96,6 +107,14 @@ unsafe extern "C" fn ocvrs_create_string(s: *const c_char) -> *mut String {
Box::into_raw(Box::new(s))
}
+/// The return type of this function goes into `receive_byte_string`
+#[no_mangle]
+unsafe extern "C" fn ocvrs_create_byte_string(v: *const u8, len: size_t) -> *mut Vec<u8> {
+ let v = slice::from_raw_parts(v, len)
+ .to_vec();
+ Box::into_raw(Box::new(v))
+}
+
#[inline]
pub unsafe fn receive_string(s: *mut String) -> String {
if s.is_null() {
@@ -103,3 +122,11 @@ pub unsafe fn receive_string(s: *mut String) -> String {
}
*Box::from_raw(s)
}
+
+#[inline]
+pub unsafe fn receive_byte_string(s: *mut Vec<u8>) -> Vec<u8> {
+ if s.is_null() {
+ panic!("Got null pointer for receive_byte_vec()");
+ }
+ *Box::from_raw(s)
+}

MODIFY
src/traits/opencv_type.rs
src/traits/opencv_type.rs
@@ -242,6 +242,57 @@ impl OpenCVTypeExternContainer for CString {
}
}
+impl<'a> OpenCVType<'a> for Vec<u8> {
+ type Arg = Self;
+ type ExternReceive = *mut c_void;
+ type ExternContainer = Self;
+
+ #[inline]
+ fn opencv_into_extern_container(self) -> Result<Self::ExternContainer> {
+ Ok(self)
+ }
+
+ #[inline]
+ fn opencv_into_extern_container_nofail(self) -> Self::ExternContainer {
+ self
+ }
+
+ #[inline]
+ unsafe fn opencv_from_extern(s: Self::ExternReceive) -> Self {
+ crate::templ::receive_byte_string(s as *mut Vec<u8>)
+ }
+}
+
+impl OpenCVTypeArg<'_> for Vec<u8> {
+ type ExternContainer = Self;
+
+ #[inline]
+ fn opencv_into_extern_container_nofail(self) -> Self::ExternContainer {
+ self.to_vec()
+ }
+}
+
+impl OpenCVTypeExternContainer for Vec<u8> {
+ type ExternSend = *const u8;
+ type ExternSendMut = *mut u8;
+
+ #[inline]
+ fn opencv_as_extern(&self) -> Self::ExternSend {
+ self.as_ptr()
+ }
+
+ #[inline]
+ fn opencv_as_extern_mut(&mut self) -> Self::ExternSendMut {
+ self.as_mut_ptr()
+ }
+
+ #[inline]
+ fn opencv_into_extern(self) -> Self::ExternSendMut {
+ unimplemented!("This is intentionally left unimplemented as there seems to be no need for it and it's difficult to implement it without leakage")
+ }
+}
+
+
opencv_type_copy! {
(),
bool,

MODIFY
src_cpp/ocvrs_common.hpp
src_cpp/ocvrs_common.hpp
@@ -32,6 +32,7 @@ CODE_CATCH(OCVRS_TYPE(return_type), std::out_of_range, cv::Error::Code::StsOutOf
// defined in src/templ.rs
extern "C" void* ocvrs_create_string(const char*);
+extern "C" void* ocvrs_create_byte_string(const char*, size_t);
template<typename T> struct Result {
int error_code;

MODIFY
tests/objdetect.rs
tests/objdetect.rs
@@ -26,10 +26,10 @@ fn qr_code() -> Result<()> {
let res = detector.detect_and_decode(&src, &mut pts, &mut straight)?;
assert_eq!(4, pts.len());
if objdetect_missing_quirc {
- assert_eq!(res, "");
+ assert_eq!(res, b"");
assert!(straight.empty());
} else {
- assert_eq!(res, "https://crates.io/crates/opencv");
+ assert_eq!(res, b"https://crates.io/crates/opencv");
assert!(!straight.empty());
}
}
@@ -44,25 +44,51 @@ fn qr_code() -> Result<()> {
let mut straight = Mat::default();
let res = detector.decode(&src, &pts, &mut straight)?;
if objdetect_missing_quirc {
- assert_eq!(res, "");
+ assert_eq!(res, b"");
assert!(straight.empty());
} else {
- assert_eq!(res, "https://crates.io/crates/opencv");
+ assert_eq!(res, b"https://crates.io/crates/opencv");
assert!(!straight.empty());
}
}
+ let binary_qr_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/qr-binary.png");
+
+ {
+ let mut detector = objdetect::QRCodeDetector::default()?;
+ let src = imgcodecs::imread(binary_qr_path.to_str().unwrap(), imgcodecs::IMREAD_COLOR)?;
+ let mut pts = VectorOfPoint::new();
+ let mut straight = Mat::default();
+ let res = detector.detect_and_decode(&src, &mut pts, &mut straight)?;
+ assert_eq!(4, pts.len());
+ if objdetect_missing_quirc {
+ assert_eq!(res, b"");
+ assert!(straight.empty());
+ } else {
+ assert_eq!(res, [0, 1, 2, 3, 4, 5]);
+ assert!(!straight.empty());
+ }
+ }
+
+ Ok(())
+}
+
+/// Return of string binary data into Vec<u8> via output argument
+#[test]
+fn output_byte_string() -> Result<()> {
#[cfg(ocvrs_opencv_branch_34)] {
+ let qr_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/qr.png");
+
let src = imgcodecs::imread(qr_path.to_str().unwrap(), imgcodecs::IMREAD_COLOR)?;
let mut pts = VectorOfPoint::new();
let res = objdetect::detect_qr_code(&src, &mut pts, 0.2, 0.1)?;
assert!(res);
assert_eq!(4, pts.len());
- let mut out = String::new();
+ let mut out = Vec::new();
let mut straight = Mat::default();
let res = objdetect::decode_qr_code(&src, &pts, &mut out, &mut straight)?;
assert!(res);
- assert_eq!(out, "https://crates.io/crates/opencv");
+ assert_eq!(out, b"https://crates.io/crates/opencv");
assert!(!straight.empty());
}
Ok(())

ADD
tests/qr-binary.png
tests/qr-binary.png
Binary files /dev/null and b/tests/qr-binary.png differ

Add support for returning Vec<u8> instead of String when needed