@@ -641,7 +641,9 @@ def decodeStreamData(stream: Any) -> Union[str, bytes]: # deprecated
641
641
return decode_stream_data (stream )
642
642
643
643
644
- mode_str_type : TypeAlias = Literal ["" , "1" , "RGB" , "P" , "L" , "RGBA" , "CMYK" ]
644
+ mode_str_type : TypeAlias = Literal [
645
+ "" , "1" , "RGB" , "2bits" , "4bits" , "P" , "L" , "RGBA" , "CMYK"
646
+ ]
645
647
646
648
647
649
def _get_imagemode (
@@ -673,6 +675,8 @@ def _get_imagemode(
673
675
674
676
mode_map = {
675
677
"1bit" : "1" , # 0 will be used for 1 bit
678
+ "2bit" : "2bits" , # 2 bits images
679
+ "4bit" : "4bits" , # 4 bits
676
680
"/DeviceGray" : "L" ,
677
681
"palette" : "P" , # reserved for color_components alignment
678
682
"/DeviceRGB" : "RGB" ,
@@ -718,6 +722,24 @@ def _handle_flate(
718
722
Process image encoded in flateEncode
719
723
Returns img, image_format, extension
720
724
"""
725
+
726
+ def bits2byte (data : bytes , size : Tuple [int , int ], bits : int ) -> bytes :
727
+ mask = (2 << bits ) - 1
728
+ nbuff = bytearray (size [0 ] * size [1 ])
729
+ by = 0
730
+ bit = 8 - bits
731
+ for y in range (size [1 ]):
732
+ if (bit != 0 ) and (bit != 8 - bits ):
733
+ by += 1
734
+ bit = 8 - bits
735
+ for x in range (size [0 ]):
736
+ nbuff [y * size [0 ] + x ] = (data [by ] >> bit ) & mask
737
+ bit -= bits
738
+ if bit < 0 :
739
+ by += 1
740
+ bit = 8 - bits
741
+ return bytes (nbuff )
742
+
721
743
extension = ".png" # mime_type = "image/png"
722
744
lookup : Any
723
745
base : Any
@@ -726,6 +748,12 @@ def _handle_flate(
726
748
color_space , base , hival , lookup = (
727
749
value .get_object () for value in color_space
728
750
)
751
+ if mode == "2bits" :
752
+ mode = "P"
753
+ data = bits2byte (data , size , 2 )
754
+ elif mode == "4bits" :
755
+ mode = "P"
756
+ data = bits2byte (data , size , 4 )
729
757
img = Image .frombytes (mode , size , data )
730
758
if color_space == "/Indexed" :
731
759
from .generic import ByteStringObject
@@ -820,8 +848,8 @@ def _handle_jpx(
820
848
):
821
849
# https://pillow.readthedocs.io/en/stable/handbook/concepts.html#modes
822
850
mode : mode_str_type = "RGB"
823
- if x_object_obj .get ("/BitsPerComponent" , 8 ) == 1 :
824
- mode = _get_imagemode ("1bit " , 0 , "" )
851
+ if x_object_obj .get ("/BitsPerComponent" , 8 ) < 8 :
852
+ mode = _get_imagemode (f" { x_object_obj . get ( '/BitsPerComponent' , 8 ) } bit " , 0 , "" )
825
853
else :
826
854
mode = _get_imagemode (
827
855
color_space ,
@@ -842,7 +870,11 @@ def _handle_jpx(
842
870
lfilters = filters [- 1 ] if isinstance (filters , list ) else filters
843
871
if lfilters == FT .FLATE_DECODE :
844
872
img , image_format , extension = _handle_flate (
845
- size , data , mode , color_space , colors
873
+ size ,
874
+ data ,
875
+ mode ,
876
+ color_space ,
877
+ colors ,
846
878
)
847
879
elif lfilters in (FT .LZW_DECODE , FT .ASCII_85_DECODE , FT .CCITT_FAX_DECODE ):
848
880
# I'm not sure if the following logic is correct.
@@ -898,14 +930,6 @@ def _handle_jpx(
898
930
# TODO : implement mask
899
931
if alpha .mode != "L" :
900
932
alpha = alpha .convert ("L" )
901
- scale = x_object_obj [IA .S_MASK ].get ("/Decode" , [0.0 , 1.0 ])
902
- if (scale [1 ] - scale [0 ]) != 1.0 :
903
- alpha = alpha .point (
904
- [
905
- round (255.0 * (v / 255.0 * (scale [1 ] - scale [0 ]) + scale [0 ]))
906
- for v in range (256 )
907
- ]
908
- )
909
933
if img .mode == "P" :
910
934
img = img .convert ("RGB" )
911
935
img .putalpha (alpha )
0 commit comments