Browse Source

A few more image improvements

LukePulverenti Luke Pulverenti luke pulverenti 12 years ago
parent
commit
bd6c2d2a22

+ 46 - 92
MediaBrowser.Api/HttpHandlers/ImageHandler.cs

@@ -1,6 +1,4 @@
-using System.Drawing.Imaging;
-using MediaBrowser.Common.Logging;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Drawing;
@@ -24,6 +22,7 @@ namespace MediaBrowser.Api.HttpHandlers
         }
 
         private string _imagePath;
+
         private async Task<string> GetImagePath()
         {
             _imagePath = _imagePath ?? await DiscoverImagePath();
@@ -32,28 +31,34 @@ namespace MediaBrowser.Api.HttpHandlers
         }
 
         private BaseEntity _sourceEntity;
+
         private async Task<BaseEntity> GetSourceEntity()
         {
             if (_sourceEntity == null)
             {
                 if (!string.IsNullOrEmpty(QueryString["personname"]))
                 {
-                    _sourceEntity = await Kernel.Instance.ItemController.GetPerson(QueryString["personname"]).ConfigureAwait(false);
+                    _sourceEntity =
+                        await Kernel.Instance.ItemController.GetPerson(QueryString["personname"]).ConfigureAwait(false);
                 }
 
                 else if (!string.IsNullOrEmpty(QueryString["genre"]))
                 {
-                    _sourceEntity = await Kernel.Instance.ItemController.GetGenre(QueryString["genre"]).ConfigureAwait(false);
+                    _sourceEntity =
+                        await Kernel.Instance.ItemController.GetGenre(QueryString["genre"]).ConfigureAwait(false);
                 }
 
                 else if (!string.IsNullOrEmpty(QueryString["year"]))
                 {
-                    _sourceEntity = await Kernel.Instance.ItemController.GetYear(int.Parse(QueryString["year"])).ConfigureAwait(false);
+                    _sourceEntity =
+                        await
+                        Kernel.Instance.ItemController.GetYear(int.Parse(QueryString["year"])).ConfigureAwait(false);
                 }
 
                 else if (!string.IsNullOrEmpty(QueryString["studio"]))
                 {
-                    _sourceEntity = await Kernel.Instance.ItemController.GetStudio(QueryString["studio"]).ConfigureAwait(false);
+                    _sourceEntity =
+                        await Kernel.Instance.ItemController.GetStudio(QueryString["studio"]).ConfigureAwait(false);
                 }
 
                 else if (!string.IsNullOrEmpty(QueryString["userid"]))
@@ -74,85 +79,62 @@ namespace MediaBrowser.Api.HttpHandlers
         {
             var entity = await GetSourceEntity().ConfigureAwait(false);
 
-            var item = entity as BaseItem;
+            return ImageProcessor.GetImagePath(entity, ImageType, ImageIndex);
+        }
 
-            if (item != null)
+        public override async Task<string> GetContentType()
+        {
+            if (Kernel.Instance.ImageProcessors.Any(i => i.RequiresTransparency))
             {
-                return GetImagePathFromTypes(item, ImageType, ImageIndex);
+                return MimeTypes.GetMimeType(".png");
             }
 
-            return entity.PrimaryImagePath;
+            return MimeTypes.GetMimeType(await GetImagePath().ConfigureAwait(false));
         }
 
-        private Stream _sourceStream;
-        private async Task<Stream> GetSourceStream()
+        public override TimeSpan CacheDuration
         {
-            await EnsureSourceStream().ConfigureAwait(false);
-            return _sourceStream;
+            get { return TimeSpan.FromDays(365); }
         }
 
-        private bool _sourceStreamEnsured;
-        private async Task EnsureSourceStream()
+        protected override async Task<DateTime?> GetLastDateModified()
         {
-            if (!_sourceStreamEnsured)
+            string path = await GetImagePath().ConfigureAwait(false);
+
+            DateTime date = File.GetLastWriteTimeUtc(path);
+
+            // If the file does not exist it will return jan 1, 1601
+            // http://msdn.microsoft.com/en-us/library/system.io.file.getlastwritetimeutc.aspx
+            if (date.Year == 1601)
             {
-                try
-                {
-                    _sourceStream = File.OpenRead(await GetImagePath().ConfigureAwait(false));
-                }
-                catch (FileNotFoundException ex)
-                {
-                    StatusCode = 404;
-                    Logger.LogException(ex);
-                }
-                catch (DirectoryNotFoundException ex)
+                if (!File.Exists(path))
                 {
                     StatusCode = 404;
-                    Logger.LogException(ex);
-                }
-                catch (UnauthorizedAccessException ex)
-                {
-                    StatusCode = 403;
-                    Logger.LogException(ex);
-                }
-                finally
-                {
-                    _sourceStreamEnsured = true;
+                    return null;
                 }
             }
-        }
-
-        public async override Task<string> GetContentType()
-        {
-            if (await GetSourceStream().ConfigureAwait(false) == null)
-            {
-                return null;
-            }
 
-            if (Kernel.Instance.ImageProcessors.Any(i => i.RequiresTransparency))
-            {
-                return MimeTypes.GetMimeType(".png");
-            }
-
-            return MimeTypes.GetMimeType(await GetImagePath().ConfigureAwait(false));
+            return await GetMostRecentDateModified(date);
         }
 
-        public override TimeSpan CacheDuration
+        private async Task<DateTime> GetMostRecentDateModified(DateTime imageFileLastDateModified)
         {
-            get
-            {
-                return TimeSpan.FromDays(365);
-            }
-        }
+            var date = imageFileLastDateModified;
 
-        protected async override Task<DateTime?> GetLastDateModified()
-        {
-            if (await GetSourceStream().ConfigureAwait(false) == null)
+            var entity = await GetSourceEntity().ConfigureAwait(false);
+            
+            foreach (var processor in Kernel.Instance.ImageProcessors)
             {
-                return null;
+                if (processor.IsConfiguredToProcess(entity, ImageType, ImageIndex))
+                {
+                    if (processor.ProcessingConfigurationDateLastModifiedUtc > date)
+                    {
+                        date = processor.ProcessingConfigurationDateLastModifiedUtc;
+                    }
+                }
             }
 
-            return File.GetLastWriteTimeUtc(await GetImagePath().ConfigureAwait(false));
+            return date;
         }
 
         private int ImageIndex
@@ -262,37 +244,9 @@ namespace MediaBrowser.Api.HttpHandlers
 
         protected override async Task WriteResponseToOutputStream(Stream stream)
         {
-            Stream sourceStream = await GetSourceStream().ConfigureAwait(false);
-
             var entity = await GetSourceEntity().ConfigureAwait(false);
 
-            ImageProcessor.ProcessImage(sourceStream, stream, Width, Height, MaxWidth, MaxHeight, Quality, entity, ImageType, ImageIndex);
-        }
-
-        private string GetImagePathFromTypes(BaseItem item, ImageType imageType, int imageIndex)
-        {
-            if (imageType == ImageType.Logo)
-            {
-                return item.LogoImagePath;
-            }
-            if (imageType == ImageType.Backdrop)
-            {
-                return item.BackdropImagePaths.ElementAt(imageIndex);
-            }
-            if (imageType == ImageType.Banner)
-            {
-                return item.BannerImagePath;
-            }
-            if (imageType == ImageType.Art)
-            {
-                return item.ArtImagePath;
-            }
-            if (imageType == ImageType.Thumbnail)
-            {
-                return item.ThumbnailImagePath;
-            }
-
-            return item.PrimaryImagePath;
+            ImageProcessor.ProcessImage(entity, ImageType, ImageIndex, stream, Width, Height, MaxWidth, MaxHeight, Quality);
         }
     }
 }

