@@ -1521,28 +1521,33 @@ def _unpack(self, im):
1521
1521
alpha = None
1522
1522
return rgb , alpha
1523
1523
1524
- def _writePng (self , data ):
1524
+ def _writePng (self , img ):
1525
1525
"""
1526
- Write the image *data * into the pdf file using png
1526
+ Write the image *img * into the pdf file using png
1527
1527
predictors with Flate compression.
1528
1528
"""
1529
1529
buffer = BytesIO ()
1530
- if data .shape [- 1 ] == 1 :
1531
- data = data .squeeze (axis = - 1 )
1532
- Image .fromarray (data ).save (buffer , format = "png" )
1530
+ img .save (buffer , format = "png" )
1533
1531
buffer .seek (8 )
1532
+ png_data = bit_depth = palette = None
1534
1533
while True :
1535
1534
length , type = struct .unpack (b'!L4s' , buffer .read (8 ))
1536
- if type == b' IDAT' :
1535
+ if type in [ b'IHDR' , b'PLTE' , b' IDAT'] :
1537
1536
data = buffer .read (length )
1538
1537
if len (data ) != length :
1539
1538
raise RuntimeError ("truncated data" )
1540
- self .currentstream .write (data )
1539
+ if type == b'IHDR' :
1540
+ bit_depth = int (data [8 ])
1541
+ elif type == b'PLTE' :
1542
+ palette = data
1543
+ elif type == b'IDAT' :
1544
+ png_data = data
1541
1545
elif type == b'IEND' :
1542
1546
break
1543
1547
else :
1544
1548
buffer .seek (length , 1 )
1545
1549
buffer .seek (4 , 1 ) # skip CRC
1550
+ return png_data , bit_depth , palette
1546
1551
1547
1552
def _writeImg (self , data , id , smask = None ):
1548
1553
"""
@@ -1561,6 +1566,34 @@ def _writeImg(self, data, id, smask=None):
1561
1566
if smask :
1562
1567
obj ['SMask' ] = smask
1563
1568
if mpl .rcParams ['pdf.compression' ]:
1569
+ if data .shape [- 1 ] == 1 :
1570
+ data = data .squeeze (axis = - 1 )
1571
+ img = Image .fromarray (data )
1572
+ img_colors = img .getcolors (maxcolors = 256 )
1573
+ if img_colors is not None :
1574
+ # Convert to indexed color if there are 256 colors or fewer
1575
+ # This can significantly reduce the file size
1576
+ num_colors = len (img_colors )
1577
+ img = img .convert (mode = 'P' , dither = Image .NONE ,
1578
+ palette = Image .ADAPTIVE , colors = num_colors )
1579
+ data , bit_depth , palette = self ._writePng (img )
1580
+ if bit_depth is None or palette is None :
1581
+ raise RuntimeError ("invalid PNG header" )
1582
+ palette = palette [:num_colors * 3 ] # Trim padding
1583
+ if colors == 1 :
1584
+ # The PNG format uses an RGB palette for all indexed color
1585
+ # images, but the PDF format allows for grayscale palettes.
1586
+ # Thus, we convert the palette.
1587
+ palette = palette [::3 ]
1588
+ palette = pdfRepr (palette )
1589
+ colorspace = obj ['ColorSpace' ].pdfRepr ()
1590
+ obj ['ColorSpace' ] = Verbatim (b'[/Indexed ' + colorspace + b' '
1591
+ + str (num_colors - 1 ).encode ()
1592
+ + b' ' + palette + b']' )
1593
+ obj ['BitsPerComponent' ] = bit_depth
1594
+ colors = 1
1595
+ else :
1596
+ data , _ , _ = self ._writePng (img )
1564
1597
png = {'Predictor' : 10 , 'Colors' : colors , 'Columns' : width }
1565
1598
else :
1566
1599
png = None
@@ -1571,7 +1604,7 @@ def _writeImg(self, data, id, smask=None):
1571
1604
png = png
1572
1605
)
1573
1606
if png :
1574
- self ._writePng (data )
1607
+ self .currentstream . write (data )
1575
1608
else :
1576
1609
self .currentstream .write (data .tobytes ())
1577
1610
self .endStream ()
0 commit comments