diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index c031ce338d..8368b846dd 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/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();
diff --git a/MediaBrowser.Model/Drawing/ImageFormat.cs b/MediaBrowser.Model/Drawing/ImageFormat.cs
index 511c16a4e2..6a586f6e32 100644
--- a/MediaBrowser.Model/Drawing/ImageFormat.cs
+++ b/MediaBrowser.Model/Drawing/ImageFormat.cs
@@ -6,28 +6,33 @@ namespace MediaBrowser.Model.Drawing
public enum ImageFormat
{
///
- /// The BMP.
+ /// BMP format.
///
Bmp,
///
- /// The GIF.
+ /// GIF format.
///
Gif,
///
- /// The JPG.
+ /// JPG format.
///
Jpg,
///
- /// The PNG.
+ /// PNG format.
///
Png,
///
- /// The webp.
+ /// WEBP format.
///
- Webp
+ Webp,
+
+ ///
+ /// SVG format.
+ ///
+ Svg,
}
}
diff --git a/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs b/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs
index 1bb24112ec..1c60ba4601 100644
--- a/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs
+++ b/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))
};
}
diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index 4ae5a9a483..a407194992 100644
--- a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -19,8 +19,8 @@ namespace Jellyfin.Drawing.Skia;
///
public class SkiaEncoder : IImageEncoder
{
+ private const string SvgFormat = "svg";
private static readonly HashSet _transparentImageTypes = new(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
-
private readonly ILogger _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
};
///
public IReadOnlyCollection SupportedOutputFormats
- => new HashSet { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
+ => new HashSet { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png, ImageFormat.Svg };
///
/// 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 ArgumentException($"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}");
diff --git a/tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs b/tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs
index 198ad5a274..2399a10a35 100644
--- a/tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs
+++ b/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(() => 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(() => format.GetExtension());
}