+ 35 - 20
MediaBrowser.Controller/Drawing/BaseImageProcessor.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Entities;
+using System;
 using System.ComponentModel.Composition;
 using System.Drawing;
 using System.Drawing.Drawing2D;
@@ -15,16 +16,7 @@ namespace MediaBrowser.Controller.Drawing
     public abstract class BaseImageProcessor
     {
         /// <summary>
-        /// Processes the primary image for a BaseEntity (Person, Studio, User, etc)
-        /// </summary>
-        /// <param name="originalImage">The original Image, before re-sizing</param>
-        /// <param name="bitmap">The bitmap holding the original image, after re-sizing</param>
-        /// <param name="graphics">The graphics surface on which the output is drawn</param>
-        /// <param name="entity">The entity that owns the image</param>
-        public abstract void ProcessImage(Image originalImage, Bitmap bitmap, Graphics graphics, BaseEntity entity);
-
-        /// <summary>
-        /// Processes an image for a BaseItem
+        /// Processes an image for a BaseEntity
         /// </summary>
         /// <param name="originalImage">The original Image, before re-sizing</param>
         /// <param name="bitmap">The bitmap holding the original image, after re-sizing</param>
@@ -32,10 +24,11 @@ namespace MediaBrowser.Controller.Drawing
         /// <param name="entity">The entity that owns the image</param>
         /// <param name="imageType">The image type</param>
         /// <param name="imageIndex">The image index (currently only used with backdrops)</param>
-        public abstract void ProcessImage(Image originalImage, Bitmap bitmap, Graphics graphics, BaseItem entity, ImageType imageType, int imageIndex);
+        public abstract void ProcessImage(Image originalImage, Bitmap bitmap, Graphics graphics, BaseEntity entity, ImageType imageType, int imageIndex);
 
         /// <summary>
         /// If true, the image output format will be forced to png, resulting in an output size that will generally run larger than jpg
+        /// If false, the original image format is preserved.
         /// </summary>
         public virtual bool RequiresTransparency
         {
@@ -44,6 +37,18 @@ namespace MediaBrowser.Controller.Drawing
                 return false;
             }
         }
