Поделиться через


Фильтры изображений SkiaSharp

Фильтры изображений — это эффекты, которые работают со всеми битами цветов пикселей, составляющих изображение. Они являются более универсальными, чем фильтры маски, которые работают только на альфа-канале, как описано в статье Фильтры маски SkiaSharp. Чтобы использовать фильтр изображений, задайте ImageFilter свойство SKPaint объекта типа SKImageFilter , созданного путем вызова одного из статических методов класса.

Лучший способ ознакомиться с фильтрами маски — экспериментировать с этими статическими методами. Фильтр маски можно использовать для размытия всего растрового изображения:

Пример размытия

В этой статье также показано использование фильтра изображений для создания тени капли, а также для эффектов гравюры и гравюры.

Размытие векторной графики и растровых изображений

Эффект размытия, созданный SKImageFilter.CreateBlur статическим методом, имеет значительное преимущество по сравнению с методами размытия в SKMaskFilter классе: фильтр изображений может размытие всего растрового изображения. Метод имеет следующий синтаксис:

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

Метод имеет два значения сигмы — первый для степени размытия в горизонтальном направлении и второй для вертикального направления. Можно каскадные фильтры изображений, указав другой фильтр изображений в качестве дополнительного третьего аргумента. Можно также указать прямоугольник обрезки.

Страница "Эксперимент с размытием изображений" в примере содержит два 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>

Файл программной части использует два Slider значения для вызова SKImageFilter.CreateBlur объекта, используемого SKPaint для отображения текста и растрового изображения:

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

На трех снимках экрана показаны различные параметры sigmaX и sigmaY параметры:

Эксперимент размытия изображений

Чтобы обеспечить согласованность размытия между различными размерами и разрешениями дисплея, задайте sigmaX и sigmaY значения, пропорциональные размеру отрисованного пикселя изображения, к которому применяется размытие.

Тень

Статический SKImageFilter.CreateDropShadowSKImageFilter метод создает объект для тени падения:

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, и все, что вы рисуете с этим объектом, будет иметь за ним тень падения.

dy Параметры dx указывают горизонтальные и вертикальные смещения тени в пикселях из графического объекта. Соглашение в 2D-графике заключается в том, чтобы предположить источник света, поступающий из верхнего левого угла, что означает, что оба этих аргумента должны быть положительными для размещения тени ниже и справа от графического объекта.

sigmaY Параметры sigmaX — это размытые факторы для тени падения.

Параметр color — это цвет тени падения. Это SKColor значение может включать прозрачность. Одним из возможных вариантов является значение SKColors.Black.WithAlpha(0x80) цвета для темного фона любого цвета.

Последние два параметра являются необязательными.

Программа Drop Shadow Experiment позволяет экспериментировать со значениями dx, dysigmaXа также sigmaY отображать текстовую строку с тенью. Файл XAML создает экземпляры четырех 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 определяет шесть методов, которые имеют аналогичные имена и параметры, перечисленные здесь в порядке повышения сложности:

Эти методы создают фильтры изображений, которые имитируют эффект различных видов света на трехмерных поверхностях. Результирующий фильтр изображений освещает двухмерные объекты, как если бы они существовали в трехмерном пространстве, что может привести к отображению этих объектов с повышенными или отступами или с зрительным выделением.

Методы Distant света предполагают, что свет исходит от далекого расстояния. Для освещения объектов предполагается, что свет указывает на одно согласованное направление в трехмерном пространстве, как Солнце на небольшой площади Земли. Методы Point света имитируют лампочку, расположенную в трехмерном пространстве, который выдает свет во всех направлениях. Свет Spot имеет как позицию, так и направление, как фонарик.

Расположения и направления в трехмерном пространстве указываются со значениями SKPoint3 структуры, которая похожа на SKPoint три свойства с именем X, Yи Z.

Количество и сложность параметров для этих методов затрудняют экспериментирование. Чтобы приступить к работе, страница "Удаленный световый эксперимент" позволяет экспериментировать с параметрами методаCreateDistantLightDiffuse:

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

Страница не использует последние два необязательных параметра.

Три Slider представления в XAML-файле позволяют выбрать Z координату SKPoint3 значения, 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>

Файл программной части получает эти три значения и использует их для создания фильтра изображений для отображения текстовой строки:

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 должно быть положительным. (Программа позволяет отрицательным значениям, чтобы увидеть, что они вызывают эффект для остановки работы.) Более высокие значения вызывают более интенсивный свет.

Эти факторы можно сбалансировать для получения эффектов, когда отрицательный (как и с экранами с iOS и Android) и выгравированный эффект surfaceScale , когда surfaceScale положительный, как и с снимок экрана UWP справа:

Удаленный эксперимент света

Снимок экрана Android имеет значение Z 0, что означает, что свет указывает только вниз и справа. Фон не освещается, а поверхность текстовой строки не освещается. Свет влияет только на край текста для очень тонкого эффекта.

Альтернативный подход к выгравированному и выгравированному тексту был показан в статье Преобразование перевода: текстовая строка отображается дважды с различными цветами, которые немного смещаются друг от друга.