Merge pull request #3597 from barronpm/jellyfin-drawing-skia-cleanup

Jellyfin.Drawing.Skia Cleanup
pull/3720/head
Anthony Lavado 4 years ago committed by GitHub
commit 2f315bb0dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,22 +19,18 @@ namespace Jellyfin.Drawing.Skia
/// <param name="percent">The percentage played to display with the indicator.</param> /// <param name="percent">The percentage played to display with the indicator.</param>
public static void Process(SKCanvas canvas, ImageDimensions imageSize, double percent) public static void Process(SKCanvas canvas, ImageDimensions imageSize, double percent)
{ {
using (var paint = new SKPaint()) using var paint = new SKPaint();
{ var endX = imageSize.Width - 1;
var endX = imageSize.Width - 1; var endY = imageSize.Height - 1;
var endY = imageSize.Height - 1;
paint.Color = SKColor.Parse("#99000000"); paint.Color = SKColor.Parse("#99000000");
paint.Style = SKPaintStyle.Fill; paint.Style = SKPaintStyle.Fill;
canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, (float)endX, (float)endY), paint); canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, endX, endY), paint);
double foregroundWidth = endX; double foregroundWidth = (endX * percent) / 100;
foregroundWidth *= percent;
foregroundWidth /= 100;
paint.Color = SKColor.Parse("#FF00A4DC"); paint.Color = SKColor.Parse("#FF00A4DC");
canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(foregroundWidth), (float)endY), paint); canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(foregroundWidth), endY), paint);
}
} }
} }
} }

@ -22,31 +22,27 @@ namespace Jellyfin.Drawing.Skia
{ {
var x = imageSize.Width - OffsetFromTopRightCorner; var x = imageSize.Width - OffsetFromTopRightCorner;
using (var paint = new SKPaint()) using var paint = new SKPaint
{ {
paint.Color = SKColor.Parse("#CC00A4DC"); Color = SKColor.Parse("#CC00A4DC"),
paint.Style = SKPaintStyle.Fill; Style = SKPaintStyle.Fill
canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint); };
}
using (var paint = new SKPaint()) canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint);
{
paint.Color = new SKColor(255, 255, 255, 255);
paint.Style = SKPaintStyle.Fill;
paint.TextSize = 30; paint.Color = new SKColor(255, 255, 255, 255);
paint.IsAntialias = true; paint.TextSize = 30;
paint.IsAntialias = true;
// or: // or:
// var emojiChar = 0x1F680; // var emojiChar = 0x1F680;
const string Text = "✔️"; const string Text = "✔️";
var emojiChar = StringUtilities.GetUnicodeCharacterCode(Text, SKTextEncoding.Utf32); var emojiChar = StringUtilities.GetUnicodeCharacterCode(Text, SKTextEncoding.Utf32);
// ask the font manager for a font with that character // ask the font manager for a font with that character
paint.Typeface = SKFontManager.Default.MatchCharacter(emojiChar); paint.Typeface = SKFontManager.Default.MatchCharacter(emojiChar);
canvas.DrawText(Text, (float)x - 20, OffsetFromTopRightCorner + 12, paint); canvas.DrawText(Text, (float)x - 20, OffsetFromTopRightCorner + 12, paint);
}
} }
} }
} }

@ -12,7 +12,7 @@ namespace Jellyfin.Drawing.Skia
/// Initializes a new instance of the <see cref="SkiaCodecException" /> class. /// Initializes a new instance of the <see cref="SkiaCodecException" /> class.
/// </summary> /// </summary>
/// <param name="result">The non-successful codec result returned by Skia.</param> /// <param name="result">The non-successful codec result returned by Skia.</param>
public SkiaCodecException(SKCodecResult result) : base() public SkiaCodecException(SKCodecResult result)
{ {
CodecResult = result; CodecResult = result;
} }

