Режимы смешения Porter-Duff

Download Sample Скачайте пример

Режимы смешивания Porter-Duff называются Томасом Портером и Томом Даффом, который разработал алгебру компостирования во время работы в Lucasfilm. Их газета Compositing Digital Images была опубликована в выпуске 1984 года о компьютерной графике, страницах 253–259. Эти режимы смешивания важны для создания, который собирает различные изображения в составную сцену:

Porter-Duff Sample

Основные понятия Porter-Duff

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

Porter-Duff Destination

Эта область называется назначениемили иногда фоном или фоном.

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

Porter-Duff Source

Это называется источником или иногда передним планом.

При отображении источника в назначении вот что вы ожидаете:

Porter-Duff Source Over

Прозрачные пиксели источника позволяют фону отображаться через, в то время как размытые исходные пиксели скрывают фон. Это обычный случай, и он называется в SkiaSharp SKBlendMode.SrcOver. Это значение является параметром BlendMode по умолчанию свойства при первом создании экземпляра SKPaint объекта.

Однако можно указать другой режим смешения для другого эффекта. Если указать SKBlendMode.DstOver, то в области, в которой пересекается источник и назначение, назначение отображается вместо источника:

Porter-Duff Destination Over

В режиме SKBlendMode.DstIn смешивания отображается только область, в которой место назначения и источник пересекаются с помощью цвета назначения:

Porter-Duff Destination In

Режим SKBlendMode.Xor смешивания (монопольный ИЛИ) не приводит к отображению двух областей:

Porter-Duff Exclusive Or

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

Porter-Duff

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

Общее количество комбинаций — 2 (для верхнего левого) раза 2 (для нижнего правого) раза 3 (для центра) или 12. Это 12 базовых режимов создания портера-даффа.

К концу создания цифровых изображений (страница 256), Портер и Дафф добавляют 13-й режим плюс (соответствующий член SkiaSharp SKBlendMode.Plus и режим W3C Lighter (который не следует путать с режимом W3C Lighten.) Этот Plus режим добавляет цвета назначения и источника, процесс, который будет подробно описан в ближайшее время.

Skia добавляет 14-й режим Modulate , который очень похож на Plus то, что назначение и исходные цвета умножаются. Его можно рассматривать как дополнительный режим смешивания Porter-Duff.

Ниже приведены 14 режимов Porter-Duff, как определено в SkiaSharp. В таблице показано, как они цветят каждую из трех непустых областей на схеме выше:

Режим Назначение Пересечения Оригинал
Clear
Src Источник X
Dst X Назначение
SrcOver X Источник X
DstOver X Назначение X
SrcIn Источник
DstIn Назначение
SrcOut X
DstOut X
SrcATop X Источник
DstATop Назначение X
Xor X X
Plus X Sum X
Modulate Продукт

Эти режимы смешивания симметричные. Исходный и целевой параметры можно обменять, и все режимы по-прежнему доступны.

Соглашение об именовании режимов соответствует нескольким простым правилам:

  • Src или Dst само по себе означает, что видимы только исходные или конечные пиксели.
  • Суффикс Over указывает, что видно в пересечении. Исходный или целевой объект рисуется "поверх" другого.
  • Суффикс в суффиксе означает, что только пересечение цветом. Выходные данные ограничены только частью источника или назначения, которая находится в другой.
  • Суффикс Out означает, что пересечение не окрашено. Выходные данные являются только частью исходного или целевого объекта, который является "вне" пересечения.
  • Суффикс ATop — это объединение in and Out. Он включает область, в которой источник или место назначения находится на вершине другого.

Обратите внимание на разницу с режимами Plus и Modulate режимами. Эти режимы выполняют другой тип вычисления на исходных и целевых пикселях. Они подробно описаны в ближайшее время.

На странице "Портер-Дафф Сетка " отображаются все 14 режимов на одном экране в виде сетки. Каждый режим — это отдельный экземпляр SKCanvasView. По этой причине класс является производным от SKCanvasView именованного PorterDuffCanvasView. Статический конструктор создает два растровых изображения одного размера, один с коричневым прямоугольником в левой верхней области, а другой с голубым прямоугольником:

class PorterDuffCanvasView : SKCanvasView
{
    static SKBitmap srcBitmap, dstBitmap;

    static PorterDuffCanvasView()
    {
        dstBitmap = new SKBitmap(300, 300);
        srcBitmap = new SKBitmap(300, 300);

        using (SKPaint paint = new SKPaint())
        {
            using (SKCanvas canvas = new SKCanvas(dstBitmap))
            {
                canvas.Clear();
                paint.Color = new SKColor(0xC0, 0x80, 0x00);
                canvas.DrawRect(new SKRect(0, 0, 200, 200), paint);
            }
            using (SKCanvas canvas = new SKCanvas(srcBitmap))
            {
                canvas.Clear();
                paint.Color = new SKColor(0x00, 0x80, 0xC0);
                canvas.DrawRect(new SKRect(100, 100, 300, 300), paint);
            }
        }
    }
    ···
}

Конструктор экземпляра имеет параметр типа SKBlendMode. Он сохраняет этот параметр в поле.

class PorterDuffCanvasView : SKCanvasView
{
    ···
    SKBlendMode blendMode;

    public PorterDuffCanvasView(SKBlendMode blendMode)
    {
        this.blendMode = blendMode;
    }

    protected override void OnPaintSurface(SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Find largest square that fits
        float rectSize = Math.Min(info.Width, info.Height);
        float x = (info.Width - rectSize) / 2;
        float y = (info.Height - rectSize) / 2;
        SKRect rect = new SKRect(x, y, x + rectSize, y + rectSize);

        // Draw destination bitmap
        canvas.DrawBitmap(dstBitmap, rect);

        // Draw source bitmap
        using (SKPaint paint = new SKPaint())
        {
            paint.BlendMode = blendMode;
            canvas.DrawBitmap(srcBitmap, rect, paint);
        }

        // Draw outline
        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Stroke;
            paint.Color = SKColors.Black;
            paint.StrokeWidth = 2;
            rect.Inflate(-1, -1);
            canvas.DrawRect(rect, paint);
        }
    }
}

