diff --git a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs index 4c911cc7a..958ca85fd 100644 --- a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs +++ b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs @@ -130,7 +130,7 @@ namespace Emby.Drawing.ImageMagick string.Equals(ext, ".webp", StringComparison.OrdinalIgnoreCase); } - public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) + public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { // Even if the caller specified 100, don't use it because it takes forever quality = Math.Min(quality, 99); diff --git a/Emby.Drawing.Skia/SkiaEncoder.cs b/Emby.Drawing.Skia/SkiaEncoder.cs index 8a38b9248..2c7dc58c2 100644 --- a/Emby.Drawing.Skia/SkiaEncoder.cs +++ b/Emby.Drawing.Skia/SkiaEncoder.cs @@ -184,7 +184,7 @@ namespace Emby.Drawing.Skia } private string[] TransparentImageTypes = new string[] { ".png", ".gif", ".webp" }; - private SKBitmap Decode(string path, bool forceCleanBitmap = false) + private SKBitmap Decode(string path, bool forceCleanBitmap, out SKCodecOrigin origin) { var requiresTransparencyHack = TransparentImageTypes.Contains(Path.GetExtension(path) ?? string.Empty); @@ -199,6 +199,8 @@ namespace Emby.Drawing.Skia // decode codec.GetPixels(bitmap.Info, bitmap.GetPixels()); + origin = codec.Origin; + return bitmap; } } @@ -207,7 +209,7 @@ namespace Emby.Drawing.Skia if (resultBitmap == null) { - return Decode(path, true); + return Decode(path, true, out origin); } // If we have to resize these they often end up distorted @@ -215,27 +217,128 @@ namespace Emby.Drawing.Skia { using (resultBitmap) { - return Decode(path, true); + return Decode(path, true, out origin); } } + origin = SKCodecOrigin.TopLeft; return resultBitmap; } - private SKBitmap GetBitmap(string path, bool cropWhitespace) + private SKBitmap GetBitmap(string path, bool cropWhitespace, bool forceAnalyzeBitmap, out SKCodecOrigin origin) { if (cropWhitespace) { - using (var bitmap = Decode(path)) + using (var bitmap = Decode(path, forceAnalyzeBitmap, out origin)) { return CropWhiteSpace(bitmap); } } - return Decode(path); + return Decode(path, forceAnalyzeBitmap, out origin); } - public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) + private SKBitmap GetBitmap(string path, bool cropWhitespace, bool autoOrient, ImageOrientation? orientation) + { + SKCodecOrigin origin; + + if (autoOrient) + { + var bitmap = GetBitmap(path, cropWhitespace, true, out origin); + + if (origin != SKCodecOrigin.TopLeft) + { + using (bitmap) + { + return RotateAndFlip(bitmap, origin); + } + } + + return bitmap; + } + + return GetBitmap(path, cropWhitespace, false, out origin); + } + + private SKBitmap RotateAndFlip(SKBitmap original, SKCodecOrigin origin) + { + // these are the origins that represent a 90 degree turn in some fashion + var differentOrientations = new SKCodecOrigin[] + { + SKCodecOrigin.LeftBottom, + SKCodecOrigin.LeftTop, + SKCodecOrigin.RightBottom, + SKCodecOrigin.RightTop + }; + + // check if we need to turn the image + bool isDifferentOrientation = differentOrientations.Any(o => o == origin); + + // define new width/height + var width = isDifferentOrientation ? original.Height : original.Width; + var height = isDifferentOrientation ? original.Width : original.Height; + + var bitmap = new SKBitmap(width, height, true); + + // todo: the stuff in this switch statement should be rewritten to use pointers + switch (origin) + { + case SKCodecOrigin.LeftBottom: + + for (var x = 0; x < original.Width; x++) + for (var y = 0; y < original.Height; y++) + bitmap.SetPixel(y, original.Width - 1 - x, original.GetPixel(x, y)); + break; + + case SKCodecOrigin.RightTop: + + for (var x = 0; x < original.Width; x++) + for (var y = 0; y < original.Height; y++) + bitmap.SetPixel(original.Height - 1 - y, x, original.GetPixel(x, y)); + break; + + case SKCodecOrigin.RightBottom: + + for (var x = 0; x < original.Width; x++) + for (var y = 0; y < original.Height; y++) + bitmap.SetPixel(original.Height - 1 - y, original.Width - 1 - x, original.GetPixel(x, y)); + + break; + + case SKCodecOrigin.LeftTop: + + for (var x = 0; x < original.Width; x++) + for (var y = 0; y < original.Height; y++) + bitmap.SetPixel(y, x, original.GetPixel(x, y)); + break; + + case SKCodecOrigin.BottomLeft: + + for (var x = 0; x < original.Width; x++) + for (var y = 0; y < original.Height; y++) + bitmap.SetPixel(x, original.Height - 1 - y, original.GetPixel(x, y)); + break; + + case SKCodecOrigin.BottomRight: + + for (var x = 0; x < original.Width; x++) + for (var y = 0; y < original.Height; y++) + bitmap.SetPixel(original.Width - 1 - x, original.Height - 1 - y, original.GetPixel(x, y)); + break; + + case SKCodecOrigin.TopRight: + + for (var x = 0; x < original.Width; x++) + for (var y = 0; y < original.Height; y++) + bitmap.SetPixel(original.Width - 1 - x, y, original.GetPixel(x, y)); + break; + + } + + return bitmap; + } + + public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { if (string.IsNullOrWhiteSpace(inputPath)) { @@ -253,7 +356,7 @@ namespace Emby.Drawing.Skia var blur = options.Blur ?? 0; var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0); - using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace)) + using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation)) { if (bitmap == null) { @@ -265,7 +368,7 @@ namespace Emby.Drawing.Skia var originalImageSize = new ImageSize(bitmap.Width, bitmap.Height); ImageHelper.SaveImageSize(inputPath, dateModified, originalImageSize); - if (!options.CropWhiteSpace && options.HasDefaultOptions(inputPath, originalImageSize)) + if (!options.CropWhiteSpace && options.HasDefaultOptions(inputPath, originalImageSize) && !autoOrient) { // Just spit out the original file if all the options are default return inputPath; diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index a1543382f..eb5e0d82a 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -217,14 +217,23 @@ namespace Emby.Drawing dateModified = tuple.Item2; } - if (options.HasDefaultOptions(originalImagePath)) + var photo = item as Photo; + var autoOrient = false; + ImageOrientation? orientation = null; + if (photo != null && photo.Orientation.HasValue && photo.Orientation.Value != ImageOrientation.TopLeft) + { + autoOrient = true; + orientation = photo.Orientation; + } + + if (options.HasDefaultOptions(originalImagePath) && !autoOrient) { // Just spit out the original file if all the options are default return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } ImageSize? originalImageSize = GetSavedImageSize(originalImagePath, dateModified); - if (originalImageSize.HasValue && options.HasDefaultOptions(originalImagePath, originalImageSize.Value)) + if (originalImageSize.HasValue && options.HasDefaultOptions(originalImagePath, originalImageSize.Value) && !autoOrient) { // Just spit out the original file if all the options are default _logger.Info("Returning original image {0}", originalImagePath); @@ -243,7 +252,6 @@ namespace Emby.Drawing if (!_fileSystem.FileExists(cacheFilePath)) { - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath)); var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath)); @@ -252,13 +260,14 @@ namespace Emby.Drawing item = _libraryManager().GetItemById(options.ItemId); } - var resultPath =_imageEncoder.EncodeImage(originalImagePath, dateModified, tmpPath, AutoOrient(item), quality, options, outputFormat); + var resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, tmpPath, autoOrient, orientation, quality, options, outputFormat); if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) { return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath)); CopyFile(tmpPath, cacheFilePath); return new Tuple(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath)); @@ -288,17 +297,6 @@ namespace Emby.Drawing } } - private bool AutoOrient(IHasImages item) - { - var photo = item as Photo; - if (photo != null && photo.Orientation.HasValue) - { - return true; - } - - return false; - } - //private static int[][] OPERATIONS = new int[][] { // TopLeft //new int[] { 0, NONE}, diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index 2241c5a86..f04e8aaf1 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -32,7 +32,7 @@ namespace Emby.Drawing throw new NotImplementedException(); } - public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) + public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { throw new NotImplementedException(); } diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 9b895587f..131d0bd9e 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Controller.Drawing /// /// Encodes the image. /// - string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, int quality, ImageProcessingOptions options, ImageFormat outputFormat); + string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat); /// /// Creates the image collage.