From de059c82aed1ce7d475b7197b310bee8fc88a82c Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Mon, 15 Jan 2018 22:21:49 -0700
Subject: [PATCH] Honour EXIF data when thumbnailing

Fixes #37
---
 .../services/thumbnail_service/thumbnailer.go | 34 +++++++++-
 .../turt2live/matrix-media-repo/util/exif.go  | 63 +++++++++++++++++++
 vendor/manifest                               |  6 ++
 3 files changed, 100 insertions(+), 3 deletions(-)
 create mode 100644 src/github.com/turt2live/matrix-media-repo/util/exif.go

diff --git a/src/github.com/turt2live/matrix-media-repo/services/thumbnail_service/thumbnailer.go b/src/github.com/turt2live/matrix-media-repo/services/thumbnail_service/thumbnailer.go
index deaf66a8..d95881e1 100644
--- a/src/github.com/turt2live/matrix-media-repo/services/thumbnail_service/thumbnailer.go
+++ b/src/github.com/turt2live/matrix-media-repo/services/thumbnail_service/thumbnailer.go
@@ -74,6 +74,15 @@ func (t *thumbnailer) GenerateThumbnail(media *types.Media, width int, height in
 		}
 	}
 
+	var orientation *util.ExifOrientation = nil
+	if media.ContentType == "image/jpeg" || media.ContentType == "image/jpg" {
+		orientation, err = util.GetExifOrientation(media)
+		if err != nil {
+			t.log.Warn("Non-fatal error getting EXIF orientation: " + err.Error())
+			orientation = nil // just in case
+		}
+	}
+
 	contentType := "image/png"
 	imgData := &bytes.Buffer{}
 	if animated && util.ArrayContains(animatedTypes, media.ContentType) {
@@ -106,7 +115,7 @@ func (t *thumbnailer) GenerateThumbnail(media *types.Media, width int, height in
 			draw.Draw(frameImg, img.Bounds(), img, image.ZP, draw.Over)
 
 			// Do the thumbnailing on the copied frame
-			frameThumb, err := thumbnailFrame(frameImg, method, width, height, imaging.Linear)
+			frameThumb, err := thumbnailFrame(frameImg, method, width, height, imaging.Linear, nil)
 			if err != nil {
 				t.log.Error("Error generating animated thumbnail frame: " + err.Error())
 				return nil, err
@@ -128,7 +137,7 @@ func (t *thumbnailer) GenerateThumbnail(media *types.Media, width int, height in
 			return nil, err
 		}
 	} else {
-		src, err = thumbnailFrame(src, method, width, height, imaging.Lanczos)
+		src, err = thumbnailFrame(src, method, width, height, imaging.Lanczos, orientation)
 		if err != nil {
 			t.log.Error("Error generating thumbnail: " + err.Error())
 			return nil, err
@@ -162,7 +171,7 @@ func (t *thumbnailer) GenerateThumbnail(media *types.Media, width int, height in
 	return thumb, nil
 }
 
-func thumbnailFrame(src image.Image, method string, width int, height int, filter imaging.ResampleFilter) (image.Image, error) {
+func thumbnailFrame(src image.Image, method string, width int, height int, filter imaging.ResampleFilter, orientation *util.ExifOrientation) (image.Image, error) {
 	var result image.Image
 	if method == "scale" {
 		result = imaging.Fit(src, width, height, filter)
@@ -172,5 +181,24 @@ func thumbnailFrame(src image.Image, method string, width int, height int, filte
 		return nil, errors.New("unrecognized method: " + method)
 	}
 
+	if orientation != nil {
+		// Rotate first
+		if orientation.RotateDegrees == 90 {
+			result = imaging.Rotate90(result)
+		} else if orientation.RotateDegrees == 180 {
+			result = imaging.Rotate180(result)
+		} else if orientation.RotateDegrees == 270 {
+			result = imaging.Rotate270(result)
+		} // else we don't care to rotate
+
+		// Flip second
+		if orientation.FlipHorizontal {
+			result = imaging.FlipH(result)
+		}
+		if orientation.FlipVertical {
+			result = imaging.FlipV(result)
+		}
+	}
+
 	return result, nil
 }
diff --git a/src/github.com/turt2live/matrix-media-repo/util/exif.go b/src/github.com/turt2live/matrix-media-repo/util/exif.go
new file mode 100644
index 00000000..d65ff0af
--- /dev/null
+++ b/src/github.com/turt2live/matrix-media-repo/util/exif.go
@@ -0,0 +1,63 @@
+package util
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/pkg/errors"
+	"github.com/rwcarlsen/goexif/exif"
+	"github.com/turt2live/matrix-media-repo/types"
+)
+
+type ExifOrientation struct {
+	RotateDegrees  int // should be 0, 90, 180, or 270
+	FlipVertical   bool
+	FlipHorizontal bool
+}
+
+func GetExifOrientation(media *types.Media) (*ExifOrientation, error) {
+	if media.ContentType != "image/jpeg" && media.ContentType != "image/jpg" {
+		return nil, errors.New("image is not a jpeg")
+	}
+
+	file, err := os.Open(media.Location)
+	if err != nil {
+		return nil, err
+	}
+
+	exifData, err := exif.Decode(file)
+	if err != nil {
+		return nil, err
+	}
+
+	rawValue, err := exifData.Get(exif.Orientation)
+	if err != nil {
+		return nil, err
+	}
+
+	orientation, err := rawValue.Int(0)
+	if err != nil {
+		return nil, err
+	}
+
+	if orientation < 1 || orientation > 8 {
+		return nil, errors.New(fmt.Sprintf("orientation out of range: %d", orientation))
+	}
+
+	flipHorizontal := orientation < 5 && (orientation%2) == 0
+	flipVertical := orientation > 4 && (orientation%2) != 0
+	degrees := 0
+
+	// TODO: There's probably a better way to represent this
+	if orientation == 1 || orientation == 2 {
+		degrees = 0
+	} else if orientation == 3 || orientation == 4 {
+		degrees = 180
+	} else if orientation == 5 || orientation == 6 {
+		degrees = 270
+	} else if orientation == 7 || degrees == 8 {
+		degrees = 90
+	}
+
+	return &ExifOrientation{degrees, flipVertical, flipHorizontal}, nil
+}
diff --git a/vendor/manifest b/vendor/manifest
index 319351c9..eb215136 100644
--- a/vendor/manifest
+++ b/vendor/manifest
@@ -134,6 +134,12 @@
 			"revision": "3bcf86f879c771238f8a67832a1af71308801a47",
 			"branch": "master"
 		},
+		{
+			"importpath": "github.com/rwcarlsen/goexif",
+			"repository": "https://github.com/rwcarlsen/goexif",
+			"revision": "17202558c8d9c3fd047859f1a5e73fd9ae709187",
+			"branch": "go1"
+		},
 		{
 			"importpath": "github.com/sirupsen/logrus",
 			"repository": "https://github.com/sirupsen/logrus",
-- 
GitLab