Skip to content

Commit

Permalink
fix: Convert EXIF orientation to avif irot and imir
Browse files Browse the repository at this point in the history
  • Loading branch information
fdintino committed Dec 6, 2023
1 parent 8f0a315 commit ec4c25d
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 2 deletions.
13 changes: 12 additions & 1 deletion src/pillow_avif/AvifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from io import BytesIO
import sys

from PIL import Image, ImageFile
from PIL import ExifTags, Image, ImageFile

try:
from pillow_avif import _avif
Expand Down Expand Up @@ -146,6 +146,16 @@ def _save(im, fp, filename, save_all=False):
exif = info.get("exif", im.info.get("exif"))
if isinstance(exif, Image.Exif):
exif = exif.tobytes()

exif_orientation = 0
if exif:
exif_data = Image.Exif()
exif_data.load(exif)
orientation_tag = next(
k for k, v in ExifTags.TAGS.items() if v == "Orientation"
)
exif_orientation = exif_data.get(orientation_tag) or 0

xmp = info.get("xmp", im.info.get("xmp") or im.info.get("XML:com.adobe.xmp"))

if isinstance(xmp, text_type):
Expand Down Expand Up @@ -187,6 +197,7 @@ def _save(im, fp, filename, save_all=False):
autotiling,
icc_profile or b"",
exif or b"",
exif_orientation,
xmp or b"",
advanced,
)
Expand Down
80 changes: 79 additions & 1 deletion src/pillow_avif/_avif.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,81 @@ exc_type_for_avif_result(avifResult result) {
}
}

static void
exif_orientation_to_irot_imir(avifImage *image, int orientation) {
const avifTransformFlags otherFlags =
image->transformFlags & ~(AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR);
//
// Mapping from Exif orientation as defined in JEITA CP-3451C section 4.6.4.A
// Orientation to irot and imir boxes as defined in HEIF ISO/IEC 28002-12:2021
// sections 6.5.10 and 6.5.12.
switch (orientation) {
case 1: // The 0th row is at the visual top of the image, and the 0th column is
// the visual left-hand side.
image->transformFlags = otherFlags;
image->irot.angle = 0; // ignored
image->imir.axis = 0; // ignored
return;
case 2: // The 0th row is at the visual top of the image, and the 0th column is
// the visual right-hand side.
image->transformFlags = otherFlags | AVIF_TRANSFORM_IMIR;
image->irot.angle = 0; // ignored
image->imir.axis = 1;
return;
case 3: // The 0th row is at the visual bottom of the image, and the 0th column
// is the visual right-hand side.
image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT;
image->irot.angle = 2;
image->imir.axis = 0; // ignored
return;
case 4: // The 0th row is at the visual bottom of the image, and the 0th column
// is the visual left-hand side.
image->transformFlags = otherFlags | AVIF_TRANSFORM_IMIR;
image->irot.angle = 0; // ignored
image->imir.axis = 0;
return;
case 5: // The 0th row is the visual left-hand side of the image, and the 0th
// column is the visual top.
image->transformFlags =
otherFlags | AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR;
image->irot.angle = 1; // applied before imir according to MIAF spec
// ISO/IEC 28002-12:2021 - section 7.3.6.7
image->imir.axis = 0;
return;
case 6: // The 0th row is the visual right-hand side of the image, and the 0th
// column is the visual top.
image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT;
image->irot.angle = 3;
image->imir.axis = 0; // ignored
return;
case 7: // The 0th row is the visual right-hand side of the image, and the 0th
// column is the visual bottom.
image->transformFlags =
otherFlags | AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR;
image->irot.angle = 3; // applied before imir according to MIAF spec
// ISO/IEC 28002-12:2021 - section 7.3.6.7
image->imir.axis = 0;
return;
case 8: // The 0th row is the visual left-hand side of the image, and the 0th
// column is the visual bottom.
image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT;
image->irot.angle = 1;
image->imir.axis = 0; // ignored
return;
default: // reserved
break;
}

// The orientation tag is not mandatory (only recommended) according to JEITA
// CP-3451C section 4.6.8.A. The default value is 1 if the orientation tag is
// missing, meaning:
// The 0th row is at the visual top of the image, and the 0th column is the visual
// left-hand side.
image->transformFlags = otherFlags;
image->irot.angle = 0; // ignored
image->imir.axis = 0; // ignored
}

static int
_codec_available(const char *name, uint32_t flags) {
avifCodecChoice codec = avifCodecChoiceFromName(name);
Expand Down Expand Up @@ -208,6 +283,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
int qmax = 10; // "High Quality", but not lossless
int quality = 75;
int speed = 8;
int exif_orientation = 0;
PyObject *icc_bytes;
PyObject *exif_bytes;
PyObject *xmp_bytes;
Expand All @@ -223,7 +299,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {

if (!PyArg_ParseTuple(
args,
"IIsiiiissiiOOSSSO",
"IIsiiiissiiOOSSiSO",
&width,
&height,
&subsampling,
Expand All @@ -239,6 +315,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
&autotiling,
&icc_bytes,
&exif_bytes,
&exif_orientation,
&xmp_bytes,
&advanced)) {
return NULL;
Expand Down Expand Up @@ -404,6 +481,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
(uint8_t *)PyBytes_AS_STRING(xmp_bytes),
PyBytes_GET_SIZE(xmp_bytes));
}
exif_orientation_to_irot_imir(image, exif_orientation);

self->image = image;
self->frame_index = -1;
Expand Down

0 comments on commit ec4c25d

Please sign in to comment.