aviutl2/
common.rs

1pub use anyhow::Result as AnyResult;
2use zerocopy::{Immutable, IntoBytes};
3
4pub use half::{self, f16};
5pub use num_rational::{self, Rational32};
6pub use raw_window_handle::{self, Win32WindowHandle};
7
8/// AviUtl2の情報。
9#[derive(Debug, Clone)]
10pub struct AviUtl2Info {
11    /// AviUtl2のバージョン。
12    pub version: AviUtl2Version,
13}
14
15/// AviUtl2のバージョン。
16///
17/// # Note
18///
19/// バージョン番号の形式は公式に定義されていないため、暫定的に以下のように解釈しています。
20/// ```text
21/// 2001500
22/// || | |
23/// || | +-- ビルドバージョン
24/// || +---- ベータバージョン
25/// |+------ マイナーバージョン
26/// +------- メジャーバージョン
27/// ```
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
29pub struct AviUtl2Version(pub u32);
30impl From<u32> for AviUtl2Version {
31    fn from(value: u32) -> Self {
32        Self(value)
33    }
34}
35impl From<AviUtl2Version> for u32 {
36    fn from(value: AviUtl2Version) -> Self {
37        value.0
38    }
39}
40impl AviUtl2Version {
41    /// 新しいバージョンを作成します。
42    ///
43    /// # Panics
44    ///
45    /// `minor`、`patch`、`build`がそれぞれ100以上の場合にパニックします。
46    pub fn new(major: u8, minor: u8, patch: u8, build: u8) -> Self {
47        assert!(minor < 100, "minor version must be less than 100");
48        assert!(patch < 100, "patch version must be less than 100");
49        assert!(build < 100, "build version must be less than 100");
50        Self(
51            (major as u32) * 1_000_000
52                + (minor as u32) * 10_000
53                + (patch as u32) * 100
54                + (build as u32),
55        )
56    }
57
58    /// メジャーバージョンを取得します。
59    pub fn major(self) -> u32 {
60        self.0 / 1000000
61    }
62
63    /// マイナーバージョンを取得します。
64    pub fn minor(self) -> u32 {
65        (self.0 / 10000) % 100
66    }
67
68    /// ベータバージョンを取得します。
69    pub fn patch(self) -> u32 {
70        (self.0 / 100) % 100
71    }
72
73    /// ビルドバージョンを取得します。
74    pub fn build(self) -> u32 {
75        self.0 % 100
76    }
77}
78#[cfg(feature = "aviutl2-alias")]
79impl aviutl2_alias::FromTableValue for AviUtl2Version {
80    type Err = std::num::ParseIntError;
81
82    fn from_table_value(value: &str) -> Result<Self, Self::Err> {
83        let v: u32 = value.parse()?;
84        Ok(Self::from(v))
85    }
86}
87
88/// ファイル選択ダイアログのフィルタを表す構造体。
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct FileFilter {
91    /// フィルタの名前。
92    pub name: String,
93    /// フィルタが適用される拡張子のリスト。
94    pub extensions: Vec<String>,
95}
96
97/// [`Vec<FileFilter>`]を簡単に作成するためのマクロ。
98///
99/// # Example
100///
101/// ```rust
102/// let filters = aviutl2::file_filters! {
103///     "Image Files" => ["png", "jpg"],
104///     "All Files" => []
105/// };
106/// ```
107#[macro_export]
108macro_rules! file_filters {
109    ($($name:expr => [$($ext:expr),* $(,)?] ),* $(,)?) => {
110        vec![
111            $(
112                $crate::FileFilter {
113                    name: $name.to_string(),
114                    extensions: vec![$($ext.to_string()),*],
115                }
116            ),*
117        ]
118    };
119}
120
121/// YC48のピクセルフォーマットを表す構造体。
122///
123/// # See Also
124/// <https://makiuchi-d.github.io/mksoft/doc/aviutlyc.html>
125#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoBytes, Immutable)]
126#[repr(C)]
127pub struct Yc48 {
128    /// Y成分の値。
129    /// 0から4096までの値を取ります。
130    pub y: i16,
131    /// Cb成分の値。
132    /// -2048から2048までの値を取ります。
133    pub cb: i16,
134    /// Cr成分の値。
135    /// -2048から2048までの値を取ります。
136    pub cr: i16,
137}
138impl Yc48 {
139    /// YUV 4:2:2(YUY2)からYC48に変換します。
140    pub fn from_yuy2(yuy2: (u8, u8, u8, u8)) -> (Self, Self) {
141        let (y1, u, y2, v) = yuy2;
142        let ny1 = ((y1 as u16 * 1197) >> 6) - 299;
143        let ny2 = ((y2 as u16 * 1197) >> 6) - 299;
144        let ncb = ((u as u16 - 128) * 4681 + 164) >> 8;
145        let ncr = ((v as u16 - 128) * 4681 + 164) >> 8;
146        (
147            Self {
148                y: ny1 as i16,
149                cb: ncb as i16,
150                cr: ncr as i16,
151            },
152            Self {
153                y: ((ny1 + ny2) >> 1) as i16,
154                cb: ncb as i16,
155                cr: ncr as i16,
156            },
157        )
158    }
159
160    /// YC48からYUV 4:2:2(YUY2)に変換します。
161    pub fn to_yuy2(self, other: Yc48) -> (u8, u8, u8, u8) {
162        let y1 = ((self.y * 219 + 383) >> 12) + 16;
163        let y2 = ((other.y * 219 + 383) >> 12) + 16;
164        let u = (((self.cb + 2048) * 7 + 66) >> 7) + 16;
165        let v = (((self.cr + 2048) * 7 + 66) >> 7) + 16;
166        let y1 = y1.min(255) as u8;
167        let y2 = y2.min(255) as u8;
168        let u = u.min(255) as u8;
169        let v = v.min(255) as u8;
170        (y1, u, y2, v)
171    }
172
173    /// RGBからYC48に変換します。
174    pub fn from_rgb(self, rgb: (u8, u8, u8)) -> Self {
175        let (r, g, b) = rgb;
176        let r = i16::from(r);
177        let g = i16::from(g);
178        let b = i16::from(b);
179        let y = ((4918 * r + 354) >> 10) + ((9655 * g + 585) >> 10) + ((1875 * b + 523) >> 10);
180        let cb = ((-2775 * r + 240) >> 10) + ((-5449 * g + 515) >> 10) + ((8224 * b + 256) >> 10);
181        let cr = ((8224 * r + 256) >> 10) + ((-6887 * g + 110) >> 10) + ((-1337 * b + 646) >> 10);
182
183        Yc48 { y, cb, cr }
184    }
185
186    /// YC48からRGBに変換します。
187    pub fn to_rgb(self) -> (u8, u8, u8) {
188        let y = self.y as i32;
189        let cr = self.cr as i32;
190        let cb = self.cr as i32;
191        let r = (255 * y + ((((22881 * cr) >> 16) + 3) << 10)) >> 12;
192        let g = (255 * y + ((((-5616 * cb) >> 16) + ((-11655 * cr) >> 16) + 3) << 10)) >> 12;
193        let b = (255 * y + ((((28919 * cb) >> 16) + 3) << 10)) >> 12;
194
195        let r = r.min(255) as u8;
196        let g = g.min(255) as u8;
197        let b = b.min(255) as u8;
198        (r, g, b)
199    }
200}
201
202pub(crate) fn format_file_filters(file_filters: &[FileFilter]) -> String {
203    let mut file_filter = String::new();
204    for filter in file_filters {
205        let display = format!(
206            "{} ({})",
207            filter.name,
208            if filter.extensions.is_empty() {
209                "*".to_string()
210            } else {
211                filter
212                    .extensions
213                    .iter()
214                    .map(|ext| format!(".{ext}"))
215                    .collect::<Vec<_>>()
216                    .join(", ")
217            }
218        );
219        file_filter.push_str(&display);
220        file_filter.push('\x00');
221        if filter.extensions.is_empty() {
222            file_filter.push('*');
223        } else {
224            file_filter.push_str(
225                &filter
226                    .extensions
227                    .iter()
228                    .map(|ext| format!("*.{ext}"))
229                    .collect::<Vec<_>>()
230                    .join(";"),
231            );
232        }
233        file_filter.push('\x00');
234    }
235
236    file_filter
237}
238
239pub(crate) enum LeakType {
240    WideString,
241    ValueVector { len: usize, name: String },
242    Null,
243    Other(String),
244}
245
246pub(crate) struct LeakManager {
247    ptrs: std::sync::Mutex<Vec<(LeakType, usize)>>,
248}
249
250pub(crate) trait IntoLeakedPtr {
251    fn into_leaked_ptr(self) -> (LeakType, usize);
252}
253pub(crate) trait LeakableValue {}
254
255impl LeakManager {
256    pub fn new() -> Self {
257        Self {
258            ptrs: std::sync::Mutex::new(Vec::new()),
259        }
260    }
261
262    pub fn leak<T: IntoLeakedPtr>(&self, value: T) -> *const T {
263        log::debug!("Leaking memory for type {}", std::any::type_name::<T>());
264        let mut ptrs = self.ptrs.lock().unwrap();
265        let leaked = value.into_leaked_ptr();
266        let ptr = leaked.1;
267        ptrs.push(leaked);
268        ptr as *const T
269    }
270
271    pub fn leak_as_wide_string(&self, s: &str) -> *const u16 {
272        log::debug!("Leaking wide string: {}", s);
273        let mut wide: Vec<u16> = s.encode_utf16().collect();
274        wide.push(0); // Null-terminate the string
275        let boxed = wide.into_boxed_slice();
276        let ptr = Box::into_raw(boxed) as *mut u16 as usize;
277        let mut ptrs = self.ptrs.lock().unwrap();
278        ptrs.push((LeakType::WideString, ptr));
279        ptr as *const u16
280    }
281
282    // pub fn leak_ptr_vec<T: IntoLeakedPtr>(&self, vec: Vec<T>) -> *const *const T {
283    //     log::debug!("Leaking vector of type {}", std::any::type_name::<T>());
284    //     let mut raw_ptrs = Vec::with_capacity(vec.len() + 1);
285    //     for item in vec {
286    //         let leaked = item.into_leaked_ptr();
287    //         let ptr = leaked.1;
288    //         raw_ptrs.push(ptr);
289    //         let mut ptrs = self.ptrs.lock().unwrap();
290    //         ptrs.push(leaked);
291    //     }
292    //     self.leak_value_vec(raw_ptrs) as _
293    // }
294
295    pub fn leak_value_vec<T: LeakableValue>(&self, vec: Vec<T>) -> *const T {
296        log::debug!(
297            "Leaking value vector of type {}",
298            std::any::type_name::<T>()
299        );
300        let len = vec.len();
301        let boxed = vec.into_boxed_slice();
302        let ptr = Box::into_raw(boxed) as *mut T as usize;
303        let mut ptrs = self.ptrs.lock().unwrap();
304        ptrs.push((
305            LeakType::ValueVector {
306                len,
307                name: std::any::type_name::<T>().to_string(),
308            },
309            ptr,
310        ));
311        ptr as *const T
312    }
313
314    pub fn free_leaked_memory(&self) {
315        let mut ptrs = self.ptrs.lock().unwrap();
316        while let Some((ptr_type, ptr)) = ptrs.pop() {
317            match ptr_type {
318                LeakType::WideString => unsafe {
319                    let _ = Box::from_raw(ptr as *mut u16);
320                },
321                LeakType::ValueVector { len, name } => {
322                    Self::free_leaked_memory_leakable_value(&name, ptr, len);
323                }
324                LeakType::Null => {
325                    assert!(ptr == 0);
326                }
327                LeakType::Other(ref type_name) => {
328                    Self::free_leaked_memory_other_ptr(type_name, ptr);
329                }
330            }
331        }
332    }
333}
334macro_rules! impl_leak_ptr {
335    ($($t:ty),* $(,)?) => {
336        $(
337            impl IntoLeakedPtr for $t {
338                fn into_leaked_ptr(self) -> (LeakType, usize) {
339                    let boxed = Box::new(self);
340                    let ptr = Box::into_raw(boxed) as usize;
341                    (LeakType::Other(std::any::type_name::<$t>().to_string()), ptr)
342                }
343            }
344        )*
345
346        impl LeakManager {
347            fn free_leaked_memory_other_ptr(ptr_type: &str, ptr: usize) {
348                unsafe {
349                    match ptr_type {
350                        $(
351                            t if t == std::any::type_name::<$t>() => {
352                                let _ = Box::from_raw(ptr as *mut $t);
353                            },
354                        )*
355                        _ => {
356                            unreachable!("Unknown leaked pointer type: {}", ptr_type);
357                        }
358                    }
359                }
360            }
361        }
362    };
363}
364macro_rules! impl_leakable_value {
365    ($($t:ty),* $(,)?) => {
366        $(
367            impl LeakableValue for $t {}
368        )*
369        impl LeakManager {
370            fn free_leaked_memory_leakable_value(type_name: &str, ptr: usize, len: usize) {
371                unsafe {
372                    match type_name {
373                        $(
374                            t if t == std::any::type_name::<$t>() => {
375                                let _ = Box::from_raw(std::slice::from_raw_parts_mut(ptr as *mut $t, len));
376                            },
377                        )*
378                        _ => {
379                            unreachable!("Unknown leaked value vector type: {}", type_name);
380                        }
381                    }
382                }
383            }
384        }
385    };
386}
387impl_leak_ptr!(
388    aviutl2_sys::input2::WAVEFORMATEX,
389    aviutl2_sys::output2::BITMAPINFOHEADER,
390    aviutl2_sys::filter2::FILTER_ITEM,
391);
392impl_leakable_value!(
393    aviutl2_sys::filter2::FILTER_ITEM_SELECT_ITEM,
394    aviutl2_sys::module2::SCRIPT_MODULE_FUNCTION,
395    usize
396);
397
398impl<T: IntoLeakedPtr> IntoLeakedPtr for Option<T> {
399    fn into_leaked_ptr(self) -> (LeakType, usize) {
400        match self {
401            Some(value) => value.into_leaked_ptr(),
402            None => (LeakType::Null, 0),
403        }
404    }
405}
406
407impl Drop for LeakManager {
408    fn drop(&mut self) {
409        self.free_leaked_memory();
410    }
411}
412
413/// # Safety
414///
415/// - `ptr` は有効なLPCWSTRであること。
416/// - `ptr` はNull Terminatedなu16文字列を指していること。
417pub(crate) unsafe fn load_wide_string(ptr: *const u16) -> String {
418    if ptr.is_null() {
419        return String::new();
420    }
421
422    let mut len = 0;
423    while unsafe { *ptr.add(len) } != 0 {
424        len += 1;
425    }
426
427    unsafe { String::from_utf16_lossy(std::slice::from_raw_parts(ptr, len)) }
428}
429
430pub(crate) fn alert_error(error: &anyhow::Error) {
431    let _ = native_dialog::DialogBuilder::message()
432        .set_title("エラー")
433        .set_level(native_dialog::MessageLevel::Error)
434        .set_text(format!("エラーが発生しました: {error}"))
435        .alert()
436        .show();
437}
438
439/// ワイド文字列(LPCWSTR)を所有するRust文字列として扱うためのラッパー。
440#[derive(Debug, Clone, PartialEq, Eq)]
441pub(crate) struct CWString(Vec<u16>);
442
443/// ヌルバイトを含む文字列を作成しようとした際のエラー。
444#[derive(thiserror::Error, Debug)]
445#[error("String contains null byte")]
446pub struct NullByteError {
447    position: usize,
448    u16_seq: Vec<u16>,
449}
450impl NullByteError {
451    pub fn nul_position(&self) -> usize {
452        self.position
453    }
454
455    pub fn into_vec(self) -> Vec<u16> {
456        self.u16_seq
457    }
458}
459
460impl CWString {
461    /// 新しいCWStringを作成します。
462    ///
463    /// # Errors
464    ///
465    /// `string`にヌルバイトが含まれている場合、`NullByteError`を返します。
466    pub fn new(string: &str) -> Result<Self, NullByteError> {
467        let mut wide: Vec<u16> = string.encode_utf16().collect();
468        if let Some((pos, _)) = wide.iter().enumerate().find(|&(_, &c)| c == 0) {
469            return Err(NullByteError {
470                position: pos,
471                u16_seq: wide,
472            });
473        }
474        wide.push(0); // Null-terminate the string
475        Ok(Self(wide))
476    }
477
478    /// 内部のポインタを取得します。
479    ///
480    /// # Warning
481    ///
482    /// <div class="warning">
483    ///
484    /// この`CWString`がDropされた後にこのポインタを使用すると未定義動作を引き起こします。
485    ///
486    /// </div>
487    pub fn as_ptr(&self) -> *const u16 {
488        self.0.as_ptr()
489    }
490}
491impl std::fmt::Display for CWString {
492    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
493        let s = String::from_utf16_lossy(&self.0[..self.0.len() - 1]);
494        write!(f, "{}", s)
495    }
496}
497
498#[cfg(test)]
499mod tests {
500    use super::*;
501
502    #[test]
503    fn test_format_file_filters() {
504        let filters = vec![
505            FileFilter {
506                name: "Image Files".to_string(),
507                extensions: vec!["png".to_string(), "jpg".to_string()],
508            },
509            FileFilter {
510                name: "All Files".to_string(),
511                extensions: vec![],
512            },
513        ];
514        let formatted = format_file_filters(&filters);
515        let expected = "Image Files (.png, .jpg)\x00*.png;*.jpg\x00All Files (*)\x00*\x00";
516        assert_eq!(formatted, expected);
517    }
518
519    #[test]
520    fn test_file_filters_macro() {
521        let filters = file_filters! {
522            "Image Files" => ["png", "jpg"],
523            "All Files" => []
524        };
525        assert_eq!(filters.len(), 2);
526        assert_eq!(filters[0].name, "Image Files");
527        assert_eq!(
528            filters[0].extensions,
529            vec!["png".to_string(), "jpg".to_string()]
530        );
531        assert_eq!(filters[1].name, "All Files");
532        assert_eq!(filters[1].extensions, Vec::<String>::new());
533    }
534
535    #[test]
536    fn test_aviutl2_version() {
537        let version = AviUtl2Version::new(2, 0, 15, 0);
538        assert_eq!(version.0, 2001500);
539        assert_eq!(version.major(), 2);
540        assert_eq!(version.minor(), 0);
541        assert_eq!(version.patch(), 15);
542        assert_eq!(version.build(), 0);
543
544        let version_from_u32: AviUtl2Version = 2001500u32.into();
545        assert_eq!(version_from_u32, version);
546
547        let u32_from_version: u32 = version.into();
548        assert_eq!(u32_from_version, 2001500);
549    }
550
551    #[test]
552    fn test_cwstring_new() {
553        let s = "Hello, world!";
554        let cwstring = CWString::new(s).unwrap();
555        assert_eq!(unsafe { load_wide_string(cwstring.as_ptr()) }, s);
556
557        let s_with_nul = "Hello\0world!";
558        let err = CWString::new(s_with_nul).unwrap_err();
559        assert_eq!(err.nul_position(), 5);
560    }
561}