From fd1b1353849cc45f79502b954f586ef7d889dac8 Mon Sep 17 00:00:00 2001
From: David Maciejak <david.maciejak@gmail.com>
Date: Thu, 29 May 2014 20:28:04 +0800
Subject: [PATCH 1/2] wrlib: add EXIF orientation feature

This patch is adding a RLoadOrientedImage that can be used
to extract the EXIF orientation field of a given image
(only JPEG for now), the resulting image is automatically flipped/rotated.
---
 wrlib/libwraster.map |   2 +
 wrlib/load.c         |  60 +++++++++++++-
 wrlib/misc.c         | 227 ++++++++++++++++++++++++++++++++++++++++++++++++---
 wrlib/wraster.h      |  19 +++++
 4 files changed, 294 insertions(+), 14 deletions(-)

diff --git a/wrlib/libwraster.map b/wrlib/libwraster.map
index cf36401..8dc5fc7 100644
--- a/wrlib/libwraster.map
+++ b/wrlib/libwraster.map
@@ -52,7 +52,9 @@ LIBWRASTER3
     RGetXImage;
     RHSVtoRGB;
     RLightImage;
+    RGetImageOrientation;
     RLoadImage;
+    RLoadOrientedImage;
     RMakeCenteredImage;
     RMakeTiledImage;
     RMessageForError;
diff --git a/wrlib/load.c b/wrlib/load.c
index 7c2e6af..cd09990 100644
--- a/wrlib/load.c
+++ b/wrlib/load.c
@@ -146,7 +146,7 @@ void RReleaseCache(void)
 	}
 }
 
-RImage *RLoadImage(RContext * context, const char *file, int index)
+static RImage *load_image(RContext *context, const char *file, int index, int orientation)
 {
 	RImage *image = NULL;
 	int i;
@@ -236,6 +236,49 @@ RImage *RLoadImage(RContext * context, const char *file, int index)
 		return NULL;
 	}
 
+	if (image && (orientation > ROrientationNormal)) {
+		RImage *tmp = NULL;
+		switch (orientation) {
+		case ROrientationFlipHorizontal:
+			tmp = RHorizontalFlipImage(image);
+			break;
+		case ROrientationRotate180:
+			tmp = RRotateImage(image, 180);
+			break;
+		case ROrientationFlipVertical:
+			tmp = RVerticalFlipImage(image);
+			break;
+		case ROrientationTranspose: {
+				RImage *tmp2;
+				tmp2 = RVerticalFlipImage(image);
+				if (tmp2) {
+					tmp = RRotateImage(tmp2, 90);
+					RReleaseImage(tmp2);
+				}
+			}
+			break;
+		case ROrientationRotate90:
+			tmp = RRotateImage(image, 90);
+			break;
+		case ROrientationTransverse: {
+				RImage *tmp2;
+				tmp2 = RVerticalFlipImage(image);
+				if (tmp2) {
+					tmp = RRotateImage(tmp2, 270);
+					RReleaseImage(tmp2);
+				}
+			}
+			break;
+		case ROrientationRotate270:
+			tmp = RRotateImage(image, 270);
+			break;
+		}
+		if (tmp) {
+			RReleaseImage(image);
+			image = tmp;
+		}
+	}
+
 	/* store image in cache */
 	if (RImageCacheSize > 0 && image &&
 	    (RImageCacheMaxImage == 0 || RImageCacheMaxImage >= image->width * image->height)) {
@@ -275,6 +318,17 @@ RImage *RLoadImage(RContext * context, const char *file, int index)
 	return image;
 }
 
