Freigeben über


Porter-Duff-Füllmethoden

Die Porter-Duff Blend Modi sind nach Thomas Porter und Tom Duff benannt, die eine Algebra der Compositing entwickelt haben, während Sie für Lucasfilm arbeiten. Ihre Zeitung Compositing Digital Images wurde im Juli 1984 ausgabe von Computer Graphics, Seiten 253 bis 259 veröffentlicht. Diese Mischungsmodi sind für die Kompositierung unerlässlich, die verschiedene Bilder in einer zusammengesetzten Szene zusammenfügt:

Porter-Duff-Beispiel

Porter-Duff-Konzepte

Angenommen, ein bräunliches Rechteck nimmt die linke und obere zwei Drittel der Anzeigeoberfläche ein:

Porter-Duff-Ziel

Dieser Bereich wird als Ziel oder manchmal als Hintergrund oder Hintergrund bezeichnet.

Sie möchten das folgende Rechteck zeichnen, das die gleiche Größe des Ziels hat. Das Rechteck ist transparent, mit Ausnahme eines bläulichen Bereichs, der die rechte und untere zwei Drittel belegt:

Porter-Duff Source

Dies wird als Quelle oder manchmal als Vordergrund bezeichnet.

Wenn Sie die Quelle am Ziel anzeigen, gehen Sie wie folgt vor:

Porter-Duff Source Over

Die transparenten Pixel der Quelle ermöglichen das Anzeigen des Hintergrunds, während die blumigen Quellpixel den Hintergrund verdeckt. Das ist der Normalfall, und es wird in SkiaSharp als SKBlendMode.SrcOver. Dieser Wert ist die Standardeinstellung der BlendMode Eigenschaft, wenn ein SKPaint Objekt zuerst instanziiert wird.

Es ist jedoch möglich, einen anderen Blendmodus für einen anderen Effekt anzugeben. Wenn Sie den Bereich angeben SKBlendMode.DstOver, in dem sich die Quelle und das Ziel schneiden, wird das Ziel anstelle der Quelle angezeigt:

Porter-Duff Destination Over

Im SKBlendMode.DstIn Blendmodus wird nur der Bereich angezeigt, in dem sich das Ziel und die Quelle mit der Zielfarbe schneiden:

Porter-Duff-Ziel in

Der Mischungsmodus ( SKBlendMode.Xor exklusiv ODER) bewirkt, dass nichts angezeigt wird, wenn sich die beiden Bereiche überlappen:

Porter-Duff Exklusiv oder

Die farbigen Ziel- und Quellrechtecke teilen die Anzeigeoberfläche effektiv in vier eindeutige Bereiche auf, die auf verschiedene Weise gefärbt werden können, die dem Vorhandensein des Ziel- und Quellrechtecks entsprechen:

Porter-Duff

Die Rechtecke oben rechts und unten links sind immer leer, weil sowohl das Ziel als auch die Quelle in diesen Bereichen transparent sind. Die Zielfarbe nimmt den oberen linken Bereich ein, sodass der Bereich entweder mit der Zielfarbe oder gar nicht eingefärbt werden kann. Gleichermaßen nimmt die Quellfarbe den unteren rechten Bereich ein, sodass der Bereich mit der Quellfarbe oder gar nicht eingefärbt werden kann. Die Schnittmenge des Ziels und der Quelle in der Mitte kann mit der Zielfarbe, der Quellfarbe oder gar nicht gefärbt werden.

Die Gesamtanzahl der Kombinationen beträgt 2 (für die obere linke) Mal 2 (für die untere rechte) Mal 3 (für die Mitte) oder 12. Dies sind die 12 grundlegenden Porter-Duff Compositing Modi.

