diff --git a/.travis.yml b/.travis.yml index 88118b1b2..fdc920ac2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ python: - 3.2 - 3.3 -install: "sudo apt-get -qq install libfreetype6-dev liblcms2-dev libwebp-dev" +install: "sudo apt-get -qq install libfreetype6-dev liblcms2-dev libwebp-dev ghostscript" script: - python setup.py clean diff --git a/CHANGES.rst b/CHANGES.rst index 56d763432..cab4dffad 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,36 @@ Changelog (Pillow) 2.3.0 (2014-01-01) ------------------ +- Quote filenames and title before using on command line + [tmccombs] + +- Fixed Viewer.show to return properly + [tmccombs] + +- Documentation fixes + [wiredfool] + +- Fixed memory leak saving images as webp when webpmux is available + [cezarsa] + +- Fix compiling with FreeType 2.5.1 + [stromnov] + +- Adds directories for NetBSD. + [deepy] + +- Support RGBA TIFF with missing ExtraSamples tag + [cgohlke] + +- Lossless WEBP Support + [wiredfool] + +- Take compression as an option in the save call for tiffs + [wiredfool] + +- Add support for saving lossless WebP. Just pass 'lossless=True' to save() + [liftoff] + - LCMS support upgraded from version 1 to version 2, fixes #343 [wiredfool] diff --git a/PIL/EpsImagePlugin.py b/PIL/EpsImagePlugin.py index bc0ed43c5..a8706b05f 100644 --- a/PIL/EpsImagePlugin.py +++ b/PIL/EpsImagePlugin.py @@ -50,14 +50,22 @@ if sys.platform.startswith('win'): else: gs_windows_binary = False -def Ghostscript(tile, size, fp): +def Ghostscript(tile, size, fp, scale=1): """Render an image using Ghostscript""" # Unpack decoder tile decoder, tile, offset, data = tile[0] length, bbox = data - import tempfile, os + #Hack to support hi-res rendering + scale = int(scale) or 1 + orig_size = size + orig_bbox = bbox + size = (size[0] * scale, size[1] * scale) + bbox = [bbox[0], bbox[1], bbox[2] * scale, bbox[3] * scale] + #print("Ghostscript", scale, size, orig_size, bbox, orig_bbox) + + import tempfile, os, subprocess file = tempfile.mktemp() @@ -65,33 +73,32 @@ def Ghostscript(tile, size, fp): command = ["gs", "-q", # quite mode "-g%dx%d" % size, # set output geometry (pixels) + "-r%d" % (72*scale), # set input DPI (dots per inch) "-dNOPAUSE -dSAFER", # don't pause between pages, safe mode "-sDEVICE=ppmraw", # ppm driver "-sOutputFile=%s" % file,# output file - "- >/dev/null 2>/dev/null"] + ] if gs_windows_binary is not None: if gs_windows_binary is False: raise WindowsError('Unable to locate Ghostscript on paths') command[0] = gs_windows_binary - command[-1] = '- >nul 2>nul' - - command = " ".join(command) # push data through ghostscript try: - gs = os.popen(command, "w") + gs = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) # adjust for image origin if bbox[0] != 0 or bbox[1] != 0: - gs.write("%d %d translate\n" % (-bbox[0], -bbox[1])) + gs.stdin.write(("%d %d translate\n" % (-bbox[0], -bbox[1])).encode('ascii')) fp.seek(offset) while length > 0: s = fp.read(8192) if not s: break length = length - len(s) - gs.write(s) - status = gs.close() + gs.stdin.write(s) + gs.stdin.close() + status = gs.wait() if status: raise IOError("gs failed (status %d)" % status) im = Image.core.open_ppm(file) @@ -304,11 +311,11 @@ class EpsImageFile(ImageFile.ImageFile): if not box: raise IOError("cannot determine EPS bounding box") - def load(self): + def load(self, scale=1): # Load EPS via Ghostscript if not self.tile: return - self.im = Ghostscript(self.tile, self.size, self.fp) + self.im = Ghostscript(self.tile, self.size, self.fp, scale) self.mode = self.im.mode self.size = self.im.size self.tile = [] diff --git a/PIL/Image.py b/PIL/Image.py index e1d88fe59..cf5c4f477 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -475,7 +475,7 @@ class Image: new.mode = im.mode new.size = im.size new.palette = self.palette - if im.mode == "P": + if im.mode == "P" and not new.palette: from PIL import ImagePalette new.palette = ImagePalette.ImagePalette() try: @@ -675,15 +675,18 @@ class Image: L = R * 299/1000 + G * 587/1000 + B * 114/1000 - When translating a greyscale image into a bilevel image (mode - "1"), all non-zero values are set to 255 (white). To use other - thresholds, use the :py:meth:`~PIL.Image.Image.point` method. + The default method of converting a greyscale ("L") or "RGB" + image into a bilevel (mode "1") image uses Floyd-Steinberg + dither to approximate the original image luminosity levels. If + dither is NONE, all non-zero values are set to 255 (white). To + use other thresholds, use the :py:meth:`~PIL.Image.Image.point` + method. :param mode: The requested mode. :param matrix: An optional conversion matrix. If given, this should be 4- or 16-tuple containing floating point values. :param dither: Dithering method, used when converting from - mode "RGB" to "P". + mode "RGB" to "P" or from "RGB" or "L" to "1". Available methods are NONE or FLOYDSTEINBERG (default). :param palette: Palette to use when converting from mode "RGB" to "P". Available palettes are WEB or ADAPTIVE. diff --git a/PIL/ImageShow.py b/PIL/ImageShow.py index 7e3d63ba3..6ed913c8d 100644 --- a/PIL/ImageShow.py +++ b/PIL/ImageShow.py @@ -17,6 +17,11 @@ from __future__ import print_function from PIL import Image import os, sys +if(sys.version_info >= (3, 3)): + from shlex import quote +else: + from pipes import quote + _viewers = [] def register(viewer, order=1): @@ -65,7 +70,7 @@ class Viewer: if base != image.mode and image.mode != "1": image = image.convert(base) - self.show_image(image, **options) + return self.show_image(image, **options) # hook methods @@ -99,7 +104,7 @@ if sys.platform == "win32": format = "BMP" def get_command(self, file, **options): return ("start /wait %s && ping -n 2 127.0.0.1 >NUL " - "&& del /f %s" % (file, file)) + "&& del /f %s" % (quote(file), quote(file))) register(WindowsViewer) @@ -111,7 +116,7 @@ elif sys.platform == "darwin": # on darwin open returns immediately resulting in the temp # file removal while app is opening command = "open -a /Applications/Preview.app" - command = "(%s %s; sleep 20; rm -f %s)&" % (command, file, file) + command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file), quote(file)) return command register(MacViewer) @@ -134,7 +139,7 @@ else: class UnixViewer(Viewer): def show_file(self, file, **options): command, executable = self.get_command_ex(file, **options) - command = "(%s %s; rm -f %s)&" % (command, file, file) + command = "(%s %s; rm -f %s)&" % (command, quote(file), quote(file)) os.system(command) return 1 @@ -154,8 +159,7 @@ else: # imagemagick's display command instead. command = executable = "xv" if title: - # FIXME: do full escaping - command = command + " -name \"%s\"" % title + command = command + " -name %s" % quote(title) return command, executable if which("xv"): diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 410e29303..57480a361 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -156,6 +156,7 @@ OPEN_INFO = { (II, 1, 3, 1, (32,), ()): ("F", "F;32F"), (II, 2, 1, 1, (8,8,8), ()): ("RGB", "RGB"), (II, 2, 1, 2, (8,8,8), ()): ("RGB", "RGB;R"), + (II, 2, 1, 1, (8,8,8,8), ()): ("RGBA", "RGBA"), # missing ExtraSamples (II, 2, 1, 1, (8,8,8,8), (0,)): ("RGBX", "RGBX"), (II, 2, 1, 1, (8,8,8,8), (1,)): ("RGBA", "RGBa"), (II, 2, 1, 1, (8,8,8,8), (2,)): ("RGBA", "RGBA"), @@ -973,7 +974,7 @@ def _save(im, fp, filename): ifd = ImageFileDirectory(prefix) - compression = im.info.get('compression','raw') + compression = im.encoderinfo.get('compression',im.info.get('compression','raw')) libtiff = compression in ["tiff_ccitt", "group3", "group4", "tiff_jpeg", "tiff_adobe_deflate", "tiff_thunderscan", "tiff_deflate", diff --git a/PIL/WebPImagePlugin.py b/PIL/WebPImagePlugin.py index ef37e301c..90e2b540e 100644 --- a/PIL/WebPImagePlugin.py +++ b/PIL/WebPImagePlugin.py @@ -12,6 +12,7 @@ _VALID_WEBP_MODES = { _VP8_MODES_BY_IDENTIFIER = { b"VP8 ": "RGB", b"VP8X": "RGBA", + b"VP8L": "RGBA", # lossless } @@ -48,6 +49,7 @@ def _save(im, fp, filename): if im.mode not in _VALID_WEBP_MODES: raise IOError("cannot write mode %s as WEBP" % image_mode) + lossless = im.encoderinfo.get("lossless", False) quality = im.encoderinfo.get("quality", 80) icc_profile = im.encoderinfo.get("icc_profile", "") exif = im.encoderinfo.get("exif", "") @@ -56,6 +58,7 @@ def _save(im, fp, filename): im.tobytes(), im.size[0], im.size[1], + lossless, float(quality), im.mode, icc_profile, diff --git a/Tests/images/create_eps.gnuplot b/Tests/images/create_eps.gnuplot new file mode 100644 index 000000000..4d7e29877 --- /dev/null +++ b/Tests/images/create_eps.gnuplot @@ -0,0 +1,30 @@ +#!/usr/bin/gnuplot + +#This is the script that was used to create our sample EPS files +#We used the following version of the gnuplot program +#G N U P L O T +#Version 4.6 patchlevel 3 last modified 2013-04-12 +#Build System: Darwin x86_64 + +#This file will generate the non_zero_bb.eps variant, in order to get the +#zero_bb.eps variant you will need to edit line6 in the result file to +#be "%%BoundingBox: 0 0 460 352" instead of "%%BoundingBox: 50 50 410 302" + +set t postscript eps color +set o "sample.eps" +set dummy u,v +set key bmargin center horizontal Right noreverse enhanced autotitles nobox +set parametric +set view 50, 30, 1, 1 +set isosamples 10, 10 +set hidden3d back offset 1 trianglepattern 3 undefined 1 altdiagonal bentover +set ticslevel 0 +set title "Interlocking Tori" + +set style line 1 lt 1 lw 1 pt 3 lc rgb "red" +set style line 2 lt 1 lw 1 pt 3 lc rgb "blue" + +set urange [ -3.14159 : 3.14159 ] noreverse nowriteback +set vrange [ -3.14159 : 3.14159 ] noreverse nowriteback +splot cos(u)+.5*cos(u)*cos(v),sin(u)+.5*sin(u)*cos(v),.5*sin(v) ls 1,\ + 1+cos(u)+.5*cos(u)*cos(v),.5*sin(v),sin(u)+.5*sin(u)*cos(v) ls 2 diff --git a/Tests/images/non_zero_bb.eps b/Tests/images/non_zero_bb.eps new file mode 100644 index 000000000..750a44b38 Binary files /dev/null and b/Tests/images/non_zero_bb.eps differ diff --git a/Tests/images/non_zero_bb.png b/Tests/images/non_zero_bb.png new file mode 100644 index 000000000..156c9a091 Binary files /dev/null and b/Tests/images/non_zero_bb.png differ diff --git a/Tests/images/non_zero_bb_scale2.png b/Tests/images/non_zero_bb_scale2.png new file mode 100644 index 000000000..2600580b3 Binary files /dev/null and b/Tests/images/non_zero_bb_scale2.png differ diff --git a/Tests/images/zero_bb.eps b/Tests/images/zero_bb.eps new file mode 100644 index 000000000..e931bf833 Binary files /dev/null and b/Tests/images/zero_bb.eps differ diff --git a/Tests/images/zero_bb.png b/Tests/images/zero_bb.png new file mode 100644 index 000000000..7d02a5814 Binary files /dev/null and b/Tests/images/zero_bb.png differ diff --git a/Tests/images/zero_bb_scale2.png b/Tests/images/zero_bb_scale2.png new file mode 100644 index 000000000..81c9d056d Binary files /dev/null and b/Tests/images/zero_bb_scale2.png differ diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py new file mode 100644 index 000000000..75570f944 --- /dev/null +++ b/Tests/test_file_eps.py @@ -0,0 +1,82 @@ +from tester import * + +from PIL import Image + +#Our two EPS test files (they are identical except for their bounding boxes) +file1 = "Tests/images/zero_bb.eps" +file2 = "Tests/images/non_zero_bb.eps" + +#Due to palletization, we'll need to convert these to RGB after load +file1_compare = "Tests/images/zero_bb.png" +file1_compare_scale2 = "Tests/images/zero_bb_scale2.png" + +file2_compare = "Tests/images/non_zero_bb.png" +file2_compare_scale2 = "Tests/images/non_zero_bb_scale2.png" + +def test_sanity(): + #Regular scale + image1 = Image.open(file1) + image1.load() + assert_equal(image1.mode, "RGB") + assert_equal(image1.size, (460, 352)) + assert_equal(image1.format, "EPS") + + image2 = Image.open(file2) + image2.load() + assert_equal(image2.mode, "RGB") + assert_equal(image2.size, (360, 252)) + assert_equal(image2.format, "EPS") + + #Double scale + image1_scale2 = Image.open(file1) + image1_scale2.load(scale=2) + assert_equal(image1_scale2.mode, "RGB") + assert_equal(image1_scale2.size, (920, 704)) + assert_equal(image1_scale2.format, "EPS") + + image2_scale2 = Image.open(file2) + image2_scale2.load(scale=2) + assert_equal(image2_scale2.mode, "RGB") + assert_equal(image2_scale2.size, (720, 504)) + assert_equal(image2_scale2.format, "EPS") + +def test_render_scale1(): + #We need png support for these render test + codecs = dir(Image.core) + if "zip_encoder" not in codecs or "zip_decoder" not in codecs: + skip("zip/deflate support not available") + + #Zero bounding box + image1_scale1 = Image.open(file1) + image1_scale1.load() + image1_scale1_compare = Image.open(file1_compare).convert("RGB") + image1_scale1_compare.load() + assert_image_similar(image1_scale1, image1_scale1_compare, 5) + + #Non-Zero bounding box + image2_scale1 = Image.open(file2) + image2_scale1.load() + image2_scale1_compare = Image.open(file2_compare).convert("RGB") + image2_scale1_compare.load() + assert_image_similar(image2_scale1, image2_scale1_compare, 10) + +def test_render_scale2(): + #We need png support for these render test + codecs = dir(Image.core) + if "zip_encoder" not in codecs or "zip_decoder" not in codecs: + skip("zip/deflate support not available") + + #Zero bounding box + image1_scale2 = Image.open(file1) + image1_scale2.load(scale=2) + image1_scale2_compare = Image.open(file1_compare_scale2).convert("RGB") + image1_scale2_compare.load() + assert_image_similar(image1_scale2, image1_scale2_compare, 5) + + #Non-Zero bounding box + image2_scale2 = Image.open(file2) + image2_scale2.load(scale=2) + image2_scale2_compare = Image.open(file2_compare_scale2).convert("RGB") + image2_scale2_compare.load() + assert_image_similar(image2_scale2, image2_scale2_compare, 10) + diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 0c27865cd..3a6478e2a 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -27,3 +27,22 @@ def test_optimize(): return len(file.getvalue()) assert_equal(test(0), 800) assert_equal(test(1), 38) + +def test_roundtrip(): + out = tempfile('temp.gif') + im = lena() + im.save(out) + reread = Image.open(out) + + assert_image_similar(reread.convert('RGB'), im, 50) + +def test_roundtrip2(): + #see https://github.com/python-imaging/Pillow/issues/403 + out = 'temp.gif'#tempfile('temp.gif') + im = Image.open('Images/lena.gif') + im2 = im.copy() + im2.save(out) + reread = Image.open(out) + + assert_image_similar(reread.convert('RGB'), lena(), 50) + diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 51ee79ab7..1c5b22228 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -92,6 +92,7 @@ def test_g4_write(): assert_equal(reread.size,(500,500)) _assert_noerr(reread) assert_image_equal(reread, rot) + assert_equal(reread.info['compression'], 'group4') assert_equal(reread.info['compression'], orig.info['compression']) @@ -106,7 +107,6 @@ def test_adobe_deflate_tiff(): assert_equal(im.tile[0][:3], ('tiff_adobe_deflate', (0, 0, 278, 374), 0)) assert_no_exception(lambda: im.load()) - def test_write_metadata(): """ Test metadata writing through libtiff """ img = Image.open('Tests/images/lena_g4.tif') @@ -131,6 +131,15 @@ def test_write_metadata(): assert_equal(value, reloaded[tag], "%s didn't roundtrip" % tag) +def test_g3_compression(): + i = Image.open('Tests/images/lena_g4_500.tif') + out = tempfile("temp.tif") + i.save(out, compression='group3') + + reread = Image.open(out) + assert_equal(reread.info['compression'], 'group3') + assert_image_equal(reread, i) + def test_little_endian(): im = Image.open('Tests/images/12bit.deflate.tif') assert_equal(im.getpixel((0,0)), 480) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 6ad42ab59..b00089080 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -66,6 +66,26 @@ def test_write_rgb(): assert_image_similar(image, target, 20.0) +def test_write_lossless_rgb(): + temp_file = tempfile("temp.webp") + + lena("RGB").save(temp_file, lossless=True) + + image = Image.open(temp_file) + image.load() + + assert_equal(image.mode, "RGB") + assert_equal(image.size, (128, 128)) + assert_equal(image.format, "WEBP") + assert_no_exception(lambda: image.load()) + assert_no_exception(lambda: image.getdata()) + + + assert_image_equal(image, lena("RGB")) + + + + def test_write_rgba(): """ Can we write a RGBA mode file to webp without error. Does it have the bits we @@ -111,3 +131,27 @@ def test_read_rgba(): target = Image.open('Images/transparent.png') assert_image_similar(image, target, 20.0) + + +def test_write_lossless_rgb(): + temp_file = tempfile("temp.webp") + #temp_file = "temp.webp" + + pil_image = lena('RGBA') + + mask = Image.new("RGBA", (64, 64), (128,128,128,128)) + pil_image.paste(mask, (0,0), mask) # add some partially transparent bits. + + pil_image.save(temp_file, lossless=True) + + image = Image.open(temp_file) + image.load() + + assert_equal(image.mode, "RGBA") + assert_equal(image.size, pil_image.size) + assert_equal(image.format, "WEBP") + assert_no_exception(lambda: image.load()) + assert_no_exception(lambda: image.getdata()) + + + assert_image_equal(image, pil_image) diff --git a/_imagingft.c b/_imagingft.c index 47d50bdca..f19555be2 100644 --- a/_imagingft.c +++ b/_imagingft.c @@ -59,7 +59,11 @@ struct { const char* message; } ft_errors[] = +#if defined(USE_FREETYPE_2_1) +#include FT_ERRORS_H +#else #include +#endif /* -------------------------------------------------------------------- */ /* font objects */ diff --git a/_webp.c b/_webp.c index a85551533..6381e1a56 100644 --- a/_webp.c +++ b/_webp.c @@ -13,6 +13,7 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args) { int width; int height; + int lossless; float quality_factor; uint8_t *rgb; uint8_t *icc_bytes; @@ -20,29 +21,36 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args) uint8_t *output; char *mode; Py_ssize_t size; - Py_ssize_t icc_size; + Py_ssize_t icc_size; Py_ssize_t exif_size; size_t ret_size; - if (!PyArg_ParseTuple(args, "s#iifss#s#", - (char**)&rgb, &size, &width, &height, &quality_factor, &mode, + if (!PyArg_ParseTuple(args, "s#iiifss#s#", + (char**)&rgb, &size, &width, &height, &lossless, &quality_factor, &mode, &icc_bytes, &icc_size, &exif_bytes, &exif_size)) { Py_RETURN_NONE; } - - if (strcmp(mode, "RGBA")==0){ - if (size < width * height * 4){ - Py_RETURN_NONE; - } - ret_size = WebPEncodeRGBA(rgb, width, height, 4* width, quality_factor, &output); - } else if (strcmp(mode, "RGB")==0){ - if (size < width * height * 3){ - Py_RETURN_NONE; - } - ret_size = WebPEncodeRGB(rgb, width, height, 3* width, quality_factor, &output); - } else { - Py_RETURN_NONE; - } + if (strcmp(mode, "RGBA")==0){ + if (size < width * height * 4){ + Py_RETURN_NONE; + } + if (lossless) { + ret_size = WebPEncodeLosslessRGBA(rgb, width, height, 4* width, &output); + } else { + ret_size = WebPEncodeRGBA(rgb, width, height, 4* width, quality_factor, &output); + } + } else if (strcmp(mode, "RGB")==0){ + if (size < width * height * 3){ + Py_RETURN_NONE; + } + if (lossless) { + ret_size = WebPEncodeLosslessRGB(rgb, width, height, 3* width, &output); + } else { + ret_size = WebPEncodeRGB(rgb, width, height, 3* width, quality_factor, &output); + } + } else { + Py_RETURN_NONE; + } #ifndef HAVE_WEBPMUX if (ret_size > 0) { @@ -53,10 +61,10 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args) #else { /* I want to truncate the *_size items that get passed into webp - data. Pypy2.1.0 had some issues where the Py_ssize_t items had + data. Pypy2.1.0 had some issues where the Py_ssize_t items had data in the upper byte. (Not sure why, it shouldn't have been there) */ - int i_icc_size = (int)icc_size; + int i_icc_size = (int)icc_size; int i_exif_size = (int)exif_size; WebPData output_data = {0}; WebPData image = { output, ret_size }; @@ -105,11 +113,11 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args) WebPMuxAssemble(mux, &output_data); WebPMuxDelete(mux); + free(output); - output = (uint8_t*)output_data.bytes; ret_size = output_data.size; if (ret_size > 0) { - PyObject *ret = PyBytes_FromStringAndSize((char*)output, ret_size); + PyObject *ret = PyBytes_FromStringAndSize((char*)output_data.bytes, ret_size); WebPDataClear(&output_data); return ret; } diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 6f74622af..a326cb56d 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -36,6 +36,20 @@ 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. +If Ghostscript is available, you can call the :py:meth:`~PIL.Image.Image.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) + GIF ^^^ @@ -322,6 +336,24 @@ WebP PIL reads and writes WebP files. The specifics of PIL's capabilities with this format are currently undocumented. +The :py:meth:`~PIL.Image.Image.save` method supports the following options: + +**lossless** + If present, instructs the WEBP writer to use lossless + compression. + +**quality** + Integer, 1-100, Defaults to 80. Sets the quality level for + lossy compression. + +**icc_procfile** + The ICC Profile to include in the saved file. Only supported if + the system webp library was built with webpmux support. + +**exif** + The exif data to include in the saved file. Only supported if + the system webp library was built with webpmux support. + XBM ^^^ diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index caf9dc07e..10833a53e 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -1,10 +1,13 @@ Writing your own file decoder ============================= -The Python Imaging Library uses a plug-in model which allows you to add your -own decoders to the library, without any changes to the library itself. Such -plug-ins have names like :file:`XxxImagePlugin.py`, where ``Xxx`` is a unique -format name (usually an abbreviation). +The Python Imaging Library uses a plug-in model which allows you to +add your own decoders to the library, without any changes to the +library itself. Such plug-ins usually have names like +:file:`XxxImagePlugin.py`, where ``Xxx`` is a unique format name +(usually an abbreviation). + +.. warning:: Pillow >= 2.1.0 no longer automatically imports any file in the Python path with a name ending in :file:`ImagePlugin.py`. You will need to import your decoder manually. A decoder plug-in should contain a decoder class, based on the :py:class:`PIL.ImageFile.ImageFile` base class. This class should provide an diff --git a/docs/porting-pil-to-pillow.rst b/docs/porting-pil-to-pillow.rst index 93bc672af..a58baac39 100644 --- a/docs/porting-pil-to-pillow.rst +++ b/docs/porting-pil-to-pillow.rst @@ -15,3 +15,9 @@ to this:: The :py:mod:`_imaging` module has been moved. You can now import it like this:: from PIL.Image import core as _imaging + +The image plugin loading mechanisim has changed. Pillow no longer +automatically imports any file in the Python path with a name ending +in :file:`ImagePlugin.py`. You will need to import your image plugin +manually. + diff --git a/setup.py b/setup.py index 6c677bbd3..2bc5ca3f6 100644 --- a/setup.py +++ b/setup.py @@ -211,6 +211,10 @@ class pil_build_ext(build_ext): # work ;-) self.add_multiarch_paths() + elif sys.platform.startswith("netbsd"): + _add_directory(library_dirs, "/usr/pkg/lib") + _add_directory(include_dirs, "/usr/pkg/include") + _add_directory(library_dirs, "/usr/local/lib") # FIXME: check /opt/stuff directories here?