Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update HarfBuzzSharp and SkiaSharp packages, improve bitmap interpolation handling, and simplify text shaping #1203

Merged
merged 6 commits into from
Dec 11, 2024
12 changes: 6 additions & 6 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
<PackageVersion Include="MonoMac.NetStandard" Version="0.0.4" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="NAudio.Wasapi" Version="2.2.1" />
<PackageVersion Include="HarfBuzzSharp" Version="7.3.0.3" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.3" />
<PackageVersion Include="HarfBuzzSharp" Version="8.3.0.1" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="8.3.0.1" />
<PackageVersion Include="ILGPU" Version="1.5.1" />
<PackageVersion Include="Kokuban" Version="0.2.0" />
<PackageVersion Include="Kurukuru" Version="1.4.2" />
Expand Down Expand Up @@ -72,9 +72,9 @@
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.OpenTelemetry" Version="1.2.0" />
<PackageVersion Include="Sharprompt" Version="2.4.5" />
<PackageVersion Include="SkiaSharp" Version="2.88.9" />
<PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.9" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
<PackageVersion Include="SkiaSharp" Version="3.116.1" />
<PackageVersion Include="SkiaSharp.HarfBuzz" Version="3.116.1" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.116.1" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.Interactive" Version="6.0.1" />
<PackageVersion Include="System.Interactive.Async" Version="6.0.1" />
Expand All @@ -84,4 +84,4 @@
<PackageVersion Include="Vortice.MediaFoundation" Version="3.6.2" />
<PackageVersion Include="Vortice.XAudio2" Version="3.6.2" />
</ItemGroup>
</Project>
</Project>
21 changes: 9 additions & 12 deletions src/Beutl.Engine/Graphics/BrushConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -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;
Expand All @@ -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();
Expand All @@ -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();
}
Expand Down Expand Up @@ -294,16 +291,16 @@ 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;
}
}
finally
{
renderTarget?.Dispose();
skbitmap?.Dispose();
skImage?.Dispose();
intermediate?.Dispose();
}
}
Expand Down
16 changes: 13 additions & 3 deletions src/Beutl.Engine/Graphics/FilterEffects/FilterEffectContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Reactive;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -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));
}

Expand Down Expand Up @@ -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));
}

Expand Down
22 changes: 22 additions & 0 deletions src/Beutl.Engine/Graphics/Image.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Bgra8888> => SKColorType.Bgra8888,
Bitmap<Bgra4444> => SKColorType.Argb4444,
Bitmap<Grayscale8> => 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<Bgra8888> typed = self.Convert<Bgra8888>();
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));
Expand Down
13 changes: 13 additions & 0 deletions src/Beutl.Engine/Graphics/SkiaSharpExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Beutl.Graphics;

internal static class SkiaSharpExtensions
{
[Obsolete("Use ToSKSamplingOptions")]
public static SKFilterQuality ToSKFilterQuality(this BitmapInterpolationMode interpolationMode)
{
return interpolationMode switch
Expand All @@ -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);
Expand Down
25 changes: 13 additions & 12 deletions src/Beutl.Engine/Media/TextFormatting/FormattedText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<ushort> glyphs = run.GetGlyphSpan();
Span<SKPoint> positions = run.GetPositionSpan();
Span<ushort> glyphs = run.Glyphs;
Span<SKPoint> positions = run.Positions;
for (int i = 0; i < result.Codepoints.Length; i++)
{
glyphs[i] = (ushort)result.Codepoints[i];
Expand All @@ -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++)
{
Expand Down Expand Up @@ -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;
Expand All @@ -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<ushort> glyphs = run.GetGlyphSpan();
Span<SKPoint> positions = run.GetPositionSpan();
Span<ushort> glyphs = run.Glyphs;
Span<SKPoint> positions = run.Positions;
CollectionsMarshal.SetCount(_pathList, result.Codepoints.Length);
Span<SKPathGeometry> pathList = CollectionsMarshal.AsSpan(_pathList);
for (int i = 0; i < result.Codepoints.Length; i++)
Expand Down Expand Up @@ -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)
{
Expand Down
Loading