diff options
Diffstat (limited to 'Emby.Drawing.ImageMagick')
| -rw-r--r-- | Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj | 83 | ||||
| -rw-r--r-- | Emby.Drawing.ImageMagick/ImageHelpers.cs | 43 | ||||
| -rw-r--r-- | Emby.Drawing.ImageMagick/ImageMagickEncoder.cs | 333 | ||||
| -rw-r--r-- | Emby.Drawing.ImageMagick/PercentPlayedDrawer.cs | 40 | ||||
| -rw-r--r-- | Emby.Drawing.ImageMagick/PlayedIndicatorDrawer.cs | 125 | ||||
| -rw-r--r-- | Emby.Drawing.ImageMagick/Properties/AssemblyInfo.cs | 36 | ||||
| -rw-r--r-- | Emby.Drawing.ImageMagick/StripCollageBuilder.cs | 202 | ||||
| -rw-r--r-- | Emby.Drawing.ImageMagick/UnplayedCountIndicator.cs | 75 | ||||
| -rw-r--r-- | Emby.Drawing.ImageMagick/packages.config | 4 |
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 |
