Porter-Duff混合模式
Porter-Duff混合模式會命名為 Thomas Porter 和 Tom Duff,後者在處理 Lucasfilm 時開發組合的代數。 其 文章 Compositing Digital Images 已于電腦 圖形1984 年 7 月發行,頁面 253 到 259。 這些混合模式是組合不可或缺的模式,可將各種影像組合成複合場景:
Porter-Duff概念
假設黑色矩形佔用顯示介面的左邊和前兩分之三:
此區域稱為目的地,有時稱為背景或背景或背景。
您想要繪製下列矩形,其大小相同。 矩形是透明的,但佔用右下兩分之二的模糊區域除外:
這稱為 來源 或有時 前景。
當您在目的地上顯示來源時,以下是您預期的內容:
來源的透明圖元可讓背景顯示,而模糊來源圖元會遮蔽背景。 這是一般情況,而且在 SkiaSharp SKBlendMode.SrcOver
中稱為 。 當物件第一 SKPaint
次具現化時, BlendMode
該值是屬性的預設設定。
不過,您可以為不同的效果指定不同的混合模式。 如果您指定 SKBlendMode.DstOver
,則在來源和目的地交集的區域中,目的地會出現,而不是來源:
SKBlendMode.DstIn
混合模式只會顯示目的地和來源使用目的地色彩交集的區域:
(獨佔 OR) 混合模式 SKBlendMode.Xor
會導致兩個區域重迭的地方出現任何專案:
彩色目的地和來源矩形可有效地將顯示介面分割成四個唯一區域,這些區域可以各種對應到目的地和來源矩形的色彩:
右上方和左下角矩形一律為空白,因為目的地和來源在這些區域中都是透明的。 目的地色彩會佔用左上方的區域,讓區域可以以目的色彩來著色,或完全不一定。 同樣地,來源色彩會佔用右下角區域,讓區域可以完全以來源色彩著色。 中間的目的地和來源交集可以以目的地色彩、來源色彩或完全不著色。
左上方) 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 |
產品 |
這些混合模式是對稱的。 來源和目的地可以交換,而且所有模式仍可供使用。
模式的命名慣例遵循幾個簡單的規則:
- Src 或 Dst 本身表示只顯示來源或目的地圖元。
- Over尾碼表示交集中顯示的內容。 來源或目的地會繪製另一個來源或目的地。
- In尾碼表示只有交集色彩。 輸出僅限於另一個來源或目的地的一部分。
- Out尾碼表示交集未加上色彩。 輸出只是交集「輸出」的來源或目的地部分。
- ATop尾碼是In和Out的聯集。它包含來源或目的地位於另一個區域「頂端」的區域。
請注意 和 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混合模式的正常運作至關重要。 類別 PorterDuffCanvasView
包含方法的三個呼叫 Canvas.Clear
總數。 全部都使用無參數方法,將所有圖元設定為透明:
canvas.Clear();
請嘗試變更上述任何呼叫,讓圖元設定為不透明白色:
canvas.Clear(SKColors.White);
在變更之後,某些混合模式似乎可以運作,但其他模式則無法運作。 如果您將來源點陣圖的背景設定為白色,則 SrcOver
模式無法運作,因為來源點陣圖中沒有透明圖元可讓目的地顯示。 如果您將目的地點陣圖或畫布的背景設定為白色,則 DstOver
無法運作,因為目的地沒有任何透明圖元。
使用更簡單 DrawRect
的呼叫,可能會想要取代Porter-Duff Grid頁面中的點陣圖。 這適用于目的地矩形,但不適用於來源矩形。 來源矩形必須只包含模糊色彩的區域。 來源矩形必須包含對應至目的地色彩區域的透明區域。 只有這樣,這些混合模式才會運作。
搭配使用Porter-Duff
[磚牆撰寫] 頁面會顯示傳統撰寫工作的範例:圖片必須從數個片段組合,包括需要消除背景的點陣圖。 以下是具有問題背景 SeatedMonkey.jpg 點陣圖:
為了準備組合,已建立對應的 Matte ,這是另一個黑色的點陣圖,您想要讓影像出現在其中,否則為透明。 此檔案名為SeatedMonkeyMatte.png,且位於SkiaSharpFormsDemos範例中Media資料夾中的資源之間:
這不是專家建立的 Matte。 最佳作法是,Matte 應該在黑色圖元的邊緣周圍包含部分透明圖元,而這個 Matte 則不會。
[磚牆撰寫] 頁面的 XAML 檔案會具現化 , SKCanvasView
並 Button
引導使用者完成撰寫最終影像的程式:
<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
看不到任何專案:
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
物件,因此沒有混合模式。 點陣圖會出現在畫面底部:
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
,這表示目的地會保留在對應至來源非透明區域的區域中。 對應至原始點陣圖之目的地矩形的其餘部分會變成透明:
已移除背景。
下一個步驟是繪製一個矩形,該矩形類似于手邊道,而此矩形位於此側邊。 此側游的外觀是以兩個著色器的組合為基礎:純色著色器和 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
。 目的地只會出現在背景為透明的位置:
最後一個步驟是新增磚牆。 程式會使用磚牆點陣圖磚,做為 類別中的 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
模式只會將輸出限制為仍然透明的畫布區域:
很明顯地,有其他方式可以撰寫此場景。 它可以從背景開始建置,並繼續進行前景。 但使用混合模式可讓您更有彈性。 特別是,使用 Matte 可讓點陣圖的背景從撰寫的場景中排除。
如您在 使用路徑和區域裁剪一文中所瞭解,類別 SKCanvas
會定義三種類型的裁剪,對應至 ClipRect
、 ClipPath
和 ClipRegion
方法。 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] |
當 Da 和 Sa 是 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 撰寫數位影像 檔第 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
不會使用混合模式。 這個繪製物件是用來繪製具有漸層的矩形,該漸層會從黑色變成透明,如陣列所示 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 時 progress
, positions
陣列是 1 和 1.1,這表示漸層是從 0 到 1 的黑色。
colors
和 position
陣列都用於建立漸層的 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% 標記: