Porter Duff 的混合模式Porter-Duff blend modes

下载示例下载示例Download Sample Download the sample

Thomas Porter 和 Tom Duff,他们负责开发组合的情况下在 Lucasfilm 代数后命名 Porter Duff 的混合模式。The Porter-Duff blend modes are named after Thomas Porter and Tom Duff, who developed an algebra of compositing while working for Lucasfilm. 其纸张组合的情况下数字图像在 1984 年 7 月发行的已发布_计算机图形中_,页 253 向 259。Their paper Compositing Digital Images was published in the July 1984 issue of Computer Graphics, pages 253 to 259. 这些混合模式是组合的情况下,这组合到复合场景的各种映像的基础:These blend modes are essential for compositing, which is assembling various images into a composite scene:

Porter Duff 示例Porter-Duff Sample

Porter Duff 概念Porter-Duff concepts

假设 brownish 矩形所占的显示图面左侧和顶部 2 / 3:Suppose a brownish rectangle occupies the left and top two-thirds of your display surface:

Porter Duff 目标Porter-Duff Destination

此区域称为_目标_有时_背景_或_背景_。This area is called the destination or sometimes the background or backdrop.

你想要绘制以下矩形的大小相同的目标。You wish to draw the following rectangle, which is the same size of the destination. 该矩形是透明占据右侧和底部的三分之二的色调蓝色区域除外:The rectangle is transparent except for a bluish area that occupies the right and bottom two-thirds:

Porter Duff 源Porter-Duff Source

这称为_源_有时_前台_。This is called the source or sometimes the foreground.

当在目标上显示的源代码时,下面是你期望的内容:When you display the source on the destination, here's what you expect:

Porter Duff 源覆盖Porter-Duff Source Over

源的透明像素允许背景以显示通过,而略带蓝色源像素会掩盖背景。The transparent pixels of the source allow the background to show through, while the bluish source pixels obscure the background. 这是正常情况下,并在为 SkiaSharp 中称为SKBlendMode.SrcOverThat's the normal case, and it is referred to in SkiaSharp as SKBlendMode.SrcOver. 值是默认设置的BlendMode属性时SKPaint对象首次实例化。That value is the default setting of the BlendMode property when an SKPaint object is first instantiated.

但是,就可以以指定不同的效果的不同的混合模式。However, it's possible to specify a different blend mode for a different effect. 如果指定SKBlendMode.DstOver,则在源和目标的相交的区域中,会出现而不是源的目标:If you specify SKBlendMode.DstOver, then in the area where the source and destination intersect, the destination appears instead of the source:

通过 Porter Duff 目标Porter-Duff Destination Over

SKBlendMode.DstIn混合模式下显示的目标和源相交使用目标颜色区域:The SKBlendMode.DstIn blend mode displays only the area where the destination and source intersect using the destination color:

中的目标,Porter DuffPorter-Duff Destination In

混合模式的SKBlendMode.Xor(独占 OR) 导致没有要显示的两个方面与发生重叠的内容:The blend mode of SKBlendMode.Xor (exclusive OR) causes nothing to appear where the two areas overlap:

Porter Duff 排他或者Porter-Duff Exclusive Or

彩色的目标和源矩形有效地将显示图面划分为四个可以以各种方式对应的目标和源矩形存在的唯一区域:The colored destination and source rectangles effectively divide the display surface into four unique areas that can be colored in various ways corresponding to the presence of the destination and source rectangles:

Porter DuffPorter-Duff

右上方和左下角矩形是始终为空,因为在这些领域中的目标和源是透明。The upper-right and lower-left rectangles are always blank because both the destination and source are transparent in those areas. 目标颜色占用窗口左上区域中,以便区域或者与目标颜色或根本不是颜色。The destination color occupies the upper-left area, so that area can either be colored with the destination color or not at all. 同样,源颜色占用右下角区域中,以便与源颜色或根本不是,可以设置颜色区域。Similarly, the source color occupies the lower-right area, so that area can be colored with the source color or not at all. 可以与目标颜色,源颜色,或者根本未着色目标和源中间的交集。The intersection of the destination and source in the middle can be colored with the destination color, the source color, or not at all.

