aboutsummaryrefslogtreecommitdiff
path: root/Emby.Drawing.ImageMagick
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Drawing.ImageMagick')
-rw-r--r--Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj83
-rw-r--r--Emby.Drawing.ImageMagick/ImageHelpers.cs43
-rw-r--r--Emby.Drawing.ImageMagick/ImageMagickEncoder.cs333
-rw-r--r--Emby.Drawing.ImageMagick/PercentPlayedDrawer.cs40
-rw-r--r--Emby.Drawing.ImageMagick/PlayedIndicatorDrawer.cs125
-rw-r--r--Emby.Drawing.ImageMagick/Properties/AssemblyInfo.cs36
-rw-r--r--Emby.Drawing.ImageMagick/StripCollageBuilder.cs202
-rw-r--r--Emby.Drawing.ImageMagick/UnplayedCountIndicator.cs75
-rw-r--r--Emby.Drawing.ImageMagick/packages.config4
9 files changed, 941 insertions, 0 deletions
diff --git a/Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj b/Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj
new file mode 100644
index 000000000..98e99c92b
--- /dev/null
+++ b/Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{6CFEE013-6E7C-432B-AC37-CABF0880C69A}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Emby.Drawing.ImageMagick</RootNamespace>
+ <AssemblyName>Emby.Drawing.ImageMagick</AssemblyName>
+ <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\ImageMagickSharp.1.0.0.18\lib\net45\ImageMagickSharp.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ImageHelpers.cs" />
+ <Compile Include="ImageMagickEncoder.cs" />
+ <Compile Include="PercentPlayedDrawer.cs" />
+ <Compile Include="PlayedIndicatorDrawer.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="StripCollageBuilder.cs" />
+ <Compile Include="UnplayedCountIndicator.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="fonts\robotoregular.ttf" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
+ <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+ <Name>MediaBrowser.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+ <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+ <Name>MediaBrowser.Controller</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project> \ No newline at end of file
diff --git a/Emby.Drawing.ImageMagick/ImageHelpers.cs b/Emby.Drawing.ImageMagick/ImageHelpers.cs
new file mode 100644
index 000000000..c623c21aa
--- /dev/null
+++ b/Emby.Drawing.ImageMagick/ImageHelpers.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Emby.Drawing.ImageMagick
+{
+ internal static class ImageHelpers
+ {
+ internal static List<string> ProjectPaths(List<string> paths, int count)
+ {
+ if (count <= 0)
+ {
+ throw new ArgumentOutOfRangeException("count");
+ }
+ if (paths.Count == 0)
+ {
+ throw new ArgumentOutOfRangeException("paths");
+ }
+
+ var list = new List<string>();
+
+ AddToList(list, paths, count);
+
+ return list.Take(count).ToList();
+ }
+
+ private static void AddToList(List<string> list, List<string> paths, int count)
+ {
+ while (list.Count < count)
+ {
+ foreach (var path in paths)
+ {
+ list.Add(path);
+
+ if (list.Count >= count)
+ {
+ return;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs
new file mode 100644
index 000000000..3410ef003
--- /dev/null
+++ b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs
@@ -0,0 +1,333 @@
+using System.Threading.Tasks;
+using ImageMagickSharp;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Logging;
+using System;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Model.IO;
+
+namespace Emby.Drawing.ImageMagick
+{
+ public class ImageMagickEncoder : IImageEncoder
+ {
+ private readonly ILogger _logger;
+ private readonly IApplicationPaths _appPaths;
+ private readonly IHttpClient _httpClient;
+ private readonly IFileSystem _fileSystem;
+ private readonly IServerConfigurationManager _config;
+
+ public ImageMagickEncoder(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config)
+ {
+ _logger = logger;
+ _appPaths = appPaths;
+ _httpClient = httpClient;
+ _fileSystem = fileSystem;
+ _config = config;
+
+ LogVersion();
+ }
+
+ public string[] SupportedInputFormats
+ {
+ get
+ {
+ // Some common file name extensions for RAW picture files include: .cr2, .crw, .dng, .nef, .orf, .rw2, .pef, .arw, .sr2, .srf, and .tif.
+ return new[]
+ {
+ "tiff",
+ "jpeg",
+ "jpg",
+ "png",
+ "aiff",
+ "cr2",
+ "crw",
+ "dng",
+ "nef",
+ "orf",
+ "pef",
+ "arw",
+ "webp",
+ "gif",
+ "bmp",
+ "erf",
+ "raf",
+ "rw2",
+ "nrw"
+ };
+ }
+ }
+
+ public ImageFormat[] SupportedOutputFormats
+ {
+ get
+ {
+ if (_webpAvailable)
+ {
+ return new[] { ImageFormat.Webp, ImageFormat.Gif, ImageFormat.Jpg, ImageFormat.Png };
+ }
+ return new[] { ImageFormat.Gif, ImageFormat.Jpg, ImageFormat.Png };
+ }
+ }
+
+ private void LogVersion()
+ {
+ _logger.Info("ImageMagick version: " + GetVersion());
+ TestWebp();
+ Wand.SetMagickThreadCount(1);
+ }
+
+ public static string GetVersion()
+ {
+ return Wand.VersionString;
+ }
+
+ private bool _webpAvailable = true;
+ private void TestWebp()
+ {
+ try
+ {
+ var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".webp");
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
+
+ using (var wand = new MagickWand(1, 1, new PixelWand("none", 1)))
+ {
+ wand.SaveImage(tmpPath);
+ }
+ }
+ catch
+ {
+ //_logger.ErrorException("Error loading webp: ", ex);
+ _webpAvailable = false;
+ }
+ }
+
+ public void CropWhiteSpace(string inputPath, string outputPath)
+ {
+ CheckDisposed();
+
+ using (var wand = new MagickWand(inputPath))
+ {
+ wand.CurrentImage.TrimImage(10);
+ wand.SaveImage(outputPath);
+ }
+ }
+
+ public ImageSize GetImageSize(string path)
+ {
+ CheckDisposed();
+
+ using (var wand = new MagickWand())
+ {
+ wand.PingImage(path);
+ var img = wand.CurrentImage;
+
+ return new ImageSize
+ {
+ Width = img.Width,
+ Height = img.Height
+ };
+ }
+ }
+
+ private bool HasTransparency(string path)
+ {
+ var ext = Path.GetExtension(path);
+
+ return string.Equals(ext, ".png", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(ext, ".webp", StringComparison.OrdinalIgnoreCase);
+ }
+
+ public void EncodeImage(string inputPath, string outputPath, bool autoOrient, int width, int height, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
+ {
+ // Even if the caller specified 100, don't use it because it takes forever
+ quality = Math.Min(quality, 99);
+
+ if (string.IsNullOrWhiteSpace(options.BackgroundColor) || !HasTransparency(inputPath))
+ {
+ using (var originalImage = new MagickWand(inputPath))
+ {
+ ScaleImage(originalImage, width, height);
+
+ if (autoOrient)
+ {
+ AutoOrientImage(originalImage);
+ }
+
+ AddForegroundLayer(originalImage, options);
+ DrawIndicator(originalImage, width, height, options);
+
+ originalImage.CurrentImage.CompressionQuality = quality;
+ originalImage.CurrentImage.StripImage();
+
+ originalImage.SaveImage(outputPath);
+ }
+ }
+ else
+ {
+ using (var wand = new MagickWand(width, height, options.BackgroundColor))
+ {
+ using (var originalImage = new MagickWand(inputPath))
+ {
+ ScaleImage(originalImage, width, height);
+
+ if (autoOrient)
+ {
+ AutoOrientImage(originalImage);
+ }
+
+ wand.CurrentImage.CompositeImage(originalImage, CompositeOperator.OverCompositeOp, 0, 0);
+
+ AddForegroundLayer(wand, options);
+ DrawIndicator(wand, width, height, options);
+
+ wand.CurrentImage.CompressionQuality = quality;
+ wand.CurrentImage.StripImage();
+
+ wand.SaveImage(outputPath);
+ }
+ }
+ }
+ }
+
+ private void AddForegroundLayer(MagickWand wand, ImageProcessingOptions options)
+ {
+ if (string.IsNullOrWhiteSpace(options.ForegroundLayer))
+ {
+ return;
+ }
+
+ Double opacity;
+ if (!Double.TryParse(options.ForegroundLayer, out opacity)) opacity = .4;
+
+ using (var pixel = new PixelWand("#000", opacity))
+ using (var overlay = new MagickWand(wand.CurrentImage.Width, wand.CurrentImage.Height, pixel))
+ {
+ wand.CurrentImage.CompositeImage(overlay, CompositeOperator.OverCompositeOp, 0, 0);
+ }
+ }
+
+ private void AutoOrientImage(MagickWand wand)
+ {
+ wand.CurrentImage.AutoOrientImage();
+ }
+
+ public static void RotateImage(MagickWand wand, float angle)
+ {
+ using (var pixelWand = new PixelWand("none", 1))
+ {
+ wand.CurrentImage.RotateImage(pixelWand, angle);
+ }
+ }
+
+ private void ScaleImage(MagickWand wand, int width, int height)
+ {
+ var highQuality = false;
+
+ if (highQuality)
+ {
+ wand.CurrentImage.ResizeImage(width, height);
+ }
+ else
+ {
+ wand.CurrentImage.ScaleImage(width, height);
+ }
+ }
+
+ /// <summary>
+ /// Draws the indicator.
+ /// </summary>
+ /// <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(MagickWand wand, int imageWidth, int imageHeight, ImageProcessingOptions options)
+ {
+ if (!options.AddPlayedIndicator && !options.UnplayedCount.HasValue && options.PercentPlayed.Equals(0))
+ {
+ return;
+ }
+
+ try
+ {
+ if (options.AddPlayedIndicator)
+ {
+ var currentImageSize = new ImageSize(imageWidth, imageHeight);
+
+ var task = new PlayedIndicatorDrawer(_appPaths, _httpClient, _fileSystem).DrawPlayedIndicator(wand, currentImageSize);
+ Task.WaitAll(task);
+ }
+ else if (options.UnplayedCount.HasValue)
+ {
+ var currentImageSize = new ImageSize(imageWidth, imageHeight);
+
+ new UnplayedCountIndicator(_appPaths, _fileSystem).DrawUnplayedCountIndicator(wand, currentImageSize, options.UnplayedCount.Value);
+ }
+
+ if (options.PercentPlayed > 0)
+ {
+ new PercentPlayedDrawer().Process(wand, options.PercentPlayed);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error drawing indicator overlay", ex);
+ }
+ }
+
+ public void CreateImageCollage(ImageCollageOptions options)
+ {
+ double ratio = options.Width;
+ ratio /= options.Height;
+
+ if (ratio >= 1.4)
+ {
+ new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height);
+ }
+ else if (ratio >= .9)
+ {
+ new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height);
+ }
+ else
+ {
+ new StripCollageBuilder(_appPaths, _fileSystem).BuildPosterCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height);
+ }
+ }
+
+ public string Name
+ {
+ get { return "ImageMagick"; }
+ }
+
+ private bool _disposed;
+ public void Dispose()
+ {
+ _disposed = true;
+ Wand.CloseEnvironment();
+ }
+
+ private void CheckDisposed()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().Name);
+ }
+ }
+
+ public bool SupportsImageCollageCreation
+ {
+ get { return true; }
+ }
+
+ public bool SupportsImageEncoding
+ {
+ get { return true; }
+ }
+ }
+}
diff --git a/Emby.Drawing.ImageMagick/PercentPlayedDrawer.cs b/Emby.Drawing.ImageMagick/PercentPlayedDrawer.cs
new file mode 100644
index 000000000..90f9d5609
--- /dev/null
+++ b/Emby.Drawing.ImageMagick/PercentPlayedDrawer.cs
@@ -0,0 +1,40 @@
+using ImageMagickSharp;
+using System;
+
+namespace Emby.Drawing.ImageMagick
+{
+ public class PercentPlayedDrawer
+ {
+ private const int IndicatorHeight = 8;
+
+ public void Process(MagickWand wand, double percent)
+ {
+ var currentImage = wand.CurrentImage;
+ var height = currentImage.Height;
+
+ using (var draw = new DrawingWand())
+ {
+ using (PixelWand pixel = new PixelWand())
+ {
+ 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;
+
+ pixel.Color = "#52B54B";
+ pixel.Opacity = 0;
+ draw.FillColor = pixel;
+ draw.DrawRectangle(0, endY - IndicatorHeight, Convert.ToInt32(Math.Round(foregroundWidth)), endY);
+ wand.CurrentImage.DrawImage(draw);
+ }
+ }
+ }
+ }
+}
diff --git a/Emby.Drawing.ImageMagick/PlayedIndicatorDrawer.cs b/Emby.Drawing.ImageMagick/PlayedIndicatorDrawer.cs
new file mode 100644
index 000000000..14fb0ddf1
--- /dev/null
+++ b/Emby.Drawing.ImageMagick/PlayedIndicatorDrawer.cs
@@ -0,0 +1,125 @@
+using ImageMagickSharp;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Drawing;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Model.IO;
+
+namespace Emby.Drawing.ImageMagick
+{
+ public class PlayedIndicatorDrawer
+ {
+ private const int FontSize = 52;
+ private const int OffsetFromTopRightCorner = 38;
+
+ private readonly IApplicationPaths _appPaths;
+ private readonly IHttpClient _iHttpClient;
+ private readonly IFileSystem _fileSystem;
+
+ public PlayedIndicatorDrawer(IApplicationPaths appPaths, IHttpClient iHttpClient, IFileSystem fileSystem)
+ {
+ _appPaths = appPaths;
+ _iHttpClient = iHttpClient;
+ _fileSystem = fileSystem;
+ }
+
+ public async Task DrawPlayedIndicator(MagickWand wand, ImageSize imageSize)
+ {
+ var x = imageSize.Width - OffsetFromTopRightCorner;
+
+ using (var draw = new DrawingWand())
+ {
+ using (PixelWand pixel = new PixelWand())
+ {
+ 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 = await DownloadFont("webdings.ttf", "https://github.com/MediaBrowser/Emby.Resources/raw/master/fonts/webdings.ttf", _appPaths, _iHttpClient, _fileSystem).ConfigureAwait(false);
+ 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);
+ }
+ }
+ }
+
+ internal static string ExtractFont(string name, IApplicationPaths paths, IFileSystem fileSystem)
+ {
+ var filePath = Path.Combine(paths.ProgramDataPath, "fonts", name);
+
+ if (fileSystem.FileExists(filePath))
+ {
+ return filePath;
+ }
+
+ var namespacePath = typeof(PlayedIndicatorDrawer).Namespace + ".fonts." + name;
+ var tempPath = Path.Combine(paths.TempDirectory, Guid.NewGuid().ToString("N") + ".ttf");
+ fileSystem.CreateDirectory(Path.GetDirectoryName(tempPath));
+
+ using (var stream = typeof(PlayedIndicatorDrawer).Assembly.GetManifestResourceStream(namespacePath))
+ {
+ using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.Read))
+ {
+ stream.CopyTo(fileStream);
+ }
+ }
+
+ fileSystem.CreateDirectory(Path.GetDirectoryName(filePath));
+
+ try
+ {
+ fileSystem.CopyFile(tempPath, filePath, false);
+ }
+ catch (IOException)
+ {
+
+ }
+
+ return tempPath;
+ }
+
+ internal static async Task<string> DownloadFont(string name, string url, IApplicationPaths paths, IHttpClient httpClient, IFileSystem fileSystem)
+ {
+ var filePath = Path.Combine(paths.ProgramDataPath, "fonts", name);
+
+ if (fileSystem.FileExists(filePath))
+ {
+ return filePath;
+ }
+
+ var tempPath = await httpClient.GetTempFile(new HttpRequestOptions
+ {
+ Url = url,
+ Progress = new Progress<double>()
+
+ }).ConfigureAwait(false);
+
+ fileSystem.CreateDirectory(Path.GetDirectoryName(filePath));
+
+ try
+ {
+ fileSystem.CopyFile(tempPath, filePath, false);
+ }
+ catch (IOException)
+ {
+
+ }
+
+ return tempPath;
+ }
+ }
+}
diff --git a/Emby.Drawing.ImageMagick/Properties/AssemblyInfo.cs b/Emby.Drawing.ImageMagick/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..1089607d6
--- /dev/null
+++ b/Emby.Drawing.ImageMagick/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Emby.Drawing.ImageMagick")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Emby.Drawing.ImageMagick")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("6cfee013-6e7c-432b-ac37-cabf0880c69a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Emby.Drawing.ImageMagick/StripCollageBuilder.cs b/Emby.Drawing.ImageMagick/StripCollageBuilder.cs
new file mode 100644
index 000000000..715ab4680
--- /dev/null
+++ b/Emby.Drawing.ImageMagick/StripCollageBuilder.cs
@@ -0,0 +1,202 @@
+using ImageMagickSharp;
+using MediaBrowser.Common.Configuration;
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Model.IO;
+
+namespace Emby.Drawing.ImageMagick
+{
+ public class StripCollageBuilder
+ {
+ private readonly IApplicationPaths _appPaths;
+ private readonly IFileSystem _fileSystem;
+
+ public StripCollageBuilder(IApplicationPaths appPaths, IFileSystem fileSystem)
+ {
+ _appPaths = appPaths;
+ _fileSystem = fileSystem;
+ }
+
+ public void BuildPosterCollage(List<string> paths, string outputPath, int width, int height)
+ {
+ using (var wand = BuildPosterCollageWand(paths, width, height))
+ {
+ wand.SaveImage(outputPath);
+ }
+ }
+
+ public void BuildSquareCollage(List<string> paths, string outputPath, int width, int height)
+ {
+ using (var wand = BuildSquareCollageWand(paths, width, height))
+ {
+ wand.SaveImage(outputPath);
+ }
+ }
+
+ public void BuildThumbCollage(List<string> paths, string outputPath, int width, int height)
+ {
+ using (var wand = BuildThumbCollageWand(paths, width, height))
+ {
+ wand.SaveImage(outputPath);
+ }
+ }
+
+ private MagickWand BuildPosterCollageWand(List<string> paths, int width, int height)
+ {
+ var inputPaths = ImageHelpers.ProjectPaths(paths, 3);
+ using (var wandImages = new MagickWand(inputPaths.ToArray()))
+ {
+ var wand = new MagickWand(width, height);
+ wand.OpenImage("gradient:#111111-#111111");
+ using (var draw = new DrawingWand())
+ {
+ var iSlice = Convert.ToInt32(width * 0.3);
+ int iTrans = Convert.ToInt32(height * .25);
+ int iHeight = Convert.ToInt32(height * .65);
+ var horizontalImagePadding = Convert.ToInt32(width * 0.0366);
+
+ foreach (var element in wandImages.ImageList)
+ {
+ using (var blackPixelWand = new PixelWand(ColorName.Black))
+ {
+ int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height);
+ element.Gravity = GravityType.CenterGravity;
+ element.BackgroundColor = blackPixelWand;
+ element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter);
+ int ix = (int)Math.Abs((iWidth - iSlice) / 2);
+ element.CropImage(iSlice, iHeight, ix, 0);
+
+ element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0);
+ }
+ }
+
+ wandImages.SetFirstIterator();
+ using (var wandList = wandImages.AppendImages())
+ {
+ wandList.CurrentImage.TrimImage(1);
+ using (var mwr = wandList.CloneMagickWand())
+ {
+ using (var blackPixelWand = new PixelWand(ColorName.Black))
+ {
+ using (var greyPixelWand = new PixelWand(ColorName.Grey70))
+ {
+ mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1);
+ mwr.CurrentImage.FlipImage();
+
+ mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel;
+ mwr.CurrentImage.ColorizeImage(blackPixelWand, greyPixelWand);
+
+ using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans))
+ {
+ mwg.OpenImage("gradient:black-none");
+ var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
+ mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.CopyOpacityCompositeOp, 0, verticalSpacing);
+
+ wandList.AddImage(mwr);
+ int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
+ wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .05));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return wand;
+ }
+ }
+
+ private MagickWand BuildThumbCollageWand(List<string> paths, int width, int height)
+ {
+ var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
+ using (var wandImages = new MagickWand(inputPaths.ToArray()))
+ {
+ var wand = new MagickWand(width, height);
+ wand.OpenImage("gradient:#111111-#111111");
+ using (var draw = new DrawingWand())
+ {
+ var iSlice = Convert.ToInt32(width * 0.24125);
+ int iTrans = Convert.ToInt32(height * .25);
+ int iHeight = Convert.ToInt32(height * .70);
+ var horizontalImagePadding = Convert.ToInt32(width * 0.0125);
+
+ foreach (var element in wandImages.ImageList)
+ {
+ using (var blackPixelWand = new PixelWand(ColorName.Black))
+ {
+ int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height);
+ element.Gravity = GravityType.CenterGravity;
+ element.BackgroundColor = blackPixelWand;
+ element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter);
+ int ix = (int)Math.Abs((iWidth - iSlice) / 2);
+ element.CropImage(iSlice, iHeight, ix, 0);
+
+ element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0);
+ }
+ }
+
+ wandImages.SetFirstIterator();
+ using (var wandList = wandImages.AppendImages())
+ {
+ wandList.CurrentImage.TrimImage(1);
+ using (var mwr = wandList.CloneMagickWand())
+ {
+ using (var blackPixelWand = new PixelWand(ColorName.Black))
+ {
+ using (var greyPixelWand = new PixelWand(ColorName.Grey70))
+ {
+ mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1);
+ mwr.CurrentImage.FlipImage();
+
+ mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel;
+ mwr.CurrentImage.ColorizeImage(blackPixelWand, greyPixelWand);
+
+ using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans))
+ {
+ mwg.OpenImage("gradient:black-none");
+ var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
+ mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.CopyOpacityCompositeOp, 0, verticalSpacing);
+
+ wandList.AddImage(mwr);
+ int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
+ wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .045));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return wand;
+ }
+ }
+
+ private MagickWand BuildSquareCollageWand(List<string> paths, int width, int height)
+ {
+ var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
+ var outputWand = new MagickWand(width, height, new PixelWand("none", 1));
+ var imageIndex = 0;
+ var cellWidth = width/2;
+ var cellHeight = height/2;
+ for (var x = 0; x < 2; x++)
+ {
+ for (var y = 0; y < 2; y++)
+ {
+ using (var temp = new MagickWand(inputPaths[imageIndex]))
+ {
+ temp.CurrentImage.ScaleImage(cellWidth, cellHeight);
+ // draw this image into the strip at the next position
+ var xPos = x*cellWidth;
+ var yPos = y*cellHeight;
+ outputWand.CurrentImage.CompositeImage(temp, CompositeOperator.OverCompositeOp, xPos, yPos);
+ }
+ imageIndex++;
+ }
+ }
+
+ return outputWand;
+ }
+ }
+}
diff --git a/Emby.Drawing.ImageMagick/UnplayedCountIndicator.cs b/Emby.Drawing.ImageMagick/UnplayedCountIndicator.cs
new file mode 100644
index 000000000..c53140099
--- /dev/null
+++ b/Emby.Drawing.ImageMagick/UnplayedCountIndicator.cs
@@ -0,0 +1,75 @@
+using ImageMagickSharp;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Drawing;
+using System.Globalization;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Model.IO;
+
+namespace Emby.Drawing.ImageMagick
+{
+ public class UnplayedCountIndicator
+ {
+ private const int OffsetFromTopRightCorner = 38;
+
+ private readonly IApplicationPaths _appPaths;
+ private readonly IFileSystem _fileSystem;
+
+ public UnplayedCountIndicator(IApplicationPaths appPaths, IFileSystem fileSystem)
+ {
+ _appPaths = appPaths;
+ _fileSystem = fileSystem;
+ }
+
+ public void DrawUnplayedCountIndicator(MagickWand wand, ImageSize imageSize, int count)
+ {
+ var x = imageSize.Width - OffsetFromTopRightCorner;
+ var text = count.ToString(CultureInfo.InvariantCulture);
+
+ using (var draw = new DrawingWand())
+ {
+ using (PixelWand pixel = new PixelWand())
+ {
+ 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 = PlayedIndicatorDrawer.ExtractFont("robotoregular.ttf", _appPaths, _fileSystem);
+ 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)
+ {
+ x += 1;
+ }
+ 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);
+ }
+
+ }
+ }
+ }
+}
diff --git a/Emby.Drawing.ImageMagick/packages.config b/Emby.Drawing.ImageMagick/packages.config
new file mode 100644
index 000000000..619310d28
--- /dev/null
+++ b/Emby.Drawing.ImageMagick/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net452" />
+</packages> \ No newline at end of file