Am Ende von Compositing Digital Images (Seite 256) fügen Porter und Duff einen 13. Modus namens Plus hinzu (entsprechend dem SkiaSharp-Element SKBlendMode.Plus und dem W3C-HellerenModus (der nicht mit dem W3C-Lichtmodus verwechselt werden soll.) Dieser Plus Modus fügt die Ziel- und Quellfarben hinzu, ein Prozess, der in Kürze ausführlicher beschrieben wird.

Skia fügt einen 14. Modus hinzu Modulate , der sehr ähnlich Plus ist, mit der Ausnahme, dass die Ziel- und Quellfarben multipliziert werden. Es kann als zusätzlicher Porter-Duff-Blend-Modus behandelt werden.

Hier sind die 14 Porter-Duff Modi wie in SkiaSharp definiert. In der Tabelle wird gezeigt, wie sie die drei nicht leeren Bereiche im obigen Diagramm einfärben:

Mode Destination Schnittmenge Quelle
Clear
Src Quelle X
Dst X Destination
SrcOver X Quelle X
DstOver X Destination X
SrcIn Quelle
DstIn Destination
SrcOut X
DstOut X
SrcATop X Quelle
DstATop Destination X
Xor X X
Plus X Sum X
Modulate Produkt

Diese Füllmethoden sind symmetrisch. Die Quelle und das Ziel können ausgetauscht werden, und alle Modi sind noch verfügbar.

Die Benennungskonvention der Modi folgt einigen einfachen Regeln:

  • Src oder Dst selbst bedeutet, dass nur die Quell- oder Zielpixel sichtbar sind.
  • Das Suffix Over gibt an, was in der Schnittmenge sichtbar ist. Entweder die Quelle oder das Ziel wird "über" die andere gezeichnet.
  • Das Suffix In bedeutet, dass nur die Schnittmenge farbig ist. Die Ausgabe ist nur auf den Teil der Quelle oder des Ziels beschränkt, der sich "in" der anderen befindet.
  • Das Suffix "Out " bedeutet, dass die Schnittmenge nicht farbig ist. Die Ausgabe ist nur der Teil der Quelle oder des Ziels, der außerhalb des Schnittpunkts steht.
  • Das ATop-Suffix ist die Vereinigung von In und Out. Sie enthält den Bereich, in dem sich die Quelle oder das Ziel auf dem anderen befindet.

Beachten Sie den Unterschied mit den Plus Modi.Modulate Diese Modi führen einen anderen Berechnungstyp für die Quell- und Zielpixel aus. Sie werden in Kürze ausführlicher beschrieben.

Auf der Seite "Porter-Duff Grid " werden alle 14 Modi auf einem Bildschirm in Form eines Rasters angezeigt. Jeder Modus ist eine separate Instanz von SKCanvasView. Aus diesem Grund wird eine Klasse von SKCanvasView dem Namen PorterDuffCanvasViewabgeleitet. Der statische Konstruktor erstellt zwei Bitmaps derselben Größe, eines mit einem bräunlichen Rechteck im oberen linken Bereich und einem anderen mit einem blumigen Rechteck:

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

Der Instanzkonstruktor verfügt über einen Parameter vom Typ SKBlendMode. Dieser Parameter wird in einem Feld gespeichert.

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

Die OnPaintSurface Außerkraftsetzung zeichnet die beiden Bitmaps. Die erste wird normal gezeichnet:

canvas.DrawBitmap(dstBitmap, rect);

Die zweite wird mit einem SKPaint Objekt gezeichnet, in dem die BlendMode Eigenschaft auf das Konstruktorargument festgelegt wurde:

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

Der Standard Umbruch der OnPaintSurface Überschreibung zeichnet ein Rechteck um die Bitmap, um die Größe anzugeben.

Die PorterDuffGridPage Klasse erstellt vierzehn Instanzen von PorterDurffCanvasView, eine für jedes Element des blendModes Arrays. Die Reihenfolge der SKBlendModes Elemente im Array unterscheidet sich etwas von der Tabelle, um ähnliche Modi nebeneinander zu positionieren. Die 14 Instanzen von PorterDuffCanvasView sind zusammen mit Bezeichnungen in einer 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;
    }
}

Das Ergebnis lautet wie folgt:

Porter-Duff Grid