+
+        /// <summary>
+        /// Determines if the image processor is configured to process the specified entity, image type and image index
+        /// This will aid http response caching so that we don't invalidate image caches when we don't have to
+        /// </summary>
+        public abstract bool IsConfiguredToProcess(BaseEntity entity, ImageType imageType, int imageIndex);
+
+        /// <summary>
+        /// This is used for caching purposes, since a configuration change needs to invalidate a user's image cache
+        /// If the image processor is hosted within a plugin then this should be the plugin ConfigurationDateLastModified 
+        /// </summary>
+        public abstract DateTime ProcessingConfigurationDateLastModifiedUtc { get; }
     }
 
     /// <summary>
@@ -52,34 +57,44 @@ namespace MediaBrowser.Controller.Drawing
     //[Export(typeof(BaseImageProcessor))]
     public class MyRoundedCornerImageProcessor : BaseImageProcessor
     {
-        public override void ProcessImage(Image originalImage, Bitmap bitmap, Graphics g, BaseEntity entity)
+        public override void ProcessImage(Image originalImage, Bitmap bitmap, Graphics graphics, BaseEntity entity, ImageType imageType, int imageIndex)
         {
             var CornerRadius = 20;
 
-            g.Clear(Color.Transparent);
-                        
+            graphics.Clear(Color.Transparent);
+
             using (GraphicsPath gp = new GraphicsPath())
             {
                 gp.AddArc(0, 0, CornerRadius, CornerRadius, 180, 90);
                 gp.AddArc(0 + bitmap.Width - CornerRadius, 0, CornerRadius, CornerRadius, 270, 90);
                 gp.AddArc(0 + bitmap.Width - CornerRadius, 0 + bitmap.Height - CornerRadius, CornerRadius, CornerRadius, 0, 90);
                 gp.AddArc(0, 0 + bitmap.Height - CornerRadius, CornerRadius, CornerRadius, 90, 90);
-                
-                g.SetClip(gp);
-                g.DrawImage(originalImage, 0, 0, bitmap.Width, bitmap.Height);
+
+                graphics.SetClip(gp);
+                graphics.DrawImage(originalImage, 0, 0, bitmap.Width, bitmap.Height);
             }
         }
 
-        public override void ProcessImage(Image originalImage, Bitmap bitmap, Graphics graphics, BaseItem entity, ImageType imageType, int imageIndex)
+        public override bool RequiresTransparency
         {
+            get
+            {
+                return true;
+            }
         }
 
-        public override bool RequiresTransparency
+        public override DateTime ProcessingConfigurationDateLastModifiedUtc
         {
             get
             {
-                return true;
+                // This will result in a situation where images are never cached, but again, this is a prototype
+                return DateTime.UtcNow;
             }
         }
+
+        public override bool IsConfiguredToProcess(BaseEntity entity, ImageType imageType, int imageIndex)
+        {
+            return true;
+        }
     }
 }

