replace System.Drawing with ImageMagick

This commit is contained in:
Luke Pulverenti 2015-02-19 20:57:10 -05:00
parent d155139e28
commit 5e10e0ff19
8 changed files with 175 additions and 537 deletions

View File

@ -1,220 +0,0 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
namespace MediaBrowser.Server.Implementations.Drawing
{
/// <summary>
/// Class ImageExtensions
/// </summary>
public static class ImageExtensions
{
/// <summary>
/// Saves the image.
/// </summary>
/// <param name="outputFormat">The output format.</param>
/// <param name="image">The image.</param>
/// <param name="toStream">To stream.</param>
/// <param name="quality">The quality.</param>
public static void Save(this Image image, ImageFormat outputFormat, Stream toStream, int quality)
{
// Use special save methods for jpeg and png that will result in a much higher quality image
// All other formats use the generic Image.Save
if (ImageFormat.Jpeg.Equals(outputFormat))
{
SaveAsJpeg(image, toStream, quality);
}
else if (ImageFormat.Png.Equals(outputFormat))
{
image.Save(toStream, ImageFormat.Png);
}
else
{
image.Save(toStream, outputFormat);
}
}
/// <summary>
/// Saves the JPEG.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="target">The target.</param>
/// <param name="quality">The quality.</param>
public static void SaveAsJpeg(this Image image, Stream target, int quality)
{
using (var encoderParameters = new EncoderParameters(1))
{
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality);
image.Save(target, GetImageCodecInfo("image/jpeg"), encoderParameters);
}
}
private static readonly ImageCodecInfo[] Encoders = ImageCodecInfo.GetImageEncoders();
/// <summary>
/// Gets the image codec info.
/// </summary>
/// <param name="mimeType">Type of the MIME.</param>
/// <returns>ImageCodecInfo.</returns>
private static ImageCodecInfo GetImageCodecInfo(string mimeType)
{
foreach (var encoder in Encoders)
{
if (string.Equals(encoder.MimeType, mimeType, StringComparison.OrdinalIgnoreCase))
{
return encoder;
}
}
return Encoders.Length == 0 ? null : Encoders[0];
}
/// <summary>
/// Crops an image by removing whitespace and transparency from the edges
/// </summary>
/// <param name="bmp">The BMP.</param>
/// <returns>Bitmap.</returns>
/// <exception cref="System.Exception"></exception>
public static Bitmap CropWhitespace(this Bitmap bmp)
{
var width = bmp.Width;
var height = bmp.Height;
var topmost = 0;
for (int row = 0; row < height; ++row)
{
if (IsAllWhiteRow(bmp, row, width))
topmost = row;
else break;
}
int bottommost = 0;
for (int row = height - 1; row >= 0; --row)
{
if (IsAllWhiteRow(bmp, row, width))
bottommost = row;
else break;
}
int leftmost = 0, rightmost = 0;
for (int col = 0; col < width; ++col)
{
if (IsAllWhiteColumn(bmp, col, height))
leftmost = col;
else
break;
}
for (int col = width - 1; col >= 0; --col)
{
if (IsAllWhiteColumn(bmp, col, height))
rightmost = col;
else
break;
}
if (rightmost == 0) rightmost = width; // As reached left
if (bottommost == 0) bottommost = height; // As reached top.
var croppedWidth = rightmost - leftmost;
var croppedHeight = bottommost - topmost;
if (croppedWidth == 0) // No border on left or right
{
leftmost = 0;
croppedWidth = width;
}
if (croppedHeight == 0) // No border on top or bottom
{
topmost = 0;
croppedHeight = height;
}
// Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
var thumbnail = new Bitmap(croppedWidth, croppedHeight, PixelFormat.Format32bppPArgb);
// Preserve the original resolution
TrySetResolution(thumbnail, bmp.HorizontalResolution, bmp.VerticalResolution);
using (var thumbnailGraph = Graphics.FromImage(thumbnail))
{
thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality;
thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
thumbnailGraph.CompositingMode = CompositingMode.SourceCopy;
thumbnailGraph.DrawImage(bmp,
new RectangleF(0, 0, croppedWidth, croppedHeight),
new RectangleF(leftmost, topmost, croppedWidth, croppedHeight),
GraphicsUnit.Pixel);
}
return thumbnail;
}
/// <summary>
/// Tries the set resolution.
/// </summary>
/// <param name="bmp">The BMP.</param>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
private static void TrySetResolution(Bitmap bmp, float x, float y)
{
if (x > 0 && y > 0)
{
bmp.SetResolution(x, y);
}
}
/// <summary>
/// Determines whether or not a row of pixels is all whitespace
/// </summary>
/// <param name="bmp">The BMP.</param>
/// <param name="row">The row.</param>
/// <param name="width">The width.</param>
/// <returns><c>true</c> if [is all white row] [the specified BMP]; otherwise, <c>false</c>.</returns>
private static bool IsAllWhiteRow(Bitmap bmp, int row, int width)
{
for (var i = 0; i < width; ++i)
{
if (!IsWhiteSpace(bmp.GetPixel(i, row)))
{
return false;
}
}
return true;
}
/// <summary>
/// Determines whether or not a column of pixels is all whitespace
/// </summary>
/// <param name="bmp">The BMP.</param>
/// <param name="col">The col.</param>
/// <param name="height">The height.</param>
/// <returns><c>true</c> if [is all white column] [the specified BMP]; otherwise, <c>false</c>.</returns>
private static bool IsAllWhiteColumn(Bitmap bmp, int col, int height)
{
for (var i = 0; i < height; ++i)
{
if (!IsWhiteSpace(bmp.GetPixel(col, i)))
{
return false;
}
}
return true;
}
/// <summary>
/// Determines if a color is whitespace
/// </summary>
/// <param name="color">The color.</param>
/// <returns><c>true</c> if [is white space] [the specified color]; otherwise, <c>false</c>.</returns>
private static bool IsWhiteSpace(Color color)
{
return (color.R == 255 && color.G == 255 && color.B == 255) || color.A == 0;
}
}
}

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.IO;
using ImageMagickSharp;
using MediaBrowser.Common.IO;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Logging;
using System;
@ -61,27 +62,15 @@ namespace MediaBrowser.Server.Implementations.Drawing
logger.Info("Failed to read image header for {0}. Doing it the slow way.", path);
}
// Buffer to memory stream to avoid image locking file
using (var fs = fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var wand = new MagickWand(path))
{
using (var memoryStream = new MemoryStream())
var img = wand.CurrentImage;
return new ImageSize
{
fs.CopyTo(memoryStream);
memoryStream.Position = 0;
// Co it the old fashioned way
using (var b = System.Drawing.Image.FromStream(memoryStream, true, false))
{
var size = b.Size;
return new ImageSize
{
Width = size.Width,
Height = size.Height
};
}
}
Width = img.Width,
Height = img.Height
};
}
}

