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

preferred-decoder option #193

Merged
merged 1 commit into from
Jan 6, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.

- `libheif_info` function: added `encoders` and `decoders` keys to the result, for future libheif plugins support. #189
- `options.PREFERRED_ENCODER` - to use `encoder` different from the default one. #192
- `options.PREFERRED_DECODER` - to use `decoder` different from the default one. #193

### Changed

Expand Down
1 change: 1 addition & 0 deletions docs/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Options
.. autodata:: pillow_heif.options.ALLOW_INCORRECT_HEADERS
.. autodata:: pillow_heif.options.SAVE_NCLX_PROFILE
.. autodata:: pillow_heif.options.PREFERRED_ENCODER
.. autodata:: pillow_heif.options.PREFERRED_DECODER

Example of use
""""""""""""""
Expand Down
25 changes: 16 additions & 9 deletions pillow_heif/_pillow_heif.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ typedef struct {
int remove_stride; // private. decode option.
int hdr_to_16bit; // private. decode option.
int reload_size; // private. decode option.
char decoder_id[64]; // private. decode option. optional
struct heif_image_handle *handle; // private
struct heif_image *heif_image; // private
const struct heif_depth_representation_info* depth_metadata; // only for image_type == 2
Expand Down Expand Up @@ -793,7 +794,8 @@ static void _CtxImage_destructor(CtxImageObject* self) {

PyObject* _CtxImage(struct heif_image_handle* handle, int hdr_to_8bit,
int bgr_mode, int remove_stride, int hdr_to_16bit,
int reload_size, int primary, PyObject* file_bytes) {
int reload_size, int primary, PyObject* file_bytes,
const char *decoder_id) {
CtxImageObject *ctx_image = PyObject_New(CtxImageObject, &CtxImage_Type);
if (!ctx_image) {
heif_image_handle_release(handle);
Expand Down Expand Up @@ -833,6 +835,7 @@ PyObject* _CtxImage(struct heif_image_handle* handle, int hdr_to_8bit,
ctx_image->primary = primary;
ctx_image->file_bytes = file_bytes;
ctx_image->stride = get_stride(ctx_image);
strcpy(ctx_image->decoder_id, decoder_id);
Py_INCREF(file_bytes);
return (PyObject*)ctx_image;
}
Expand Down Expand Up @@ -1050,6 +1053,9 @@ int decode_image(CtxImageObject* self) {
bytes_in_cc = 2;
}

if (strlen(self->decoder_id) > 0) {
decode_options->decoder_id = self->decoder_id;
}
error = heif_decode_image(self->handle, &self->heif_image, colorspace, chroma, decode_options);
heif_decoding_options_free(decode_options);
Py_END_ALLOW_THREADS
Expand Down Expand Up @@ -1172,11 +1178,9 @@ static PyObject* _CtxWrite(PyObject* self, PyObject* args) {
return NULL;

struct heif_context* ctx = heif_context_alloc();
if (strlen(encoder_id) > 0) {
if (heif_get_encoder_descriptors(heif_compression_undefined, encoder_id, encoders, 1) != 1) {
PyErr_SetString(PyExc_RuntimeError, "could not find encoder with provided ID");
return NULL;
}
if ((strlen(encoder_id) > 0) &&
(heif_get_encoder_descriptors(heif_compression_undefined, encoder_id, encoders, 1) == 1)
) {
error = heif_context_get_encoder(ctx, encoders[0], &encoder);
}
else {
Expand Down Expand Up @@ -1215,16 +1219,18 @@ static PyObject* _CtxWrite(PyObject* self, PyObject* args) {
static PyObject* _load_file(PyObject* self, PyObject* args) {
int hdr_to_8bit, threads_count, bgr_mode, remove_stride, hdr_to_16bit, reload_size;
PyObject *heif_bytes;
const char *decoder_id;

if (!PyArg_ParseTuple(args,
"Oiiiiii",
"Oiiiiiis",
&heif_bytes,
&threads_count,
&hdr_to_8bit,
&bgr_mode,
&remove_stride,
&hdr_to_16bit,
&reload_size))
&reload_size,
&decoder_id))
return NULL;

struct heif_context* heif_ctx = heif_context_alloc();
Expand Down Expand Up @@ -1272,7 +1278,8 @@ static PyObject* _load_file(PyObject* self, PyObject* args) {
PyList_SET_ITEM(images_list,
i,
_CtxImage(handle, hdr_to_8bit,
bgr_mode, remove_stride, hdr_to_16bit, reload_size, primary, heif_bytes));
bgr_mode, remove_stride, hdr_to_16bit, reload_size, primary, heif_bytes,
decoder_id));
else {
Py_INCREF(Py_None);
PyList_SET_ITEM(images_list, i, Py_None);
Expand Down
4 changes: 4 additions & 0 deletions pillow_heif/as_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ def __options_update(**kwargs):
options.ALLOW_INCORRECT_HEADERS = v
elif k == "save_nclx_profile":
options.SAVE_NCLX_PROFILE = v
elif k == "preferred_encoder":
options.PREFERRED_ENCODER = v
elif k == "preferred_decoder":
options.PREFERRED_DECODER = v
else:
warn(f"Unknown option: {k}", stacklevel=1)

Expand Down
7 changes: 7 additions & 0 deletions pillow_heif/heif.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,12 @@ def __init__(self, fp=None, convert_hdr_to_8bit=True, bgr_mode=False, **kwargs):
else:
fp_bytes = _get_bytes(fp)
mimetype = get_file_mimetype(fp_bytes)
if mimetype.find("avif") != -1:
preferred_decoder = options.PREFERRED_DECODER.get("AVIF", "")
elif mimetype.find("heic") != -1 or mimetype.find("heif") != -1:
preferred_decoder = options.PREFERRED_DECODER.get("HEIF", "")
else:
preferred_decoder = ""
images = _pillow_heif.load_file(
fp_bytes,
options.DECODE_THREADS,
Expand All @@ -231,6 +237,7 @@ def __init__(self, fp=None, convert_hdr_to_8bit=True, bgr_mode=False, **kwargs):
kwargs.get("remove_stride", True),
kwargs.get("hdr_to_16bit", True),
kwargs.get("reload_size", options.ALLOW_INCORRECT_HEADERS),
preferred_decoder,
)
self.mimetype = mimetype
self._images: List[HeifImage] = [HeifImage(i) for i in images if i is not None]
Expand Down
21 changes: 20 additions & 1 deletion pillow_heif/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,23 @@
"AVIF": "",
"HEIF": "",
}
"""Use the specified encoder for format. You can get the available encoders IDs using ``libheif_info()`` function."""
"""Use the specified encoder for format.

You can get the available encoders IDs using ``libheif_info()`` function.

When use pillow_heif as a plugin you can set this option with ``preferred_encoder`` key.

.. note:: If the specified encoder is missing, the option will be ignored."""


PREFERRED_DECODER = {
"AVIF": "",
"HEIF": "",
}
"""Use the specified decoder for format.

You can get the available decoders IDs using ``libheif_info()`` function.

When use pillow_heif as a plugin you can set this option with ``preferred_decoder`` key.

.. note:: If the specified decoder is missing, the option will be ignored."""
6 changes: 6 additions & 0 deletions tests/options_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,26 @@ def test_options_change_from_plugin_registering(register_opener):
decode_threads=3,
depth_images=False,
save_nclx_profile=False,
preferred_encoder={"HEIF": "id1", "AVIF": "id2"},
preferred_decoder={"HEIF": "id3", "AVIF": "id4"},
)
assert not options.THUMBNAILS
assert options.QUALITY == 69
assert options.SAVE_HDR_TO_12_BIT
assert options.DECODE_THREADS == 3
assert options.DEPTH_IMAGES is False
assert options.SAVE_NCLX_PROFILE is False
assert options.PREFERRED_ENCODER == {"HEIF": "id1", "AVIF": "id2"}
assert options.PREFERRED_DECODER == {"HEIF": "id3", "AVIF": "id4"}
finally:
options.THUMBNAILS = True
options.QUALITY = None
options.SAVE_HDR_TO_12_BIT = False
options.DECODE_THREADS = 4
options.DEPTH_IMAGES = True
options.SAVE_NCLX_PROFILE = True
options.PREFERRED_ENCODER = {"HEIF": "", "AVIF": ""}
options.PREFERRED_DECODER = {"HEIF": "", "AVIF": ""}


@pytest.mark.skipif(not hevc_enc(), reason="No HEVC encoder.")
Expand Down
17 changes: 17 additions & 0 deletions tests/read_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,3 +470,20 @@ def test_depth_image():
assert depth_image.info["metadata"]["disparity_reference_view"] == 0
assert depth_image.info["metadata"]["nonlinear_representation_model_size"] == 0
assert im_pil.info == depth_image.info


def test_invalid_decoder():
try:
pillow_heif.options.PREFERRED_DECODER["HEIF"] = "invalid_id"
Image.open("images/heif/RGB_8__128x128.heif").load()
finally:
pillow_heif.options.PREFERRED_DECODER["HEIF"] = ""


@pytest.mark.skipif("dav1d" not in pillow_heif.libheif_info()["decoders"], reason="Requires DAV1D AVIF decoder.")
def test_dav1d_decoder():
try:
pillow_heif.options.PREFERRED_DECODER["AVIF"] = "dav1d"
Image.open("images/heif/RGB_8__128x128.avif").load()
finally:
pillow_heif.options.PREFERRED_DECODER["AVIF"] = ""
6 changes: 2 additions & 4 deletions tests/write_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,10 +589,8 @@ def test_invalid_encoder():
try:
pillow_heif.options.PREFERRED_ENCODER["AVIF"] = "invalid_id"
pillow_heif.options.PREFERRED_ENCODER["HEIF"] = "invalid_id"
with pytest.raises(RuntimeError):
im_rgb.save(buf, format="AVIF")
with pytest.raises(RuntimeError):
im_rgb.save(buf, format="HEIF")
im_rgb.save(buf, format="AVIF")
im_rgb.save(buf, format="HEIF")
finally:
pillow_heif.options.PREFERRED_ENCODER["AVIF"] = ""
pillow_heif.options.PREFERRED_ENCODER["HEIF"] = ""
Expand Down