+RImage *RLoadImage(RContext *context, const char *file, int index)
+{
+	return load_image(context, file, index, ROrientationNormal);
+}
+
+RImage *RLoadOrientedImage(RContext *context, const char *file, int index)
+{
+	int orientation = RGetImageOrientation(file);
+	return load_image(context, file, index, orientation);
+}
+
 char *RGetImageFileFormat(const char *file)
 {
 	switch (identFile(file)) {
@@ -370,8 +424,8 @@ static WRImgFormat identFile(const char *path)
 		return IM_GIF;
 
 	/* check for WEBP */
-	if (buffer[ 0] == 'R' && buffer[ 1] == 'I' && buffer[ 2] == 'F' && buffer[ 3] == 'F' &&
-	    buffer[ 8] == 'W' && buffer[ 9] == 'E' && buffer[10] == 'B' && buffer[11] == 'P' &&
+	if (buffer[0] == 'R' && buffer[1] == 'I' && buffer[2] == 'F' && buffer[3] == 'F' &&
+	    buffer[8] == 'W' && buffer[9] == 'E' && buffer[10] == 'B' && buffer[11] == 'P' &&
 	    buffer[12] == 'V' && buffer[13] == 'P' && buffer[14] == '8' &&
 	    (buffer[15] == ' '       /* Simple File Format (Lossy) */
 	     || buffer[15] == 'L'    /* Simple File Format (Lossless) */
diff --git a/wrlib/misc.c b/wrlib/misc.c
index 615777e..d5a63a5 100644
--- a/wrlib/misc.c
+++ b/wrlib/misc.c
@@ -30,7 +30,7 @@
 #include "convert.h"
 
 
-void RBevelImage(RImage * image, int bevel_type)
+void RBevelImage(RImage *image, int bevel_type)
 {
 	RColor color;
 	RColor cdelta;
@@ -83,7 +83,7 @@ void RBevelImage(RImage * image, int bevel_type)
 	}
 }
 
-void RFillImage(RImage * image, const RColor * color)
+void RFillImage(RImage *image, const RColor *color)
 {
 	unsigned char *d = image->data;
 	unsigned lineSize;
@@ -97,9 +97,8 @@ void RFillImage(RImage * image, const RColor * color)
 			*d++ = color->alpha;
 		}
 		lineSize = image->width * 4;
-		for (i = 1; i < image->height; i++, d += lineSize) {
+		for (i = 1; i < image->height; i++, d += lineSize)
 			memcpy(d, image->data, lineSize);
-		}
 	} else {
 		for (i = 0; i < image->width; i++) {
 			*d++ = color->red;
@@ -107,13 +106,12 @@ void RFillImage(RImage * image, const RColor * color)
 			*d++ = color->blue;
 		}
 		lineSize = image->width * 3;
-		for (i = 1; i < image->height; i++, d += lineSize) {
+		for (i = 1; i < image->height; i++, d += lineSize)
 			memcpy(d, image->data, lineSize);
-		}
 	}
 }
 
-void RClearImage(RImage * image, const RColor * color)
+void RClearImage(RImage *image, const RColor *color)
 {
 	unsigned char *d = image->data;
 	unsigned lineSize;
@@ -128,9 +126,8 @@ void RClearImage(RImage * image, const RColor * color)
 				*d++ = 0xff;
 			}
 			lineSize = image->width * 4;
-			for (i = 1; i < image->height; i++, d += lineSize) {
+			for (i = 1; i < image->height; i++, d += lineSize)
 				memcpy(d, image->data, lineSize);
-			}
 		} else {
 			for (i = 0; i < image->width; i++) {
 				*d++ = color->red;
@@ -138,9 +135,8 @@ void RClearImage(RImage * image, const RColor * color)
 				*d++ = color->blue;
 			}
 			lineSize = image->width * 3;
-			for (i = 1; i < image->height; i++, d += lineSize) {
+			for (i = 1; i < image->height; i++, d += lineSize)
 				memcpy(d, image->data, lineSize);
-			}
 		}
 	} else {
 		int bytes = image->width * image->height;
@@ -199,6 +195,215 @@ void RLightImage(RImage *image, const RColor *color)
 	}
 }
 