组合的总数乘以时间 (适用于中心),3 或 12 2 (对于右下角) 是 2 (对于左上角)。The total number of combinations is 2 (for the upper-left) times 2 (for the lower-right) times 3 (for the center), or 12. 这些是 12 基本 Porter Duff 组合模式。These are the 12 basic Porter-Duff compositing modes.

末尾的_组合的情况下数字图像_(页面 256) Porter 和 Duff 添加名为 13 模式_plus_ (对应于 SkiaSharpSKBlendMode.Plus成员和 W3C_较浅_模式 (这并不是与 W3C 混淆_变淡_模式。)这Plus模式添加目标和源颜色将稍后更详细地介绍的过程。Towards the end of Compositing Digital Images (page 256), Porter and Duff add a 13th mode called plus (corresponding to the SkiaSharp SKBlendMode.Plus member and the W3C Lighter mode (which is not to be confused with the W3C Lighten mode.) This Plus mode adds the destination and source colors, a process that will be described in more detail shortly.

Skia 添加 14 模式称为Modulate非常类似于Plus不同之处在于目标和源颜色相乘。Skia adds a 14th mode called Modulate that is very similar to Plus except that the destination and source colors are multiplied. 它可将其视为其他 Porter Duff 混合模式。It can be treated as an additional Porter-Duff blend mode.

以下是 14 Porter Duff 模式 SkiaSharp 中定义。Here are the 14 Porter-Duff modes as defined in SkiaSharp. 下表显示如何颜色上图中的三个非空白区域的每个:The table shows how they color each of the three non-blank areas in the diagram above:

模式Mode 目标Destination 交集Intersection Source
Clear
Src Source XX
Dst XX 目标Destination
SrcOver XX Source XX
DstOver XX 目标Destination XX
SrcIn Source
DstIn 目标Destination
SrcOut XX
DstOut XX
SrcATop XX Source
DstATop 目标Destination XX
Xor XX XX
Plus XX SumSum XX
Modulate 产品Product

这些混合模式是对称的。These blend modes are symmetrical. 可交换的源和目标,且所有的模式仍可用。The source and destination can be exchanged and all the modes are still available.

命名约定的模式遵循几个简单规则:The naming convention of the modes follows a few simple rules:

  • SrcDst本身意味着只有源或目标像素是否可见。Src or Dst by itself means that only the source or destination pixels are visible.
  • 转移后缀指示了交集中可见的内容。The Over suffix indicates what is visible in the intersection. "结束"的其他绘制的源或目标。Either the source or destination is drawn "over" the other.
  • 后缀意味着仅交集进行着色。The In suffix means that only the intersection is colored. 输出被限制为只有在源或目标可以是"in"的其他部分。The output is restricted to only the part of the source or destination that is "in" the other.
  • 后缀意味着交集不带颜色。The Out suffix means that the intersection is not colored. 输出只是交集的一部分的源或目标可以是交集的"out"。The output is only the part of the source or destination that is "out" of the intersection.
  • 之上后缀是联合。它的其他包括其中的源或目标是"在"区域。The ATop suffix is the union of In and Out. It includes the area where the source or destination is "atop" of the other.

请注意的不同之处PlusModulate模式。Notice the difference with the Plus and Modulate modes. 这些模式对源和目标像素执行不同类型的计算。These modes are performing a different type of calculation on the source and destination pixels. 他们稍后所述更多详细信息。They are described in more detail shortly.

Porter Duff 网格页在网格的窗体中的一个屏幕上显示所有 14 模式。The Porter-Duff Grid page shows all 14 modes on one screen in the form of a grid. 每种模式是一个单独的实例SKCanvasViewEach mode is a separate instance of SKCanvasView. 因此,一个类派生自SKCanvasView名为PorterDuffCanvasViewFor that reason, a class is derived from SKCanvasView named PorterDuffCanvasView. 静态构造函数创建另一个使用其左上角区域中的 brownish 矩形,另一个略带蓝色矩形具有相同大小的两个位图:The static constructor creates two bitmaps of the same size, one with a brownish rectangle in its upper-left area and another with a bluish rectangle:

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);
            }
        }
    }
    ···
}