Sie werden sich überzeugen, dass Transparenz für das ordnungsgemäße Funktionieren der Porter-Duff-Blend-Modi von entscheidender Bedeutung ist. Die PorterDuffCanvasView Klasse enthält insgesamt drei Aufrufe der Canvas.Clear Methode. Alle verwenden die parameterlose Methode, die alle Pixel auf transparent festlegt:

canvas.Clear();

Versuchen Sie, einen dieser Aufrufe so zu ändern, dass die Pixel auf undurchsichtig weiß festgelegt sind:

canvas.Clear(SKColors.White);

Nach dieser Änderung scheinen einige der Blendmodi zu funktionieren, andere werden jedoch nicht funktionieren. Wenn Sie den Hintergrund der Quellbitmap auf Weiß festlegen, funktioniert der SrcOver Modus nicht, da keine transparenten Pixel in der Quellbitmap vorhanden sind, damit das Ziel angezeigt werden kann. Wenn Sie den Hintergrund der Zielbitmap oder des Zeichenbereichs auf Weiß festlegen, funktioniert dies nicht, DstOver da das Ziel keine transparenten Pixel aufweist.

Es könnte eine Versuchung geben, die Bitmaps auf der Porter-Duff Grid-Seite durch einfachere DrawRect Aufrufe zu ersetzen. Dies funktioniert für das Zielrechteck, aber nicht für das Quellrechteck. Das Quellrechteck muss mehr als nur den blaufarbigen Bereich umfassen. Das Quellrechteck muss einen transparenten Bereich enthalten, der dem farbigen Bereich des Ziels entspricht. Nur dann funktionieren diese Mischmodi.

Verwenden von Mattes mit Porter-Duff

Die Brick-Wall Compositing-Seite zeigt ein Beispiel für eine klassische Compositing-Aufgabe: Ein Bild muss aus mehreren Teilen zusammengestellt werden, einschließlich einer Bitmap mit einem Hintergrund, der eliminiert werden muss. Hier sehen Sie die SeatedMonkey.jpg Bitmap mit dem problematischen Hintergrund:

Sitzaffenaffe

Zur Vorbereitung auf die Kompositierung wurde eine entsprechende Matte erstellt, bei der es sich um eine andere Bitmap handelt, die schwarz ist, wo das Bild angezeigt und ansonsten transparent angezeigt werden soll. Diese Datei heißt SeatedMonkeyMatte.png und gehört zu den Ressourcen im Ordner "Medien " im Beispiel:

Sitzend Monkey Matte

Dies ist kein fachmännisch erstellter Matte. Optimalerweise sollte die Matte teilweise transparente Pixel um den Rand der schwarzen Pixel enthalten, und dieses Matte nicht.

Die XAML-Datei für die Brick-Wall Compositing-Seite instanziiert eine SKCanvasView und eine Button , die den Benutzer durch den Prozess der Erstellung des endgültigen Bilds führt:

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

Die CodeBehind-Datei lädt die beiden benötigten Bitmaps und behandelt das Clicked Ereignis des Button. Für jeden Button Klick wird das step Feld inkrementiert, und eine neue Text Eigenschaft wird für die Buttonfestgelegt. Wenn step 5 erreicht wird, wird sie auf 0 zurückgesetzt:

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

Wenn das Programm zum ersten Mal ausgeführt wird, ist nichts sichtbar, außer dem Button:

Ziegelwand Compositing Schritt 0

Wenn Sie die Button einmal step drücken, wird auf 1 erhöht, und der PaintSurface Handler zeigt jetzt SeatedMonkey.jpg an:

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

Es gibt kein SKPaint Objekt und daher keinen Blendmodus. Die Bitmap wird unten auf dem Bildschirm angezeigt:

Ziegelwandkomositing Schritt 1

Drücken Sie erneut, Button und step erhöhen Sie 2. Dies ist der entscheidende Schritt beim Anzeigen der SeatedMonkeyMatte.png Datei:

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

