1use crate::{
9 At, CodecSet, CodecSetError, DecodeOutput, DecodePolicy, EncodeOutput, EncodePolicy,
10 ImageFormat, encode::Fidelity,
11};
12use alloc::borrow::Cow;
13use alloc::boxed::Box;
14use alloc::vec::Vec;
15use core::fmt;
16use zenpixels::{PixelDescriptor, PixelSlice};
17
18mod binding;
19mod collection;
20mod conversion;
21mod decoding;
22mod encoding_route;
23mod frame;
24mod image;
25mod policy;
26mod scheduling;
27mod transcode;
28mod transition;
29pub use binding::{BoundPolicy, OperationPolicy};
30pub use collection::{CheckedCollection, CollectionOperationError, CollectionStage};
31pub use conversion::{ImageConversionOffer, resolve_image_conversion};
32pub use encoding_route::{
33 EncodeEvidence, EncodeRouteFacts, EncodeRouteOffer, InputAcquisition, ReplayEvidence,
34 resolve_encode_route,
35};
36pub use frame::{FrameFacts, PublicationOffer, resolve_frame};
37pub use image::{ImageFacts, resolve_image};
38pub use policy::{
39 BoundaryOffer, Choice, ChoiceId, Choices, Decision, DecisionCode, DecisionContext,
40 DecisionRecord, Decline, EncodeFacts, EncodeOffer, OperationReport, PolicyFailure,
41 PolicyRequirement, PolicyResolver, PolicySet, Question, RequirementEvidence, Selection,
42 UnmetRequirements, resolve_boundary,
43};
44pub use scheduling::{
45 OutputSchedule, OutputScheduleFacts, OutputScheduleOffer, OutputSource, resolve_output_schedule,
46};
47pub use transcode::{MetadataTransfer, TranscodeOffer, resolve_transcode};
48pub use transition::{
49 DestinationFacts, FrameConversionOffer, FrameRoute, FrameRouteFacts, FrameRouteOffer,
50 FrameRouteState, StagingFacts, StagingKind, resolve_frame_route,
51};
52
53#[derive(Debug)]
57pub struct Outcome<T> {
58 output: T,
59 report: OperationReport,
60}
61impl<T> Outcome<T> {
62 pub const fn from_parts(output: T, report: OperationReport) -> Self {
65 Self { output, report }
66 }
67 pub const fn output(&self) -> &T {
69 &self.output
70 }
71 pub fn into_output(self) -> T {
73 self.output
74 }
75 pub fn into_parts(self) -> (T, OperationReport) {
77 (self.output, self.report)
78 }
79 pub const fn report(&self) -> &OperationReport {
81 &self.report
82 }
83}
84impl<T> core::ops::Deref for Outcome<T> {
85 type Target = T;
86 fn deref(&self) -> &T {
87 &self.output
88 }
89}
90
91#[derive(Debug)]
93pub struct RouteRejection {
94 pub preference_index: usize,
96 pub fidelity: Option<Fidelity>,
98 pub cause: CodecSetError,
100}
101
102#[derive(Debug)]
104#[non_exhaustive]
105pub enum OperationError {
106 Codec(CodecSetError),
108 AlternativeCount {
110 count: usize,
112 limit: usize,
114 },
115 InvalidFidelity {
117 preference_index: usize,
119 cause: crate::encode::FidelityTargetError,
121 },
122 NoEncodeRoute(Vec<RouteRejection>),
124 Allocation,
126 Stopped(enough::StopReason),
128 Policy(PolicyFailure),
130 Execution {
132 cause: Box<CodecSetError>,
134 report: Box<OperationReport>,
136 },
137 OutputDescriptor {
139 requested: PixelDescriptor,
141 actual: PixelDescriptor,
143 },
144}
145
146impl fmt::Display for OperationError {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 match self {
149 Self::Codec(e) => write!(f, "operation: {e}"),
150 Self::AlternativeCount { count, limit } => {
151 write!(f, "request has {count} alternatives; expected 1..={limit}")
152 }
153 Self::InvalidFidelity {
154 preference_index,
155 cause,
156 } => write!(f, "fidelity alternative {preference_index}: {cause}"),
157 Self::NoEncodeRoute(rejections) => write!(
158 f,
159 "none of {} permitted encoder routes could be prepared",
160 rejections.len()
161 ),
162 Self::Allocation => f.write_str("operation planning allocation failed"),
163 Self::Stopped(reason) => write!(f, "operation stopped: {reason:?}"),
164 Self::Policy(e) => e.fmt(f),
165 Self::Execution { .. } => {
166 f.write_str("selected encoder failed; inspect the decision report and native cause")
167 }
168 Self::OutputDescriptor { requested, actual } => write!(
169 f,
170 "required output {requested:?}; decoder produced {actual:?}"
171 ),
172 }
173 }
174}
175impl core::error::Error for OperationError {
176 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
177 match self {
178 Self::Codec(e) => Some(e),
179 Self::InvalidFidelity { cause, .. } => Some(cause),
180 Self::NoEncodeRoute(rejections) => rejections
181 .first()
182 .map(|r| &r.cause as &dyn core::error::Error),
183 Self::Policy(e) => Some(e),
184 Self::Execution { cause, .. } => Some(cause.as_ref()),
185 _ => None,
186 }
187 }
188}
189
190#[derive(Clone, Debug, Default)]
191enum Output {
192 #[default]
193 Native,
194 Prefer(Vec<PixelDescriptor>),
195 Require(PixelDescriptor),
196}
197
198#[derive(Clone, Debug, Default)]
205pub struct DecodeRequest {
206 format: Option<ImageFormat>,
207 policy: Option<DecodePolicy>,
208 output: Output,
209}
210
211impl DecodeRequest {
212 pub const fn new() -> Self {
214 Self {
215 format: None,
216 policy: None,
217 output: Output::Native,
218 }
219 }
220
221 pub const fn with_format(mut self, format: ImageFormat) -> Self {
223 self.format = Some(format);
224 self
225 }
226
227 pub const fn with_policy(mut self, policy: DecodePolicy) -> Self {
229 self.policy = Some(policy);
230 self
231 }
232
233 pub fn with_output(mut self, descriptor: PixelDescriptor) -> Self {
241 self.output = Output::Require(descriptor);
242 self
243 }
244
245 pub fn preferring_output(mut self, descriptors: impl Into<Vec<PixelDescriptor>>) -> Self {
248 self.output = Output::Prefer(descriptors.into());
249 self
250 }
251
252 #[track_caller]
262 pub fn start_controlled(
263 &self,
264 codecs: &CodecSet,
265 storage: crate::storage::StorageBudget,
266 ) -> Result<Box<crate::decode::DynControlledDecoder>, At<OperationError>> {
267 let session = decoding::CheckedDecoder::new(self.clone(), codecs.clone(), storage)
268 .map_err(|e| At::wrap(e).at_named("start controlled decode"))?;
269 Ok(crate::decode::erase_controlled(session))
270 }
271
272 #[track_caller]
275 pub fn decode(
276 &self,
277 codecs: &CodecSet,
278 input: &[u8],
279 ) -> Result<Outcome<DecodeOutput>, At<OperationError>> {
280 let run = || -> Result<DecodeOutput, OperationError> {
281 codecs
282 .operation_pulse()
283 .check()
284 .map_err(OperationError::Stopped)?;
285 let format = self
286 .format
287 .or_else(|| codecs.detect(input))
288 .ok_or(OperationError::Codec(CodecSetError::UnrecognizedFormat))?;
289 let preferred = match &self.output {
290 Output::Native => &[][..],
291 Output::Prefer(list) => list,
292 Output::Require(descriptor) => core::slice::from_ref(descriptor),
293 };
294 let job = codecs
295 .checked_decode_job(format, self.policy)
296 .map_err(OperationError::Codec)?;
297 let decoder = job
298 .into_decoder(Cow::Borrowed(input), preferred)
299 .map_err(|e| OperationError::Codec(e.into()))?;
300 let output = decoder
301 .decode()
302 .map_err(|e| OperationError::Codec(e.into()))?;
303 codecs
304 .operation_pulse()
305 .check()
306 .map_err(OperationError::Stopped)?;
307 if let Output::Require(requested) = self.output {
308 let actual = output.pixels().descriptor();
309 if actual != requested {
310 return Err(OperationError::OutputDescriptor { requested, actual });
311 }
312 }
313 Ok(output)
314 };
315 run()
316 .map(|output| Outcome {
317 output,
318 report: OperationReport::default(),
319 })
320 .map_err(|e| At::wrap(e).at_named("checked image decode"))
321 }
322}
323
324pub struct PreparedEncode {
332 offer: EncodeOffer,
333 job: Box<dyn crate::encode::DynEncodeJob>,
334}
335impl fmt::Debug for PreparedEncode {
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 f.debug_struct("PreparedEncode")
338 .field("offer", &self.offer)
339 .finish_non_exhaustive()
340 }
341}
342impl PreparedEncode {
343 pub const fn offer(&self) -> EncodeOffer {
345 self.offer
346 }
347
348 pub fn into_job(self) -> Box<dyn crate::encode::DynEncodeJob> {
352 self.job
353 }
354}
355
356pub struct PreparedSendEncode {
361 offer: EncodeOffer,
362 job: Box<dyn crate::encode::DynEncodeJob + Send>,
363}
364impl fmt::Debug for PreparedSendEncode {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 f.debug_struct("PreparedSendEncode")
367 .field("offer", &self.offer)
368 .finish_non_exhaustive()
369 }
370}
371impl PreparedSendEncode {
372 pub const fn offer(&self) -> EncodeOffer {
374 self.offer
375 }
376 pub fn into_job(self) -> Box<dyn crate::encode::DynEncodeJob + Send> {
378 self.job
379 }
380}
381
382#[derive(Clone, Debug)]
388pub struct EncodeRequest {
389 format: ImageFormat,
390 fidelity: FidelityRequest,
391 policy: Option<EncodePolicy>,
392 policy_defaults: EncodePolicy,
393 preset: PolicySet,
394 resolution: Option<crate::Resolution>,
395 orientation_mode: Option<crate::encode::EncodeOrientation>,
396}
397
398#[derive(Clone, Debug)]
399enum FidelityRequest {
400 Template,
401 Required(Fidelity),
402 Alternatives(Vec<Fidelity>),
403}
404
405impl EncodeRequest {
406 pub const fn new(format: ImageFormat) -> Self {
408 Self {
409 format,
410 fidelity: FidelityRequest::Template,
411 policy: None,
412 policy_defaults: EncodePolicy::none(),
413 preset: PolicySet::new(),
414 resolution: None,
415 orientation_mode: None,
416 }
417 }
418
419 pub fn with_fidelity(mut self, fidelity: Fidelity) -> Self {
422 self.fidelity = FidelityRequest::Required(fidelity);
423 self
424 }
425
426 pub fn preferring_fidelity(mut self, targets: impl Into<Vec<Fidelity>>) -> Self {
430 self.fidelity = FidelityRequest::Alternatives(targets.into());
431 self
432 }
433
434 #[deprecated(note = "Pass PolicySet or BoundPolicy as the operation policy argument")]
438 pub const fn with_policy_set(mut self, preset: PolicySet) -> Self {
439 self.preset = preset;
440 self
441 }
442
443 pub const fn with_resolution(mut self, resolution: crate::Resolution) -> Self {
447 self.resolution = Some(resolution);
448 self
449 }
450
451 pub const fn with_orientation_mode(mut self, mode: crate::encode::EncodeOrientation) -> Self {
456 self.orientation_mode = Some(mode);
457 self
458 }
459
460 pub const fn with_policy(mut self, policy: EncodePolicy) -> Self {
462 self.policy = Some(policy);
463 self
464 }
465
466 pub const fn with_policy_defaults(mut self, defaults: EncodePolicy) -> Self {
473 self.policy_defaults = defaults;
474 self
475 }
476
477 fn effective_policy(&self, codecs: &CodecSet) -> Option<EncodePolicy> {
478 let required = self.policy.or(codecs.operation_encode_policy());
479 if self.policy_defaults == EncodePolicy::none() {
480 return required;
481 }
482 let mut policy = required.unwrap_or_else(EncodePolicy::none);
483 policy.color = policy.color.or(self.policy_defaults.color);
484 policy.embed_icc = policy.embed_icc.or(self.policy_defaults.embed_icc);
485 policy.embed_exif = policy.embed_exif.or(self.policy_defaults.embed_exif);
486 policy.embed_xmp = policy.embed_xmp.or(self.policy_defaults.embed_xmp);
487 policy.strict = policy.strict.or(self.policy_defaults.strict);
488 Some(policy)
489 }
490
491 #[track_caller]
498 pub fn prepare(&self, codecs: &CodecSet) -> Result<Vec<PreparedEncode>, At<OperationError>> {
499 self.prepare_with(codecs, |offer| {
500 let mut job = codecs.checked_encode_job(
501 self.format,
502 offer.fidelity,
503 self.effective_policy(codecs),
504 )?;
505 if let Some(mode) = self.orientation_mode {
506 job.try_set_orientation_mode(mode)?;
507 }
508 if let Some(resolution) = offer.resolution {
509 job.try_set_resolution(resolution)?;
510 }
511 Ok(PreparedEncode { offer, job })
512 })
513 }
514
515 #[track_caller]
519 pub fn prepare_send(
520 &self,
521 codecs: &CodecSet,
522 ) -> Result<Vec<PreparedSendEncode>, At<OperationError>> {
523 self.prepare_with(codecs, |offer| {
524 let mut job = codecs.checked_send_encode_job(
525 self.format,
526 offer.fidelity,
527 self.effective_policy(codecs),
528 )?;
529 if let Some(mode) = self.orientation_mode {
530 job.try_set_orientation_mode(mode)?;
531 }
532 if let Some(resolution) = offer.resolution {
533 job.try_set_resolution(resolution)?;
534 }
535 Ok(PreparedSendEncode { offer, job })
536 })
537 }
538
539 fn prepare_with<T>(
540 &self,
541 codecs: &CodecSet,
542 mut create: impl FnMut(EncodeOffer) -> Result<T, CodecSetError>,
543 ) -> Result<Vec<T>, At<OperationError>> {
544 let failure = |error| At::wrap(error).at_named("prepare encode routes");
545 if let Some(resolution) = self.resolution {
546 resolution.validate().map_err(|error| {
547 failure(OperationError::Codec(CodecSetError::Codec(Box::new(error))))
548 })?;
549 }
550 let targets = match &self.fidelity {
551 FidelityRequest::Template => None,
552 FidelityRequest::Required(target) => Some(core::slice::from_ref(target)),
553 FidelityRequest::Alternatives(targets) => Some(targets.as_slice()),
554 };
555 let count = targets.map_or(1, <[_]>::len);
556 codecs
557 .operation_pulse()
558 .check()
559 .map_err(|reason| failure(OperationError::Stopped(reason)))?;
560 if count == 0 || count > policy::MAX_OFFERS {
561 return Err(failure(OperationError::AlternativeCount {
562 count,
563 limit: policy::MAX_OFFERS,
564 }));
565 }
566 if let Some(targets) = targets {
567 for (index, target) in targets.iter().enumerate() {
568 target.validate_target().map_err(|cause| {
569 failure(OperationError::InvalidFidelity {
570 preference_index: index,
571 cause,
572 })
573 })?;
574 }
575 }
576 let mut jobs = Vec::new();
577 let mut rejections = Vec::new();
578 jobs.try_reserve_exact(count)
579 .map_err(|_| failure(OperationError::Allocation))?;
580 rejections
581 .try_reserve_exact(count)
582 .map_err(|_| failure(OperationError::Allocation))?;
583 for index in 0..count {
584 codecs
585 .operation_pulse()
586 .check()
587 .map_err(|reason| failure(OperationError::Stopped(reason)))?;
588 let fidelity = targets.map(|targets| targets[index]);
589 match create(EncodeOffer {
590 format: self.format,
591 fidelity,
592 preference_index: index,
593 resolution: self.resolution,
594 }) {
595 Ok(job) => jobs.push(job),
596 Err(cause) => rejections.push(RouteRejection {
597 preference_index: index,
598 fidelity,
599 cause,
600 }),
601 }
602 }
603 if jobs.is_empty() {
604 return Err(failure(OperationError::NoEncodeRoute(rejections)));
605 }
606 Ok(jobs)
607 }
608
609 #[track_caller]
612 pub fn encode(
613 &self,
614 codecs: &CodecSet,
615 pixels: PixelSlice<'_>,
616 ) -> Result<Outcome<EncodeOutput>, At<OperationError>> {
617 let mut delegate = |_: Question<'_>| -> Result<Decision, At<core::convert::Infallible>> {
618 Ok(Decision::UsePreset)
619 };
620 self.encode_with(codecs, pixels, &mut delegate)
621 }
622
623 #[track_caller]
627 pub fn encode_with<R: OperationPolicy + ?Sized>(
628 &self,
629 codecs: &CodecSet,
630 pixels: PixelSlice<'_>,
631 resolver: &mut R,
632 ) -> Result<Outcome<EncodeOutput>, At<OperationError>> {
633 let mut jobs = self.prepare(codecs)?;
634 let mut offers = Vec::new();
635 offers
636 .try_reserve_exact(jobs.len())
637 .map_err(|_| At::wrap(OperationError::Allocation).at())?;
638 offers.extend(jobs.iter().map(PreparedEncode::offer));
639 let facts = EncodeFacts {
640 width: pixels.width(),
641 height: pixels.rows(),
642 descriptor: pixels.descriptor(),
643 };
644 let (selected, record) = policy::choose_encode(
645 facts,
646 &offers,
647 resolver,
648 self.preset,
649 codecs.operation_pulse(),
650 )
651 .map_err(|error| {
652 error
653 .map_error(OperationError::Policy)
654 .at_named("resolve encode route")
655 })?;
656 let mut report = OperationReport::default();
657 report.record(record);
658 let job = jobs.swap_remove(selected).into_job();
659 drop(jobs);
662 let output = job.encode(pixels).map_err(|cause| {
663 At::wrap(OperationError::Execution {
664 cause: Box::new(cause.into()),
665 report: Box::new(report.clone()),
666 })
667 .at_named("execute selected encoder")
668 })?;
669 Ok(Outcome { output, report })
670 }
671}