实例构造函数具有类型参数的SKBlendModeThe instance constructor has a parameter of type SKBlendMode. 它将此参数保存在字段中。It saves this parameter in a field.

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替代绘制两个位图。The OnPaintSurface override draws the two bitmaps. 通常情况下绘制第一个:The first is drawn normally:

canvas.DrawBitmap(dstBitmap, rect);

使用绘制第二个SKPaint对象在其中BlendMode属性已设置为构造函数参数:The second is drawn with an SKPaint object where the BlendMode property has been set to the constructor argument:

using (SKPaint paint = new SKPaint())
{
    paint.BlendMode = blendMode;
    canvas.DrawBitmap(srcBitmap, rect, paint);
}

其余部分OnPaintSurface替代周围绘制一个矩形以指示其大小的位图。The remainder of the OnPaintSurface override draws a rectangle around the bitmap to indicate their sizes.

PorterDuffGridPage类创建的 14 个实例PorterDurffCanvasView,一个用于的每个成员blendModes数组。The PorterDuffGridPage class creates fourteen instances of PorterDurffCanvasView, one for each member of the blendModes array. 顺序SKBlendModes数组中的成员是表的稍有不同的以便放置彼此相邻的类似模式。The order of the SKBlendModes members in the array is a little different than the table in order to position similar modes adjacent to each other. 14 的实例PorterDuffCanvasView组织中的标签以及Grid:The 14 instances of PorterDuffCanvasView are organized along with labels in a 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;
    }
}

下面是结果:Here's the result:

Porter Duff 网格Porter-Duff Grid

你将想要使您自己确信透明度是至关重要的 Porter Duff 的混合模式运行正常。You'll want to convince yourself that transparency is crucial to the proper functioning of the Porter-Duff blend modes. PorterDuffCanvasView类包含三个对总共Canvas.Clear方法。The PorterDuffCanvasView class contains a total of three calls to the Canvas.Clear method. 所有这些使用无参数方法,它将所有像素都设置为透明:All of them use the parameterless method, which sets all the pixels to transparent:

canvas.Clear();

请尝试更改这些调用的任何位置,以像素为单位设置为不透明的白色:Try changing any of those calls so that the pixels are set to opaque white:

canvas.Clear(SKColors.White);

按照此更改后,一些的混合模式将起来起作用,但其他人将不会。Following that change, some of the blend modes will seem to work, but others will not. 如果将源位图的背景设置为白色,则SrcOver模式不起作用,因为源位图,以便通过显示目标中的不透明的像素为单位。If you set the background of the source bitmap to white, then the SrcOver mode doesn't work because there's no transparent pixels in the source bitmap to let the destination show through. 如果设置的目标位图或为白色,然后在画布背景DstOver不起作用,因为目标不具有任何透明的像素为单位。If you set the background of the destination bitmap or the canvas to white, then DstOver doesn't work because the destination doesn't have any transparent pixels.

可能倾向于替换中的位图Porter Duff 网格页上使用更简单DrawRect调用。There might be a temptation to replace the bitmaps in the Porter-Duff Grid page with simpler DrawRect calls. 有关目标矩形而不是源矩形,这将起作用。That will work for the destination rectangle but not for the source rectangle. 源矩形必须包含不止是颜色色调蓝色的区域。The source rectangle must encompass more than just the bluish-colored area. 源矩形必须包含对应于目标的彩色区域的透明区域。The source rectangle must include a transparent area that corresponds to the colored area of the destination. 这些仅然后将混合模式下工作。Only then will these blend modes work.

使用 Porter Duff 遮罩Using mattes with Porter-Duff

"砖墙合成" 页显示了一个经典合成任务的示例:需要将图片从多个部分进行组装, 其中包括需要消除背景的位图。The Brick-Wall Compositing page shows an example of a classic compositing task: A picture needs to be assembled from several pieces, including a bitmap with a background that needs to be eliminated. 下面是SeatedMonkey.jpg有问题的背景位图:Here's the SeatedMonkey.jpg bitmap with the problematic background:

就位 MonkeySeated Monkey