Переопределение OnPaintSurface рисует два растровых изображения. Первый рисуется обычно:

canvas.DrawBitmap(dstBitmap, rect);

Второй рисуется с SKPaint объектом, в котором BlendMode свойство задано аргументом конструктора:

using (SKPaint paint = new SKPaint())
{
    paint.BlendMode = blendMode;
    canvas.DrawBitmap(srcBitmap, rect, paint);
}

Оставшаяся часть OnPaintSurface переопределения рисует прямоугольник вокруг растрового изображения, чтобы указать их размеры.

Класс PorterDuffGridPage создает четырнадцать экземпляров PorterDurffCanvasView, по одному для каждого члена массива blendModes . Порядок SKBlendModes элементов в массиве немного отличается от таблицы, чтобы разместить аналогичные режимы рядом друг с другом. 14 экземпляров PorterDuffCanvasView организованы вместе с метками в :Grid

public class PorterDuffGridPage : ContentPage
{
    public PorterDuffGridPage()
    {
        Title = "Porter-Duff Grid";

        SKBlendMode[] blendModes =
        {
            SKBlendMode.Src, SKBlendMode.Dst, SKBlendMode.SrcOver, SKBlendMode.DstOver,
            SKBlendMode.SrcIn, SKBlendMode.DstIn, SKBlendMode.SrcOut, SKBlendMode.DstOut,
            SKBlendMode.SrcATop, SKBlendMode.DstATop, SKBlendMode.Xor, SKBlendMode.Plus,
            SKBlendMode.Modulate, SKBlendMode.Clear
        };

        Grid grid = new Grid
        {
            Margin = new Thickness(5)
        };

        for (int row = 0; row < 4; row++)
        {
            grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
            grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star });
        }

        for (int col = 0; col < 3; col++)
        {
            grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });
        }

        for (int i = 0; i < blendModes.Length; i++)
        {
            SKBlendMode blendMode = blendModes[i];
            int row = 2 * (i / 4);
            int col = i % 4;

            Label label = new Label
            {
                Text = blendMode.ToString(),
                HorizontalTextAlignment = TextAlignment.Center
            };
            Grid.SetRow(label, row);
            Grid.SetColumn(label, col);
            grid.Children.Add(label);

            PorterDuffCanvasView canvasView = new PorterDuffCanvasView(blendMode);

            Grid.SetRow(canvasView, row + 1);
            Grid.SetColumn(canvasView, col);
            grid.Children.Add(canvasView);
        }

        Content = grid;
    }
}

