Files
pdfcraft/vendor/vello_cpu/src/util.rs
T
KenM76andClaude Opus 5.5 05630b5f15 fix(render): a gray drawn with the Color blend mode is no longer drawn black
vello_cpu 0.0.8's clip_color, shared by the nonseparable blend modes,
divided 0 by 0 when a gray's equal components were rounded just below 0
or above 1 after set_lum. The NaN drew the pixel black at the source's
alpha whatever the backdrop: nine gray levels under /BM /Color in an
empty isolated group (0.96 g among them) rendered (0, 0, 0), and two
more at half opacity came out wrong.

Vendor vello_cpu 0.0.8 (Apache-2.0 OR MIT) with a backport of
linebender/vello#2000, which clips only when the denominator is
positive. No vello_cpu release carries the fix yet; drop the copy when
one does.

Tests in it::nonseparable_gray run every gray level. The two Color
ramps fail with the unpatched blend.rs. The Luminosity ramp (black over
each opaque gray, upstream's other case) passes either way here: pages
use the u8 pipeline, and that case only failed in the f32 one; it stays
as a guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-10-28 14:45:32 -04:00

113 lines
4.0 KiB
Rust

// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use crate::peniko::ImageQuality;
use vello_common::encode::EncodedImage;
use vello_common::fearless_simd::{Simd, SimdBase, f32x4, u8x32};
use vello_common::math::FloatExt;
use vello_common::util::Div255Ext;
pub(crate) mod scalar {
/// Perform an approximate division by 255.
///
/// There are three reasons for having this method.
/// 1) Divisions are slower than shifting + adding, and the compiler does not seem to replace
/// divisions by 255 with an equivalent (this was verified by benchmarking; doing / 255 was
/// significantly slower).
/// 2) Integer divisions are usually not available in SIMD, so this provides a good baseline
/// implementation.
/// 3) There are two options for performing the division: One is to perform the division
/// in a way that completely preserves the rounding semantics of a integer division by
/// 255. This could be achieved using the implementation `(val + 1 + (val >> 8)) >> 8`.
/// The second approach (used here) has slightly different rounding behavior to a
/// normal division by 255, but is much faster (see <https://github.com/linebender/vello/issues/904>)
/// and therefore preferable for the high-performance pipeline.
///
/// Four properties worth mentioning:
/// - This actually calculates the ceiling of `val / 256`.
/// - Within the allowed range for `val`, rounding errors do not appear for values divisible by 255, i.e. any call `div_255(val * 255)` will always yield `val`.
/// - If there is a discrepancy, this division will always yield a value 1 higher than the original.
/// - This holds for values of `val` up to and including `65279`. You should not call this function with higher values.
#[inline(always)]
pub(crate) const fn div_255(val: u16) -> u16 {
debug_assert!(
val < 65280,
"the properties of `div_255` do not hold for values of `65280` or greater"
);
(val + 255) >> 8
}
#[cfg(test)]
mod tests {
use crate::util::scalar::div_255;
#[test]
fn div_255_properties() {
for i in 0_u16..256 * 255 {
let expected = i / 255;
let actual = div_255(i);
assert!(
expected <= actual,
"In case of a discrepancy, the division should yield a value higher than the original."
);
let diff = expected.abs_diff(actual);
assert!(diff <= 1, "Rounding error shouldn't be higher than 1.");
if i % 255 == 0 {
assert_eq!(diff, 0, "Division should be accurate for multiples of 255.");
}
}
}
}
}
pub(crate) trait NormalizedMulExt {
fn normalized_mul(self, other: Self) -> Self;
}
impl<S: Simd> NormalizedMulExt for u8x32<S> {
#[inline(always)]
fn normalized_mul(self, other: Self) -> Self {
let divided = (self.simd.widen_u8x32(self) * other.simd.widen_u8x32(other)).div_255();
self.simd.narrow_u16x32(divided)
}
}
pub(crate) trait EncodedImageExt {
fn has_skew(&self) -> bool;
fn nearest_neighbor(&self) -> bool;
}
impl EncodedImageExt for EncodedImage {
fn has_skew(&self) -> bool {
!(self.x_advance.y as f32).is_nearly_zero() || !(self.y_advance.x as f32).is_nearly_zero()
}
fn nearest_neighbor(&self) -> bool {
self.sampler.quality == ImageQuality::Low
}
}
pub(crate) trait Premultiply {
fn premultiply(self, alphas: Self) -> Self;
fn unpremultiply(self, alphas: Self) -> Self;
}
impl<S: Simd> Premultiply for f32x4<S> {
#[inline(always)]
fn premultiply(self, alphas: Self) -> Self {
self * alphas
}
#[inline(always)]
fn unpremultiply(self, alphas: Self) -> Self {
let zero = Self::splat(alphas.simd, 0.0);
let divided = self / alphas;
self.simd
.select_f32x4(self.simd.simd_eq_f32x4(alphas, zero), zero, divided)
}
}