在组合的情况下,相应准备_亚光效果_创建的这是另一个位图,否则为黑色,如果你想要显示的图像和透明。In preparation for compositing, a corresponding matte was created, which is another bitmap that is black where you want the image to appear and transparent otherwise. 此文件命名为SeatedMonkeyMatte.png是在中资源之间媒体文件夹中的 SkiaSharpFormsDemos 示例:This file is named SeatedMonkeyMatte.png and is among the resources in the Media folder in the SkiaSharpFormsDemos sample:

就位 Monkey 遮罩Seated Monkey Matte

这是_不_熟练地创建亚光效果。This is not an expertly created matte. 理想情况下,亚光效果应包括部分透明的黑色像素,边缘像素和此亚光效果却没有。Optimally, the matte should include partially transparent pixels around the edge of the black pixels, and this matte does not.

XAML 文件程序块-Wall 组合的情况下页上实例化SKCanvasView和一个Button指导用户完成撰写最终映像的过程:The XAML file for the Brick-Wall Compositing page instantiates an SKCanvasView and a Button that guides the user through the process of composing the final image:

<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事件的ButtonThe code-behind file loads the two bitmaps that it needs and handles the Clicked event of the Button. 为每个Button依次step字段是递增和的新Text属性设置为ButtonFor every Button click, the step field is incremented and a new Text property is set for the Button. step达到 5,它将重新设置为 0:When step reaches 5, it is set back to 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:When the program first runs, nothing is visible except the Button:

程序块-Wall 组合步骤 0Brick-Wall Compositing Step 0

按下Button一次将导致step增量为 1,并PaintSurface处理程序现在将显示SeatedMonkey.jpg:Pressing the Button once causes step to increment to 1, and the PaintSurface handler now displays 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对象,因此无混合模式。There's no SKPaint object and hence no blend mode. 位图显示在屏幕的底部:The bitmap appears at the bottom of the screen:

程序块-Wall 组合步骤 1Brick-Wall Compositing Step 1

Button再次和step递增为 2。Press the Button again and step increments to 2. 这是显示的关键步骤SeatedMonkeyMatte.png文件:This is the crucial step of displaying the SeatedMonkeyMatte.png file:

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,这意味着目标将被保留,对应于源的非透明区域的区域中。The blend mode is SKBlendMode.DstIn, which means that the destination will be preserved in areas corresponding to non-transparent areas of the source. 对应于原始位图的目标矩形的其余部分将变为透明:The remainder of the destination rectangle corresponding to the original bitmap becomes transparent:

程序块-Wall 组合步骤 2Brick-Wall Compositing Step 2

在后台已删除。The background has been removed.

下一步是要绘制一个矩形,类似于 monkey 坐在人行道。The next step is to draw a rectangle that resembles a sidewalk that the monkey is sitting on. 此人行道的外观取决于两个着色器的组合: 纯色着色器和 Perlin 噪音着色器:The appearance of this sidewalk is based on a composition of two shaders: a solid color shader and a Perlin noise shader:

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);
            }
        }
        ···
    }
}

因为此人行道必须深入 monkey,混合模式是DstOverBecause this sidewalk must go behind the monkey, the blend mode is DstOver. 仅在背景色为透明色其中仅显示目标:The destination appears only where the background is transparent:

程序块-Wall 组合步骤 3Brick-Wall Compositing Step 3

最后一步添加产生效果。The final step is adding a brick wall. 该程序作为静态属性使用可用的程序块-wall 位图磁贴BrickWallTileAlgorithmicBrickWallPage类。The program uses the brick-wall bitmap tile available as the static property BrickWallTile in the AlgorithmicBrickWallPage class. 平移转换添加到SKShader.CreateBitmap调用,以便最后一行是完整的磁贴移动磁贴:A translation transform is added to the SKShader.CreateBitmap call to shift the tiles so that the bottom row is a full tile:

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模式将输出限制为仅画布仍透明的区域:For convenience, the DrawRect call displays this shader over the entire canvas, but the DstOver mode limits the output to only the area of the canvas that is still transparent:

程序块-Wall 组合步骤 4Brick-Wall Compositing Step 4

显然,还有其他方式来编写此场景。Obviously there are other ways to compose this scene. 它可以开始在后台和前台进行构建。It could be built up starting at the background and progressing to the foreground. 但使用的混合模式提供了更大的灵活性。But using the blend modes gives you more flexibility. 具体而言,遮罩的使用可以从组合场景要排除的位图的背景。In particular, the use of the matte allows the background of a bitmap to be excluded from the composed scene.