Ниже приведен результат:

Porter-Duff Grid

Вы хотите убедить себя в том, что прозрачность имеет решающее значение для правильного функционирования режимов смешивания Porter-Duff. Класс PorterDuffCanvasView содержит в общей сложности три вызова Canvas.Clear метода. Все из них используют метод без параметров, который задает для всех пикселей прозрачный:

canvas.Clear();

Попробуйте изменить любой из этих вызовов, чтобы пиксели были заданы как непрозрачные белые:

canvas.Clear(SKColors.White);

После этого изменения некоторые режимы смешивания, кажется, будут работать, но другие не будут. Если для исходного растрового изображения задано значение "белый", режим не работает, так как в исходном растровом изображении нет прозрачных пикселей, SrcOver чтобы позволить целевому объекту отображаться. Если для фона целевого растрового изображения или холста задано белое значение, то DstOver не работает, так как назначение не имеет прозрачных пикселей.

Может возникнуть соблазн заменить растровые изображения на странице Porter-Duff Grid более простыми DrawRect вызовами. Это будет работать для прямоугольника назначения, но не для исходного прямоугольника. Исходный прямоугольник должен охватывать больше, чем просто сине-цветную область. Исходный прямоугольник должен содержать прозрачную область, соответствующую цветной области назначения. Только тогда эти режимы смешивания будут работать.

Использование матов с Портером-Дафф

На странице "Сборка кирпича стены" показан пример классической задачи компостирования : рисунок должен быть собран из нескольких частей, включая растровое изображение с фоном, который необходимо устранить. Ниже приведено SeatedMonkey.jpg растровое изображение с проблемным фоном:

Seated Monkey

При подготовке к составлению был создан соответствующий матовый рисунок, который является другим растровым изображением, которое черное, где изображение должно отображаться и прозрачно в противном случае. Этот файл называется SeatedMonkeyMatte.png и является одним из ресурсов в папке мультимедиа в примере SkiaSharpFormsDemos:

Seated Monkey Matte

Это не экспертно созданное матовое. Оптимально матовый элемент должен включать частично прозрачные пиксели вокруг края черных пикселей, и этот матовый не делает.

XAML-файл для страницы "Создание кирпичной стены " создает экземпляр и SKCanvasView создает Button экземпляр пользователя через процесс создания окончательного изображения:

<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.BrickWallCompositingPage"
             Title="Brick-Wall Compositing">

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

        <Button Text="Show sitting monkey"
                HorizontalOptions="Center"
                Margin="0, 10"
                Clicked="OnButtonClicked" />

    </StackLayout>
</ContentPage>

Файл программной части загружает два растровых изображения, необходимых для него, и обрабатывает Clicked событие Buttonобъекта. Для каждого Button щелчка поле увеличивается, step а для Buttonкаждого щелчка задано новое Text свойство. Когда step достигает 5, он возвращается в значение 0:

public partial class BrickWallCompositingPage : ContentPage
{
    SKBitmap monkeyBitmap = BitmapExtensions.LoadBitmapResource(
        typeof(BrickWallCompositingPage), 
        "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");

    SKBitmap matteBitmap = BitmapExtensions.LoadBitmapResource(
        typeof(BrickWallCompositingPage), 
        "SkiaSharpFormsDemos.Media.SeatedMonkeyMatte.png");

    int step = 0;

    public BrickWallCompositingPage ()
    {
        InitializeComponent ();
    }

    void OnButtonClicked(object sender, EventArgs args)
    {
        Button btn = (Button)sender;
        step = (step + 1) % 5;

        switch (step)
        {
            case 0: btn.Text = "Show sitting monkey"; break;
            case 1: btn.Text = "Draw matte with DstIn"; break;
            case 2: btn.Text = "Draw sidewalk with DstOver"; break;
            case 3: btn.Text = "Draw brick wall with DstOver"; break;
            case 4: btn.Text = "Reset"; break;
        }

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

        canvas.Clear();
        ···
    }
}

