diff options
| -rw-r--r-- | Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj | 13 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs | 9 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs | 15 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/SkiaCodecException.cs | 16 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/SkiaEncoder.cs | 49 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/SkiaException.cs | 19 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 27 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs | 17 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Drawing/IImageEncoder.cs | 16 | ||||
| -rw-r--r-- | jellyfin.ruleset | 2 |
10 files changed, 152 insertions, 31 deletions
diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 988ac364a..febb1adab 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -4,6 +4,7 @@ <TargetFramework>netstandard2.1</TargetFramework> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateDocumentationFile>true</GenerateDocumentationFile> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> </PropertyGroup> <ItemGroup> @@ -22,4 +23,16 @@ <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" /> </ItemGroup> + <!-- Code analysers--> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" /> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + </ItemGroup> + + <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> + <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + </Project> diff --git a/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs b/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs index c72f295fd..f2df066ec 100644 --- a/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs +++ b/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs @@ -4,10 +4,19 @@ using SkiaSharp; namespace Jellyfin.Drawing.Skia { + /// <summary> + /// Static helper class used to draw percentage-played indicators on images. + /// </summary> public static class PercentPlayedDrawer { private const int IndicatorHeight = 8; + /// <summary> + /// Draw a percentage played indicator on a canvas. + /// </summary> + /// <param name="canvas">The canvas to draw the indicator on.</param> + /// <param name="imageSize">The size of the image being drawn on.</param> + /// <param name="percent">The percentage played to display with the indicator.</param> public static void Process(SKCanvas canvas, ImageDimensions imageSize, double percent) { using (var paint = new SKPaint()) diff --git a/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs b/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs index 7f3c18bb2..5084fd211 100644 --- a/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs +++ b/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs @@ -3,10 +3,21 @@ using SkiaSharp; namespace Jellyfin.Drawing.Skia { + /// <summary> + /// Static helper class for drawing 'played' indicators. + /// </summary> public static class PlayedIndicatorDrawer { private const int OffsetFromTopRightCorner = 38; + /// <summary> + /// Draw a 'played' indicator in the top right corner of a canvas. + /// </summary> + /// <param name="canvas">The canvas to draw the indicator on.</param> + /// <param name="imageSize"> + /// The dimensions of the image to draw the indicator on. The width is used to determine the x-position of the + /// indicator. + /// </param> public static void DrawPlayedIndicator(SKCanvas canvas, ImageDimensions imageSize) { var x = imageSize.Width - OffsetFromTopRightCorner; @@ -26,10 +37,10 @@ namespace Jellyfin.Drawing.Skia paint.TextSize = 30; paint.IsAntialias = true; + // or: + // var emojiChar = 0x1F680; var text = "✔️"; var emojiChar = StringUtilities.GetUnicodeCharacterCode(text, SKTextEncoding.Utf32); - // or: - //var emojiChar = 0x1F680; // ask the font manager for a font with that character var fontManager = SKFontManager.Default; diff --git a/Jellyfin.Drawing.Skia/SkiaCodecException.cs b/Jellyfin.Drawing.Skia/SkiaCodecException.cs index f848636bc..8158b846d 100644 --- a/Jellyfin.Drawing.Skia/SkiaCodecException.cs +++ b/Jellyfin.Drawing.Skia/SkiaCodecException.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Globalization; using SkiaSharp; @@ -9,15 +10,9 @@ namespace Jellyfin.Drawing.Skia public class SkiaCodecException : SkiaException { /// <summary> - /// Returns the non-successfull codec result returned by Skia. - /// </summary> - /// <value>The non-successfull codec result returned by Skia.</value> - public SKCodecResult CodecResult { get; } - - /// <summary> /// Initializes a new instance of the <see cref="SkiaCodecException" /> class. /// </summary> - /// <param name="result">The non-successfull codec result returned by Skia.</param> + /// <param name="result">The non-successful codec result returned by Skia.</param> public SkiaCodecException(SKCodecResult result) : base() { CodecResult = result; @@ -27,7 +22,7 @@ namespace Jellyfin.Drawing.Skia /// Initializes a new instance of the <see cref="SkiaCodecException" /> class /// with a specified error message. /// </summary> - /// <param name="result">The non-successfull codec result returned by Skia.</param> + /// <param name="result">The non-successful codec result returned by Skia.</param> /// <param name="message">The message that describes the error.</param> public SkiaCodecException(SKCodecResult result, string message) : base(message) @@ -35,6 +30,11 @@ namespace Jellyfin.Drawing.Skia CodecResult = result; } + /// <summary> + /// Gets the non-successful codec result returned by Skia. + /// </summary> + public SKCodecResult CodecResult { get; } + /// <inheritdoc /> public override string ToString() => string.Format( diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 66b814f6e..b080b3e6a 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -13,6 +13,9 @@ using static Jellyfin.Drawing.Skia.SkiaHelper; namespace Jellyfin.Drawing.Skia { + /// <summary> + /// Image encoder that uses <see cref="SkiaSharp"/> to manipulate images. + /// </summary> public class SkiaEncoder : IImageEncoder { private readonly ILogger _logger; @@ -22,6 +25,12 @@ namespace Jellyfin.Drawing.Skia private static readonly HashSet<string> _transparentImageTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; + /// <summary> + /// Initializes a new instance of the <see cref="SkiaEncoder"/> class. + /// </summary> + /// <param name="logger">The application logger.</param> + /// <param name="appPaths">The application paths.</param> + /// <param name="localizationManager">The application localization manager.</param> public SkiaEncoder( ILogger<SkiaEncoder> logger, IApplicationPaths appPaths, @@ -32,12 +41,16 @@ namespace Jellyfin.Drawing.Skia _localizationManager = localizationManager; } + /// <inheritdoc/> public string Name => "Skia"; + /// <inheritdoc/> public bool SupportsImageCollageCreation => true; + /// <inheritdoc/> public bool SupportsImageEncoding => true; + /// <inheritdoc/> public IReadOnlyCollection<string> SupportedInputFormats => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { @@ -65,11 +78,12 @@ namespace Jellyfin.Drawing.Skia "arw" }; + /// <inheritdoc/> public IReadOnlyCollection<ImageFormat> SupportedOutputFormats => new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png }; /// <summary> - /// Test to determine if the native lib is available + /// Test to determine if the native lib is available. /// </summary> public static void TestSkia() { @@ -80,6 +94,11 @@ namespace Jellyfin.Drawing.Skia private static bool IsTransparent(SKColor color) => (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0; + /// <summary> + /// Convert a <see cref="ImageFormat"/> to a <see cref="SKEncodedImageFormat"/>. + /// </summary> + /// <param name="selectedFormat">The format to convert.</param> + /// <returns>The converted format.</returns> public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat) { switch (selectedFormat) @@ -186,6 +205,9 @@ namespace Jellyfin.Drawing.Skia } /// <inheritdoc /> + /// <exception cref="ArgumentNullException">The path is null.</exception> + /// <exception cref="FileNotFoundException">The path is not valid.</exception> + /// <exception cref="SkiaCodecException">The file at the specified path could not be used to generate a codec.</exception> public ImageDimensions GetImageSize(string path) { if (path == null) @@ -269,6 +291,14 @@ namespace Jellyfin.Drawing.Skia } } + /// <summary> + /// Decode an image. + /// </summary> + /// <param name="path">The filepath of the image to decode.</param> + /// <param name="forceCleanBitmap">Whether to force clean the bitmap.</param> + /// <param name="orientation">The orientation of the image.</param> + /// <param name="origin">The detected origin of the image.</param> + /// <returns>The resulting bitmap of the image.</returns> internal SKBitmap Decode(string path, bool forceCleanBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin) { if (!File.Exists(path)) @@ -358,16 +388,6 @@ namespace Jellyfin.Drawing.Skia private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin) { - //var transformations = { - // 2: { rotate: 0, flip: true}, - // 3: { rotate: 180, flip: false}, - // 4: { rotate: 180, flip: true}, - // 5: { rotate: 90, flip: true}, - // 6: { rotate: 90, flip: false}, - // 7: { rotate: 270, flip: true}, - // 8: { rotate: 270, flip: false}, - //} - switch (origin) { case SKEncodedOrigin.TopRight: @@ -497,6 +517,7 @@ namespace Jellyfin.Drawing.Skia } } + /// <inheritdoc/> public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { if (string.IsNullOrWhiteSpace(inputPath)) @@ -520,7 +541,7 @@ namespace Jellyfin.Drawing.Skia { if (bitmap == null) { - throw new ArgumentOutOfRangeException(string.Format("Skia unable to read image {0}", inputPath)); + throw new ArgumentOutOfRangeException($"Skia unable to read image {inputPath}"); } var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height); @@ -556,7 +577,7 @@ namespace Jellyfin.Drawing.Skia } // create bitmap to use for canvas drawing used to draw into bitmap - using (var saveBitmap = new SKBitmap(width, height))//, bitmap.ColorType, bitmap.AlphaType)) + using (var saveBitmap = new SKBitmap(width, height)) // , bitmap.ColorType, bitmap.AlphaType)) using (var canvas = new SKCanvas(saveBitmap)) { // set background color if present @@ -609,9 +630,11 @@ namespace Jellyfin.Drawing.Skia } } } + return outputPath; } + /// <inheritdoc/> public void CreateImageCollage(ImageCollageOptions options) { double ratio = (double)options.Width / options.Height; diff --git a/Jellyfin.Drawing.Skia/SkiaException.cs b/Jellyfin.Drawing.Skia/SkiaException.cs index 7aeaf083e..968d3a244 100644 --- a/Jellyfin.Drawing.Skia/SkiaException.cs +++ b/Jellyfin.Drawing.Skia/SkiaException.cs @@ -7,17 +7,30 @@ namespace Jellyfin.Drawing.Skia /// </summary> public class SkiaException : Exception { - /// <inheritdoc /> + /// <summary> + /// Initializes a new instance of the <see cref="SkiaException"/> class. + /// </summary> public SkiaException() : base() { } - /// <inheritdoc /> + /// <summary> + /// Initializes a new instance of the <see cref="SkiaException"/> class with a specified error message. + /// </summary> + /// <param name="message">The message that describes the error.</param> public SkiaException(string message) : base(message) { } - /// <inheritdoc /> + /// <summary> + /// Initializes a new instance of the <see cref="SkiaException"/> class with a specified error message and a + /// reference to the inner exception that is the cause of this exception. + /// </summary> + /// <param name="message">The error message that explains the reason for the exception.</param> + /// <param name="innerException"> + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if + /// no inner exception is specified. + /// </param> public SkiaException(string message, Exception innerException) : base(message, innerException) { diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 1f2a6e81a..0735ef194 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -5,15 +5,27 @@ using SkiaSharp; namespace Jellyfin.Drawing.Skia { + /// <summary> + /// Used to build collages of multiple images arranged in vertical strips. + /// </summary> public class StripCollageBuilder { private readonly SkiaEncoder _skiaEncoder; + /// <summary> + /// Initializes a new instance of the <see cref="StripCollageBuilder"/> class. + /// </summary> + /// <param name="skiaEncoder">The encoder to use for building collages.</param> public StripCollageBuilder(SkiaEncoder skiaEncoder) { _skiaEncoder = skiaEncoder; } + /// <summary> + /// Check which format an image has been encoded with using its filename extension. + /// </summary> + /// <param name="outputPath">The path to the image to get the format for.</param> + /// <returns>The image format.</returns> public static SKEncodedImageFormat GetEncodedFormat(string outputPath) { if (outputPath == null) @@ -48,6 +60,13 @@ namespace Jellyfin.Drawing.Skia return SKEncodedImageFormat.Png; } + /// <summary> + /// Create a square collage. + /// </summary> + /// <param name="paths">The paths of the images to use in the collage.</param> + /// <param name="outputPath">The path at which to place the resulting collage image.</param> + /// <param name="width">The desired width of the collage.</param> + /// <param name="height">The desired height of the collage.</param> public void BuildSquareCollage(string[] paths, string outputPath, int width, int height) { using (var bitmap = BuildSquareCollageBitmap(paths, width, height)) @@ -58,6 +77,13 @@ namespace Jellyfin.Drawing.Skia } } + /// <summary> + /// Create a thumb collage. + /// </summary> + /// <param name="paths">The paths of the images to use in the collage.</param> + /// <param name="outputPath">The path at which to place the resulting image.</param> + /// <param name="width">The desired width of the collage.</param> + /// <param name="height">The desired height of the collage.</param> public void BuildThumbCollage(string[] paths, string outputPath, int width, int height) { using (var bitmap = BuildThumbCollageBitmap(paths, width, height)) @@ -98,6 +124,7 @@ namespace Jellyfin.Drawing.Skia using (var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType)) { currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High); + // crop image int ix = (int)Math.Abs((iWidth - iSlice) / 2); using (var image = SKImage.FromBitmap(resizeBitmap)) diff --git a/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs b/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs index dbf935f4e..a10fff9df 100644 --- a/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs +++ b/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs @@ -4,10 +4,25 @@ using SkiaSharp; namespace Jellyfin.Drawing.Skia { + /// <summary> + /// Static helper class for drawing unplayed count indicators. + /// </summary> public static class UnplayedCountIndicator { + /// <summary> + /// The x-offset used when drawing an unplayed count indicator. + /// </summary> private const int OffsetFromTopRightCorner = 38; + /// <summary> + /// Draw an unplayed count indicator in the top right corner of a canvas. + /// </summary> + /// <param name="canvas">The canvas to draw the indicator on.</param> + /// <param name="imageSize"> + /// The dimensions of the image to draw the indicator on. The width is used to determine the x-position of the + /// indicator. + /// </param> + /// <param name="count">The number to draw in the indicator.</param> public static void DrawUnplayedCountIndicator(SKCanvas canvas, ImageDimensions imageSize, int count) { var x = imageSize.Width - OffsetFromTopRightCorner; @@ -19,6 +34,7 @@ namespace Jellyfin.Drawing.Skia paint.Style = SKPaintStyle.Fill; canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint); } + using (var paint = new SKPaint()) { paint.Color = new SKColor(255, 255, 255, 255); @@ -33,6 +49,7 @@ namespace Jellyfin.Drawing.Skia { x -= 7; } + if (text.Length == 2) { x -= 13; diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index a0f9ae46e..88e67b648 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -11,6 +11,7 @@ namespace MediaBrowser.Controller.Drawing /// </summary> /// <value>The supported input formats.</value> IReadOnlyCollection<string> SupportedInputFormats { get; } + /// <summary> /// Gets the supported output formats. /// </summary> @@ -18,9 +19,9 @@ namespace MediaBrowser.Controller.Drawing IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; } /// <summary> - /// Gets the name. + /// Gets the display name for the encoder. /// </summary> - /// <value>The name.</value> + /// <value>The display name.</value> string Name { get; } /// <summary> @@ -35,17 +36,22 @@ namespace MediaBrowser.Controller.Drawing /// <value><c>true</c> if [supports image encoding]; otherwise, <c>false</c>.</value> bool SupportsImageEncoding { get; } + /// <summary> + /// Get the dimensions of an image from the filesystem. + /// </summary> + /// <param name="path">The filepath of the image.</param> + /// <returns>The image dimensions.</returns> ImageDimensions GetImageSize(string path); /// <summary> - /// Encodes the image. + /// Encode an image. /// </summary> string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat); /// <summary> - /// Creates the image collage. + /// Create an image collage. /// </summary> - /// <param name="options">The options.</param> + /// <param name="options">The options to use when creating the collage.</param> void CreateImageCollage(ImageCollageOptions options); } } diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 75b5573b6..27d8a7cd9 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -31,6 +31,8 @@ <Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design"> <!-- disable warning CA1031: Do not catch general exception types --> <Rule Id="CA1031" Action="Info" /> + <!-- disable warning CA1032: Implement standard exception constructors --> + <Rule Id="CA1032" Action="Info" /> <!-- disable warning CA1062: Validate arguments of public methods --> <Rule Id="CA1062" Action="Info" /> <!-- disable warning CA1720: Identifiers should not contain type names --> |