正如本文中获知路径和区域中的剪辑,则SKCanvas类定义了三种类型的剪辑,对应于 ClipRect ClipPath ,并 ClipRegion 方法。As you learned in the article Clipping with Paths and Regions, the SKCanvas class defines three types of clipping, corresponding to the ClipRect, ClipPath, and ClipRegion methods. Porter Duff 的混合模式添加另一种类型的剪辑,允许限制对任何内容,您可以绘制,包括位图图像。The Porter-Duff blend modes add another type of clipping, which allows restricting an image to anything that you can draw, including bitmaps. 在中使用遮罩程序块-Wall 合成实质上是定义剪辑区域。The matte used in Brick-Wall Compositing essentially defines a clipping area.

渐变透明度和转换Gradient transparency and transitions

Porter Duff 示例混合模式在这篇文章有所有涉及组成不透明像素和透明的像素为单位,但不是部分透明的像素的图像前面所示。The examples of the Porter-Duff blend modes shown earlier in this article have all involved images that consisted of opaque pixels and transparent pixels, but not partially transparent pixels. 混合模式函数为这些像素为单位定义。The blend-mode functions are defined for those pixels as well. 下表是 Porter Duff 的混合模式的更正式定义,使用表示法中 Skia SkBlendMode 引用The following table is a more formal definition of the Porter-Duff blend modes that uses notation found in the Skia SkBlendMode Reference. (因为SkBlendMode 引用为 Skia 引用,使用 c + + 语法。)(Because SkBlendMode Reference is a Skia reference, C++ syntax is used.)

从概念上讲,每个像素的红色、 绿色、 蓝色和 alpha 组件从字节转换为 0 到 1 范围内的浮点数。Conceptually, the red, green, blue, and alpha components of each pixel are converted from bytes to floating-point numbers in the range of 0 to 1. Alpha 通道,0 表示完全透明,1 是完全不透明For the alpha channel, 0 is fully transparent and 1 is fully opaque

下表中的表示法使用下列缩写:The notation in the table below uses the following abbreviations:

  • Da是目标的 alpha 通道Da is the destination alpha channel
  • Dc是 RGB 颜色的目标Dc is the destination RGB color
  • Sa是源 alpha 通道Sa is the source alpha channel
  • Sc是 RGB 颜色的源Sc is the source RGB color

RGB 颜色预先乘以 alpha 值。The RGB colors are pre-multiplied by the alpha value. 例如,如果Sc表示纯红色,但Sa是 RGB 颜色为 0x80, (0x80,0,0)For example, if Sc represents pure red but Sa is 0x80, then the RGB color is (0x80, 0, 0). 如果Sa为 0,则所有 RGB 组件也都是零。If Sa is 0, then all the RGB components are also zero.

结果显示在使用 alpha 通道和由逗号分隔的 RGB 颜色的方括号中: [alpha,颜色]The result is shown in brackets with the alpha channel and the RGB color separated by a comma: [alpha, color]. 颜色为红色、 绿色和蓝色组件单独执行计算:For the color, the calculation is performed separately for the red, green, and blue components:

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

这些操作是更轻松地分析何时DaSa为 0 或 1。These operations are easier to analyze when Da and Sa are either 0 or 1. 例如,对于默认值SrcOver模式下,如果Sa为 0,则Sc是 0,并将该结果也是 [Da,Dc] ,目标 alpha 和颜色。For example, for the default SrcOver mode, if Sa is 0, then Sc is also 0, and the result is [Da, Dc], the destination alpha and color. 如果Sa为 1,则会得到 [Sa,Sc] ,源 alpha 和颜色,或 [1,Sc]If Sa is 1, then the result is [Sa, Sc], the source alpha and color, or [1, Sc].

