Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 40132e7

Browse files
committed
Use indexed color for PNG images in PDF files when possible.
When PNG images have 256 colors or fewer, convert them to index color before saving them in a PDF. This can result in a signifcant reduction in file size.
1 parent ec0132f commit 40132e7

File tree

1 file changed

+41
-8
lines changed

1 file changed

+41
-8
lines changed

lib/matplotlib/backends/backend_pdf.py

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,28 +1521,33 @@ def _unpack(self, im):
15211521
alpha = None
15221522
return rgb, alpha
15231523

1524-
def _writePng(self, data):
1524+
def _writePng(self, img):
15251525
"""
1526-
Write the image *data* into the pdf file using png
1526+
Write the image *img* into the pdf file using png
15271527
predictors with Flate compression.
15281528
"""
15291529
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")
15331531
buffer.seek(8)
1532+
png_data = bit_depth = palette = None
15341533
while True:
15351534
length, type = struct.unpack(b'!L4s', buffer.read(8))
1536-
if type == b'IDAT':
1535+
if type in [b'IHDR', b'PLTE', b'IDAT']:
15371536
data = buffer.read(length)
15381537
if len(data) != length:
15391538
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
15411545
elif type == b'IEND':
15421546
break
15431547
else:
15441548
buffer.seek(length, 1)
15451549
buffer.seek(4, 1) # skip CRC
1550+
return png_data, bit_depth, palette
15461551

15471552
def _writeImg(self, data, id, smask=None):
15481553
"""
@@ -1561,6 +1566,34 @@ def _writeImg(self, data, id, smask=None):
15611566
if smask:
15621567
obj['SMask'] = smask
15631568
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)
15641597
png = {'Predictor': 10, 'Colors': colors, 'Columns': width}
15651598
else:
15661599
png = None
@@ -1571,7 +1604,7 @@ def _writeImg(self, data, id, smask=None):
15711604
png=png
15721605
)
15731606
if png:
1574-
self._writePng(data)
1607+
self.currentstream.write(data)
15751608
else:
15761609
self.currentstream.write(data.tobytes())
15771610
self.endStream()

0 commit comments

Comments
 (0)