Partager via


Modes de fusion Porter-Duff

Les modes de mélange Porter-Duff sont nommés après Thomas Porter et Tom Duff, qui ont développé une algèbre de compositing tout en travaillant pour Lucasfilm. Leur document Compositing Digital Images a été publié dans la édition de juillet 1984 de Computer Graphics, pages 253 à 259. Ces modes de fusion sont essentiels pour la composition, qui assemble différentes images dans une scène composite :

Exemple Porter-Duff

Concepts porter-Duff

Supposons qu’un rectangle brunâtre occupe la gauche et le haut des deux tiers de votre surface d’affichage :

Porter-Duff Destination

Cette zone est appelée destination ou parfois arrière-plan ou arrière-plan.

Vous souhaitez dessiner le rectangle suivant, qui correspond à la même taille de la destination. Le rectangle est transparent à l’exception d’une zone floue qui occupe les deux tiers droit et inférieur :

Porter-Duff Source

Il s’agit de la source ou parfois du premier plan.

Lorsque vous affichez la source sur la destination, voici ce que vous attendez :

Source Porter-Duff sur

Les pixels transparents de la source permettent à l’arrière-plan de s’afficher, tandis que les pixels sources bleuâtres masquent l’arrière-plan. C’est le cas normal, et on parle de SkiaSharp comme SKBlendMode.SrcOver. Cette valeur est le paramètre par défaut de la BlendMode propriété lorsqu’un SKPaint objet est instancié pour la première fois.

Toutefois, il est possible de spécifier un mode de fusion différent pour un effet différent. Si vous spécifiez SKBlendMode.DstOver, puis dans la zone où la source et la destination se croisent, la destination apparaît au lieu de la source :

Destination Porter-Duff sur

Le SKBlendMode.DstIn mode de fusion affiche uniquement la zone où la destination et la source se croisent à l’aide de la couleur de destination :

Destination Porter-Duff dans

Le mode de fusion de (OR exclusif) n’entraîne SKBlendMode.Xor aucun affichage où les deux zones se chevauchent :

Porter-Duff exclusif ou

Les rectangles de destination et de source colorés divisent efficacement la surface d’affichage en quatre zones uniques qui peuvent être colorées de différentes manières correspondant à la présence des rectangles de destination et sources :

Porter-Duff

Les rectangles supérieur droit et inférieur gauche sont toujours vides, car la destination et la source sont transparentes dans ces zones. La couleur de destination occupe la zone supérieure gauche, afin que cette zone puisse être colorée avec la couleur de destination ou pas du tout. De même, la couleur source occupe la zone inférieure droite, de sorte que cette zone puisse être colorée avec la couleur source ou pas du tout. L’intersection de la destination et de la source au milieu peut être colorée avec la couleur de destination, la couleur source ou pas du tout.

Le nombre total de combinaisons est de 2 (pour le coin supérieur gauche) fois 2 (pour le coin inférieur droit) 3 (pour le centre) ou 12. Il s’agit des 12 modes de composition de base Porter-Duff.

