Porter-Duff混合模式

下載範例 下載範例

Porter-Duff混合模式會命名為 Thomas Porter 和 Tom Duff,後者在處理 Lucasfilm 時開發組合的代數。 其 文章 Compositing Digital Images 已于電腦 圖形1984 年 7 月發行,頁面 253 到 259。 這些混合模式是組合不可或缺的模式,可將各種影像組合成複合場景:

Porter-Duff 範例

Porter-Duff概念

假設黑色矩形佔用顯示介面的左邊和前兩分之三:

Porter-Duff 目的地

此區域稱為目的地,有時稱為背景背景或背景。

您想要繪製下列矩形,其大小相同。 矩形是透明的,但佔用右下兩分之二的模糊區域除外:

Porter-Duff 來源

這稱為 來源 或有時 前景

當您在目的地上顯示來源時,以下是您預期的內容:

透過 Porter-Duff 來源透過 Porter-Duff

來源的透明圖元可讓背景顯示,而模糊來源圖元會遮蔽背景。 這是一般情況,而且在 SkiaSharp SKBlendMode.SrcOver 中稱為 。 當物件第一 SKPaint 次具現化時, BlendMode 該值是屬性的預設設定。

不過,您可以為不同的效果指定不同的混合模式。 如果您指定 SKBlendMode.DstOver ,則在來源和目的地交集的區域中,目的地會出現,而不是來源:

透過 Porter-Duff 目的地透過 Porter-Duff

SKBlendMode.DstIn混合模式只會顯示目的地和來源使用目的地色彩交集的區域:

Porter-Duff 目的地中的 Porter-Duff

(獨佔 OR) 混合模式 SKBlendMode.Xor 會導致兩個區域重迭的地方出現任何專案:

Porter-Duff 獨佔或

彩色目的地和來源矩形可有效地將顯示介面分割成四個唯一區域,這些區域可以各種對應到目的地和來源矩形的色彩:

Porter-Duff

右上方和左下角矩形一律為空白,因為目的地和來源在這些區域中都是透明的。 目的地色彩會佔用左上方的區域,讓區域可以以目的色彩來著色,或完全不一定。 同樣地,來源色彩會佔用右下角區域,讓區域可以完全以來源色彩著色。 中間的目的地和來源交集可以以目的地色彩、來源色彩或完全不著色。