При первом запуске программы ничего не отображается, кроме Button:

Brick-Wall Compositing Step 0

При нажатии step клавиши Button один раз приращение к 1, а PaintSurface обработчик теперь отображает SeatedMonkey.jpg:

public partial class BrickWallCompositingPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        float x = (info.Width - monkeyBitmap.Width) / 2;
        float y = info.Height - monkeyBitmap.Height;

        // Draw monkey bitmap
        if (step >= 1)
        {
            canvas.DrawBitmap(monkeyBitmap, x, y);
        }
        ···
    }
}

SKPaint Нет объекта, поэтому режим смешивания отсутствует. Растровое изображение отображается в нижней части экрана:

Brick-Wall Compositing Step 1

Нажмите еще раз и step приращение Button к 2. Это важный шаг отображения файла SeatedMonkeyMatte.png :

public partial class BrickWallCompositingPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        // Draw matte to exclude monkey's surroundings
        if (step >= 2)
        {
            using (SKPaint paint = new SKPaint())
            {
                paint.BlendMode = SKBlendMode.DstIn;
                canvas.DrawBitmap(matteBitmap, x, y, paint);
            }
        }
        ···
    }
}

Режим смешивания — это SKBlendMode.DstInозначает, что назначение будет сохранено в областях, соответствующих непрозрачными областям источника. Оставшаяся часть прямоугольника назначения, соответствующего исходной растровой карте, становится прозрачной:

Brick-Wall Compositing Step 2

Фон был удален.

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

public partial class BrickWallCompositingPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        const float sidewalkHeight = 80;
        SKRect rect = new SKRect(info.Rect.Left, info.Rect.Bottom - sidewalkHeight,
                                 info.Rect.Right, info.Rect.Bottom);

        // Draw gravel sidewalk for monkey to sit on
        if (step >= 3)
        {
            using (SKPaint paint = new SKPaint())
            {
                paint.Shader = SKShader.CreateCompose(
                                    SKShader.CreateColor(SKColors.SandyBrown),
                                    SKShader.CreatePerlinNoiseTurbulence(0.1f, 0.3f, 1, 9));

                paint.BlendMode = SKBlendMode.DstOver;
                canvas.DrawRect(rect, paint);
            }
        }
        ···
    }
}

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

Brick-Wall Compositing Step 3

Последний шаг заключается в добавлении кирпичной стены. Программа использует плитку битового рисунка кирпичной стены, доступную в качестве статического свойства BrickWallTile в AlgorithmicBrickWallPage классе. Преобразование перевода добавляется в SKShader.CreateBitmap вызов для перемещения плиток, чтобы нижняя строка была полной плиткой:

public partial class BrickWallCompositingPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        // Draw bitmap tiled brick wall behind monkey
        if (step >= 4)
        {
            using (SKPaint paint = new SKPaint())
            {
                SKBitmap bitmap = AlgorithmicBrickWallPage.BrickWallTile;
                float yAdjust = (info.Height - sidewalkHeight) % bitmap.Height;

                paint.Shader = SKShader.CreateBitmap(bitmap,
                                                     SKShaderTileMode.Repeat,
                                                     SKShaderTileMode.Repeat,
                                                     SKMatrix.MakeTranslation(0, yAdjust));
                paint.BlendMode = SKBlendMode.DstOver;
                canvas.DrawRect(info.Rect, paint);
            }
        }
    }
}

Для удобства DrawRect вызов отображает этот шейдер по всему холсту, но DstOver режим ограничивает выходные данные только областью холста, который по-прежнему прозрачный:

Brick-Wall Compositing Step 4

Очевидно, есть и другие способы создания этой сцены. Его можно создать, начиная с фона и прогрессируя на переднем плане. Но использование режимов смешения обеспечивает большую гибкость. В частности, использование матового цвета позволяет исключить фон растрового изображения из составной сцены.