Vers la fin de la composition d’images numériques (page 256), Porter et Duff ajoutent un 13e mode appelé plus (correspondant au membre SkiaSharp SKBlendMode.Plus et au mode W3C Light (qui ne doit pas être confondu avec le mode Lighten W3C.) Ce Plus mode ajoute les couleurs de destination et de source, un processus qui sera décrit plus en détail sous peu.

Skia ajoute un 14e mode appelé Modulate qui est très similaire à ce Plus que la destination et les couleurs sources soient multipliées. Il peut être traité comme un mode de fusion Porter-Duff supplémentaire.

Voici les 14 modes Porter-Duff définis dans SkiaSharp. Le tableau montre comment ils colorent chacune des trois zones non vides du diagramme ci-dessus :

Mode Destination Intersection Source
Clear
Src Source X
Dst X Destination
SrcOver X Source X
DstOver X Destination X
SrcIn Source
DstIn Destination
SrcOut X
DstOut X
SrcATop X Source
DstATop Destination X
Xor X X
Plus X Somme X
Modulate Produit

Ces modes de fusion sont symétriques. La source et la destination peuvent être échangées et tous les modes sont toujours disponibles.

La convention d’affectation de noms des modes suit quelques règles simples :

  • Src ou Dst signifie que seuls les pixels source ou de destination sont visibles.
  • Le suffixe Over indique ce qui est visible dans l’intersection. La source ou la destination est dessinée « sur » l’autre.
  • Le suffixe In signifie que seule l’intersection est colorée. La sortie est limitée uniquement à la partie de la source ou de la destination qui est « dans » l’autre.
  • Le suffixe Out signifie que l’intersection n’est pas colorée. La sortie est uniquement la partie de la source ou de la destination qui est « hors » de l’intersection.
  • Le suffixe ATop est l’union de In et Out. Il inclut la zone où la source ou la destination est « au sommet » de l’autre.

Notez la différence avec les modes et Modulate les Plus modes. Ces modes effectuent un autre type de calcul sur les pixels source et de destination. Ils sont décrits plus en détail sous peu.

La page Porter-Duff Grid affiche tous les 14 modes sur un seul écran sous la forme d’une grille. Chaque mode est une instance distincte de SKCanvasView. Pour cette raison, une classe est dérivée du SKCanvasView nom PorterDuffCanvasView. Le constructeur statique crée deux bitmaps de la même taille, une avec un rectangle brunâtre dans sa zone supérieure gauche et une autre avec un rectangle bleuté :

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

Le constructeur d’instance a un paramètre de type SKBlendMode. Il enregistre ce paramètre dans un champ.

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

La OnPaintSurface substitution dessine les deux bitmaps. Le premier est dessiné normalement :

canvas.DrawBitmap(dstBitmap, rect);

La deuxième est dessinée avec un SKPaint objet où la BlendMode propriété a été définie sur l’argument du constructeur :

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

Le reste de la OnPaintSurface substitution dessine un rectangle autour de la bitmap pour indiquer leurs tailles.

La PorterDuffGridPage classe crée quatorze instances de PorterDurffCanvasView, une pour chaque membre du blendModes tableau. L’ordre des SKBlendModes membres du tableau est un peu différent de celui de la table afin de positionner les modes similaires adjacents les uns aux autres. Les 14 instances de PorterDuffCanvasView sont organisées avec des étiquettes dans un 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;
    }
}

Voici le résultat :

Porter-Duff Grid

Vous voudrez vous convaincre que la transparence est essentielle au bon fonctionnement des modes de fusion Porter-Duff. La PorterDuffCanvasView classe contient un total de trois appels à la Canvas.Clear méthode. Toutes utilisent la méthode sans paramètre, qui définit tous les pixels sur transparent :

canvas.Clear();

Essayez de modifier l’un de ces appels afin que les pixels soient définis sur blanc opaque :

canvas.Clear(SKColors.White);

Après ce changement, certains modes de fusion semblent fonctionner, mais d’autres ne le seront pas. Si vous définissez l’arrière-plan de la bitmap source sur blanc, le SrcOver mode ne fonctionne pas, car il n’y a pas de pixels transparents dans la bitmap source pour laisser la destination s’afficher. Si vous définissez l’arrière-plan de la bitmap de destination ou le canevas sur blanc, DstOver cela ne fonctionne pas, car la destination n’a pas de pixels transparents.

Il peut y avoir une tentation de remplacer les bitmaps dans la page Porter-Duff Grid par des appels plus simples DrawRect . Cela fonctionnera pour le rectangle de destination, mais pas pour le rectangle source. Le rectangle source doit englober plus que la zone bleutée. Le rectangle source doit inclure une zone transparente qui correspond à la zone colorée de la destination. Seuls ces modes de fusion fonctionnent.

Utilisation de mats avec Porter-Duff

