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 force_wide_buffers: bool,
203}
204
205impl JxlImageBuilder {
206 pub fn pool(mut self, pool: JxlThreadPool) -> Self {
208 self.pool = Some(pool);
209 self
210 }
211
212 pub fn alloc_tracker(mut self, tracker: AllocTracker) -> Self {
214 self.tracker = Some(tracker);
215 self
216 }
217
218 pub fn force_wide_buffers(mut self, force_wide_buffers: bool) -> Self {
220 self.force_wide_buffers = force_wide_buffers;
221 self
222 }
223
224 pub fn build_uninit(self) -> UninitializedJxlImage {
226 UninitializedJxlImage {
227 pool: self.pool.unwrap_or_else(default_pool),
228 tracker: self.tracker,
229 reader: ContainerParser::new(),
230 buffer: Vec::new(),
231 aux_boxes: AuxBoxList::new(),
232 force_wide_buffers: self.force_wide_buffers,
233 }
234 }
235
236 pub fn read(self, mut reader: impl std::io::Read) -> Result<JxlImage> {
238 let mut uninit = self.build_uninit();
239 let mut buf = vec![0u8; 4096];
240 let mut buf_valid = 0usize;
241 let mut image = loop {
242 let count = reader.read(&mut buf[buf_valid..])?;
243 if count == 0 {
244 return Err(std::io::Error::new(
245 std::io::ErrorKind::UnexpectedEof,
246 "reader ended before parsing image header",
247 )
248 .into());
249 }
250 buf_valid += count;
251 let consumed = uninit.feed_bytes(&buf[..buf_valid])?;
252 buf.copy_within(consumed..buf_valid, 0);
253 buf_valid -= consumed;
254
255 match uninit.try_init()? {
256 InitializeResult::NeedMoreData(x) => {
257 uninit = x;
258 }
259 InitializeResult::Initialized(x) => {
260 break x;
261 }
262 }
263 };
264
265 while !image.inner.end_of_image {
266 let count = reader.read(&mut buf[buf_valid..])?;
267 if count == 0 {
268 break;
269 }
270 buf_valid += count;
271 let consumed = image.feed_bytes(&buf[..buf_valid])?;
272 buf.copy_within(consumed..buf_valid, 0);
273 buf_valid -= consumed;
274 }
275
276 buf.truncate(buf_valid);
277 image.finalize()?;
278 Ok(image)
279 }
280
281 pub fn open(self, path: impl AsRef<std::path::Path>) -> Result<JxlImage> {
283 let file = std::fs::File::open(path)?;
284 self.read(file)
285 }
286}
287
288pub struct UninitializedJxlImage {
313 pool: JxlThreadPool,
314 tracker: Option<AllocTracker>,
315 reader: ContainerParser,
316 buffer: Vec<u8>,
317 aux_boxes: AuxBoxList,
318 force_wide_buffers: bool,
319}
320
321impl UninitializedJxlImage {
322 pub fn feed_bytes(&mut self, buf: &[u8]) -> Result<usize> {
326 for event in self.reader.feed_bytes(buf) {
327 match event? {
328 ParseEvent::BitstreamKind(_) => {}
329 ParseEvent::Codestream(buf) => {
330 self.buffer.extend_from_slice(buf);
331 }
332 aux_box_event => {
333 self.aux_boxes.handle_event(aux_box_event)?;
334 }
335 }
336 }
337 Ok(self.reader.previous_consumed_bytes())
338 }
339
340 #[inline]
342 pub fn reader(&self) -> &ContainerParser {
343 &self.reader
344 }
345
346 pub fn try_init(mut self) -> Result<InitializeResult> {
354 let mut bitstream = Bitstream::new(&self.buffer);
355 let image_header = match ImageHeader::parse(&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
365 let embedded_icc = if image_header.metadata.colour_encoding.want_icc() {
366 let icc = match jxl_color::icc::read_icc(&mut bitstream) {
367 Ok(x) => x,
368 Err(e) if e.unexpected_eof() => {
369 return Ok(InitializeResult::NeedMoreData(self));
370 }
371 Err(e) => {
372 return Err(e.into());
373 }
374 };
375 tracing::debug!("Image has an embedded ICC profile");
376 let icc = jxl_color::icc::decode_icc(&icc)?;
377 Some(icc)
378 } else {
379 None
380 };
381 bitstream.zero_pad_to_byte()?;
382
383 let image_header = Arc::new(image_header);
384 let skip_bytes = if image_header.metadata.preview.is_some() {
385 let frame = match Frame::parse(
386 &mut bitstream,
387 FrameContext {
388 image_header: image_header.clone(),
389 tracker: self.tracker.as_ref(),
390 pool: self.pool.clone(),
391 },
392 ) {
393 Ok(x) => x,
394 Err(e) if e.unexpected_eof() => {
395 return Ok(InitializeResult::NeedMoreData(self));
396 }
397 Err(e) => {
398 return Err(e.into());
399 }
400 };
401
402 let bytes_read = bitstream.num_read_bits() / 8;
403 let x = frame.toc().total_byte_size();
404 if self.buffer.len() < bytes_read + x {
405 return Ok(InitializeResult::NeedMoreData(self));
406 }
407
408 x
409 } else {
410 0usize
411 };
412
413 let bytes_read = bitstream.num_read_bits() / 8 + skip_bytes;
414 self.buffer.drain(..bytes_read);
415
416 let render_spot_color = !image_header.metadata.grayscale();
417
418 let mut builder = RenderContext::builder().pool(self.pool.clone());
419 if let Some(icc) = embedded_icc {
420 builder = builder.embedded_icc(icc);
421 }
422 if let Some(tracker) = self.tracker {
423 builder = builder.alloc_tracker(tracker);
424 }
425 builder = builder.force_wide_buffers(self.force_wide_buffers);
426 #[cfg_attr(not(any(feature = "lcms2", feature = "moxcms")), allow(unused_mut))]
427 let mut ctx = builder.build(image_header.clone())?;
428 #[cfg(feature = "lcms2")]
429 ctx.set_cms(Lcms2);
430 #[cfg(all(not(feature = "lcms2"), feature = "moxcms"))]
431 ctx.set_cms(Moxcms);
432
433 let mut image = JxlImage {
434 pool: self.pool.clone(),
435 reader: self.reader,
436 image_header,
437 ctx: Box::new(ctx),
438 render_spot_color,
439 inner: JxlImageInner {
440 end_of_image: false,
441 buffer: Vec::new(),
442 buffer_offset: bytes_read,
443 frame_offsets: Vec::new(),
444 aux_boxes: self.aux_boxes,
445 },
446 };
447 image.inner.feed_bytes_inner(&mut image.ctx, &self.buffer)?;
448
449 Ok(InitializeResult::Initialized(image))
450 }
451}
452
453pub enum InitializeResult {
455 NeedMoreData(UninitializedJxlImage),
457 Initialized(JxlImage),
459}
460
461#[derive(Debug)]
463pub struct JxlImage {
464 pool: JxlThreadPool,
465 reader: ContainerParser,
466 image_header: Arc<ImageHeader>,
467 ctx: Box<RenderContext>,
468 render_spot_color: bool,
469 inner: JxlImageInner,
470}
471
472impl JxlImage {
474 pub fn builder() -> JxlImageBuilder {
476 JxlImageBuilder::default()
477 }
478
479 pub fn read_with_defaults(reader: impl std::io::Read) -> Result<JxlImage> {
481 Self::builder().read(reader)
482 }
483
484 pub fn open_with_defaults(path: impl AsRef<std::path::Path>) -> Result<JxlImage> {
486 Self::builder().open(path)
487 }
488
489 pub fn feed_bytes(&mut self, buf: &[u8]) -> Result<usize> {
493 for event in self.reader.feed_bytes(buf) {
494 match event? {
495 ParseEvent::BitstreamKind(_) => {}
496 ParseEvent::Codestream(buf) => {
497 self.inner.feed_bytes_inner(&mut self.ctx, buf)?;
498 }
499 aux_box_event => {
500 self.inner.aux_boxes.handle_event(aux_box_event)?;
501 }
502 }
503 }
504 Ok(self.reader.previous_consumed_bytes())
505 }
506
507 pub fn finalize(&mut self) -> Result<()> {
511 self.inner.aux_boxes.eof()?;
512 Ok(())
513 }
514}
515
516impl JxlImage {
518 #[inline]
520 pub fn image_header(&self) -> &ImageHeader {
521 &self.image_header
522 }
523
524 #[inline]
526 pub fn width(&self) -> u32 {
527 self.image_header.width_with_orientation()
528 }
529
530 #[inline]
532 pub fn height(&self) -> u32 {
533 self.image_header.height_with_orientation()
534 }
535
536 #[inline]
538 pub fn original_icc(&self) -> Option<&[u8]> {
539 self.ctx.embedded_icc()
540 }
541
542 pub fn rendered_icc(&self) -> Vec<u8> {
548 let encoding = self.ctx.requested_color_encoding();
549 match encoding.encoding() {
550 color::ColourEncoding::Enum(encoding) => {
551 jxl_color::icc::colour_encoding_to_icc(encoding)
552 }
553 color::ColourEncoding::IccProfile(_) => encoding.icc_profile().to_vec(),
554 }
555 }
556
557 #[inline]
559 pub fn rendered_cicp(&self) -> Option<[u8; 4]> {
560 let encoding = self.ctx.requested_color_encoding();
561 encoding.encoding().cicp()
562 }
563
564 pub fn pixel_format(&self) -> PixelFormat {
566 let encoding = self.ctx.requested_color_encoding();
567 let is_grayscale = encoding.is_grayscale();
568 let has_black = encoding.is_cmyk();
569 let mut has_alpha = false;
570 for ec_info in &self.image_header.metadata.ec_info {
571 if ec_info.is_alpha() {
572 has_alpha = true;
573 }
574 }
575
576 match (is_grayscale, has_black, has_alpha) {
577 (false, false, false) => PixelFormat::Rgb,
578 (false, false, true) => PixelFormat::Rgba,
579 (false, true, false) => PixelFormat::Cmyk,
580 (false, true, true) => PixelFormat::Cmyka,
581 (true, _, false) => PixelFormat::Gray,
582 (true, _, true) => PixelFormat::Graya,
583 }
584 }
585
586 pub fn hdr_type(&self) -> Option<HdrType> {
590 self.ctx.suggested_hdr_tf().and_then(|tf| match tf {
591 color::TransferFunction::Pq => Some(HdrType::Pq),
592 color::TransferFunction::Hlg => Some(HdrType::Hlg),
593 _ => None,
594 })
595 }
596
597 #[inline]
599 pub fn render_spot_color(&self) -> bool {
600 self.render_spot_color
601 }
602
603 #[inline]
605 pub fn set_render_spot_color(&mut self, render_spot_color: bool) -> &mut Self {
606 if render_spot_color && self.image_header.metadata.grayscale() {
607 tracing::warn!("Spot colour channels are not rendered on grayscale images");
608 return self;
609 }
610 self.render_spot_color = render_spot_color;
611 self
612 }
613
614 pub fn aux_boxes(&self) -> &AuxBoxList {
618 &self.inner.aux_boxes
619 }
620
621 #[inline]
623 pub fn num_loaded_keyframes(&self) -> usize {
624 self.ctx.loaded_keyframes()
625 }
626
627 #[inline]
630 pub fn num_loaded_frames(&self) -> usize {
631 self.ctx.loaded_frames()
632 }
633
634 #[inline]
637 pub fn is_loading_done(&self) -> bool {
638 self.inner.end_of_image
639 }
640
641 pub fn frame_by_keyframe(&self, keyframe_index: usize) -> Option<&IndexedFrame> {
643 self.ctx.keyframe(keyframe_index)
644 }
645
646 pub fn frame_header(&self, keyframe_index: usize) -> Option<&FrameHeader> {
649 let frame = self.ctx.keyframe(keyframe_index)?;
650 Some(frame.header())
651 }
652
653 pub fn frame(&self, frame_idx: usize) -> Option<&IndexedFrame> {
661 self.ctx.frame(frame_idx)
662 }
663
664 pub fn frame_offset(&self, frame_index: usize) -> Option<usize> {
666 self.inner.frame_offsets.get(frame_index).copied()
667 }
668
669 #[inline]
671 pub fn pool(&self) -> &JxlThreadPool {
672 &self.pool
673 }
674
675 pub fn reader(&self) -> &ContainerParser {
677 &self.reader
678 }
679}
680
681impl JxlImage {
683 #[inline]
685 pub fn set_cms(&mut self, cms: impl ColorManagementSystem + Send + Sync + 'static) {
686 self.ctx.set_cms(cms);
687 }
688
689 pub fn request_icc(&mut self, icc_profile: &[u8]) -> Result<()> {
694 self.ctx
695 .request_color_encoding(ColorEncodingWithProfile::with_icc(icc_profile)?);
696 Ok(())
697 }
698
699 pub fn request_color_encoding(&mut self, color_encoding: EnumColourEncoding) {
702 self.ctx
703 .request_color_encoding(ColorEncodingWithProfile::new(color_encoding))
704 }
705}
706
707impl JxlImage {
709 pub fn render_frame(&self, keyframe_index: usize) -> Result<Render> {
711 self.render_frame_cropped(keyframe_index)
712 }
713
714 pub fn render_frame_cropped(&self, keyframe_index: usize) -> Result<Render> {
716 let image = self.ctx.render_keyframe(keyframe_index)?;
717
718 let image_region = self
719 .ctx
720 .image_region()
721 .apply_orientation(&self.image_header);
722 let frame = self.ctx.keyframe(keyframe_index).unwrap();
723 let frame_header = frame.header();
724 let target_frame_region = image_region.translate(-frame_header.x0, -frame_header.y0);
725
726 let is_cmyk = self.ctx.requested_color_encoding().is_cmyk();
727 let result = Render {
728 keyframe_index,
729 name: frame_header.name.clone(),
730 duration: frame_header.duration,
731 orientation: self.image_header.metadata.orientation,
732 image,
733 extra_channels: self.convert_ec_info(),
734 target_frame_region,
735 color_bit_depth: self.image_header.metadata.bit_depth,
736 is_cmyk,
737 render_spot_color: self.render_spot_color,
738 };
739 Ok(result)
740 }
741
742 pub fn render_loading_frame(&mut self) -> Result<Render> {
744 self.render_loading_frame_cropped()
745 }
746
747 pub fn render_loading_frame_cropped(&mut self) -> Result<Render> {
749 let (frame, image) = self.ctx.render_loading_keyframe()?;
750 let frame_header = frame.header();
751 let name = frame_header.name.clone();
752 let duration = frame_header.duration;
753
754 let image_region = self
755 .ctx
756 .image_region()
757 .apply_orientation(&self.image_header);
758 let frame = self
759 .ctx
760 .frame(self.ctx.loaded_frames())
761 .or_else(|| self.ctx.frame(self.ctx.loaded_frames() - 1))
762 .unwrap();
763 let frame_header = frame.header();
764 let target_frame_region = image_region.translate(-frame_header.x0, -frame_header.y0);
765
766 let is_cmyk = self.ctx.requested_color_encoding().is_cmyk();
767 let result = Render {
768 keyframe_index: self.ctx.loaded_keyframes(),
769 name,
770 duration,
771 orientation: self.image_header.metadata.orientation,
772 image,
773 extra_channels: self.convert_ec_info(),
774 target_frame_region,
775 color_bit_depth: self.image_header.metadata.bit_depth,
776 is_cmyk,
777 render_spot_color: self.render_spot_color,
778 };
779 Ok(result)
780 }
781
782 pub fn current_image_region(&self) -> CropInfo {
784 let region = self.ctx.image_region();
785 region.into()
786 }
787
788 pub fn set_image_region(&mut self, region: CropInfo) -> &mut Self {
792 self.ctx.request_image_region(region.into());
793 self
794 }
795}
796
797impl JxlImage {
799 pub fn jpeg_reconstruction_status(&self) -> JpegReconstructionStatus {
801 match self.inner.aux_boxes.jbrd() {
802 AuxBoxData::Data(jbrd) => {
803 let header = jbrd.header();
804 let Ok(exif) = self.inner.aux_boxes.first_exif() else {
805 return JpegReconstructionStatus::Invalid;
806 };
807 let xml = self.inner.aux_boxes.first_xml();
808
809 if header.expected_icc_len() > 0 {
810 if !self.image_header.metadata.colour_encoding.want_icc() {
811 return JpegReconstructionStatus::Invalid;
812 } else if self.original_icc().is_none() {
813 return JpegReconstructionStatus::NeedMoreData;
814 }
815 }
816 if header.expected_exif_len() > 0 {
817 if exif.is_decoding() {
818 return JpegReconstructionStatus::NeedMoreData;
819 } else if exif.is_not_found() {
820 return JpegReconstructionStatus::Invalid;
821 }
822 }
823 if header.expected_xmp_len() > 0 {
824 if xml.is_decoding() {
825 return JpegReconstructionStatus::NeedMoreData;
826 } else if xml.is_not_found() {
827 return JpegReconstructionStatus::Invalid;
828 }
829 }
830
831 JpegReconstructionStatus::Available
832 }
833 AuxBoxData::Decoding => {
834 if self.num_loaded_frames() >= 2 {
835 return JpegReconstructionStatus::Invalid;
836 }
837 let Some(frame) = self.frame(0) else {
838 return JpegReconstructionStatus::NeedMoreData;
839 };
840 let frame_header = frame.header();
841 if frame_header.encoding != jxl_frame::header::Encoding::VarDct {
842 return JpegReconstructionStatus::Invalid;
843 }
844 if !frame_header.frame_type.is_normal_frame() {
845 return JpegReconstructionStatus::Invalid;
846 }
847 JpegReconstructionStatus::NeedMoreData
848 }
849 AuxBoxData::NotFound => JpegReconstructionStatus::Unavailable,
850 }
851 }
852
853 pub fn reconstruct_jpeg(&self, jpeg_output: impl std::io::Write) -> Result<()> {
861 let aux_boxes = &self.inner.aux_boxes;
862 let jbrd = match aux_boxes.jbrd() {
863 AuxBoxData::Data(jbrd) => jbrd,
864 AuxBoxData::Decoding => {
865 return Err(jxl_jbr::Error::ReconstructionDataIncomplete.into());
866 }
867 AuxBoxData::NotFound => {
868 return Err(jxl_jbr::Error::ReconstructionUnavailable.into());
869 }
870 };
871 if self.num_loaded_frames() == 0 {
872 return Err(jxl_jbr::Error::FrameDataIncomplete.into());
873 }
874
875 let jbrd_header = jbrd.header();
876 let expected_icc_len = jbrd_header.expected_icc_len();
877 let expected_exif_len = jbrd_header.expected_exif_len();
878 let expected_xmp_len = jbrd_header.expected_xmp_len();
879
880 let icc = if expected_icc_len > 0 {
881 self.original_icc().unwrap_or(&[])
882 } else {
883 &[]
884 };
885
886 let exif = if expected_exif_len > 0 {
887 let b = aux_boxes.first_exif()?;
888 b.map(|x| x.payload()).unwrap_or(&[])
889 } else {
890 &[]
891 };
892
893 let xmp = if expected_xmp_len > 0 {
894 aux_boxes.first_xml().unwrap_or(&[])
895 } else {
896 &[]
897 };
898
899 let frame = self.frame(0).unwrap();
900 jbrd.reconstruct(frame, icc, exif, xmp, &self.pool)?
901 .write(jpeg_output)?;
902
903 Ok(())
904 }
905}
906
907impl JxlImage {
909 fn convert_ec_info(&self) -> Vec<ExtraChannel> {
910 self.image_header
911 .metadata
912 .ec_info
913 .iter()
914 .map(|ec_info| ExtraChannel {
915 ty: ec_info.ty,
916 name: ec_info.name.clone(),
917 bit_depth: ec_info.bit_depth,
918 })
919 .collect()
920 }
921}
922
923#[derive(Debug)]
924struct JxlImageInner {
925 end_of_image: bool,
926 buffer: Vec<u8>,
927 buffer_offset: usize,
928 frame_offsets: Vec<usize>,
929 aux_boxes: AuxBoxList,
930}
931
932impl JxlImageInner {
933 fn feed_bytes_inner(&mut self, ctx: &mut RenderContext, mut buf: &[u8]) -> Result<()> {
934 if buf.is_empty() {
935 return Ok(());
936 }
937
938 if self.end_of_image {
939 self.buffer.extend_from_slice(buf);
940 return Ok(());
941 }
942
943 if let Some(loading_frame) = ctx.current_loading_frame() {
944 debug_assert!(self.buffer.is_empty());
945 let len = buf.len();
946 buf = loading_frame.feed_bytes(buf)?;
947 let count = len - buf.len();
948 self.buffer_offset += count;
949
950 if loading_frame.is_loading_done() {
951 let is_last = loading_frame.header().is_last;
952 ctx.finalize_current_frame();
953 if is_last {
954 self.end_of_image = true;
955 self.buffer = buf.to_vec();
956 return Ok(());
957 }
958 }
959 if buf.is_empty() {
960 return Ok(());
961 }
962 }
963
964 self.buffer.extend_from_slice(buf);
965 let mut buf = &*self.buffer;
966 while !buf.is_empty() {
967 let mut bitstream = Bitstream::new(buf);
968 let frame = match ctx.load_frame_header(&mut bitstream) {
969 Ok(x) => x,
970 Err(e) if e.unexpected_eof() => {
971 self.buffer = buf.to_vec();
972 return Ok(());
973 }
974 Err(e) => {
975 return Err(e.into());
976 }
977 };
978 let frame_index = frame.index();
979 assert_eq!(self.frame_offsets.len(), frame_index);
980 self.frame_offsets.push(self.buffer_offset);
981
982 let read_bytes = bitstream.num_read_bits() / 8;
983 buf = &buf[read_bytes..];
984 let len = buf.len();
985 buf = frame.feed_bytes(buf)?;
986 let read_bytes = read_bytes + (len - buf.len());
987 self.buffer_offset += read_bytes;
988
989 if frame.is_loading_done() {
990 let is_last = frame.header().is_last;
991 ctx.finalize_current_frame();
992 if is_last {
993 self.end_of_image = true;
994 self.buffer = buf.to_vec();
995 return Ok(());
996 }
997 }
998 }
999
1000 self.buffer.clear();
1001 Ok(())
1002 }
1003}
1004
1005#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1007pub enum PixelFormat {
1008 Gray,
1010 Graya,
1012 Rgb,
1014 Rgba,
1016 Cmyk,
1018 Cmyka,
1020}
1021
1022impl PixelFormat {
1023 #[inline]
1025 pub fn channels(self) -> usize {
1026 match self {
1027 PixelFormat::Gray => 1,
1028 PixelFormat::Graya => 2,
1029 PixelFormat::Rgb => 3,
1030 PixelFormat::Rgba => 4,
1031 PixelFormat::Cmyk => 4,
1032 PixelFormat::Cmyka => 5,
1033 }
1034 }
1035
1036 #[inline]
1038 pub fn is_grayscale(self) -> bool {
1039 matches!(self, Self::Gray | Self::Graya)
1040 }
1041
1042 #[inline]
1044 pub fn has_alpha(self) -> bool {
1045 matches!(
1046 self,
1047 PixelFormat::Graya | PixelFormat::Rgba | PixelFormat::Cmyka
1048 )
1049 }
1050
1051 #[inline]
1053 pub fn has_black(self) -> bool {
1054 matches!(self, PixelFormat::Cmyk | PixelFormat::Cmyka)
1055 }
1056}
1057
1058#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1060pub enum HdrType {
1061 Pq,
1063 Hlg,
1065}
1066
1067#[derive(Debug)]
1069pub enum LoadResult {
1070 Done(usize),
1072 NeedMoreData,
1074 NoMoreFrames,
1076}
1077
1078#[derive(Debug)]
1080pub enum RenderResult {
1081 Done(Render),
1083 NeedMoreData,
1085 NoMoreFrames,
1087}
1088
1089#[derive(Debug)]
1091pub struct Render {
1092 keyframe_index: usize,
1093 name: Name,
1094 duration: u32,
1095 orientation: u32,
1096 image: Arc<ImageWithRegion>,
1097 extra_channels: Vec<ExtraChannel>,
1098 target_frame_region: Region,
1099 color_bit_depth: BitDepth,
1100 is_cmyk: bool,
1101 render_spot_color: bool,
1102}
1103
1104impl Render {
1105 #[inline]
1107 pub fn keyframe_index(&self) -> usize {
1108 self.keyframe_index
1109 }
1110
1111 #[inline]
1113 pub fn name(&self) -> &str {
1114 &self.name
1115 }
1116
1117 #[inline]
1119 pub fn duration(&self) -> u32 {
1120 self.duration
1121 }
1122
1123 #[inline]
1125 pub fn orientation(&self) -> u32 {
1126 self.orientation
1127 }
1128
1129 pub fn stream(&self) -> ImageStream {
1134 ImageStream::from_render(self, false)
1135 }
1136
1137 pub fn stream_no_alpha(&self) -> ImageStream {
1142 ImageStream::from_render(self, true)
1143 }
1144
1145 #[inline]
1150 pub fn image_all_channels(&self) -> FrameBuffer {
1151 let fb: Vec<_> = self.image.buffer().iter().collect();
1152 let mut bit_depth = vec![self.color_bit_depth; self.image.color_channels()];
1153 for ec in &self.extra_channels {
1154 bit_depth.push(ec.bit_depth);
1155 }
1156 let regions: Vec<_> = self
1157 .image
1158 .regions_and_shifts()
1159 .iter()
1160 .map(|(region, _)| *region)
1161 .collect();
1162
1163 FrameBuffer::from_grids(
1164 &fb,
1165 &bit_depth,
1166 ®ions,
1167 self.target_frame_region,
1168 self.orientation,
1169 )
1170 }
1171
1172 pub fn image_planar(&self) -> Vec<FrameBuffer> {
1176 let grids = self.image.buffer();
1177 let bit_depth_it = std::iter::repeat_n(self.color_bit_depth, self.image.color_channels())
1178 .chain(self.extra_channels.iter().map(|ec| ec.bit_depth));
1179 let region_it = self
1180 .image
1181 .regions_and_shifts()
1182 .iter()
1183 .map(|(region, _)| *region);
1184
1185 bit_depth_it
1186 .zip(region_it)
1187 .zip(grids)
1188 .map(|((bit_depth, region), x)| {
1189 FrameBuffer::from_grids(
1190 &[x],
1191 &[bit_depth],
1192 &[region],
1193 self.target_frame_region,
1194 self.orientation,
1195 )
1196 })
1197 .collect()
1198 }
1199
1200 #[inline]
1204 pub fn color_channels(&self) -> &[ImageBuffer] {
1205 let color_channels = self.image.color_channels();
1206 &self.image.buffer()[..color_channels]
1207 }
1208
1209 #[inline]
1213 pub fn extra_channels(&self) -> (&[ExtraChannel], &[ImageBuffer]) {
1214 let color_channels = self.image.color_channels();
1215 (&self.extra_channels, &self.image.buffer()[color_channels..])
1216 }
1217}
1218
1219#[derive(Debug)]
1221pub struct ExtraChannel {
1222 ty: ExtraChannelType,
1223 name: Name,
1224 bit_depth: BitDepth,
1225}
1226
1227impl ExtraChannel {
1228 #[inline]
1230 pub fn ty(&self) -> ExtraChannelType {
1231 self.ty
1232 }
1233
1234 #[inline]
1236 pub fn name(&self) -> &str {
1237 &self.name
1238 }
1239
1240 #[inline]
1242 pub fn is_black(&self) -> bool {
1243 matches!(self.ty, ExtraChannelType::Black)
1244 }
1245
1246 #[inline]
1248 pub fn is_alpha(&self) -> bool {
1249 matches!(self.ty, ExtraChannelType::Alpha { .. })
1250 }
1251
1252 #[inline]
1254 pub fn is_spot_colour(&self) -> bool {
1255 matches!(self.ty, ExtraChannelType::SpotColour { .. })
1256 }
1257}
1258
1259#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
1261pub struct CropInfo {
1262 pub width: u32,
1263 pub height: u32,
1264 pub left: u32,
1265 pub top: u32,
1266}
1267
1268impl From<CropInfo> for jxl_render::Region {
1269 fn from(value: CropInfo) -> Self {
1270 Self {
1271 left: value.left as i32,
1272 top: value.top as i32,
1273 width: value.width,
1274 height: value.height,
1275 }
1276 }
1277}
1278
1279impl From<jxl_render::Region> for CropInfo {
1280 fn from(value: jxl_render::Region) -> Self {
1281 Self {
1282 left: value.left.max(0) as u32,
1283 top: value.top.max(0) as u32,
1284 width: value.width,
1285 height: value.height,
1286 }
1287 }
1288}
1289
1290#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1292pub enum JpegReconstructionStatus {
1293 Available,
1295 Invalid,
1298 Unavailable,
1300 NeedMoreData,
1302}