Как вы узнали в статье Обрезка с путями и регионами, SKCanvas класс определяет три типа вырезки, соответствующие ClipRectClipPathметодам и ClipRegion методам. Режимы смешивания Porter-Duff добавляют другой тип вырезки, что позволяет ограничить изображение любым способом рисования, включая растровые изображения. Матовый элемент, используемый в Brick-Wall Compositing, по сути определяет область вырезки .

Прозрачность градиента и переходы

Примеры режимов смешения Porter-Duff, показанные ранее в этой статье, содержат все изображения, состоящие из непрозрачных пикселей и прозрачных пикселей, но не частично прозрачных пикселей. Функции в режиме смешивания также определены для этих пикселей. В следующей таблице представлено более формальное определение режимов смешивания Porter-Duff, использующих нотацию, найденную в справочнике skia SkBlendMode. (Потому что Справочник по SkBlendMode — это ссылка на Skia, используется синтаксис C++.)

Концептуально красные, зеленые, синие и альфа-компоненты каждого пикселя преобразуются из байтов в числа с плавающей запятой в диапазоне от 0 до 1. Для альфа-канала 0 полностью прозрачный, и 1 полностью непрозрачны

Нотация в приведенной ниже таблице использует следующие сокращены:

  • Da — это целевой альфа-канал
  • Dc — это целевой цвет RGB
  • Sa — исходный альфа-канал
  • Sc — это исходный цвет RGB

Цвета RGB предварительно умножаются на альфа-значение. Например, если Sc представляет чистый красный цвет, но Sa 0x80, цвет RGB имеет значение (0x80, 0, 0). Если значение Sa равно 0, все компоненты RGB также равны нулю.

Результат показан в скобках с альфа-каналом и цветом RGB, разделенным запятой: [альфа, цвет]. Для цвета вычисления выполняются отдельно для красных, зеленых и синих компонентов:

