diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 1c260dd6b..4f36f5a1f 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -554,6 +554,7 @@ def _save(im, fp, filename): info.get("exif", b"") ) + # if we optimize, libjpeg needs a buffer big enough to hold the whole image in a shot. # Guessing on the size, at im.size bytes. (raw pizel size is channels*size, this # is a value that's been used in a django patch. @@ -561,6 +562,10 @@ def _save(im, fp, filename): bufsize=0 if "optimize" in info: bufsize = im.size[0]*im.size[1] + + # The exif info needs to be written as one block, + APP1, + one spare byte. + # Ensure that our buffer is big enough + bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif",b"")) + 5 ) ImageFile._save(im, fp, [("jpeg", (0,0)+im.size, 0, rawmode)], bufsize) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 556786aa7..1027d0174 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -120,6 +120,12 @@ def test_optimize_large_buffer(): im = Image.new("RGB", (4096,4096), 0xff3333) im.save(f, format="JPEG", optimize=True) +def test_large_exif(): + #https://github.com/python-imaging/Pillow/issues/148 + f = tempfile('temp.jpg') + im = lena() + im.save(f,'JPEG', quality=90, exif=b"1"*65532) + def test_progressive(): im1 = roundtrip(lena()) im2 = roundtrip(lena(), progressive=1) diff --git a/libImaging/JpegEncode.c b/libImaging/JpegEncode.c index 221f8ccfa..5d0b85d6a 100644 --- a/libImaging/JpegEncode.c +++ b/libImaging/JpegEncode.c @@ -22,7 +22,7 @@ #include "Imaging.h" -#ifdef HAVE_LIBJPEG +#ifdef HAVE_LIBJPEG #undef HAVE_PROTOTYPES #undef HAVE_STDLIB_H @@ -36,7 +36,7 @@ #include "Jpeg.h" /* -------------------------------------------------------------------- */ -/* Suspending output handler */ +/* Suspending output handler */ /* -------------------------------------------------------------------- */ METHODDEF(void) @@ -64,16 +64,16 @@ jpeg_buffer_dest(j_compress_ptr cinfo, JPEGDESTINATION* destination) /* -------------------------------------------------------------------- */ -/* Error handler */ +/* Error handler */ /* -------------------------------------------------------------------- */ METHODDEF(void) error(j_common_ptr cinfo) { - JPEGERROR* error; - error = (JPEGERROR*) cinfo->err; - (*cinfo->err->output_message) (cinfo); - longjmp(error->setjmp_buffer, 1); + JPEGERROR* error; + error = (JPEGERROR*) cinfo->err; + (*cinfo->err->output_message) (cinfo); + longjmp(error->setjmp_buffer, 1); } @@ -146,59 +146,59 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) /* Use custom quantization tables */ if (context->qtables) { - int i; - int quality = 100; - if (context->quality > 0) { - quality = context->quality; - } - for (i = 0; i < sizeof(context->qtables)/sizeof(unsigned int); i++) { - // TODO: Should add support for none baseline - jpeg_add_quant_table(&context->cinfo, i, context->qtables[i], - quality, TRUE); - } + int i; + int quality = 100; + if (context->quality > 0) { + quality = context->quality; + } + for (i = 0; i < sizeof(context->qtables)/sizeof(unsigned int); i++) { + // TODO: Should add support for none baseline + jpeg_add_quant_table(&context->cinfo, i, context->qtables[i], + quality, TRUE); + } } else if (context->quality > 0) { jpeg_set_quality(&context->cinfo, context->quality, 1); } /* Set subsampling options */ switch (context->subsampling) - { - case 0: /* 1x1 1x1 1x1 (4:4:4) : None */ + { + case 0: /* 1x1 1x1 1x1 (4:4:4) : None */ { - context->cinfo.comp_info[0].h_samp_factor = 1; - context->cinfo.comp_info[0].v_samp_factor = 1; - context->cinfo.comp_info[1].h_samp_factor = 1; - context->cinfo.comp_info[1].v_samp_factor = 1; - context->cinfo.comp_info[2].h_samp_factor = 1; - context->cinfo.comp_info[2].v_samp_factor = 1; - break; + context->cinfo.comp_info[0].h_samp_factor = 1; + context->cinfo.comp_info[0].v_samp_factor = 1; + context->cinfo.comp_info[1].h_samp_factor = 1; + context->cinfo.comp_info[1].v_samp_factor = 1; + context->cinfo.comp_info[2].h_samp_factor = 1; + context->cinfo.comp_info[2].v_samp_factor = 1; + break; } - case 1: /* 2x1, 1x1, 1x1 (4:2:2) : Medium */ + case 1: /* 2x1, 1x1, 1x1 (4:2:2) : Medium */ { - context->cinfo.comp_info[0].h_samp_factor = 2; - context->cinfo.comp_info[0].v_samp_factor = 1; - context->cinfo.comp_info[1].h_samp_factor = 1; - context->cinfo.comp_info[1].v_samp_factor = 1; - context->cinfo.comp_info[2].h_samp_factor = 1; - context->cinfo.comp_info[2].v_samp_factor = 1; - break; + context->cinfo.comp_info[0].h_samp_factor = 2; + context->cinfo.comp_info[0].v_samp_factor = 1; + context->cinfo.comp_info[1].h_samp_factor = 1; + context->cinfo.comp_info[1].v_samp_factor = 1; + context->cinfo.comp_info[2].h_samp_factor = 1; + context->cinfo.comp_info[2].v_samp_factor = 1; + break; } - case 2: /* 2x2, 1x1, 1x1 (4:1:1) : High */ + case 2: /* 2x2, 1x1, 1x1 (4:1:1) : High */ { - context->cinfo.comp_info[0].h_samp_factor = 2; - context->cinfo.comp_info[0].v_samp_factor = 2; - context->cinfo.comp_info[1].h_samp_factor = 1; - context->cinfo.comp_info[1].v_samp_factor = 1; - context->cinfo.comp_info[2].h_samp_factor = 1; - context->cinfo.comp_info[2].v_samp_factor = 1; - break; + context->cinfo.comp_info[0].h_samp_factor = 2; + context->cinfo.comp_info[0].v_samp_factor = 2; + context->cinfo.comp_info[1].h_samp_factor = 1; + context->cinfo.comp_info[1].v_samp_factor = 1; + context->cinfo.comp_info[2].h_samp_factor = 1; + context->cinfo.comp_info[2].v_samp_factor = 1; + break; } - default: + default: { - /* Use the lib's default */ - break; + /* Use the lib's default */ + break; } - } + } if (context->progressive) jpeg_simple_progression(&context->cinfo); context->cinfo.smoothing_factor = context->smooth; @@ -219,24 +219,29 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) jpeg_start_compress(&context->cinfo, FALSE); /* suppress extra section */ context->extra_offset = context->extra_size; - //add exif header - if (context->rawExifLen > 0) - jpeg_write_marker(&context->cinfo, JPEG_APP0+1, (unsigned char*)context->rawExif, context->rawExifLen); - break; default: /* interchange stream */ jpeg_start_compress(&context->cinfo, TRUE); - //add exif header - if (context->rawExifLen > 0) - jpeg_write_marker(&context->cinfo, JPEG_APP0+1, (unsigned char*)context->rawExif, context->rawExifLen); - - break; + break; } state->state++; /* fall through */ case 2: + // check for exif len + 'APP1' header bytes + if (context->rawExifLen + 5 > context->destination.pub.free_in_buffer){ + break; + } + //add exif header + if (context->rawExifLen > 0){ + jpeg_write_marker(&context->cinfo, JPEG_APP0+1, + (unsigned char*)context->rawExif, context->rawExifLen); + } + + state->state++; + /* fall through */ + case 3: if (context->extra) { /* copy extra buffer to output buffer */ @@ -253,9 +258,12 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) else break; } else - state->state++; + state->state++; - case 3: + case 4: + if (1024 > context->destination.pub.free_in_buffer){ + break; + } ok = 1; while (state->y < state->ysize) { @@ -273,7 +281,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) state->state++; /* fall through */ - case 4: + case 5: /* Finish compression */ if (context->destination.pub.free_in_buffer < 100)