使用 Python`s PIL,如何在加载图像之前设置 DPI?

With Python`s PIL, how to set DPI before loading an image?

我试图使用 PIL 打开一个 (Illustrator) .eps 文件,进行一些更改并保存。我想在打开、创建或解释对象之前将文档设置为 300 dpi,并将颜色模式设置为 cmyk

首先,我用 PythonMagick 做了同样的尝试,结果是这样的:

import PythonMagick
# That's NOT what I want
img72 = PythonMagick.Image()
img_file = 'epstest.eps'
img.read(img_file)
img_dens = img72.density()
print 'W: %d, H: %d' % (img.size().width(), img.size().height())
# W: 403, H: 2475 <-- See here
print 'Density Width: %r' % img_dens.width() # 72
print 'Density Height: %r' % img_dens.height() # 72

# THAT is what I want
img300 = PythonMagick.Image()
img_file = 'epstest.eps'
img300.density('300')      # set density / resolution
img300.read(img_file)      # opens with defined density
img_dens = img300.density()
print 'W: %d, H: %d' % (img.size().width(), img.size().height())
# W: 1679, H: 10312 <-- See here!
print 'Density Width: %r' % img_dens.width() # 300
print 'Density Height: %r' % img_dens.height() # 300

PythonMagick 的问题:转换颜色模式不起作用,所以我尝试了 PIL,我更喜欢:

from PIL import Image

img = Image.open('epstest.eps')

我知道可以在保存的时候设置dpi。

没有的东西:

img = Image() # TypeError: 'module' object is not callable
img = Image.new() # TypeError: new() takes at least 2 arguments (0 given)
# .new() would create a different object anyway..
img = Image.open('epstest.eps', dpi = 300)
img = Image.open('epstest.eps', dpi = (300, 300) )
# After opening an Image
img.load(dpi=(300,300))

关于 输入:我的 .eps 文件 - 如果用 72dpi 解释(似乎是 PIL 默认值)它最终为 403x2475 像素,300dpi 应该是 1677x10311像素。此外,.eps 文件不包含预览位图,也不包含任何位图数据。只有 2 种颜色(黑色和白色),普通矢量。制作一个包含大量分色 .eps 文件的目录会很有用。

关于输出:将是一个 png。

解决方案:

非常感谢 Paulo - 这是他的解决方案,改动非常小:

from PIL import Image
from PIL import EpsImagePlugin
import math
filename = 'epstest.eps'
def open_eps(filename, dpi=300.0):
    img = Image.open(filename)
    original = [float(d) for d in img.size]
    # scale = width / original[0] # calculated wrong height
    scale = dpi/72.0            # this fixed it
    if dpi is not 0:
        img.load(scale = math.ceil(scale))
    if scale != 1:
        img.thumbnail([round(scale * d) for d in original], Image.ANTIALIAS)
    return img

img = open_eps(filename, dpi=300.0)
img.save('pil_test.png', dpi=(300.0, 300.0))

据我所知,虽然它可以包含嵌入的位图和预览缩略图,但 EPS 是一种基于矢量的格式。如果您以位图格式生成输出,则设置 DPI 才有意义。

You are right - I am trying to generate a bitmap picture from the eps. But opening (parsing?) an .eps-file with a certain resolution determines the actual pixel-size (given a certain document size). PythonMagick does this right but i would like to use PIL if possible. – OP

这是因为 PythonMagick 中的 EPS 驱动程序将 EPS 转换为输入的位图表示(记住 IM,底层库,是一个 'raster image processor')——而在 PIL 中,EPS 驱动程序也可以写入 EPS 图像.

参见 ImageMagick 中的“A word about Vector Image formats”:

Why is this important? Because IM is a 'raster image processor', and while it can read or write images stored in one of the vector formats it does so by converting the image to and from a internal raster image. Consequently if you are trying to convert a image from a vector format, to another vector format, IM will essentially rasterize this image at the currently defined resolution or density which will hopefully (but unlikely) be suitable for the output device you intend to use it on. In other words, any output from IM will never be a true vector format. While it can convert its internal raster format into a vector format file, the result is only a superficial vector image wrapper around an image in raster format. And unless the raster image is defined properly (at the right resolution) for the output device, the result will not be particularly good. Unfortunately new uses to IM do not know anything about this. They see IM as a converter that can convert say PDF to Postscript, producing images with 'blocky' aliasing effects, 'washed out' colors, or blurry images that just do not look good at all, on the intended output device. Which brings use to what I am trying to say... Avoid using ImageMagick for 'Vector Image' to 'Vector Image' conversions EG: converting between formats like: PDF, PS, SVG In other words, use the right tool for the right job. And for this situation, ImageMagick is not the right tool.

另请参阅有关 EPS on PIL 的注释:

PIL identifies EPS files containing image data, and can read files that contain embedded raster images (ImageData descriptors). If Ghostscript is available, other EPS files can be read as well. The EPS driver can also write EPS images.

[更新 1]

PIL 文档中缺少来自 Pillow docs 的信息:

If Ghostscript is available, you can call the load() method with the following parameter to affect how Ghostscript renders the EPS

scale

Affects the scale of the resultant rasterized image. If the EPS suggests that the image be rendered at 100px x 100px, setting this parameter to 2 will make the Ghostscript render a 200px x 200px image instead. The relative position of the bounding box is maintained:

im = Image.open(...)
im.size #(100,100)
im.load(scale=2)
im.size #(200,200)

[更新 2]

与我最初的猜测相反,PIL 还对图像进行了光栅化。当我保存为 EPS 时,它只是在位图周围做了一个包装。根据 OP,在他的环境中默认分辨率似乎是 72 ppi。

如果您知道默认分辨率是 72 ppi(每英寸像素),计算您想要的任何密度的比例是一个简单的比例问题 - 给定 r 作为您想要的分辨率,s 是规模:1 : s = 72 : r 因此:

im.load(scale=300.0/72.0)

如果您只指定所需的宽度而不是分辨率可能是最好的 - 例如,如果您想要它的宽度为 1677 像素:

def open_eps(filename, width=None):
    original_width = float(Image.open(filename).size[0])
    im = Image.open(filename)
    if width is not None:
        im.load(scale=width/original_width)
    return im

im = open_eps('testfile.eps', 1677)

所以最后的答案是:虽然没有内置参数在加载EPS文件时以ppi指定所需的分辨率,但您可以使用scale参数以您想要的任何分辨率加载它。如果您足够关心,我想 Pillow 维护者会很高兴为此收到 PR。

[编辑 3]

Paolo, the way is good, but it looks like scale is only accepting plain integers... 4,166666667 (300.0/72.0) is rounded to 4.

我很遗憾没有测试。

def open_eps(filename, width=None):
    original = [float(d) for d in Image.open(filename).size]
    scale = width / original[0]
    im = Image.open(filename)
    if width is not None:
        im.load(scale=math.ceil(scale))
    if scale != 1:
        im.thumbnail([int(scale * d) for d in original], Image.ANTIALIAS)
    return im

im = open_eps('testfile.eps', 1677)

不确定我是否应该使用 math.round 而不是 int 但你明白了。