可分离混合模式
如文章 SkiaSharp Porter-Duff 混合模式中所述,Porter-Duff 混合模式通常执行剪裁操作。 可分离混合模式与此不同。 可分离模式改变图像的各个红色、绿色和蓝色分量。 可分离混合模式可以混合颜色以证明红色、绿色和蓝色的组合确实是白色:
变亮和变暗两种方式
位图有点太暗或太亮是很常见的。 可以使用可分离混合模式来使图像变亮或变暗。 事实上,SKBlendMode
枚举中的两个可分离混合模式名为 Lighten
和 Darken
。
“变亮和变暗”页演示了这两种模式。 XAML 文件实例化两个 SKCanvasView
对象和两个 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.LightenAndDarkenPage"
Title="Lighten and Darken">
<StackLayout>
<skia:SKCanvasView x:Name="lightenCanvasView"
VerticalOptions="FillAndExpand"
PaintSurface="OnCanvasViewPaintSurface" />
<Slider x:Name="lightenSlider"
Margin="10"
ValueChanged="OnSliderValueChanged" />
<skia:SKCanvasView x:Name="darkenCanvasView"
VerticalOptions="FillAndExpand"
PaintSurface="OnCanvasViewPaintSurface" />
<Slider x:Name="darkenSlider"
Margin="10"
ValueChanged="OnSliderValueChanged" />
</StackLayout>
</ContentPage>
第一个 SKCanvasView
和 Slider
演示 SKBlendMode.Lighten
,第二对演示 SKBlendMode.Darken
。 两个 Slider
视图共享同一个 ValueChanged
处理程序,两个 SKCanvasView
共享同一个 PaintSurface
处理程序。 这两个事件处理程序都会检查哪个对象正在触发事件:
public partial class LightenAndDarkenPage : ContentPage
{
SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
typeof(SeparableBlendModesPage),
"SkiaSharpFormsDemos.Media.Banana.jpg");
public LightenAndDarkenPage ()
{
InitializeComponent ();
}
void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
{
if ((Slider)sender == lightenSlider)
{
lightenCanvasView.InvalidateSurface();
}
else
{
darkenCanvasView.InvalidateSurface();
}
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Find largest size rectangle in canvas
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
canvas.DrawBitmap(bitmap, rect);
// Display gray rectangle with blend mode
using (SKPaint paint = new SKPaint())
{
if ((SKCanvasView)sender == lightenCanvasView)
{
byte value = (byte)(255 * lightenSlider.Value);
paint.Color = new SKColor(value, value, value);
paint.BlendMode = SKBlendMode.Lighten;
}
else
{
byte value = (byte)(255 * (1 - darkenSlider.Value));
paint.Color = new SKColor(value, value, value);
paint.BlendMode = SKBlendMode.Darken;
}
canvas.DrawRect(rect, paint);
}
}
}
PaintSurface
处理程序计算适合位图的矩形。 该处理程序显示该位图,然后使用 SKPaint
对象(其 BlendMode
属性设置为 SKBlendMode.Lighten
或 SKBlendMode.Darken
)在该位图上显示一个矩形。 Color
属性是基于 Slider
的灰色阴影。 对于 Lighten
模式,颜色范围为黑色到白色,而对于 Darken
模式,颜色范围为白色到黑色。
从左到右的屏幕截图显示,随着顶部图像变亮而底部图像变暗,Slider
值越来越大:
此程序演示了使用可分离混合模式的正常方式:目标是某种图像,通常是位图。 源是使用 SKPaint
对象(其 BlendMode
属性设置为可分离混合模式)显示的矩形。 该矩形可以是纯色(如此处所示)或渐变。 透明度通常不与可分离混合模式一起使用。
当体验此程序时,你会发现这两种混合模式不会均匀地使图像变亮和变暗。 相反,Slider
似乎设置了某种阈值。 例如,当增加 Lighten
模式的 Slider
时,图像的较暗区域首先变亮,而较亮区域保持不变。
对于 Lighten
模式,如果目标像素为 RGB 颜色值 (Dr, Dg, Db),源像素为颜色 (Sr, Sg, Sb),则输出为如下所示计算出的 (Or, Og, Ob):
Or = max(Dr, Sr)
Og = max(Dg, Sg)
Ob = max(Db, Sb)
对于红色、绿色和蓝色,结果是目标与源中较大的一个。 这会产生首先照亮目标的黑暗区域的效果。
Darken
模式与此类似,只是结果是目标和源中较小的一个:
Or = min(Dr, Sr)
Og = min(Dg, Sg)
Ob = min(Db, Sb)
红色、绿色和蓝色分量分别单独处理,因此这些混合模式被称为可分离混合模式。 因此,Dc 和 Sc 缩写可用于目标颜色和源颜色,并且计算分别适用于红色、绿色和蓝色分量。
下表显示了所有可分离混合模式,并简要说明了它们的作用。 第二列显示不产生任何变化的源颜色:
混合模式 | 没有变化 | 操作 |
---|---|---|
Plus |
黑 | 通过将颜色相加来变亮:Sc + Dc |
Modulate |
白色 | 通过将颜色相乘来变暗:Sc·Dc |
Screen |
黑 | 补足乘积:Sc + Dc – Sc·Dc |
Overlay |
灰色 | HardLight 的逆 |
Darken |
白色 | 颜色最小值:min(Sc, Dc) |
Lighten |
黑 | 颜色最大值:max(Sc, Dc) |
ColorDodge |
黑 | 根据源使目标变亮 |
ColorBurn |
白色 | 根据源使目标变暗 |
HardLight |
灰色 | 类似于刺眼聚光灯的效果 |
SoftLight |
灰色 | 类似于柔光聚光灯的效果 |
Difference |
黑 | 从较亮部分减去较暗部分:Abs(Dc – Sc) |
Exclusion |
黑 | 类似于 Difference 但对比度更低 |
Multiply |
白色 | 通过将颜色相乘来变暗:Sc·Dc |
更详细的算法可以在 W3C 合成与混合级别 1 规范和 Skia SkBlendMode 参考中找到,不过这两个源中的表示法并不相同。 请记住,Plus
通常被视为 Porter-Duff 混合模式,而 Modulate
不是 W3C 规范的一部分。
如果源是透明的,则对于除 Modulate
之外的所有可分离混合模式,混合模式都不起作用。 如前所述,Modulate
混合模式在乘法中合并 alpha 通道。 否则,Modulate
与 Multiply
具有相同的效果。
请注意名为 ColorDodge
和 ColorBurn
的两种模式。 “局部遮光”和“炽烤”这两个词起源于摄影暗室实践。 放大镜通过将光线照射到底片上来制作影印件。 在没有光线的情况下,影印件是白色的。 随着更多的光线照射到影印件上并持续更长时间,影印件会变得更暗。 影印制作者经常用手或小物体阻挡部分光线落在影印件的某个部分,使该区域变亮。 这称为局部遮光。 相反,可以使用带孔的不透明材料(或用手挡住大部分光线)将更多光线引导至到特定位置,使其变暗,这称为炽烤。
局部遮光和炽烤程序与变亮和变暗非常相似。 XAML 文件的结构相同,但元素名称不同,代码隐藏文件也非常相似,但这两种混合模式的效果却截然不同:
对于较小的 Slider
值,Lighten
模式首先使暗区域变亮,而 ColorDodge
则变亮得更均匀。
图像处理应用程序通常允许将局部遮光和炽烤限制在特定区域,就像在暗室中一样。 这可以通过渐变或具有不同灰度的位图来完成。
探索可分离混合模式
在“可分离混合模式”页中,可以检查所有可分离混合模式。 它使用其中一种混合模式显示位图目标和彩色矩形源。
XAML 文件定义了一个 Picker
(用于选择混合模式)和四个滑块。 前三个滑块可让你设置源的红色、绿色和蓝色分量。 第四个滑块旨在通过设置灰色阴影来重写这些值。 各个滑块未识别,但颜色指示其功能:
<?xml version="1.0" encoding="utf-8" ?>
<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.SeparableBlendModesPage"
Title="Separable Blend Modes">
<StackLayout>
<skiaviews:SKCanvasView x:Name="canvasView"
VerticalOptions="FillAndExpand"
PaintSurface="OnCanvasViewPaintSurface" />
<Picker x:Name="blendModePicker"
Title="Blend Mode"
Margin="10, 0"
SelectedIndexChanged="OnPickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type skia:SKBlendMode}">
<x:Static Member="skia:SKBlendMode.Plus" />
<x:Static Member="skia:SKBlendMode.Modulate" />
<x:Static Member="skia:SKBlendMode.Screen" />
<x:Static Member="skia:SKBlendMode.Overlay" />
<x:Static Member="skia:SKBlendMode.Darken" />
<x:Static Member="skia:SKBlendMode.Lighten" />
<x:Static Member="skia:SKBlendMode.ColorDodge" />
<x:Static Member="skia:SKBlendMode.ColorBurn" />
<x:Static Member="skia:SKBlendMode.HardLight" />
<x:Static Member="skia:SKBlendMode.SoftLight" />
<x:Static Member="skia:SKBlendMode.Difference" />
<x:Static Member="skia:SKBlendMode.Exclusion" />
<x:Static Member="skia:SKBlendMode.Multiply" />
</x:Array>
</Picker.ItemsSource>
<Picker.SelectedIndex>
0
</Picker.SelectedIndex>
</Picker>
<Slider x:Name="redSlider"
MinimumTrackColor="Red"
MaximumTrackColor="Red"
Margin="10, 0"
ValueChanged="OnSliderValueChanged" />
<Slider x:Name="greenSlider"
MinimumTrackColor="Green"
MaximumTrackColor="Green"
Margin="10, 0"
ValueChanged="OnSliderValueChanged" />
<Slider x:Name="blueSlider"
MinimumTrackColor="Blue"
MaximumTrackColor="Blue"
Margin="10, 0"
ValueChanged="OnSliderValueChanged" />
<Slider x:Name="graySlider"
MinimumTrackColor="Gray"
MaximumTrackColor="Gray"
Margin="10, 0"
ValueChanged="OnSliderValueChanged" />
<Label x:Name="colorLabel"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentPage>
代码隐藏文件加载位图资源之一并将其绘制两次,一次在画布的上半部分,另一次在画布的下半部分:
public partial class SeparableBlendModesPage : ContentPage
{
SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
typeof(SeparableBlendModesPage),
"SkiaSharpFormsDemos.Media.Banana.jpg");
public SeparableBlendModesPage()
{
InitializeComponent();
}
void OnPickerSelectedIndexChanged(object sender, EventArgs args)
{
canvasView.InvalidateSurface();
}
void OnSliderValueChanged(object sender, ValueChangedEventArgs e)
{
if (sender == graySlider)
{
redSlider.Value = greenSlider.Value = blueSlider.Value = graySlider.Value;
}
colorLabel.Text = String.Format("Color = {0:X2} {1:X2} {2:X2}",
(byte)(255 * redSlider.Value),
(byte)(255 * greenSlider.Value),
(byte)(255 * blueSlider.Value));
canvasView.InvalidateSurface();
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Draw bitmap in top half
SKRect rect = new SKRect(0, 0, info.Width, info.Height / 2);
canvas.DrawBitmap(bitmap, rect, BitmapStretch.Uniform);
// Draw bitmap in bottom halr
rect = new SKRect(0, info.Height / 2, info.Width, info.Height);
canvas.DrawBitmap(bitmap, rect, BitmapStretch.Uniform);
// Get values from XAML controls
SKBlendMode blendMode =
(SKBlendMode)(blendModePicker.SelectedIndex == -1 ?
0 : blendModePicker.SelectedItem);
SKColor color = new SKColor((byte)(255 * redSlider.Value),
(byte)(255 * greenSlider.Value),
(byte)(255 * blueSlider.Value));
// Draw rectangle with blend mode in bottom half
using (SKPaint paint = new SKPaint())
{
paint.Color = color;
paint.BlendMode = blendMode;
canvas.DrawRect(rect, paint);
}
}
}
在 PaintSurface
处理程序的底部,使用选定的混合模式和选定的颜色在第二个位图上绘制一个矩形。 可以将底部的修改后位图与顶部的原始位图进行比较:
加法和减法原色
“原色页”绘制了三个重叠的红色、绿色和蓝色圆:
这些是加法原色。 任意两种颜色的组合会产生青色、品红色和黄色,所有三种颜色的组合会产生白色。
这三个圆是使用 SKBlendMode.Plus
模式绘制的,但你也可以使用 Screen
、Lighten
或 Difference
来达到相同的效果。 以下是程序:
public class PrimaryColorsPage : ContentPage
{
bool isSubtractive;
public PrimaryColorsPage ()
{
Title = "Primary Colors";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
// Switch between additive and subtractive primaries at tap
TapGestureRecognizer tap = new TapGestureRecognizer();
tap.Tapped += (sender, args) =>
{
isSubtractive ^= true;
canvasView.InvalidateSurface();
};
canvasView.GestureRecognizers.Add(tap);
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKPoint center = new SKPoint(info.Rect.MidX, info.Rect.MidY);
float radius = Math.Min(info.Width, info.Height) / 4;
float distance = 0.8f * radius; // from canvas center to circle center
SKPoint center1 = center +
new SKPoint(distance * (float)Math.Cos(9 * Math.PI / 6),
distance * (float)Math.Sin(9 * Math.PI / 6));
SKPoint center2 = center +
new SKPoint(distance * (float)Math.Cos(1 * Math.PI / 6),
distance * (float)Math.Sin(1 * Math.PI / 6));
SKPoint center3 = center +
new SKPoint(distance * (float)Math.Cos(5 * Math.PI / 6),
distance * (float)Math.Sin(5 * Math.PI / 6));
using (SKPaint paint = new SKPaint())
{
if (!isSubtractive)
{
paint.BlendMode = SKBlendMode.Plus;
System.Diagnostics.Debug.WriteLine(paint.BlendMode);
paint.Color = SKColors.Red;
canvas.DrawCircle(center1, radius, paint);
paint.Color = SKColors.Lime; // == (00, FF, 00)
canvas.DrawCircle(center2, radius, paint);
paint.Color = SKColors.Blue;
canvas.DrawCircle(center3, radius, paint);
}
else
{
paint.BlendMode = SKBlendMode.Multiply
System.Diagnostics.Debug.WriteLine(paint.BlendMode);
paint.Color = SKColors.Cyan;
canvas.DrawCircle(center1, radius, paint);
paint.Color = SKColors.Magenta;
canvas.DrawCircle(center2, radius, paint);
paint.Color = SKColors.Yellow;
canvas.DrawCircle(center3, radius, paint);
}
}
}
}
该程序包括一个 TabGestureRecognizer
。 点击或单击屏幕时,该程序会使用 SKBlendMode.Multiply
显示三种减法原色:
Darken
模式也可以达到同样的效果。