Режимы смешения Porter-Duff
Режимы смешивания Porter-Duff называются Томасом Портером и Томом Даффом, который разработал алгебру компостирования во время работы в Lucasfilm. Их газета Compositing Digital Images была опубликована в выпуске 1984 года о компьютерной графике, страницах 253–259. Эти режимы смешивания важны для создания, который собирает различные изображения в составную сцену:
Основные понятия Porter-Duff
Предположим, что коричневый прямоугольник занимает левую и верхнюю две трети поверхности дисплея:
Эта область называется назначением или иногда фоном или фоном.
Вы хотите нарисовать следующий прямоугольник, который совпадает с размером назначения. Прямоугольник прозрачный, за исключением синей области, которая занимает правое и нижнее две трети:
Это называется источником или иногда передним планом.
При отображении источника в назначении вот что вы ожидаете:
Прозрачные пиксели источника позволяют фону отображаться через, в то время как размытые исходные пиксели скрывают фон. Это обычный случай, и он называется в SkiaSharp SKBlendMode.SrcOver
. Это значение является параметром BlendMode
по умолчанию свойства при первом создании экземпляра SKPaint
объекта.
Однако можно указать другой режим смешения для другого эффекта. Если указать SKBlendMode.DstOver
, то в области, в которой пересекается источник и назначение, назначение отображается вместо источника:
В режиме SKBlendMode.DstIn
смешивания отображается только область, в которой место назначения и источник пересекаются с помощью цвета назначения:
Режим SKBlendMode.Xor
смешивания (монопольный ИЛИ) не приводит к отображению двух областей:
Цветные целевые и исходные прямоугольники эффективно разделяют поверхность отображения на четыре уникальные области, которые можно цветить различными способами, соответствующими присутствию целевых и исходных прямоугольников:
Прямоугольники в правом верхнем и нижнем углу всегда пусты, так как назначение и источник прозрачны в этих областях. Цвет назначения занимает верхнюю левую область, чтобы область может быть цветом с помощью цвета назначения или вообще. Аналогичным образом, исходный цвет занимает правое нижнее правое пространство, чтобы область может быть окрашена с помощью исходного цвета или не вообще. Пересечение назначения и источника в середине может быть цветом назначения, цветом источника или вообще.
Общее количество комбинаций — 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. Класс PorterDuffCanvasView
содержит в общей сложности три вызова Canvas.Clear
метода. Все из них используют метод без параметров, который задает для всех пикселей прозрачный:
canvas.Clear();
Попробуйте изменить любой из этих вызовов, чтобы пиксели были заданы как непрозрачные белые:
canvas.Clear(SKColors.White);
После этого изменения некоторые режимы смешивания, кажется, будут работать, но другие не будут. Если для исходного растрового изображения задано значение "белый", режим не работает, так как в исходном растровом изображении нет прозрачных пикселей, SrcOver
чтобы позволить целевому объекту отображаться. Если для фона целевого растрового изображения или холста задано белое значение, то DstOver
не работает, так как назначение не имеет прозрачных пикселей.
Может возникнуть соблазн заменить растровые изображения на странице Porter-Duff Grid более простыми DrawRect
вызовами. Это будет работать для прямоугольника назначения, но не для исходного прямоугольника. Исходный прямоугольник должен охватывать больше, чем просто сине-цветную область. Исходный прямоугольник должен содержать прозрачную область, соответствующую цветной области назначения. Только тогда эти режимы смешивания будут работать.
Использование матов с Портером-Дафф
На странице "Сборка кирпича стены" показан пример классической задачи компостирования : рисунок должен быть собран из нескольких частей, включая растровое изображение с фоном, который необходимо устранить. Ниже приведено SeatedMonkey.jpg растровое изображение с проблемным фоном:
При подготовке к составлению был создан соответствующий матовый рисунок, который является другим растровым изображением, которое черное, где изображение должно отображаться и прозрачно в противном случае. Этот файл называется SeatedMonkeyMatte.png и является одним из ресурсов в папке мультимедиа в примере:
Это не экспертно созданное матовое. Оптимально матовый элемент должен включать частично прозрачные пиксели вокруг края черных пикселей, и этот матовый не делает.
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
:
При нажатии 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
Нет объекта, поэтому режим смешивания отсутствует. Растровое изображение отображается в нижней части экрана:
Нажмите еще раз и 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
означает, что назначение будет сохранено в областях, соответствующих непрозрачными областям источника. Оставшаяся часть прямоугольника назначения, соответствующего исходной растровой карте, становится прозрачной:
Фон был удален.
Следующий шаг заключается в рисовании прямоугольника, который напоминает тротуар, на который сидит обезьяна. Внешний вид этого тротуара основан на композиции двух шейдеров: сплошной цветовой шейдер и шейдер перлин шумов:
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
Назначение отображается только в том месте, где фон прозрачный:
Последний шаг заключается в добавлении кирпичной стены. Программа использует плитку битового рисунка кирпичной стены, доступную в качестве статического свойства 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
режим ограничивает выходные данные только областью холста, который по-прежнему прозрачный:
Очевидно, есть и другие способы создания этой сцены. Его можно создать, начиная с фона и прогрессируя на переднем плане. Но использование режимов смешения обеспечивает большую гибкость. В частности, использование матового цвета позволяет исключить фон растрового изображения из составной сцены.
Как вы узнали в статье Обрезка с путями и регионами, SKCanvas
класс определяет три типа вырезки, соответствующие ClipRect
ClipPath
методам и 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 можно использовать с графическими объектами, кроме растровых изображений. Однако источник должен включать прозрачную область. Это так, потому что градиент заполняет прямоугольник, но часть градиента является прозрачной.
Ниже приведены три примера:
Конфигурация назначения и источника очень похожа на схемы, показанные на странице 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);
Все прозрачные области холста цветные розовый:
Можно также использовать режимы 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 %.