mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-02 02:43:06 +03:00
Merge pull request #6774 from smason/write-jpeg-com
Support saving JPEG comments
This commit is contained in:
commit
378adeba3a
|
@ -86,6 +86,33 @@ class TestFileJpeg:
|
||||||
assert len(im.applist) == 2
|
assert len(im.applist) == 2
|
||||||
|
|
||||||
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"]
|
||||||
|
|
||||||
|
def test_comment_write(self):
|
||||||
|
with Image.open(TEST_FILE) as im:
|
||||||
|
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00"
|
||||||
|
|
||||||
|
# Test that existing comment is saved by default
|
||||||
|
out = BytesIO()
|
||||||
|
im.save(out, format="JPEG")
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert im.info["comment"] == reloaded.info["comment"]
|
||||||
|
|
||||||
|
# Ensure that a blank comment causes any existing comment to be removed
|
||||||
|
for comment in ("", b"", None):
|
||||||
|
out = BytesIO()
|
||||||
|
im.save(out, format="JPEG", comment=comment)
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert "comment" not in reloaded.info
|
||||||
|
|
||||||
|
# Test that a comment argument overrides the default comment
|
||||||
|
for comment in ("Test comment text", b"Text comment text"):
|
||||||
|
out = BytesIO()
|
||||||
|
im.save(out, format="JPEG", comment=comment)
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
if not isinstance(comment, bytes):
|
||||||
|
comment = comment.encode()
|
||||||
|
assert reloaded.info["comment"] == comment
|
||||||
|
|
||||||
def test_cmyk(self):
|
def test_cmyk(self):
|
||||||
# Test CMYK handling. Thanks to Tim and Charlie for test data,
|
# Test CMYK handling. Thanks to Tim and Charlie for test data,
|
||||||
|
|
|
@ -45,6 +45,7 @@ from . import Image, ImageFile, TiffImagePlugin
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
from ._binary import i32be as i32
|
from ._binary import i32be as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
|
from ._binary import o16be as o16
|
||||||
from ._deprecate import deprecate
|
from ._deprecate import deprecate
|
||||||
from .JpegPresets import presets
|
from .JpegPresets import presets
|
||||||
|
|
||||||
|
@ -724,7 +725,7 @@ def _save(im, fp, filename):
|
||||||
icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]
|
icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]
|
||||||
i = 1
|
i = 1
|
||||||
for marker in markers:
|
for marker in markers:
|
||||||
size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
|
size = o16(2 + ICC_OVERHEAD_LEN + len(marker))
|
||||||
extra += (
|
extra += (
|
||||||
b"\xFF\xE2"
|
b"\xFF\xE2"
|
||||||
+ size
|
+ size
|
||||||
|
@ -735,6 +736,8 @@ def _save(im, fp, filename):
|
||||||
)
|
)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
comment = info.get("comment", im.info.get("comment"))
|
||||||
|
|
||||||
# "progressive" is the official name, but older documentation
|
# "progressive" is the official name, but older documentation
|
||||||
# says "progression"
|
# says "progression"
|
||||||
# FIXME: issue a warning if the wrong form is used (post-1.1.7)
|
# FIXME: issue a warning if the wrong form is used (post-1.1.7)
|
||||||
|
@ -757,6 +760,7 @@ def _save(im, fp, filename):
|
||||||
dpi[1],
|
dpi[1],
|
||||||
subsampling,
|
subsampling,
|
||||||
qtables,
|
qtables,
|
||||||
|
comment,
|
||||||
extra,
|
extra,
|
||||||
exif,
|
exif,
|
||||||
)
|
)
|
||||||
|
|
32
src/encode.c
32
src/encode.c
|
@ -1048,6 +1048,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
PyObject *qtables = NULL;
|
PyObject *qtables = NULL;
|
||||||
unsigned int *qarrays = NULL;
|
unsigned int *qarrays = NULL;
|
||||||
int qtablesLen = 0;
|
int qtablesLen = 0;
|
||||||
|
char *comment = NULL;
|
||||||
|
Py_ssize_t comment_size;
|
||||||
char *extra = NULL;
|
char *extra = NULL;
|
||||||
Py_ssize_t extra_size;
|
Py_ssize_t extra_size;
|
||||||
char *rawExif = NULL;
|
char *rawExif = NULL;
|
||||||
|
@ -1055,7 +1057,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"ss|nnnnnnnnOy#y#",
|
"ss|nnnnnnnnOz#y#y#",
|
||||||
&mode,
|
&mode,
|
||||||
&rawmode,
|
&rawmode,
|
||||||
&quality,
|
&quality,
|
||||||
|
@ -1067,6 +1069,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
&ydpi,
|
&ydpi,
|
||||||
&subsampling,
|
&subsampling,
|
||||||
&qtables,
|
&qtables,
|
||||||
|
&comment,
|
||||||
|
&comment_size,
|
||||||
&extra,
|
&extra,
|
||||||
&extra_size,
|
&extra_size,
|
||||||
&rawExif,
|
&rawExif,
|
||||||
|
@ -1090,13 +1094,28 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Freed in JpegEncode, Case 5
|
// Freed in JpegEncode, Case 6
|
||||||
qarrays = get_qtables_arrays(qtables, &qtablesLen);
|
qarrays = get_qtables_arrays(qtables, &qtablesLen);
|
||||||
|
|
||||||
|
if (comment && comment_size > 0) {
|
||||||
|
/* malloc check ok, length is from python parsearg */
|
||||||
|
char *p = malloc(comment_size); // Freed in JpegEncode, Case 6
|
||||||
|
if (!p) {
|
||||||
|
return ImagingError_MemoryError();
|
||||||
|
}
|
||||||
|
memcpy(p, comment, comment_size);
|
||||||
|
comment = p;
|
||||||
|
} else {
|
||||||
|
comment = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (extra && extra_size > 0) {
|
if (extra && extra_size > 0) {
|
||||||
/* malloc check ok, length is from python parsearg */
|
/* malloc check ok, length is from python parsearg */
|
||||||
char *p = malloc(extra_size); // Freed in JpegEncode, Case 5
|
char *p = malloc(extra_size); // Freed in JpegEncode, Case 6
|
||||||
if (!p) {
|
if (!p) {
|
||||||
|
if (comment) {
|
||||||
|
free(comment);
|
||||||
|
}
|
||||||
return ImagingError_MemoryError();
|
return ImagingError_MemoryError();
|
||||||
}
|
}
|
||||||
memcpy(p, extra, extra_size);
|
memcpy(p, extra, extra_size);
|
||||||
|
@ -1107,8 +1126,11 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (rawExif && rawExifLen > 0) {
|
if (rawExif && rawExifLen > 0) {
|
||||||
/* malloc check ok, length is from python parsearg */
|
/* malloc check ok, length is from python parsearg */
|
||||||
char *pp = malloc(rawExifLen); // Freed in JpegEncode, Case 5
|
char *pp = malloc(rawExifLen); // Freed in JpegEncode, Case 6
|
||||||
if (!pp) {
|
if (!pp) {
|
||||||
|
if (comment) {
|
||||||
|
free(comment);
|
||||||
|
}
|
||||||
if (extra) {
|
if (extra) {
|
||||||
free(extra);
|
free(extra);
|
||||||
}
|
}
|
||||||
|
@ -1134,6 +1156,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype;
|
((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi;
|
((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi;
|
((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi;
|
||||||
|
((JPEGENCODERSTATE *)encoder->state.context)->comment = comment;
|
||||||
|
((JPEGENCODERSTATE *)encoder->state.context)->comment_size = comment_size;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->extra = extra;
|
((JPEGENCODERSTATE *)encoder->state.context)->extra = extra;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->extra_size = extra_size;
|
((JPEGENCODERSTATE *)encoder->state.context)->extra_size = extra_size;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->rawExif = rawExif;
|
((JPEGENCODERSTATE *)encoder->state.context)->rawExif = rawExif;
|
||||||
|
|
|
@ -92,6 +92,10 @@ typedef struct {
|
||||||
/* in factors of DCTSIZE2 */
|
/* in factors of DCTSIZE2 */
|
||||||
int qtablesLen;
|
int qtablesLen;
|
||||||
|
|
||||||
|
/* Comment */
|
||||||
|
char *comment;
|
||||||
|
size_t comment_size;
|
||||||
|
|
||||||
/* Extra data (to be injected after header) */
|
/* Extra data (to be injected after header) */
|
||||||
char *extra;
|
char *extra;
|
||||||
int extra_size;
|
int extra_size;
|
||||||
|
|
|
@ -277,6 +277,13 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 4:
|
case 4:
|
||||||
|
|
||||||
|
if (context->comment) {
|
||||||
|
jpeg_write_marker(&context->cinfo, JPEG_COM, (unsigned char *)context->comment, context->comment_size);
|
||||||
|
}
|
||||||
|
state->state++;
|
||||||
|
|
||||||
|
case 5:
|
||||||
if (1024 > context->destination.pub.free_in_buffer) {
|
if (1024 > context->destination.pub.free_in_buffer) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -301,7 +308,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
state->state++;
|
state->state++;
|
||||||
/* fall through */
|
/* fall through */
|
||||||
|
|
||||||
case 5:
|
case 6:
|
||||||
|
|
||||||
/* Finish compression */
|
/* Finish compression */
|
||||||
if (context->destination.pub.free_in_buffer < 100) {
|
if (context->destination.pub.free_in_buffer < 100) {
|
||||||
|
@ -310,6 +317,10 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
jpeg_finish_compress(&context->cinfo);
|
jpeg_finish_compress(&context->cinfo);
|
||||||
|
|
||||||
/* Clean up */
|
/* Clean up */
|
||||||
|
if (context->comment) {
|
||||||
|
free(context->comment);
|
||||||
|
context->comment = NULL;
|
||||||
|
}
|
||||||
if (context->extra) {
|
if (context->extra) {
|
||||||
free(context->extra);
|
free(context->extra);
|
||||||
context->extra = NULL;
|
context->extra = NULL;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user