mirror of
https://github.com/visioncortex/vtracer.git
synced 2026-09-16 17:16:22 -07:00
Enrich binary thresholding: tunable fixed + Bradley–Roth adaptive
BinaryFrontend gains a Threshold enum: Fixed(u8) (now tunable, was
hardcoded to 128) and Adaptive { window, t } — Bradley–Roth adaptive
thresholding computed via visioncortex's SummedAreaTable, O(pixels)
regardless of window size, for images with uneven lighting. Both use a
shared (r+g+b)/3 intensity so they agree on "dark"; the grayscale
checker_bw golden is unaffected.
Exposed through Config and all bindings: CLI (--threshold, --adaptive,
--adaptive-window, --adaptive-t), Python (constructor kwargs + getters/
setters), and the Node package (binaryThreshold, adaptive, adaptiveWindow,
adaptiveT). Adds tests covering fixed tunability and adaptive recovering
locally-dark marks under a brightness gradient that a global cutoff can't.
This commit is contained in:
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
* Progress reporting and cancellation: `Pipeline::run_with_progress` with a `CancelToken` and a per-phase progress callback (for driving desktop UIs from a worker thread).
|
||||
* Binary thresholding methods: a tunable fixed threshold and Bradley–Roth adaptive thresholding (via visioncortex's summed-area table) for images with uneven lighting. Exposed on `Config` (`binary_threshold`, `binary_adaptive`, `binary_adaptive_window`, `binary_adaptive_t`), the CLI (`--threshold`, `--adaptive`, `--adaptive-window`, `--adaptive-t`), Python, and the Node package (`binaryThreshold`, `adaptive`, `adaptiveWindow`, `adaptiveT`).
|
||||
|
||||
## 1.0.0-alpha.1 - 2026-07-24
|
||||
|
||||
Ground-up rewrite of VTracer into a **vectorization framework** with pluggable stages.
|
||||
|
||||
@@ -90,6 +90,22 @@ struct Args {
|
||||
/// Optimization level: 0 = off, 1 = quantize+simplify, 2 = + shorthands/grouping.
|
||||
#[arg(long, value_parser = clap::value_parser!(u8).range(0..=2))]
|
||||
optimize: Option<u8>,
|
||||
|
||||
/// Binary mode: fixed threshold (0..=255); foreground when intensity is below it.
|
||||
#[arg(long, value_parser = clap::value_parser!(u8))]
|
||||
threshold: Option<u8>,
|
||||
|
||||
/// Binary mode: use Bradley–Roth adaptive thresholding (handles uneven lighting).
|
||||
#[arg(long)]
|
||||
adaptive: bool,
|
||||
|
||||
/// Adaptive window side length in px (0 = auto). Implies --adaptive.
|
||||
#[arg(long)]
|
||||
adaptive_window: Option<u32>,
|
||||
|
||||
/// Adaptive sensitivity: percent below the local mean (default 15). Implies --adaptive.
|
||||
#[arg(long)]
|
||||
adaptive_t: Option<f64>,
|
||||
}
|
||||
|
||||
fn parse_segment_length(s: &str) -> Result<f64, String> {
|
||||
@@ -169,6 +185,21 @@ fn build_config(args: &Args) -> Result<Config, String> {
|
||||
config.max_colors = Some(v);
|
||||
}
|
||||
|
||||
// Binary thresholding: --adaptive (or either adaptive tuning flag) selects
|
||||
// Bradley–Roth; otherwise --threshold tunes the fixed cutoff.
|
||||
if let Some(v) = args.threshold {
|
||||
config.binary_threshold = v;
|
||||
}
|
||||
if args.adaptive || args.adaptive_window.is_some() || args.adaptive_t.is_some() {
|
||||
config.binary_adaptive = true;
|
||||
}
|
||||
if let Some(v) = args.adaptive_window {
|
||||
config.binary_adaptive_window = v;
|
||||
}
|
||||
if let Some(v) = args.adaptive_t {
|
||||
config.binary_adaptive_t = v;
|
||||
}
|
||||
|
||||
// Palette: inline flag wins over file; both parse to a color list.
|
||||
if let Some(text) = &args.palette {
|
||||
config.palette = parse_palette(text)?;
|
||||
|
||||
@@ -141,6 +141,10 @@ impl PyConfig {
|
||||
palette = None,
|
||||
max_colors = None,
|
||||
optimize = 1,
|
||||
binary_threshold = 128,
|
||||
adaptive = false,
|
||||
adaptive_window = 0,
|
||||
adaptive_t = 15.0,
|
||||
))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
@@ -158,6 +162,10 @@ impl PyConfig {
|
||||
palette: Option<Vec<String>>,
|
||||
max_colors: Option<usize>,
|
||||
optimize: u8,
|
||||
binary_threshold: u8,
|
||||
adaptive: bool,
|
||||
adaptive_window: u32,
|
||||
adaptive_t: f64,
|
||||
) -> PyResult<Self> {
|
||||
let palette = match palette {
|
||||
Some(list) => list.iter().map(|s| parse_hex(s)).collect::<PyResult<_>>()?,
|
||||
@@ -179,6 +187,10 @@ impl PyConfig {
|
||||
palette,
|
||||
max_colors,
|
||||
optimize,
|
||||
binary_threshold,
|
||||
binary_adaptive: adaptive,
|
||||
binary_adaptive_window: adaptive_window,
|
||||
binary_adaptive_t: adaptive_t,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -333,6 +345,42 @@ impl PyConfig {
|
||||
self.inner.optimize = v;
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn binary_threshold(&self) -> u8 {
|
||||
self.inner.binary_threshold
|
||||
}
|
||||
#[setter]
|
||||
fn set_binary_threshold(&mut self, v: u8) {
|
||||
self.inner.binary_threshold = v;
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn adaptive(&self) -> bool {
|
||||
self.inner.binary_adaptive
|
||||
}
|
||||
#[setter]
|
||||
fn set_adaptive(&mut self, v: bool) {
|
||||
self.inner.binary_adaptive = v;
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn adaptive_window(&self) -> u32 {
|
||||
self.inner.binary_adaptive_window
|
||||
}
|
||||
#[setter]
|
||||
fn set_adaptive_window(&mut self, v: u32) {
|
||||
self.inner.binary_adaptive_window = v;
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn adaptive_t(&self) -> f64 {
|
||||
self.inner.binary_adaptive_t
|
||||
}
|
||||
#[setter]
|
||||
fn set_adaptive_t(&mut self, v: f64) {
|
||||
self.inner.binary_adaptive_t = v;
|
||||
}
|
||||
|
||||
// --- conversion ---
|
||||
|
||||
/// Trace the image at `input_path` and write the SVG to `output_path`.
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::colorfit::{AutoQuantize, ColorFitter, FixedPalette, Identity, MergeAd
|
||||
use crate::compose::Compositing;
|
||||
use crate::error::Error;
|
||||
use crate::fitter::{CurveFitter, FitParams, PixelFitter, PolygonFitter, SplineFitter};
|
||||
use crate::frontend::{BinaryFrontend, ColorClusterFrontend, Frontend};
|
||||
use crate::frontend::{BinaryFrontend, ColorClusterFrontend, Frontend, Threshold};
|
||||
use crate::mosaic::{
|
||||
PixelSegmentFitter, PolygonSegmentFitter, SegmentFitter, SplineSegmentFitter,
|
||||
};
|
||||
@@ -71,6 +71,16 @@ pub struct Config {
|
||||
pub max_colors: Option<usize>,
|
||||
/// Optimization level: 0 = off, 1 = quantize+simplify, 2 = + shorthands/grouping.
|
||||
pub optimize: u8,
|
||||
/// Binary-mode fixed threshold (0..=255): foreground when grayscale
|
||||
/// intensity is below this. Ignored when `binary_adaptive` is set.
|
||||
pub binary_threshold: u8,
|
||||
/// Binary mode: use Bradley–Roth adaptive thresholding instead of the fixed
|
||||
/// cutoff (better for uneven lighting).
|
||||
pub binary_adaptive: bool,
|
||||
/// Adaptive window side length in pixels; 0 = auto (~1/8 of the shorter side).
|
||||
pub binary_adaptive_window: u32,
|
||||
/// Adaptive sensitivity `t`: percent below the local mean (default 15).
|
||||
pub binary_adaptive_t: f64,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -90,6 +100,10 @@ impl Default for Config {
|
||||
palette: Vec::new(),
|
||||
max_colors: None,
|
||||
optimize: 1,
|
||||
binary_threshold: 128,
|
||||
binary_adaptive: false,
|
||||
binary_adaptive_window: 0,
|
||||
binary_adaptive_t: 15.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,11 +148,21 @@ impl Config {
|
||||
color_precision_loss: 8 - self.color_precision,
|
||||
layer_difference: self.layer_difference,
|
||||
}),
|
||||
ColorMode::Binary => Box::new(BinaryFrontend {
|
||||
filter_speckle_area,
|
||||
threshold: 128,
|
||||
diagonal: false,
|
||||
}),
|
||||
ColorMode::Binary => {
|
||||
let threshold = if self.binary_adaptive {
|
||||
Threshold::Adaptive {
|
||||
window: self.binary_adaptive_window,
|
||||
t: self.binary_adaptive_t,
|
||||
}
|
||||
} else {
|
||||
Threshold::Fixed(self.binary_threshold)
|
||||
};
|
||||
Box::new(BinaryFrontend {
|
||||
filter_speckle_area,
|
||||
threshold,
|
||||
diagonal: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,63 @@
|
||||
use visioncortex::{Color, ColorImage, PointI32};
|
||||
use visioncortex::{BinaryImage, Color, ColorImage, PointI32, SummedAreaTable};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::ir::{Layer, Paint, RegionMask, Segmentation};
|
||||
|
||||
use super::Frontend;
|
||||
|
||||
/// Grayscale intensity (0..=255) used by every thresholding method. Matches the
|
||||
/// metric `SummedAreaTable::from_color_image` sums, so fixed and adaptive
|
||||
/// thresholds agree on what "dark" means.
|
||||
#[inline]
|
||||
fn intensity(c: Color) -> u32 {
|
||||
(c.r as u32 + c.g as u32 + c.b as u32) / 3
|
||||
}
|
||||
|
||||
/// How the binary frontend separates foreground (dark) from background pixels.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Threshold {
|
||||
/// Global cutoff: a pixel is foreground when its intensity is below this
|
||||
/// value (0..=255). Fast and predictable; best for clean, evenly-lit input.
|
||||
Fixed(u8),
|
||||
/// Bradley–Roth adaptive threshold: a pixel is foreground when its
|
||||
/// intensity is more than `t` percent below the mean of the surrounding
|
||||
/// `window`×`window` block. Handles uneven lighting and shadows that defeat
|
||||
/// a single global cutoff. Computed in one pass with a summed-area table,
|
||||
/// so it stays O(pixels) regardless of window size.
|
||||
Adaptive {
|
||||
/// Window side length in pixels; `0` auto-derives ~1/8 of the shorter
|
||||
/// image dimension (the value suggested by the paper).
|
||||
window: u32,
|
||||
/// Sensitivity, as a percentage below the local mean (paper default 15).
|
||||
t: f64,
|
||||
},
|
||||
}
|
||||
|
||||
impl Threshold {
|
||||
/// Bradley–Roth adaptive thresholding with the paper's defaults
|
||||
/// (auto window, `t = 15`).
|
||||
pub const fn adaptive() -> Self {
|
||||
Threshold::Adaptive {
|
||||
window: 0,
|
||||
t: 15.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Threshold {
|
||||
fn default() -> Self {
|
||||
Threshold::Fixed(128)
|
||||
}
|
||||
}
|
||||
|
||||
/// Binary (black/white) frontend: threshold the image then cluster the
|
||||
/// foreground. Every region is painted black.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BinaryFrontend {
|
||||
/// Discard clusters smaller than this many pixels.
|
||||
pub filter_speckle_area: usize,
|
||||
/// A pixel is foreground when its red channel is below this threshold.
|
||||
pub threshold: u8,
|
||||
/// How foreground pixels are selected.
|
||||
pub threshold: Threshold,
|
||||
/// Whether to connect clusters diagonally.
|
||||
pub diagonal: bool,
|
||||
}
|
||||
@@ -21,12 +66,62 @@ impl Default for BinaryFrontend {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
filter_speckle_area: 16,
|
||||
threshold: 128,
|
||||
threshold: Threshold::default(),
|
||||
diagonal: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryFrontend {
|
||||
/// Binarize `img` into a foreground mask according to [`Self::threshold`].
|
||||
fn binarize(&self, img: &ColorImage) -> BinaryImage {
|
||||
match self.threshold {
|
||||
Threshold::Fixed(value) => {
|
||||
let value = value as u32;
|
||||
img.to_binary_image(|c| intensity(c) < value)
|
||||
}
|
||||
Threshold::Adaptive { window, t } => adaptive_bradley_roth(img, window, t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bradley–Roth adaptive thresholding via a summed-area table.
|
||||
///
|
||||
/// For each pixel, compare its intensity to the mean of a surrounding window:
|
||||
/// it is foreground when `value <= mean * (1 - t/100)`, i.e. more than `t`
|
||||
/// percent darker than its neighborhood.
|
||||
fn adaptive_bradley_roth(img: &ColorImage, window: u32, t: f64) -> BinaryImage {
|
||||
let (w, h) = (img.width, img.height);
|
||||
let sat = SummedAreaTable::from_color_image(img);
|
||||
|
||||
// Window: 0 => auto (~1/8 of the shorter side, per the paper), min 1.
|
||||
let side = if window == 0 {
|
||||
(w.min(h) / 8).max(1)
|
||||
} else {
|
||||
window as usize
|
||||
};
|
||||
let half = side / 2;
|
||||
let factor = 1.0 - t.clamp(0.0, 100.0) / 100.0;
|
||||
|
||||
let mut out = BinaryImage::new_w_h(w, h);
|
||||
for y in 0..h {
|
||||
let y0 = y.saturating_sub(half);
|
||||
let y1 = (y + half).min(h - 1);
|
||||
for x in 0..w {
|
||||
let x0 = x.saturating_sub(half);
|
||||
let x1 = (x + half).min(w - 1);
|
||||
|
||||
let count = ((x1 - x0 + 1) * (y1 - y0 + 1)) as f64;
|
||||
let sum = sat.get_region_sum_x_y_w_h(x0, y0, x1 - x0 + 1, y1 - y0 + 1) as f64;
|
||||
let value = intensity(img.get_pixel(x, y)) as f64;
|
||||
|
||||
// value <= mean * factor ⇔ value * count <= sum * factor
|
||||
out.set_pixel(x, y, value * count <= sum * factor);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
impl Frontend for BinaryFrontend {
|
||||
fn segment(&self, img: &ColorImage) -> Result<Segmentation, Error> {
|
||||
if img.width == 0 || img.height == 0 {
|
||||
@@ -35,8 +130,7 @@ impl Frontend for BinaryFrontend {
|
||||
|
||||
let width = img.width;
|
||||
let height = img.height;
|
||||
let threshold = self.threshold;
|
||||
let binary = img.to_binary_image(|c| c.r < threshold);
|
||||
let binary = self.binarize(img);
|
||||
let clusters = binary.to_clusters(self.diagonal);
|
||||
|
||||
let mut seg = Segmentation::new(width as u32, height as u32);
|
||||
|
||||
@@ -12,7 +12,7 @@ mod binary;
|
||||
mod color_cluster;
|
||||
mod keying;
|
||||
|
||||
pub use binary::BinaryFrontend;
|
||||
pub use binary::{BinaryFrontend, Threshold};
|
||||
pub use color_cluster::ColorClusterFrontend;
|
||||
|
||||
use visioncortex::ColorImage;
|
||||
|
||||
@@ -44,6 +44,7 @@ pub mod svg;
|
||||
|
||||
pub use config::{ColorMode, Config, FitMode, Hierarchical, Preset};
|
||||
pub use error::Error;
|
||||
pub use frontend::Threshold;
|
||||
pub use pipeline::Pipeline;
|
||||
pub use progress::{CancelToken, Phase, Progress};
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
//! Binary thresholding: tunable fixed cutoff and Bradley–Roth adaptive.
|
||||
|
||||
use vtracer::frontend::{BinaryFrontend, Frontend};
|
||||
use vtracer::{ColorImage, Threshold};
|
||||
|
||||
fn gray(w: usize, h: usize, f: impl Fn(usize, usize) -> u8) -> ColorImage {
|
||||
let mut pixels = Vec::with_capacity(w * h * 4);
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let v = f(x, y);
|
||||
pixels.extend_from_slice(&[v, v, v, 255]);
|
||||
}
|
||||
}
|
||||
ColorImage {
|
||||
pixels,
|
||||
width: w,
|
||||
height: h,
|
||||
}
|
||||
}
|
||||
|
||||
/// Total foreground pixels selected by a frontend over an image.
|
||||
fn foreground_area(front: &BinaryFrontend, img: &ColorImage) -> usize {
|
||||
front
|
||||
.segment(img)
|
||||
.unwrap()
|
||||
.layers
|
||||
.iter()
|
||||
.map(|l| l.mask.area())
|
||||
.sum()
|
||||
}
|
||||
|
||||
/// A uniform gray field: a lower fixed threshold selects strictly fewer pixels.
|
||||
#[test]
|
||||
fn fixed_threshold_is_tunable() {
|
||||
// Left third value 80, middle 130, right 180.
|
||||
let img = gray(60, 20, |x, _| match x / 20 {
|
||||
0 => 80,
|
||||
1 => 130,
|
||||
_ => 180,
|
||||
});
|
||||
|
||||
let front = |v: u8| BinaryFrontend {
|
||||
filter_speckle_area: 1,
|
||||
threshold: Threshold::Fixed(v),
|
||||
diagonal: false,
|
||||
};
|
||||
|
||||
let low = foreground_area(&front(100), &img); // catches only the 80 band
|
||||
let mid = foreground_area(&front(150), &img); // 80 + 130 bands
|
||||
let high = foreground_area(&front(200), &img); // everything
|
||||
|
||||
assert!(
|
||||
low < mid && mid < high,
|
||||
"higher threshold must select more foreground: {low} < {mid} < {high}"
|
||||
);
|
||||
assert_eq!(high, 60 * 20, "threshold above all values selects everything");
|
||||
}
|
||||
|
||||
/// Adaptive thresholding recovers locally-dark marks under a brightness
|
||||
/// gradient that no single global cutoff can separate.
|
||||
#[test]
|
||||
fn adaptive_beats_fixed_under_uneven_lighting() {
|
||||
let (w, h) = (80, 40);
|
||||
|
||||
// Background ramps left(70) → right(210). Two 6x6 marks, each 40 darker
|
||||
// than their local background: one on the dark side, one on the bright side.
|
||||
let bg = |x: usize| 70 + (x * 140 / (w - 1)) as u8;
|
||||
let marks = [(16usize, 17usize), (60, 17)];
|
||||
let is_mark = |x: usize, y: usize| {
|
||||
marks
|
||||
.iter()
|
||||
.any(|&(mx, my)| x >= mx && x < mx + 6 && y >= my && y < my + 6)
|
||||
};
|
||||
let img = gray(w, h, |x, y| {
|
||||
if is_mark(x, y) {
|
||||
bg(x).saturating_sub(40)
|
||||
} else {
|
||||
bg(x)
|
||||
}
|
||||
});
|
||||
|
||||
let base = BinaryFrontend {
|
||||
filter_speckle_area: 4,
|
||||
threshold: Threshold::Fixed(128),
|
||||
diagonal: false,
|
||||
};
|
||||
|
||||
// A global cutoff can't isolate both marks: 128 catches the dark-side mark
|
||||
// but floods the whole dark half of the ramp, and misses the bright-side
|
||||
// mark (~136) entirely — so fixed has no region on the bright half.
|
||||
let fixed_seg = base.segment(&img).unwrap();
|
||||
let fixed_area: usize = fixed_seg.layers.iter().map(|l| l.mask.area()).sum();
|
||||
let mid = (w as i32) / 2;
|
||||
let fixed_right = fixed_seg.layers.iter().any(|l| l.mask.offset.x >= mid);
|
||||
|
||||
// Adaptive: window comfortably larger than the 6px marks so they fill.
|
||||
let adaptive = BinaryFrontend {
|
||||
threshold: Threshold::Adaptive {
|
||||
window: 21,
|
||||
t: 15.0,
|
||||
},
|
||||
..base.clone()
|
||||
};
|
||||
let adaptive_seg = adaptive.segment(&img).unwrap();
|
||||
let adaptive_area: usize = adaptive_seg.layers.iter().map(|l| l.mask.area()).sum();
|
||||
let adaptive_left = adaptive_seg.layers.iter().any(|l| l.mask.offset.x < mid);
|
||||
let adaptive_right = adaptive_seg.layers.iter().any(|l| l.mask.offset.x >= mid);
|
||||
|
||||
// The point of adaptive: it finds locally-dark marks on *both* sides of the
|
||||
// ramp, where the global threshold catches only the dark half.
|
||||
assert!(!fixed_right, "fixed(128) should miss the bright-side mark");
|
||||
assert!(
|
||||
adaptive_left && adaptive_right,
|
||||
"adaptive should detect marks on both the dark and bright sides"
|
||||
);
|
||||
assert!(adaptive_area > 0, "adaptive must select some foreground");
|
||||
assert!(
|
||||
adaptive_area * 3 < fixed_area,
|
||||
"adaptive should select far less than fixed's flooded half: \
|
||||
adaptive={adaptive_area}, fixed={fixed_area}"
|
||||
);
|
||||
}
|
||||
Vendored
+8
@@ -19,6 +19,14 @@ export interface Options {
|
||||
maxColors?: number;
|
||||
/** 0 = off, 1 = quantize+simplify, 2 = + shorthands/grouping. */
|
||||
optimize?: number;
|
||||
/** Binary mode (`colorMode: 'bw'`): fixed threshold 0..=255; foreground when intensity is below it. */
|
||||
binaryThreshold?: number;
|
||||
/** Binary mode: use Bradley–Roth adaptive thresholding (handles uneven lighting). */
|
||||
adaptive?: boolean;
|
||||
/** Adaptive window side length in px; 0 = auto (~1/8 of the shorter side). */
|
||||
adaptiveWindow?: number;
|
||||
/** Adaptive sensitivity: percent below the local mean (default 15). */
|
||||
adaptiveT?: number;
|
||||
}
|
||||
|
||||
/** Vectorize an encoded image (PNG/JPEG/GIF/BMP) buffer to an SVG string. */
|
||||
|
||||
@@ -29,6 +29,14 @@ struct Options {
|
||||
palette: Option<Vec<String>>,
|
||||
max_colors: Option<usize>,
|
||||
optimize: Option<u8>,
|
||||
/// Binary-mode fixed threshold (0..=255).
|
||||
binary_threshold: Option<u8>,
|
||||
/// Binary mode: use Bradley–Roth adaptive thresholding.
|
||||
adaptive: Option<bool>,
|
||||
/// Adaptive window side length in px (0 = auto).
|
||||
adaptive_window: Option<u32>,
|
||||
/// Adaptive sensitivity: percent below the local mean (default 15).
|
||||
adaptive_t: Option<f64>,
|
||||
/// One of "bw" | "poster" | "photo"; applied before the other fields.
|
||||
preset: Option<String>,
|
||||
}
|
||||
@@ -105,6 +113,19 @@ fn config_from(options: JsValue) -> Result<Config, JsValue> {
|
||||
if let Some(v) = opts.optimize {
|
||||
config.optimize = v;
|
||||
}
|
||||
if let Some(v) = opts.binary_threshold {
|
||||
config.binary_threshold = v;
|
||||
}
|
||||
// Any adaptive tuning field (or `adaptive: true`) switches on Bradley–Roth.
|
||||
if opts.adaptive == Some(true) || opts.adaptive_window.is_some() || opts.adaptive_t.is_some() {
|
||||
config.binary_adaptive = true;
|
||||
}
|
||||
if let Some(v) = opts.adaptive_window {
|
||||
config.binary_adaptive_window = v;
|
||||
}
|
||||
if let Some(v) = opts.adaptive_t {
|
||||
config.binary_adaptive_t = v;
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user