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

Skip to content

Commit c6e84d4

Browse files
committed
pdf: Use explicit palette when saving indexed images
Asking Pillow for an "adaptive palette" does not appear to guarantee that the chosen colours will be the same, even if asking for exactly the same number as exist in the image. Instead, create an explicit palette, and quantize using it. Additionally, since now the palette may be smaller than 256 colours, Pillow may choose to encode the image data with fewer than 8 bits per component, so we need to properly reflect that in the decode parameters (this was already done for the image parameters). The effect on test images with _many_ colours is small, with a maximum RMS of 1.024, but for images with few colours, the result can be completely wrong as in the reported #25806.
1 parent 5a89bdb commit c6e84d4

21 files changed

+15
-21
lines changed

lib/matplotlib/backends/backend_pdf.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1730,39 +1730,38 @@ def _writeImg(self, data, id, smask=None):
17301730
'Subtype': Name('Image'),
17311731
'Width': width,
17321732
'Height': height,
1733-
'ColorSpace': Name({1: 'DeviceGray',
1734-
3: 'DeviceRGB'}[color_channels]),
1733+
'ColorSpace': Name({1: 'DeviceGray', 3: 'DeviceRGB'}[color_channels]),
17351734
'BitsPerComponent': 8}
17361735
if smask:
17371736
obj['SMask'] = smask
17381737
if mpl.rcParams['pdf.compression']:
17391738
if data.shape[-1] == 1:
17401739
data = data.squeeze(axis=-1)
1740+
png = {'Predictor': 10, 'Colors': color_channels, 'Columns': width}
17411741
img = Image.fromarray(data)
17421742
img_colors = img.getcolors(maxcolors=256)
17431743
if color_channels == 3 and img_colors is not None:
17441744
# Convert to indexed color if there are 256 colors or fewer
17451745
# This can significantly reduce the file size
17461746
num_colors = len(img_colors)
1747-
# These constants were converted to IntEnums and deprecated in
1748-
# Pillow 9.2
1747+
palette_img = Image.new('P', (1, 1))
1748+
palette_img.putpalette([component
1749+
for _, color in img_colors
1750+
for component in color])
1751+
# This constant was converted to IntEnum and deprecated in Pillow 9.2.
17491752
dither = getattr(Image, 'Dither', Image).NONE
1750-
pmode = getattr(Image, 'Palette', Image).ADAPTIVE
1751-
img = img.convert(
1752-
mode='P', dither=dither, palette=pmode, colors=num_colors
1753-
)
1753+
img = img.quantize(dither=dither, palette=palette_img)
17541754
png_data, bit_depth, palette = self._writePng(img)
17551755
if bit_depth is None or palette is None:
17561756
raise RuntimeError("invalid PNG header")
1757-
palette = palette[:num_colors * 3] # Trim padding
1758-
obj['ColorSpace'] = Verbatim(
1759-
b'[/Indexed /DeviceRGB %d %s]'
1760-
% (num_colors - 1, pdfRepr(palette)))
1757+
palette = palette[:num_colors * 3] # Trim padding; remove for Pillow>=9
1758+
obj['ColorSpace'] = [Name('Indexed'), Name('DeviceRGB'),
1759+
num_colors - 1, palette]
17611760
obj['BitsPerComponent'] = bit_depth
1762-
color_channels = 1
1761+
png['Colors'] = 1
1762+
png['BitsPerComponent'] = bit_depth
17631763
else:
17641764
png_data, _, _ = self._writePng(img)
1765-
png = {'Predictor': 10, 'Colors': color_channels, 'Columns': width}
17661765
else:
17671766
png = None
17681767
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_image.py

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

756756

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

lib/matplotlib/tests/test_streamplot.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ def test_startpoints():
3434
plt.plot(start_x, start_y, 'ok')
3535

3636

37-
@image_comparison(['streamplot_colormap'], remove_text=True, style='mpl20',
38-
tol=0.022)
37+
@image_comparison(['streamplot_colormap'], remove_text=True, style='mpl20')
3938
def test_colormap():
4039
X, Y, U, V = velocity_field()
4140
plt.streamplot(X, Y, U, V, color=U, density=0.6, linewidth=2,

0 commit comments

Comments
 (0)