1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
148
149use std::sync::Arc;
150
151use jxl_bitstream::{Bitstream, ContainerDetectingReader, ParseEvent};
152use jxl_frame::FrameContext;
153use jxl_image::BitDepth;
154use jxl_oxide_common::{Bundle, Name};
155use jxl_render::ImageBuffer;
156use jxl_render::ImageWithRegion;
157use jxl_render::Region;
158use jxl_render::{IndexedFrame, RenderContext};
159
160pub use jxl_color::header as color;
161pub use jxl_color::{
162 ColorEncodingWithProfile, ColorManagementSystem, EnumColourEncoding, NullCms, RenderingIntent,
163};
164pub use jxl_frame::header as frame;
165pub use jxl_frame::{Frame, FrameHeader};
166pub use jxl_grid::{AlignedGrid, AllocTracker};
167pub use jxl_image as image;
168pub use jxl_image::{ExtraChannelType, ImageHeader};
169pub use jxl_jbr as jpeg_bitstream;
170pub use jxl_threadpool::JxlThreadPool;
171
172mod aux_box;
173mod fb;
174pub mod integration;
175#[cfg(feature = "lcms2")]
176mod lcms2;
177
178#[cfg(feature = "lcms2")]
179pub use self::lcms2::Lcms2;
180pub use aux_box::{AuxBoxData, AuxBoxList, RawExif};
181pub use fb::{FrameBuffer, FrameBufferSample, ImageStream};
182
183pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync + 'static>>;
184
185#[cfg(feature = "rayon")]
186fn default_pool() -> JxlThreadPool {
187 JxlThreadPool::rayon_global()
188}
189
190#[cfg(not(feature = "rayon"))]
191fn default_pool() -> JxlThreadPool {
192 JxlThreadPool::none()
193}
194
195#[derive(Debug, Default)]
197pub struct JxlImageBuilder {
198 pool: Option<JxlThreadPool>,
199 tracker: Option<AllocTracker>,
200}
201
202impl JxlImageBuilder {
203 pub fn pool(mut self, pool: JxlThreadPool) -> Self {
205 self.pool = Some(pool);
206 self
207 }
208
209 pub fn alloc_tracker(mut self, tracker: AllocTracker) -> Self {
211 self.tracker = Some(tracker);
212 self
213 }
214
215 pub fn build_uninit(self) -> UninitializedJxlImage {
217 UninitializedJxlImage {
218 pool: self.pool.unwrap_or_else(default_pool),
219 tracker: self.tracker,
220 reader: ContainerDetectingReader::new(),
221 buffer: Vec::new(),
222 aux_boxes: AuxBoxList::new(),
223 }
224 }
225
226 pub fn read(self, mut reader: impl std::io::Read) -> Result<JxlImage> {
228 let mut uninit = self.build_uninit();
229 let mut buf = vec![0u8; 4096];
230 let mut buf_valid = 0usize;
231 let mut image = loop {
232 let count = reader.read(&mut buf[buf_valid..])?;
233 if count == 0 {
234 return Err(std::io::Error::new(
235 std::io::ErrorKind::UnexpectedEof,
236 "reader ended before parsing image header",
237 )
238 .into());
239 }
240 buf_valid += count;
241 let consumed = uninit.feed_bytes(&buf[..buf_valid])?;
242 buf.copy_within(consumed..buf_valid, 0);
243 buf_valid -= consumed;
244
245 match uninit.try_init()? {
246 InitializeResult::NeedMoreData(x) => {
247 uninit = x;
248 }
249 InitializeResult::Initialized(x) => {
250 break x;
251 }
252 }
253 };
254
255 while !image.inner.end_of_image {
256 let count = reader.read(&mut buf[buf_valid..])?;
257 if count == 0 {
258 break;
259 }
260 buf_valid += count;
261 let consumed = image.feed_bytes(&buf[..buf_valid])?;
262 buf.copy_within(consumed..buf_valid, 0);
263 buf_valid -= consumed;
264 }
265
266 buf.truncate(buf_valid);
267 image.finalize()?;
268 Ok(image)
269 }
270
271 pub fn open(self, path: impl AsRef<std::path::Path>) -> Result<JxlImage> {
273 let file = std::fs::File::open(path)?;
274 self.read(file)
275 }
276}
277
278pub struct UninitializedJxlImage {
303 pool: JxlThreadPool,
304 tracker: Option<AllocTracker>,
305 reader: ContainerDetectingReader,
306 buffer: Vec<u8>,
307 aux_boxes: AuxBoxList,
308}
309
310impl UninitializedJxlImage {
311 pub fn feed_bytes(&mut self, buf: &[u8]) -> Result<usize> {
315 for event in self.reader.feed_bytes(buf) {
316 match event? {
317 ParseEvent::BitstreamKind(_) => {}
318 ParseEvent::Codestream(buf) => {
319 self.buffer.extend_from_slice(buf);
320 }
321 aux_box_event => {
322 self.aux_boxes.handle_event(aux_box_event)?;
323 }
324 }
325 }
326 Ok(self.reader.previous_consumed_bytes())
327 }
328
329 #[inline]
331 pub fn reader(&self) -> &ContainerDetectingReader {
332 &self.reader
333 }
334
335 pub fn try_init(mut self) -> Result<InitializeResult> {
343 let mut bitstream = Bitstream::new(&self.buffer);
344 let image_header = match ImageHeader::parse(&mut bitstream, ()) {
345 Ok(x) => x,
346 Err(e) if e.unexpected_eof() => {
347 return Ok(InitializeResult::NeedMoreData(self));
348 }
349 Err(e) => {
350 return Err(e.into());
351 }
352 };
353
354 let embedded_icc = if image_header.metadata.colour_encoding.want_icc() {
355 let icc = match jxl_color::icc::read_icc(&mut bitstream) {
356 Ok(x) => x,
357 Err(e) if e.unexpected_eof() => {
358 return Ok(InitializeResult::NeedMoreData(self));
359 }
360 Err(e) => {
361 return Err(e.into());
362 }
363 };
364 tracing::debug!("Image has an embedded ICC profile");
365 let icc = jxl_color::icc::decode_icc(&icc)?;
366 Some(icc)
367 } else {
368 None
369 };
370 bitstream.zero_pad_to_byte()?;
371
372 let image_header = Arc::new(image_header);
373 let skip_bytes = if image_header.metadata.preview.is_some() {
374 let frame = match Frame::parse(
375 &mut bitstream,
376 FrameContext {
377 image_header: image_header.clone(),
378 tracker: self.tracker.as_ref(),
379 pool: self.pool.clone(),
380 },
381 ) {
382 Ok(x) => x,
383 Err(e) if e.unexpected_eof() => {
384 return Ok(InitializeResult::NeedMoreData(self));
385 }
386 Err(e) => {
387 return Err(e.into());
388 }
389 };
390
391 let bytes_read = bitstream.num_read_bits() / 8;
392 let x = frame.toc().total_byte_size();
393 if self.buffer.len() < bytes_read + x {
394 return Ok(InitializeResult::NeedMoreData(self));
395 }
396
397 x
398 } else {
399 0usize
400 };
401
402 let bytes_read = bitstream.num_read_bits() / 8 + skip_bytes;
403 self.buffer.drain(..bytes_read);
404
405 let render_spot_color = !image_header.metadata.grayscale();
406
407 let mut builder = RenderContext::builder().pool(self.pool.clone());
408 if let Some(icc) = embedded_icc {
409 builder = builder.embedded_icc(icc);
410 }
411 if let Some(tracker) = self.tracker {
412 builder = builder.alloc_tracker(tracker);
413 }
414 #[cfg_attr(not(feature = "lcms2"), allow(unused_mut))]
415 let mut ctx = builder.build(image_header.clone())?;
416 #[cfg(feature = "lcms2")]
417 ctx.set_cms(Lcms2);
418
419 let mut image = JxlImage {
420 pool: self.pool.clone(),
421 reader: self.reader,
422 image_header,
423 ctx,
424 render_spot_color,
425 inner: JxlImageInner {
426 end_of_image: false,
427 buffer: Vec::new(),
428 buffer_offset: bytes_read,
429 frame_offsets: Vec::new(),
430 aux_boxes: self.aux_boxes,
431 },
432 };
433 image.inner.feed_bytes_inner(&mut image.ctx, &self.buffer)?;
434
435 Ok(InitializeResult::Initialized(image))
436 }
437}
438
439pub enum InitializeResult {
441 NeedMoreData(UninitializedJxlImage),
443 Initialized(JxlImage),
445}
446
447#[derive(Debug)]
449pub struct JxlImage {
450 pool: JxlThreadPool,
451 reader: ContainerDetectingReader,
452 image_header: Arc<ImageHeader>,
453 ctx: RenderContext,
454 render_spot_color: bool,
455 inner: JxlImageInner,
456}
457
458impl JxlImage {
460 pub fn builder() -> JxlImageBuilder {
462 JxlImageBuilder::default()
463 }
464
465 pub fn read_with_defaults(reader: impl std::io::Read) -> Result<JxlImage> {
467 Self::builder().read(reader)
468 }
469
470 pub fn open_with_defaults(path: impl AsRef<std::path::Path>) -> Result<JxlImage> {
472 Self::builder().open(path)
473 }
474
475 pub fn feed_bytes(&mut self, buf: &[u8]) -> Result<usize> {
479 for event in self.reader.feed_bytes(buf) {
480 match event? {
481 ParseEvent::BitstreamKind(_) => {}
482 ParseEvent::Codestream(buf) => {
483 self.inner.feed_bytes_inner(&mut self.ctx, buf)?;
484 }
485 aux_box_event => {
486 self.inner.aux_boxes.handle_event(aux_box_event)?;
487 }
488 }
489 }
490 Ok(self.reader.previous_consumed_bytes())
491 }
492
493 pub fn finalize(&mut self) -> Result<()> {
497 self.inner.aux_boxes.eof()?;
498 Ok(())
499 }
500}
501
502impl JxlImage {
504 #[inline]
506 pub fn image_header(&self) -> &ImageHeader {
507 &self.image_header
508 }
509
510 #[inline]
512 pub fn width(&self) -> u32 {
513 self.image_header.width_with_orientation()
514 }
515
516 #[inline]
518 pub fn height(&self) -> u32 {
519 self.image_header.height_with_orientation()
520 }
521
522 #[inline]
524 pub fn original_icc(&self) -> Option<&[u8]> {
525 self.ctx.embedded_icc()
526 }
527
528 pub fn rendered_icc(&self) -> Vec<u8> {
534 let encoding = self.ctx.requested_color_encoding();
535 match encoding.encoding() {
536 jxl_color::ColourEncoding::Enum(encoding) => {
537 jxl_color::icc::colour_encoding_to_icc(encoding)
538 }
539 jxl_color::ColourEncoding::IccProfile(_) => encoding.icc_profile().to_vec(),
540 }
541 }
542
543 #[inline]
545 pub fn rendered_cicp(&self) -> Option<[u8; 4]> {
546 let encoding = self.ctx.requested_color_encoding();
547 encoding.encoding().cicp()
548 }
549
550 pub fn pixel_format(&self) -> PixelFormat {
552 let encoding = self.ctx.requested_color_encoding();
553 let is_grayscale = encoding.is_grayscale();
554 let has_black = encoding.is_cmyk();
555 let mut has_alpha = false;
556 for ec_info in &self.image_header.metadata.ec_info {
557 if ec_info.is_alpha() {
558 has_alpha = true;
559 }
560 }
561
562 match (is_grayscale, has_black, has_alpha) {
563 (false, false, false) => PixelFormat::Rgb,
564 (false, false, true) => PixelFormat::Rgba,
565 (false, true, false) => PixelFormat::Cmyk,
566 (false, true, true) => PixelFormat::Cmyka,
567 (true, _, false) => PixelFormat::Gray,
568 (true, _, true) => PixelFormat::Graya,
569 }
570 }
571
572 pub fn hdr_type(&self) -> Option<HdrType> {
576 self.ctx.suggested_hdr_tf().and_then(|tf| match tf {
577 jxl_color::TransferFunction::Pq => Some(HdrType::Pq),
578 jxl_color::TransferFunction::Hlg => Some(HdrType::Hlg),
579 _ => None,
580 })
581 }
582
583 #[inline]
585 pub fn render_spot_color(&self) -> bool {
586 self.render_spot_color
587 }
588
589 #[inline]
591 pub fn set_render_spot_color(&mut self, render_spot_color: bool) -> &mut Self {
592 if render_spot_color && self.image_header.metadata.grayscale() {
593 tracing::warn!("Spot colour channels are not rendered on grayscale images");
594 return self;
595 }
596 self.render_spot_color = render_spot_color;
597 self
598 }
599
600 pub fn aux_boxes(&self) -> &AuxBoxList {
604 &self.inner.aux_boxes
605 }
606
607 #[inline]
609 pub fn num_loaded_keyframes(&self) -> usize {
610 self.ctx.loaded_keyframes()
611 }
612
613 #[inline]
616 pub fn num_loaded_frames(&self) -> usize {
617 self.ctx.loaded_frames()
618 }
619
620 #[inline]
623 pub fn is_loading_done(&self) -> bool {
624 self.inner.end_of_image
625 }
626
627 pub fn frame_by_keyframe(&self, keyframe_index: usize) -> Option<&IndexedFrame> {
629 self.ctx.keyframe(keyframe_index)
630 }
631
632 pub fn frame_header(&self, keyframe_index: usize) -> Option<&FrameHeader> {
635 let frame = self.ctx.keyframe(keyframe_index)?;
636 Some(frame.header())
637 }
638
639 pub fn frame(&self, frame_idx: usize) -> Option<&IndexedFrame> {
647 self.ctx.frame(frame_idx)
648 }
649
650 pub fn frame_offset(&self, frame_index: usize) -> Option<usize> {
652 self.inner.frame_offsets.get(frame_index).copied()
653 }
654
655 #[inline]
657 pub fn pool(&self) -> &JxlThreadPool {
658 &self.pool
659 }
660
661 pub fn reader(&self) -> &ContainerDetectingReader {
663 &self.reader
664 }
665}
666
667impl JxlImage {
669 #[inline]
671 pub fn set_cms(&mut self, cms: impl ColorManagementSystem + Send + Sync + 'static) {
672 self.ctx.set_cms(cms);
673 }
674
675 pub fn request_icc(&mut self, icc_profile: &[u8]) -> Result<()> {
680 self.ctx
681 .request_color_encoding(ColorEncodingWithProfile::with_icc(icc_profile)?);
682 Ok(())
683 }
684
685 pub fn request_color_encoding(&mut self, color_encoding: EnumColourEncoding) {
688 self.ctx
689 .request_color_encoding(ColorEncodingWithProfile::new(color_encoding))
690 }
691}
692
693impl JxlImage {
695 pub fn render_frame(&self, keyframe_index: usize) -> Result<Render> {
697 self.render_frame_cropped(keyframe_index)
698 }
699
700 pub fn render_frame_cropped(&self, keyframe_index: usize) -> Result<Render> {
702 let image = self.ctx.render_keyframe(keyframe_index)?;
703
704 let image_region = self
705 .ctx
706 .image_region()
707 .apply_orientation(&self.image_header);
708 let frame = self.ctx.keyframe(keyframe_index).unwrap();
709 let frame_header = frame.header();
710 let target_frame_region = image_region.translate(-frame_header.x0, -frame_header.y0);
711
712 let is_cmyk = self.ctx.requested_color_encoding().is_cmyk();
713 let result = Render {
714 keyframe_index,
715 name: frame_header.name.clone(),
716 duration: frame_header.duration,
717 orientation: self.image_header.metadata.orientation,
718 image,
719 extra_channels: self.convert_ec_info(),
720 target_frame_region,
721 color_bit_depth: self.image_header.metadata.bit_depth,
722 is_cmyk,
723 render_spot_color: self.render_spot_color,
724 };
725 Ok(result)
726 }
727
728 pub fn render_loading_frame(&mut self) -> Result<Render> {
730 self.render_loading_frame_cropped()
731 }
732
733 pub fn render_loading_frame_cropped(&mut self) -> Result<Render> {
735 let (frame, image) = self.ctx.render_loading_keyframe()?;
736 let frame_header = frame.header();
737 let name = frame_header.name.clone();
738 let duration = frame_header.duration;
739
740 let image_region = self
741 .ctx
742 .image_region()
743 .apply_orientation(&self.image_header);
744 let frame = self
745 .ctx
746 .frame(self.ctx.loaded_frames())
747 .or_else(|| self.ctx.frame(self.ctx.loaded_frames() - 1))
748 .unwrap();
749 let frame_header = frame.header();
750 let target_frame_region = image_region.translate(-frame_header.x0, -frame_header.y0);
751
752 let is_cmyk = self.ctx.requested_color_encoding().is_cmyk();
753 let result = Render {
754 keyframe_index: self.ctx.loaded_keyframes(),
755 name,
756 duration,
757 orientation: self.image_header.metadata.orientation,
758 image,
759 extra_channels: self.convert_ec_info(),
760 target_frame_region,
761 color_bit_depth: self.image_header.metadata.bit_depth,
762 is_cmyk,
763 render_spot_color: self.render_spot_color,
764 };
765 Ok(result)
766 }
767
768 pub fn set_image_region(&mut self, region: CropInfo) -> &mut Self {
772 self.ctx.request_image_region(region.into());
773 self
774 }
775}
776
777impl JxlImage {
779 pub fn jpeg_reconstruction_status(&self) -> JpegReconstructionStatus {
781 match self.inner.aux_boxes.jbrd() {
782 AuxBoxData::Data(jbrd) => {
783 let header = jbrd.header();
784 let Ok(exif) = self.inner.aux_boxes.first_exif() else {
785 return JpegReconstructionStatus::Invalid;
786 };
787 let xml = self.inner.aux_boxes.first_xml();
788
789 if header.expected_icc_len() > 0 {
790 if !self.image_header.metadata.colour_encoding.want_icc() {
791 return JpegReconstructionStatus::Invalid;
792 } else if self.original_icc().is_none() {
793 return JpegReconstructionStatus::NeedMoreData;
794 }
795 }
796 if header.expected_exif_len() > 0 {
797 if exif.is_decoding() {
798 return JpegReconstructionStatus::NeedMoreData;
799 } else if exif.is_not_found() {
800 return JpegReconstructionStatus::Invalid;
801 }
802 }
803 if header.expected_xmp_len() > 0 {
804 if xml.is_decoding() {
805 return JpegReconstructionStatus::NeedMoreData;
806 } else if xml.is_not_found() {
807 return JpegReconstructionStatus::Invalid;
808 }
809 }
810
811 JpegReconstructionStatus::Available
812 }
813 AuxBoxData::Decoding => {
814 if self.num_loaded_frames() >= 2 {
815 return JpegReconstructionStatus::Invalid;
816 }
817 let Some(frame) = self.frame(0) else {
818 return JpegReconstructionStatus::NeedMoreData;
819 };
820 let frame_header = frame.header();
821 if frame_header.encoding != jxl_frame::header::Encoding::VarDct {
822 return JpegReconstructionStatus::Invalid;
823 }
824 if !frame_header.frame_type.is_normal_frame() {
825 return JpegReconstructionStatus::Invalid;
826 }
827 JpegReconstructionStatus::NeedMoreData
828 }
829 AuxBoxData::NotFound => JpegReconstructionStatus::Unavailable,
830 }
831 }
832
833 pub fn reconstruct_jpeg(&self, jpeg_output: impl std::io::Write) -> Result<()> {
841 let aux_boxes = &self.inner.aux_boxes;
842 let jbrd = match aux_boxes.jbrd() {
843 AuxBoxData::Data(jbrd) => jbrd,
844 AuxBoxData::Decoding => {
845 return Err(jxl_jbr::Error::ReconstructionDataIncomplete.into());
846 }
847 AuxBoxData::NotFound => {
848 return Err(jxl_jbr::Error::ReconstructionUnavailable.into());
849 }
850 };
851 if self.num_loaded_frames() == 0 {
852 return Err(jxl_jbr::Error::FrameDataIncomplete.into());
853 }
854
855 let jbrd_header = jbrd.header();
856 let expected_icc_len = jbrd_header.expected_icc_len();
857 let expected_exif_len = jbrd_header.expected_exif_len();
858 let expected_xmp_len = jbrd_header.expected_xmp_len();
859
860 let icc = if expected_icc_len > 0 {
861 self.original_icc().unwrap_or(&[])
862 } else {
863 &[]
864 };
865
866 let exif = if expected_exif_len > 0 {
867 let b = aux_boxes.first_exif()?;
868 b.map(|x| x.payload()).unwrap_or(&[])
869 } else {
870 &[]
871 };
872
873 let xmp = if expected_xmp_len > 0 {
874 aux_boxes.first_xml().unwrap_or(&[])
875 } else {
876 &[]
877 };
878
879 let frame = self.frame(0).unwrap();
880 jbrd.reconstruct(frame, icc, exif, xmp, &self.pool)?
881 .write(jpeg_output)?;
882
883 Ok(())
884 }
885}
886
887impl JxlImage {
889 fn convert_ec_info(&self) -> Vec<ExtraChannel> {
890 self.image_header
891 .metadata
892 .ec_info
893 .iter()
894 .map(|ec_info| ExtraChannel {
895 ty: ec_info.ty,
896 name: ec_info.name.clone(),
897 bit_depth: ec_info.bit_depth,
898 })
899 .collect()
900 }
901}
902
903#[derive(Debug)]
904struct JxlImageInner {
905 end_of_image: bool,
906 buffer: Vec<u8>,
907 buffer_offset: usize,
908 frame_offsets: Vec<usize>,
909 aux_boxes: AuxBoxList,
910}
911
912impl JxlImageInner {
913 fn feed_bytes_inner(&mut self, ctx: &mut RenderContext, mut buf: &[u8]) -> Result<()> {
914 if buf.is_empty() {
915 return Ok(());
916 }
917
918 if self.end_of_image {
919 self.buffer.extend_from_slice(buf);
920 return Ok(());
921 }
922
923 if let Some(loading_frame) = ctx.current_loading_frame() {
924 debug_assert!(self.buffer.is_empty());
925 let len = buf.len();
926 buf = loading_frame.feed_bytes(buf)?;
927 let count = len - buf.len();
928 self.buffer_offset += count;
929
930 if loading_frame.is_loading_done() {
931 let is_last = loading_frame.header().is_last;
932 ctx.finalize_current_frame();
933 if is_last {
934 self.end_of_image = true;
935 self.buffer = buf.to_vec();
936 return Ok(());
937 }
938 }
939 if buf.is_empty() {
940 return Ok(());
941 }
942 }
943
944 self.buffer.extend_from_slice(buf);
945 let mut buf = &*self.buffer;
946 while !buf.is_empty() {
947 let mut bitstream = Bitstream::new(buf);
948 let frame = match ctx.load_frame_header(&mut bitstream) {
949 Ok(x) => x,
950 Err(e) if e.unexpected_eof() => {
951 self.buffer = buf.to_vec();
952 return Ok(());
953 }
954 Err(e) => {
955 return Err(e.into());
956 }
957 };
958 let frame_index = frame.index();
959 assert_eq!(self.frame_offsets.len(), frame_index);
960 self.frame_offsets.push(self.buffer_offset);
961
962 let read_bytes = bitstream.num_read_bits() / 8;
963 buf = &buf[read_bytes..];
964 let len = buf.len();
965 buf = frame.feed_bytes(buf)?;
966 let read_bytes = read_bytes + (len - buf.len());
967 self.buffer_offset += read_bytes;
968
969 if frame.is_loading_done() {
970 let is_last = frame.header().is_last;
971 ctx.finalize_current_frame();
972 if is_last {
973 self.end_of_image = true;
974 self.buffer = buf.to_vec();
975 return Ok(());
976 }
977 }
978 }
979
980 self.buffer.clear();
981 Ok(())
982 }
983}
984
985#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
987pub enum PixelFormat {
988 Gray,
990 Graya,
992 Rgb,
994 Rgba,
996 Cmyk,
998 Cmyka,
1000}
1001
1002impl PixelFormat {
1003 #[inline]
1005 pub fn channels(self) -> usize {
1006 match self {
1007 PixelFormat::Gray => 1,
1008 PixelFormat::Graya => 2,
1009 PixelFormat::Rgb => 3,
1010 PixelFormat::Rgba => 4,
1011 PixelFormat::Cmyk => 4,
1012 PixelFormat::Cmyka => 5,
1013 }
1014 }
1015
1016 #[inline]
1018 pub fn is_grayscale(self) -> bool {
1019 matches!(self, Self::Gray | Self::Graya)
1020 }
1021
1022 #[inline]
1024 pub fn has_alpha(self) -> bool {
1025 matches!(
1026 self,
1027 PixelFormat::Graya | PixelFormat::Rgba | PixelFormat::Cmyka
1028 )
1029 }
1030
1031 #[inline]
1033 pub fn has_black(self) -> bool {
1034 matches!(self, PixelFormat::Cmyk | PixelFormat::Cmyka)
1035 }
1036}
1037
1038#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1040pub enum HdrType {
1041 Pq,
1043 Hlg,
1045}
1046
1047#[derive(Debug)]
1049pub enum LoadResult {
1050 Done(usize),
1052 NeedMoreData,
1054 NoMoreFrames,
1056}
1057
1058#[derive(Debug)]
1060pub enum RenderResult {
1061 Done(Render),
1063 NeedMoreData,
1065 NoMoreFrames,
1067}
1068
1069#[derive(Debug)]
1071pub struct Render {
1072 keyframe_index: usize,
1073 name: Name,
1074 duration: u32,
1075 orientation: u32,
1076 image: Arc<ImageWithRegion>,
1077 extra_channels: Vec<ExtraChannel>,
1078 target_frame_region: Region,
1079 color_bit_depth: BitDepth,
1080 is_cmyk: bool,
1081 render_spot_color: bool,
1082}
1083
1084impl Render {
1085 #[inline]
1087 pub fn keyframe_index(&self) -> usize {
1088 self.keyframe_index
1089 }
1090
1091 #[inline]
1093 pub fn name(&self) -> &str {
1094 &self.name
1095 }
1096
1097 #[inline]
1099 pub fn duration(&self) -> u32 {
1100 self.duration
1101 }
1102
1103 #[inline]
1105 pub fn orientation(&self) -> u32 {
1106 self.orientation
1107 }
1108
1109 pub fn stream(&self) -> ImageStream {
1114 ImageStream::from_render(self, false)
1115 }
1116
1117 pub fn stream_no_alpha(&self) -> ImageStream {
1122 ImageStream::from_render(self, true)
1123 }
1124
1125 #[inline]
1130 pub fn image_all_channels(&self) -> FrameBuffer {
1131 let fb: Vec<_> = self.image.buffer().iter().collect();
1132 let mut bit_depth = vec![self.color_bit_depth; self.image.color_channels()];
1133 for ec in &self.extra_channels {
1134 bit_depth.push(ec.bit_depth);
1135 }
1136 let regions: Vec<_> = self
1137 .image
1138 .regions_and_shifts()
1139 .iter()
1140 .map(|(region, _)| *region)
1141 .collect();
1142
1143 FrameBuffer::from_grids(
1144 &fb,
1145 &bit_depth,
1146 ®ions,
1147 self.target_frame_region,
1148 self.orientation,
1149 )
1150 }
1151
1152 pub fn image_planar(&self) -> Vec<FrameBuffer> {
1156 let grids = self.image.buffer();
1157 let bit_depth_it = std::iter::repeat(self.color_bit_depth)
1158 .take(self.image.color_channels())
1159 .chain(self.extra_channels.iter().map(|ec| ec.bit_depth));
1160 let region_it = self
1161 .image
1162 .regions_and_shifts()
1163 .iter()
1164 .map(|(region, _)| *region);
1165
1166 bit_depth_it
1167 .zip(region_it)
1168 .zip(grids)
1169 .map(|((bit_depth, region), x)| {
1170 FrameBuffer::from_grids(
1171 &[x],
1172 &[bit_depth],
1173 &[region],
1174 self.target_frame_region,
1175 self.orientation,
1176 )
1177 })
1178 .collect()
1179 }
1180
1181 #[inline]
1185 pub fn color_channels(&self) -> &[ImageBuffer] {
1186 let color_channels = self.image.color_channels();
1187 &self.image.buffer()[..color_channels]
1188 }
1189
1190 #[inline]
1194 pub fn extra_channels(&self) -> (&[ExtraChannel], &[ImageBuffer]) {
1195 let color_channels = self.image.color_channels();
1196 (&self.extra_channels, &self.image.buffer()[color_channels..])
1197 }
1198}
1199
1200#[derive(Debug)]
1202pub struct ExtraChannel {
1203 ty: ExtraChannelType,
1204 name: Name,
1205 bit_depth: BitDepth,
1206}
1207
1208impl ExtraChannel {
1209 #[inline]
1211 pub fn ty(&self) -> ExtraChannelType {
1212 self.ty
1213 }
1214
1215 #[inline]
1217 pub fn name(&self) -> &str {
1218 &self.name
1219 }
1220
1221 #[inline]
1223 pub fn is_black(&self) -> bool {
1224 matches!(self.ty, ExtraChannelType::Black)
1225 }
1226
1227 #[inline]
1229 pub fn is_alpha(&self) -> bool {
1230 matches!(self.ty, ExtraChannelType::Alpha { .. })
1231 }
1232
1233 #[inline]
1235 pub fn is_spot_colour(&self) -> bool {
1236 matches!(self.ty, ExtraChannelType::SpotColour { .. })
1237 }
1238}
1239
1240#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
1242pub struct CropInfo {
1243 pub width: u32,
1244 pub height: u32,
1245 pub left: u32,
1246 pub top: u32,
1247}
1248
1249impl From<CropInfo> for jxl_render::Region {
1250 fn from(value: CropInfo) -> Self {
1251 Self {
1252 left: value.left as i32,
1253 top: value.top as i32,
1254 width: value.width,
1255 height: value.height,
1256 }
1257 }
1258}
1259
1260#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1262pub enum JpegReconstructionStatus {
1263 Available,
1265 Invalid,
1268 Unavailable,
1270 NeedMoreData,
1272}