View File

@ -1,10 +1,9 @@
using Imazen.WebP;
using ImageMagickSharp;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
@ -13,9 +12,6 @@ using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Linq;
@ -54,14 +50,12 @@ namespace MediaBrowser.Server.Implementations.Drawing
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationPaths _appPaths;
private readonly IMediaEncoder _mediaEncoder;
public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder)
public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IJsonSerializer jsonSerializer)
{
_logger = logger;
_fileSystem = fileSystem;
_jsonSerializer = jsonSerializer;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
_saveImageSizeTimer = new Timer(SaveImageSizeCallback, null, Timeout.Infinite, Timeout.Infinite);
@ -92,7 +86,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
_cachedImagedSizes = new ConcurrentDictionary<Guid, ImageSize>(sizeDictionary);
LogWebPVersion();
LogImageMagickVersionVersion();
}
private string ResizedImageCachePath
@ -134,13 +128,9 @@ namespace MediaBrowser.Server.Implementations.Drawing
}
}
public Model.Drawing.ImageFormat[] GetSupportedImageOutputFormats()
public ImageFormat[] GetSupportedImageOutputFormats()
{
if (_webpAvailable)
{
return new[] { Model.Drawing.ImageFormat.Webp, Model.Drawing.ImageFormat.Gif, Model.Drawing.ImageFormat.Jpg, Model.Drawing.ImageFormat.Png };
}
return new[] { Model.Drawing.ImageFormat.Gif, Model.Drawing.ImageFormat.Jpg, Model.Drawing.ImageFormat.Png };
return new[] { ImageFormat.Webp, ImageFormat.Gif, ImageFormat.Jpg, ImageFormat.Png };
}
public async Task<string> ProcessImage(ImageProcessingOptions options)
@ -212,77 +202,42 @@ namespace MediaBrowser.Server.Implementations.Drawing
try
{
var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed > 0;
var newWidth = Convert.ToInt32(newSize.Width);
var newHeight = Convert.ToInt32(newSize.Height);
using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
if (string.IsNullOrWhiteSpace(options.BackgroundColor))
{
// Copy to memory stream to avoid Image locking file
using (var memoryStream = new MemoryStream())
using (var originalImage = new MagickWand(originalImagePath))
{
await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
originalImage.CurrentImage.ResizeImage(newWidth, newHeight);
using (var originalImage = Image.FromStream(memoryStream, true, false))
DrawIndicator(originalImage, newWidth, newHeight, options);
originalImage.CurrentImage.CompressionQuality = quality;
originalImage.SaveImage(cacheFilePath);
return cacheFilePath;
}
}
else
{
using (var wand = new MagickWand(newWidth, newHeight, options.BackgroundColor))
{
using (var originalImage = new MagickWand(originalImagePath))
{
var newWidth = Convert.ToInt32(newSize.Width);
var newHeight = Convert.ToInt32(newSize.Height);
originalImage.CurrentImage.ResizeImage(newWidth, newHeight);
var selectedOutputFormat = options.OutputFormat;
wand.CurrentImage.CompositeImage(originalImage, CompositeOperator.OverCompositeOp, 0, 0);
DrawIndicator(wand, newWidth, newHeight, options);
_logger.Debug("Processing image to {0}", selectedOutputFormat);
wand.CurrentImage.CompressionQuality = quality;
// Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
// Also, Webp only supports Format32bppArgb and Format32bppRgb
var pixelFormat = selectedOutputFormat == Model.Drawing.ImageFormat.Webp
? PixelFormat.Format32bppArgb
: PixelFormat.Format32bppPArgb;
using (var thumbnail = new Bitmap(newWidth, newHeight, pixelFormat))
{
// Mono throw an exeception if assign 0 to SetResolution
if (originalImage.HorizontalResolution > 0 && originalImage.VerticalResolution > 0)
{
// Preserve the original resolution
thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
}
using (var thumbnailGraph = Graphics.FromImage(thumbnail))
{
thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality;
thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
thumbnailGraph.CompositingMode = !hasPostProcessing ?
CompositingMode.SourceCopy :
CompositingMode.SourceOver;
SetBackgroundColor(thumbnailGraph, options);
thumbnailGraph.DrawImage(originalImage, 0, 0, newWidth, newHeight);
DrawIndicator(thumbnailGraph, newWidth, newHeight, options);
var outputFormat = GetOutputFormat(originalImage, selectedOutputFormat);
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
// Save to the cache location
using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, false))
{
if (selectedOutputFormat == Model.Drawing.ImageFormat.Webp)
{
SaveToWebP(thumbnail, cacheFileStream, quality);
}
else
{
// Save to the memory stream
thumbnail.Save(outputFormat, cacheFileStream, quality);
}
}
return cacheFilePath;
}
}
wand.SaveImage(cacheFilePath);
return cacheFilePath;
}
}
}
@ -293,59 +248,26 @@ namespace MediaBrowser.Server.Implementations.Drawing
}
}
private void SaveToWebP(Bitmap thumbnail, Stream toStream, int quality)
{
new SimpleEncoder().Encode(thumbnail, toStream, quality);
}
private bool _webpAvailable = true;
private void LogWebPVersion()
private void LogImageMagickVersionVersion()
{
try
{
_logger.Info("libwebp version: " + SimpleEncoder.GetEncoderVersion());
_logger.Info("ImageMagick version: " + Wand.VersionString);
}
catch (Exception ex)
{
_logger.ErrorException("Error loading libwebp: ", ex);
_webpAvailable = false;
}
}
/// <summary>
/// Sets the color of the background.
/// </summary>
/// <param name="graphics">The graphics.</param>
/// <param name="options">The options.</param>
private void SetBackgroundColor(Graphics graphics, ImageProcessingOptions options)
{
var color = options.BackgroundColor;
if (!string.IsNullOrEmpty(color))
{
Color drawingColor;
try
{
drawingColor = ColorTranslator.FromHtml(color);
}
catch
{
drawingColor = ColorTranslator.FromHtml("#" + color);
}
graphics.Clear(drawingColor);
_logger.ErrorException("Error loading ImageMagick: ", ex);
}
}
/// <summary>
/// Draws the indicator.
/// </summary>
/// <param name="graphics">The graphics.</param>
/// <param name="wand">The wand.</param>
/// <param name="imageWidth">Width of the image.</param>
/// <param name="imageHeight">Height of the image.</param>
/// <param name="options">The options.</param>
private void DrawIndicator(Graphics graphics, int imageWidth, int imageHeight, ImageProcessingOptions options)
private void DrawIndicator(MagickWand wand, int imageWidth, int imageHeight, ImageProcessingOptions options)
{
if (!options.AddPlayedIndicator && !options.UnplayedCount.HasValue && options.PercentPlayed.Equals(0))
{
@ -356,22 +278,20 @@ namespace MediaBrowser.Server.Implementations.Drawing
{
if (options.AddPlayedIndicator)
{
var currentImageSize = new Size(imageWidth, imageHeight);
var currentImageSize = new ImageSize(imageWidth, imageHeight);
new PlayedIndicatorDrawer().DrawPlayedIndicator(graphics, currentImageSize);
new PlayedIndicatorDrawer().DrawPlayedIndicator(wand, currentImageSize);
}
else if (options.UnplayedCount.HasValue)
{
var currentImageSize = new Size(imageWidth, imageHeight);
var currentImageSize = new ImageSize(imageWidth, imageHeight);
new UnplayedCountIndicator().DrawUnplayedCountIndicator(graphics, currentImageSize, options.UnplayedCount.Value);
new UnplayedCountIndicator().DrawUnplayedCountIndicator(wand, currentImageSize, options.UnplayedCount.Value);
}
if (options.PercentPlayed > 0)
{
var currentImageSize = new Size(imageWidth, imageHeight);
new PercentPlayedDrawer().Process(graphics, currentImageSize, options.PercentPlayed);
new PercentPlayedDrawer().Process(wand, options.PercentPlayed);
}
}
catch (Exception ex)
@ -380,29 +300,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
}
}
/// <summary>
/// Gets the output format.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="outputFormat">The output format.</param>
/// <returns>ImageFormat.</returns>
private System.Drawing.Imaging.ImageFormat GetOutputFormat(Image image, Model.Drawing.ImageFormat outputFormat)
{
switch (outputFormat)
{
case Model.Drawing.ImageFormat.Bmp:
return System.Drawing.Imaging.ImageFormat.Bmp;
case Model.Drawing.ImageFormat.Gif:
return System.Drawing.Imaging.ImageFormat.Gif;
case Model.Drawing.ImageFormat.Jpg:
return System.Drawing.Imaging.ImageFormat.Jpeg;
case Model.Drawing.ImageFormat.Png:
return System.Drawing.Imaging.ImageFormat.Png;
default:
return image.RawFormat;
}
}
/// <summary>
/// Crops whitespace from an image, caches the result, and returns the cached path
/// </summary>
@ -429,28 +326,12 @@ namespace MediaBrowser.Server.Implementations.Drawing
try
{
using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
Directory.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
using (var wand = new MagickWand(originalImagePath))
{
// Copy to memory stream to avoid Image locking file
using (var memoryStream = new MemoryStream())
{
await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
using (var originalImage = (Bitmap)Image.FromStream(memoryStream, true, false))
{
var outputFormat = originalImage.RawFormat;
using (var croppedImage = originalImage.CropWhitespace())
{
Directory.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
using (var outputStream = _fileSystem.GetFileStream(croppedImagePath, FileMode.Create, FileAccess.Write, FileShare.Read, false))
{
croppedImage.Save(outputFormat, outputStream, 100);
}
}
}
}
wand.CurrentImage.TrimImage(10);
wand.SaveImage(croppedImagePath);
}
}
catch (Exception ex)
@ -476,7 +357,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <summary>
/// Gets the cache file path based on a set of parameters
/// </summary>
private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, Model.Drawing.ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, string backgroundColor)
private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, string backgroundColor)
{
var filename = originalPath;
@ -727,7 +608,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
}
/// <summary>
/// Runs an image through the image enhancers, caches the result, and returns the cached path
/// Gets the enhanced image internal.
/// </summary>
/// <param name="originalImagePath">The original image path.</param>
/// <param name="item">The item.</param>
@ -735,8 +616,12 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <param name="imageIndex">Index of the image.</param>
/// <param name="supportedEnhancers">The supported enhancers.</param>
/// <param name="cacheGuid">The cache unique identifier.</param>
/// <returns>System.String.</returns>
/// <exception cref="System.ArgumentNullException">originalImagePath</exception>
/// <returns>Task&lt;System.String&gt;.</returns>
/// <exception cref="ArgumentNullException">
/// originalImagePath
/// or
/// item
/// </exception>
private async Task<string> GetEnhancedImageInternal(string originalImagePath,
IHasImages item,
ImageType imageType,
@ -770,51 +655,8 @@ namespace MediaBrowser.Server.Implementations.Drawing
try
{
using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
{
// Copy to memory stream to avoid Image locking file
using (var memoryStream = new MemoryStream())
{
await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
var imageStream = new ImageStream
{
Stream = memoryStream,
Format = GetFormat(originalImagePath)
};
//Pass the image through registered enhancers
using (var newImageStream = await ExecuteImageEnhancers(supportedEnhancers, imageStream, item, imageType, imageIndex).ConfigureAwait(false))
{
var parentDirectory = Path.GetDirectoryName(enhancedImagePath);
Directory.CreateDirectory(parentDirectory);
// Save as png
if (newImageStream.Format == Model.Drawing.ImageFormat.Png)
{
//And then save it in the cache
using (var outputStream = _fileSystem.GetFileStream(enhancedImagePath, FileMode.Create, FileAccess.Write, FileShare.Read, false))
{
await newImageStream.Stream.CopyToAsync(outputStream).ConfigureAwait(false);
}
}
else
{
using (var newImage = Image.FromStream(newImageStream.Stream, true, false))
{
//And then save it in the cache
using (var outputStream = _fileSystem.GetFileStream(enhancedImagePath, FileMode.Create, FileAccess.Write, FileShare.Read, false))
{
newImage.Save(System.Drawing.Imaging.ImageFormat.Png, outputStream, 100);
}
}
}
}
}
}
Directory.CreateDirectory(Path.GetDirectoryName(enhancedImagePath));
await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, enhancedImagePath, item, imageType, imageIndex).ConfigureAwait(false);
}
finally
{
@ -824,43 +666,42 @@ namespace MediaBrowser.Server.Implementations.Drawing
return enhancedImagePath;
}
private Model.Drawing.ImageFormat GetFormat(string path)
private ImageFormat GetFormat(string path)
{
var extension = Path.GetExtension(path);
if (string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase))
{
return Model.Drawing.ImageFormat.Png;
return ImageFormat.Png;
}
if (string.Equals(extension, ".gif", StringComparison.OrdinalIgnoreCase))
{
return Model.Drawing.ImageFormat.Gif;
return ImageFormat.Gif;
}
if (string.Equals(extension, ".webp", StringComparison.OrdinalIgnoreCase))
{
return Model.Drawing.ImageFormat.Webp;
return ImageFormat.Webp;
}
if (string.Equals(extension, ".bmp", StringComparison.OrdinalIgnoreCase))
{
return Model.Drawing.ImageFormat.Bmp;
return ImageFormat.Bmp;
}
return Model.Drawing.ImageFormat.Jpg;
return ImageFormat.Jpg;
}
/// <summary>
/// Executes the image enhancers.
/// </summary>
/// <param name="imageEnhancers">The image enhancers.</param>
/// <param name="originalImage">The original image.</param>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{EnhancedImage}.</returns>
private async Task<ImageStream> ExecuteImageEnhancers(IEnumerable<IImageEnhancer> imageEnhancers, ImageStream originalImage, IHasImages item, ImageType imageType, int imageIndex)
private async Task ExecuteImageEnhancers(IEnumerable<IImageEnhancer> imageEnhancers, string inputPath, string outputPath, IHasImages item, ImageType imageType, int imageIndex)
{
var result = originalImage;
// Run the enhancers sequentially in order of priority
foreach (var enhancer in imageEnhancers)
{
@ -868,7 +709,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
try
{
result = await enhancer.EnhanceImageAsync(item, result, imageType, imageIndex).ConfigureAwait(false);
await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false);
}
catch (Exception ex)
{
@ -876,9 +717,10 @@ namespace MediaBrowser.Server.Implementations.Drawing
throw;
}
}
return result;
// Feed the output into the next enhancer as input
inputPath = outputPath;
}
}
/// <summary>

