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

Skip to content

Commit 349849b

Browse files
authored
Merge pull request #26215 from meeseeksmachine/auto-backport-of-pr-25824-on-v3.7.x
Backport PR #25824 on branch v3.7.x (pdf: Use explicit palette when saving indexed images)
2 parents 8d42212 + 399ebe1 commit 349849b

21 files changed

+46
-22
lines changed

lib/matplotlib/backends/backend_pdf.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,39 +1757,43 @@ def _writeImg(self, data, id, smask=None):
17571757
'Subtype': Name('Image'),
17581758
'Width': width,
17591759
'Height': height,
1760-
'ColorSpace': Name({1: 'DeviceGray',
1761-
3: 'DeviceRGB'}[color_channels]),
1760+
'ColorSpace': Name({1: 'DeviceGray', 3: 'DeviceRGB'}[color_channels]),
17621761
'BitsPerComponent': 8}
17631762
if smask:
17641763
obj['SMask'] = smask
17651764
if mpl.rcParams['pdf.compression']:
17661765
if data.shape[-1] == 1:
17671766
data = data.squeeze(axis=-1)
1767+
png = {'Predictor': 10, 'Colors': color_channels, 'Columns': width}
17681768
img = Image.fromarray(data)
17691769
img_colors = img.getcolors(maxcolors=256)
17701770
if color_channels == 3 and img_colors is not None:
1771-
# Convert to indexed color if there are 256 colors or fewer
1772-
# This can significantly reduce the file size
1771+
# Convert to indexed color if there are 256 colors or fewer. This can
1772+
# significantly reduce the file size.
17731773
num_colors = len(img_colors)
1774-
# These constants were converted to IntEnums and deprecated in
1775-
# Pillow 9.2
1776-
dither = getattr(Image, 'Dither', Image).NONE
1777-
pmode = getattr(Image, 'Palette', Image).ADAPTIVE
1778-
img = img.convert(
1779-
mode='P', dither=dither, palette=pmode, colors=num_colors
1780-
)
1774+
palette = np.array([comp for _, color in img_colors for comp in color],
1775+
dtype=np.uint8)
1776+
palette24 = ((palette[0::3].astype(np.uint32) << 16) |
1777+
(palette[1::3].astype(np.uint32) << 8) |
1778+
palette[2::3])
1779+
rgb24 = ((data[:, :, 0].astype(np.uint32) << 16) |
1780+
(data[:, :, 1].astype(np.uint32) << 8) |
1781+
data[:, :, 2])
1782+
indices = np.argsort(palette24).astype(np.uint8)
1783+
rgb8 = indices[np.searchsorted(palette24, rgb24, sorter=indices)]
1784+
img = Image.fromarray(rgb8, mode='P')
1785+
img.putpalette(palette)
17811786
png_data, bit_depth, palette = self._writePng(img)
17821787
if bit_depth is None or palette is None:
17831788
raise RuntimeError("invalid PNG header")
1784-
palette = palette[:num_colors * 3] # Trim padding
1785-
obj['ColorSpace'] = Verbatim(
1786-
b'[/Indexed /DeviceRGB %d %s]'
1787-
% (num_colors - 1, pdfRepr(palette)))
1789+
palette = palette[:num_colors * 3] # Trim padding; remove for Pillow>=9
1790+
obj['ColorSpace'] = [Name('Indexed'), Name('DeviceRGB'),
1791+
num_colors - 1, palette]
17881792
obj['BitsPerComponent'] = bit_depth
1789-
color_channels = 1
1793+
png['Colors'] = 1
1794+
png['BitsPerComponent'] = bit_depth
17901795
else:
17911796
png_data, _, _ = self._writePng(img)
1792-
png = {'Predictor': 10, 'Colors': color_channels, 'Columns': width}
17931797
else:
17941798
png = None
17951799
self.beginStream(
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

lib/matplotlib/tests/test_backend_pdf.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,30 @@ def test_composite_image():
131131
assert len(pdf._file._images) == 2
132132

133133

134+
def test_indexed_image():
135+
# An image with low color count should compress to a palette-indexed format.
136+
pikepdf = pytest.importorskip('pikepdf')
137+
138+
data = np.zeros((256, 1, 3), dtype=np.uint8)
139+
data[:, 0, 0] = np.arange(256) # Maximum unique colours for an indexed image.
140+
141+
rcParams['pdf.compression'] = True
142+
fig = plt.figure()
143+
fig.figimage(data, resize=True)
144+
buf = io.BytesIO()
145+
fig.savefig(buf, format='pdf', dpi='figure')
146+
147+
with pikepdf.Pdf.open(buf) as pdf:
148+
page, = pdf.pages
149+
image, = page.images.values()
150+
pdf_image = pikepdf.PdfImage(image)
151+
assert pdf_image.indexed
152+
pil_image = pdf_image.as_pil_image()
153+
rgb = np.asarray(pil_image.convert('RGB'))
154+
155+
np.testing.assert_array_equal(data, rgb)
156+
157+
134158
def test_savefig_metadata(monkeypatch):
135159
pikepdf = pytest.importorskip('pikepdf')
136160
monkeypatch.setenv('SOURCE_DATE_EPOCH', '0')

lib/matplotlib/tests/test_image.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -753,11 +753,7 @@ def test_log_scale_image():
753753
ax.set(yscale='log')
754754

755755

756-
# Increased tolerance is needed for PDF test to avoid failure. After the PDF
757-
# backend was modified to use indexed color, there are ten pixels that differ
758-
# due to how the subpixel calculation is done when converting the PDF files to
759-
# PNG images.
760-
@image_comparison(['rotate_image'], remove_text=True, tol=0.35)
756+
@image_comparison(['rotate_image'], remove_text=True)
761757
def test_rotate_image():
762758
delta = 0.25
763759
x = y = np.arange(-3.0, 3.0, delta)

0 commit comments

Comments
 (0)