SkiaSharp-Bildfilter

Beispiel herunterladen Das Beispiel herunterladen

Bildfilter sind Effekte, die auf alle Farbbits von Pixeln angewendet werden, aus denen ein Bild besteht. Sie sind vielseitiger als Maskenfilter, die nur im Alphakanal arbeiten, wie im Artikel SkiaSharp Maskenfilter beschrieben. Um einen Bildfilter zu verwenden, legen Sie die ImageFilter -Eigenschaft von SKPaint auf ein Objekt vom Typ SKImageFilter fest, das Sie erstellt haben, indem Sie eine der statischen Methoden der Klasse aufrufen.

Die beste Möglichkeit, sich mit Maskenfiltern vertraut zu machen, besteht darin, mit diesen statischen Methoden zu experimentieren. Sie können einen Maskenfilter verwenden, um eine gesamte Bitmap zu weichzeichnen:

Weichzeichnerbeispiel

In diesem Artikel wird auch die Verwendung eines Bildfilters zum Erstellen eines Schlagschattens sowie für Prägungs- und Graviereffekte veranschaulicht.

Verschwommen von Vektorgrafiken und Bitmaps

Der von der SKImageFilter.CreateBlur statischen Methode erzeugte Weichzeichnereffekt hat einen erheblichen Vorteil gegenüber den Weichzeichnermethoden in der SKMaskFilter -Klasse: Der Bildfilter kann eine gesamte Bitmap weichzeichnen. Die -Methode weist die folgende Syntax auf:

public static SkiaSharp.SKImageFilter CreateBlur (float sigmaX, float sigmaY,
                                                  SKImageFilter input = null,
                                                  SKImageFilter.CropRect cropRect = null);

Die Methode verfügt über zwei Sigmawerte – den ersten für den Weichzeichnerbereich in horizontaler Richtung und den zweiten für die vertikale Richtung. Sie können Bildfilter kaskadieren, indem Sie einen anderen Bildfilter als optionales drittes Argument angeben. Ein Zuschneiderechteck kann auch angegeben werden.

Die Seite Bildunschärfeexperiment in SkiaSharpFormsDemos enthält zwei Slider Ansichten, mit denen Sie mit dem Festlegen verschiedener Unschärfestufen experimentieren können:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Effects.ImageBlurExperimentPage"
             Title="Image Blur Experiment">

    <StackLayout>
        <skia:SKCanvasView x:Name="canvasView"
                           VerticalOptions="FillAndExpand"
                           PaintSurface="OnCanvasViewPaintSurface" />

        <Slider x:Name="sigmaXSlider"
                Maximum="10"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="{Binding Source={x:Reference sigmaXSlider},
                              Path=Value,
                              StringFormat='Sigma X = {0:F1}'}"
               HorizontalTextAlignment="Center" />

        <Slider x:Name="sigmaYSlider"
                Maximum="10"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="{Binding Source={x:Reference sigmaYSlider},
                              Path=Value,
                              StringFormat='Sigma Y = {0:F1}'}"
               HorizontalTextAlignment="Center" />
    </StackLayout>
</ContentPage>

Die CodeBehind-Datei verwendet die beiden Slider Werte, um für das Objekt aufzurufen SKImageFilter.CreateBlur , das SKPaint zum Anzeigen von Text und einer Bitmap verwendet wird:

public partial class ImageBlurExperimentPage : ContentPage
{
    const string TEXT = "Blur My Text";

    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
                            typeof(MaskBlurExperimentPage),
                            "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");

    public ImageBlurExperimentPage ()
    {
        InitializeComponent ();
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear(SKColors.Pink);

        // Get values from sliders
        float sigmaX = (float)sigmaXSlider.Value;
        float sigmaY = (float)sigmaYSlider.Value;

        using (SKPaint paint = new SKPaint())
        {
            // Set SKPaint properties
            paint.TextSize = (info.Width - 100) / (TEXT.Length / 2);
            paint.ImageFilter = SKImageFilter.CreateBlur(sigmaX, sigmaY);

            // Get text bounds and calculate display rectangle
            SKRect textBounds = new SKRect();
            paint.MeasureText(TEXT, ref textBounds);
            SKRect textRect = new SKRect(0, 0, info.Width, textBounds.Height + 50);

            // Center the text in the display rectangle
            float xText = textRect.Width / 2 - textBounds.MidX;
            float yText = textRect.Height / 2 - textBounds.MidY;

            canvas.DrawText(TEXT, xText, yText, paint);

            // Calculate rectangle for bitmap
            SKRect bitmapRect = new SKRect(0, textRect.Bottom, info.Width, info.Height);
            bitmapRect.Inflate(-50, -50);

            canvas.DrawBitmap(bitmap, bitmapRect, BitmapStretch.Uniform, paint: paint);
        }
    }
}

