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