Synxis
Synxis

Reputation: 9388

Keep original image data when saving to pdf

I have plots that I annotate using images:

def add_image(axe, filename, position, zoom):
    img = plt.imread(filename)
    off_img = matplotlib.offsetbox.OffsetImage(img, zoom = zoom, resample = False)
    art = matplotlib.offsetbox.AnnotationBbox(off_img, position, xybox = (0, 0),
        xycoords = axe.transAxes, boxcoords = "offset points", frameon = False)
    axe.add_artist(art)

Then I save the figure to some pdf file, say fig.pdf. I expect the exact original image to be embedded in the resulting pdf, without resampling. However, the image is resampled according to the dpi parameter of savefig().

How can I force matplotlib to NOT resample the image (there is no point in doing that for a vector output anyway) ?

For more details, here is a simple example, using this image as image.png:

import numpy as np
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt

def add_image(axe, filename, position, zoom):
    img = plt.imread(filename)
    off_img = matplotlib.offsetbox.OffsetImage(img, zoom = zoom, resample = False)
    art = matplotlib.offsetbox.AnnotationBbox(off_img, position, xybox = (0, 0),
        xycoords = axe.transAxes, boxcoords = "offset points", frameon = False)
    axe.add_artist(art)

# ==========
fig = plt.figure()
axe = plt.axes()
fig.set_size_inches(3, 1.5)
axe.plot(np.arange(10), np.arange(10))

add_image(axe, "image.png", position = (0.2, 0.7), zoom = 0.07)

fig.savefig("temp.pdf", bbox_inches = "tight", pad_inches = 0)

Expected result:

expected

Actual result:

actual

EDIT: There is a bug/feature issue for this question

Upvotes: 1

Views: 292

Answers (1)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339610

Just a quick summary of the discussion in https://github.com/matplotlib/matplotlib/issues/16268:

  • Passing the image through without resampling is indeed a desireable feature, mostly because for vector output, it should really be up to the renderer (e.g. pdf viewer, printer etc.) to determine the resolution.

  • The fact that matplotlib currently does not allow for this is mostly an oversight.

  • A workaround solution (a quick hack) is to add the following code before producing the figure:

    from matplotlib.backends.backend_mixed import MixedModeRenderer
    
    def _check_unsampled_image(self, renderer):
        if isinstance(renderer, MixedModeRenderer):
            return True
        else:
            return False
    
    matplotlib.image.BboxImage._check_unsampled_image = _check_unsampled_image
    

    This is not meant to be used in production code though, and a more robust solution needs to be implemented in a future matplotlib version. Contributions are welcome.

Upvotes: 2

Related Questions