Die drei Screenshots zeigen verschiedene Einstellungen für die sigmaX Einstellungen und sigmaY :

Bild-Weichzeichnerexperiment

Um die Unschärfe zwischen verschiedenen Anzeigegrößen und -auflösungen konsistent zu halten, legen Sie und sigmaY auf Werte festsigmaX, die proportional zur gerenderten Pixelgröße des Bilds sind, auf das die Unschärfe angewendet wird.

Schlagschatten

Die SKImageFilter.CreateDropShadow statische Methode erstellt ein SKImageFilter -Objekt für einen Schlagschatten:

public static SKImageFilter CreateDropShadow (float dx, float dy,
                                              float sigmaX, float sigmaY,
                                              SKColor color,
                                              SKDropShadowImageFilterShadowMode shadowMode,
                                              SKImageFilter input = null,
                                              SKImageFilter.CropRect cropRect = null);

Legen Sie dieses Objekt auf die ImageFilter -Eigenschaft eines SKPaint -Objekts fest, und alles, was Sie mit diesem Objekt zeichnen, hat einen Schlagschatten dahinter.

Die dx Parameter und dy geben die horizontalen und vertikalen Offsets des Schattens in Pixeln aus dem grafischen Objekt an. Die Konvention in 2D-Grafiken besteht darin, von einer Lichtquelle aus der oberen linken Ecke auszugehen, was impliziert, dass beide Argumente positiv sein sollten, um den Schatten unterhalb und rechts neben dem grafischen Objekt zu positionieren.

Die sigmaX Parameter und sigmaY sind verschwommene Faktoren für den Schlagschatten.

Der color Parameter ist die Farbe des Schlagschattens. Dieser SKColor Wert kann Transparenz enthalten. Eine Möglichkeit ist der Farbwert SKColors.Black.WithAlpha(0x80) , um einen beliebigen Farbhintergrund abzudunken.

Die letzten beiden Parameter sind optional.

Mit dem Drop Shadow Experiment-Programm können Sie mit Werten von dx, dy, sigmaXund sigmaY experimentieren, um eine Textzeichenfolge mit einem Schlagschatten anzuzeigen. Die XAML-Datei instanziiert vier Slider Ansichten, um diese Werte festzulegen:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Effects.DropShadowExperimentPage"
             Title="Drop Shadow Experiment">
    <ContentPage.Resources>
        <Style TargetType="Slider">
            <Setter Property="Margin" Value="10, 0" />
        </Style>

        <Style TargetType="Label">
            <Setter Property="HorizontalTextAlignment" Value="Center" />
        </Style>
    </ContentPage.Resources>

    <StackLayout>
        <skia:SKCanvasView x:Name="canvasView"
                           VerticalOptions="FillAndExpand"
                           PaintSurface="OnCanvasViewPaintSurface" />

        <Slider x:Name="dxSlider"
                Minimum="-20"
                Maximum="20"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="{Binding Source={x:Reference dxSlider},
                              Path=Value,
                              StringFormat='Horizontal offset = {0:F1}'}" />

        <Slider x:Name="dySlider"
                Minimum="-20"
                Maximum="20"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="{Binding Source={x:Reference dySlider},
                              Path=Value,
                              StringFormat='Vertical offset = {0:F1}'}" />

        <Slider x:Name="sigmaXSlider"
                Maximum="10"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="{Binding Source={x:Reference sigmaXSlider},
                              Path=Value,
                              StringFormat='Sigma X = {0:F1}'}" />

        <Slider x:Name="sigmaYSlider"
                Maximum="10"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="{Binding Source={x:Reference sigmaYSlider},
                              Path=Value,
                              StringFormat='Sigma Y = {0:F1}'}" />
    </StackLayout>
</ContentPage>

Die CodeBehind-Datei verwendet diese Werte, um einen roten Schlagschatten auf einer blauen Textzeichenfolge zu erstellen:

public partial class DropShadowExperimentPage : ContentPage
{
    const string TEXT = "Drop Shadow";

