Skip to content

Commit

Permalink
Merge pull request #4440 from radarhere/jpeg_quality
Browse files Browse the repository at this point in the history
Allow saving of zero quality JPEG images
  • Loading branch information
hugovk committed Feb 25, 2020
2 parents 80994bc + a99b9d6 commit 3c995fd
Show file tree
Hide file tree
Showing 6 changed files with 29 additions and 9 deletions.
4 changes: 4 additions & 0 deletions Tests/test_file_jpeg.py
Expand Up @@ -299,6 +299,10 @@ def test_quality(self):
assert_image(im1, im2.mode, im2.size)
assert im1.bytes >= im2.bytes

im3 = self.roundtrip(hopper(), quality=0)
assert_image(im1, im3.mode, im3.size)
self.assertGreater(im2.bytes, im3.bytes)

def test_smooth(self):
im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), smooth=100)
Expand Down
2 changes: 1 addition & 1 deletion docs/handbook/image-file-formats.rst
Expand Up @@ -302,7 +302,7 @@ The :py:meth:`~PIL.Image.Image.open` method may set the following
The :py:meth:`~PIL.Image.Image.save` method supports the following options:

**quality**
The image quality, on a scale from 1 (worst) to 95 (best). The default is
The image quality, on a scale from 0 (worst) to 95 (best). The default is
75. Values above 95 should be avoided; 100 disables portions of the JPEG
compression algorithm, and results in large files with hardly any gain in
image quality.
Expand Down
16 changes: 16 additions & 0 deletions docs/releasenotes/7.1.0.rst
@@ -0,0 +1,16 @@
7.0.0
-----

Allow saving of zero quality JPEG images
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If no quality was specified when saving a JPEG, Pillow internally used a value
of zero to indicate that the default quality should be used. However, this
removed the ability to actually save a JPEG with zero quality. This has now
been resolved.

.. code-block:: python
from PIL import Image
im = Image.open("hopper.jpg")
im.save("out.jpg", quality=0)
10 changes: 5 additions & 5 deletions src/PIL/JpegImagePlugin.py
Expand Up @@ -616,17 +616,17 @@ def _save(im, fp, filename):

dpi = [round(x) for x in info.get("dpi", (0, 0))]

quality = info.get("quality", 0)
quality = info.get("quality", -1)
subsampling = info.get("subsampling", -1)
qtables = info.get("qtables")

if quality == "keep":
quality = 0
quality = -1
subsampling = "keep"
qtables = "keep"
elif quality in presets:
preset = presets[quality]
quality = 0
quality = -1
subsampling = preset.get("subsampling", -1)
qtables = preset.get("quantization")
elif not isinstance(quality, int):
Expand Down Expand Up @@ -749,8 +749,8 @@ def validate_qtables(qtables):
# CMYK can be bigger
if im.mode == "CMYK":
bufsize = 4 * im.size[0] * im.size[1]
# keep sets quality to 0, but the actual value may be high.
elif quality >= 95 or quality == 0:
# keep sets quality to -1, but the actual value may be high.
elif quality >= 95 or quality == -1:
bufsize = 2 * im.size[0] * im.size[1]
else:
bufsize = im.size[0] * im.size[1]
Expand Down
2 changes: 1 addition & 1 deletion src/libImaging/Jpeg.h
Expand Up @@ -67,7 +67,7 @@ typedef struct {

/* CONFIGURATION */

/* Quality (1-100, 0 means default) */
/* Quality (0-100, -1 means default) */
int quality;

/* Progressive mode */
Expand Down
4 changes: 2 additions & 2 deletions src/libImaging/JpegEncode.c
Expand Up @@ -153,7 +153,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
int i;
int quality = 100;
int last_q = 0;
if (context->quality > 0) {
if (context->quality != -1) {
quality = context->quality;
}
for (i = 0; i < context->qtablesLen; i++) {
Expand All @@ -171,7 +171,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
for (i = last_q; i < context->cinfo.num_components; i++) {
context->cinfo.comp_info[i].quant_tbl_no = last_q;
}
} else if (context->quality > 0) {
} else if (context->quality != -1) {
jpeg_set_quality(&context->cinfo, context->quality, 1);
}

Expand Down

0 comments on commit 3c995fd

Please sign in to comment.