fix photo orientation

This commit is contained in:
Luke Pulverenti 2017-06-09 15:24:31 -04:00
parent e2ec83cbaa
commit d76bcd8473
5 changed files with 128 additions and 27 deletions

View File

@ -130,7 +130,7 @@ namespace Emby.Drawing.ImageMagick
string.Equals(ext, ".webp", StringComparison.OrdinalIgnoreCase); 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 // Even if the caller specified 100, don't use it because it takes forever
quality = Math.Min(quality, 99); quality = Math.Min(quality, 99);

View File

@ -184,7 +184,7 @@ namespace Emby.Drawing.Skia
} }
private string[] TransparentImageTypes = new string[] { ".png", ".gif", ".webp" }; 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); var requiresTransparencyHack = TransparentImageTypes.Contains(Path.GetExtension(path) ?? string.Empty);
@ -199,6 +199,8 @@ namespace Emby.Drawing.Skia
// decode // decode
codec.GetPixels(bitmap.Info, bitmap.GetPixels()); codec.GetPixels(bitmap.Info, bitmap.GetPixels());
origin = codec.Origin;
return bitmap; return bitmap;
} }
} }
@ -207,7 +209,7 @@ namespace Emby.Drawing.Skia
if (resultBitmap == null) if (resultBitmap == null)
{ {
return Decode(path, true); return Decode(path, true, out origin);
} }
// If we have to resize these they often end up distorted // If we have to resize these they often end up distorted
@ -215,27 +217,128 @@ namespace Emby.Drawing.Skia
{ {
using (resultBitmap) using (resultBitmap)
{ {
return Decode(path, true); return Decode(path, true, out origin);
} }
} }
origin = SKCodecOrigin.TopLeft;
return resultBitmap; return resultBitmap;
} }
private SKBitmap GetBitmap(string path, bool cropWhitespace) private SKBitmap GetBitmap(string path, bool cropWhitespace, bool forceAnalyzeBitmap, out SKCodecOrigin origin)
{ {
if (cropWhitespace) if (cropWhitespace)
{ {
using (var bitmap = Decode(path)) using (var bitmap = Decode(path, forceAnalyzeBitmap, out origin))
{ {
return CropWhiteSpace(bitmap); 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)) if (string.IsNullOrWhiteSpace(inputPath))
{ {
@ -253,7 +356,7 @@ namespace Emby.Drawing.Skia
var blur = options.Blur ?? 0; var blur = options.Blur ?? 0;
var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(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) if (bitmap == null)
{ {
@ -265,7 +368,7 @@ namespace Emby.Drawing.Skia
var originalImageSize = new ImageSize(bitmap.Width, bitmap.Height); var originalImageSize = new ImageSize(bitmap.Width, bitmap.Height);
ImageHelper.SaveImageSize(inputPath, dateModified, originalImageSize); 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 // Just spit out the original file if all the options are default
return inputPath; return inputPath;

View File

@ -217,14 +217,23 @@ namespace Emby.Drawing
dateModified = tuple.Item2; 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 // Just spit out the original file if all the options are default
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
} }
ImageSize? originalImageSize = GetSavedImageSize(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 // Just spit out the original file if all the options are default
_logger.Info("Returning original image {0}", originalImagePath); _logger.Info("Returning original image {0}", originalImagePath);
@ -243,7 +252,6 @@ namespace Emby.Drawing
if (!_fileSystem.FileExists(cacheFilePath)) if (!_fileSystem.FileExists(cacheFilePath))
{ {
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath));
var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath)); var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath));
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath));
@ -252,13 +260,14 @@ namespace Emby.Drawing
item = _libraryManager().GetItemById(options.ItemId); 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)) if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase))
{ {
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
} }
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath));
CopyFile(tmpPath, cacheFilePath); CopyFile(tmpPath, cacheFilePath);
return new Tuple<string, string, DateTime>(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath)); return new Tuple<string, string, DateTime>(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[][] { //private static int[][] OPERATIONS = new int[][] {
// TopLeft // TopLeft
//new int[] { 0, NONE}, //new int[] { 0, NONE},

View File

@ -32,7 +32,7 @@ namespace Emby.Drawing
throw new NotImplementedException(); 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(); throw new NotImplementedException();
} }

View File

@ -19,7 +19,7 @@ namespace MediaBrowser.Controller.Drawing
/// <summary> /// <summary>
/// Encodes the image. /// Encodes the image.
/// </summary> /// </summary>
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);
/// <summary> /// <summary>
/// Creates the image collage. /// Creates the image collage.