    public DropShadowExperimentPage ()
    {
        InitializeComponent ();
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Get values from sliders
        float dx = (float)dxSlider.Value;
        float dy = (float)dySlider.Value;
        float sigmaX = (float)sigmaXSlider.Value;
        float sigmaY = (float)sigmaYSlider.Value;

        using (SKPaint paint = new SKPaint())
        {
            // Set SKPaint properties
            paint.TextSize = info.Width / 7;
            paint.Color = SKColors.Blue;
            paint.ImageFilter = SKImageFilter.CreateDropShadow(
                                    dx,
                                    dy,
                                    sigmaX,
                                    sigmaY,
                                    SKColors.Red,
                                    SKDropShadowImageFilterShadowMode.DrawShadowAndForeground);

            SKRect textBounds = new SKRect();
            paint.MeasureText(TEXT, ref textBounds);

            // Center the text in the display rectangle
            float xText = info.Width / 2 - textBounds.MidX;
            float yText = info.Height / 2 - textBounds.MidY;

            canvas.DrawText(TEXT, xText, yText, paint);
        }
    }
}

Dies ist das Programm, das ausgeführt wird:

Drop Shadow Experiment

Die negativen Offsetwerte im Universelle Windows-Plattform Screenshot ganz rechts bewirken, dass der Schatten oben und links neben dem Text angezeigt wird. Dies deutet auf eine Lichtquelle in der unteren rechten Ecke hin, die nicht die Konvention für Computergrafiken ist. Aber es scheint in keiner Weise falsch, vielleicht, weil der Schatten auch sehr verschwommen ist und ornamentaler erscheint als die meisten Schlagschatten.

Beleuchtungseffekte

Die SKImageFilter -Klasse definiert sechs Methoden mit ähnlichen Namen und Parametern, die hier in der Reihenfolge der zunehmenden Komplexität aufgeführt sind:

Diese Methoden erstellen Bildfilter, die die Wirkung verschiedener Arten von Licht auf dreidimensionalen Oberflächen imitieren. Der resultierende Bildfilter beleuchtet zweidimensionale Objekte so, als ob sie im 3D-Raum vorhanden seien, was dazu führen kann, dass diese Objekte erhöht oder eingelassen erscheinen, oder mit glanziger Hervorhebung.

Die Distant Lichtmethoden gehen davon aus, dass das Licht aus großer Entfernung kommt. Für den Zweck der Beleuchtung von Objekten wird angenommen, dass das Licht in eine konsistente Richtung im 3D-Raum zeigt, ähnlich wie die Sonne auf einem kleinen Bereich der Erde. Die Point Lichtmethoden imitieren eine im 3D-Raum positionierte Glühbirne, die Licht in alle Richtungen abgibt. Das Spot Licht hat sowohl eine Position als auch eine Richtung, ähnlich wie eine Taschenlampe.

Standorte und Richtungen im 3D-Raum werden beide mit Werten der SKPoint3 -Struktur angegeben, die ähnlich ist SKPoint , aber mit drei Eigenschaften namens X, Yund Z.

Die Anzahl und Komplexität der Parameter für diese Methoden erschwert das Experimentieren mit ihnen. Um Ihnen den Einstieg zu erleichtern, können Sie auf der Seite Fernlichtexperiment mit Parametern für die CreateDistantLightDiffuse Methode experimentieren:

public static SKImageFilter CreateDistantLitDiffuse (SKPoint3 direction,
                                                     SKColor lightColor,
                                                     float surfaceScale,
                                                     float kd,
                                                     SKImageFilter input = null,
                                                     SKImageFilter.CropRect cropRect = null);

Die Seite verwendet nicht die letzten beiden optionalen Parameter.

Mit drei Slider Ansichten in der XAML-Datei können Sie die Z Koordinate des SKPoint3 Werts, den surfaceScale Parameter und den Parameter auswählen, der kd in der API-Dokumentation als "diffuse Beleuchtungskonstante" definiert ist:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaLightExperiment.MainPage"
             Title="Distant Light Experiment">

    <StackLayout>

        <skia:SKCanvasView x:Name="canvasView"
                           PaintSurface="OnCanvasViewPaintSurface"
                           VerticalOptions="FillAndExpand" />

        <Slider x:Name="zSlider"
                Minimum="-10"
                Maximum="10"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="{Binding Source={x:Reference zSlider},
                              Path=Value,
                              StringFormat='Z = {0:F0}'}"
               HorizontalTextAlignment="Center" />

        <Slider x:Name="surfaceScaleSlider"
                Minimum="-1"
                Maximum="1"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="{Binding Source={x:Reference surfaceScaleSlider},
                              Path=Value,
                              StringFormat='Surface Scale = {0:F1}'}"
               HorizontalTextAlignment="Center" />

        <Slider x:Name="lightConstantSlider"
                Minimum="-1"
                Maximum="1"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="{Binding Source={x:Reference lightConstantSlider},
                              Path=Value,
                              StringFormat='Light Constant = {0:F1}'}"
               HorizontalTextAlignment="Center" />
    </StackLayout>
