diff --git a/Directory.Packages.props b/Directory.Packages.props index f2a02fee6..25b283216 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -32,8 +32,8 @@ - - + + @@ -72,9 +72,9 @@ - - - + + + @@ -84,4 +84,4 @@ - + \ No newline at end of file diff --git a/src/Beutl.Engine/Graphics/BrushConstructor.cs b/src/Beutl.Engine/Graphics/BrushConstructor.cs index 27da4933c..a1b4756dc 100644 --- a/src/Beutl.Engine/Graphics/BrushConstructor.cs +++ b/src/Beutl.Engine/Graphics/BrushConstructor.cs @@ -18,9 +18,6 @@ public void ConfigurePaint(SKPaint paint) float opacity = (Brush?.Opacity ?? 0) / 100f; paint.IsAntialias = true; paint.BlendMode = (SKBlendMode)BlendMode; - paint.HintingLevel = SKPaintHinting.Full; - paint.LcdRenderText = true; - paint.SubpixelText = true; paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity)); @@ -194,7 +191,7 @@ private void ConfigureGradientBrush(SKPaint paint, IGradientBrush gradientBrush) private void ConfigureTileBrush(SKPaint paint, ITileBrush tileBrush) { RenderTarget? renderTarget = null; - SKBitmap? skbitmap = null; + SKImage? skImage = null; PixelSize pixelSize; if (tileBrush is RenderSceneBrush sceneBrush) @@ -228,7 +225,7 @@ private void ConfigureTileBrush(SKPaint paint, ITileBrush tileBrush) { using (bitmap) { - skbitmap = bitmap.Value.ToSKBitmap(); + skImage = bitmap.Value.ToSKImage(copy: true); pixelSize = new(bitmap.Value.Width, bitmap.Value.Height); } } @@ -237,7 +234,7 @@ private void ConfigureTileBrush(SKPaint paint, ITileBrush tileBrush) throw new InvalidOperationException($"'{tileBrush.GetType().Name}' not supported."); } - if (renderTarget == null && skbitmap == null) + if (renderTarget == null && skImage == null) return; RenderTarget? intermediate = null; @@ -253,7 +250,7 @@ private void ConfigureTileBrush(SKPaint paint, ITileBrush tileBrush) SKCanvas canvas = intermediate.Value.Canvas; using var ipaint = new SKPaint(); { - ipaint.FilterQuality = tileBrush.BitmapInterpolationMode.ToSKFilterQuality(); + var options = tileBrush.BitmapInterpolationMode.ToSKSamplingOptions(); canvas.Clear(); canvas.Save(); @@ -262,8 +259,8 @@ private void ConfigureTileBrush(SKPaint paint, ITileBrush tileBrush) if (renderTarget != null) canvas.DrawSurface(renderTarget.Value, default, ipaint); - else if (skbitmap != null) - canvas.DrawBitmap(skbitmap, (SKPoint)default, ipaint); + else if (skImage != null) + canvas.DrawImage(skImage, (SKPoint)default, options, ipaint); canvas.Restore(); } @@ -294,8 +291,8 @@ private void ConfigureTileBrush(SKPaint paint, ITileBrush tileBrush) tileTransform = tileTransform.PreConcat(transform.ToSKMatrix()); } - using (SKImage skimage = intermediate.Value.Snapshot()) - using (SKShader shader = skimage.ToShader(tileX, tileY, tileTransform)) + using (SKImage snapshot = intermediate.Value.Snapshot()) + using (SKShader shader = snapshot.ToShader(tileX, tileY, tileTransform)) { paint.Shader = shader; } @@ -303,7 +300,7 @@ private void ConfigureTileBrush(SKPaint paint, ITileBrush tileBrush) finally { renderTarget?.Dispose(); - skbitmap?.Dispose(); + skImage?.Dispose(); intermediate?.Dispose(); } } diff --git a/src/Beutl.Engine/Graphics/FilterEffects/FilterEffectContext.cs b/src/Beutl.Engine/Graphics/FilterEffects/FilterEffectContext.cs index 9f9648627..0348b5e1f 100644 --- a/src/Beutl.Engine/Graphics/FilterEffects/FilterEffectContext.cs +++ b/src/Beutl.Engine/Graphics/FilterEffects/FilterEffectContext.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Reactive; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -193,7 +193,17 @@ public void DisplacementMap( AppendSkiaFilter( data: (xChannelSelector, yChannelSelector, scale, child), factory: static (t, input, activator) - => SKImageFilter.CreateDisplacementMapEffect(t.xChannelSelector, t.yChannelSelector, t.scale, activator.Activate(t.child), input), + => + { + var displacement = activator.Activate(t.child); + if (displacement != null) + { + return SKImageFilter.CreateDisplacementMapEffect(t.xChannelSelector, t.yChannelSelector, t.scale, + displacement, input); + } + + return input; + }, transformBounds: static (data, bounds) => bounds.Inflate(data.scale / 2)); } @@ -298,7 +308,7 @@ public void Transform(Matrix matrix, BitmapInterpolationMode bitmapInterpolation { AppendSkiaFilter( (matrix, bitmapInterpolationMode), - (data, input, _) => SKImageFilter.CreateMatrix(data.matrix.ToSKMatrix(), data.bitmapInterpolationMode.ToSKFilterQuality(), input), + (data, input, _) => SKImageFilter.CreateMatrix(data.matrix.ToSKMatrix(), data.bitmapInterpolationMode.ToSKSamplingOptions(), input), (data, rect) => rect.TransformToAABB(data.matrix)); } diff --git a/src/Beutl.Engine/Graphics/Image.cs b/src/Beutl.Engine/Graphics/Image.cs index 7ff4835bf..92ca2cdae 100644 --- a/src/Beutl.Engine/Graphics/Image.cs +++ b/src/Beutl.Engine/Graphics/Image.cs @@ -146,6 +146,28 @@ public static SKBitmap ToSKBitmap(this IBitmap self) } } + public static SKImage ToSKImage(this IBitmap self, bool copy = false) + { + SKColorType? type = self switch + { + Bitmap => SKColorType.Bgra8888, + Bitmap => SKColorType.Argb4444, + Bitmap => SKColorType.Alpha8, + _ => null + }; + if (type.HasValue) + { + return copy + ? SKImage.FromPixelCopy(new(self.Width, self.Height, type.Value), self.Data) + : SKImage.FromPixels(new(self.Width, self.Height, type.Value), self.Data); + } + else + { + using Bitmap typed = self.Convert(); + return SKImage.FromPixelCopy(new(self.Width, self.Height, SKColorType.Bgra8888), typed.Data); + } + } + public static SKBitmap ToSKBitmap(this Mat self) { var result = new SKBitmap(new(self.Width, self.Height, SKColorType.Bgra8888)); diff --git a/src/Beutl.Engine/Graphics/SkiaSharpExtensions.cs b/src/Beutl.Engine/Graphics/SkiaSharpExtensions.cs index 3b579d1f1..c44a8ec52 100644 --- a/src/Beutl.Engine/Graphics/SkiaSharpExtensions.cs +++ b/src/Beutl.Engine/Graphics/SkiaSharpExtensions.cs @@ -6,6 +6,7 @@ namespace Beutl.Graphics; internal static class SkiaSharpExtensions { + [Obsolete("Use ToSKSamplingOptions")] public static SKFilterQuality ToSKFilterQuality(this BitmapInterpolationMode interpolationMode) { return interpolationMode switch @@ -18,6 +19,18 @@ public static SKFilterQuality ToSKFilterQuality(this BitmapInterpolationMode int }; } + public static SKSamplingOptions ToSKSamplingOptions(this BitmapInterpolationMode interpolationMode) + { + return interpolationMode switch + { + BitmapInterpolationMode.Default => new SKSamplingOptions(SKFilterMode.Nearest, SKMipmapMode.None), + BitmapInterpolationMode.LowQuality => new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.None), + BitmapInterpolationMode.MediumQuality => new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear), + BitmapInterpolationMode.HighQuality => new SKSamplingOptions(SKCubicResampler.Mitchell), + _ => throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null) + }; + } + public static SKPoint ToSKPoint(this Point p) { return new SKPoint(p.X, p.Y); diff --git a/src/Beutl.Engine/Media/TextFormatting/FormattedText.cs b/src/Beutl.Engine/Media/TextFormatting/FormattedText.cs index 51f180d22..676b33650 100644 --- a/src/Beutl.Engine/Media/TextFormatting/FormattedText.cs +++ b/src/Beutl.Engine/Media/TextFormatting/FormattedText.cs @@ -2,7 +2,6 @@ using System.Runtime.InteropServices; using Beutl.Graphics; using Beutl.Graphics.Rendering; -using Beutl.Media.Immutable; using Beutl.Reactive; using SkiaSharp; using SkiaSharp.HarfBuzz; @@ -128,16 +127,15 @@ internal Point AddToSKPath(SKPath path, Point point) buffer.AddUtf16(Text.AsSpan()); buffer.GuessSegmentProperties(); - using SKPaint paint = new() { TextSize = Size, Typeface = font.Typeface }; - SKShaper.Result result = shaper.Shape(buffer, paint); + SKShaper.Result result = shaper.Shape(buffer, font); // create the text blob using var builder = new SKTextBlobBuilder(); SKPositionedRunBuffer run = builder.AllocatePositionedRun(font, result.Codepoints.Length); // copy the glyphs - Span glyphs = run.GetGlyphSpan(); - Span positions = run.GetPositionSpan(); + Span glyphs = run.Glyphs; + Span positions = run.Positions; for (int i = 0; i < result.Codepoints.Length; i++) { glyphs[i] = (ushort)result.Codepoints[i]; @@ -147,7 +145,7 @@ internal Point AddToSKPath(SKPath path, Point point) } // build - using SKTextBlob textBlob = builder.Build(); + using SKTextBlob? textBlob = builder.Build(); for (int i = 0; i < glyphs.Length; i++) { @@ -187,7 +185,11 @@ internal SKFont ToSKFont() { Edging = SKFontEdging.Antialias, Subpixel = true, - Hinting = SKFontHinting.Full + Hinting = SKFontHinting.Full, + Embolden = true + //paint.HintingLevel = SKPaintHinting.Full; + //paint.LcdRenderText = true; + //paint.SubpixelText = true; }; return font; @@ -208,16 +210,15 @@ private void Measure() buffer.AddUtf16(Text.AsSpan()); buffer.GuessSegmentProperties(); - using SKPaint paint = new() { TextSize = Size, Typeface = font.Typeface, }; - SKShaper.Result result = shaper.Shape(buffer, paint); + SKShaper.Result result = shaper.Shape(buffer, font); // create the text blob using var builder = new SKTextBlobBuilder(); SKPositionedRunBuffer run = builder.AllocatePositionedRun(font, result.Codepoints.Length); var fillPath = new SKPath(); - Span glyphs = run.GetGlyphSpan(); - Span positions = run.GetPositionSpan(); + Span glyphs = run.Glyphs; + Span positions = run.Positions; CollectionsMarshal.SetCount(_pathList, result.Codepoints.Length); Span pathList = CollectionsMarshal.AsSpan(_pathList); for (int i = 0; i < result.Codepoints.Length; i++) @@ -250,7 +251,7 @@ private void Measure() // 空白で開始または、終了した場合 var bounds = new Rect(0, 0, (glyphs.Length - 1) * Spacing + result.Width, fillPath.TightBounds.Height); Rect actualBounds = fillPath.TightBounds.ToGraphicsRect(); - SKTextBlob textBlob = builder.Build(); + SKTextBlob? textBlob = builder.Build(); if (result.Codepoints.Length > 0) {