A few more image improvements

This commit is contained in:
LukePulverenti Luke Pulverenti luke pulverenti 2012-09-18 18:32:37 -04:00
parent e76ff3bf16
commit bd6c2d2a22
3 changed files with 127 additions and 135 deletions

View File

@ -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,61 +79,11 @@ namespace MediaBrowser.Api.HttpHandlers
{
var entity = await GetSourceEntity().ConfigureAwait(false);
var item = entity as BaseItem;
if (item != null)
{
return GetImagePathFromTypes(item, ImageType, ImageIndex);
}
return entity.PrimaryImagePath;
return ImageProcessor.GetImagePath(entity, ImageType, ImageIndex);
}
private Stream _sourceStream;
private async Task<Stream> GetSourceStream()
public override async Task<string> GetContentType()
{
await EnsureSourceStream().ConfigureAwait(false);
return _sourceStream;
}
private bool _sourceStreamEnsured;
private async Task EnsureSourceStream()
{
if (!_sourceStreamEnsured)
{
try
{
_sourceStream = File.OpenRead(await GetImagePath().ConfigureAwait(false));
}
catch (FileNotFoundException ex)
{
StatusCode = 404;
Logger.LogException(ex);
}
catch (DirectoryNotFoundException ex)
{
StatusCode = 404;
Logger.LogException(ex);
}
catch (UnauthorizedAccessException ex)
{
StatusCode = 403;
Logger.LogException(ex);
}
finally
{
_sourceStreamEnsured = true;
}
}
}
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");
@ -139,20 +94,47 @@ namespace MediaBrowser.Api.HttpHandlers
public override TimeSpan CacheDuration
{
get
{
return TimeSpan.FromDays(365);
}
get { return TimeSpan.FromDays(365); }
}
protected async override Task<DateTime?> GetLastDateModified()
protected override async Task<DateTime?> GetLastDateModified()
{
if (await GetSourceStream().ConfigureAwait(false) == null)
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)
{
return null;
if (!File.Exists(path))
{
StatusCode = 404;
return null;
}
}
return File.GetLastWriteTimeUtc(await GetImagePath().ConfigureAwait(false));
return await GetMostRecentDateModified(date);
}
private async Task<DateTime> GetMostRecentDateModified(DateTime imageFileLastDateModified)
{
var date = imageFileLastDateModified;
var entity = await GetSourceEntity().ConfigureAwait(false);
foreach (var processor in Kernel.Instance.ImageProcessors)
{
if (processor.IsConfiguredToProcess(entity, ImageType, ImageIndex))
{
if (processor.ProcessingConfigurationDateLastModifiedUtc > date)
{
date = processor.ProcessingConfigurationDateLastModifiedUtc;
}
}
}
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);
}
}
}

View File

@ -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,11 +57,11 @@ 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())
{
@ -65,15 +70,11 @@ namespace MediaBrowser.Controller.Drawing
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
@ -81,5 +82,19 @@ namespace MediaBrowser.Controller.Drawing
return true;
}
}
public override DateTime ProcessingConfigurationDateLastModifiedUtc
{
get
{
// 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;
}
}
}

View File

@ -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)
{
foreach (var processor in Kernel.Instance.ImageProcessors)
if (processor.IsConfiguredToProcess(entity, imageType, imageIndex))
{
processor.ProcessImage(originalImage, bitmap, graphics, baseItem, imageType, imageIndex);
}
}
else
{
foreach (var processor in Kernel.Instance.ImageProcessors)
{
processor.ProcessImage(originalImage, bitmap, graphics, entity);
processor.ProcessImage(originalImage, bitmap, graphics, entity, imageType, imageIndex);
}
}
}