共用方式為


Porter-Duff 混合模式

波特-達夫混合模式以湯瑪斯·波特和湯姆·達夫命名,後者在為盧卡斯電影公司工作時開發了組合代數。 他們的論文《組合數位圖像》發表在1984年7月的《計算機圖形雜誌上,第253頁至259頁。 這些混合模式對於組合而言很重要,它會將各種影像組合成復合場景:

Porter-Duff 範例

Porter-Duff 概念

假設棕色矩形佔據顯示表面的左邊和前三分之二:

Porter-Duff 目的地

這個區域稱為 目的地 ,有時稱為 背景背景

您想要繪製下列矩形,其大小與目的地相同。 矩形是透明的,除了佔據右下三分之二的藍色區域:

Porter-Duff 來源

這稱為 來源 ,有時稱為 前景

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

Porter-Duff 來源 Over

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

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

透過 Porter-Duff 目的地

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

Porter-Duff 目的地 In

(獨佔 OR) 的 SKBlendMode.Xor 混合模式不會顯示兩個區域重疊的位置:

Porter-Duff 獨佔或

彩色目的地和來源矩形會有效地將顯示介面分成四個唯一區域,這些區域可以透過各種方式著色,以對應目的地和來源矩形的存在:

Porter-Duff

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

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

在 Compositing Digital Images (第 256 頁) 結尾處,Porter 和 Duff 新增了名為 plus 的第 13 個模式(對應至 SkiaSharp SKBlendMode.Plus 成員和 W3C 較輕模式(這不會與 W3C Lighten 模式混淆)。此Plus模式會新增目的地和來源色彩,此程式將在稍後更詳細地描述。

Skia 新增了名為 Modulate 的第14個模式,其非常類似 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 Products

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

模式的命名慣例會遵循一些簡單的規則:

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

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

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 會建立的 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 Grid

您會想要說服自己,透明度對於 Porter-Duff 混合模式的適當運作至關重要。 類別 PorterDuffCanvasView 包含對方法的三次呼叫 Canvas.Clear 。 它們全都使用無參數方法,將所有像素設定為透明:

canvas.Clear();

請嘗試變更其中任何一個呼叫,讓像素設定為不透明白色:

canvas.Clear(SKColors.White);

在該變更之後,某些混合模式似乎可以運作,但其他模式則無法運作。 如果您將來源位圖的背景設定為白色,則 SrcOver 模式無法運作,因為來源位圖中沒有透明圖元可讓目的地顯示。 如果您將目的地位圖或畫布的背景設定為白色,則 DstOver 無法運作,因為目的地沒有任何透明圖元。

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

搭配 Porter-Duff 使用啞光

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

坐式猴子

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

座位猴子馬特

這不是一個專家製作的啞光。 最佳方式是,啞光應該包含黑色像素邊緣的部分透明圖元,而且這個啞光不會。

Brick-Wall Compositing 頁面的 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>

程序代碼後置檔案會載入它所需的兩個點陣圖,並處理 ClickedButton事件。 每次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

顯然還有其他方法可以撰寫這個場景。 它可以從背景開始建置,並進展到前景。 但是,使用混合模式可讓您更有彈性。 特別是,使用啞光可讓位圖的背景從組成場景中排除。

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

漸層透明度和轉換

本文稍早所示的 Porter-Duff 混合模式範例包含由不透明圖元和透明圖元組成的影像,但不包含部分透明圖元。 混合模式函式也會針對這些像素定義。 下表是 Porter-Duff 混合模式的更正式定義,該模式使用 Skia SkBlendMode 參考中找到的表示法。 (因為 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,色彩]。 針對色彩,計算會分別針對紅色、綠色和藍色元件執行:

模式 作業
Clear [0, 0]
Src [Sa, Sc]
Dst [Da, Dc]
SrcOver [Sa + Da·(1 – Sa), Sc + Dc·(1 – 薩)
DstOver [Da + Sa·(1 – Da), Dc + Sc·(1 – 達)
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]

Plus Modulate 模式與其他模式稍有不同,因為新的色彩可能會由來源和目的地的組合所產生。 模式 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 Compositing Digital Images 紙張第 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);

畫布的所有透明區域都會標示為粉紅色:

撰寫遮罩

您也可以使用 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 不會使用混合模式。 這個繪製對像是用來繪製具有漸層的矩形,該漸層會從黑色到透明,如數位所示 colorspositions陣列是以 的位置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。

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% 標記顯示:

漸層轉換