3333else :
3434 jpeg = TurboJPEG ()
3535
36+ # Register Pillow openers for formats TurboJPEG/OpenCV can't decode (AVIF, HEIF/HEIC).
37+ # register_heif_opener() covers both HEIF and AVIF. Optional dependency: if pillow-heif
38+ # isn't installed we simply skip the fallback and behave as before.
39+ try :
40+ import pillow_heif
41+
42+ pillow_heif .register_heif_opener ()
43+ _PILLOW_FALLBACK = True
44+ except Exception :
45+ _PILLOW_FALLBACK = False
46+
3647settings = Settings ()
3748def_headers = settings .defaults .img_req_headers
3849
@@ -178,6 +189,34 @@ def b64_to_bytes(b64encoded, b64_decode=True, **kwargs):
178189 return __bin , None
179190
180191
192+ def decode_with_pillow (im_bytes ):
193+ """
194+ Fallback decoder for formats TurboJPEG and OpenCV can't handle (e.g. AVIF, HEIF/HEIC).
195+ Requires pillow-heif; returns None if it's unavailable or decoding fails.
196+
197+ Args:
198+ im_bytes (np.ndarray): The image bytes to be decoded.
199+
200+ Returns:
201+ np.ndarray: The decoded image in BGR order, or None on failure.
202+ """
203+ if not _PILLOW_FALLBACK :
204+ return None
205+ try :
206+ from PIL import Image , ImageOps
207+
208+ raw = im_bytes .tobytes () if isinstance (im_bytes , np .ndarray ) else im_bytes
209+ with Image .open (io .BytesIO (raw )) as img :
210+ img = ImageOps .exif_transpose (img ) # honor EXIF orientation
211+ img = img .convert ('RGB' )
212+ arr = np .asarray (img )
213+ # Convert RGB -> BGR to match TurboJPEG/cv2 output ordering.
214+ return cv2 .cvtColor (arr , cv2 .COLOR_RGB2BGR )
215+ except Exception :
216+ logger .debug ('Pillow decode failed' , exc_info = True )
217+ return None
218+
219+
181220def decode_img_bytes (im_bytes , ** kwargs ):
182221 """
183222 Decodes the image bytes and returns it as a numpy array.
@@ -197,6 +236,9 @@ def decode_img_bytes(im_bytes, **kwargs):
197236 except :
198237 logger .debug ('JPEG decoder failed, fallback to cv2.imdecode' )
199238 _image = cv2 .imdecode (im_bytes , cv2 .IMREAD_COLOR )
239+ if _image is None :
240+ logger .debug ('cv2.imdecode failed, fallback to Pillow (AVIF/HEIF)' )
241+ _image = decode_with_pillow (im_bytes )
200242 t1 = time .perf_counter ()
201243 logger .debug (f'Decoding took: { (t1 - t0 ) * 1000 :.3f} ms.' )
202244 return _image
0 commit comments