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.
  • Loading branch information
bgilbert committed Oct 24, 2023
1 parent d05ff50 commit e8ebc48
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 3 deletions.
20 changes: 20 additions & 0 deletions Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,26 @@ def closure(mode, *args):
im.load()
ImageFile.LOAD_TRUNCATED_IMAGES = False

def test_separate_tables(self):
im = hopper()
tables = BytesIO()
image = BytesIO()
im.save(tables, format="JPEG", streamtype=1)
im.save(image, format="JPEG", streamtype=2)
expectations = (
(b"\xff\xc0", False, True), # SOF0
(b"\xff\xc4", True, False), # DHT
(b"\xff\xd8", True, True), # SOI
(b"\xff\xd9", True, True), # EOI
(b"\xff\xda", False, True), # SOS
(b"\xff\xdb", True, False), # DQT
)
for marker, in_tables, in_image in expectations:
assert (marker in tables.getvalue()) == in_tables
assert (marker in image.getvalue()) == in_image
im2 = Image.open(BytesIO(tables.getvalue() + image.getvalue()))
assert_image_similar(im, im2, 17)

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 e8ebc48

Please sign in to comment.