View File

@ -1,5 +1,5 @@
using System;
using System.Drawing;
using ImageMagickSharp;
using System;
namespace MediaBrowser.Server.Implementations.Drawing
{
@ -7,26 +7,32 @@ namespace MediaBrowser.Server.Implementations.Drawing
{
private const int IndicatorHeight = 8;
public void Process(Graphics graphics, Size imageSize, double percent)
public void Process(MagickWand wand, double percent)
{
var y = imageSize.Height - IndicatorHeight;
var currentImage = wand.CurrentImage;
var height = currentImage.Height;
using (var backdroundBrush = new SolidBrush(Color.FromArgb(225, 0, 0, 0)))
using (var draw = new DrawingWand())
{
const int innerX = 0;
var innerY = y;
var innerWidth = imageSize.Width;
var innerHeight = imageSize.Height;
graphics.FillRectangle(backdroundBrush, innerX, innerY, innerWidth, innerHeight);
using (var foregroundBrush = new SolidBrush(Color.FromArgb(82, 181, 75)))
using (PixelWand pixel = new PixelWand())
{
double foregroundWidth = innerWidth;
var endX = currentImage.Width - 1;
var endY = height - 1;
pixel.Color = "black";
pixel.Opacity = 0.4;
draw.FillColor = pixel;
draw.DrawRectangle(0, endY - IndicatorHeight, endX, endY);
double foregroundWidth = endX;
foregroundWidth *= percent;
foregroundWidth /= 100;
graphics.FillRectangle(foregroundBrush, innerX, innerY, Convert.ToInt32(Math.Round(foregroundWidth)), innerHeight);
pixel.Color = "#52B54B";
pixel.Opacity = 0;
draw.FillColor = pixel;
draw.DrawRectangle(0, endY - IndicatorHeight, Convert.ToInt32(Math.Round(foregroundWidth)), endY);
wand.CurrentImage.DrawImage(draw);
}
}
}

View File

@ -1,31 +1,41 @@
using System.Drawing;
using ImageMagickSharp;
using MediaBrowser.Model.Drawing;
namespace MediaBrowser.Server.Implementations.Drawing
{
public class PlayedIndicatorDrawer
{
private const int IndicatorHeight = 40;
public const int IndicatorWidth = 40;
private const int FontSize = 40;
private const int OffsetFromTopRightCorner = 10;
private const int FontSize = 52;
private const int OffsetFromTopRightCorner = 38;
public void DrawPlayedIndicator(Graphics graphics, Size imageSize)
public void DrawPlayedIndicator(MagickWand wand, ImageSize imageSize)
{
var x = imageSize.Width - IndicatorWidth - OffsetFromTopRightCorner;
var x = imageSize.Width - OffsetFromTopRightCorner;
using (var backdroundBrush = new SolidBrush(Color.FromArgb(225, 82, 181, 75)))
using (var draw = new DrawingWand())
{
graphics.FillEllipse(backdroundBrush, x, OffsetFromTopRightCorner, IndicatorWidth, IndicatorHeight);
x = imageSize.Width - 45 - OffsetFromTopRightCorner;
using (var font = new Font("Webdings", FontSize, FontStyle.Regular, GraphicsUnit.Pixel))
using (PixelWand pixel = new PixelWand())
{
using (var fontBrush = new SolidBrush(Color.White))
{
graphics.DrawString("a", font, fontBrush, x, OffsetFromTopRightCorner - 2);
}
pixel.Color = "#52B54B";
pixel.Opacity = 0.2;
draw.FillColor = pixel;
draw.DrawCircle(x, OffsetFromTopRightCorner, x - 20, OffsetFromTopRightCorner - 20);
pixel.Opacity = 0;
pixel.Color = "white";
draw.FillColor = pixel;
draw.Font = "Webdings";
draw.FontSize = FontSize;
draw.FontStyle = FontStyleType.NormalStyle;
draw.TextAlignment = TextAlignType.CenterAlign;
draw.FontWeight = FontWeightType.RegularStyle;
draw.TextAntialias = true;
draw.DrawAnnotation(x + 4, OffsetFromTopRightCorner + 14, "a");
draw.FillColor = pixel;
wand.CurrentImage.DrawImage(draw);
}
}
}
}

View File

@ -1,49 +1,61 @@
using System.Drawing;
using System.Globalization;
using ImageMagickSharp;
using MediaBrowser.Model.Drawing;
namespace MediaBrowser.Server.Implementations.Drawing
{
public class UnplayedCountIndicator
{
private const int IndicatorHeight = 41;
public const int IndicatorWidth = 41;
private const int OffsetFromTopRightCorner = 10;
private const int OffsetFromTopRightCorner = 38;
public void DrawUnplayedCountIndicator(Graphics graphics, Size imageSize, int count)
public void DrawUnplayedCountIndicator(MagickWand wand, ImageSize imageSize, int count)
{
var x = imageSize.Width - IndicatorWidth - OffsetFromTopRightCorner;
var x = imageSize.Width - OffsetFromTopRightCorner;
var text = count.ToString(CultureInfo.InvariantCulture);
using (var backdroundBrush = new SolidBrush(Color.FromArgb(225, 82, 181, 75)))
using (var draw = new DrawingWand())
{
graphics.FillEllipse(backdroundBrush, x, OffsetFromTopRightCorner, IndicatorWidth, IndicatorHeight);
var text = count.ToString();
x = imageSize.Width - IndicatorWidth - OffsetFromTopRightCorner;
var y = OffsetFromTopRightCorner + 6;
var fontSize = 24;
if (text.Length == 1)
using (PixelWand pixel = new PixelWand())
{
x += 10;
}
else if (text.Length == 2)
{
x += 3;
}
else if (text.Length == 3)
{
x += 1;
y += 1;
fontSize = 20;
}
pixel.Color = "#52B54B";
pixel.Opacity = 0.2;
draw.FillColor = pixel;
draw.DrawCircle(x, OffsetFromTopRightCorner, x - 20, OffsetFromTopRightCorner - 20);
using (var font = new Font("Sans-Serif", fontSize, FontStyle.Regular, GraphicsUnit.Pixel))
{
using (var fontBrush = new SolidBrush(Color.White))
pixel.Opacity = 0;
pixel.Color = "white";
draw.FillColor = pixel;
draw.Font = "Sans-Serif";
draw.FontStyle = FontStyleType.NormalStyle;
draw.TextAlignment = TextAlignType.CenterAlign;
draw.FontWeight = FontWeightType.RegularStyle;
draw.TextAntialias = true;
var fontSize = 30;
var y = OffsetFromTopRightCorner + 11;
if (text.Length == 1)
{
graphics.DrawString(text, font, fontBrush, x, y);
x += 2;
}
else if (text.Length == 2)
{
x += 1;
}
else if (text.Length >= 3)
{
x += 1;
y -= 2;
fontSize = 24;
}
draw.FontSize = fontSize;
draw.DrawAnnotation(x, y, text);
draw.FillColor = pixel;
wand.CurrentImage.DrawImage(draw);
}
}
}
}

View File

@ -45,9 +45,8 @@
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Imazen.WebP, Version=0.2.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\ThirdParty\libwebp\Imazen.WebP.dll</HintPath>
<Reference Include="ImageMagickSharp">
<HintPath>..\packages\ImageMagickSharp.1.0.0.0\lib\net45\ImageMagickSharp.dll</HintPath>
</Reference>
<Reference Include="MediaBrowser.Naming, Version=1.0.5509.27636, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@ -77,9 +76,9 @@
<Reference Include="System.Data.SQLite">
<HintPath>..\ThirdParty\System.Data.SQLite.ManagedOnly\1.0.94.0\System.Data.SQLite.dll</HintPath>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net" />
<Reference Include="System.Security" />
<Reference Include="System.Web" />
@ -126,7 +125,6 @@
<Compile Include="Devices\DeviceManager.cs" />
<Compile Include="Devices\DeviceRepository.cs" />
<Compile Include="Devices\CameraUploadsFolder.cs" />
<Compile Include="Drawing\ImageExtensions.cs" />
<Compile Include="Drawing\ImageHeader.cs" />
<Compile Include="Drawing\PercentPlayedDrawer.cs" />
<Compile Include="Drawing\PlayedIndicatorDrawer.cs" />

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ImageMagickSharp" version="1.0.0.0" targetFramework="net45" />
<package id="MediaBrowser.Naming" version="1.0.0.32" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.21.0" targetFramework="net45" />
<package id="morelinq" version="1.1.0" targetFramework="net45" />