1use alloc::{boxed::Box, sync::Arc, vec::Vec};
4use core::fmt;
5use zencodec::{
6 At, CodecError,
7 collection::{Cause, Composition},
8 image::{
9 Auxiliary, AuxiliaryGrid, AuxiliaryImageOwner, ImageGeometry, ImageOutput, Meaning,
10 OutputError, PackedImageOwner, Rect, Samples, ValueSlice,
11 },
12 storage::{SharedStorage, StorageBudget, StorageError},
13};
14use zenpixels::{
15 ChannelLayout, ColorContext, Orientation, PixelDescriptor, PixelSlice, PixelSliceMut,
16 TransferFunction,
17};
18use zenpixels_convert::orient::apply_orientation_into;
19
20#[derive(Debug, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum Refusal {
24 Composition(Option<Composition>),
26 Placement,
28 Interpretation,
30 MissingStoredRegion {
32 required: Rect,
34 available: Rect,
36 },
37 AuxiliaryGrid {
39 index: usize,
41 dimensions: (usize, usize),
43 registration: AuxiliaryGrid,
45 },
46}
47impl fmt::Display for Refusal {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 write!(f, "canvas projection refused: {self:?}")
50 }
51}
52impl core::error::Error for Refusal {}
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub struct ProjectionSource {
57 pub geometry: ImageGeometry,
59 pub orientation: Orientation,
61}
62
63struct Plane {
64 data: SharedStorage<u64>,
65 width: u32,
66 rows: u32,
67 stride: usize,
68 descriptor: PixelDescriptor,
69 context: Option<Arc<ColorContext>>,
70}
71impl Plane {
72 fn pixels(&self) -> Result<PixelSlice<'_>, At<OutputError>> {
73 let pixels = PixelSlice::new(
74 bytemuck::cast_slice(self.data.as_slice()),
75 self.width,
76 self.rows,
77 self.stride,
78 self.descriptor,
79 )
80 .map_err(|error| {
81 At::wrap(OutputError::Mapping(zencodec::frame::MapError::Backend(
82 Box::new(error),
83 )))
84 })?;
85 Ok(match &self.context {
86 Some(context) => pixels.with_color_context(context.clone()),
87 None => pixels,
88 })
89 }
90}
91struct Extras(Vec<Plane>);
92impl AuxiliaryImageOwner for Extras {
93 fn auxiliary(&self, index: usize) -> Result<Option<ValueSlice<'_>>, At<OutputError>> {
94 self.0
95 .get(index)
96 .map(|plane| {
97 ValueSlice::new(
98 bytemuck::cast_slice(plane.data.as_slice()),
99 plane.width,
100 plane.rows,
101 plane.stride,
102 plane.descriptor.channel_type(),
103 )
104 .map(|values| values.with_grid(AuxiliaryGrid::ColorSamples))
105 })
106 .transpose()
107 }
108}
109struct Projected {
110 color: Plane,
111 extras: Arc<Extras>,
112}
113impl PackedImageOwner for Projected {
114 fn pixels(&self) -> Result<PixelSlice<'_>, At<OutputError>> {
115 self.color.pixels()
116 }
117 fn auxiliary(&self, index: usize) -> Result<Option<ValueSlice<'_>>, At<OutputError>> {
118 self.extras.auxiliary(index)
119 }
120 fn retain_auxiliaries(&self) -> Result<Option<Arc<dyn AuxiliaryImageOwner>>, At<OutputError>> {
121 Ok(Some(self.extras.clone()))
122 }
123}
124
125fn check(pulse: &dyn zencodec::Pulse) -> Result<(), Cause> {
126 pulse
127 .check()
128 .map_err(|reason| At::wrap(CodecError::new(None, reason.into())).into())
129}
130
131pub fn project(
140 image: &ImageOutput,
141 region: Option<Rect>,
142 storage: &StorageBudget,
143 pulse: Option<&dyn zencodec::Pulse>,
144) -> Result<ImageOutput, Cause> {
145 let pulse = pulse.unwrap_or(&zencodec::NoPulse);
146 check(pulse)?;
147 let source = image.interpretation();
148 if source.composition != Some(Composition::Composited) {
149 return Err(Refusal::Composition(source.composition).into());
150 }
151 if source.placement.is_some() {
152 return Err(Refusal::Placement.into());
153 }
154 if source.signal.is_some() || source.presentation.is_some() {
155 return Err(Refusal::Interpretation.into());
156 }
157 let orientation = source.orientation.ok_or(Refusal::Interpretation)?;
158 source.geometry.validate()?;
159 let (canvas_width, canvas_height) =
160 orientation.output_dimensions(source.geometry.canvas_width, source.geometry.canvas_height);
161 let region = region.unwrap_or(Rect {
162 x: 0,
163 y: 0,
164 width: canvas_width,
165 height: canvas_height,
166 });
167 let geometry = ImageGeometry {
168 canvas_width,
169 canvas_height,
170 region,
171 };
172 geometry.validate()?;
173 let inverse = orientation.inverse();
176 let (ax, ay) = inverse.forward_map(region.x, region.y, canvas_width, canvas_height);
177 let (bx, by) = inverse.forward_map(
178 region.x + region.width - 1,
179 region.y + region.height - 1,
180 canvas_width,
181 canvas_height,
182 );
183 let required = Rect {
184 x: ax.min(bx),
185 y: ay.min(by),
186 width: ax.abs_diff(bx) + 1,
187 height: ay.abs_diff(by) + 1,
188 };
189 let available = source.geometry.region;
190 if required.x < available.x
191 || required.y < available.y
192 || required.x + required.width > available.x + available.width
193 || required.y + required.height > available.y + available.height
194 {
195 return Err(Refusal::MissingStoredRegion {
196 required,
197 available,
198 }
199 .into());
200 }
201 let mapped = image.map()?;
202 let mut views = Vec::new();
203 views.try_reserve_exact(mapped.plane_count())?;
204 for index in 0..mapped.plane_count() {
205 let plane = mapped.plane(index)?.ok_or(OutputError::MissingPlane)?;
206 let dimensions = plane.samples.dimensions();
207 let channel = plane.samples.channel_type();
208 let stride = plane.samples.stride_bytes();
209 let (pixels, auxiliary) = match (index, plane.meaning, plane.samples) {
210 (0, Meaning::Color, Samples::Pixels(pixels)) => (pixels, None),
211 (_, Meaning::Auxiliary(role), Samples::Values(values)) if index > 0 => {
212 if values.grid() != AuxiliaryGrid::ColorSamples
213 || dimensions != (available.width as usize, available.height as usize)
214 {
215 return Err(Refusal::AuxiliaryGrid {
216 index,
217 dimensions,
218 registration: values.grid(),
219 }
220 .into());
221 }
222 let descriptor = PixelDescriptor::new(
226 channel,
227 ChannelLayout::Gray,
228 None,
229 TransferFunction::Unknown,
230 );
231 (
232 PixelSlice::new(
233 values.as_strided_bytes(),
234 available.width,
235 available.height,
236 stride,
237 descriptor,
238 )?,
239 Some(role.clone()),
240 )
241 }
242 _ => return Err(Refusal::Interpretation.into()),
243 };
244 views.push((pixels, auxiliary));
245 }
246 if views.is_empty() {
247 return Err(OutputError::MissingPlane.into());
248 }
249 let mut planes = Vec::new();
250 let mut roles: Vec<Auxiliary> = Vec::new();
251 planes.try_reserve_exact(views.len())?;
252 roles.try_reserve_exact(views.len() - 1)?;
253 for (pixels, auxiliary) in views {
254 check(pulse)?;
255 let descriptor = pixels.descriptor();
256 let stride = (region.width as usize)
262 .checked_add(8)
263 .map(|n| n & !7)
264 .and_then(|n| n.checked_mul(descriptor.bytes_per_pixel()))
265 .ok_or(StorageError::SizeOverflow)?;
266 let bytes = stride
267 .checked_mul(region.height as usize)
268 .ok_or(StorageError::SizeOverflow)?;
269 let mut data = storage.filled(bytes / 8, 0xa5a5_a5a5_a5a5_a5a5u64)?;
270 let dst = PixelSliceMut::new(
271 bytemuck::cast_slice_mut(data.as_mut_slice()),
272 region.width,
273 region.height,
274 stride,
275 descriptor,
276 )?;
277 let selected = pixels.crop_view(
278 required.x - available.x,
279 required.y - available.y,
280 required.width,
281 required.height,
282 );
283 apply_orientation_into(selected, orientation, dst)?;
284 check(pulse)?;
285 planes.push(Plane {
286 data: data.freeze(),
287 width: region.width,
288 rows: region.height,
289 stride,
290 descriptor,
291 context: pixels.color_context().cloned(),
292 });
293 if let Some(role) = auxiliary {
294 roles.push(role);
295 }
296 }
297 let color = planes.remove(0);
298 let mut interpretation = source.clone();
299 interpretation.geometry = geometry;
300 interpretation.orientation = Some(Orientation::Identity);
301 interpretation.source_details.insert(ProjectionSource {
302 geometry: source.geometry,
303 orientation,
304 });
305 let output = ImageOutput::from_packed_owner(
306 Projected {
307 color,
308 extras: Arc::new(Extras(planes)),
309 },
310 roles,
311 interpretation,
312 )?;
313 check(pulse)?;
314 Ok(output)
315}