@@ -139,6 +139,118 @@ exc_type_for_avif_result(avifResult result) {
139139 }
140140}
141141
142+ static void
143+ exif_orientation_to_irot_imir (avifImage * image , int orientation ) {
144+ const avifTransformFlags otherFlags =
145+ image -> transformFlags & ~(AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR );
146+
147+ //
148+ // Mapping from Exif orientation as defined in JEITA CP-3451C section 4.6.4.A
149+ // Orientation to irot and imir boxes as defined in HEIF ISO/IEC 28002-12:2021
150+ // sections 6.5.10 and 6.5.12.
151+ switch (orientation ) {
152+ case 1 : // The 0th row is at the visual top of the image, and the 0th column is
153+ // the visual left-hand side.
154+ image -> transformFlags = otherFlags ;
155+ image -> irot .angle = 0 ; // ignored
156+ #if AVIF_VERSION_MAJOR >= 1
157+ image -> imir .axis = 0 ; // ignored
158+ #else
159+ image -> imir .mode = 0 ; // ignored
160+ #endif
161+ return ;
162+ case 2 : // The 0th row is at the visual top of the image, and the 0th column is
163+ // the visual right-hand side.
164+ image -> transformFlags = otherFlags | AVIF_TRANSFORM_IMIR ;
165+ image -> irot .angle = 0 ; // ignored
166+ #if AVIF_VERSION_MAJOR >= 1
167+ image -> imir .axis = 1 ;
168+ #else
169+ image -> imir .mode = 1 ;
170+ #endif
171+ return ;
172+ case 3 : // The 0th row is at the visual bottom of the image, and the 0th column
173+ // is the visual right-hand side.
174+ image -> transformFlags = otherFlags | AVIF_TRANSFORM_IROT ;
175+ image -> irot .angle = 2 ;
176+ #if AVIF_VERSION_MAJOR >= 1
177+ image -> imir .axis = 0 ; // ignored
178+ #else
179+ image -> imir .mode = 0 ; // ignored
180+ #endif
181+ return ;
182+ case 4 : // The 0th row is at the visual bottom of the image, and the 0th column
183+ // is the visual left-hand side.
184+ image -> transformFlags = otherFlags | AVIF_TRANSFORM_IMIR ;
185+ image -> irot .angle = 0 ; // ignored
186+ #if AVIF_VERSION_MAJOR >= 1
187+ image -> imir .axis = 0 ;
188+ #else
189+ image -> imir .mode = 0 ;
190+ #endif
191+ return ;
192+ case 5 : // The 0th row is the visual left-hand side of the image, and the 0th
193+ // column is the visual top.
194+ image -> transformFlags =
195+ otherFlags | AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR ;
196+ image -> irot .angle = 1 ; // applied before imir according to MIAF spec
197+ // ISO/IEC 28002-12:2021 - section 7.3.6.7
198+ #if AVIF_VERSION_MAJOR >= 1
199+ image -> imir .axis = 0 ;
200+ #else
201+ image -> imir .mode = 0 ;
202+ #endif
203+ return ;
204+ case 6 : // The 0th row is the visual right-hand side of the image, and the 0th
205+ // column is the visual top.
206+ image -> transformFlags = otherFlags | AVIF_TRANSFORM_IROT ;
207+ image -> irot .angle = 3 ;
208+ #if AVIF_VERSION_MAJOR >= 1
209+ image -> imir .axis = 0 ; // ignored
210+ #else
211+ image -> imir .mode = 0 ; // ignored
212+ #endif
213+ return ;
214+ case 7 : // The 0th row is the visual right-hand side of the image, and the 0th
215+ // column is the visual bottom.
216+ image -> transformFlags =
217+ otherFlags | AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR ;
218+ image -> irot .angle = 3 ; // applied before imir according to MIAF spec
219+ // ISO/IEC 28002-12:2021 - section 7.3.6.7
220+ #if AVIF_VERSION_MAJOR >= 1
221+ image -> imir .axis = 0 ;
222+ #else
223+ image -> imir .mode = 0 ;
224+ #endif
225+ return ;
226+ case 8 : // The 0th row is the visual left-hand side of the image, and the 0th
227+ // column is the visual bottom.
228+ image -> transformFlags = otherFlags | AVIF_TRANSFORM_IROT ;
229+ image -> irot .angle = 1 ;
230+ #if AVIF_VERSION_MAJOR >= 1
231+ image -> imir .axis = 0 ; // ignored
232+ #else
233+ image -> imir .mode = 0 ; // ignored
234+ #endif
235+ return ;
236+ default : // reserved
237+ break ;
238+ }
239+
240+ // The orientation tag is not mandatory (only recommended) according to JEITA
241+ // CP-3451C section 4.6.8.A. The default value is 1 if the orientation tag is
242+ // missing, meaning:
243+ // The 0th row is at the visual top of the image, and the 0th column is the visual
244+ // left-hand side.
245+ image -> transformFlags = otherFlags ;
246+ image -> irot .angle = 0 ; // ignored
247+ #if AVIF_VERSION_MAJOR >= 1
248+ image -> imir .axis = 0 ; // ignored
249+ #else
250+ image -> imir .mode = 0 ; // ignored
251+ #endif
252+ }
253+
142254static int
143255_codec_available (const char * name , uint32_t flags ) {
144256 avifCodecChoice codec = avifCodecChoiceFromName (name );
@@ -208,6 +320,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
208320 int qmax = 10 ; // "High Quality", but not lossless
209321 int quality = 75 ;
210322 int speed = 8 ;
323+ int exif_orientation = 0 ;
211324 PyObject * icc_bytes ;
212325 PyObject * exif_bytes ;
213326 PyObject * xmp_bytes ;
@@ -223,7 +336,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
223336
224337 if (!PyArg_ParseTuple (
225338 args ,
226- "IIsiiiissiiOOSSSO " ,
339+ "IIsiiiissiiOOSSiSO " ,
227340 & width ,
228341 & height ,
229342 & subsampling ,
@@ -239,6 +352,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
239352 & autotiling ,
240353 & icc_bytes ,
241354 & exif_bytes ,
355+ & exif_orientation ,
242356 & xmp_bytes ,
243357 & advanced )) {
244358 return NULL ;
@@ -404,6 +518,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
404518 (uint8_t * )PyBytes_AS_STRING (xmp_bytes ),
405519 PyBytes_GET_SIZE (xmp_bytes ));
406520 }
521+ exif_orientation_to_irot_imir (image , exif_orientation );
407522
408523 self -> image = image ;
409524 self -> frame_index = -1 ;
0 commit comments