use super::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RgbComposite {
Truncate,
QuantizedBlend,
}
impl ImageOps {
pub fn prepare_composite_rgb(
&self,
image: &TensorDesc,
crop: &TensorDesc,
mode: RgbComposite,
) -> Result<Arc<PreparedModel>> {
for (desc, dtype) in [(image, DType::U8), (crop, DType::F32)] {
if desc.dtype() != dtype
|| desc.layout() != Layout::Nhwc
|| !desc.is_contiguous()
|| desc.is_empty()
|| desc.shape().len() != 4
|| desc.shape()[0] != 1
|| desc.shape()[3] != 3
|| desc.elements() > i32::MAX as usize
|| desc.shape()[1..3].iter().any(|&n| n > 1 << 24)
{
return Err(Error::Message(
"composite requires one contiguous RGB frame and crop".into(),
));
}
}
let mask = TensorDesc::new(DType::F32, crop.shape()[1..3].to_vec())?;
let matrix = TensorDesc::new(DType::F32, vec![1, 2, 3])?;
let region = TensorDesc::new(DType::F32, vec![5])?;
self.composite_plans.get_or_prepare((image.clone(),crop.clone(),mode), || {
let count = image.elements();
let mut source = include_str!("composite_rgb.loom").to_owned();
for (name,value) in [
("COUNT",count),("GRID",count.div_ceil(256)),("WIDTH",image.shape()[2]),
("LAST_OUTPUT",count-1),("LAST_CROP",crop.elements()-1),("LAST_MASK",mask.elements()-1),
("LAST_X",crop.shape()[2]-1),("LAST_Y",crop.shape()[1]-1),
("CROP",crop.elements()),("MASK",mask.elements()),
("CROP_WIDTH",crop.shape()[2]),("CROP_HEIGHT",crop.shape()[1]),
] { source = source.replace(&format!("@{name}@"),&value.to_string()); }
source = source.replace("@CROP_ROUND@", if mode == RgbComposite::QuantizedBlend {
"%half = scalar.constant 0.5 : f32\n%shift = scalar.addf %face0, %half : f32\n%face = scalar.floorf %shift : f32"
} else { "%face = scalar.addf %face0, %fzero : f32" });
source = source.replace("@FINAL_BLEND@", if mode == RgbComposite::QuantizedBlend {
"%quantized = scalar.floorf %clamped : f32\n%blend_at = index.constant 4 : index\n%blend0 = view.load %rv[%blend_at] : view<5xf32> -> f32\n%blendlo = scalar.maxnumf %blend0, %fzero : f32\n%blend = scalar.minnumf %blendlo, %fone : f32\n%inverse = scalar.subf %fone, %blend : f32\n%before2 = scalar.mulf %original_float, %inverse : f32\n%after2 = scalar.mulf %quantized, %blend : f32\n%mixed = scalar.addf %before2, %after2 : f32\n%final = scalar.roundevenf %mixed : f32"
} else { "%final = scalar.addf %clamped, %fzero : f32" });
let mut model = ModelSession::in_context(&self.context)?;
let descs = [image.clone(), crop.clone(), mask, matrix, region];
let inputs = descs.into_iter().map(|d| Ok((model.allocate(d.bytes())?,d))).collect::<Result<Vec<_>>>()?;
let dst = model.allocate(image.bytes())?;
let mut bindings = inputs.iter().map(|(r,_)| r.read()).collect::<Vec<_>>();
bindings.push(dst.write());
let kernel = unsafe { model.compile(&[(&source,Specialization::new("composite_rgb"))])? }[0];
unsafe { model.freeze(&self.context)?.prepare(
&[Command::Dispatch(Dispatch::indices(kernel,[0],[count.div_ceil(256) as u32,1,1],bindings))],
&inputs, &[(dst,image.clone())], 3,
) }
})
}
#[allow(clippy::too_many_arguments)]
pub fn composite_rgb(
&self,
image: &DeviceTensor,
crop: &DeviceTensor,
mask: &DeviceTensor,
matrix: &DeviceTensor,
region: [usize; 4],
blend: f32,
mode: RgbComposite,
) -> Result<Inference> {
for tensor in [image, crop, mask, matrix] {
self.context.validate(tensor)?;
}
let plan = self.prepare_composite_rgb(image.desc(), crop.desc(), mode)?;
let s = image.desc().shape();
if region[0] > region[2]
|| region[1] > region[3]
|| region[2] > s[2]
|| region[3] > s[1]
|| !blend.is_finite()
|| !(0.0..=1.0).contains(&blend)
{
return Err(Error::Message("invalid composite region or blend".into()));
}
let bytes = region
.into_iter()
.map(|v| v as f32)
.chain([blend])
.flat_map(f32::to_le_bytes)
.collect::<Vec<_>>();
let metadata = self
.context
.upload(TensorDesc::new(DType::F32, vec![5])?, &bytes)?;
plan.submit(&[
image.clone(),
crop.clone(),
mask.clone(),
matrix.clone(),
metadata,
])
}
}