Merge pull request #7946 from cvium/svg

(cherry picked from commit 4ebe70cf6a)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
pull/7845/head
Cody Robibero 2 years ago committed by Joshua Boniface
parent a168040cc8
commit 7f1223016d

@ -395,7 +395,13 @@ namespace Emby.Drawing
public string GetImageBlurHash(string path)
{
var size = GetImageDimensions(path);
if (size.Width <= 0 || size.Height <= 0)
return GetImageBlurHash(path, size);
}
/// <inheritdoc />
public string GetImageBlurHash(string path, ImageDimensions imageDimensions)
{
if (imageDimensions.Width <= 0 || imageDimensions.Height <= 0)
{
return string.Empty;
}
@ -403,8 +409,8 @@ namespace Emby.Drawing
// We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance.
// One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width.
// See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components
float xCompF = MathF.Sqrt(16.0f * size.Width / size.Height);
float yCompF = xCompF * size.Height / size.Width;
float xCompF = MathF.Sqrt(16.0f * imageDimensions.Width / imageDimensions.Height);
float yCompF = xCompF * imageDimensions.Height / imageDimensions.Width;
int xComp = Math.Min((int)xCompF + 1, 9);
int yComp = Math.Min((int)yCompF + 1, 9);
@ -439,47 +445,46 @@ namespace Emby.Drawing
.ToString("N", CultureInfo.InvariantCulture);
}
private async Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
{
var inputFormat = Path.GetExtension(originalImagePath)
.TrimStart('.')
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
var inputFormat = Path.GetExtension(originalImagePath.AsSpan()).TrimStart('.').ToString();
// These are just jpg files renamed as tbn
if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase))
{
return (originalImagePath, dateModified);
}
if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
{
try
{
string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
var file = _fileSystem.GetFileInfo(outputPath);
if (!file.Exists)
{
await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
}
else
{
dateModified = file.LastWriteTimeUtc;
}
originalImagePath = outputPath;
}
catch (Exception ex)
{
_logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
}
}
return (originalImagePath, dateModified);
return Task.FromResult((originalImagePath, dateModified));
}
// TODO _mediaEncoder.ConvertImage is not implemented
// if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
// {
// try
// {
// string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
//
// string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
// var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
//
// var file = _fileSystem.GetFileInfo(outputPath);
// if (!file.Exists)
// {
// await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
// dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
// }
// else
// {
// dateModified = file.LastWriteTimeUtc;
// }
//
// originalImagePath = outputPath;
// }
// catch (Exception ex)
// {
// _logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
// }
// }
return Task.FromResult((originalImagePath, dateModified));
}
/// <summary>

