分離可能なブレンド モード
「SkiaSharp Porter-Duff ブレンド モード」の記事で説明したように、Porter-Duff ブレンド モードでは通常、クリッピング操作が実行されます。 分離可能なブレンド モードは異なります。 分離可能モードでは、画像の個々の赤、緑、青の色コンポーネントが変更されます。 分離可能なブレンド モードでは、色をミックスして、赤、緑、青の組み合わせが実際に白であることを示すことができます。
明るくしたり暗くしたりする 2 つの方法
ビットマップは暗すぎるか、明るすぎるのが一般的です。 分離可能なブレンド モードを使用して、画像を明るくしたり暗くしたりできます。 実際に、SKBlendMode
列挙の分離可能なブレンド モードのうち 2 つは、Lighten
と Darken
という名前がつけられています。
これら 2 つのモードは Lighten と Darken ページに示されています。 XAML ファイルは、2 つの SKCanvasView
オブジェクトと 2 つの Slider
ビューをインスタンス化します。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Effects.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
を示し、2 番目のペアは SKBlendMode.Darken
を示します。 2 つの Slider
ビューは同じ ValueChanged
ハンドラーを共有し、2 つの 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
ハンドラーは、ビットマップに適した四角形を計算します。 ハンドラーは、そのビットマップを表示し、BlendMode
プロパティが SKBlendMode.Lighten
または SKBlendMode.Darken
に設定された SKPaint
オブジェクトを使用して、ビットマップ上に四角形を表示します。 Color
プロパティは、Slider
に基づく灰色の網掛けです。 Lighten
モードでは、色の範囲は黒から白ですが、Darken
モードの場合は白から黒の範囲です。
スクリーンショットでは、左から右の順に上の画像が明るくなり、下の画像が暗くなるにつれて、Slider
の値がより大きくなっていることを示しています。
このプログラムでは、分離可能なブレンド モードを使用する通常の方法を示します。変換先は何らかの種類の画像で、ほとんどの場合ビットマップです。 変換元は、BlendMode
プロパティが分離可能なブレンド モードに設定された SKPaint
オブジェクトを使用して表示される四角形です。 四角形には、単色 (ここで示すように) またはグラデーションを指定できます。 透明度は、通常、分離可能なブレンド モードでは使用されません。
このプログラムを試すうちに、これら 2 つのブレンド モードでは、画像が均一に明るく、または、暗くなっていないことがわかります。 代わりに、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 の省略形を使用でき、赤、緑、青の各コンポーネントに別々に計算が適用されると理解されます。
次の表は、すべての分離可能なブレンド モードと、その動作の簡単な説明を示しています。 2 番目の列は、変化が生じない変換元の色を示しています。
ブレンド モード | 変更なし | 操作 |
---|---|---|
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 Compositing and Blending Level 1 仕様および Skia SkBlendMode Reference に記載されていますが、これら 2 つの出典の表記は同じではありません。 Plus
は一般的に Porter-Duff ブレンド モードと見なされ、Modulate
は W3C 仕様の一部ではないことに注意してください。
変換元が透明の場合、Modulate
を除くすべての分離可能なブレンド モードでは、ブレンド モードは効果がありません。 前に説明したように、Modulate
ブレンド モードでは乗算にアルファ チャネルが組み込まれています。 そうでなければ、Modulate
は Multiply
と同じ効果を持ちます。
ColorDodge
と ColorBurn
という 2 つのモードに注目してください。 dodge (覆い焼き) とburn (焼き込み) という言葉は、写真の暗室の練習で生まれました。 引き伸ばし機は、ネガに光を当てて写真プリントを作ります。 光がない場合、プリントは白くなります。 より多くの光が長時間プリントに降り注ぐにつれて、プリントは暗くなります。 プリントの製作者は、手や小さな物体を使ってプリントの特定の部分に当たる光の一部を遮断し、その部分を明るくすることがよくありました。 これは "覆い焼き" と呼ばれます。 逆に、穴のあいた不透明な素材 (または光の大部分を遮る手) を使って、特定の場所に多くの光を当てて暗くすることもでき、これは "焼きこみ" と呼ばれます。
Dodge と Burn プログラムは Lighten と Darken によく似ています。 XAML ファイルは同じ構造ですが、要素名が異なり、分離コード ファイルも同様に似ていますが、これら 2 つのブレンド モードの効果はまったく異なります。
Slider
値が小さい場合、Lighten
モードでは最初に暗い領域が明るくなり、ColorDodge
では、より均一に明るくなります。
画像処理アプリケーション プログラムでは、多くの場合、暗室と同様に、覆い焼きと焼きこみを特定の領域に制限できます。 これは、グラデーション、またはさまざまな灰色の網掛けビットマップによって実現できます。
分離可能なブレンド モードの探索
[分離可能なブレンド モード] のページでは、分離可能なブレンド モードをすべて調べることができます。 いずれかのブレンド モードを使用して、ビットマップの変換先と色付きの四角形の変換元が表示されます。
XAML ファイルは、(ブレンド モードを選択するための) Picker
と 4 つのスライダーを定義します。 最初の 3 つのスライダーを使用すると、変換元の赤、緑、青のコンポーネントを設定できます。 4 番目のスライダーは、灰色の網掛けを設定してこれらの値をオーバーライドすることを目的としています。 個々のスライダーは識別されませんが、色はその機能を示します。
<?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>
分離コード ファイルにより、ビットマップ リソースの 1 つが読み込まれ、キャンバスの上半分に 1 回、キャンバスの下半分に 2 回描画されます。
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
ハンドラーの下部に向かって、選択したブレンド モードと選択した色を持つ 2 番目のビットマップの上に四角形が描画されます。 下部にある変更されたビットマップと、一番上にある元のビットマップを比較できます。
加法と減算の原色
[原色] ページには、赤、緑、青の 3 つの重複する円が描画されます。
これらは加法原色です。 任意の 2 つの組み合わせによってシアン、マゼンタ、黄色が生成され、3 つすべての組み合わせが白になります。
これらの 3 つの円は 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
を使用して 3 つの減算の原色が表示されます。
Darken
モードも、この同じ効果に対して機能します。