Режим Операция
Clear [0, 0]
Src [Sa, Sc]
Dst [Da, Dc]
SrcOver [Sa + Da· (1 – Sa), Sc + Dc· (1 – Са)
DstOver [Da + Sa· (1 – Да), Dc + Sc· (1 – Да)
SrcIn [Sa· Da, Sc · Да]
DstIn [Da· Sa, Dc·Sa]
SrcOut [Sa· (1 – Да), Sc· (1 – Да)]
DstOut [Da· (1 – Sa), Dc· (1 – Са)]
SrcATop [Da, Sc· Da + Dc· (1 – Са)]
DstATop [Sa, Dc·Sa + Sc· (1 – Да)]
Xor [Sa + Da – 2· Sa· Da, Sc · (1 – Да) + Dc· (1 – Са)]
Plus [Sa + Da, Sc + Dc]
Modulate [Sa· Da, Sc · Dc]

Эти операции проще анализировать, когда Da и Sa имеют значение 0 или 1. Например, для режима по умолчанию SrcOver , если значение Sa равно 0, то Sc также равно 0, а результатом является [Da, Dc], целевой альфа и цвет. Если sa равен 1, результатом является [Sa, Sc], исходный альфа-и цвет, или [1, Sc].

Режимы Plus и Modulate режимы немного отличаются от других в том, что новые цвета могут привести к сочетанию источника и назначения. Режим Plus можно интерпретировать как с компонентами байтов, так и с компонентами с плавающей запятой. На странице "Портер-Дафф Сетка", показанной ранее, цвет назначения — (0xC0, 0x80, 0x00), а исходный цвет — (0x00, 0x80, 0xC0). Каждая пара компонентов добавляется, но сумма зажата на 0xFF. Результатом является цвет (0xC0, 0xFF, 0xC0). Это цвет, показанный на пересечении.

Modulate Для режима значения RGB необходимо преобразовать в плавающую точку. Целевой цвет — (0,75, 0,5, 0), а источник — (0, 0,5, 0,75). Компоненты RGB умножаются вместе, и результатом является (0, 0,25, 0). Это цвет, показанный на пересечении на странице "Портер-Дафф Сетка " для этого режима.

Страница прозрачности Porter-Duff позволяет изучить, как режимы смешивания Porter-Duff работают с графическими объектами, которые частично прозрачны. XAML-файл содержит Picker режимы Porter-Duff:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp;assembly=SkiaSharp"
             xmlns:skiaviews="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Effects.PorterDuffTransparencyPage"
             Title="Porter-Duff Transparency">

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

        <Picker x:Name="blendModePicker"
                Title="Blend Mode"
                Margin="10"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type skia:SKBlendMode}">
                    <x:Static Member="skia:SKBlendMode.Clear" />
                    <x:Static Member="skia:SKBlendMode.Src" />
                    <x:Static Member="skia:SKBlendMode.Dst" />
                    <x:Static Member="skia:SKBlendMode.SrcOver" />
                    <x:Static Member="skia:SKBlendMode.DstOver" />
                    <x:Static Member="skia:SKBlendMode.SrcIn" />
                    <x:Static Member="skia:SKBlendMode.DstIn" />
                    <x:Static Member="skia:SKBlendMode.SrcOut" />
                    <x:Static Member="skia:SKBlendMode.DstOut" />
                    <x:Static Member="skia:SKBlendMode.SrcATop" />
                    <x:Static Member="skia:SKBlendMode.DstATop" />
                    <x:Static Member="skia:SKBlendMode.Xor" />
                    <x:Static Member="skia:SKBlendMode.Plus" />
                    <x:Static Member="skia:SKBlendMode.Modulate" />
                </x:Array>
            </Picker.ItemsSource>

            <Picker.SelectedIndex>
                3
            </Picker.SelectedIndex>
        </Picker>
    </StackLayout>
</ContentPage>

Файл программной части заполняет два прямоугольника одного размера с помощью линейного градиента. Градиент назначения — от верхнего справа до нижнего левого. Он коричневый в правом верхнем углу, но затем к центру начинает исчезать прозрачно, и прозрачный в левом нижнем углу.

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

public partial class PorterDuffTransparencyPage : ContentPage
{
    public PorterDuffTransparencyPage()
    {
        InitializeComponent();
    }

    void OnPickerSelectedIndexChanged(object sender, EventArgs args)
    {
        canvasView.InvalidateSurface();
    }

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

        canvas.Clear();

        // Make square display rectangle smaller than canvas
        float size = 0.9f * Math.Min(info.Width, info.Height);
        float x = (info.Width - size) / 2;
        float y = (info.Height - size) / 2;
        SKRect rect = new SKRect(x, y, x + size, y + size);

        using (SKPaint paint = new SKPaint())
        {
            // Draw destination
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(rect.Right, rect.Top),
                                new SKPoint(rect.Left, rect.Bottom),
                                new SKColor[] { new SKColor(0xC0, 0x80, 0x00),
                                                new SKColor(0xC0, 0x80, 0x00, 0) },
                                new float[] { 0.4f, 0.6f },
                                SKShaderTileMode.Clamp);

            canvas.DrawRect(rect, paint);

            // Draw source
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(rect.Left, rect.Top),
                                new SKPoint(rect.Right, rect.Bottom),
                                new SKColor[] { new SKColor(0x00, 0x80, 0xC0), 
                                                new SKColor(0x00, 0x80, 0xC0, 0) },
                                new float[] { 0.4f, 0.6f },
                                SKShaderTileMode.Clamp);

            // Get the blend mode from the picker
            paint.BlendMode = blendModePicker.SelectedIndex == -1 ? 0 :
                                    (SKBlendMode)blendModePicker.SelectedItem;

            canvas.DrawRect(rect, paint);

            // Stroke surrounding rectangle
            paint.Shader = null;
            paint.BlendMode = SKBlendMode.SrcOver;
            paint.Style = SKPaintStyle.Stroke;
            paint.Color = SKColors.Black;
            paint.StrokeWidth = 3;
            canvas.DrawRect(rect, paint);
        }
    }
}

Эта программа демонстрирует, что режимы смешения Porter-Duff можно использовать с графическими объектами, кроме растровых изображений. Однако источник должен включать прозрачную область. Это так, потому что градиент заполняет прямоугольник, но часть градиента является прозрачной.

Ниже приведены три примера:

Porter-Duff Transparency

Конфигурация назначения и источника очень похожа на схемы, показанные на странице 255 исходного документа Porter-Duff Compositing Digital Images , но на этой странице показано, что режимы смешения хорошо ведут себя для областей частичной прозрачности.

