次の方法で共有


分離可能なブレンド モード

SkiaSharp Porter-Duff ブレンド モード」の記事で説明したように、Porter-Duff ブレンド モードでは通常、クリッピング操作が実行されます。 分離可能なブレンド モードは異なります。 分離可能モードでは、画像の個々の赤、緑、青の色コンポーネントが変更されます。 分離可能なブレンド モードでは、色をミックスして、赤、緑、青の組み合わせが実際に白であることを示すことができます。

原色

明るくしたり暗くしたりする 2 つの方法

ビットマップは暗すぎるか、明るすぎるのが一般的です。 分離可能なブレンド モードを使用して、画像を明るくしたり暗くしたりできます。 実際に、SKBlendMode 列挙の分離可能なブレンド モードのうち 2 つは、LightenDarken という名前がつけられています。

これら 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>

最初の SKCanvasViewSliderSKBlendMode.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 の値がより大きくなっていることを示しています。

Lighten と Darken

このプログラムでは、分離可能なブレンド モードを使用する通常の方法を示します。変換先は何らかの種類の画像で、ほとんどの場合ビットマップです。 変換元は、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)

赤、緑、青の各コンポーネントはそれぞれ個別に処理されるため、これらのブレンド モードは "分離可能" なブレンド モードと呼ばれます。 このため、変換先の色と変換元の色には DcSc の省略形を使用でき、赤、緑、青の各コンポーネントに別々に計算が適用されると理解されます。

次の表は、すべての分離可能なブレンド モードと、その動作の簡単な説明を示しています。 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 ブレンド モードでは乗算にアルファ チャネルが組み込まれています。 そうでなければ、ModulateMultiply と同じ効果を持ちます。

ColorDodgeColorBurn という 2 つのモードに注目してください。 dodge (覆い焼き) とburn (焼き込み) という言葉は、写真の暗室の練習で生まれました。 引き伸ばし機は、ネガに光を当てて写真プリントを作ります。 光がない場合、プリントは白くなります。 より多くの光が長時間プリントに降り注ぐにつれて、プリントは暗くなります。 プリントの製作者は、手や小さな物体を使ってプリントの特定の部分に当たる光の一部を遮断し、その部分を明るくすることがよくありました。 これは "覆い焼き" と呼ばれます。 逆に、穴のあいた不透明な素材 (または光の大部分を遮る手) を使って、特定の場所に多くの光を当てて暗くすることもでき、これは "焼きこみ" と呼ばれます。

Dodge と Burn プログラムは Lighten と Darken によく似ています。 XAML ファイルは同じ構造ですが、要素名が異なり、分離コード ファイルも同様に似ていますが、これら 2 つのブレンド モードの効果はまったく異なります。

Dodge と Burn

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 モードで描画されますが、ScreenLightenDifference を使っても同じ効果が得られます。 プログラムを次に示します。

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 モードも、この同じ効果に対して機能します。