A few more image improvements

pull/702/head
LukePulverenti Luke Pulverenti luke pulverenti 12 years ago
parent e76ff3bf16
commit bd6c2d2a22

@ -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);
}
}
}

@ -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;
}
}
}

@ -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);
}
}
}

Loading…
Cancel
Save