Прозрачные градиенты можно использовать для некоторых различных эффектов. Одна из возможных возможностей заключается в маскировке, которая похожа на технику, показанную в радиальных градиентах для маскирования раздела страницы циклических градиентов SkiaSharp. Большая часть страницы "Маски создания" аналогична этой предыдущей программе. Он загружает ресурс растрового изображения и определяет прямоугольник, в котором он будет отображаться. Радиальный градиент создается на основе предопределенного центра и радиуса:

public class CompositingMaskPage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
        typeof(CompositingMaskPage),
        "SkiaSharpFormsDemos.Media.MountainClimbers.jpg");

    static readonly SKPoint CENTER = new SKPoint(180, 300);
    static readonly float RADIUS = 120;

    public CompositingMaskPage ()
    {
        Title = "Compositing Mask";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

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

        canvas.Clear();

        // Find rectangle to display bitmap
        float scale = Math.Min((float)info.Width / bitmap.Width,
                               (float)info.Height / bitmap.Height);

        SKRect rect = SKRect.Create(scale * bitmap.Width, scale * bitmap.Height);

        float x = (info.Width - rect.Width) / 2;
        float y = (info.Height - rect.Height) / 2;
        rect.Offset(x, y);

        // Display bitmap in rectangle
        canvas.DrawBitmap(bitmap, rect);

        // Adjust center and radius for scaled and offset bitmap
        SKPoint center = new SKPoint(scale * CENTER.X + x,
                                        scale * CENTER.Y + y);
        float radius = scale * RADIUS;

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateRadialGradient(
                                center,
                                radius,
                                new SKColor[] { SKColors.Black,
                                                SKColors.Transparent },
                                new float[] { 0.6f, 1 },
                                SKShaderTileMode.Clamp);

            paint.BlendMode = SKBlendMode.DstIn;

            // Display rectangle using that gradient and blend mode
            canvas.DrawRect(rect, paint);
        }

        canvas.DrawColor(SKColors.Pink, SKBlendMode.DstOver);
    }
}

Разница с этой программой заключается в том, что градиент начинается с черного в центре и заканчивается прозрачностью. Он отображается на растровом рисунке с режимом DstInсмешения, который показывает назначение только в областях источника, которые не прозрачны.

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

canvas.DrawColor(SKColors.Pink, SKBlendMode.DstOver);

Все прозрачные области холста цветные розовый:

Compositing Mask

Можно также использовать режимы Porter-Duff и частично прозрачные градиенты для перехода с одного изображения на другой. Страница "Переходы градиента" включает Slider уровень хода выполнения перехода от 0 до 1, а Picker также тип перехода, который требуется выполнить:

<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.GradientTransitionsPage"
             Title="Gradient Transitions">
    
    <StackLayout>
        <skia:SKCanvasView x:Name="canvasView"
                           VerticalOptions="FillAndExpand"
                           PaintSurface="OnCanvasViewPaintSurface" />

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

        <Label Text="{Binding Source={x:Reference progressSlider},
                              Path=Value,
                              StringFormat='Progress = {0:F2}'}"
               HorizontalTextAlignment="Center" />

        <Picker x:Name="transitionPicker" 
                Title="Transition" 
                Margin="10"
                SelectedIndexChanged="OnPickerSelectedIndexChanged" />
        
    </StackLayout>
</ContentPage>

Файл программной части загружает два растровых ресурса для демонстрации перехода. Это те же два изображения, которые используются на странице "Растворение растрового изображения" ранее в этой статье. Код также определяет перечисление с тремя элементами, соответствующими трем типам градиентов — линейных, радиальных и сверток. Эти значения загружаются в Picker:

public partial class GradientTransitionsPage : ContentPage
{
    SKBitmap bitmap1 = BitmapExtensions.LoadBitmapResource(
        typeof(GradientTransitionsPage),
        "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");

    SKBitmap bitmap2 = BitmapExtensions.LoadBitmapResource(
        typeof(GradientTransitionsPage),
        "SkiaSharpFormsDemos.Media.FacePalm.jpg");

    enum TransitionMode
    {
        Linear,
        Radial,
        Sweep
    };

