1#![cfg_attr(docsrs, feature(doc_cfg))]
28
29mod config;
30mod error;
31mod foreground;
32mod inference;
33mod mask;
34mod vectorizer;
35
36#[doc(inline)]
37pub use crate::config::{
38 DEFAULT_MODEL_PATH, ENV_MODEL_PATH, InferenceSettings, MaskProcessingOptions,
39};
40#[doc(inline)]
41pub use crate::error::{OutlineError, OutlineResult};
42pub use vectorizer::MaskVectorizer;
43
44#[cfg(feature = "vectorizer-vtracer")]
45#[cfg_attr(docsrs, doc(cfg(feature = "vectorizer-vtracer")))]
46#[doc(inline)]
47pub use vectorizer::vtracer::{TraceOptions, VtracerSvgVectorizer, trace_to_svg_string};
48
49use std::path::Path;
50use std::path::PathBuf;
51use std::sync::Arc;
52
53use image::imageops::FilterType;
54use image::{GrayImage, RgbImage, RgbaImage};
55
56use crate::foreground::compose_foreground;
57use crate::inference::run_matte_pipeline;
58use crate::mask::{MaskOperation, apply_operations, operations_from_options};
59
60#[derive(Debug, Clone)]
66pub struct Outline {
67 settings: InferenceSettings,
69 default_mask_processing: MaskProcessingOptions,
71}
72
73impl Outline {
74 pub fn new(model_path: impl Into<PathBuf>) -> Self {
76 Self {
77 settings: InferenceSettings::new(model_path),
78 default_mask_processing: MaskProcessingOptions::default(),
79 }
80 }
81
82 pub fn from_env_or_default() -> Self {
84 let resolved = std::env::var_os(ENV_MODEL_PATH)
85 .map(PathBuf::from)
86 .unwrap_or_else(|| PathBuf::from(DEFAULT_MODEL_PATH));
87 Self::new(resolved)
88 }
89
90 pub fn try_from_env() -> OutlineResult<Self> {
92 if let Some(from_env) = std::env::var_os(ENV_MODEL_PATH) {
93 return Ok(Self::new(PathBuf::from(from_env)));
94 }
95 Err(OutlineError::Io(std::io::Error::new(
96 std::io::ErrorKind::NotFound,
97 format!(
98 "Model path not specified in env {}; set the variable to proceed",
99 ENV_MODEL_PATH
100 ),
101 )))
102 }
103
104 pub fn with_input_resize_filter(mut self, filter: FilterType) -> Self {
106 self.settings.input_resize_filter = filter;
107 self
108 }
109
110 pub fn with_output_resize_filter(mut self, filter: FilterType) -> Self {
112 self.settings.output_resize_filter = filter;
113 self
114 }
115
116 pub fn with_intra_threads(mut self, intra_threads: Option<usize>) -> Self {
118 self.settings.intra_threads = intra_threads;
119 self
120 }
121
122 pub fn with_default_mask_processing(mut self, options: MaskProcessingOptions) -> Self {
124 self.default_mask_processing = options;
125 self
126 }
127
128 pub fn default_mask_processing(&self) -> &MaskProcessingOptions {
130 &self.default_mask_processing
131 }
132
133 pub fn for_image(&self, image_path: impl AsRef<Path>) -> OutlineResult<InferencedMatte> {
136 let (rgb, matte) = run_matte_pipeline(&self.settings, image_path.as_ref())?;
137 Ok(InferencedMatte::new(
138 rgb,
139 matte,
140 self.default_mask_processing.clone(),
141 ))
142 }
143}
144
145#[derive(Debug, Clone)]
167pub struct InferencedMatte {
168 rgb_image: Arc<RgbImage>,
169 raw_matte: Arc<GrayImage>,
170 default_mask_processing: MaskProcessingOptions,
171}
172
173impl InferencedMatte {
174 fn new(
175 rgb_image: RgbImage,
176 raw_matte: GrayImage,
177 default_mask_processing: MaskProcessingOptions,
178 ) -> Self {
179 Self {
180 rgb_image: Arc::new(rgb_image),
181 raw_matte: Arc::new(raw_matte),
182 default_mask_processing,
183 }
184 }
185
186 pub fn rgb_image(&self) -> &RgbImage {
188 self.rgb_image.as_ref()
189 }
190
191 pub fn raw_matte(&self) -> &GrayImage {
193 self.raw_matte.as_ref()
194 }
195
196 pub fn matte(&self) -> MatteHandle {
198 MatteHandle {
199 rgb_image: Arc::clone(&self.rgb_image),
200 raw_matte: Arc::clone(&self.raw_matte),
201 default_mask_processing: self.default_mask_processing.clone(),
202 operations: Vec::new(),
203 }
204 }
205}
206
207#[derive(Debug, Clone)]
229pub struct MatteHandle {
230 rgb_image: Arc<RgbImage>,
231 raw_matte: Arc<GrayImage>,
232 default_mask_processing: MaskProcessingOptions,
233 operations: Vec<MaskOperation>,
234}
235
236impl MatteHandle {
237 pub fn raw(&self) -> GrayImage {
239 (*self.raw_matte).clone()
240 }
241
242 pub fn into_image(self) -> GrayImage {
244 (*self.raw_matte).clone()
245 }
246
247 pub fn save(&self, path: impl AsRef<Path>) -> OutlineResult<()> {
249 self.raw_matte.as_ref().save(path)?;
250 Ok(())
251 }
252
253 pub fn blur(mut self) -> Self {
255 let sigma = self.default_mask_processing.blur_sigma;
256 self.operations.push(MaskOperation::Blur { sigma });
257 self
258 }
259
260 pub fn blur_with(mut self, sigma: f32) -> Self {
262 self.operations.push(MaskOperation::Blur { sigma });
263 self
264 }
265
266 pub fn threshold(mut self) -> Self {
268 let value = self.default_mask_processing.mask_threshold;
269 self.operations.push(MaskOperation::Threshold { value });
270 self
271 }
272
273 pub fn threshold_with(mut self, value: u8) -> Self {
275 self.operations.push(MaskOperation::Threshold { value });
276 self
277 }
278
279 pub fn dilate(mut self) -> Self {
284 let radius = self.default_mask_processing.dilation_radius;
285 self.operations.push(MaskOperation::Dilate { radius });
286 self
287 }
288
289 pub fn dilate_with(mut self, radius: f32) -> Self {
294 self.operations.push(MaskOperation::Dilate { radius });
295 self
296 }
297
298 pub fn fill_holes(mut self) -> Self {
303 let threshold = self.default_mask_processing.mask_threshold;
304 self.operations.push(MaskOperation::FillHoles { threshold });
305 self
306 }
307
308 pub fn processed(self) -> OutlineResult<MaskHandle> {
310 self.process_with_options(None)
311 }
312
313 pub fn processed_with(self, options: &MaskProcessingOptions) -> OutlineResult<MaskHandle> {
315 self.process_with_options(Some(options))
316 }
317
318 fn process_with_options(
320 mut self,
321 options: Option<&MaskProcessingOptions>,
322 ) -> OutlineResult<MaskHandle> {
323 let mut ops = std::mem::take(&mut self.operations);
324 match options {
325 Some(custom) => ops.extend(operations_from_options(custom)),
326 None if ops.is_empty() => {
327 ops.extend(operations_from_options(&self.default_mask_processing))
328 }
329 None => {}
330 }
331
332 let mask = apply_operations(self.raw_matte.as_ref(), &ops);
333 Ok(MaskHandle::new(
334 Arc::clone(&self.rgb_image),
335 mask,
336 self.default_mask_processing,
337 ))
338 }
339
340 pub fn foreground(&self) -> OutlineResult<ForegroundHandle> {
342 let rgba = compose_foreground(self.rgb_image.as_ref(), self.raw_matte.as_ref())?;
343 Ok(ForegroundHandle { image: rgba })
344 }
345
346 pub fn trace<V>(&self, vectorizer: &V, options: &V::Options) -> OutlineResult<V::Output>
348 where
349 V: MaskVectorizer,
350 {
351 vectorizer.vectorize(self.raw_matte.as_ref(), options)
352 }
353}
354
355#[derive(Debug, Clone)]
378pub struct MaskHandle {
379 rgb_image: Arc<RgbImage>,
380 mask: GrayImage,
381 default_mask_processing: MaskProcessingOptions,
382 operations: Vec<MaskOperation>,
383}
384
385impl MaskHandle {
386 fn new(
387 rgb_image: Arc<RgbImage>,
388 mask: GrayImage,
389 default_mask_processing: MaskProcessingOptions,
390 ) -> Self {
391 Self {
392 rgb_image,
393 mask,
394 default_mask_processing,
395 operations: Vec::new(),
396 }
397 }
398
399 pub fn raw(&self) -> GrayImage {
401 self.mask.clone()
402 }
403
404 pub fn image(&self) -> &GrayImage {
406 &self.mask
407 }
408
409 pub fn into_image(self) -> GrayImage {
411 self.mask
412 }
413
414 pub fn save(&self, path: impl AsRef<Path>) -> OutlineResult<()> {
416 self.mask.save(path)?;
417 Ok(())
418 }
419
420 pub fn blur(mut self) -> Self {
422 let sigma = self.default_mask_processing.blur_sigma;
423 self.operations.push(MaskOperation::Blur { sigma });
424 self
425 }
426
427 pub fn blur_with(mut self, sigma: f32) -> Self {
429 self.operations.push(MaskOperation::Blur { sigma });
430 self
431 }
432
433 pub fn threshold(mut self) -> Self {
435 let value = self.default_mask_processing.mask_threshold;
436 self.operations.push(MaskOperation::Threshold { value });
437 self
438 }
439
440 pub fn threshold_with(mut self, value: u8) -> Self {
442 self.operations.push(MaskOperation::Threshold { value });
443 self
444 }
445
446 pub fn dilate(mut self) -> Self {
451 let radius = self.default_mask_processing.dilation_radius;
452 self.operations.push(MaskOperation::Dilate { radius });
453 self
454 }
455
456 pub fn dilate_with(mut self, radius: f32) -> Self {
461 self.operations.push(MaskOperation::Dilate { radius });
462 self
463 }
464
465 pub fn fill_holes(mut self) -> Self {
470 let threshold = self.default_mask_processing.mask_threshold;
471 self.operations.push(MaskOperation::FillHoles { threshold });
472 self
473 }
474
475 pub fn processed(self) -> OutlineResult<MaskHandle> {
477 self.process_with_options(None)
478 }
479
480 pub fn processed_with(self, options: &MaskProcessingOptions) -> OutlineResult<MaskHandle> {
482 self.process_with_options(Some(options))
483 }
484
485 fn process_with_options(
487 mut self,
488 options: Option<&MaskProcessingOptions>,
489 ) -> OutlineResult<MaskHandle> {
490 let mut ops = std::mem::take(&mut self.operations);
491 match options {
492 Some(custom) => ops.extend(operations_from_options(custom)),
493 None if ops.is_empty() => {
494 ops.extend(operations_from_options(&self.default_mask_processing))
495 }
496 None => {}
497 }
498
499 let mask = apply_operations(&self.mask, &ops);
500 Ok(MaskHandle::new(
501 self.rgb_image,
502 mask,
503 self.default_mask_processing,
504 ))
505 }
506
507 pub fn foreground(&self) -> OutlineResult<ForegroundHandle> {
509 let rgba = compose_foreground(self.rgb_image.as_ref(), &self.mask)?;
510 Ok(ForegroundHandle { image: rgba })
511 }
512
513 pub fn trace<V>(&self, vectorizer: &V, options: &V::Options) -> OutlineResult<V::Output>
515 where
516 V: MaskVectorizer,
517 {
518 vectorizer.vectorize(&self.mask, options)
519 }
520}
521
522pub struct ForegroundHandle {
549 image: RgbaImage,
550}
551
552impl ForegroundHandle {
553 pub fn image(&self) -> &RgbaImage {
555 &self.image
556 }
557
558 pub fn into_image(self) -> RgbaImage {
560 self.image
561 }
562
563 pub fn save(&self, path: impl AsRef<Path>) -> OutlineResult<()> {
565 self.image.save(path)?;
566 Ok(())
567 }
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573 use std::sync::Mutex;
574
575 static ENV_LOCK: Mutex<()> = Mutex::new(());
577
578 mod outline_new {
579 use super::*;
580
581 #[test]
582 fn user_value_is_stored_directly() {
583 let outline = Outline::new("/explicit/model.onnx");
584 assert_eq!(
585 outline.settings.model_path,
586 PathBuf::from("/explicit/model.onnx")
587 );
588 }
589
590 #[test]
591 fn user_value_ignores_env_var() {
592 let _lock = ENV_LOCK.lock().unwrap();
593 unsafe { std::env::set_var(ENV_MODEL_PATH, "env.onnx") };
595 let outline = Outline::new("user.onnx");
596 unsafe { std::env::remove_var(ENV_MODEL_PATH) };
597 assert_eq!(outline.settings.model_path, PathBuf::from("user.onnx"));
598 }
599 }
600
601 mod outline_from_env_or_default {
602 use super::*;
603
604 #[test]
605 fn uses_env_var_when_set() {
606 let _lock = ENV_LOCK.lock().unwrap();
607 unsafe { std::env::set_var(ENV_MODEL_PATH, "/from/env.onnx") };
609 let outline = Outline::from_env_or_default();
610 unsafe { std::env::remove_var(ENV_MODEL_PATH) };
611 assert_eq!(outline.settings.model_path, PathBuf::from("/from/env.onnx"));
612 }
613
614 #[test]
615 fn falls_back_to_default_when_env_unset() {
616 let _lock = ENV_LOCK.lock().unwrap();
617 unsafe { std::env::remove_var(ENV_MODEL_PATH) };
619 let outline = Outline::from_env_or_default();
620 assert_eq!(
621 outline.settings.model_path,
622 PathBuf::from(DEFAULT_MODEL_PATH)
623 );
624 }
625 }
626
627 mod outline_try_from_env {
628 use super::*;
629
630 #[test]
631 fn succeeds_when_env_set() {
632 let _lock = ENV_LOCK.lock().unwrap();
633 unsafe { std::env::set_var(ENV_MODEL_PATH, "/from/env.onnx") };
635 let result = Outline::try_from_env();
636 unsafe { std::env::remove_var(ENV_MODEL_PATH) };
637 let outline = result.expect("should succeed when env is set");
638 assert_eq!(outline.settings.model_path, PathBuf::from("/from/env.onnx"));
639 }
640
641 #[test]
642 fn errors_when_env_unset() {
643 let _lock = ENV_LOCK.lock().unwrap();
644 unsafe { std::env::remove_var(ENV_MODEL_PATH) };
646 let result = Outline::try_from_env();
647 assert!(result.is_err());
648 }
649 }
650}