</ContentPage>

Die CodeBehind-Datei ruft diese drei Werte ab und verwendet sie, um einen Bildfilter zum Anzeigen einer Textzeichenfolge zu erstellen:

public partial class DistantLightExperimentPage : ContentPage
{
    const string TEXT = "Lighting";

    public DistantLightExperimentPage()
    {
        InitializeComponent();
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        float z = (float)zSlider.Value;
        float surfaceScale = (float)surfaceScaleSlider.Value;
        float lightConstant = (float)lightConstantSlider.Value;

        using (SKPaint paint = new SKPaint())
        {
            paint.IsAntialias = true;

            // Size text to 90% of canvas width
            paint.TextSize = 100;
            float textWidth = paint.MeasureText(TEXT);
            paint.TextSize *= 0.9f * info.Width / textWidth;

            // Find coordinates to center text
            SKRect textBounds = new SKRect();
            paint.MeasureText(TEXT, ref textBounds);

            float xText = info.Rect.MidX - textBounds.MidX;
            float yText = info.Rect.MidY - textBounds.MidY;

            // Create distant light image filter
            paint.ImageFilter = SKImageFilter.CreateDistantLitDiffuse(
                                    new SKPoint3(2, 3, z),
                                    SKColors.White,
                                    surfaceScale,
                                    lightConstant);

            canvas.DrawText(TEXT, xText, yText, paint);
        }
    }
}

Das erste Argument von SKImageFilter.CreateDistantLitDiffuse ist die Richtung des Lichts. Die positiven X- und Y-Koordinaten zeigen an, dass das Licht nach rechts und unten zeigt. Positive Z-Koordinaten zeigen auf den Bildschirm. Mit der XAML-Datei können Sie negative Z-Werte auswählen, aber das ist nur so, dass Sie sehen können, was passiert: Vom Konzept her bewirken negative Z-Koordinaten, dass das Licht aus dem Bildschirm zeigt. Bei allen anderen als kleinen negativen Werten funktioniert der Lichteffekt nicht mehr.

Das surfaceScale Argument kann zwischen –1 und 1 liegen. (Höhere oder niedrigere Werte haben keine weiteren Auswirkungen.) Dies sind relative Werte auf der Z-Achse, die die Verschiebung des grafischen Objekts (in diesem Fall die Textzeichenfolge) von der Canvasoberfläche angeben. Verwenden Sie negative Werte, um die Textzeichenfolge über die Oberfläche des Zeichenbereichs zu heben, und positive Werte, um sie in den Zeichenbereich zu drücken.

Der lightConstant Wert sollte positiv sein. (Das Programm lässt negative Werte zu, sodass Sie sehen können, dass sie dazu führen, dass der Effekt nicht mehr funktioniert.) Höhere Werte verursachen intensiveres Licht.

Diese Faktoren können ausgeglichen werden, um einen geprägten Effekt zu erhalten, wenn surfaceScale negativ ist (wie bei den iOS- und Android-Screenshots) und einem gravierten Effekt, wenn surfaceScale positiv ist, wie beim UWP-Screenshot auf der rechten Seite:

Fernlichtexperiment

Der Android-Screenshot weist den Z-Wert 0 auf, was bedeutet, dass das Licht nur nach unten und nach rechts zeigt. Der Hintergrund wird nicht beleuchtet, und die Oberfläche der Textzeichenfolge wird auch nicht beleuchtet. Das Licht wirkt sich nur auf den Textrand aus, um einen sehr subtilen Effekt zu erzielen.

Ein alternativer Ansatz für geprägten und gravierten Text wurde im Artikel Die Übersetzungstransformation veranschaulicht: Die Textzeichenfolge wird zweimal mit unterschiedlichen Farben angezeigt, die leicht voneinander entfernt sind.