    public GradientTransitionsPage ()
    {
        InitializeComponent ();

        foreach (TransitionMode mode in Enum.GetValues(typeof(TransitionMode)))
        {
            transitionPicker.Items.Add(mode.ToString());
        }

        transitionPicker.SelectedIndex = 0;
    }

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

    void OnPickerSelectedIndexChanged(object sender, EventArgs args)
    {
        canvasView.InvalidateSurface();
    }
    ···
}

Файл программной части создает три SKPaint объекта. Объект paint0 не использует режим смешения. Этот объект краски используется для рисования прямоугольника с градиентом, который переходит от черного к прозрачному, как указано в массиве colors . Массив positions основан на позиции, Sliderно несколько скорректирован. Slider Если значение равно минимальному или максимальному, progress значения равно 0 или 1, а одно из двух растровых изображений должно быть полностью видимым. Массив positions должен быть задан соответствующим образом для этих значений.

progress Если значение равно 0, positions массив содержит значения -0,1 и 0. SkiaSharp изменит первое значение, равное 0, что означает, что градиент черный только в 0 и прозрачный в противном случае. Если progress значение равно 0,5, массив содержит значения 0,45 и 0,55. Градиент черный от 0 до 0,45, затем переходит на прозрачный и полностью прозрачный от 0,55 до 1. Если progress значение равно 1, positions массив равен 1 и 1.1, то есть градиент черный от 0 до 1.

position Оба colors массива используются в трех методах SKShader создания градиента. Только один из этих шейдеров создается на Picker основе выбора:

public partial class GradientTransitionsPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Assume both bitmaps are square for display rectangle
        float size = Math.Min(info.Width, info.Height);
        SKRect rect = SKRect.Create(size, size);
        float x = (info.Width - size) / 2;
        float y = (info.Height - size) / 2;
        rect.Offset(x, y);

        using (SKPaint paint0 = new SKPaint())
        using (SKPaint paint1 = new SKPaint())
        using (SKPaint paint2 = new SKPaint())
        {
            SKColor[] colors = new SKColor[] { SKColors.Black,
                                               SKColors.Transparent };

            float progress = (float)progressSlider.Value;

            float[] positions = new float[]{ 1.1f * progress - 0.1f,
                                             1.1f * progress };

            switch ((TransitionMode)transitionPicker.SelectedIndex)
            {
                case TransitionMode.Linear:
                    paint0.Shader = SKShader.CreateLinearGradient(
                                        new SKPoint(rect.Left, 0),
                                        new SKPoint(rect.Right, 0),
                                        colors,
                                        positions,
                                        SKShaderTileMode.Clamp);
                    break;

                case TransitionMode.Radial:
                    paint0.Shader = SKShader.CreateRadialGradient(
                                        new SKPoint(rect.MidX, rect.MidY),
                                        (float)Math.Sqrt(Math.Pow(rect.Width / 2, 2) +
                                                         Math.Pow(rect.Height / 2, 2)),
                                        colors,
                                        positions,
                                        SKShaderTileMode.Clamp);
                    break;

                case TransitionMode.Sweep:
                    paint0.Shader = SKShader.CreateSweepGradient(
                                        new SKPoint(rect.MidX, rect.MidY),
                                        colors,
                                        positions);
                    break;
            }

            canvas.DrawRect(rect, paint0);

            paint1.BlendMode = SKBlendMode.SrcOut;
            canvas.DrawBitmap(bitmap1, rect, paint1);

            paint2.BlendMode = SKBlendMode.DstOver;
            canvas.DrawBitmap(bitmap2, rect, paint2);
        }
    }
}

Этот градиент отображается в прямоугольнике без режима смешения. После этого DrawRect на холсте просто содержится градиент от черного до прозрачного. Объем черного цвета увеличивается с более высокими Slider значениями.

В последних четырех инструкциях обработчика PaintSurface отображаются два растровых изображения. Режим смешения означает, что первая растровая SrcOut карта отображается только в прозрачных областях фона. Режим DstOver для второго растрового изображения означает, что второй растровый рисунок отображается только в тех областях, где первый растровый рисунок не отображается.

На следующих снимках экрана показаны три различных типа переходов, каждый из которых имеет отметку 50 %.

Gradient Transitions