浏览代码

Ensure Skia images are always disposed (#12786)

JPVenson 7 月之前
父节点
当前提交
8b4fa42e49
共有 2 个文件被更改,包括 194 次插入130 次删除
  1. 98 59
      src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
  2. 96 71
      src/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs

+ 98 - 59
src/Jellyfin.Drawing.Skia/SkiaEncoder.cs

@@ -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)
-        {
-            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;
+        SKBitmap? rotated = null;
+        try
+        {
+            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)
+            {
+                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);
         }

+ 96 - 71
src/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs

@@ -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);
-
-            while (currentWidthPos < WallWidth)
-            {
-                SKBitmap? currentImage;
-
-                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;
-                }
-
-                if (currentImage is null)
-                {
-                    throw new ArgumentException("Not enough valid pictures provided to create a splashscreen!");
-                }
-
-                // 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);
-
-                // draw on canvas
-                canvas.DrawBitmap(resizedBitmap, currentWidthPos, currentHeight);
+            bitmap = new SKBitmap(WallWidth, WallHeight);
+            using var canvas = new SKCanvas(bitmap);
+            canvas.Clear(SKColors.Black);
 
-                currentWidthPos += imageWidth + Spacing;
+            int posterHeight = WallHeight / 6;
 
-                currentImage.Dispose();
+            for (int i = 0; i < Rows; i++)
+            {
+                int imageCounter = Random.Shared.Next(0, 5);
+                int currentWidthPos = i * 75;
+                int currentHeight = i * (posterHeight + Spacing);
 
-                if (imageCounter >= 4)
-                {
-                    imageCounter = 0;
-                }
-                else
+                while (currentWidthPos < WallWidth)
                 {
-                    imageCounter++;
+                    SKBitmap? currentImage;
+
+                    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;
+                    }
+
+                    if (currentImage is null)
+                    {
+                        throw new ArgumentException("Not enough valid pictures provided to create a splashscreen!");
+                    }
+
+                    using (currentImage)
+                    {
+                        var imageWidth = Math.Abs(posterHeight * currentImage.Width / currentImage.Height);
+                        using var resizedBitmap = new SKBitmap(imageWidth, posterHeight);
+                        currentImage.ScalePixels(resizedBitmap, SKFilterQuality.High);
+
+                        // draw on canvas
+                        canvas.DrawBitmap(resizedBitmap, currentWidthPos, currentHeight);
+
+                        // resize to the same aspect as the original
+                        currentWidthPos += imageWidth + Spacing;
+                    }
+
+                    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
-        };
-
-        canvas.SetMatrix(matrix);
-        canvas.DrawBitmap(input, 0, 0);
-
-        return bitmap;
+            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);
+
+            return bitmap;
+        }
+        catch (Exception e)
+        {
+            _logger.LogError(e, "Detected intermediary error creating splashscreen image transforming the image");
+            bitmap?.Dispose();
+            throw;
+        }
     }
 }