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 deaf66a87192e11a62e59de159feb34d6a617564..d95881e1e5aae035b2b4708b812874f18c069601 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 0000000000000000000000000000000000000000..d65ff0afd325b9167d2977b851f7e8357c699e57 --- /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 319351c9c74fcaac2d75e7cd4216b685a1982dfe..eb2151363ce10cd5c4649bd5296d875063d716c7 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",