次の方法で共有


SkiaSharp 画像フィルター

画像フィルターは、画像を構成するピクセルのすべてのカラー ビットに対して動作する効果です。 これらは、「SkiaSharp マスク フィルター」の記事で説明されているようにアルファ チャネルでのみ動作するマスク フィルターよりも汎用性が高くなります。 画像フィルターを使用するには、クラスの静的メソッドのいずれかを呼び出して作成した SKImageFilter 型のオブジェクトに SKPaintImageFilter プロパティを設定します。

マスク フィルターについてよく理解するための最善の方法は、これらの静的メソッドを試すことです。 マスク フィルターを使用すると、ビットマップ全体をぼかすことができます。

ぼかしの例

この記事では、画像フィルターを使用して、ドロップ シャドウを作成したり、エンボスやエッチング効果を得る方法についても説明します。

ベクター グラフィックスとビットマップのぼかし

SKImageFilter.CreateBlur 静的メソッドによって作成されるぼかし効果には、SKMaskFilter クラスのぼかしメソッドよりも大きな利点があります。画像フィルターではビットマップ全体をぼかすことができます。 メソッドの構文は次のとおりです。

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

メソッドには 2 つのシグマ値があります。1 つ目は水平方向のぼかし範囲、2 つ目は垂直方向のものです。 オプションの 3 つ目の引数として別の画像フィルターを指定することで、画像フィルターをカスケードできます。 トリミングする四角形を指定することもできます。

サンプルの [Image Blur Experiment] ページには、次の 2 つの Slider ビューが含まれています。このビューを使用すると、さまざまなレベルのぼかしの設定を試すことができます。

<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>

分離コード ファイルでは、2 つの Slider 値を使って、テキストとビットマップの両方を表示するために使用される SKPaint オブジェクトの SKImageFilter.CreateBlur を呼び出します。

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);
        }
    }
}

次の 3 つのスクリーンショットは、sigmaXsigmaY の設定のさまざまな設定を示しています。

画像ぼかし実験

さまざまな表示サイズと解像度の間でぼかしの一貫性を保つために、sigmaXsigmaY を、ぼかしが適用される画像のレンダリングされたピクセル サイズに比例する値に設定します。

ドロップ シャドウ

SKImageFilter.CreateDropShadow 静的メソッドでは、ドロップ シャドウ用の SKImageFilter オブジェクトが作成されます。

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

このオブジェクトを SKPaint オブジェクトの ImageFilter プロパティに設定すると、そのオブジェクトで描画するすべての背後にドロップ シャドウが示されます。

dxdy パラメーターは、グラフィカル オブジェクトからの影の水平方向および垂直方向のオフセットをピクセル単位で示します。 2D グラフィックスの規則では、左上からの光源を想定します。これは、両方の引数がグラフィカル オブジェクトの下と右に影を配置するために正である必要があることを意味します。

sigmaXsigmaY パラメーターは、ドロップ シャドウのぼかし要因です。

color パラメーターは、ドロップ シャドウの色です。 この SKColor 値には透明度を含めることができます。 その 1 つとして、色の背景を暗くするための色の値 SKColors.Black.WithAlpha(0x80) が考えられます。

最後の 2 つのパラメーターは省略できます。

[ドロップ シャドウ実験] プログラムを使用すると、dxdysigmaXsigmaY の値を試して、ドロップ シャドウ付きのテキスト文字列を表示できます。 XAML ファイルでは、次の値を設定するために 4 つの Slider ビューをインスタンス化します。

<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>

分離コード ファイルでは、それらの値を使用して、青いテキスト文字列に赤いドロップ シャドウが作成されます。

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);
        }
    }
}

実行中のプログラムを次に示します。

ドロップ シャドウ実験

右端のユニバーサル Windows プラットフォームのスクリーンショットの負のオフセット値を使用すると、テキストの上と左に影が表示されます。 これは、右下に光源があることを示すものであり、コンピューター グラフィックスの規則ではありません。 しかし、影も非常にぼやけて、ほとんどのドロップ シャドウよりも装飾的に見えるので、まったく間違ってはいないようです。

照明効果

SKImageFilter クラスでは、名前とパラメーターが似ている 6 つのメソッドが定義されます。ここでは、増す複雑さの順に一覧表示されています。

これらのメソッドでは、3 次元表面に対するさまざまな種類の光の効果を模倣する画像フィルターが作成されます。 結果の画像フィルターでは、まるで 3D 空間に存在するかのように 2 次元オブジェクトが照らされています。これにより、オブジェクトの凹凸やスペキュラー ハイライトを示すことができます。

Distant 光メソッドでは、光が遠くから来ていることが想定されます。 オブジェクトを照らす目的で、光は、地球の小さな領域の太陽のように、3D 空間で 1 つの一貫した方向を指していると見なされます。 Point 光メソッドでは、すべての方向に光を放出する 3D 空間に配置された電球を模倣します。 Spot 光には、懐中電灯のように、位置と方向の両方があります。

3D 空間の位置と方向はどちらも SKPoint3 構造体の値で指定されます。これは SKPoint に似ていますが、XYZ という名前の 3 つのプロパティが使用されます。

これらのメソッドのパラメーターの数と複雑さは、それらの実験を困難にします。 作業を開始するために、[遠くの光の実験] ページで、CreateDistantLightDiffuse メソッドのパラメーターを試すことができます。

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

このページでは、最後の 2 つの省略可能なパラメーターは使用されません。

XAML ファイル内の 3 つの Slider ビューを使用すると、SKPoint3 値の Z 座標、surfaceScale パラメーター、および kd パラメーターを選択できます。これは、API ドキュメントで "拡散照明定数" として定義されています。

<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>

分離コード ファイルでは、これら 3 つの値が取得され、それらを使用してテキスト文字列を表示する画像フィルターが作成されます。

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);
        }
    }
}

SKImageFilter.CreateDistantLitDiffuse の最初の引数は、光の方向です。 正の X 座標と Y 座標は、光が右と下を指していることを示します。 正の Z 座標は画面内を指します。 XAML ファイルを使用すると、負の Z 値を選択できますが、これは何が起こっているかを確認できる場合に限られます。概念的には、負の Z 座標を指定すると、光が画面の外を指します。 小さな負の値以外では、照明効果は機能しなくなります。

surfaceScale 引数は –1 から 1 の範囲にすることができます (大きい値または小さい値はそれ以上の効果はありません)。これらは、キャンバス表面からのグラフィカル オブジェクト (この場合はテキスト文字列) の変位を示す Z 軸の相対値です。 負の値を使用してキャンバス表面の上にテキスト文字列を上げ、正の値を使用してキャンバスに押し下げます。

lightConstant 値は正である必要があります (プログラムでは負の値を使用できるため、効果が機能しなくなることを確認できます)。値を大きくすると、光が強くなります。

これらの要因のバランスを取って、surfaceScale が負の場合はエンボス効果を得ることができ (iOS や Android のスクリーンショットのように)、surfaceScale が正の場合は、右側の UWP スクリーンショットのようにエッチング効果が得られます。

遠方光実験

Android のスクリーンショットの Z 値は 0 です。これは、光が単に下と右を指していることを意味します。 背景は照らされず、テキスト文字列の表面も照らされません。 光はテキストの端にのみ効果を与え、非常に微妙な効果となっています。

エンボスおよびエッチング テキストに対する別のアプローチは、「平行移動変換」の記事に示されているものです。テキスト文字列は、互いにわずかにオフセットされた異なる色で 2 回表示されます。