From 3f1a272e90a25fd29a1c5fb36def2af8d2e1f99c Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 8 Jan 2026 15:41:13 -0800
Subject: [PATCH] Handle orientation metadata
This supports the orientation in TIFF files and the XMP orientation metadata in PNG files when using libpng. It does not currently support Exif metadata.
Fixes https://github.com/libsdl-org/SDL_image/issues/468
---
src/IMG.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++
src/IMG_ImageIO.m | 20 +++-----------
src/IMG_libpng.c | 23 +++++++++++++++++
src/IMG_tif.c | 40 +++++++++++++++++++++++++++-
src/IMG_utils.h | 23 +++++++++++++++++
5 files changed, 154 insertions(+), 18 deletions(-)
create mode 100644 src/IMG_utils.h
diff --git a/src/IMG.c b/src/IMG.c
index 9d07c120..ca913e9b 100644
--- a/src/IMG.c
+++ b/src/IMG.c
@@ -546,3 +546,69 @@ Uint64 IMG_TimebaseDuration(Uint64 pts, Uint64 duration, Uint64 src_numerator, U
Uint64 b = ( ( ( pts * 2 ) + 1 ) * src_numerator * dst_denominator ) / ( 2 * src_denominator * dst_numerator );
return (a - b);
}
+
+SDL_Surface *IMG_ApplyOrientation(SDL_Surface *surface, int orientation)
+{
+ SDL_Surface *tmp;
+ switch (orientation)
+ {
+ case 1:
+ // Normal (no rotation required)
+ break;
+ case 2:
+ // Flipped horizontally
+ if (!SDL_FlipSurface(surface, SDL_FLIP_HORIZONTAL)) {
+ SDL_DestroySurface(surface);
+ return NULL;
+ }
+ break;
+ case 3:
+ // Upside-down (180 degrees rotation)
+ tmp = SDL_RotateSurface(surface, 180.0f);
+ SDL_DestroySurface(surface);
+ surface = tmp;
+ break;
+ case 4:
+ // Flipped vertically
+ if (!SDL_FlipSurface(surface, SDL_FLIP_VERTICAL)) {
+ SDL_DestroySurface(surface);
+ return NULL;
+ }
+ break;
+ case 5:
+ // Flip horizontally and rotate 90 degrees counterclockwise
+ if (!SDL_FlipSurface(surface, SDL_FLIP_HORIZONTAL)) {
+ SDL_DestroySurface(surface);
+ return NULL;
+ }
+ tmp = SDL_RotateSurface(surface, -90.0f);
+ SDL_DestroySurface(surface);
+ surface = tmp;
+ break;
+ case 6:
+ // Rotate 90 degrees clockwise
+ tmp = SDL_RotateSurface(surface, 90.0f);
+ SDL_DestroySurface(surface);
+ surface = tmp;
+ break;
+ case 7:
+ // Flip horizontally and rotate 90 degrees clockwise
+ if (!SDL_FlipSurface(surface, SDL_FLIP_HORIZONTAL)) {
+ SDL_DestroySurface(surface);
+ return NULL;
+ }
+ tmp = SDL_RotateSurface(surface, 90.0f);
+ SDL_DestroySurface(surface);
+ surface = tmp;
+ break;
+ case 8:
+ // Rotate 90 degrees counterclockwise
+ tmp = SDL_RotateSurface(surface, -90.0f);
+ SDL_DestroySurface(surface);
+ surface = tmp;
+ break;
+ default:
+ break;
+ }
+ return surface;
+}
diff --git a/src/IMG_ImageIO.m b/src/IMG_ImageIO.m
index aa889fc3..d17d937a 100644
--- a/src/IMG_ImageIO.m
+++ b/src/IMG_ImageIO.m
@@ -11,6 +11,8 @@
#include <SDL3_image/SDL_image.h>
+#include "IMG_utils.h"
+
// Used because CGDataProviderCreate became deprecated in 10.5
#include <AvailabilityMacros.h>
#include <TargetConditionals.h>
@@ -355,25 +357,9 @@ static CFDictionaryRef CreateHintDictionary(CFStringRef uti_string_hint)
if (surface && properties) {
CFNumberRef numval;
if (CFDictionaryGetValueIfPresent(properties, kCGImagePropertyOrientation, (const void **)&numval)) {
- float rotation = 0.0f;
CGImagePropertyOrientation orientation;
CFNumberGetValue(numval, kCFNumberSInt32Type, &orientation);
- switch (orientation) {
- case kCGImagePropertyOrientationRight:
- rotation = 90.0f;
- break;
- case kCGImagePropertyOrientationDown:
- rotation = 180.0f;
- break;
- case kCGImagePropertyOrientationLeft:
- rotation = 270.0f;
- break;
- default:
- break;
- }
- if (rotation != 0.0f) {
- SDL_SetFloatProperty(SDL_GetSurfaceProperties(surface), SDL_PROP_SURFACE_ROTATION_FLOAT, rotation);
- }
+ surface = IMG_ApplyOrientation(surface, orientation);
}
}
return surface;
diff --git a/src/IMG_libpng.c b/src/IMG_libpng.c
index 55b7fc82..ffbad87b 100644
--- a/src/IMG_libpng.c
+++ b/src/IMG_libpng.c
@@ -32,6 +32,7 @@
#include "IMG_libpng.h"
#include "IMG_anim_encoder.h"
#include "IMG_anim_decoder.h"
+#include "IMG_utils.h"
#ifdef SDL_IMAGE_LIBPNG
#ifdef INCLUDE_PNG_FRAMEWORK
@@ -173,6 +174,7 @@ static struct
png_byte (*png_get_color_type)(png_const_structrp png_ptr, png_const_inforp info_ptr);
png_uint_32 (*png_get_image_width)(png_const_structrp png_ptr, png_const_inforp info_ptr);
png_uint_32 (*png_get_image_height)(png_const_structrp png_ptr, png_const_inforp info_ptr);
+ int (*png_get_text)(png_const_structp png_ptr, png_infop info_ptr, png_textp *text_ptr, int *num_text);
void (*png_write_flush)(png_structrp png_ptr);
} lib;
@@ -277,6 +279,7 @@ bool IMG_InitPNG(void)
FUNCTION_LOADER_LIBPNG(png_get_color_type, png_byte(*)(png_const_structrp png_ptr, png_const_inforp info_ptr))
FUNCTION_LOADER_LIBPNG(png_get_image_width, png_uint_32(*)(png_const_structrp png_ptr, png_const_inforp info_ptr))
FUNCTION_LOADER_LIBPNG(png_get_image_height, png_uint_32(*)(png_const_structrp png_ptr, png_const_inforp info_ptr))
+ FUNCTION_LOADER_LIBPNG(png_get_text, int (*)(png_const_structrp png_ptr, png_inforp info_ptr, png_textp *text_ptr, int *num_text))
FUNCTION_LOADER_LIBPNG(png_write_flush, void (*)(png_structrp png_ptr))
}
@@ -491,6 +494,26 @@ static bool LIBPNG_LoadPNG_IO_Internal(SDL_IOStream *src, struct png_load_vars *
}
#endif
+ png_textp text_ptr = NULL;
+ int num_text = 0;
+ if (lib.png_get_text(vars->png_ptr, vars->info_ptr, &text_ptr, &num_text) > 0) {
+ for (int i = 0; i < num_text; ++i, ++text_ptr) {
+ if (SDL_strcmp(text_ptr->key, "XML:com.adobe.xmp") == 0) {
+ // Look for tiff:Orientation in the XMP data
+ int orientation;
+ const char *value = SDL_strstr(text_ptr->text, "tiff:Orientation=\"");
+ if (value) {
+ value += 18;
+ orientation = (*value - '0');
+ vars->surface = IMG_ApplyOrientation(vars->surface, orientation);
+ if (!vars->surface) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
return true;
}
diff --git a/src/IMG_tif.c b/src/IMG_tif.c
index c2598d23..b16a0c53 100644
--- a/src/IMG_tif.c
+++ b/src/IMG_tif.c
@@ -176,6 +176,7 @@ SDL_Surface* IMG_LoadTIF_IO(SDL_IOStream * src)
TIFF* tiff = NULL;
SDL_Surface* surface = NULL;
Uint32 img_width, img_height;
+ Uint16 img_orientation = 1;
if ( !src ) {
/* The error message has been set in SDL_IOFromFile */
@@ -196,16 +197,53 @@ SDL_Surface* IMG_LoadTIF_IO(SDL_IOStream * src)
/* Retrieve the dimensions of the image from the TIFF tags */
lib.TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &img_width);
lib.TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &img_height);
+ lib.TIFFGetField(tiff, TIFFTAG_ORIENTATION, &img_orientation);
surface = SDL_CreateSurface(img_width, img_height, SDL_PIXELFORMAT_ABGR8888);
if(!surface)
goto error;
- if(!lib.TIFFReadRGBAImageOriented(tiff, img_width, img_height, (Uint32 *)surface->pixels, ORIENTATION_TOPLEFT, 0))
+ int load_orientation;
+ switch (img_orientation) {
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ load_orientation = ORIENTATION_TOPRIGHT;
+ break;
+ default:
+ load_orientation = ORIENTATION_TOPLEFT;
+ break;
+ }
+ if(!lib.TIFFReadRGBAImageOriented(tiff, img_width, img_height, (Uint32 *)surface->pixels, load_orientation, 0)) {
goto error;
+ }
lib.TIFFClose(tiff);
+ SDL_Surface *rotated;
+ switch (img_orientation) {
+ case 5:
+ case 7:
+ rotated = SDL_RotateSurface(surface, 270.0f);
+ if (!rotated) {
+ goto error;
+ }
+ SDL_DestroySurface(surface);
+ surface = rotated;
+ break;
+ case 6:
+ case 8:
+ rotated = SDL_RotateSurface(surface, 90.0f);
+ if (!rotated) {
+ goto error;
+ }
+ SDL_DestroySurface(surface);
+ surface = rotated;
+ break;
+ default:
+ break;
+ }
return surface;
error:
diff --git a/src/IMG_utils.h b/src/IMG_utils.h
new file mode 100644
index 00000000..b465dfa1
--- /dev/null
+++ b/src/IMG_utils.h
@@ -0,0 +1,23 @@
+/*
+ SDL_image: An example image loading library for use with SDL
+ Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+extern SDL_Surface *IMG_ApplyOrientation(SDL_Surface *surface, int orientation);
+