@@ -25,9 +25,7 @@ def _accept(prefix: bytes) -> bool | str:
2525class JpegXlImageFile (ImageFile .ImageFile ):
2626 format = "JPEG XL"
2727 format_description = "JPEG XL image"
28- __loaded = - 1
29- __logical_frame = 0
30- __physical_frame = 0
28+ __frame = 0
3129
3230 def _open (self ) -> None :
3331 self ._decoder = _jpegxl .JpegXlDecoder (self .fp .read ())
@@ -39,13 +37,15 @@ def _open(self) -> None:
3937 tps_num ,
4038 tps_denom ,
4139 self .info ["loop" ],
40+ tps_duration ,
4241 ) = self ._decoder .get_info ()
4342
4443 self ._n_frames = None if self .is_animated else 1
4544 self ._tps_dur_secs = tps_num / tps_denom if tps_denom != 0 else 1
45+ self .info ["duration" ] = 1000 * tps_duration * (1 / self ._tps_dur_secs )
4646
4747 # TODO: handle libjxl time codes
48- self .__timestamp = 0
48+ self .info [ "timestamp" ] = 0
4949
5050 if icc := self ._decoder .get_icc ():
5151 self .info ["icc_profile" ] = icc
@@ -57,6 +57,7 @@ def _open(self) -> None:
5757 self .info ["exif" ] = exif [exif_start_offset + 4 :]
5858 if xmp := self ._decoder .get_xmp ():
5959 self .info ["xmp" ] = xmp
60+ self .tile = [ImageFile ._Tile ("raw" , (0 , 0 ) + self .size , 0 , self .mode )]
6061
6162 @property
6263 def n_frames (self ) -> int :
@@ -67,72 +68,51 @@ def n_frames(self) -> int:
6768
6869 return self ._n_frames
6970
70- def _get_next (self ) -> tuple [bytes , float , float ]:
71- # Get next frame
72- next_frame = self ._decoder .get_next ()
73- self .__physical_frame += 1
71+ def _get_next (self ) -> bytes :
72+ data , tps_duration , is_last = self ._decoder .get_next ()
7473
75- # this actually means EOF, errors are raised in _jxl
76- if next_frame is None :
77- msg = "failed to decode next frame in JXL file"
78- raise EOFError (msg )
79-
80- data , tps_duration , is_last = next_frame
8174 if is_last and self ._n_frames is None :
82- # libjxl said this frame is the last one
83- self ._n_frames = self .__physical_frame
75+ self ._n_frames = self .__frame
8476
8577 # duration in milliseconds
86- duration = 1000 * tps_duration * (1 / self ._tps_dur_secs )
87- timestamp = self .__timestamp
88- self .__timestamp += duration
89-
90- return data , timestamp , duration
78+ self .info ["timestamp" ] += self .info ["duration" ]
79+ self .info ["duration" ] = 1000 * tps_duration * (1 / self ._tps_dur_secs )
9180
92- def _seek (self , frame : int ) -> None :
93- if frame == self .__physical_frame :
94- return # Nothing to do
95- if frame < self .__physical_frame :
96- # also rewind libjxl decoder instance
97- self ._decoder .rewind ()
98- self .__physical_frame = 0
99- self .__loaded = - 1
100- self .__timestamp = 0
101-
102- while self .__physical_frame < frame :
103- self ._get_next () # Advance to the requested frame
81+ return data
10482
10583 def seek (self , frame : int ) -> None :
106- if self ._n_frames is None :
107- self .n_frames
10884 if not self ._seek_check (frame ):
10985 return
11086
111- # Set logical frame to requested position
112- self .__logical_frame = frame
87+ if frame < self .__frame :
88+ self .__frame = 0
89+ self ._decoder .rewind ()
90+ self .info ["timestamp" ] = 0
11391
114- def load (self ) -> Image .core .PixelAccess | None :
115- if self .__loaded != self .__logical_frame :
116- self ._seek (self .__logical_frame )
92+ while self .__frame < frame :
93+ self .__frame += 1
94+ self ._get_next ()
95+ if self ._n_frames is not None and self ._n_frames < frame :
96+ msg = "no more images in JPEG XL file"
97+ raise EOFError (msg )
11798
118- data , self .info ["timestamp" ], self .info ["duration" ] = self ._get_next ()
119- self .__loaded = self .__logical_frame
99+ self .tile = [ImageFile ._Tile ("raw" , (0 , 0 ) + self .size , 0 , self .mode )]
100+
101+ def load (self ) -> Image .core .PixelAccess | None :
102+ if self .tile :
103+ data = self ._get_next ()
120104
121- # Set tile
122105 if self .fp and self ._exclusive_fp :
123106 self .fp .close ()
124- # this is horribly memory inefficient
125- # you need probably 2*(raw image plane) bytes of memory
126107 self .fp = BytesIO (data )
127- self .tile = [ImageFile ._Tile ("raw" , (0 , 0 ) + self .size , 0 , self .mode )]
128108
129109 return super ().load ()
130110
131111 def load_seek (self , pos : int ) -> None :
132112 pass
133113
134114 def tell (self ) -> int :
135- return self .__logical_frame
115+ return self .__frame
136116
137117
138118Image .register_open (JpegXlImageFile .format , JpegXlImageFile , _accept )
0 commit comments