Skip to content

Commit

Permalink
added preferred-decoder option (#193)
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Piskun <[email protected]>
  • Loading branch information
bigcat88 authored Jan 6, 2024
1 parent 17e7bb2 commit e0815ad
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 14 deletions.
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

0 comments on commit e0815ad

Please sign in to comment.