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