La page Brick-Wall Compositing montre un exemple de tâche de composition classique : une image doit être assemblée à partir de plusieurs morceaux, y compris une bitmap avec un arrière-plan qui doit être éliminé. Voici la bitmap SeatedMonkey.jpg avec l’arrière-plan problématique :

Singe assis

En préparation de la composition, un mat correspondant a été créé, qui est un autre bitmap noir où vous souhaitez que l’image apparaisse et transparente dans le cas contraire. Ce fichier est nommé SeatedMonkeyMatte.png et fait partie des ressources du dossier Media de l’exemple :

Singe assis Mat

Ce n’est pas un mat créé par un expert. De façon optimale, le mat doit inclure des pixels partiellement transparents autour du bord des pixels noirs, et ce mat ne le fait pas.

Le fichier XAML de la page Brick-Wall Compositing instancie un SKCanvasView et un Button qui guide l’utilisateur tout au long du processus de composition de l’image finale :

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

Le fichier code-behind charge les deux bitmaps dont il a besoin et gère l’événement Clicked du Button. Pour chaque Button clic, le step champ est incrémenté et une nouvelle Text propriété est définie pour le Button. Quand step elle atteint 5, elle est rétablie à 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();
        ···
    }
}

Lorsque le programme s’exécute pour la première fois, rien n’est visible à l’exception des Buttonpoints suivants :

Brick-Wall Compositing Step 0

Appuyez une fois sur les Button causes de l’incrémentation sur 1, et le PaintSurface gestionnaire affiche maintenant SeatedMonkey.jpgstep :

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

Il n’y a pas SKPaint d’objet et donc pas de mode blend. La bitmap apparaît en bas de l’écran :

Brick-Wall Compositing Step 1

Button Appuyez de nouveau et step incrémentez sur 2. Il s’agit de l’étape cruciale de l’affichage du fichier 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);
            }
        }
        ···
    }
}

Le mode de fusion est SKBlendMode.DstIn, ce qui signifie que la destination sera conservée dans les zones correspondant à des zones non transparentes de la source. Le reste du rectangle de destination correspondant à la bitmap d’origine devient transparent :

Brick-Wall Compositing Step 2

L’arrière-plan a été supprimé.

L’étape suivante consiste à dessiner un rectangle qui ressemble à un trottoir sur lequel le singe est assis. L’apparence de ce trottoir est basée sur une composition de deux nuanceurs : un nuanceur de couleur unie et un nuanceur de bruit 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);
            }
        }
        ···
    }
}

Parce que ce trottoir doit se rendre derrière le singe, le mode mélange est DstOver. La destination apparaît uniquement où l’arrière-plan est transparent :

Brick-Wall Compositing Step 3

La dernière étape consiste à ajouter un mur en briques. Le programme utilise la vignette bitmap de mur de briques disponible en tant que propriété BrickWallTile statique dans la AlgorithmicBrickWallPage classe. Une transformation de traduction est ajoutée à l’appel SKShader.CreateBitmap pour décaler les vignettes afin que la ligne inférieure soit une vignette complète :

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

Pour plus de commodité, l’appel DrawRect affiche ce nuanceur sur l’ensemble du canevas, mais le DstOver mode limite la sortie uniquement à la zone du canevas qui est toujours transparente :

Brick-Wall Compositing Step 4

Évidemment, il existe d’autres façons de composer cette scène. Il peut être créé en commençant à l’arrière-plan et en progressant au premier plan. Mais l’utilisation des modes de fusion vous offre une plus grande flexibilité. En particulier, l’utilisation du mat permet à l’arrière-plan d’une bitmap d’être exclu de la scène composée.

Comme vous l’avez appris dans l’article Clipping with Paths and Regions, la SKCanvas classe définit trois types de découpage, correspondant aux méthodes et ClipRegion ClipPathaux ClipRectméthodes. Les modes de fusion Porter-Duff ajoutent un autre type de découpage, ce qui permet de restreindre une image à tout ce que vous pouvez dessiner, y compris les bitmaps. Le mat utilisé dans brick-wall compositing définit essentiellement une zone de découpage.

