Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for 16-bit precision JPEG quantization values #4918

Merged
merged 6 commits into from Oct 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file added Tests/images/hopper_16bit_qtables.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions Tests/test_file_jpeg.py
Expand Up @@ -446,6 +446,7 @@ def _n_qtables_helper(n, test_file):
assert len(im.quantization) == n
reloaded = self.roundtrip(im, qtables="keep")
assert im.quantization == reloaded.quantization
assert reloaded.quantization[0].typecode == "B"

with Image.open("Tests/images/hopper.jpg") as im:
qtables = im.quantization
Expand Down Expand Up @@ -544,6 +545,30 @@ def _n_qtables_helper(n, test_file):
with pytest.raises(ValueError):
self.roundtrip(im, qtables=[[1, 2, 3, 4]])

def test_load_16bit_qtables(self):
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
assert len(im.quantization) == 2
assert len(im.quantization[0]) == 64
assert max(im.quantization[0]) > 255

def test_save_multiple_16bit_qtables(self):
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
im2 = self.roundtrip(im, qtables="keep")
assert im.quantization == im2.quantization

def test_save_single_16bit_qtable(self):
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
im2 = self.roundtrip(im, qtables={0: im.quantization[0]})
assert len(im2.quantization) == 1
assert im2.quantization[0] == im.quantization[0]

def test_save_low_quality_baseline_qtables(self):
with Image.open(TEST_FILE) as im:
im2 = self.roundtrip(im, quality=10)
assert len(im2.quantization) == 2
assert max(im2.quantization[0]) <= 255
assert max(im2.quantization[1]) <= 255

@pytest.mark.skipif(not djpeg_available(), reason="djpeg not available")
def test_load_djpeg(self):
with Image.open(TEST_FILE) as img:
Expand Down
25 changes: 13 additions & 12 deletions src/PIL/JpegImagePlugin.py
Expand Up @@ -36,6 +36,7 @@
import os
import struct
import subprocess
import sys
import tempfile
import warnings

Expand Down Expand Up @@ -234,25 +235,25 @@ def SOF(self, marker):

def DQT(self, marker):
#
# Define quantization table. Support baseline 8-bit tables
# only. Note that there might be more than one table in
# each marker.
# Define quantization table. Note that there might be more
# than one table in each marker.

# FIXME: The quantization tables can be used to estimate the
# compression quality.

n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n)
while len(s):
if len(s) < 65:
raise SyntaxError("bad quantization table marker")
v = i8(s[0])
if v // 16 == 0:
self.quantization[v & 15] = array.array("B", s[1:65])
s = s[65:]
else:
return # FIXME: add code to read 16-bit tables!
# raise SyntaxError, "bad quantization table element size"
precision = 1 if (v // 16 == 0) else 2 # in bytes
qt_length = 1 + precision * 64
if len(s) < qt_length:
raise SyntaxError("bad quantization table marker")
data = array.array("B" if precision == 1 else "H", s[1:qt_length])
if sys.byteorder == "little" and precision > 1:
data.byteswap() # the values are always big-endian
self.quantization[v & 15] = data
s = s[qt_length:]


#
Expand Down Expand Up @@ -683,7 +684,7 @@ def validate_qtables(qtables):
try:
if len(table) != 64:
raise TypeError
table = array.array("B", table)
table = array.array("H", table)
except TypeError as e:
raise ValueError("Invalid quantization table") from e
else:
Expand Down
7 changes: 3 additions & 4 deletions src/libImaging/JpegEncode.c
Expand Up @@ -159,22 +159,21 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
quality = context->quality;
}
for (i = 0; i < context->qtablesLen; i++) {
// TODO: Should add support for none baseline
jpeg_add_quant_table(&context->cinfo, i, &context->qtables[i * DCTSIZE2],
quality, TRUE);
quality, FALSE);
context->cinfo.comp_info[i].quant_tbl_no = i;
last_q = i;
}
if (context->qtablesLen == 1) {
// jpeg_set_defaults created two qtables internally, but we only wanted one.
jpeg_add_quant_table(&context->cinfo, 1, &context->qtables[0],
quality, TRUE);
quality, FALSE);
}
for (i = last_q; i < context->cinfo.num_components; i++) {
context->cinfo.comp_info[i].quant_tbl_no = last_q;
}
} else if (context->quality != -1) {
jpeg_set_quality(&context->cinfo, context->quality, 1);
jpeg_set_quality(&context->cinfo, context->quality, TRUE);
}

/* Set subsampling options */
Expand Down