From b289b4cc7f547a982b9a06e54cd2fbc893e122bd Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 8 Apr 2015 11:45:30 -0400 Subject: [PATCH] complete gdi fallback --- Emby.Drawing/Emby.Drawing.csproj | 1 + Emby.Drawing/GDI/DynamicImageHelpers.cs | 138 ++++++++++++++++++ Emby.Drawing/GDI/GDIImageEncoder.cs | 18 ++- .../ImageMagick/StripCollageBuilder.cs | 2 +- .../Providers/IProviderManager.cs | 13 ++ .../Manager/ProviderManager.cs | 7 + .../Photos/BaseDynamicImageProvider.cs | 93 ++++-------- .../UserViews/DynamicImageProvider.cs | 12 +- 8 files changed, 215 insertions(+), 69 deletions(-) create mode 100644 Emby.Drawing/GDI/DynamicImageHelpers.cs diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj index 1907381e9c..0e368d70e9 100644 --- a/Emby.Drawing/Emby.Drawing.csproj +++ b/Emby.Drawing/Emby.Drawing.csproj @@ -48,6 +48,7 @@ Properties\SharedVersion.cs + diff --git a/Emby.Drawing/GDI/DynamicImageHelpers.cs b/Emby.Drawing/GDI/DynamicImageHelpers.cs new file mode 100644 index 0000000000..c49007c5fd --- /dev/null +++ b/Emby.Drawing/GDI/DynamicImageHelpers.cs @@ -0,0 +1,138 @@ +using Emby.Drawing.ImageMagick; +using MediaBrowser.Common.IO; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; + +namespace Emby.Drawing.GDI +{ + public static class DynamicImageHelpers + { + public static void CreateThumbCollage(List files, + IFileSystem fileSystem, + string file, + int width, + int height) + { + const int numStrips = 4; + files = StripCollageBuilder.ProjectPaths(files, numStrips).ToList(); + + const int rows = 1; + int cols = numStrips; + + int cellWidth = 2 * (width / 3); + int cellHeight = height; + var index = 0; + + using (var img = new Bitmap(width, height, PixelFormat.Format32bppPArgb)) + { + using (var graphics = Graphics.FromImage(img)) + { + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingMode = CompositingMode.SourceCopy; + + for (var row = 0; row < rows; row++) + { + for (var col = 0; col < cols; col++) + { + var x = col * (cellWidth / 2); + var y = row * cellHeight; + + if (files.Count > index) + { + using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true)) + { + using (var memoryStream = new MemoryStream()) + { + fileStream.CopyTo(memoryStream); + + memoryStream.Position = 0; + + using (var imgtemp = Image.FromStream(memoryStream, true, false)) + { + graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight); + } + } + } + } + + index++; + } + } + img.Save(file); + } + } + } + + public static void CreateSquareCollage(List files, + IFileSystem fileSystem, + string file, + int width, + int height) + { + files = StripCollageBuilder.ProjectPaths(files, 4).ToList(); + + const int rows = 2; + const int cols = 2; + + int singleSize = width / 2; + var index = 0; + + using (var img = new Bitmap(width, height, PixelFormat.Format32bppPArgb)) + { + using (var graphics = Graphics.FromImage(img)) + { + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingMode = CompositingMode.SourceCopy; + + for (var row = 0; row < rows; row++) + { + for (var col = 0; col < cols; col++) + { + var x = col * singleSize; + var y = row * singleSize; + + using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true)) + { + using (var memoryStream = new MemoryStream()) + { + fileStream.CopyTo(memoryStream); + + memoryStream.Position = 0; + + using (var imgtemp = Image.FromStream(memoryStream, true, false)) + { + graphics.DrawImage(imgtemp, x, y, singleSize, singleSize); + } + } + } + + index++; + } + } + img.Save(file); + } + } + } + + private static Stream GetStream(Image image) + { + var ms = new MemoryStream(); + + image.Save(ms, ImageFormat.Png); + + ms.Position = 0; + + return ms; + } + } +} diff --git a/Emby.Drawing/GDI/GDIImageEncoder.cs b/Emby.Drawing/GDI/GDIImageEncoder.cs index c6537fc436..33502c5e11 100644 --- a/Emby.Drawing/GDI/GDIImageEncoder.cs +++ b/Emby.Drawing/GDI/GDIImageEncoder.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.IO; +using System.Linq; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Logging; @@ -224,6 +225,21 @@ namespace Emby.Drawing.GDI public void CreateImageCollage(ImageCollageOptions options) { + double ratio = options.Width; + ratio /= options.Height; + + if (ratio >= 1.4) + { + DynamicImageHelpers.CreateThumbCollage(options.InputPaths.ToList(), _fileSystem, options.OutputPath, options.Width, options.Height); + } + else if (ratio >= .9) + { + DynamicImageHelpers.CreateSquareCollage(options.InputPaths.ToList(), _fileSystem, options.OutputPath, options.Width, options.Height); + } + else + { + DynamicImageHelpers.CreateSquareCollage(options.InputPaths.ToList(), _fileSystem, options.OutputPath, options.Width, options.Width); + } } public void Dispose() diff --git a/Emby.Drawing/ImageMagick/StripCollageBuilder.cs b/Emby.Drawing/ImageMagick/StripCollageBuilder.cs index d6e05a531a..7cdd0077de 100644 --- a/Emby.Drawing/ImageMagick/StripCollageBuilder.cs +++ b/Emby.Drawing/ImageMagick/StripCollageBuilder.cs @@ -69,7 +69,7 @@ namespace Emby.Drawing.ImageMagick } } - private string[] ProjectPaths(IEnumerable paths, int count) + internal static string[] ProjectPaths(IEnumerable paths, int count) { var clone = paths.ToList(); var list = new List(); diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index d40fa835fc..d6fc39c5f5 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -78,6 +78,19 @@ namespace MediaBrowser.Controller.Providers /// The cancellation token. /// Task. Task SaveImage(IHasImages item, Stream source, string mimeType, ImageType type, int? imageIndex, string internalCacheKey, CancellationToken cancellationToken); + + /// + /// Saves the image. + /// + /// The item. + /// The source. + /// Type of the MIME. + /// The type. + /// Index of the image. + /// The internal cache key. + /// The cancellation token. + /// Task. + Task SaveImage(IHasImages item, string source, string mimeType, ImageType type, int? imageIndex, string internalCacheKey, CancellationToken cancellationToken); /// /// Adds the metadata providers. diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index c9ae47ad0b..01a89bf26d 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -148,6 +148,13 @@ namespace MediaBrowser.Providers.Manager return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, internalCacheKey, cancellationToken); } + public Task SaveImage(IHasImages item, string source, string mimeType, ImageType type, int? imageIndex, string internalCacheKey, CancellationToken cancellationToken) + { + var fileStream = _fileSystem.GetFileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true); + + return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, internalCacheKey, cancellationToken); + } + public async Task> GetAvailableRemoteImages(IHasImages item, RemoteImageQuery query, CancellationToken cancellationToken) { var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders); diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 1e6189beb0..4c9a65cf62 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -6,7 +6,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; @@ -79,56 +78,23 @@ namespace MediaBrowser.Server.Implementations.Photos string cacheKey, CancellationToken cancellationToken) { - var stream = CreateImageAsync(item, itemsWithImages, imageType, 0); + var outputPath = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid() + ".png"); + Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); + var imageCreated = CreateImage(item, itemsWithImages, outputPath, imageType, 0); - if (stream == null) + if (!imageCreated) { return ItemUpdateType.None; } - if (stream is MemoryStream) - { - using (stream) - { - stream.Position = 0; - - await ProviderManager.SaveImage(item, stream, "image/png", imageType, null, cacheKey, cancellationToken).ConfigureAwait(false); - } - } - else - { - using (var ms = new MemoryStream()) - { - await stream.CopyToAsync(ms).ConfigureAwait(false); - - ms.Position = 0; - - await ProviderManager.SaveImage(item, ms, "image/png", imageType, null, cacheKey, cancellationToken).ConfigureAwait(false); - } - } + await ProviderManager.SaveImage(item, outputPath, "image/png", imageType, null, cacheKey, cancellationToken).ConfigureAwait(false); return ItemUpdateType.ImageUpdate; } - public async Task GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken) - { - var items = await GetItemsWithImages(item).ConfigureAwait(false); - var cacheKey = GetConfigurationCacheKey(items, item.Name); - - var result = CreateImageAsync(item, items, type, 0); - - return new DynamicImageResponse - { - HasImage = result != null, - Stream = result, - InternalCacheKey = cacheKey, - Format = ImageFormat.Png - }; - } - protected abstract Task> GetItemsWithImages(IHasImages item); - private const string Version = "20"; + private const string Version = "27"; protected string GetConfigurationCacheKey(List items, string itemName) { var parts = Version + "_" + (itemName ?? string.Empty) + "_" + @@ -137,39 +103,35 @@ namespace MediaBrowser.Server.Implementations.Photos return parts.GetMD5().ToString("N"); } - protected Stream GetThumbCollage(IHasImages primaryItem, List items) + protected void CreateThumbCollage(IHasImages primaryItem, List items, string outputPath) { - return GetThumbCollage(primaryItem, items, 960, 540, true, primaryItem.Name); + CreateCollage(primaryItem, items, outputPath, 960, 540, true, primaryItem.Name); } - protected virtual IEnumerable GetStripCollageImagePaths(IHasImages primaryItem, IEnumerable items) + protected virtual IEnumerable GetStripCollageImagePaths(IHasImages primaryItem, IEnumerable items) { return items .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)) .Where(i => !string.IsNullOrWhiteSpace(i)); } - protected Stream GetPosterCollage(IHasImages primaryItem, List items) + protected void CreatePosterCollage(IHasImages primaryItem, List items, string outputPath) { - var path = CreateCollage(primaryItem, items, 600, 900, true, primaryItem.Name); - return FileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + CreateCollage(primaryItem, items, outputPath, 600, 900, true, primaryItem.Name); } - protected Stream GetSquareCollage(IHasImages primaryItem, List items) + protected void CreateSquareCollage(IHasImages primaryItem, List items, string outputPath) { - var path = CreateCollage(primaryItem, items, 800, 800, true, primaryItem.Name); - return FileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + CreateCollage(primaryItem, items, outputPath, 800, 800, true, primaryItem.Name); } - protected Stream GetThumbCollage(IHasImages primaryItem, List items, int width, int height, bool drawText, string text) + protected void CreateThumbCollage(IHasImages primaryItem, List items, string outputPath, int width, int height, bool drawText, string text) { - var path = CreateCollage(primaryItem, items, width, height, drawText, text); - return FileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + CreateCollage(primaryItem, items, outputPath, width, height, drawText, text); } - private string CreateCollage(IHasImages primaryItem, List items, int width, int height, bool drawText, string text) + private void CreateCollage(IHasImages primaryItem, List items, string outputPath, int width, int height, bool drawText, string text) { - var outputPath = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid() + ".png"); Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); var options = new ImageCollageOptions @@ -182,8 +144,6 @@ namespace MediaBrowser.Server.Implementations.Photos }; ImageProcessor.CreateImageCollage(options); - - return outputPath; } public string Name @@ -191,26 +151,35 @@ namespace MediaBrowser.Server.Implementations.Photos get { return "Dynamic Image Provider"; } } - protected virtual Stream CreateImageAsync(IHasImages item, + protected virtual bool CreateImage(IHasImages item, List itemsWithImages, + string outputPath, ImageType imageType, int imageIndex) { if (itemsWithImages.Count == 0) { - return null; + return false; } if (imageType == ImageType.Thumb) { - return GetThumbCollage(item, itemsWithImages); + CreateThumbCollage(item, itemsWithImages, outputPath); + return true; } if (imageType == ImageType.Primary) { - return item is PhotoAlbum || item is Playlist ? - GetSquareCollage(item, itemsWithImages) : - GetPosterCollage(item, itemsWithImages); + if (item is PhotoAlbum || item is Playlist) + { + CreateSquareCollage(item, itemsWithImages, outputPath); + } + else + { + CreatePosterCollage(item, itemsWithImages, outputPath); + } + + return true; } throw new ArgumentException("Unexpected image type"); diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs index fc05f3169d..ad8c6e4146 100644 --- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -23,7 +23,8 @@ namespace MediaBrowser.Server.Implementations.UserViews private readonly IUserManager _userManager; private readonly ILibraryManager _libraryManager; - public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) + public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager) + : base(fileSystem, providerManager, applicationPaths, imageProcessor) { _userManager = userManager; _libraryManager = libraryManager; @@ -238,20 +239,21 @@ namespace MediaBrowser.Server.Implementations.UserViews return collectionStripViewTypes.Contains(view.ViewType ?? string.Empty); } - protected override Stream CreateImageAsync(IHasImages item, List itemsWithImages, ImageType imageType, int imageIndex) + protected override bool CreateImage(IHasImages item, List itemsWithImages, string outputPath, ImageType imageType, int imageIndex) { var view = (UserView)item; if (imageType == ImageType.Primary && IsUsingCollectionStrip(view)) { if (itemsWithImages.Count == 0 && !string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) { - return null; + return false; } - return GetThumbCollage(item, itemsWithImages, 960, 540, false, item.Name); + CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540, false, item.Name); + return true; } - return base.CreateImageAsync(item, itemsWithImages, imageType, imageIndex); + return base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex); } protected override IEnumerable GetStripCollageImagePaths(IHasImages primaryItem, IEnumerable items)