use crate::bitmap::{Bitmap, BitmapBand};
use crate::clip::{Clip, ClipResult};
use crate::path::Path;
use crate::pipe::{PipeSrc, PipeState};
use crate::scanner::XPathScanner;
use crate::scanner::iter::ScanIterator;
use crate::types::AA_SIZE;
use crate::xpath::XPath;
use color::Pixel;
use super::{draw_span, draw_span_clipped, fill_impl};
pub const PARALLEL_FILL_MIN_HEIGHT: u32 = 256;
#[expect(
clippy::too_many_arguments,
reason = "mirrors fill API; all params necessary"
)]
pub fn fill_parallel<P: Pixel + Send>(
bitmap: &mut Bitmap<P>,
clip: &Clip,
path: &Path,
pipe: &PipeState<'_>,
src: &PipeSrc<'_>,
matrix: &[f64; 6],
flatness: f64,
vector_antialias: bool,
n_bands: usize,
) {
fill_impl_parallel::<P>(
bitmap,
clip,
path,
pipe,
src,
matrix,
flatness,
vector_antialias,
false,
n_bands,
);
}
#[expect(
clippy::too_many_arguments,
reason = "mirrors eo_fill API; all params necessary"
)]
pub fn eo_fill_parallel<P: Pixel + Send>(
bitmap: &mut Bitmap<P>,
clip: &Clip,
path: &Path,
pipe: &PipeState<'_>,
src: &PipeSrc<'_>,
matrix: &[f64; 6],
flatness: f64,
vector_antialias: bool,
n_bands: usize,
) {
fill_impl_parallel::<P>(
bitmap,
clip,
path,
pipe,
src,
matrix,
flatness,
vector_antialias,
true,
n_bands,
);
}
#[expect(
clippy::too_many_arguments,
reason = "mirrors fill_impl API; all params necessary"
)]
fn fill_impl_parallel<P: Pixel + Send>(
bitmap: &mut Bitmap<P>,
clip: &Clip,
path: &Path,
pipe: &PipeState<'_>,
src: &PipeSrc<'_>,
matrix: &[f64; 6],
flatness: f64,
vector_antialias: bool,
eo: bool,
n_bands: usize,
) {
use rayon::iter::{IntoParallelIterator, ParallelIterator};
if path.pts.is_empty() {
return;
}
assert!(
i32::try_from(bitmap.height).is_ok(),
"bitmap height {} exceeds i32::MAX; cannot compute band y-coordinates",
bitmap.height,
);
if n_bands <= 1 || bitmap.height < PARALLEL_FILL_MIN_HEIGHT {
fill_impl::<P>(
bitmap,
clip,
path,
pipe,
src,
matrix,
flatness,
vector_antialias,
eo,
);
return;
}
let mut xpath = XPath::new(path, matrix, flatness, true);
let (y_min_clip, y_max_clip) = if vector_antialias {
xpath.aa_scale();
let y_min = clip
.y_min_i
.checked_mul(AA_SIZE)
.expect("AA y_lo overflows i32: clip.y_min_i is unreasonably large");
let y_max = clip
.y_max_i
.checked_add(1)
.and_then(|v| v.checked_mul(AA_SIZE))
.map(|v| v - 1)
.expect("AA y_hi overflows i32: clip.y_max_i is unreasonably large");
(y_min, y_max)
} else {
(clip.y_min_i, clip.y_max_i)
};
let scanner = XPathScanner::new(&xpath, eo, y_min_clip, y_max_clip);
if scanner.is_empty() {
return;
}
let (x_min_i, y_min_i, x_max_i, y_max_i) = if vector_antialias {
(
scanner.x_min / AA_SIZE,
scanner.y_min / AA_SIZE,
scanner.x_max / AA_SIZE,
scanner.y_max / AA_SIZE,
)
} else {
(scanner.x_min, scanner.y_min, scanner.x_max, scanner.y_max)
};
let clip_res = clip.test_rect(x_min_i, y_min_i, x_max_i, y_max_i);
if clip_res == ClipResult::AllOutside {
return;
}
if vector_antialias {
fill_impl::<P>(
bitmap,
clip,
path,
pipe,
src,
matrix,
flatness,
vector_antialias,
eo,
);
return;
}
let bands = bitmap.bands_mut(n_bands);
bands.into_par_iter().for_each(|mut band| {
fill_band::<P>(&mut band, clip, pipe, src, &scanner, clip_res);
});
}
fn fill_band<P: Pixel>(
band: &mut BitmapBand<'_, P>,
clip: &Clip,
pipe: &PipeState<'_>,
src: &PipeSrc<'_>,
scanner: &XPathScanner,
clip_res: ClipResult,
) {
#[expect(
clippy::cast_possible_wrap,
reason = "band.y_start ≤ bitmap.height ≤ i32::MAX; asserted in fill_impl_parallel"
)]
let y_band_min = band.y_start as i32;
#[expect(
clippy::cast_possible_wrap,
reason = "band.y_start + band.height - 1 ≤ bitmap.height - 1 ≤ i32::MAX; asserted in fill_impl_parallel"
)]
let y_band_max = (band.y_start + band.height - 1) as i32;
let y_start = scanner.y_min.max(y_band_min);
let y_end = scanner.y_max.min(y_band_max);
if y_start > y_end {
return;
}
if band.width == 0 {
return;
}
#[expect(
clippy::cast_possible_wrap,
reason = "band.width ≤ bitmap.width ≤ i32::MAX; zero checked above"
)]
let width_i = band.width as i32;
for y in scanner
.nonempty_rows()
.filter(|&y| y >= y_start && y <= y_end)
{
for (x0, x1) in ScanIterator::new(scanner, y) {
let (mut sx0, mut sx1) = (x0, x1);
let inner_clip = if clip_res == ClipResult::AllInside {
sx0 = sx0.max(0);
sx1 = sx1.min(width_i - 1);
true
} else {
sx0 = sx0.max(clip.x_min_i);
sx1 = sx1.min(clip.x_max_i);
clip.test_span(sx0, sx1, y) == ClipResult::AllInside
};
if sx0 > sx1 {
continue;
}
if inner_clip {
draw_span::<P, _>(band, pipe, src, sx0, sx1, y);
} else {
draw_span_clipped::<P, _>(band, clip, pipe, src, sx0, sx1, y);
}
}
}
}