mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-06-03 05:34:16 -04:00
Ensure Skia images are always disposed (#12786)
This commit is contained in:
parent
4251cbc277
commit
8b4fa42e49
@ -269,14 +269,24 @@ public class SkiaEncoder : IImageEncoder
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create the bitmap
|
// create the bitmap
|
||||||
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
|
SKBitmap? bitmap = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
|
||||||
|
|
||||||
// decode
|
// decode
|
||||||
_ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
_ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
||||||
|
|
||||||
origin = codec.EncodedOrigin;
|
origin = codec.EncodedOrigin;
|
||||||
|
|
||||||
return bitmap;
|
return bitmap!;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Detected intermediary error decoding image {0}", path);
|
||||||
|
bitmap?.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultBitmap = SKBitmap.Decode(NormalizePath(path));
|
var resultBitmap = SKBitmap.Decode(NormalizePath(path));
|
||||||
@ -286,17 +296,26 @@ public class SkiaEncoder : IImageEncoder
|
|||||||
return Decode(path, true, orientation, out origin);
|
return Decode(path, true, orientation, out origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have to resize these they often end up distorted
|
try
|
||||||
if (resultBitmap.ColorType == SKColorType.Gray8)
|
|
||||||
{
|
{
|
||||||
using (resultBitmap)
|
// If we have to resize these they often end up distorted
|
||||||
|
if (resultBitmap.ColorType == SKColorType.Gray8)
|
||||||
{
|
{
|
||||||
return Decode(path, true, orientation, out origin);
|
using (resultBitmap)
|
||||||
|
{
|
||||||
|
return Decode(path, true, orientation, out origin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
origin = SKEncodedOrigin.TopLeft;
|
origin = SKEncodedOrigin.TopLeft;
|
||||||
return resultBitmap;
|
return resultBitmap;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Detected intermediary error decoding image {0}", path);
|
||||||
|
resultBitmap?.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SKBitmap? GetBitmap(string path, bool autoOrient, ImageOrientation? orientation)
|
private SKBitmap? GetBitmap(string path, bool autoOrient, ImageOrientation? orientation)
|
||||||
@ -335,58 +354,78 @@ public class SkiaEncoder : IImageEncoder
|
|||||||
var width = (int)Math.Round(svg.Drawable.Bounds.Width);
|
var width = (int)Math.Round(svg.Drawable.Bounds.Width);
|
||||||
var height = (int)Math.Round(svg.Drawable.Bounds.Height);
|
var height = (int)Math.Round(svg.Drawable.Bounds.Height);
|
||||||
|
|
||||||
var bitmap = new SKBitmap(width, height);
|
SKBitmap? bitmap = null;
|
||||||
using var canvas = new SKCanvas(bitmap);
|
try
|
||||||
canvas.DrawPicture(svg.Picture);
|
{
|
||||||
canvas.Flush();
|
bitmap = new SKBitmap(width, height);
|
||||||
canvas.Save();
|
using var canvas = new SKCanvas(bitmap);
|
||||||
|
canvas.DrawPicture(svg.Picture);
|
||||||
|
canvas.Flush();
|
||||||
|
canvas.Save();
|
||||||
|
|
||||||
return bitmap;
|
return bitmap!;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Detected intermediary error extracting image {0}", path);
|
||||||
|
bitmap?.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
var rotated = needsFlip
|
SKBitmap? rotated = null;
|
||||||
? new SKBitmap(bitmap.Height, bitmap.Width)
|
try
|
||||||
: new SKBitmap(bitmap.Width, bitmap.Height);
|
|
||||||
using var surface = new SKCanvas(rotated);
|
|
||||||
var midX = (float)rotated.Width / 2;
|
|
||||||
var midY = (float)rotated.Height / 2;
|
|
||||||
|
|
||||||
switch (origin)
|
|
||||||
{
|
{
|
||||||
case SKEncodedOrigin.TopRight:
|
rotated = needsFlip
|
||||||
surface.Scale(-1, 1, midX, midY);
|
? new SKBitmap(bitmap.Height, bitmap.Width)
|
||||||
break;
|
: new SKBitmap(bitmap.Width, bitmap.Height);
|
||||||
case SKEncodedOrigin.BottomRight:
|
using var surface = new SKCanvas(rotated);
|
||||||
surface.RotateDegrees(180, midX, midY);
|
var midX = (float)rotated.Width / 2;
|
||||||
break;
|
var midY = (float)rotated.Height / 2;
|
||||||
case SKEncodedOrigin.BottomLeft:
|
|
||||||
surface.Scale(1, -1, midX, midY);
|
|
||||||
break;
|
|
||||||
case SKEncodedOrigin.LeftTop:
|
|
||||||
surface.Translate(0, -rotated.Height);
|
|
||||||
surface.Scale(1, -1, midX, midY);
|
|
||||||
surface.RotateDegrees(-90);
|
|
||||||
break;
|
|
||||||
case SKEncodedOrigin.RightTop:
|
|
||||||
surface.Translate(rotated.Width, 0);
|
|
||||||
surface.RotateDegrees(90);
|
|
||||||
break;
|
|
||||||
case SKEncodedOrigin.RightBottom:
|
|
||||||
surface.Translate(rotated.Width, 0);
|
|
||||||
surface.Scale(1, -1, midX, midY);
|
|
||||||
surface.RotateDegrees(90);
|
|
||||||
break;
|
|
||||||
case SKEncodedOrigin.LeftBottom:
|
|
||||||
surface.Translate(0, rotated.Height);
|
|
||||||
surface.RotateDegrees(-90);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
surface.DrawBitmap(bitmap, 0, 0);
|
switch (origin)
|
||||||
return rotated;
|
{
|
||||||
|
case SKEncodedOrigin.TopRight:
|
||||||
|
surface.Scale(-1, 1, midX, midY);
|
||||||
|
break;
|
||||||
|
case SKEncodedOrigin.BottomRight:
|
||||||
|
surface.RotateDegrees(180, midX, midY);
|
||||||
|
break;
|
||||||
|
case SKEncodedOrigin.BottomLeft:
|
||||||
|
surface.Scale(1, -1, midX, midY);
|
||||||
|
break;
|
||||||
|
case SKEncodedOrigin.LeftTop:
|
||||||
|
surface.Translate(0, -rotated.Height);
|
||||||
|
surface.Scale(1, -1, midX, midY);
|
||||||
|
surface.RotateDegrees(-90);
|
||||||
|
break;
|
||||||
|
case SKEncodedOrigin.RightTop:
|
||||||
|
surface.Translate(rotated.Width, 0);
|
||||||
|
surface.RotateDegrees(90);
|
||||||
|
break;
|
||||||
|
case SKEncodedOrigin.RightBottom:
|
||||||
|
surface.Translate(rotated.Width, 0);
|
||||||
|
surface.Scale(1, -1, midX, midY);
|
||||||
|
surface.RotateDegrees(90);
|
||||||
|
break;
|
||||||
|
case SKEncodedOrigin.LeftBottom:
|
||||||
|
surface.Translate(0, rotated.Height);
|
||||||
|
surface.RotateDegrees(-90);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
surface.DrawBitmap(bitmap, 0, 0);
|
||||||
|
return rotated;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Detected intermediary error rotating image");
|
||||||
|
rotated?.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -562,7 +601,7 @@ public class SkiaEncoder : IImageEncoder
|
|||||||
// Only generate the splash screen if we have at least one poster and at least one backdrop/thumbnail.
|
// Only generate the splash screen if we have at least one poster and at least one backdrop/thumbnail.
|
||||||
if (posters.Count > 0 && backdrops.Count > 0)
|
if (posters.Count > 0 && backdrops.Count > 0)
|
||||||
{
|
{
|
||||||
var splashBuilder = new SplashscreenBuilder(this);
|
var splashBuilder = new SplashscreenBuilder(this, _logger);
|
||||||
var outputPath = Path.Combine(_appPaths.DataPath, "splashscreen.png");
|
var outputPath = Path.Combine(_appPaths.DataPath, "splashscreen.png");
|
||||||
splashBuilder.GenerateSplash(posters, backdrops, outputPath);
|
splashBuilder.GenerateSplash(posters, backdrops, outputPath);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Jellyfin.Drawing.Skia;
|
namespace Jellyfin.Drawing.Skia;
|
||||||
@ -18,14 +19,17 @@ public class SplashscreenBuilder
|
|||||||
private const int Spacing = 20;
|
private const int Spacing = 20;
|
||||||
|
|
||||||
private readonly SkiaEncoder _skiaEncoder;
|
private readonly SkiaEncoder _skiaEncoder;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SplashscreenBuilder"/> class.
|
/// Initializes a new instance of the <see cref="SplashscreenBuilder"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="skiaEncoder">The SkiaEncoder.</param>
|
/// <param name="skiaEncoder">The SkiaEncoder.</param>
|
||||||
public SplashscreenBuilder(SkiaEncoder skiaEncoder)
|
/// <param name="logger">The logger.</param>
|
||||||
|
public SplashscreenBuilder(SkiaEncoder skiaEncoder, ILogger logger)
|
||||||
{
|
{
|
||||||
_skiaEncoder = skiaEncoder;
|
_skiaEncoder = skiaEncoder;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -55,65 +59,76 @@ public class SplashscreenBuilder
|
|||||||
var posterIndex = 0;
|
var posterIndex = 0;
|
||||||
var backdropIndex = 0;
|
var backdropIndex = 0;
|
||||||
|
|
||||||
var bitmap = new SKBitmap(WallWidth, WallHeight);
|
SKBitmap? bitmap = null;
|
||||||
using var canvas = new SKCanvas(bitmap);
|
try
|
||||||
canvas.Clear(SKColors.Black);
|
|
||||||
|
|
||||||
int posterHeight = WallHeight / 6;
|
|
||||||
|
|
||||||
for (int i = 0; i < Rows; i++)
|
|
||||||
{
|
{
|
||||||
int imageCounter = Random.Shared.Next(0, 5);
|
bitmap = new SKBitmap(WallWidth, WallHeight);
|
||||||
int currentWidthPos = i * 75;
|
using var canvas = new SKCanvas(bitmap);
|
||||||
int currentHeight = i * (posterHeight + Spacing);
|
canvas.Clear(SKColors.Black);
|
||||||
|
|
||||||
while (currentWidthPos < WallWidth)
|
int posterHeight = WallHeight / 6;
|
||||||
|
|
||||||
|
for (int i = 0; i < Rows; i++)
|
||||||
{
|
{
|
||||||
SKBitmap? currentImage;
|
int imageCounter = Random.Shared.Next(0, 5);
|
||||||
|
int currentWidthPos = i * 75;
|
||||||
|
int currentHeight = i * (posterHeight + Spacing);
|
||||||
|
|
||||||
switch (imageCounter)
|
while (currentWidthPos < WallWidth)
|
||||||
{
|
{
|
||||||
case 0:
|
SKBitmap? currentImage;
|
||||||
case 2:
|
|
||||||
case 3:
|
|
||||||
currentImage = SkiaHelper.GetNextValidImage(_skiaEncoder, posters, posterIndex, out int newPosterIndex);
|
|
||||||
posterIndex = newPosterIndex;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
currentImage = SkiaHelper.GetNextValidImage(_skiaEncoder, backdrops, backdropIndex, out int newBackdropIndex);
|
|
||||||
backdropIndex = newBackdropIndex;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentImage is null)
|
switch (imageCounter)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Not enough valid pictures provided to create a splashscreen!");
|
case 0:
|
||||||
}
|
case 2:
|
||||||
|
case 3:
|
||||||
|
currentImage = SkiaHelper.GetNextValidImage(_skiaEncoder, posters, posterIndex, out int newPosterIndex);
|
||||||
|
posterIndex = newPosterIndex;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
currentImage = SkiaHelper.GetNextValidImage(_skiaEncoder, backdrops, backdropIndex, out int newBackdropIndex);
|
||||||
|
backdropIndex = newBackdropIndex;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// resize to the same aspect as the original
|
if (currentImage is null)
|
||||||
var imageWidth = Math.Abs(posterHeight * currentImage.Width / currentImage.Height);
|
{
|
||||||
using var resizedBitmap = new SKBitmap(imageWidth, posterHeight);
|
throw new ArgumentException("Not enough valid pictures provided to create a splashscreen!");
|
||||||
currentImage.ScalePixels(resizedBitmap, SKFilterQuality.High);
|
}
|
||||||
|
|
||||||
// draw on canvas
|
using (currentImage)
|
||||||
canvas.DrawBitmap(resizedBitmap, currentWidthPos, currentHeight);
|
{
|
||||||
|
var imageWidth = Math.Abs(posterHeight * currentImage.Width / currentImage.Height);
|
||||||
|
using var resizedBitmap = new SKBitmap(imageWidth, posterHeight);
|
||||||
|
currentImage.ScalePixels(resizedBitmap, SKFilterQuality.High);
|
||||||
|
|
||||||
currentWidthPos += imageWidth + Spacing;
|
// draw on canvas
|
||||||
|
canvas.DrawBitmap(resizedBitmap, currentWidthPos, currentHeight);
|
||||||
|
|
||||||
currentImage.Dispose();
|
// resize to the same aspect as the original
|
||||||
|
currentWidthPos += imageWidth + Spacing;
|
||||||
|
}
|
||||||
|
|
||||||
if (imageCounter >= 4)
|
if (imageCounter >= 4)
|
||||||
{
|
{
|
||||||
imageCounter = 0;
|
imageCounter = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
imageCounter++;
|
imageCounter++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return bitmap;
|
return bitmap;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Detected intermediary error creating splashscreen image");
|
||||||
|
bitmap?.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -123,25 +138,35 @@ public class SplashscreenBuilder
|
|||||||
/// <returns>The transformed image.</returns>
|
/// <returns>The transformed image.</returns>
|
||||||
private SKBitmap Transform3D(SKBitmap input)
|
private SKBitmap Transform3D(SKBitmap input)
|
||||||
{
|
{
|
||||||
var bitmap = new SKBitmap(FinalWidth, FinalHeight);
|
SKBitmap? bitmap = null;
|
||||||
using var canvas = new SKCanvas(bitmap);
|
try
|
||||||
canvas.Clear(SKColors.Black);
|
|
||||||
var matrix = new SKMatrix
|
|
||||||
{
|
{
|
||||||
ScaleX = 0.324108899f,
|
bitmap = new SKBitmap(FinalWidth, FinalHeight);
|
||||||
ScaleY = 0.563934922f,
|
using var canvas = new SKCanvas(bitmap);
|
||||||
SkewX = -0.244337708f,
|
canvas.Clear(SKColors.Black);
|
||||||
SkewY = 0.0377609022f,
|
var matrix = new SKMatrix
|
||||||
TransX = 42.0407715f,
|
{
|
||||||
TransY = -198.104706f,
|
ScaleX = 0.324108899f,
|
||||||
Persp0 = -9.08959337E-05f,
|
ScaleY = 0.563934922f,
|
||||||
Persp1 = 6.85242048E-05f,
|
SkewX = -0.244337708f,
|
||||||
Persp2 = 0.988209724f
|
SkewY = 0.0377609022f,
|
||||||
};
|
TransX = 42.0407715f,
|
||||||
|
TransY = -198.104706f,
|
||||||
|
Persp0 = -9.08959337E-05f,
|
||||||
|
Persp1 = 6.85242048E-05f,
|
||||||
|
Persp2 = 0.988209724f
|
||||||
|
};
|
||||||
|
|
||||||
canvas.SetMatrix(matrix);
|
canvas.SetMatrix(matrix);
|
||||||
canvas.DrawBitmap(input, 0, 0);
|
canvas.DrawBitmap(input, 0, 0);
|
||||||
|
|
||||||
return bitmap;
|
return bitmap;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Detected intermediary error creating splashscreen image transforming the image");
|
||||||
|
bitmap?.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user