Der Mischmodus bedeutet SKBlendMode.DstIn, dass das Ziel in Bereichen beibehalten wird, die nicht transparenten Bereichen der Quelle entsprechen. Der Re Standard der des Zielrechtecks, das der ursprünglichen Bitmap entspricht, wird transparent:

Ziegelwandkomositing Schritt 2

Der Hintergrund wurde entfernt.

Der nächste Schritt besteht darin, ein Rechteck zu zeichnen, das einem Bürgersteig ähnelt, auf dem der Affen sitzt. Das Aussehen dieses Bürgersteigs basiert auf einer Komposition von zwei Shadern: einem Volltonfarb-Shader und einem Perlin-Rausch-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);
            }
        }
        ···
    }
}

Da dieser Gehweg hinter dem Affen gehen muss, ist DstOverder Blend-Modus . Das Ziel wird nur angezeigt, wenn der Hintergrund transparent ist:

Ziegelwand-Compositing Schritt 3

Der letzte Schritt besteht darin, eine Ziegelwand hinzuzufügen. Das Programm verwendet die Bitmap-Kachel der Ziegelwand, die als statische Eigenschaft BrickWallTile in der AlgorithmicBrickWallPage Klasse verfügbar ist. Dem Aufruf wird eine SKShader.CreateBitmap Übersetzungstransformation hinzugefügt, um die Kacheln so zu verschieben, dass die untere Zeile eine vollständige Kachel ist:

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

Aus Gründen der Einfachheit zeigt der DrawRect Aufruf diesen Shader über dem gesamten Zeichenbereich an, aber der DstOver Modus beschränkt die Ausgabe auf den Bereich der Canvas, der noch transparent ist:

Ziegelwandkomositing Schritt 4

Offensichtlich gibt es andere Möglichkeiten, diese Szene zu verfassen. Es könnte erstellt werden, beginnend mit dem Hintergrund und dem Fortschritt im Vordergrund. Die Verwendung der Blendmodi bietet Ihnen jedoch mehr Flexibilität. Insbesondere ermöglicht die Verwendung des Mattes, dass der Hintergrund einer Bitmap von der zusammengesetzten Szene ausgeschlossen werden kann.

Wie Sie im Artikel "Clipping mit Pfaden und Regionen" gelernt haben, definiert die SKCanvas Klasse drei Arten von Clipping, die den Methoden , ClipPathund ClipRegion den ClipRectMethoden entsprechen. Die Porter-Duff-Blend-Modi fügen eine weitere Art von Clipping hinzu, wodurch das Einschränken eines Bilds auf beliebige Elemente ermöglicht wird, die Sie zeichnen können, einschließlich Bitmaps. Die in Brick-Wall Compositing verwendete Matte definiert im Wesentlichen einen Beschneidungsbereich.

Farbverlaufstransparenz und Übergänge

Die Beispiele für die Porter-Duff-Blend-Modi, die weiter oben in diesem Artikel gezeigt wurden, enthalten alle beteiligten Bilder, die aus undurchsichtigen Pixeln und transparenten Pixeln, aber nicht teilweise transparenten Pixeln bestehen. Die Blend-Mode-Funktionen werden auch für diese Pixel definiert. Die folgende Tabelle ist eine formellere Definition der Porter-Duff-Blend-Modi, die notation verwendet, die in der Skia SkBlendMode Reference gefunden wird. (Weil SkBlendMode Reference ist ein Skia-Verweis, C++-Syntax wird verwendet.)

Konzeptionell werden die roten, grünen, blauen und Alphakomponenten jedes Pixels von Bytes in Gleitkommazahlen im Bereich von 0 bis 1 konvertiert. Für den Alphakanal ist 0 vollständig transparent und 1 vollständig undurchsichtig.

Die Notation in der folgenden Tabelle verwendet die folgenden Abkürzungen:

  • Da ist der Ziel-Alphakanal
  • Dc ist die RGB-Zielfarbe.
  • Sa ist der Quell-Alphakanal
  • Sc ist die RGB-Quellfarbe.

