use std::sync::Once;
use peniko::{Brush, Color, ImageBrush, ImageData, ImageQuality, ImageSampler};
use vello_common::encode::{EncodeExt, EncodedImage, EncodedPaint};
use vello_common::kurbo::{Affine, Rect};
use vello_common::paint::{IndexedPaint, Paint, Tint, TintMode};
use crate::cache::images::{ImageResidency, ImageSkip, ResidentImage};
use crate::cache::{CachedRamp, GradientCache};
use crate::gpu::atlas::{natural_to_dest, x_y_advances};
const IMAGE_PLACEHOLDER: Color = Color::TRANSPARENT;
static IMAGE_BRUSH_WARNING: Once = Once::new();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LutRequest {
pub paint_index: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BrushEncoding {
pub paint: Paint,
pub lut_request: Option<LutRequest>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImageEncoding {
pub paint: Paint,
pub paint_index: usize,
pub resident: ResidentImage,
}
pub fn encode_brush(
brush: &Brush,
transform: Affine,
encoded_paints: &mut Vec<EncodedPaint>,
) -> BrushEncoding {
match brush {
Brush::Solid(color) => BrushEncoding {
paint: (*color).into(),
lut_request: None,
},
Brush::Gradient(gradient) => {
let paint = gradient.encode_into(encoded_paints, transform, None);
let lut_request = match &paint {
Paint::Indexed(indexed) => Some(LutRequest {
paint_index: indexed.index(),
}),
Paint::Solid(_) => None,
};
BrushEncoding { paint, lut_request }
}
Brush::Image(_) => {
IMAGE_BRUSH_WARNING.call_once(|| {
log::warn!(
"an image brush encoded with no residency paints transparent \
(logged once); the compiler routes image brushes through \
`encode_image_brush` instead"
);
});
BrushEncoding {
paint: IMAGE_PLACEHOLDER.into(),
lut_request: None,
}
}
}
}
pub fn encode_image_brush(
brush: &ImageBrush,
transform: Affine,
encoded_paints: &mut Vec<EncodedPaint>,
images: &mut ImageResidency,
) -> Result<ImageEncoding, ImageSkip> {
encode_image(
&brush.image,
brush.sampler,
transform,
encoded_paints,
images,
)
}
pub fn encode_image_command(
data: &ImageData,
dest: Rect,
transform: Affine,
encoded_paints: &mut Vec<EncodedPaint>,
images: &mut ImageResidency,
) -> Result<ImageEncoding, ImageSkip> {
let paint_transform = natural_to_dest(transform, (data.width, data.height), dest).ok_or(
ImageSkip::DegenerateExtent {
width: data.width,
height: data.height,
},
)?;
encode_image(
data,
ImageSampler::default(),
paint_transform,
encoded_paints,
images,
)
}
pub fn encode_image(
data: &ImageData,
sampler: ImageSampler,
paint_transform: Affine,
encoded_paints: &mut Vec<EncodedPaint>,
images: &mut ImageResidency,
) -> Result<ImageEncoding, ImageSkip> {
let transform = paint_transform.inverse();
if !transform.as_coeffs().iter().all(|coeff| coeff.is_finite()) {
return Err(ImageSkip::SingularTransform);
}
let resident = images.resolve(data)?;
let (x_advance, y_advance) = x_y_advances(transform);
let tint = alpha_tint(sampler.alpha);
let sampler = ImageSampler {
quality: resolved_quality(sampler.quality, paint_transform),
alpha: 1.0,
..sampler
};
let paint_index = encoded_paints.len();
encoded_paints.push(EncodedPaint::Image(EncodedImage {
may_have_transparency: resident.may_have_transparency || tint.is_some(),
source: resident.source(),
sampler,
transform,
x_advance,
y_advance,
tint,
}));
Ok(ImageEncoding {
paint: Paint::Indexed(IndexedPaint::new(paint_index)),
paint_index,
resident,
})
}
fn alpha_tint(alpha: f32) -> Option<Tint> {
if !alpha.is_finite() || alpha >= 1.0 {
return None;
}
Some(Tint {
color: Color::WHITE.multiply_alpha(alpha.max(0.0)),
mode: TintMode::Multiply,
})
}
fn resolved_quality(quality: ImageQuality, paint_transform: Affine) -> ImageQuality {
if quality != ImageQuality::Medium {
return quality;
}
let c = paint_transform.as_coeffs();
let unit_translation = (c[0] - 1.0).abs() < NEARLY_ZERO
&& c[1].abs() < NEARLY_ZERO
&& c[2].abs() < NEARLY_ZERO
&& (c[3] - 1.0).abs() < NEARLY_ZERO
&& (c[4] - c[4].floor()).abs() < NEARLY_ZERO
&& (c[5] - c[5].floor()).abs() < NEARLY_ZERO;
if unit_translation {
ImageQuality::Low
} else {
ImageQuality::Medium
}
}
const NEARLY_ZERO: f64 = 1.0 / 4096.0;
pub fn resolve_lut_request(
request: LutRequest,
encoded_paints: &[EncodedPaint],
cache: &mut GradientCache,
) -> Option<CachedRamp> {
match encoded_paints.get(request.paint_index) {
Some(EncodedPaint::Gradient(gradient)) => Some(cache.get_or_create_ramp(gradient)),
_ => None,
}
}