Add support for converting from svg to other image types

pull/11077/head
Cody Robibero 3 months ago
parent 4f0f364ac9
commit c5e723bccd

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

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

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

@ -19,8 +19,8 @@ namespace Jellyfin.Drawing.Skia;
/// </summary> /// </summary>
public class SkiaEncoder : IImageEncoder public class SkiaEncoder : IImageEncoder
{ {
private const string SvgFormat = "svg";
private static readonly HashSet<string> _transparentImageTypes = new(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; private static readonly HashSet<string> _transparentImageTypes = new(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
private readonly ILogger<SkiaEncoder> _logger; private readonly ILogger<SkiaEncoder> _logger;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private static readonly SKImageFilter _imageFilter; private static readonly SKImageFilter _imageFilter;
@ -89,12 +89,13 @@ public class SkiaEncoder : IImageEncoder
// working on windows at least // working on windows at least
"cr2", "cr2",
"nef", "nef",
"arw" "arw",
SvgFormat
}; };
/// <inheritdoc/> /// <inheritdoc/>
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats 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> /// <summary>
/// Check if the native lib is available. /// Check if the native lib is available.
@ -312,6 +313,31 @@ public class SkiaEncoder : IImageEncoder
return Decode(path, false, orientation, out _); 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) private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
{ {
var needsFlip = origin is SKEncodedOrigin.LeftBottom or SKEncodedOrigin.LeftTop or SKEncodedOrigin.RightBottom or SKEncodedOrigin.RightTop; 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; 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 skiaOutputFormat = GetImageFormat(outputFormat);
var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor); var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);
@ -409,7 +441,10 @@ public class SkiaEncoder : IImageEncoder
var blur = options.Blur ?? 0; var blur = options.Blur ?? 0;
var hasIndicator = options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(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) if (bitmap is null)
{ {
throw new InvalidDataException($"Skia unable to read image {inputPath}"); throw new InvalidDataException($"Skia unable to read image {inputPath}");

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

Loading…
Cancel
Save