Skip to content

Commit

Permalink
Implement streamtype=1 option for tables-only JPEG encoding
Browse files Browse the repository at this point in the history
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 <radarhere@users.noreply.github.com>
  • Loading branch information
bgilbert and radarhere committed Oct 25, 2023
1 parent d05ff50 commit 4d7372b
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 3 deletions.
22 changes: 22 additions & 0 deletions Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,28 @@ def closure(mode, *args):
im.load()
ImageFile.LOAD_TRUNCATED_IMAGES = False

def test_separate_tables(self):
im = hopper()
data = [] # [interchange, tables-only, image-only]
for streamtype in range(3):
out = BytesIO()
im.save(out, format="JPEG", streamtype=streamtype)
data.append(out.getvalue())

# SOI, EOI
for marker in b"\xff\xd8", b"\xff\xd9":
assert marker in data[1] and marker in data[2]
# DHT, DQT
for marker in b"\xff\xc4", b"\xff\xdb":
assert marker in data[1] and marker not in data[2]
# SOF0, SOS, APP0 (JFIF header)
for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
assert marker not in data[1] and marker in data[2]

with Image.open(BytesIO(data[0])) as interchange_im:
with Image.open(BytesIO(data[1] + data[2])) as combined_im:
assert_image_equal(interchange_im, combined_im)

def test_repr_jpeg(self):
im = hopper()

Expand Down
7 changes: 4 additions & 3 deletions src/libImaging/JpegEncode.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,9 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
}
switch (context->streamtype) {
case 1:
/* tables only -- not yet implemented */
state->errcode = IMAGING_CODEC_CONFIG;
return -1;
/* tables only */
jpeg_write_tables(&context->cinfo);
goto cleanup;
case 2:
/* image only */
jpeg_suppress_tables(&context->cinfo, TRUE);
Expand Down Expand Up @@ -316,6 +316,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
}
jpeg_finish_compress(&context->cinfo);

cleanup:
/* Clean up */
if (context->comment) {
free(context->comment);
Expand Down

0 comments on commit 4d7372b

Please sign in to comment.