@ -1860,7 +1860,9 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(item));
}
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
var outdated = forceUpdate
? item.ImageInfos.Where(i => i.Path != null).ToArray()
: item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
// Skip image processing if current or live tv source
if (outdated.Length == 0 || item.SourceType != SourceType.Library)
{
@ -1883,7 +1885,7 @@ namespace Emby.Server.Implementations.Library
_logger.LogWarning("Cannot get image index for {ImagePath}", img.Path);
continue;
}
catch (Exception ex) when (ex is InvalidOperationException || ex is IOException)
catch (Exception ex) when (ex is InvalidOperationException or IOException)
{
_logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path);
continue;
@ -1895,23 +1897,24 @@ namespace Emby.Server.Implementations.Library
}
}
ImageDimensions size;
try
{
ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
size = _imageProcessor.GetImageDimensions(item, image);
image.Width = size.Width;
image.Height = size.Height;
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
size = new ImageDimensions(0, 0);
image.Width = 0;
image.Height = 0;
continue;
}
try
{
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path, size);
}
catch (Exception ex)
{

@ -20,6 +20,7 @@
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.1" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1-preview.1" />
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
</ItemGroup>
<ItemGroup>

@ -10,7 +10,7 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Drawing;
using Microsoft.Extensions.Logging;
using SkiaSharp;
using static Jellyfin.Drawing.Skia.SkiaHelper;
using SKSvg = SkiaSharp.Extended.Svg.SKSvg;
namespace Jellyfin.Drawing.Skia
{
@ -19,8 +19,7 @@ namespace Jellyfin.Drawing.Skia
/// </summary>
public class SkiaEncoder : IImageEncoder
{
private static readonly HashSet<string> _transparentImageTypes
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
private static readonly HashSet<string> _transparentImageTypes = new(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
private readonly ILogger<SkiaEncoder> _logger;
private readonly IApplicationPaths _appPaths;
@ -71,7 +70,7 @@ namespace Jellyfin.Drawing.Skia
/// <inheritdoc/>
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
=> new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
=> new HashSet<ImageFormat> { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
/// <summary>
/// Check if the native lib is available.
@ -109,9 +108,7 @@ 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 (!File.Exists(path))
@ -119,12 +116,27 @@ namespace Jellyfin.Drawing.Skia
throw new FileNotFoundException("File not found", path);
}
using var codec = SKCodec.Create(path, out SKCodecResult result);
EnsureSuccess(result);
var info = codec.Info;
var extension = Path.GetExtension(path.AsSpan());
if (extension.Equals(".svg", StringComparison.OrdinalIgnoreCase))
{
var svg = new SKSvg();
svg.Load(path);
return new ImageDimensions(Convert.ToInt32(svg.Picture.CullRect.Width), Convert.ToInt32(svg.Picture.CullRect.Height));
}
return new ImageDimensions(info.Width, info.Height);
using var codec = SKCodec.Create(path, out SKCodecResult result);
switch (result)
{
case SKCodecResult.Success:
var info = codec.Info;
return new ImageDimensions(info.Width, info.Height);
case SKCodecResult.Unimplemented:
_logger.LogDebug("Image format not supported: {FilePath}", path);
return new ImageDimensions(0, 0);
default:
_logger.LogError("Unable to determine image dimensions for {FilePath}: {SkCodecResult}", path, result);
return new ImageDimensions(0, 0);
}
}
/// <inheritdoc />
@ -138,6 +150,13 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentNullException(nameof(path));
}
var extension = Path.GetExtension(path.AsSpan()).TrimStart('.');
if (!SupportedInputFormats.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
_logger.LogDebug("Unable to compute blur hash due to unsupported format: {ImagePath}", path);
return string.Empty;
}
// Any larger than 128x128 is too slow and there's no visually discernible difference
return BlurHashEncoder.Encode(xComp, yComp, path, 128, 128);
}
@ -378,6 +397,13 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentException("String can't be empty.", nameof(outputPath));
}
var inputFormat = Path.GetExtension(inputPath.AsSpan()).TrimStart('.');
if (!SupportedInputFormats.Contains(inputFormat, StringComparison.OrdinalIgnoreCase))
{
_logger.LogDebug("Unable to encode image due to unsupported format: {ImagePath}", inputPath);
return inputPath;
}
var skiaOutputFormat = GetImageFormat(outputFormat);
var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);

@ -8,19 +8,6 @@ namespace Jellyfin.Drawing.Skia
/// </summary>
public static class SkiaHelper
{
/// <summary>
/// Ensures the result is a success
/// by throwing an exception when that's not the case.
/// </summary>
/// <param name="result">The result returned by Skia.</param>
public static void EnsureSuccess(SKCodecResult result)
{
if (result != SKCodecResult.Success)
{
throw new SkiaCodecException(result);
}
}
/// <summary>
/// Gets the next valid image as a bitmap.
/// </summary>

@ -50,6 +50,14 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>BlurHash.</returns>
string GetImageBlurHash(string path);
/// <summary>
/// Gets the blurhash of the image.
/// </summary>
/// <param name="path">Path to the image file.</param>
/// <param name="imageDimensions">The image dimensions.</param>
/// <returns>BlurHash.</returns>
string GetImageBlurHash(string path, ImageDimensions imageDimensions);
/// <summary>
/// Gets the image cache tag.
/// </summary>

Loading…
Cancel
Save