mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-12-27 18:06:43 +00:00
Ensure Skia images are always disposed (#12786)
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
This commit is contained in:
parent
4251cbc277
commit
8b4fa42e49
|
@ -269,14 +269,24 @@ public class SkiaEncoder : IImageEncoder
|
|||
}
|
||||
|
||||
// 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
|
||||
_ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
||||
// decode
|
||||
_ = 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));
|
||||
|
@ -286,17 +296,26 @@ public class SkiaEncoder : IImageEncoder
|
|||
return Decode(path, true, orientation, out origin);
|
||||
}
|
||||
|
||||
// If we have to resize these they often end up distorted
|
||||
if (resultBitmap.ColorType == SKColorType.Gray8)
|
||||
try
|
||||
{
|
||||
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;
|
||||
return resultBitmap;
|
||||
origin = SKEncodedOrigin.TopLeft;
|
||||
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)
|
||||
|
@ -335,58 +354,78 @@ public class SkiaEncoder : IImageEncoder
|
|||
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();
|
||||
SKBitmap? bitmap = null;
|
||||
try
|
||||
{
|
||||
bitmap = new SKBitmap(width, height);
|
||||
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)
|
||||
{
|
||||
var needsFlip = origin is SKEncodedOrigin.LeftBottom or SKEncodedOrigin.LeftTop or SKEncodedOrigin.RightBottom or SKEncodedOrigin.RightTop;
|
||||
var rotated = needsFlip
|
||||
? new SKBitmap(bitmap.Height, bitmap.Width)
|
||||
: 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)
|
||||
SKBitmap? rotated = null;
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
rotated = needsFlip
|
||||
? new SKBitmap(bitmap.Height, bitmap.Width)
|
||||
: new SKBitmap(bitmap.Width, bitmap.Height);
|
||||
using var surface = new SKCanvas(rotated);
|
||||
var midX = (float)rotated.Width / 2;
|
||||
var midY = (float)rotated.Height / 2;
|
||||
|
||||
surface.DrawBitmap(bitmap, 0, 0);
|
||||
return rotated;
|
||||
switch (origin)
|
||||
{
|
||||
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>
|
||||
|
@ -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.
|
||||
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");
|
||||
splashBuilder.GenerateSplash(posters, backdrops, outputPath);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Jellyfin.Drawing.Skia;
|
||||
|
@ -18,14 +19,17 @@ public class SplashscreenBuilder
|
|||
private const int Spacing = 20;
|
||||
|
||||
private readonly SkiaEncoder _skiaEncoder;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SplashscreenBuilder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="skiaEncoder">The SkiaEncoder.</param>
|
||||
public SplashscreenBuilder(SkiaEncoder skiaEncoder)
|
||||
/// <param name="logger">The logger.</param>
|
||||
public SplashscreenBuilder(SkiaEncoder skiaEncoder, ILogger logger)
|
||||
{
|
||||
_skiaEncoder = skiaEncoder;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -55,65 +59,76 @@ public class SplashscreenBuilder
|
|||
var posterIndex = 0;
|
||||
var backdropIndex = 0;
|
||||
|
||||
var bitmap = new SKBitmap(WallWidth, WallHeight);
|
||||
using var canvas = new SKCanvas(bitmap);
|
||||
canvas.Clear(SKColors.Black);
|
||||
|
||||
int posterHeight = WallHeight / 6;
|
||||
|
||||
for (int i = 0; i < Rows; i++)
|
||||
SKBitmap? bitmap = null;
|
||||
try
|
||||
{
|
||||
int imageCounter = Random.Shared.Next(0, 5);
|
||||
int currentWidthPos = i * 75;
|
||||
int currentHeight = i * (posterHeight + Spacing);
|
||||
bitmap = new SKBitmap(WallWidth, WallHeight);
|
||||
using var canvas = new SKCanvas(bitmap);
|
||||
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:
|
||||
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;
|
||||
}
|
||||
SKBitmap? currentImage;
|
||||
|
||||
if (currentImage is null)
|
||||
{
|
||||
throw new ArgumentException("Not enough valid pictures provided to create a splashscreen!");
|
||||
}
|
||||
switch (imageCounter)
|
||||
{
|
||||
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
|
||||
var imageWidth = Math.Abs(posterHeight * currentImage.Width / currentImage.Height);
|
||||
using var resizedBitmap = new SKBitmap(imageWidth, posterHeight);
|
||||
currentImage.ScalePixels(resizedBitmap, SKFilterQuality.High);
|
||||
if (currentImage is null)
|
||||
{
|
||||
throw new ArgumentException("Not enough valid pictures provided to create a splashscreen!");
|
||||
}
|
||||
|
||||
// draw on canvas
|
||||
canvas.DrawBitmap(resizedBitmap, currentWidthPos, currentHeight);
|
||||
using (currentImage)
|
||||
{
|
||||
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)
|
||||
{
|
||||
imageCounter = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
imageCounter++;
|
||||
if (imageCounter >= 4)
|
||||
{
|
||||
imageCounter = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
imageCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
return bitmap;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Detected intermediary error creating splashscreen image");
|
||||
bitmap?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -123,25 +138,35 @@ public class SplashscreenBuilder
|
|||
/// <returns>The transformed image.</returns>
|
||||
private SKBitmap Transform3D(SKBitmap input)
|
||||
{
|
||||
var bitmap = new SKBitmap(FinalWidth, FinalHeight);
|
||||
using var canvas = new SKCanvas(bitmap);
|
||||
canvas.Clear(SKColors.Black);
|
||||
var matrix = new SKMatrix
|
||||
SKBitmap? bitmap = null;
|
||||
try
|
||||
{
|
||||
ScaleX = 0.324108899f,
|
||||
ScaleY = 0.563934922f,
|
||||
SkewX = -0.244337708f,
|
||||
SkewY = 0.0377609022f,
|
||||
TransX = 42.0407715f,
|
||||
TransY = -198.104706f,
|
||||
Persp0 = -9.08959337E-05f,
|
||||
Persp1 = 6.85242048E-05f,
|
||||
Persp2 = 0.988209724f
|
||||
};
|
||||
bitmap = new SKBitmap(FinalWidth, FinalHeight);
|
||||
using var canvas = new SKCanvas(bitmap);
|
||||
canvas.Clear(SKColors.Black);
|
||||
var matrix = new SKMatrix
|
||||
{
|
||||
ScaleX = 0.324108899f,
|
||||
ScaleY = 0.563934922f,
|
||||
SkewX = -0.244337708f,
|
||||
SkewY = 0.0377609022f,
|
||||
TransX = 42.0407715f,
|
||||
TransY = -198.104706f,
|
||||
Persp0 = -9.08959337E-05f,
|
||||
Persp1 = 6.85242048E-05f,
|
||||
Persp2 = 0.988209724f
|
||||
};
|
||||
|
||||
canvas.SetMatrix(matrix);
|
||||
canvas.DrawBitmap(input, 0, 0);
|
||||
canvas.SetMatrix(matrix);
|
||||
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…
Reference in a new issue