Transparence et transitions dégradées

Les exemples des modes de fusion Porter-Duff présentés précédemment dans cet article ont tous impliqué des images composées de pixels opaques et de pixels transparents, mais pas de pixels partiellement transparents. Les fonctions en mode blend sont également définies pour ces pixels. Le tableau suivant est une définition plus formelle des modes de fusion Porter-Duff qui utilise la notation trouvée dans la référence Skia SkBlendMode. (Parce que SkBlendMode Reference est une référence Skia, la syntaxe C++ est utilisée.)

Conceptuellement, les composants rouge, vert, bleu et alpha de chaque pixel sont convertis de octets en nombres à virgule flottante dans la plage de 0 à 1. Pour le canal alpha, 0 est entièrement transparent et 1 est entièrement opaque

La notation dans le tableau ci-dessous utilise les abréviations suivantes :

  • Da est le canal alpha de destination
  • Dc est la couleur RVB de destination
  • Sa est le canal alpha source
  • Sc est la couleur RVB source

Les couleurs RVB sont pré multipliées par la valeur alpha. Par exemple, si Sc représente le rouge pur, mais sa est 0x80, la couleur RVB est (0x80, 0, 0). Si Sa est égal à 0, tous les composants RVB sont également zéro.

Le résultat est affiché entre crochets avec le canal alpha et la couleur RVB séparées par une virgule : [alpha, couleur]. Pour la couleur, le calcul est effectué séparément pour les composants rouges, verts et bleus :

Mode Opération
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]

Ces opérations sont plus faciles à analyser quand Da et Sa sont 0 ou 1. Par exemple, pour le mode par défautSrcOver, si Sa est 0, sc est également 0, et le résultat est [Da, Dc], l’alpha de destination et la couleur. Si Sa est 1, le résultat est [Sa, Sc], l’alpha source et la couleur, ou [1, Sc].

Les Plus modes et Modulate les modes sont un peu différents des autres dans cette nouvelle couleur peut résulter de la combinaison de la source et de la destination. Le Plus mode peut être interprété avec des composants d’octets ou des composants à virgule flottante. Dans la page Grille Porter-Duff affichée précédemment, la couleur de destination est (0xC0, 0x80, 0x00) et la couleur source est (0x00, 0x80, 0xC0). Chaque paire de composants est ajoutée, mais la somme est limitée à 0xFF. Le résultat est la couleur (0xC0, 0xFF, 0xC0). C’est la couleur affichée dans l’intersection.

Pour le Modulate mode, les valeurs RVB doivent être converties en virgule flottante. La couleur de destination est (0,75, 0,5, 0) et la source est (0, 0,5, 0,75). Les composants RVB sont multipliés ensemble et le résultat est (0, 0,25, 0). C’est la couleur affichée dans l’intersection dans la page Porter-Duff Grid pour ce mode.

La page Transparence Porter-Duff vous permet d’examiner comment fonctionnent les modes de fusion Porter-Duff sur des objets graphiques partiellement transparents. Le fichier XAML inclut un Picker avec les modes 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>

Le fichier code-behind remplit deux rectangles de la même taille à l’aide d’un dégradé linéaire. Le dégradé de destination se situe du coin supérieur droit au bas à gauche. Il est brunâtre dans le coin supérieur droit, mais puis vers le centre commence à disparaître en transparent, et est transparent dans le coin inférieur gauche.

Le rectangle source a un dégradé entre le coin supérieur gauche et le bas à droite. L’angle supérieur gauche est bleuté, mais est à nouveau fondu en transparent, et est transparent dans le coin inférieur droit.

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

Ce programme montre que les modes de fusion Porter-Duff peuvent être utilisés avec des objets graphiques autres que des bitmaps. Toutefois, la source doit inclure une zone transparente. C’est le cas ici, car le dégradé remplit le rectangle, mais une partie du dégradé est transparente.