@ -29,9 +29,7 @@ namespace Jellyfin.Drawing.Skia
/// </summary> /// </summary>
/// <param name="logger">The application logger.</param> /// <param name="logger">The application logger.</param>
/// <param name="appPaths">The application paths.</param> /// <param name="appPaths">The application paths.</param>
public SkiaEncoder( public SkiaEncoder(ILogger<SkiaEncoder> logger, IApplicationPaths appPaths)
ILogger<SkiaEncoder> logger,
IApplicationPaths appPaths)
{ {
_logger = logger; _logger = logger;
_appPaths = appPaths; _appPaths = appPaths;
@ -102,19 +100,14 @@ namespace Jellyfin.Drawing.Skia
/// <returns>The converted format.</returns> /// <returns>The converted format.</returns>
public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat) public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat)
{ {
switch (selectedFormat) return selectedFormat switch
{ {
case ImageFormat.Bmp: ImageFormat.Bmp => SKEncodedImageFormat.Bmp,
return SKEncodedImageFormat.Bmp; ImageFormat.Jpg => SKEncodedImageFormat.Jpeg,
case ImageFormat.Jpg: ImageFormat.Gif => SKEncodedImageFormat.Gif,
return SKEncodedImageFormat.Jpeg; ImageFormat.Webp => SKEncodedImageFormat.Webp,
case ImageFormat.Gif: _ => SKEncodedImageFormat.Png
return SKEncodedImageFormat.Gif; };
case ImageFormat.Webp:
return SKEncodedImageFormat.Webp;
default:
return SKEncodedImageFormat.Png;
}
} }
private static bool IsTransparentRow(SKBitmap bmp, int row) private static bool IsTransparentRow(SKBitmap bmp, int row)
@ -146,63 +139,34 @@ namespace Jellyfin.Drawing.Skia
private SKBitmap CropWhiteSpace(SKBitmap bitmap) private SKBitmap CropWhiteSpace(SKBitmap bitmap)
{ {
var topmost = 0; var topmost = 0;
for (int row = 0; row < bitmap.Height; ++row) while (topmost < bitmap.Height && IsTransparentRow(bitmap, topmost))
{ {
if (IsTransparentRow(bitmap, row)) topmost++;
{
topmost = row + 1;
}
else
{
break;
}
} }
int bottommost = bitmap.Height; int bottommost = bitmap.Height;
for (int row = bitmap.Height - 1; row >= 0; --row) while (bottommost >= 0 && IsTransparentRow(bitmap, bottommost - 1))
{ {
if (IsTransparentRow(bitmap, row)) bottommost--;
{
bottommost = row;
}
else
{
break;
}
} }
int leftmost = 0, rightmost = bitmap.Width; var leftmost = 0;
for (int col = 0; col < bitmap.Width; ++col) while (leftmost < bitmap.Width && IsTransparentColumn(bitmap, leftmost))
{ {
if (IsTransparentColumn(bitmap, col)) leftmost++;
{
leftmost = col + 1;
}
else
{
break;
}
} }
for (int col = bitmap.Width - 1; col >= 0; --col) var rightmost = bitmap.Width;
while (rightmost >= 0 && IsTransparentColumn(bitmap, rightmost - 1))
{ {
if (IsTransparentColumn(bitmap, col)) rightmost--;
{
rightmost = col;
}
else
{
break;
}
} }
var newRect = SKRectI.Create(leftmost, topmost, rightmost - leftmost, bottommost - topmost); var newRect = SKRectI.Create(leftmost, topmost, rightmost - leftmost, bottommost - topmost);
using (var image = SKImage.FromBitmap(bitmap)) using var image = SKImage.FromBitmap(bitmap);
using (var subset = image.Subset(newRect)) using var subset = image.Subset(newRect);
{ return SKBitmap.FromImage(subset);
return SKBitmap.FromImage(subset);
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -216,14 +180,12 @@ namespace Jellyfin.Drawing.Skia
throw new FileNotFoundException("File not found", path); throw new FileNotFoundException("File not found", path);
} }
using (var codec = SKCodec.Create(path, out SKCodecResult result)) using var codec = SKCodec.Create(path, out SKCodecResult result);
{ EnsureSuccess(result);
EnsureSuccess(result);
var info = codec.Info; var info = codec.Info;
return new ImageDimensions(info.Width, info.Height); return new ImageDimensions(info.Width, info.Height);
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -253,12 +215,7 @@ namespace Jellyfin.Drawing.Skia
} }
} }
if (HasDiacritics(path)) return HasDiacritics(path);
{
return true;
}
return false;
} }
private string NormalizePath(string path) private string NormalizePath(string path)
@ -283,25 +240,17 @@ namespace Jellyfin.Drawing.Skia
return SKEncodedOrigin.TopLeft; return SKEncodedOrigin.TopLeft;
} }
switch (orientation.Value) return orientation.Value switch
{ {
case ImageOrientation.TopRight: ImageOrientation.TopRight => SKEncodedOrigin.TopRight,
return SKEncodedOrigin.TopRight; ImageOrientation.RightTop => SKEncodedOrigin.RightTop,
case ImageOrientation.RightTop: ImageOrientation.RightBottom => SKEncodedOrigin.RightBottom,
return SKEncodedOrigin.RightTop; ImageOrientation.LeftTop => SKEncodedOrigin.LeftTop,
case ImageOrientation.RightBottom: ImageOrientation.LeftBottom => SKEncodedOrigin.LeftBottom,
return SKEncodedOrigin.RightBottom; ImageOrientation.BottomRight => SKEncodedOrigin.BottomRight,
case ImageOrientation.LeftTop: ImageOrientation.BottomLeft => SKEncodedOrigin.BottomLeft,
return SKEncodedOrigin.LeftTop; _ => SKEncodedOrigin.TopLeft
case ImageOrientation.LeftBottom: };
return SKEncodedOrigin.LeftBottom;
case ImageOrientation.BottomRight:
return SKEncodedOrigin.BottomRight;
case ImageOrientation.BottomLeft:
return SKEncodedOrigin.BottomLeft;
default:
return SKEncodedOrigin.TopLeft;
}
} }
/// <summary> /// <summary>
@ -323,24 +272,22 @@ namespace Jellyfin.Drawing.Skia
if (requiresTransparencyHack || forceCleanBitmap) if (requiresTransparencyHack || forceCleanBitmap)
{ {
using (var codec = SKCodec.Create(NormalizePath(path))) using var codec = SKCodec.Create(NormalizePath(path));
if (codec == null)
{ {
if (codec == null) origin = GetSKEncodedOrigin(orientation);
{ return null;
origin = GetSKEncodedOrigin(orientation); }
return null;
}
// create the bitmap // create the bitmap
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack); var 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;
}
} }
var resultBitmap = SKBitmap.Decode(NormalizePath(path)); var resultBitmap = SKBitmap.Decode(NormalizePath(path));
@ -367,15 +314,8 @@ namespace Jellyfin.Drawing.Skia
{ {
if (cropWhitespace) if (cropWhitespace)
{ {
using (var bitmap = Decode(path, forceAnalyzeBitmap, orientation, out origin)) using var bitmap = Decode(path, forceAnalyzeBitmap, orientation, out origin);
{ return bitmap == null ? null : CropWhiteSpace(bitmap);
if (bitmap == null)
{
return null;
}
return CropWhiteSpace(bitmap);
}
} }
return Decode(path, forceAnalyzeBitmap, orientation, out origin); return Decode(path, forceAnalyzeBitmap, orientation, out origin);
@ -403,133 +343,55 @@ namespace Jellyfin.Drawing.Skia
private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin) private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
{ {
if (origin == SKEncodedOrigin.Default)
{
return bitmap;
}
var needsFlip = origin == SKEncodedOrigin.LeftBottom
|| origin == SKEncodedOrigin.LeftTop
|| origin == SKEncodedOrigin.RightBottom
|| origin == 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) switch (origin)
{ {
case SKEncodedOrigin.TopRight: case SKEncodedOrigin.TopRight:
{ surface.Scale(-1, 1, midX, midY);
var rotated = new SKBitmap(bitmap.Width, bitmap.Height); break;
using (var surface = new SKCanvas(rotated))
{
surface.Translate(rotated.Width, 0);
surface.Scale(-1, 1);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
}
case SKEncodedOrigin.BottomRight: case SKEncodedOrigin.BottomRight:
{ surface.RotateDegrees(180, midX, midY);
var rotated = new SKBitmap(bitmap.Width, bitmap.Height); break;
using (var surface = new SKCanvas(rotated))
{
float px = (float)bitmap.Width / 2;
float py = (float)bitmap.Height / 2;
surface.RotateDegrees(180, px, py);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
}
case SKEncodedOrigin.BottomLeft: case SKEncodedOrigin.BottomLeft:
{ surface.Scale(1, -1, midX, midY);
var rotated = new SKBitmap(bitmap.Width, bitmap.Height); break;
using (var surface = new SKCanvas(rotated))
{
float px = (float)bitmap.Width / 2;
float py = (float)bitmap.Height / 2;
surface.Translate(rotated.Width, 0);
surface.Scale(-1, 1);
surface.RotateDegrees(180, px, py);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
}
case SKEncodedOrigin.LeftTop: case SKEncodedOrigin.LeftTop:
{ surface.Translate(0, -rotated.Height);
// TODO: Remove dual canvases, had trouble with flipping surface.Scale(1, -1, midX, midY);
using (var rotated = new SKBitmap(bitmap.Height, bitmap.Width)) surface.RotateDegrees(-90);
{ break;
using (var surface = new SKCanvas(rotated))
{
surface.Translate(rotated.Width, 0);
surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0);
}
var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height);
using (var flippedCanvas = new SKCanvas(flippedBitmap))
{
flippedCanvas.Translate(flippedBitmap.Width, 0);
flippedCanvas.Scale(-1, 1);
flippedCanvas.DrawBitmap(rotated, 0, 0);
}
return flippedBitmap;
}
}
case SKEncodedOrigin.RightTop: case SKEncodedOrigin.RightTop:
{ surface.Translate(rotated.Width, 0);
var rotated = new SKBitmap(bitmap.Height, bitmap.Width); surface.RotateDegrees(90);
using (var surface = new SKCanvas(rotated)) break;
{
surface.Translate(rotated.Width, 0);
surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
}
case SKEncodedOrigin.RightBottom: case SKEncodedOrigin.RightBottom:
{ surface.Translate(rotated.Width, 0);
// TODO: Remove dual canvases, had trouble with flipping surface.Scale(1, -1, midX, midY);
using (var rotated = new SKBitmap(bitmap.Height, bitmap.Width)) surface.RotateDegrees(90);
{ break;
using (var surface = new SKCanvas(rotated))
{
surface.Translate(0, rotated.Height);
surface.RotateDegrees(270);
surface.DrawBitmap(bitmap, 0, 0);
}
var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height);
using (var flippedCanvas = new SKCanvas(flippedBitmap))
{
flippedCanvas.Translate(flippedBitmap.Width, 0);
flippedCanvas.Scale(-1, 1);
flippedCanvas.DrawBitmap(rotated, 0, 0);
}
return flippedBitmap;
}
}
case SKEncodedOrigin.LeftBottom: case SKEncodedOrigin.LeftBottom:
{ surface.Translate(0, rotated.Height);
var rotated = new SKBitmap(bitmap.Height, bitmap.Width); surface.RotateDegrees(-90);
using (var surface = new SKCanvas(rotated)) break;
{
surface.Translate(0, rotated.Height);
surface.RotateDegrees(270);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
}
default: return bitmap;
} }
surface.DrawBitmap(bitmap, 0, 0);
return rotated;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -552,97 +414,87 @@ namespace Jellyfin.Drawing.Skia
var blur = options.Blur ?? 0; var blur = options.Blur ?? 0;
var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0); var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0);
using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation)) using var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation);
if (bitmap == null)
{ {
if (bitmap == null) throw new InvalidDataException($"Skia unable to read image {inputPath}");
{ }
throw new InvalidDataException($"Skia unable to read image {inputPath}");
}
var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height); var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height);
if (!options.CropWhiteSpace if (!options.CropWhiteSpace
&& options.HasDefaultOptions(inputPath, originalImageSize) && options.HasDefaultOptions(inputPath, originalImageSize)
&& !autoOrient) && !autoOrient)
{ {
// Just spit out the original file if all the options are default // Just spit out the original file if all the options are default
return inputPath; return inputPath;
} }
var newImageSize = ImageHelper.GetNewImageSize(options, originalImageSize); var newImageSize = ImageHelper.GetNewImageSize(options, originalImageSize);
var width = newImageSize.Width; var width = newImageSize.Width;
var height = newImageSize.Height; var height = newImageSize.Height;
using var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType);
// scale image
bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High);
// If all we're doing is resizing then we can stop now
if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator)
{
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
using var outputStream = new SKFileWStream(outputPath);
using var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels());
pixmap.Encode(outputStream, skiaOutputFormat, quality);
return outputPath;
}
using (var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType)) // create bitmap to use for canvas drawing used to draw into bitmap
using var saveBitmap = new SKBitmap(width, height);
using var canvas = new SKCanvas(saveBitmap);
// set background color if present
if (hasBackgroundColor)
{
canvas.Clear(SKColor.Parse(options.BackgroundColor));
}
// Add blur if option is present
if (blur > 0)
{
// create image from resized bitmap to apply blur
using var paint = new SKPaint();
using var filter = SKImageFilter.CreateBlur(blur, blur);
paint.ImageFilter = filter;
canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height), paint);
}
else
{
// draw resized bitmap onto canvas
canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height));
}
// If foreground layer present then draw
if (hasForegroundColor)
{
if (!double.TryParse(options.ForegroundLayer, out double opacity))
{ {
// scale image opacity = .4;
bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); }
// If all we're doing is resizing then we can stop now canvas.DrawColor(new SKColor(0, 0, 0, (byte)((1 - opacity) * 0xFF)), SKBlendMode.SrcOver);
if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) }
{
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
using (var outputStream = new SKFileWStream(outputPath))
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels()))
{
pixmap.Encode(outputStream, skiaOutputFormat, quality);
return outputPath;
}
}
// create bitmap to use for canvas drawing used to draw into bitmap if (hasIndicator)
using (var saveBitmap = new SKBitmap(width, height)) // , bitmap.ColorType, bitmap.AlphaType)) {
using (var canvas = new SKCanvas(saveBitmap)) DrawIndicator(canvas, width, height, options);
{ }
// set background color if present
if (hasBackgroundColor) Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
{ using (var outputStream = new SKFileWStream(outputPath))
canvas.Clear(SKColor.Parse(options.BackgroundColor)); {
} using (var pixmap = new SKPixmap(new SKImageInfo(width, height), saveBitmap.GetPixels()))
{
// Add blur if option is present pixmap.Encode(outputStream, skiaOutputFormat, quality);
if (blur > 0)
{
// create image from resized bitmap to apply blur
using (var paint = new SKPaint())
using (var filter = SKImageFilter.CreateBlur(blur, blur))
{
paint.ImageFilter = filter;
canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height), paint);
}
}
else
{
// draw resized bitmap onto canvas
canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height));
}
// If foreground layer present then draw
if (hasForegroundColor)
{
if (!double.TryParse(options.ForegroundLayer, out double opacity))
{
opacity = .4;
}
canvas.DrawColor(new SKColor(0, 0, 0, (byte)((1 - opacity) * 0xFF)), SKBlendMode.SrcOver);
}
if (hasIndicator)
{
DrawIndicator(canvas, width, height, options);
}
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
using (var outputStream = new SKFileWStream(outputPath))
{
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), saveBitmap.GetPixels()))
{
pixmap.Encode(outputStream, skiaOutputFormat, quality);
}
}
}
} }
} }

