diff options
Diffstat (limited to 'Emby.Drawing.Skia/SkiaEncoder.cs')
| -rw-r--r-- | Emby.Drawing.Skia/SkiaEncoder.cs | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/Emby.Drawing.Skia/SkiaEncoder.cs b/Emby.Drawing.Skia/SkiaEncoder.cs new file mode 100644 index 000000000..d52ad4734 --- /dev/null +++ b/Emby.Drawing.Skia/SkiaEncoder.cs @@ -0,0 +1,380 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using SkiaSharp; +using System; +using System.Reflection; +using System.Threading.Tasks; + +namespace Emby.Drawing.Skia +{ + public class SkiaEncoder : IImageEncoder + { + private readonly ILogger _logger; + private readonly IApplicationPaths _appPaths; + private readonly Func<IHttpClient> _httpClientFactory; + private readonly IFileSystem _fileSystem; + + public SkiaEncoder(ILogger logger, IApplicationPaths appPaths, Func<IHttpClient> httpClientFactory, IFileSystem fileSystem) + { + _logger = logger; + _appPaths = appPaths; + _httpClientFactory = httpClientFactory; + _fileSystem = fileSystem; + + LogVersion(); + } + + public string[] SupportedInputFormats + { + get + { + // Some common file name extensions for RAW picture files include: .cr2, .crw, .dng, .nef, .orf, .rw2, .pef, .arw, .sr2, .srf, and .tif. + return new[] + { + "jpeg", + "jpg", + "png", + "dng", + "webp", + "gif", + "bmp", + "ico", + "astc", + "ktx", + "pkm", + "wbmp" + }; + } + } + + public ImageFormat[] SupportedOutputFormats + { + get + { + return new[] { ImageFormat.Webp, ImageFormat.Gif, ImageFormat.Jpg, ImageFormat.Png, ImageFormat.Bmp }; + } + } + + private void LogVersion() + { + _logger.Info("SkiaSharp version: " + GetVersion()); + } + + public static string GetVersion() + { + using (var bitmap = new SKBitmap()) + { + return typeof(SKBitmap).GetTypeInfo().Assembly.GetName().Version.ToString(); + } + } + + private static bool IsWhiteSpace(SKColor color) + { + return (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0; + } + + public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat) + { + switch (selectedFormat) + { + case ImageFormat.Bmp: + return SKEncodedImageFormat.Bmp; + case ImageFormat.Jpg: + return SKEncodedImageFormat.Jpeg; + case ImageFormat.Gif: + return SKEncodedImageFormat.Gif; + case ImageFormat.Webp: + return SKEncodedImageFormat.Webp; + default: + return SKEncodedImageFormat.Png; + } + } + + private static bool IsAllWhiteRow(SKBitmap bmp, int row) + { + for (var i = 0; i < bmp.Width; ++i) + { + if (!IsWhiteSpace(bmp.GetPixel(i, row))) + { + return false; + } + } + return true; + } + + private static bool IsAllWhiteColumn(SKBitmap bmp, int col) + { + for (var i = 0; i < bmp.Height; ++i) + { + if (!IsWhiteSpace(bmp.GetPixel(col, i))) + { + return false; + } + } + return true; + } + + private SKBitmap CropWhiteSpace(SKBitmap bitmap) + { + CheckDisposed(); + + var topmost = 0; + for (int row = 0; row < bitmap.Height; ++row) + { + if (IsAllWhiteRow(bitmap, row)) + topmost = row; + else break; + } + + int bottommost = 0; + for (int row = bitmap.Height - 1; row >= 0; --row) + { + if (IsAllWhiteRow(bitmap, row)) + bottommost = row; + else break; + } + + int leftmost = 0, rightmost = 0; + for (int col = 0; col < bitmap.Width; ++col) + { + if (IsAllWhiteColumn(bitmap, col)) + leftmost = col; + else + break; + } + + for (int col = bitmap.Width - 1; col >= 0; --col) + { + if (IsAllWhiteColumn(bitmap, col)) + rightmost = col; + else + break; + } + + var newRect = SKRectI.Create(leftmost, topmost, rightmost - leftmost, bottommost - topmost); + + using (var image = SKImage.FromBitmap(bitmap)) + { + using (var subset = image.Subset(newRect)) + { + return SKBitmap.FromImage(subset); + //using (var data = subset.Encode(StripCollageBuilder.GetEncodedFormat(outputPath), 90)) + //{ + // using (var fileStream = _fileSystem.GetFileStream(outputPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) + // { + // data.AsStream().CopyTo(fileStream); + // } + //} + } + } + } + + public ImageSize GetImageSize(string path) + { + CheckDisposed(); + + using (var s = new SKFileStream(path)) + { + using (var codec = SKCodec.Create(s)) + { + var info = codec.Info; + + return new ImageSize + { + Width = info.Width, + Height = info.Height + }; + } + } + } + + private SKBitmap GetBitmap(string path, bool cropWhitespace) + { + if (cropWhitespace) + { + using (var bitmap = SKBitmap.Decode(path)) + { + return CropWhiteSpace(bitmap); + } + } + + return SKBitmap.Decode(path); + } + + public void EncodeImage(string inputPath, string outputPath, bool autoOrient, int width, int height, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) + { + if (string.IsNullOrWhiteSpace(inputPath)) + { + throw new ArgumentNullException("inputPath"); + } + if (string.IsNullOrWhiteSpace(inputPath)) + { + throw new ArgumentNullException("outputPath"); + } + + var skiaOutputFormat = GetImageFormat(selectedOutputFormat); + + var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor); + var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer); + var blur = options.Blur ?? 0; + var hasIndicator = !options.AddPlayedIndicator && !options.UnplayedCount.HasValue && options.PercentPlayed.Equals(0); + + using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace)) + { + using (var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType)) + { + // scale image + var resizeMethod = options.Image.Type == MediaBrowser.Model.Entities.ImageType.Logo || + options.Image.Type == MediaBrowser.Model.Entities.ImageType.Art + ? SKBitmapResizeMethod.Lanczos3 + : SKBitmapResizeMethod.Lanczos3; + + bitmap.Resize(resizedBitmap, resizeMethod); + + // If all we're doing is resizing then we can stop now + if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) + { + using (var outputStream = new SKFileWStream(outputPath)) + { + resizedBitmap.Encode(outputStream, skiaOutputFormat, quality); + return; + } + } + + // create bitmap to use for canvas drawing + using (var saveBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType)) + { + // create canvas used to draw into bitmap + 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) + { + using (var paint = new SKPaint()) + { + // create image from resized bitmap to apply blur + using (var filter = SKImageFilter.CreateBlur(5, 5)) + { + 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) + { + Double opacity; + if (!Double.TryParse(options.ForegroundLayer, out opacity)) opacity = .4; + + var foregroundColor = String.Format("#{0:X2}000000", (Byte)((1 - opacity) * 0xFF)); + canvas.DrawColor(SKColor.Parse(foregroundColor), SKBlendMode.SrcOver); + } + + if (hasIndicator) + { + DrawIndicator(canvas, width, height, options); + } + + using (var outputStream = new SKFileWStream(outputPath)) + { + saveBitmap.Encode(outputStream, skiaOutputFormat, quality); + } + } + } + } + } + } + + public void CreateImageCollage(ImageCollageOptions options) + { + double ratio = options.Width; + ratio /= options.Height; + + if (ratio >= 1.4) + { + new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); + } + else if (ratio >= .9) + { + new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); + } + else + { + // @todo create Poster collage capability + new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); + } + } + + private void DrawIndicator(SKCanvas canvas, int imageWidth, int imageHeight, ImageProcessingOptions options) + { + try + { + var currentImageSize = new ImageSize(imageWidth, imageHeight); + + if (options.AddPlayedIndicator) + { + var task = new PlayedIndicatorDrawer(_appPaths, _httpClientFactory(), _fileSystem).DrawPlayedIndicator(canvas, currentImageSize); + Task.WaitAll(task); + } + else if (options.UnplayedCount.HasValue) + { + new UnplayedCountIndicator(_appPaths, _httpClientFactory(), _fileSystem).DrawUnplayedCountIndicator(canvas, currentImageSize, options.UnplayedCount.Value); + } + + if (options.PercentPlayed > 0) + { + new PercentPlayedDrawer().Process(canvas, currentImageSize, options.PercentPlayed); + } + } + catch (Exception ex) + { + _logger.ErrorException("Error drawing indicator overlay", ex); + } + } + + public string Name + { + get { return "Skia"; } + } + + private bool _disposed; + public void Dispose() + { + _disposed = true; + } + + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + public bool SupportsImageCollageCreation + { + get { return true; } + } + + public bool SupportsImageEncoding + { + get { return true; } + } + } +}
\ No newline at end of file |