PlusModulate模式是从其他稍有不同,新的颜色,可以得到的源和目标组合。The Plus and Modulate modes are a little different from the others in that new colors can result from the combination of the source and the destination. Plus模式可以解释使用字节组件或浮点组件。The Plus mode can be interpreted either with byte components or floating-point components. 在中Porter Duff 网格目标颜色之前所示的页面 (0xC0、 0x80,0x00) 源颜色是 (0x00、 0x80,0xC0)In the Porter-Duff Grid page shown earlier, the destination color is (0xC0, 0x80, 0x00) and the source color is (0x00, 0x80, 0xC0). 添加组件的每个对,但在 0xFF 其限制之和。Each pair of components is added but the sum is clamped at 0xFF. 结果是颜色 (0xC0、 0xff 内,0xC0)The result is the color (0xC0, 0xFF, 0xC0). 这是交集所示的颜色。That's the color shown in the intersection.

有关Modulate模式下,必须 RGB 值转换为浮点。For the Modulate mode, the RGB values must be converted to floating-point. 目标颜色是 (0.75,0.5,0) ,而源 (0、 0.5、 0.75)The destination color is (0.75, 0.5, 0) and the source is (0, 0.5, 0.75). RGB 组件是每个全部相乘,并且结果为 (0,0.25,0)The RGB components are each multiplied together, and the result is (0, 0.25, 0). 这是在交集所示的颜色Porter Duff 网格此模式下的页。That's the color shown in the intersection in the Porter-Duff Grid page for this mode.

Porter Duff 透明度页面允许您检查 Porter Duff 的混合模式的部分透明的图形对象的进行操作。The Porter-Duff Transparency page allows you to examine how the Porter-Duff blend modes operate on graphical objects that are partially transparent. XAML 文件包含PickerPorter Duff 模式:The XAML file includes a Picker with the Porter-Duff modes:

<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>

代码隐藏文件已满使用线性渐变的大小相同的两个矩形。The code-behind file fills two rectangles of the same size using a linear gradient. 目标渐变是从右上到左下。The destination gradient is from the upper right to the lower left. 它是 brownish 右上角中,但然后向中心开始淡入淡出为透明,并是透明的左下角。It is brownish in the upper-right corner but then towards the center begins fading to transparent, and is transparent in the lower-left corner.

源矩形具有从左上向右下的渐变。The source rectangle has a gradient from the upper left to the lower right. 左上角是略带蓝色但再次淡出为透明的并且是透明的右下角。The upper-left corner is bluish but again fades to transparent, and is transparent in the lower-right corner.

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 的混合模式可用于非位图图形对象。This program demonstrates that the Porter-Duff blend modes can be used with graphic objects other than bitmaps. 但是,源必须包括透明区域。However, the source must include a transparent area. 这是此处的示例,因为渐变填充矩形,但的渐变的部分是透明。This is the case here because the gradient fills the rectangle, but part of the gradient is transparent.

以下是三个示例:Here are three examples:

Porter Duff 透明度Porter-Duff Transparency

目标和源的配置是非常类似于原始的 Porter Duff 255 页中显示的图组合的情况下数字图像白皮书,不过它在此页演示混合模式是良好的部分透明的区域。The configuration of the destination and source is very similar to the diagrams shown in page 255 of the original Porter-Duff Compositing Digital Images paper, but this page demonstrates that the blend modes are well-behaved for areas of partial transparency.

对于一些不同的效果,可以使用透明的梯度。You can use transparent gradients for some different effects. 一种可能性屏蔽,它类似的方法中所示径向渐变进行屏蔽一部分SkiaSharp 循环渐变页One possibility is masking, which is similar to the technique shown in the Radial gradients for masking section of the SkiaSharp circular gradients page. 大部分组合的情况下掩码页是类似于该早期版本的程序。Much of the Compositing Mask page is similar to that earlier program. 它加载位图资源,并确定要在其中显示该矩形。It loads a bitmap resource and determines a rectangle in which to display it. 径向渐变是基于创建的预先确定的中心和半径:A radial gradient is created based on a pre-determined center and radius:

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);
    }
}

此程序的不同之处是渐变中心的黑色开头和结尾的透明度。The difference with this program is that the gradient begins with black in the center and ends with transparency. 该字符串出现在混合模式的位图DstIn,其中只有几个方面的源的不透明的显示目标。It is displayed on the bitmap with a blend mode of DstIn, which shows the destination only in the areas of the source that are not transparent.