Die RGB-Farben werden vorab mit dem Alphawert multipliziert. Wenn Sc beispielsweise reines Rot darstellt, sa jedoch 0x80 ist, lautet die RGB-Farbe (0x80, 0, 0). Wenn Sa 0 ist, sind alle RGB-Komponenten ebenfalls null.

Das Ergebnis wird in eckigen Klammern mit dem Alphakanal und der RGB-Farbe angezeigt, die durch ein Komma getrennt ist: [Alpha, Farbe]. Für die Farbe wird die Berechnung separat für die Rot-, Grün- und Blaukomponenten durchgeführt:

Mode Vorgang
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]

Diese Vorgänge sind einfacher zu analysieren, wenn Da und Sa entweder 0 oder 1 sind. 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. Wenn Sa 1 ist, lautet das Ergebnis [Sa, Sc], das Quell-Alpha und die Farbe oder [1, Sc].

Die Plus Modi Modulate unterscheiden sich geringfügig von den anderen, in denen neue Farben aus der Kombination der Quelle und des Ziels resultieren können. Der Plus Modus kann entweder mit Bytekomponenten oder Gleitkommakomponenten interpretiert werden. Auf der weiter oben gezeigten Porter-Duff-Rasterseite ist die Zielfarbe (0xC0, 0x80, 0x00) und die Quellfarbe ist (0x00, 0x80, 0xC0). Jedes Komponentenpaar wird hinzugefügt, aber die Summe wird an 0xFF klammert. Das Ergebnis ist die Farbe (0xC0, 0xFF, 0xC0). Dies ist die Farbe, die im Schnittpunkt angezeigt wird.

Für den Modulate Modus müssen die RGB-Werte in Gleitkommawerte konvertiert werden. Die Zielfarbe ist (0,75, 0,5, 0) und die Quelle ist (0, 0,5, 0,75). Die RGB-Komponenten werden miteinander multipliziert, und das Ergebnis lautet (0, 0,25, 0). Dies ist die Farbe, die in der Schnittmenge auf der Porter-Duff Grid-Seite für diesen Modus angezeigt wird.

Auf der Seite "Porter-Duff Transparency " können Sie untersuchen, wie die Porter-Duff-Blend-Modi grafische Objekte verwenden, die teilweise transparent sind. Die XAML-Datei enthält einen Picker mit den Porter-Duff-Modi:

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

Die CodeBehind-Datei füllt zwei Rechtecke derselben Größe mit einem linearen Farbverlauf. Der Zielverlauf liegt von oben rechts nach unten links. Es ist bräunlich in der oberen rechten Ecke, aber dann in Richtung Mitte beginnt auf transparent zu verblassen und ist in der unteren linken Ecke transparent.

Das Quellrechteck weist einen Farbverlauf von oben links nach unten rechts auf. Die obere linke Ecke ist weich, wird aber wieder transparent eingeblendet und ist in der unteren rechten Ecke transparent.

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

Dieses Programm veranschaulicht, dass die Porter-Duff-Blend-Modi mit anderen Grafikobjekten als Bitmaps verwendet werden können. Die Quelle muss jedoch einen transparenten Bereich enthalten. Dies ist hier der Fall, da der Farbverlauf das Rechteck ausfüllt, aber ein Teil des Farbverlaufs transparent ist.

Hier sind drei Beispiele:

Porter-Duff Transparenz

Die Konfiguration des Ziels und der Quelle ähnelt sehr den Diagrammen, die auf Seite 255 des ursprünglichen Papiers Porter-Duff Compositing Digital Images angezeigt werden, aber diese Seite zeigt, dass sich die Blendmodi für Bereiche der teilweisen Transparenz gut verhalten.

Sie können transparente Farbverläufe für einige verschiedene Effekte verwenden. Eine Möglichkeit besteht in der Maskierung, die mit der Technik vergleichbar ist, die in den Radial-Farbverläufen zum Maskieren des Abschnitts der SkiaSharp-Kreisverlaufseite gezeigt wird. Ein Großteil der Seite "Compositing Mask" ähnelt diesem früheren Programm. Sie lädt eine Bitmapressource und bestimmt ein Rechteck, in dem sie angezeigt werden soll. Ein radialer Farbverlauf wird basierend auf einem vordefinierten Mittelpunkt und Radius erstellt:

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

