浏览代码

Add support for converting from svg to other image types

Cody Robibero 1 年之前
父节点
当前提交
c5e723bccd

+ 1 - 2
Jellyfin.Api/Controllers/ImageController.cs

@@ -11,7 +11,6 @@ using System.Security.Cryptography;
 using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Api.Attributes;
-using Jellyfin.Api.Constants;
 using Jellyfin.Api.Helpers;
 using MediaBrowser.Common.Api;
 using MediaBrowser.Common.Configuration;
@@ -1993,7 +1992,7 @@ public class ImageController : BaseJellyfinApiController
     {
         if (format.HasValue)
         {
-            return new[] { format.Value };
+            return [format.Value];
         }
 
         return GetClientSupportedFormats();

+ 6 - 1
MediaBrowser.Model/Drawing/ImageFormat.cs

@@ -28,6 +28,11 @@ namespace MediaBrowser.Model.Drawing
         /// <summary>
         /// The webp.
         /// </summary>
-        Webp
+        Webp,
+
+        /// <summary>
+        /// The svg format.
+        /// </summary>
+        Svg,
     }
 }

+ 2 - 0
MediaBrowser.Model/Drawing/ImageFormatExtensions.cs

@@ -22,6 +22,7 @@ public static class ImageFormatExtensions
             ImageFormat.Jpg => MediaTypeNames.Image.Jpeg,
             ImageFormat.Png => "image/png",
             ImageFormat.Webp => "image/webp",
+            ImageFormat.Svg => "image/svg+xml",
             _ => throw new InvalidEnumArgumentException(nameof(format), (int)format, typeof(ImageFormat))
         };
 
@@ -39,6 +40,7 @@ public static class ImageFormatExtensions
             ImageFormat.Jpg => ".jpg",
             ImageFormat.Png => ".png",
             ImageFormat.Webp => ".webp",
+            ImageFormat.Svg => ".svg",
             _ => throw new InvalidEnumArgumentException(nameof(format), (int)format, typeof(ImageFormat))
         };
 }

+ 39 - 4
src/Jellyfin.Drawing.Skia/SkiaEncoder.cs

@@ -19,8 +19,8 @@ namespace Jellyfin.Drawing.Skia;
 /// </summary>
 public class SkiaEncoder : IImageEncoder
 {
+    private const string SvgFormat = "svg";
     private static readonly HashSet<string> _transparentImageTypes = new(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
-
     private readonly ILogger<SkiaEncoder> _logger;
     private readonly IApplicationPaths _appPaths;
     private static readonly SKImageFilter _imageFilter;
@@ -89,12 +89,13 @@ public class SkiaEncoder : IImageEncoder
             // working on windows at least
             "cr2",
             "nef",
-            "arw"
+            "arw",
+            SvgFormat
         };
 
     /// <inheritdoc/>
     public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
-        => new HashSet<ImageFormat> { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
+        => new HashSet<ImageFormat> { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png, ImageFormat.Svg };
 
     /// <summary>
     /// Check if the native lib is available.
@@ -312,6 +313,31 @@ public class SkiaEncoder : IImageEncoder
         return Decode(path, false, orientation, out _);
     }
 
+    private SKBitmap? GetBitmapFromSvg(string path)
+    {
+        if (!File.Exists(path))
+        {
+            throw new FileNotFoundException("File not found", path);
+        }
+
+        using var svg = SKSvg.CreateFromFile(path);
+        if (svg.Drawable is null)
+        {
+            return null;
+        }
+
+        var width = (int)Math.Round(svg.Drawable.Bounds.Width);
+        var height = (int)Math.Round(svg.Drawable.Bounds.Height);
+
+        var bitmap = new SKBitmap(width, height);
+        using var canvas = new SKCanvas(bitmap);
+        canvas.DrawPicture(svg.Picture);
+        canvas.Flush();
+        canvas.Save();
+
+        return bitmap;
+    }
+
     private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
     {
         var needsFlip = origin is SKEncodedOrigin.LeftBottom or SKEncodedOrigin.LeftTop or SKEncodedOrigin.RightBottom or SKEncodedOrigin.RightTop;
@@ -402,6 +428,12 @@ public class SkiaEncoder : IImageEncoder
             return inputPath;
         }
 
+        if (outputFormat == ImageFormat.Svg
+            && !inputFormat.Equals(SvgFormat, StringComparison.OrdinalIgnoreCase))
+        {
+            throw new InvalidOperationException($"Requested svg output from {inputFormat} input");
+        }
+
         var skiaOutputFormat = GetImageFormat(outputFormat);
 
         var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);
@@ -409,7 +441,10 @@ public class SkiaEncoder : IImageEncoder
         var blur = options.Blur ?? 0;
         var hasIndicator = options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0);
 
-        using var bitmap = GetBitmap(inputPath, autoOrient, orientation);
+        using var bitmap = inputFormat.Equals(SvgFormat, StringComparison.OrdinalIgnoreCase)
+            ? GetBitmapFromSvg(inputPath)
+            : GetBitmap(inputPath, autoOrient, orientation);
+
         if (bitmap is null)
         {
             throw new InvalidDataException($"Skia unable to read image {inputPath}");

+ 2 - 2
tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs

@@ -27,7 +27,7 @@ public static class ImageFormatExtensionsTests
     [InlineData((ImageFormat)int.MinValue)]
     [InlineData((ImageFormat)int.MaxValue)]
     [InlineData((ImageFormat)(-1))]
-    [InlineData((ImageFormat)5)]
+    [InlineData((ImageFormat)6)]
     public static void GetMimeType_Valid_ThrowsInvalidEnumArgumentException(ImageFormat format)
         => Assert.Throws<InvalidEnumArgumentException>(() => format.GetMimeType());
 
@@ -40,7 +40,7 @@ public static class ImageFormatExtensionsTests
     [InlineData((ImageFormat)int.MinValue)]
     [InlineData((ImageFormat)int.MaxValue)]
     [InlineData((ImageFormat)(-1))]
-    [InlineData((ImageFormat)5)]
+    [InlineData((ImageFormat)6)]
     public static void GetExtension_Valid_ThrowsInvalidEnumArgumentException(ImageFormat format)
         => Assert.Throws<InvalidEnumArgumentException>(() => format.GetExtension());
 }