之后DrawRect调用中,画布的整个图面是透明定义径向渐变圆圈除外。After the DrawRect call, the entire surface of the canvas is transparent except for the circle defined by the radial gradient. 由最后一次调用:A final call is made:

canvas.DrawColor(SKColors.Pink, SKBlendMode.DstOver);

被粉色的画布的透明区域:All the transparent areas of the canvas are colored pink:

组合的情况下掩码Compositing Mask

此外可以使用 Porter Duff 模式和部分透明渐变到另一个图像中的转换。You can also use Porter-Duff modes and partially transparent gradients for transitions from one image to another. 渐变过渡页包含Slider以指示从 0 到 1 的转换中的进度级别和一个Picker选择所需的转换的类型:The Gradient Transitions page includes a Slider to indicate a progress level in the transition from 0 to 1, and a Picker to choose the type of transition you want:

<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>

代码隐藏文件加载两个位图资源,以演示转换。The code-behind file loads two bitmap resources to demonstrate the transition. 这些是相同的两个图像中使用位图消失在本文前面的页。These are the same two images used in the Bitmap Dissolve page earlier in this article. 该代码还定义一个枚举,包含对应于三种类型的渐变的三个成员—线性的径向和扫描。The code also defines an enumeration with three members corresponding to three types of gradients — linear, radial, and sweep. 这些值加载到Picker:These values are loaded into the 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对象。The code-behind file creates three SKPaint objects. paint0对象不使用混合模式。The paint0 object doesn't use a blend mode. 此绘制对象用于绘制一个具有渐变从黑色到透明中所示的矩形colors数组。This paint object is used to draw a rectangle with a gradient that goes from black to transparent as indicated in the colors array. positions上的位置基于数组Slider,但调整某种程度上。The positions array is based on the position of the Slider, but adjusted somewhat. 如果Slider位于其最小值或最大值、progress的值为 0 或 1,和两个位图之一应是完全可见。If the Slider is at its minimum or maximum, the progress values are 0 or 1, and one of the two bitmaps should be fully visible. positions数组必须相应地设置这些值。The positions array must be set accordingly for those values.

如果progress值为 0,则positions数组包含值-0.1 和 0。If the progress value is 0, then the positions array contains the values -0.1 and 0. SkiaSharp 将否则调整将等于 0,表示渐变是黑色只能在 0 和透明第一个值。SkiaSharp will adjust that first value to be equal to 0, which means that the gradient is black only at 0 and transparent otherwise. progress为 0.5,则该数组包含 0.45 和 0.55 的值。When progress is 0.5, then the array contains the values 0.45 and 0.55. 渐变是黑色从 0 到 0.45,则将转换为透明的并且是完全透明的 0.55 为 1。The gradient is black from 0 to 0.45, then transitions to transparent, and is fully transparent from 0.55 to 1. progress为 1,positions数组是 1 和 1.1 中,这意味着渐变是黑色从 0 到 1。When progress is 1, the positions array is 1 and 1.1, which means the gradient is black from 0 to 1.

colorsposition数组所使用的三个方法中SKShader创建渐变。The colors and position arrays are both used in the three methods of SKShader that create a gradient. 只创建一个这些着色器根据Picker选择:Only one of these shaders is created based on the Picker selection:

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);
        }
    }
}

但不使用混合模式的矩形中显示该渐变。That gradient is displayed in the rectangle without a blend mode. 在此之后DrawRect调用中,画布只包含从黑色到透明的渐变。After that DrawRect call, the canvas simply contains a gradient from black to transparent. 黑色的量增加更高版本Slider值。The amount of black increases with higher Slider values.

中的最后四个语句PaintSurface处理程序中,将显示两个位图。In the final four statements of the PaintSurface handler, the two bitmaps are displayed. SrcOut混合模式,即仅在后台透明区域中显示第一个位图。The SrcOut blend mode means that the first bitmap is displayed only in the transparent areas of the background. DstOver第二个位图的模式,即在其中第一个位图不会显示这些领域中仅显示第二个位图。The DstOver mode for the second bitmap means that the second bitmap is displayed only in those areas where the first bitmap is not displayed.

以下屏幕截图显示三个不同的转换类型,每 50%标记处:The following screenshots show the three different transitions types, each at the 50% mark:

渐变过渡Gradient Transitions