Voici trois exemples :

Transparence porter-Duff

La configuration de la destination et de la source est très similaire aux diagrammes présentés à la page 255 du papier Porter-Duff Compositing Digital Images , mais cette page montre que les modes de fusion sont bien comportementés pour les zones de transparence partielle.

Vous pouvez utiliser des dégradés transparents pour certains effets différents. Une possibilité est le masquage, qui est similaire à la technique présentée dans les dégradés radial pour masquer la section des dégradés circulaires SkiaSharp. Une grande partie de la page Compositing Mask est similaire à celle du programme précédent. Il charge une ressource bitmap et détermine un rectangle dans lequel l’afficher. Un dégradé radial est créé en fonction d’un centre et d’un rayon prédéfins :

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

La différence avec ce programme est que le dégradé commence par le noir au centre et se termine par la transparence. Il est affiché sur la bitmap avec un mode de fusion de DstIn, qui affiche la destination uniquement dans les zones de la source qui ne sont pas transparentes.

Après l’appel DrawRect , la surface entière du canevas est transparente, à l’exception du cercle défini par le dégradé radial. Un appel final est effectué :

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

Toutes les zones transparentes de la toile sont colorées roses :

Masque de composition

Vous pouvez également utiliser des modes Porter-Duff et des dégradés partiellement transparents pour les transitions d’une image à une autre. La page Transitions de dégradé inclut un Slider niveau de progression dans la transition de 0 à 1, et un Picker pour choisir le type de transition souhaité :

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

Le fichier code-behind charge deux ressources bitmap pour illustrer la transition. Il s’agit des deux mêmes images utilisées dans la page Bitmap Dissolve plus haut dans cet article. Le code définit également une énumération avec trois membres correspondant à trois types de dégradés ( linéaire, radial et balayage). Ces valeurs sont chargées dans les Pickeréléments suivants :

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

Le fichier code-behind crée trois SKPaint objets. L’objet paint0 n’utilise pas de mode blend. Cet objet de peinture est utilisé pour dessiner un rectangle avec un dégradé allant du noir au transparent, comme indiqué dans le colors tableau. Le positions tableau est basé sur la position du Slidertableau, mais ajusté un peu. Si la Slider valeur est minimale ou maximale, les progress valeurs sont 0 ou 1, et l’une des deux bitmaps doit être entièrement visible. Le positions tableau doit être défini en conséquence pour ces valeurs.

Si la progress valeur est 0, le positions tableau contient les valeurs -0.1 et 0. SkiaSharp ajuste cette première valeur à 0, ce qui signifie que le dégradé est noir uniquement à 0 et transparent sinon. Quand progress est 0.5, le tableau contient les valeurs 0.45 et 0.55. Le dégradé est noir de 0 à 0,45, puis passe à transparent et est entièrement transparent de 0,55 à 1. Quand progress est 1, le positions tableau est 1 et 1.1, ce qui signifie que le dégradé est noir de 0 à 1.

Les colors tableaux et position les tableaux sont utilisés dans les trois méthodes de SKShader création d’un dégradé. Un seul de ces nuanceurs est créé en fonction de la Picker sélection :

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

Ce dégradé s’affiche dans le rectangle sans mode de fusion. Après cet DrawRect appel, le canevas contient simplement un dégradé de noir à transparent. La quantité de noir augmente avec des valeurs plus élevées Slider .

Dans les quatre dernières instructions du PaintSurface gestionnaire, les deux bitmaps sont affichées. Le SrcOut mode de fusion signifie que la première bitmap est affichée uniquement dans les zones transparentes de l’arrière-plan. Le DstOver mode de la deuxième bitmap signifie que la deuxième bitmap est affichée uniquement dans les zones où la première bitmap n’est pas affichée.

Les captures d’écran suivantes montrent les trois types de transitions différents, chacun à la marque de 50 % :

Transitions de dégradé