+/*
+	Based on jpegexiforient.c
+	Full src available at http://ftp.freebsd.org/pub/FreeBSD/distfiles/jpeg8b/jpegexiforient.c
+
+	Tested with img samples from http://github.com/recurser/exif-orientation-examples
+*/
+int RGetImageOrientation(const char *file)
+{
+	int c1, c2;
+	int set_flag = ROrientationUnknown;
+	unsigned int length, i;
+	/* Flag for byte order */
+	int is_motorola;
+	unsigned int exif_offset, offset, number_of_tags, tagnum;
+	FILE *myfile;
+	unsigned char exif_data[65536L];
+
+	myfile = fopen(file, "rb");
+	if (myfile == NULL)
+		return 0;
+
+	/* Read File head, check for JPEG SOI */
+	for (i = 0; i < 2; i++) {
+		int c;
+		c = getc(myfile);
+		if (c == EOF)
+			goto clean_return;
+		exif_data[i] = (unsigned char) c;
+	}
+	/* Modified from the original code as the marker could be located at any marker positions */
+	if (exif_data[0] != 0xFF || exif_data[1] != 0xD8)
+		goto clean_return;
+
+	/* search exif data marker APP1 0xFFE1 */
+	exif_offset = 2;
+	while (exif_offset) {
+		for (i = 0; i < 2; i++) {
+			int c;
+			c = getc(myfile);
+			if (c == EOF)
+				exif_offset = 0;
+			exif_data[i] = (unsigned char) c;
+		}
+
+		/* Get the marker parameter length count */
+		c1 = getc(myfile);
+		if (c1 == EOF)
+			exif_offset = 0;
+		c2 = getc(myfile);
+		if (c2 == EOF)
+			exif_offset = 0;
+		length = (((unsigned int) c1) << 8) + ((unsigned int) c2);
+
+		/* Length includes itself, so must be at least 2 */
+		/* Following Exif data length must be at least 6 */
+		if (length < 8)
+			exif_offset = 0;
+
+		exif_offset += 2;
+
+		/* No marker tag. */
+		if (exif_data[0] != 0xFF)
+			exif_offset = 0;
+
+		/* Exif if APP1 is found. */
+		if (exif_data[1] == 0xE1)
+			break;
+
+		exif_offset += length;
+
+		/* Some other marker found, seek to next one. */
+		if (-1 == fseek(myfile, length - 2, SEEK_CUR))
+			/* Can't seek. */
+			exif_offset = 0;
+	}
+
+	/* check if something went wrong */
+	if (!exif_offset)
+		goto clean_return;
+
+	length -= 8;
+	/* Read Exif head, check for "Exif" */
+	for (i = 0; i < 6; i++) {
+		int c;
+		c = getc(myfile);
+		if (c == EOF)
+			goto clean_return;
+		exif_data[i] = (unsigned char) c;
+	}
+
+	if (exif_data[0] != 0x45 || exif_data[1] != 0x78 || exif_data[2] != 0x69 ||
+		exif_data[3] != 0x66 || exif_data[4] != 0 || exif_data[5] != 0)
+		goto clean_return;
+
+	/* Read Exif body */
+	for (i = 0; i < length; i++) {
+		int c;
+		c = getc(myfile);
+		if (c == EOF)
+			goto clean_return;
+		exif_data[i] = (unsigned char) c;
+	}
+
+	/* Length of an IFD entry */
+	if (length < 12)
+		goto clean_return;
+
+	/* Discover byte order */
+	if (exif_data[0] == 0x49 && exif_data[1] == 0x49)
+		is_motorola = 0;
+	else {
+		if (exif_data[0] == 0x4D && exif_data[1] == 0x4D)
+			is_motorola = 1;
+		else
+		    goto clean_return;
+	}
+
+	/* Check Tag Mark */
+	if (is_motorola) {
+		if (exif_data[2] != 0)
+			goto clean_return;
+		if (exif_data[3] != 0x2A)
+			goto clean_return;
+	} else {
+		if (exif_data[3] != 0)
+			goto clean_return;
+		if (exif_data[2] != 0x2A)
+			goto clean_return;
+	}
+
+	/* Get first IFD offset (offset to IFD0) */
+	if (is_motorola) {
+		if (exif_data[4] != 0)
+			goto clean_return;
+		if (exif_data[5] != 0)
+			goto clean_return;
+		offset = exif_data[6];
+		offset <<= 8;
+		offset += exif_data[7];
+	} else {
+		if (exif_data[7] != 0)
+			goto clean_return;
+		if (exif_data[6] != 0)
+			goto clean_return;
+		offset = exif_data[5];
+		offset <<= 8;
+		offset += exif_data[4];
+	}
+
+	/* check end of data segment */
+	if (offset > length - 2)
+		goto clean_return;
+
+	/* Get the number of directory entries contained in this IFD */
+	if (is_motorola) {
+		number_of_tags = exif_data[offset];
+		number_of_tags <<= 8;
+		number_of_tags += exif_data[offset+1];
+	} else {
+		number_of_tags = exif_data[offset+1];
+		number_of_tags <<= 8;
+		number_of_tags += exif_data[offset];
+	}
+
+	if (number_of_tags == 0)
+		goto clean_return;
+	offset += 2;
+
+	/* Search for Orientation Tag in IFD0 */
+	for (;;) {
+		/* check end of data segment */
+		if (offset > length - 12)
+			goto clean_return;
+		/* Get Tag number */
+		if (is_motorola) {
+			tagnum = exif_data[offset];
+			tagnum <<= 8;
+			tagnum += exif_data[offset+1];
+		} else {
+			tagnum = exif_data[offset+1];
+			tagnum <<= 8;
+			tagnum += exif_data[offset];
+		}
+
+		/* found Orientation Tag */
+		if (tagnum == 0x0112)
+			break;
+		if (--number_of_tags == 0)
+			goto clean_return;
+		offset += 12;
+	}
+
+	/* Get the Orientation value */
+	if (is_motorola) {
+		if (exif_data[offset+8] != 0)
+			goto clean_return;
+		set_flag = exif_data[offset+9];
+		} else {
+			if (exif_data[offset+9] != 0)
+				goto clean_return;
+			set_flag = exif_data[offset+8];
+		}
+		if (set_flag > 8)
+			return ROrientationUnknown;
+clean_return:
+	fclose(myfile);
+	return set_flag;
+}
+
 const char *RMessageForError(int errorCode)
 {
 	switch (errorCode) {
diff --git a/wrlib/wraster.h b/wrlib/wraster.h
index 45cbcc6..ee460bc 100644
--- a/wrlib/wraster.h
+++ b/wrlib/wraster.h
@@ -264,6 +264,19 @@ enum {
     RVerticalGradient = 3,
     RDiagonalGradient = 4
 };
+
+enum {
+	ROrientationUnknown = 0,
+	ROrientationNormal = 1,
+	ROrientationFlipHorizontal = 2,
+	ROrientationRotate180 = 3,
+	ROrientationFlipVertical = 4,
+	ROrientationTranspose = 5,
+	ROrientationRotate90 = 6,
+	ROrientationTransverse = 7,
+	ROrientationRotate270 = 8
+};
+
 /* for backwards compatibility */
 #define RGRD_HORIZONTAL  RHorizontalGradient
 #define RGRD_VERTICAL 	RVerticalGradient
@@ -326,6 +339,8 @@ RImage *RCreateImageFromDrawable(RContext *context, Drawable drawable,
 
 RImage *RLoadImage(RContext *context, const char *file, int index);
 
+RImage *RLoadOrientedImage(RContext *context, const char *file, int index);
+
 RImage* RRetainImage(RImage *image);
 
 void RReleaseImage(RImage *image);
@@ -369,6 +384,8 @@ RImage *RScaleImage(RImage *image, unsigned new_width, unsigned new_height);
 RImage *RSmoothScaleImage(RImage *src, unsigned new_width,
                           unsigned new_height);
 
+int RGetImageOrientation(const char *file);
+
 RImage *RRotateImage(RImage *image, float angle);
 
 RImage *RVerticalFlipImage(RImage *image);
@@ -472,6 +489,8 @@ const char *RMessageForError(int errorCode);
 
 int RBlurImage(RImage *image);
 
+int RGetImageOrientation(const char *file);
+
 /****** Global Variables *******/
 
 extern int RErrorCode;
-- 
1.8.3.2

