Fixed issue #857.

When saving a JPEG and specifying 'keep' for quality or subsampling,
if the source JPEG image is in grayscale mode, don't try to find the
subsampling of the source, because grayscale images don't have any
subsampling (it's only for color components).

For the moment the fix also ignores subsampling of CMYK JPEG because
currently Pillow doesn't support encoding JPEG in YCCK mode (and
subsampling doesn't make sense in CMYK, but Pillow permits saving CMYK
JPEG with subsampling, that's a bug). This fix pass those errors
silently, i.e. it doesn't raise an error when 'keep' is used but it's
not possible to keep the subsampling (because the image is grayscale
or CMYK). I think it's the proper behavior but I'm not sure.
This commit is contained in:
etienne 2014-09-02 14:49:24 -04:00 committed by wiredfool
parent a65351fcdf
commit 416d8e340e
4 changed files with 23 additions and 5 deletions

View File

@ -545,6 +545,15 @@ def convert_dict_qtables(qtables):
def get_sampling(im): def get_sampling(im):
# There's no subsampling when image have only 1 layer
# (grayscale images) or when they are CMYK (4 layers),
# so set subsampling to default value.
#
# NOTE: currently Pillow can't encode JPEG to YCCK format.
# If YCCK support is added in the future, subsampling code will have
# to be updated (here and in JpegEncode.c) to deal with 4 layers.
if not hasattr(im, 'layers') or im.layers in (1, 4):
return -1
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3] sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
return samplings.get(sampling, -1) return samplings.get(sampling, -1)

BIN
Tests/images/lena_gray.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -224,9 +224,18 @@ class TestFileJpeg(PillowTestCase):
self.assertIsNone(im._getmp()) self.assertIsNone(im._getmp())
def test_quality_keep(self): def test_quality_keep(self):
# RGB
im = Image.open("Tests/images/lena.jpg") im = Image.open("Tests/images/lena.jpg")
f = self.tempfile('temp.jpg') f = self.tempfile('temp.jpg')
im.save(f, quality='keep') im.save(f, quality='keep')
# Grayscale
im = Image.open("Tests/images/lena_gray.jpg")
f = self.tempfile('temp.jpg')
im.save(f, quality='keep')
# CMYK
im = Image.open("Tests/images/pil_sample_cmyk.jpg")
f = self.tempfile('temp.jpg')
im.save(f, quality='keep')
def test_junk_jpeg_header(self): def test_junk_jpeg_header(self):
# https://github.com/python-pillow/Pillow/issues/630 # https://github.com/python-pillow/Pillow/issues/630

View File

@ -540,8 +540,8 @@ static unsigned int** get_qtables_arrays(PyObject* qtables) {
tables = PySequence_Fast(qtables, "expected a sequence"); tables = PySequence_Fast(qtables, "expected a sequence");
num_tables = PySequence_Size(qtables); num_tables = PySequence_Size(qtables);
if (num_tables < 2 || num_tables > NUM_QUANT_TBLS) { if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) {
PyErr_SetString(PyExc_ValueError, "Not a valid numbers of quantization tables. Should be between 2 and 4."); PyErr_SetString(PyExc_ValueError, "Not a valid numbers of quantization tables. Should be between 1 and 4.");
return NULL; return NULL;
} }
qarrays = (unsigned int**) PyMem_Malloc(num_tables * sizeof(unsigned int*)); qarrays = (unsigned int**) PyMem_Malloc(num_tables * sizeof(unsigned int*));
@ -760,7 +760,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
(ttag_t) PyInt_AsLong(key), (ttag_t) PyInt_AsLong(key),
intav); intav);
free(intav); free(intav);
} }
} else { } else {
TRACE((" %d elements, setting as floats \n", len)); TRACE((" %d elements, setting as floats \n", len));
floatav = malloc(sizeof(float)*len); floatav = malloc(sizeof(float)*len);
@ -903,7 +903,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
j2k_decode_coord_tuple(tile_offset, j2k_decode_coord_tuple(tile_offset,
&context->tile_offset_x, &context->tile_offset_x,
&context->tile_offset_y); &context->tile_offset_y);
j2k_decode_coord_tuple(tile_size, j2k_decode_coord_tuple(tile_size,
&context->tile_size_x, &context->tile_size_x,
&context->tile_size_y); &context->tile_size_y);
@ -918,7 +918,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
if (context->tile_offset_x > context->offset_x if (context->tile_offset_x > context->offset_x
|| context->tile_offset_y > context->offset_y) { || context->tile_offset_y > context->offset_y) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"JPEG 2000 tile offset too large to cover image area"); "JPEG 2000 tile offset too large to cover image area");
Py_DECREF(encoder); Py_DECREF(encoder);
return NULL; return NULL;