@ -10,7 +10,7 @@ namespace Jellyfin.Drawing.Skia
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SkiaException"/> class. /// Initializes a new instance of the <see cref="SkiaException"/> class.
/// </summary> /// </summary>
public SkiaException() : base() public SkiaException()
{ {
} }

@ -69,12 +69,10 @@ namespace Jellyfin.Drawing.Skia
/// <param name="height">The desired height of the collage.</param> /// <param name="height">The desired height of the collage.</param>
public void BuildSquareCollage(string[] paths, string outputPath, int width, int height) public void BuildSquareCollage(string[] paths, string outputPath, int width, int height)
{ {
using (var bitmap = BuildSquareCollageBitmap(paths, width, height)) using var bitmap = BuildSquareCollageBitmap(paths, width, height);
using (var outputStream = new SKFileWStream(outputPath)) using var outputStream = new SKFileWStream(outputPath);
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels())) using var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels());
{ pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90);
pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90);
}
} }
/// <summary> /// <summary>
@ -86,56 +84,46 @@ namespace Jellyfin.Drawing.Skia
/// <param name="height">The desired height of the collage.</param> /// <param name="height">The desired height of the collage.</param>
public void BuildThumbCollage(string[] paths, string outputPath, int width, int height) public void BuildThumbCollage(string[] paths, string outputPath, int width, int height)
{ {
using (var bitmap = BuildThumbCollageBitmap(paths, width, height)) using var bitmap = BuildThumbCollageBitmap(paths, width, height);
using (var outputStream = new SKFileWStream(outputPath)) using var outputStream = new SKFileWStream(outputPath);
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels())) using var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels());
{ pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90);
pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90);
}
} }
private SKBitmap BuildThumbCollageBitmap(string[] paths, int width, int height) private SKBitmap BuildThumbCollageBitmap(string[] paths, int width, int height)
{ {
var bitmap = new SKBitmap(width, height); var bitmap = new SKBitmap(width, height);
using (var canvas = new SKCanvas(bitmap)) using var canvas = new SKCanvas(bitmap);
{ canvas.Clear(SKColors.Black);
canvas.Clear(SKColors.Black);
// number of images used in the thumbnail // number of images used in the thumbnail
var iCount = 3; var iCount = 3;
// determine sizes for each image that will composited into the final image // determine sizes for each image that will composited into the final image
var iSlice = Convert.ToInt32(width / iCount); var iSlice = Convert.ToInt32(width / iCount);
int iHeight = Convert.ToInt32(height * 1.00); int iHeight = Convert.ToInt32(height * 1.00);
int imageIndex = 0; int imageIndex = 0;
for (int i = 0; i < iCount; i++) for (int i = 0; i < iCount; i++)
{
using var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex);
imageIndex = newIndex;
if (currentBitmap == null)
{ {
using (var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex)) continue;
{
imageIndex = newIndex;
if (currentBitmap == null)
{
continue;
}
// resize to the same aspect as the original
int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height);
using (var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType))
{
currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High);
// crop image
int ix = Math.Abs((iWidth - iSlice) / 2);
using (var image = SKImage.FromBitmap(resizeBitmap))
using (var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight)))
{
// draw image onto canvas
canvas.DrawImage(subset ?? image, iSlice * i, 0);
}
}
}
} }
// resize to the same aspect as the original
int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height);
using var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType);
currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High);
// crop image
int ix = Math.Abs((iWidth - iSlice) / 2);
using var image = SKImage.FromBitmap(resizeBitmap);
using var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight));
// draw image onto canvas
canvas.DrawImage(subset ?? image, iSlice * i, 0);
} }
return bitmap; return bitmap;
@ -176,33 +164,27 @@ namespace Jellyfin.Drawing.Skia
var cellWidth = width / 2; var cellWidth = width / 2;
var cellHeight = height / 2; var cellHeight = height / 2;
using (var canvas = new SKCanvas(bitmap)) using var canvas = new SKCanvas(bitmap);
for (var x = 0; x < 2; x++)
{ {
for (var x = 0; x < 2; x++) for (var y = 0; y < 2; y++)
{ {
for (var y = 0; y < 2; y++) using var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex);
imageIndex = newIndex;
if (currentBitmap == null)
{ {
using (var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex)) continue;
{
imageIndex = newIndex;
if (currentBitmap == null)
{
continue;
}
using (var resizedBitmap = new SKBitmap(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType))
{
// scale image
currentBitmap.ScalePixels(resizedBitmap, SKFilterQuality.High);
// draw this image into the strip at the next position
var xPos = x * cellWidth;
var yPos = y * cellHeight;
canvas.DrawBitmap(resizedBitmap, xPos, yPos);
}
}
} }
using var resizedBitmap = new SKBitmap(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType);
// scale image
currentBitmap.ScalePixels(resizedBitmap, SKFilterQuality.High);
// draw this image into the strip at the next position
var xPos = x * cellWidth;
var yPos = y * cellHeight;
canvas.DrawBitmap(resizedBitmap, xPos, yPos);
} }
} }