左上方) 2 次的組合總數為 (2 (,右下) 3 次 (中央) 或 12。 這些是 12 種基本Porter-Duff組合模式。

撰寫數位影像 (第 256 頁) 結尾,Porter 和 Duff 會新增第 13 個模式,稱為 plus (,對應至 SkiaSharp SKBlendMode.Plus 成員和 W3C 較淺 模式 (, (此模式不會與 W3C Lighten 模式混淆。) 此 Plus 模式會新增目的地和來源色彩,稍後將會更詳細地描述此程式。

Skia 新增名為 的第 14 個模式 Modulate ,其非常類似 Plus ,不同之處在于目的地和來源色彩會乘以。 它可以視為其他Porter-Duff混合模式。

以下是 SkiaSharp 中所定義的 14 個Porter-Duff模式。 下表顯示它們如何為上圖中三個非空白區域各色彩:

[模式] Destination 交叉 口 來源
Clear
Src 來源 X
Dst X Destination
SrcOver X 來源 X
DstOver X Destination X
SrcIn 來源
DstIn Destination
SrcOut X
DstOut X
SrcATop X 來源
DstATop Destination X
Xor X X
Plus X Sum X
Modulate 產品

這些混合模式是對稱的。 來源和目的地可以交換,而且所有模式仍可供使用。

模式的命名慣例遵循幾個簡單的規則:

  • SrcDst 本身表示只顯示來源或目的地圖元。
  • Over尾碼表示交集中顯示的內容。 來源或目的地會繪製另一個來源或目的地。
  • In尾碼表示只有交集色彩。 輸出僅限於另一個來源或目的地的一部分。
  • Out尾碼表示交集未加上色彩。 輸出只是交集「輸出」的來源或目的地部分。
  • ATop尾碼是InOut的聯集。它包含來源或目的地位於另一個區域「頂端」的區域。

請注意 和 Modulate 模式的差異 Plus 。 這些模式會在來源和目的地圖元上執行不同類型的計算。 稍後會更詳細地描述它們。

[ Porter-Duff Grid ] 頁面會在一個畫面上以格線的形式顯示所有 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 會為 陣列的每個成員 blendModes 建立一個十四個 實例 PorterDurffCanvasView 。 陣列中成員的順序與資料表稍 SKBlendModes 有不同,以放置彼此連續的類似模式。 的 14 個 Grid 實例 PorterDuffCanvasView 會與 中的標籤一起組織:

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 無法運作,因為目的地沒有任何透明圖元。

使用更簡單 DrawRect 的呼叫,可能會想要取代Porter-Duff Grid頁面中的點陣圖。 這適用于目的地矩形,但不適用於來源矩形。 來源矩形必須只包含模糊色彩的區域。 來源矩形必須包含對應至目的地色彩區域的透明區域。 只有這樣,這些混合模式才會運作。

搭配使用Porter-Duff

[磚牆撰寫] 頁面會顯示傳統撰寫工作的範例:圖片必須從數個片段組合,包括需要消除背景的點陣圖。 以下是具有問題背景 SeatedMonkey.jpg 點陣圖:

基座的

為了準備組合,已建立對應的 Matte ,這是另一個黑色的點陣圖,您想要讓影像出現在其中,否則為透明。 此檔案名為SeatedMonkeyMatte.png,且位於SkiaSharpFormsDemos範例中Media資料夾中的資源之間:

Seated Matte

這不是專家建立的 Matte。 最佳作法是,Matte 應該在黑色圖元的邊緣周圍包含部分透明圖元,而這個 Matte 則不會。

[磚牆撰寫] 頁面的 XAML 檔案會具現化 , SKCanvasViewButton 引導使用者完成撰寫最終影像的程式:

<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 屬性。 達到 5 時 step ,它會設定回 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 看不到任何專案:

磚牆組合步驟 0

Button按下一次會導致 step 遞增為 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 物件,因此沒有混合模式。 點陣圖會出現在畫面底部:

磚牆組合步驟 1

Button再次按下 ,並將 step 遞增為 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 ,這表示目的地會保留在對應至來源非透明區域的區域中。 對應至原始點陣圖之目的地矩形的其餘部分會變成透明:

磚牆組合步驟 2

已移除背景。

下一個步驟是繪製一個矩形,該矩形類似于手邊道,而此矩形位於此側邊。 此側游的外觀是以兩個著色器的組合為基礎:純色著色器和 Perlin 雜訊著色器:

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 。 目的地只會出現在背景為透明的位置:

磚牆組合步驟 3

最後一個步驟是新增磚牆。 程式會使用磚牆點陣圖磚,做為 類別中的 AlgorithmicBrickWallPage 靜態屬性 BrickWallTile 。 翻譯轉換會新增至 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 模式只會將輸出限制為仍然透明的畫布區域:

磚牆組合步驟 4

很明顯地,有其他方式可以撰寫此場景。 它可以從背景開始建置,並繼續進行前景。 但使用混合模式可讓您更有彈性。 特別是,使用 Matte 可讓點陣圖的背景從撰寫的場景中排除。

如您在 使用路徑和區域裁剪一文中所瞭解,類別 SKCanvas 會定義三種類型的裁剪,對應至 ClipRectClipPathClipRegion 方法。 Porter-Duff混合模式會新增另一種類型的裁剪,允許將影像限制為您可以繪製的任何專案,包括點陣圖。 在磚牆組合中使用的光面基本上會定義裁剪區域。

漸層透明度和轉換

本文稍早所顯示之Porter-Duff混合模式的範例包含所有包含不透明圖元和透明圖元的影像,但不包含部分透明圖元。 混合模式函式也會針對這些圖元定義。 下表是使用 Skia SkBlendMode 參考中找到的標記法的Porter-Duff混合模式更正式的定義。 (因為 SkBlendMode 參考 是 Skia 參考,所以會使用 C++ 語法。)

概念上,每個圖元的紅色、綠色、藍色和 Alpha 元件會從位元組轉換成 0 到 1 的浮點數。 針對 Alpha 色板,0 完全透明,1 完全不透明

下表中的標記法使用下列縮寫:

  • Da 是目的地 Alpha 色板
  • Dc 是目的地 RGB 色彩
  • Sa 是來源 Alpha 色板
  • Sc 是來源 RGB 色彩

RGB 色彩會預先乘以 Alpha 值。 例如,如果 Sc 代表純紅色,但 Sa 是0x80,則 RGB 色彩 會 (0x80、0、0) 。 如果 Sa 為 0,則所有 RGB 元件也是零。

結果會以方括弧顯示,並以 Alpha 色板和以逗號分隔的 RGB 色彩: [Alpha, color]。 針對色彩,計算會針對紅色、綠色和藍色元件個別執行:

[模式] 作業
Clear [0, 0]
Src [Sa, Sc]
Dst [Da, Dc]
SrcOver [Sa + da· (1 – Sa) , Sc + Dc· (1 – Sa)
DstOver [Da + Sa· (1 – da) , Dc + Sc· (1 – da)
SrcIn [Sa·Da, Sc·Da]
DstIn [Da·Sa、Dc·Sa]
SrcOut [Sa· (1 – Da) , Sc· (1 – Da) ]
DstOut [Da· (1 – Sa) , Dc· (1 – Sa) ]
SrcATop [Da, Sc·Da + Dc· (1 – Sa) ]
DstATop [Sa, Dc·Sa + Sc· (1 – da) ]
Xor [Sa + da – 2•Sa•Da, Sc· (1 – Da) + Dc· (1 – Sa) ]
Plus [Sa + da, Sc + Dc]
Modulate [Sa·Da, Sc·Dc]

DaSa 是 0 或 1 時,這些作業比較容易分析。 例如,針對預設 SrcOver 模式,如果 Sa 是 0, 則 Sc 也是 0,結果為 [Da, Dc],目的地 Alpha 和色彩。 如果 Sa 是 1,則結果為 [Sa、Sc]、來源 Alpha 和色彩,或 [1,Sc]

PlusModulate 模式與其他人稍有不同,因為新的色彩可能是來源和目的地的組合所產生。 Plus您可以使用位元組元件或浮點元件來解譯模式。 在稍早顯示的 Porter-Duff Grid 頁面中,目的地色彩是 (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 Grid 頁面中交集中顯示的色彩。

Porter-Duff Transparency頁面可讓您檢查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

目的地和來源的組態與原始Porter-Duff 撰寫數位影像 檔第 255 頁中顯示的圖表非常類似,但此頁面會示範混合模式對於部分透明度區域的行為良好。

您可以針對一些不同的效果使用透明漸層。 其中一個可能性是遮罩,這類似于SkiaSharp 圓形漸層頁面之遮罩區段的星形漸層中顯示的技術。 大部分 的 Compositing Mask 頁面與先前的程式類似。 它會載入點陣圖資源,並決定要在其中顯示它的矩形。 星形漸層會根據預先決定的中心和半徑來建立:

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。 當 為 1 時 progresspositions 陣列是 1 和 1.1,這表示漸層是從 0 到 1 的黑色。

colorsposition 陣列都用於建立漸層的 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% 標記:

漸層轉換