aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Drawing.Skia/SplashscreenBuilder.cs
blob: e5fa6c2bd1880c11c382f49c036d3c2508bad554 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
using System;
using System.Collections.Generic;
using SkiaSharp;

namespace Jellyfin.Drawing.Skia
{
    /// <summary>
    /// Used to build the splashscreen.
    /// </summary>
    public class SplashscreenBuilder
    {
        private const int FinalWidth = 1920;
        private const int FinalHeight = 1080;
        // generated collage resolution should be higher than the final resolution
        private const int WallWidth = FinalWidth * 3;
        private const int WallHeight = FinalHeight * 2;
        private const int Rows = 6;
        private const int Spacing = 20;

        private readonly SkiaEncoder _skiaEncoder;

        /// <summary>
        /// Initializes a new instance of the <see cref="SplashscreenBuilder"/> class.
        /// </summary>
        /// <param name="skiaEncoder">The SkiaEncoder.</param>
        public SplashscreenBuilder(SkiaEncoder skiaEncoder)
        {
            _skiaEncoder = skiaEncoder;
        }

        /// <summary>
        /// Generate a splashscreen.
        /// </summary>
        /// <param name="posters">The poster paths.</param>
        /// <param name="backdrops">The landscape paths.</param>
        /// <param name="outputPath">The output path.</param>
        public void GenerateSplash(IReadOnlyList<string> posters, IReadOnlyList<string> backdrops, string outputPath)
        {
            using var wall = GenerateCollage(posters, backdrops);
            using var transformed = Transform3D(wall);

            using var outputStream = new SKFileWStream(outputPath);
            using var pixmap = new SKPixmap(new SKImageInfo(FinalWidth, FinalHeight), transformed.GetPixels());
            pixmap.Encode(outputStream, StripCollageBuilder.GetEncodedFormat(outputPath), 90);
        }

        /// <summary>
        /// Generates a collage of posters and landscape pictures.
        /// </summary>
        /// <param name="posters">The poster paths.</param>
        /// <param name="backdrops">The landscape paths.</param>
        /// <returns>The created collage as a bitmap.</returns>
        private SKBitmap GenerateCollage(IReadOnlyList<string> posters, IReadOnlyList<string> backdrops)
        {
            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++)
            {
                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 == 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);

                    currentWidthPos += imageWidth + Spacing;

                    currentImage.Dispose();

                    if (imageCounter >= 4)
                    {
                        imageCounter = 0;
                    }
                    else
                    {
                        imageCounter++;
                    }
                }
            }

            return bitmap;
        }

        /// <summary>
        /// Transform the collage in 3D space.
        /// </summary>
        /// <param name="input">The bitmap to transform.</param>
        /// <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
            {
                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;
        }
    }
}