Merge pull request #7488 from bgilbert/jpeg-restart

Allow configuring JPEG restart marker interval on save
This commit is contained in:
Andrew Murray 2023-11-14 23:42:50 +11:00 committed by GitHub
commit 4b308dc2bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 44 additions and 1 deletions

View File

@ -643,6 +643,23 @@ class TestFileJpeg:
assert max(im2.quantization[0]) <= 255
assert max(im2.quantization[1]) <= 255
@pytest.mark.parametrize(
"blocks, rows, markers",
((0, 0, 0), (1, 0, 15), (3, 0, 5), (8, 0, 1), (0, 1, 3), (0, 2, 1)),
)
def test_restart_markers(self, blocks, rows, markers):
im = Image.new("RGB", (32, 32)) # 16 MCUs
out = BytesIO()
im.save(
out,
format="JPEG",
restart_marker_blocks=blocks,
restart_marker_rows=rows,
# force 8x8 pixel MCUs
subsampling=0,
)
assert len(re.findall(b"\xff[\xd0-\xd7]", out.getvalue())) == markers
@pytest.mark.skipif(not djpeg_available(), reason="djpeg not available")
def test_load_djpeg(self):
with Image.open(TEST_FILE) as img:

View File

@ -494,6 +494,18 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
If absent, the setting will be determined by libjpeg or libjpeg-turbo.
**restart_marker_blocks**
If present, emit a restart marker whenever the specified number of MCU
blocks has been produced.
.. versionadded:: 10.2.0
**restart_marker_rows**
If present, emit a restart marker whenever the specified number of MCU
rows has been produced.
.. versionadded:: 10.2.0
**qtables**
If present, sets the qtables for the encoder. This is listed as an
advanced option for wizards in the JPEG documentation. Use with

View File

@ -787,6 +787,8 @@ def _save(im, fp, filename):
dpi[0],
dpi[1],
subsampling,
info.get("restart_marker_blocks", 0),
info.get("restart_marker_rows", 0),
qtables,
comment,
extra,

View File

@ -1045,6 +1045,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
Py_ssize_t streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */
Py_ssize_t xdpi = 0, ydpi = 0;
Py_ssize_t subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */
Py_ssize_t restart_marker_blocks = 0;
Py_ssize_t restart_marker_rows = 0;
PyObject *qtables = NULL;
unsigned int *qarrays = NULL;
int qtablesLen = 0;
@ -1057,7 +1059,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
"ss|nnnnnnnnOz#y#y#",
"ss|nnnnnnnnnnOz#y#y#",
&mode,
&rawmode,
&quality,
@ -1068,6 +1070,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
&xdpi,
&ydpi,
&subsampling,
&restart_marker_blocks,
&restart_marker_rows,
&qtables,
&comment,
&comment_size,
@ -1156,6 +1160,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype;
((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi;
((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi;
((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_blocks = restart_marker_blocks;
((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_rows = restart_marker_rows;
((JPEGENCODERSTATE *)encoder->state.context)->comment = comment;
((JPEGENCODERSTATE *)encoder->state.context)->comment_size = comment_size;
((JPEGENCODERSTATE *)encoder->state.context)->extra = extra;

View File

@ -83,6 +83,10 @@ typedef struct {
/* Chroma Subsampling (-1=default, 0=none, 1=medium, 2=high) */
int subsampling;
/* Restart marker interval, in MCU blocks or MCU rows, or 0 for none */
unsigned int restart_marker_blocks;
unsigned int restart_marker_rows;
/* Converter input mode (input to the shuffler) */
char rawmode[8 + 1];

View File

@ -210,6 +210,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
}
context->cinfo.smoothing_factor = context->smooth;
context->cinfo.optimize_coding = (boolean)context->optimize;
context->cinfo.restart_interval = context->restart_marker_blocks;
context->cinfo.restart_in_rows = context->restart_marker_rows;
if (context->xdpi > 0 && context->ydpi > 0) {
context->cinfo.write_JFIF_header = TRUE;
context->cinfo.density_unit = 1; /* dots per inch */