mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-10 16:22:22 +03:00
Allow disabling default emission of JPEG APP0 and APP14 segments
When embedding JPEGs into a container file format, it may be desirable
to minimize JPEG metadata, since the container will include the pertinent
details. By default, libjpeg emits a JFIF APP0 segment for JFIF-
compatible colorspaces (grayscale or YCbCr) and Adobe APP14 otherwise.
Add a no_default_app_segments option to disable these.
660894cd36
added code to force emission of the JFIF segment if the DPI is
specified, even for JFIF-incompatible colorspaces. This seems
inconsistent with the JFIF spec, but apparently other software does it
too. no_default_app_segments does not disable this behavior, since it
only happens when the application explicitly specifies the DPI.
This commit is contained in:
parent
6ade47f7c0
commit
8053d5e5a0
|
@ -88,6 +88,31 @@ class TestFileJpeg:
|
||||||
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00"
|
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00"
|
||||||
assert im.app["COM"] == im.info["comment"]
|
assert im.app["COM"] == im.info["comment"]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"keep_rgb, no_default_app_segments, expect_app0, expect_app14",
|
||||||
|
(
|
||||||
|
(False, False, True, False),
|
||||||
|
(True, False, False, True),
|
||||||
|
(False, True, False, False),
|
||||||
|
(True, True, False, False),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_default_app_write(
|
||||||
|
self,
|
||||||
|
keep_rgb,
|
||||||
|
no_default_app_segments,
|
||||||
|
expect_app0,
|
||||||
|
expect_app14,
|
||||||
|
):
|
||||||
|
im = self.roundtrip(
|
||||||
|
hopper(),
|
||||||
|
keep_rgb=keep_rgb,
|
||||||
|
no_default_app_segments=no_default_app_segments,
|
||||||
|
)
|
||||||
|
markers = {m[0] for m in im.applist}
|
||||||
|
assert ("APP0" in markers) == expect_app0
|
||||||
|
assert ("APP14" in markers) == expect_app14
|
||||||
|
|
||||||
def test_comment_write(self):
|
def test_comment_write(self):
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00"
|
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00"
|
||||||
|
|
|
@ -487,6 +487,13 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
**exif**
|
**exif**
|
||||||
If present, the image will be stored with the provided raw EXIF data.
|
If present, the image will be stored with the provided raw EXIF data.
|
||||||
|
|
||||||
|
**no_default_app_segments**
|
||||||
|
If present and true, the image is stored without default JFIF and Adobe
|
||||||
|
application segments. The JFIF segment will still be stored if **dpi**
|
||||||
|
is also specified.
|
||||||
|
|
||||||
|
.. versionadded:: 10.3.0
|
||||||
|
|
||||||
**keep_rgb**
|
**keep_rgb**
|
||||||
By default, libjpeg converts images with an RGB color space to YCbCr.
|
By default, libjpeg converts images with an RGB color space to YCbCr.
|
||||||
If this option is present and true, those images will be stored as RGB
|
If this option is present and true, those images will be stored as RGB
|
||||||
|
|
|
@ -26,10 +26,12 @@ TODO
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
|
||||||
TODO
|
JPEG app segments
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
When saving JPEG files, ``no_default_app_segments`` can now be set to ``True`` to store
|
||||||
|
the image without default JFIF and Adobe application segments. The JFIF segment will
|
||||||
|
still be stored if ``dpi`` is also specified.
|
||||||
|
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
|
@ -786,6 +786,7 @@ def _save(im, fp, filename):
|
||||||
info.get("smooth", 0),
|
info.get("smooth", 0),
|
||||||
optimize,
|
optimize,
|
||||||
info.get("keep_rgb", False),
|
info.get("keep_rgb", False),
|
||||||
|
info.get("no_default_app_segments", False),
|
||||||
info.get("streamtype", 0),
|
info.get("streamtype", 0),
|
||||||
dpi[0],
|
dpi[0],
|
||||||
dpi[1],
|
dpi[1],
|
||||||
|
|
|
@ -1043,6 +1043,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
Py_ssize_t smooth = 0;
|
Py_ssize_t smooth = 0;
|
||||||
Py_ssize_t optimize = 0;
|
Py_ssize_t optimize = 0;
|
||||||
int keep_rgb = 0;
|
int keep_rgb = 0;
|
||||||
|
int no_default_app_segments = 0;
|
||||||
Py_ssize_t streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */
|
Py_ssize_t streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */
|
||||||
Py_ssize_t xdpi = 0, ydpi = 0;
|
Py_ssize_t xdpi = 0, ydpi = 0;
|
||||||
Py_ssize_t subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */
|
Py_ssize_t subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */
|
||||||
|
@ -1060,7 +1061,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"ss|nnnnpnnnnnnOz#y#y#",
|
"ss|nnnnppnnnnnnOz#y#y#",
|
||||||
&mode,
|
&mode,
|
||||||
&rawmode,
|
&rawmode,
|
||||||
&quality,
|
&quality,
|
||||||
|
@ -1068,6 +1069,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
&smooth,
|
&smooth,
|
||||||
&optimize,
|
&optimize,
|
||||||
&keep_rgb,
|
&keep_rgb,
|
||||||
|
&no_default_app_segments,
|
||||||
&streamtype,
|
&streamtype,
|
||||||
&xdpi,
|
&xdpi,
|
||||||
&ydpi,
|
&ydpi,
|
||||||
|
@ -1153,6 +1155,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
strncpy(((JPEGENCODERSTATE *)encoder->state.context)->rawmode, rawmode, 8);
|
strncpy(((JPEGENCODERSTATE *)encoder->state.context)->rawmode, rawmode, 8);
|
||||||
|
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->keep_rgb = keep_rgb;
|
((JPEGENCODERSTATE *)encoder->state.context)->keep_rgb = keep_rgb;
|
||||||
|
((JPEGENCODERSTATE *)encoder->state.context)->no_default_app_segments = no_default_app_segments;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->quality = quality;
|
((JPEGENCODERSTATE *)encoder->state.context)->quality = quality;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->qtables = qarrays;
|
((JPEGENCODERSTATE *)encoder->state.context)->qtables = qarrays;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->qtablesLen = qtablesLen;
|
((JPEGENCODERSTATE *)encoder->state.context)->qtablesLen = qtablesLen;
|
||||||
|
|
|
@ -77,6 +77,9 @@ typedef struct {
|
||||||
/* Disable automatic conversion of RGB images to YCbCr if non-zero */
|
/* Disable automatic conversion of RGB images to YCbCr if non-zero */
|
||||||
int keep_rgb;
|
int keep_rgb;
|
||||||
|
|
||||||
|
/* Disable default application segments if non-zero */
|
||||||
|
int no_default_app_segments;
|
||||||
|
|
||||||
/* Stream type (0=full, 1=tables only, 2=image only) */
|
/* Stream type (0=full, 1=tables only, 2=image only) */
|
||||||
int streamtype;
|
int streamtype;
|
||||||
|
|
||||||
|
|
|
@ -161,6 +161,13 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Disable app markers if the colorspace enabled them.
|
||||||
|
xdpi/ydpi will still override this. */
|
||||||
|
if (context->no_default_app_segments) {
|
||||||
|
context->cinfo.write_JFIF_header = FALSE;
|
||||||
|
context->cinfo.write_Adobe_marker = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
/* Use custom quantization tables */
|
/* Use custom quantization tables */
|
||||||
if (context->qtables) {
|
if (context->qtables) {
|
||||||
int i;
|
int i;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user