mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 01:46:18 +03:00
Merge pull request #667 from wiredfool/hugovk_pdf
PDF Tests and Fixes merge
This commit is contained in:
commit
97ee7938cb
82
PIL/Image.py
82
PIL/Image.py
|
@ -30,6 +30,7 @@ from PIL import VERSION, PILLOW_VERSION, _plugins
|
|||
|
||||
import warnings
|
||||
|
||||
|
||||
class _imaging_not_installed:
|
||||
# module placeholder
|
||||
def __getattr__(self, id):
|
||||
|
@ -91,10 +92,13 @@ except ImportError:
|
|||
builtins = __builtin__
|
||||
|
||||
from PIL import ImageMode
|
||||
from PIL._binary import i8, o8
|
||||
from PIL._util import isPath, isStringType, deferred_error
|
||||
from PIL._binary import i8
|
||||
from PIL._util import isPath
|
||||
from PIL._util import isStringType
|
||||
from PIL._util import deferred_error
|
||||
|
||||
import os, sys
|
||||
import os
|
||||
import sys
|
||||
|
||||
# type stuff
|
||||
import collections
|
||||
|
@ -108,6 +112,7 @@ try:
|
|||
except:
|
||||
HAS_CFFI = False
|
||||
|
||||
|
||||
def isImageType(t):
|
||||
"""
|
||||
Checks if an object is an image object.
|
||||
|
@ -248,6 +253,7 @@ _MODE_CONV = {
|
|||
"I;32LS": ('<i4', None),
|
||||
}
|
||||
|
||||
|
||||
def _conv_type_shape(im):
|
||||
shape = im.size[1], im.size[0]
|
||||
typ, extra = _MODE_CONV[im.mode]
|
||||
|
@ -379,6 +385,7 @@ def init():
|
|||
_initialized = 2
|
||||
return 1
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Codec factories (used by tobytes/frombytes and ImageFile.load)
|
||||
|
||||
|
@ -398,6 +405,7 @@ def _getdecoder(mode, decoder_name, args, extra=()):
|
|||
except AttributeError:
|
||||
raise IOError("decoder %s not available" % decoder_name)
|
||||
|
||||
|
||||
def _getencoder(mode, encoder_name, args, extra=()):
|
||||
|
||||
# tweak arguments
|
||||
|
@ -421,14 +429,18 @@ def _getencoder(mode, encoder_name, args, extra=()):
|
|||
def coerce_e(value):
|
||||
return value if isinstance(value, _E) else _E(value)
|
||||
|
||||
|
||||
class _E:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def __add__(self, other):
|
||||
return _E((self.data, "__add__", coerce_e(other).data))
|
||||
|
||||
def __mul__(self, other):
|
||||
return _E((self.data, "__mul__", coerce_e(other).data))
|
||||
|
||||
|
||||
def _getscaleoffset(expr):
|
||||
stub = ["stub"]
|
||||
data = expr(_E(stub)).data
|
||||
|
@ -438,13 +450,15 @@ def _getscaleoffset(expr):
|
|||
return c, 0.0
|
||||
if (a is stub and b == "__add__" and isinstance(c, numbers.Number)):
|
||||
return 1.0, c
|
||||
except TypeError: pass
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
((a, b, c), d, e) = data # full syntax
|
||||
if (a is stub and b == "__mul__" and isinstance(c, numbers.Number) and
|
||||
d == "__add__" and isinstance(e, numbers.Number)):
|
||||
return c, e
|
||||
except TypeError: pass
|
||||
except TypeError:
|
||||
pass
|
||||
raise ValueError("illegal expression")
|
||||
|
||||
|
||||
|
@ -500,6 +514,7 @@ class Image:
|
|||
# Context Manager Support
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
|
@ -525,7 +540,6 @@ class Image:
|
|||
# object is gone.
|
||||
self.im = deferred_error(ValueError("Operation on closed image"))
|
||||
|
||||
|
||||
def _copy(self):
|
||||
self.load()
|
||||
self.im = self.im.copy()
|
||||
|
@ -533,7 +547,8 @@ class Image:
|
|||
self.readonly = 0
|
||||
|
||||
def _dump(self, file=None, format=None):
|
||||
import tempfile, os
|
||||
import os
|
||||
import tempfile
|
||||
suffix = ''
|
||||
if format:
|
||||
suffix = '.'+format
|
||||
|
@ -663,7 +678,9 @@ class Image:
|
|||
|
||||
.. deprecated:: 2.0
|
||||
"""
|
||||
warnings.warn('fromstring() is deprecated. Please call frombytes() instead.', DeprecationWarning)
|
||||
warnings.warn(
|
||||
'fromstring() is deprecated. Please call frombytes() instead.',
|
||||
DeprecationWarning)
|
||||
return self.frombytes(*args, **kw)
|
||||
|
||||
def load(self):
|
||||
|
@ -802,6 +819,7 @@ class Image:
|
|||
# after quantization.
|
||||
trns_im = trns_im.convert('RGB')
|
||||
trns = trns_im.getpixel((0,0))
|
||||
|
||||
elif self.mode == 'P' and mode == 'RGBA':
|
||||
delete_trns = True
|
||||
|
||||
|
@ -1071,7 +1089,6 @@ class Image:
|
|||
self.load()
|
||||
return self.im.ptr
|
||||
|
||||
|
||||
def getpalette(self):
|
||||
"""
|
||||
Returns the image palette as a list.
|
||||
|
@ -1089,7 +1106,6 @@ class Image:
|
|||
except ValueError:
|
||||
return None # no palette
|
||||
|
||||
|
||||
def getpixel(self, xy):
|
||||
"""
|
||||
Returns the pixel value at a given position.
|
||||
|
@ -1210,7 +1226,8 @@ class Image:
|
|||
|
||||
if isImageType(box) and mask is None:
|
||||
# abbreviated paste(im, mask) syntax
|
||||
mask = box; box = None
|
||||
mask = box
|
||||
box = None
|
||||
|
||||
if box is None:
|
||||
# cover all of self
|
||||
|
@ -1493,6 +1510,7 @@ class Image:
|
|||
math.cos(angle), math.sin(angle), 0.0,
|
||||
-math.sin(angle), math.cos(angle), 0.0
|
||||
]
|
||||
|
||||
def transform(x, y, matrix=matrix):
|
||||
(a, b, c, d, e, f) = matrix
|
||||
return a*x + b*y + c, d*x + e*y + f
|
||||
|
@ -1668,7 +1686,7 @@ class Image:
|
|||
"""
|
||||
return 0
|
||||
|
||||
def thumbnail(self, size, resample=NEAREST):
|
||||
def thumbnail(self, size, resample=ANTIALIAS):
|
||||
"""
|
||||
Make this image into a thumbnail. This method modifies the
|
||||
image to contain a thumbnail version of itself, no larger than
|
||||
|
@ -1683,26 +1701,27 @@ class Image:
|
|||
important than quality.
|
||||
|
||||
Also note that this function modifies the :py:class:`~PIL.Image.Image`
|
||||
object in place. If you need to use the full resolution image as well, apply
|
||||
this method to a :py:meth:`~PIL.Image.Image.copy` of the original image.
|
||||
object in place. If you need to use the full resolution image as well,
|
||||
apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original
|
||||
image.
|
||||
|
||||
:param size: Requested size.
|
||||
:param resample: Optional resampling filter. This can be one
|
||||
of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
|
||||
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS`
|
||||
(best quality). If omitted, it defaults to
|
||||
:py:attr:`PIL.Image.NEAREST` (this will be changed to ANTIALIAS in a
|
||||
future version).
|
||||
:py:attr:`PIL.Image.ANTIALIAS`.
|
||||
:returns: None
|
||||
"""
|
||||
|
||||
# FIXME: the default resampling filter will be changed
|
||||
# to ANTIALIAS in future versions
|
||||
|
||||
# preserve aspect ratio
|
||||
x, y = self.size
|
||||
if x > size[0]: y = int(max(y * size[0] / x, 1)); x = int(size[0])
|
||||
if y > size[1]: x = int(max(x * size[1] / y, 1)); y = int(size[1])
|
||||
if x > size[0]:
|
||||
y = int(max(y * size[0] / x, 1))
|
||||
x = int(size[0])
|
||||
if y > size[1]:
|
||||
x = int(max(x * size[1] / y, 1))
|
||||
y = int(size[1])
|
||||
size = x, y
|
||||
|
||||
if size == self.size:
|
||||
|
@ -1753,7 +1772,9 @@ class Image:
|
|||
"""
|
||||
|
||||
if self.mode == 'RGBA':
|
||||
return self.convert('RGBa').transform(size, method, data, resample, fill).convert('RGBA')
|
||||
return self.convert('RGBa') \
|
||||
.transform(size, method, data, resample, fill) \
|
||||
.convert('RGBA')
|
||||
|
||||
if isinstance(method, ImageTransformHandler):
|
||||
return method.transform(size, self, resample=resample, fill=fill)
|
||||
|
@ -1800,8 +1821,13 @@ class Image:
|
|||
elif method == QUAD:
|
||||
# quadrilateral warp. data specifies the four corners
|
||||
# given as NW, SW, SE, and NE.
|
||||
nw = data[0:2]; sw = data[2:4]; se = data[4:6]; ne = data[6:8]
|
||||
x0, y0 = nw; As = 1.0 / w; At = 1.0 / h
|
||||
nw = data[0:2]
|
||||
sw = data[2:4]
|
||||
se = data[4:6]
|
||||
ne = data[6:8]
|
||||
x0, y0 = nw
|
||||
As = 1.0 / w
|
||||
At = 1.0 / h
|
||||
data = (x0, (ne[0]-x0)*As, (sw[0]-x0)*At,
|
||||
(se[0]-sw[0]-ne[0]+x0)*As*At,
|
||||
y0, (ne[1]-y0)*As, (sw[1]-y0)*At,
|
||||
|
@ -1835,6 +1861,7 @@ class Image:
|
|||
im = self.im.transpose(method)
|
||||
return self._new(im)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Lazy operations
|
||||
|
||||
|
@ -1870,6 +1897,7 @@ class _ImageCrop(Image):
|
|||
# FIXME: future versions should optimize crop/paste
|
||||
# sequences!
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Abstract handlers.
|
||||
|
||||
|
@ -1877,10 +1905,12 @@ class ImagePointHandler:
|
|||
# used as a mixin by point transforms (for use with im.point)
|
||||
pass
|
||||
|
||||
|
||||
class ImageTransformHandler:
|
||||
# used as a mixin by geometry transforms (for use with im.transform)
|
||||
pass
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Factories
|
||||
|
||||
|
@ -1956,6 +1986,7 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
|
|||
im.frombytes(data, decoder_name, args)
|
||||
return im
|
||||
|
||||
|
||||
def fromstring(*args, **kw):
|
||||
"""Deprecated alias to frombytes.
|
||||
|
||||
|
@ -2160,6 +2191,7 @@ def open(fp, mode="r"):
|
|||
raise IOError("cannot identify image file %r"
|
||||
% (filename if filename else fp))
|
||||
|
||||
|
||||
#
|
||||
# Image processing.
|
||||
|
||||
|
@ -2258,6 +2290,7 @@ def merge(mode, bands):
|
|||
im.putband(bands[i].im, i)
|
||||
return bands[0]._new(im)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Plugin registry
|
||||
|
||||
|
@ -2316,6 +2349,7 @@ def _show(image, **options):
|
|||
# override me, as necessary
|
||||
_showxv(image, **options)
|
||||
|
||||
|
||||
def _showxv(image, title=None, **options):
|
||||
from PIL import ImageShow
|
||||
ImageShow.show(image, title, **options)
|
||||
|
|
|
@ -46,9 +46,11 @@ def _obj(fp, obj, **dict):
|
|||
fp.write("/%s %s\n" % (k, v))
|
||||
fp.write(">>\n")
|
||||
|
||||
|
||||
def _endobj(fp):
|
||||
fp.write("endobj\n")
|
||||
|
||||
|
||||
##
|
||||
# (Internal) Image save plugin for the PDF format.
|
||||
|
||||
|
@ -64,8 +66,10 @@ def _save(im, fp, filename):
|
|||
class TextWriter:
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.fp, name)
|
||||
|
||||
def write(self, value):
|
||||
self.fp.write(value.encode('latin-1'))
|
||||
|
||||
|
@ -105,7 +109,7 @@ def _save(im, fp, filename):
|
|||
g = i8(palette[i*3+1])
|
||||
b = i8(palette[i*3+2])
|
||||
colorspace = colorspace + "%02x%02x%02x " % (r, g, b)
|
||||
colorspace = colorspace + b"> ]"
|
||||
colorspace = colorspace + "> ]"
|
||||
procset = "/ImageI" # indexed color
|
||||
elif im.mode == "RGB":
|
||||
filter = "/DCTDecode"
|
||||
|
@ -122,7 +126,9 @@ def _save(im, fp, filename):
|
|||
# catalogue
|
||||
|
||||
xref[1] = fp.tell()
|
||||
_obj(fp, 1, Type = "/Catalog",
|
||||
_obj(
|
||||
fp, 1,
|
||||
Type="/Catalog",
|
||||
Pages="2 0 R")
|
||||
_endobj(fp)
|
||||
|
||||
|
@ -130,7 +136,9 @@ def _save(im, fp, filename):
|
|||
# pages
|
||||
|
||||
xref[2] = fp.tell()
|
||||
_obj(fp, 2, Type = "/Pages",
|
||||
_obj(
|
||||
fp, 2,
|
||||
Type="/Pages",
|
||||
Count=1,
|
||||
Kids="[4 0 R]")
|
||||
_endobj(fp)
|
||||
|
@ -144,7 +152,7 @@ def _save(im, fp, filename):
|
|||
if bits == 1:
|
||||
# FIXME: the hex encoder doesn't support packed 1-bit
|
||||
# images; do things the hard way...
|
||||
data = im.tostring("raw", "1")
|
||||
data = im.tobytes("raw", "1")
|
||||
im = Image.new("L", (len(data), 1), None)
|
||||
im.putdata(data)
|
||||
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
|
||||
|
@ -158,7 +166,9 @@ def _save(im, fp, filename):
|
|||
raise ValueError("unsupported PDF filter (%s)" % filter)
|
||||
|
||||
xref[3] = fp.tell()
|
||||
_obj(fp, 3, Type = "/XObject",
|
||||
_obj(
|
||||
fp, 3,
|
||||
Type="/XObject",
|
||||
Subtype="/Image",
|
||||
Width=width, # * 72.0 / resolution,
|
||||
Height=height, # * 72.0 / resolution,
|
||||
|
@ -179,11 +189,14 @@ def _save(im, fp, filename):
|
|||
|
||||
xref[4] = fp.tell()
|
||||
_obj(fp, 4)
|
||||
fp.write("<<\n/Type /Page\n/Parent 2 0 R\n"\
|
||||
"/Resources <<\n/ProcSet [ /PDF %s ]\n"\
|
||||
"/XObject << /image 3 0 R >>\n>>\n"\
|
||||
"/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" %\
|
||||
(procset, int(width * 72.0 /resolution) , int(height * 72.0 / resolution)))
|
||||
fp.write(
|
||||
"<<\n/Type /Page\n/Parent 2 0 R\n"
|
||||
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
|
||||
"/XObject << /image 3 0 R >>\n>>\n"
|
||||
"/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" % (
|
||||
procset,
|
||||
int(width * 72.0 / resolution),
|
||||
int(height * 72.0 / resolution)))
|
||||
_endobj(fp)
|
||||
|
||||
#
|
||||
|
@ -191,7 +204,10 @@ def _save(im, fp, filename):
|
|||
|
||||
op = TextWriter(io.BytesIO())
|
||||
|
||||
op.write("q %d 0 0 %d 0 0 cm /image Do Q\n" % (int(width * 72.0 / resolution), int(height * 72.0 / resolution)))
|
||||
op.write(
|
||||
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
|
||||
int(width * 72.0 / resolution),
|
||||
int(height * 72.0 / resolution)))
|
||||
|
||||
xref[5] = fp.tell()
|
||||
_obj(fp, 5, Length=len(op.fp.getvalue()))
|
||||
|
|
58
Tests/test_file_pdf.py
Normal file
58
Tests/test_file_pdf.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from tester import *
|
||||
import os.path
|
||||
|
||||
|
||||
def helper_save_as_pdf(mode):
|
||||
# Arrange
|
||||
im = lena(mode)
|
||||
outfile = tempfile("temp_" + mode + ".pdf")
|
||||
|
||||
# Act
|
||||
im.save(outfile)
|
||||
|
||||
# Assert
|
||||
assert_true(os.path.isfile(outfile))
|
||||
assert_greater(os.path.getsize(outfile), 0)
|
||||
|
||||
|
||||
def test_monochrome():
|
||||
# Arrange
|
||||
mode = "1"
|
||||
|
||||
# Act / Assert
|
||||
helper_save_as_pdf(mode)
|
||||
|
||||
|
||||
def test_greyscale():
|
||||
# Arrange
|
||||
mode = "L"
|
||||
|
||||
# Act / Assert
|
||||
helper_save_as_pdf(mode)
|
||||
|
||||
|
||||
def test_rgb():
|
||||
# Arrange
|
||||
mode = "RGB"
|
||||
|
||||
# Act / Assert
|
||||
helper_save_as_pdf(mode)
|
||||
|
||||
|
||||
def test_p_mode():
|
||||
# Arrange
|
||||
mode = "P"
|
||||
|
||||
# Act / Assert
|
||||
helper_save_as_pdf(mode)
|
||||
|
||||
|
||||
def test_cmyk_mode():
|
||||
# Arrange
|
||||
mode = "CMYK"
|
||||
|
||||
# Act / Assert
|
||||
helper_save_as_pdf(mode)
|
||||
|
||||
|
||||
# End of file
|
Loading…
Reference in New Issue
Block a user