Skip to content

Commit

Permalink
Do not apply EXIF orientation when saving, since EXIF is already saved
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere committed Dec 2, 2024
1 parent c40bcbf commit 83553f9
Show file tree
Hide file tree
Showing 2 changed files with 2 additions and 131 deletions.
16 changes: 1 addition & 15 deletions src/PIL/AvifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from io import BytesIO
from typing import IO

from . import ExifTags, Image, ImageFile
from . import Image, ImageFile

try:
from . import _avif
Expand Down Expand Up @@ -172,19 +172,6 @@ def _save(
if isinstance(exif, Image.Exif):
exif = exif.tobytes()

exif_orientation = 0
if exif:
exif_data = Image.Exif()
try:
exif_data.load(exif)
except SyntaxError:
pass
else:
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, str):
Expand Down Expand Up @@ -228,7 +215,6 @@ def _save(
autotiling,
icc_profile or b"",
exif or b"",
exif_orientation,
xmp or b"",
advanced,
)
Expand Down
117 changes: 1 addition & 116 deletions src/_avif.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,118 +76,6 @@ 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
#if AVIF_VERSION_MAJOR >= 1
image->imir.axis = 0; // ignored
#else
image->imir.mode = 0; // ignored
#endif
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
#if AVIF_VERSION_MAJOR >= 1
image->imir.axis = 1;
#else
image->imir.mode = 1;
#endif
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;
#if AVIF_VERSION_MAJOR >= 1
image->imir.axis = 0; // ignored
#else
image->imir.mode = 0; // ignored
#endif
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
#if AVIF_VERSION_MAJOR >= 1
image->imir.axis = 0;
#else
image->imir.mode = 0;
#endif
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
#if AVIF_VERSION_MAJOR >= 1
image->imir.axis = 0;
#else
image->imir.mode = 0;
#endif
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;
#if AVIF_VERSION_MAJOR >= 1
image->imir.axis = 0; // ignored
#else
image->imir.mode = 0; // ignored
#endif
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
#if AVIF_VERSION_MAJOR >= 1
image->imir.axis = 0;
#else
image->imir.mode = 0;
#endif
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;
#if AVIF_VERSION_MAJOR >= 1
image->imir.axis = 0; // ignored
#else
image->imir.mode = 0; // ignored
#endif
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
#if AVIF_VERSION_MAJOR >= 1
image->imir.axis = 0; // ignored
#else
image->imir.mode = 0; // ignored
#endif
}

static int
_codec_available(const char *name, uint32_t flags) {
avifCodecChoice codec = avifCodecChoiceFromName(name);
Expand Down Expand Up @@ -270,7 +158,6 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
int qmax;
int quality;
int speed;
int exif_orientation;
int max_threads;
PyObject *icc_bytes;
PyObject *exif_bytes;
Expand All @@ -287,7 +174,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {

if (!PyArg_ParseTuple(
args,
"IIsiiiiissiiOOSSiSO",
"IIsiiiiissiiOOSSSO",
&width,
&height,
&subsampling,
Expand All @@ -304,7 +191,6 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
&autotiling,
&icc_bytes,
&exif_bytes,
&exif_orientation,
&xmp_bytes,
&advanced
)) {
Expand Down Expand Up @@ -500,7 +386,6 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
return NULL;
}
}
exif_orientation_to_irot_imir(image, exif_orientation);

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

0 comments on commit 83553f9

Please sign in to comment.