Skip to content

Commit 4d7372b

Browse files
bgilbertradarhere
andcommitted
Implement streamtype=1 option for tables-only JPEG encoding
We already support streamtype=2 to skip producing JPEG tables, but streamtype=1, which skips everything but the tables, was never implemented. The streamtype=1 stub code dates to Git pre-history, so it's not immediately clear why. Implement the missing support. jpeg_write_tables() can't resume after a full output buffer (it fails with JERR_CANT_SUSPEND), so it might seem that Pillow needs to pre-compute the necessary buffer size. However, in the normal case of producing an interchange stream, the tables are written via the same libjpeg codepath during the first jpeg_write_scanlines() call, and table writes aren't resumable there either. Thus, any buffer large enough for the normal case will also be large enough for a tables-only file. The streamtype option isn't documented and this commit doesn't change that. It does add a test though. Co-authored-by: Andrew Murray <[email protected]>
1 parent d05ff50 commit 4d7372b

File tree

2 files changed

+26
-3
lines changed

2 files changed

+26
-3
lines changed

Tests/test_file_jpeg.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,28 @@ def closure(mode, *args):
961961
im.load()
962962
ImageFile.LOAD_TRUNCATED_IMAGES = False
963963

964+
def test_separate_tables(self):
965+
im = hopper()
966+
data = [] # [interchange, tables-only, image-only]
967+
for streamtype in range(3):
968+
out = BytesIO()
969+
im.save(out, format="JPEG", streamtype=streamtype)
970+
data.append(out.getvalue())
971+
972+
# SOI, EOI
973+
for marker in b"\xff\xd8", b"\xff\xd9":
974+
assert marker in data[1] and marker in data[2]
975+
# DHT, DQT
976+
for marker in b"\xff\xc4", b"\xff\xdb":
977+
assert marker in data[1] and marker not in data[2]
978+
# SOF0, SOS, APP0 (JFIF header)
979+
for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
980+
assert marker not in data[1] and marker in data[2]
981+
982+
with Image.open(BytesIO(data[0])) as interchange_im:
983+
with Image.open(BytesIO(data[1] + data[2])) as combined_im:
984+
assert_image_equal(interchange_im, combined_im)
985+
964986
def test_repr_jpeg(self):
965987
im = hopper()
966988

src/libImaging/JpegEncode.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,9 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
218218
}
219219
switch (context->streamtype) {
220220
case 1:
221-
/* tables only -- not yet implemented */
222-
state->errcode = IMAGING_CODEC_CONFIG;
223-
return -1;
221+
/* tables only */
222+
jpeg_write_tables(&context->cinfo);
223+
goto cleanup;
224224
case 2:
225225
/* image only */
226226
jpeg_suppress_tables(&context->cinfo, TRUE);
@@ -316,6 +316,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
316316
}
317317
jpeg_finish_compress(&context->cinfo);
318318

319+
cleanup:
319320
/* Clean up */
320321
if (context->comment) {
321322
free(context->comment);

0 commit comments

Comments
 (0)