+ 41 - 18
MediaBrowser.Controller/Drawing/ImageProcessor.cs

@@ -14,19 +14,18 @@ namespace MediaBrowser.Controller.Drawing
         /// <summary>
         /// Processes an image by resizing to target dimensions
         /// </summary>
-        /// <param name="sourceImageStream">The stream containing the source image</param>
+        /// <param name="entity">The entity that owns the image</param>
+        /// <param name="imageType">The image type</param>
+        /// <param name="imageIndex">The image index (currently only used with backdrops)</param>
         /// <param name="toStream">The stream to save the new image to</param>
         /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
         /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
         /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
         /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
         /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
-        /// <param name="entity">The entity that owns the image</param>
-        /// <param name="imageType">The image type</param>
-        /// <param name="imageIndex">The image index (currently only used with backdrops)</param>
-        public static void ProcessImage(Stream sourceImageStream, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality, BaseEntity entity, ImageType imageType, int imageIndex)
+        public static void ProcessImage(BaseEntity entity, ImageType imageType, int imageIndex, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality)
         {
-            Image originalImage = Image.FromStream(sourceImageStream);
+            Image originalImage = Image.FromFile(GetImagePath(entity, imageType, imageIndex));
 
             // Determine the output size based on incoming parameters
             Size newSize = DrawingUtils.Resize(originalImage.Size, width, height, maxWidth, maxHeight);
@@ -79,9 +78,42 @@ namespace MediaBrowser.Controller.Drawing
             originalImage.Dispose();
         }
 
+        public static string GetImagePath(BaseEntity entity, ImageType imageType, int imageIndex)
+        {
+            var item = entity as BaseItem;
+
+            if (item != null)
+            {
+                if (imageType == ImageType.Logo)
+                {
+                    return item.LogoImagePath;
+                }
+                if (imageType == ImageType.Backdrop)
+                {
+                    return item.BackdropImagePaths.ElementAt(imageIndex);
+                }
+                if (imageType == ImageType.Banner)
+                {
+                    return item.BannerImagePath;
+                }
+                if (imageType == ImageType.Art)
+                {
+                    return item.ArtImagePath;
+                }
+                if (imageType == ImageType.Thumbnail)
+                {
+                    return item.ThumbnailImagePath;
+                }
+            }
+
+            return entity.PrimaryImagePath;
+        }
+
+
         /// <summary>
         /// Executes additional image processors that are registered with the Kernel
         /// </summary>
+        /// <param name="originalImage">The original Image, before re-sizing</param>
         /// <param name="bitmap">The bitmap holding the original image, after re-sizing</param>
         /// <param name="graphics">The graphics surface on which the output is drawn</param>
         /// <param name="entity">The entity that owns the image</param>
@@ -89,20 +121,11 @@ namespace MediaBrowser.Controller.Drawing
         /// <param name="imageIndex">The image index (currently only used with backdrops)</param>
         private static void ExecuteAdditionalImageProcessors(Image originalImage, Bitmap bitmap, Graphics graphics, BaseEntity entity, ImageType imageType, int imageIndex)
         {
-            var baseItem = entity as BaseItem;
-
-            if (baseItem != null)
-            {
-                foreach (var processor in Kernel.Instance.ImageProcessors)
-                {
-                    processor.ProcessImage(originalImage, bitmap, graphics, baseItem, imageType, imageIndex);
-                }
-            }
-            else
+            foreach (var processor in Kernel.Instance.ImageProcessors)
             {
-                foreach (var processor in Kernel.Instance.ImageProcessors)
+                if (processor.IsConfiguredToProcess(entity, imageType, imageIndex))
                 {
-                    processor.ProcessImage(originalImage, bitmap, graphics, entity);
+                    processor.ProcessImage(originalImage, bitmap, graphics, entity, imageType, imageIndex);
                 }
             }
         }