Der Unterschied bei diesem Programm besteht darin, dass der Farbverlauf mit Schwarz in der Mitte beginnt und mit Transparenz endet. Sie wird in der Bitmap mit einem Mischmodus DstInangezeigt, der das Ziel nur in den Bereichen der Quelle anzeigt, die nicht transparent sind.

Nach dem DrawRect Aufruf ist die gesamte Oberfläche des Zeichenbereichs transparent, mit Ausnahme des kreisförmigen Farbverlaufs. Es wird ein letzter Anruf getätigt:

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

Alle transparenten Bereiche des Zeichenbereichs sind rosa gefärbt:

Compositingmaske

Sie können auch Porter-Duff-Modi und teilweise transparente Farbverläufe für Übergänge von einem Bild zu einem anderen verwenden. Auf der Seite "Farbverlaufsübergänge" wird eine Slider Statusstufe im Übergang von 0 zu 1 angezeigt, und eine Picker , um den gewünschten Übergangstyp auszuwählen:

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

Die CodeBehind-Datei lädt zwei Bitmapressourcen, um den Übergang zu veranschaulichen. Dies sind die gleichen beiden Bilder, die auf der Bitmap-Auflösungsseite weiter oben in diesem Artikel verwendet werden. Der Code definiert außerdem eine Enumeration mit drei Elementen, die drei Arten von Farbverläufen entsprechen – linear, radial und aufräumen. Diese Werte werden in folgendes Pickergeladen:

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

Die CodeBehind-Datei erstellt drei SKPaint Objekte. Das paint0 Objekt verwendet keinen Blendmodus. Dieses Paint-Objekt wird verwendet, um ein Rechteck mit einem Farbverlauf zu zeichnen, der von Schwarz zu transparent verläuft, wie im colors Array angegeben. Das positions Array basiert auf der Position der Slider, aber etwas angepasst. Wenn der Slider Wert mindestens oder maximal ist, sind die progress Werte 0 oder 1, und eine der beiden Bitmaps sollte vollständig sichtbar sein. Das positions Array muss für diese Werte entsprechend festgelegt werden.

Wenn der progress Wert 0 ist, enthält das positions Array die Werte -0,1 und 0. SkiaSharp passt diesen ersten Wert auf 0 an, was bedeutet, dass der Farbverlauf nur bei 0 und transparent ist. Wenn progress 0,5 ist, enthält das Array die Werte 0,45 und 0,55. Der Farbverlauf ist schwarz von 0 bis 0,45, wechselt dann zu transparent und ist vollständig transparent von 0,55 bis 1. Bei progress 1 ist das positions Array 1 und 1,1, was bedeutet, dass der Farbverlauf schwarz von 0 bis 1 ist.

Die colors beiden position Arrays werden in den drei Methoden verwendet, mit SKShader denen ein Farbverlauf erstellt wird. Basierend auf der Picker Auswahl wird nur eins dieser Shader erstellt:

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

Dieser Farbverlauf wird im Rechteck ohne Blendmodus angezeigt. Nach diesem DrawRect Aufruf enthält die Canvas einfach einen Farbverlauf von Schwarz zu transparent. Die Menge an Schwarz steigt mit höheren Slider Werten.

In den letzten vier Anweisungen des PaintSurface Handlers werden die beiden Bitmaps angezeigt. Der SrcOut Blendmodus bedeutet, dass die erste Bitmap nur in den transparenten Bereichen des Hintergrunds angezeigt wird. Der DstOver Modus für die zweite Bitmap bedeutet, dass die zweite Bitmap nur in den Bereichen angezeigt wird, in denen die erste Bitmap nicht angezeigt wird.

Die folgenden Screenshots zeigen die drei verschiedenen Übergänge, jeweils bei der Marke 50 %:

Farbverlaufsübergänge