@ -28,41 +28,37 @@ namespace Jellyfin.Drawing.Skia
var x = imageSize.Width - OffsetFromTopRightCorner; var x = imageSize.Width - OffsetFromTopRightCorner;
var text = count.ToString(CultureInfo.InvariantCulture); var text = count.ToString(CultureInfo.InvariantCulture);
using (var paint = new SKPaint()) using var paint = new SKPaint
{ {
paint.Color = SKColor.Parse("#CC00A4DC"); Color = SKColor.Parse("#CC00A4DC"),
paint.Style = SKPaintStyle.Fill; Style = SKPaintStyle.Fill
canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint); };
}
using (var paint = new SKPaint())
{
paint.Color = new SKColor(255, 255, 255, 255);
paint.Style = SKPaintStyle.Fill;
paint.TextSize = 24; canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint);
paint.IsAntialias = true;
var y = OffsetFromTopRightCorner + 9; paint.Color = new SKColor(255, 255, 255, 255);
paint.TextSize = 24;
paint.IsAntialias = true;
if (text.Length == 1) var y = OffsetFromTopRightCorner + 9;
{
x -= 7;
}
if (text.Length == 2) if (text.Length == 1)
{ {
x -= 13; x -= 7;
} }
else if (text.Length >= 3)
{
x -= 15;
y -= 2;
paint.TextSize = 18;
}
canvas.DrawText(text, x, y, paint); if (text.Length == 2)
{
x -= 13;
}
else if (text.Length >= 3)
{
x -= 15;
y -= 2;
paint.TextSize = 18;
} }
canvas.DrawText(text, x, y, paint);
} }
} }
} }

Loading…
Cancel
Save