Partilhar via


Renderização Personalizada de Tinta

A propriedade DrawingAttributes de um traço permite que você especifique a aparência de um traço, como seu tamanho, cor e forma, mas pode haver ocasiões em que você deseja personalizar a aparência além do que DrawingAttributes permite. Convém personalizar a aparência da tinta renderizando na aparência de um pincel de ar, pintura a óleo e muitos outros efeitos. O Windows Presentation Foundation (WPF) permite renderização personalizada de tinta implementando um objeto personalizado DynamicRenderer e Stroke.

Este tópico contém as subseções a seguir:

  • Architecture

  • Implementing a Dynamic Renderer

  • Implementing a Custom Stroke

  • Implementing a Custom InkCanvas

  • Conclusion

Arquitetura

Renderização de tinta ocorre duas vezes; quando um usuário grava tinta em uma superfície de escrita à tinta, e novamente após o traço ser adicionado à superfície que permite tinta. O DynamicRenderer renderiza a tinta quando o usuário move a caneta eletrônica no digitalizador, e o Stroke renderiza a si próprio assim que ele é adicionado a um elemento.

Há três classes para implementar quando se realiza renderização dinâmica de tinta.

  1. DynamicRenderer: Implemente uma classe que seja derivada de DynamicRenderer. Essa classe é um StylusPlugIn especializado que processa o traço conforme ele é desenhado. O DynamicRenderer faz a renderização em uma thread separada, para que a superfície de tinta pareça coletar tinta mesmo que a thread de interface de usuário (UI) da aplicação esteja bloqueada. Para obter mais informações sobre o modelo de threads, consulte O modelo de encadeamento de tinta. Para personalizar a renderização dinâmica de um traço, substitua o método OnDraw.

  2. Traçado: Implemente uma classe que seja derivada de Stroke. Essa classe é responsável pela renderização estática dos dados de StylusPoint depois de ele ter sido convertido em um objeto Stroke. Sobrescrever o método DrawCore para garantir que renderização estática do traço está consistente com a renderização dinâmica.

  3. InkCanvas: Implementar uma classe que deriva de InkCanvas. Atribua o DynamicRenderer personalizado à propriedade DynamicRenderer. Sobrescrever o método OnStrokeCollected e adicionar um traço personalizado à propriedade Strokes. Isso assegura que a aparência da tinta seja consistente.

Implementando um Dynamic Renderer

Embora a classe DynamicRenderer seja uma parte padrão de WPF, para executar renderização mais especializada, você deve criar um Dynamic Renderer personalizado que é derivado de DynamicRenderer e substituir o método OnDraw.

O exemplo a seguir demonstra um DynamicRenderer personalizado que desenha tinta com um efeito de pincel de gradiente linear.

Imports System
Imports System.Windows.Media
Imports System.Windows
Imports System.Windows.Input.StylusPlugIns
Imports System.Windows.Input
Imports System.Windows.Ink


...


' A StylusPlugin that renders ink with a linear gradient brush effect.
Class CustomDynamicRenderer
    Inherits DynamicRenderer
    <ThreadStatic()> _
    Private Shared brush As Brush = Nothing

    <ThreadStatic()> _
    Private Shared pen As Pen = Nothing

    Private prevPoint As Point


    Protected Overrides Sub OnStylusDown(ByVal rawStylusInput As RawStylusInput)
        ' Allocate memory to store the previous point to draw from.
        prevPoint = New Point(Double.NegativeInfinity, Double.NegativeInfinity)
        MyBase.OnStylusDown(rawStylusInput)

    End Sub 'OnStylusDown


    Protected Overrides Sub OnDraw(ByVal drawingContext As DrawingContext, _
                                   ByVal stylusPoints As StylusPointCollection, _
                                   ByVal geometry As Geometry, _
                                   ByVal fillBrush As Brush)

        ' Create a new Brush, if necessary.
        If brush Is Nothing Then
            brush = New LinearGradientBrush(Colors.Red, Colors.Blue, 20.0)
        End If

        ' Create a new Pen, if necessary.
        If pen Is Nothing Then
            pen = New Pen(brush, 2.0)
        End If

        ' Draw linear gradient ellipses between 
        ' all the StylusPoints that have come in.
        Dim i As Integer
        For i = 0 To stylusPoints.Count - 1

            Dim pt As Point = CType(stylusPoints(i), Point)
            Dim v As Vector = Point.Subtract(prevPoint, pt)

            ' Only draw if we are at least 4 units away 
            ' from the end of the last ellipse. Otherwise, 
            ' we're just redrawing and wasting cycles.
            If v.Length > 4 Then
                ' Set the thickness of the stroke based 
                ' on how hard the user pressed.
                Dim radius As Double = stylusPoints(i).PressureFactor * 10.0
                drawingContext.DrawEllipse(brush, pen, pt, radius, radius)
                prevPoint = pt
            End If
        Next i

    End Sub 'OnDraw
End Class 'CustomDynamicRenderer
using System;
using System.Windows.Media;
using System.Windows;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Input;
using System.Windows.Ink;


...


// A StylusPlugin that renders ink with a linear gradient brush effect.
class CustomDynamicRenderer : DynamicRenderer
{
    [ThreadStatic]
    static private Brush brush = null;

    [ThreadStatic]
    static private Pen pen = null;

    private Point prevPoint;

    protected override void OnStylusDown(RawStylusInput rawStylusInput)
    {
        // Allocate memory to store the previous point to draw from.
        prevPoint = new Point(double.NegativeInfinity, double.NegativeInfinity);
        base.OnStylusDown(rawStylusInput);
    }

    protected override void OnDraw(DrawingContext drawingContext,
                                   StylusPointCollection stylusPoints,
                                   Geometry geometry, Brush fillBrush)
    {
        // Create a new Brush, if necessary.
        if (brush == null)
        {
            brush = new LinearGradientBrush(Colors.Red, Colors.Blue, 20d);
        }

        // Create a new Pen, if necessary.
        if (pen == null)
        {
            pen = new Pen(brush, 2d);
        }

        // Draw linear gradient ellipses between 
        // all the StylusPoints that have come in.
        for (int i = 0; i < stylusPoints.Count; i++)
        {
            Point pt = (Point)stylusPoints[i];
            Vector v = Point.Subtract(prevPoint, pt);

            // Only draw if we are at least 4 units away 
            // from the end of the last ellipse. Otherwise, 
            // we're just redrawing and wasting cycles.
            if (v.Length > 4)
            {
                // Set the thickness of the stroke based 
                // on how hard the user pressed.
                double radius = stylusPoints[i].PressureFactor * 10d;
                drawingContext.DrawEllipse(brush, pen, pt, radius, radius);
                prevPoint = pt;
            }
        }
    }
}

Implementando Traços Personalizados

Implemente uma classe que seja derivada de Stroke. Essa classe é responsável pela renderização dos dados de StylusPoint depois de ele ter sido convertido em um objeto Stroke. Substitua a classe DrawCore para fazer o desenho real.

A classe Stroke também pode armazenar dados personalizados usando o método AddPropertyData. Esses dados são armazenados com os dados de traço quando persistidos.

A classe Stroke também pode executar teste de clique. Você também pode implementar seu próprio algoritmo de teste de clique substituindo o método HitTest na classe atual.

O seguinte código C# demonstra uma classe Stroke personalizada que renderiza dados StylusPoint como um traço 3-D.

Imports System
Imports System.Windows.Media
Imports System.Windows
Imports System.Windows.Input.StylusPlugIns
Imports System.Windows.Input
Imports System.Windows.Ink


...


' A class for rendering custom strokes
Class CustomStroke
    Inherits Stroke
    Private brush As Brush
    Private pen As Pen


    Public Sub New(ByVal stylusPoints As StylusPointCollection)
        MyBase.New(stylusPoints)
        ' Create the Brush and Pen used for drawing.
        brush = New LinearGradientBrush(Colors.Red, Colors.Blue, 20.0)
        pen = New Pen(brush, 2.0)

    End Sub 'New


    Protected Overrides Sub DrawCore(ByVal drawingContext As DrawingContext, _
                                     ByVal drawingAttributes As DrawingAttributes)

        ' Allocate memory to store the previous point to draw from.
        Dim prevPoint As New Point(Double.NegativeInfinity, Double.NegativeInfinity)

        ' Draw linear gradient ellipses between 
        ' all the StylusPoints in the Stroke.
        Dim i As Integer
        For i = 0 To Me.StylusPoints.Count - 1
            Dim pt As Point = CType(Me.StylusPoints(i), Point)
            Dim v As Vector = Point.Subtract(prevPoint, pt)

            ' Only draw if we are at least 4 units away 
            ' from the end of the last ellipse. Otherwise, 
            ' we're just redrawing and wasting cycles.
            If v.Length > 4 Then
                ' Set the thickness of the stroke 
                ' based on how hard the user pressed.
                Dim radius As Double = Me.StylusPoints(i).PressureFactor * 10.0
                drawingContext.DrawEllipse(brush, pen, pt, radius, radius)
                prevPoint = pt
            End If
        Next i

    End Sub 'DrawCore
End Class 'CustomStroke
using System;
using System.Windows.Media;
using System.Windows;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Input;
using System.Windows.Ink;


...


// A class for rendering custom strokes
class CustomStroke : Stroke
{
    Brush brush;
    Pen pen;

    public CustomStroke(StylusPointCollection stylusPoints)
        : base(stylusPoints)
    {
        // Create the Brush and Pen used for drawing.
        brush = new LinearGradientBrush(Colors.Red, Colors.Blue, 20d);
        pen = new Pen(brush, 2d);
    }

    protected override void DrawCore(DrawingContext drawingContext, 
                                     DrawingAttributes drawingAttributes)
    {
        // Allocate memory to store the previous point to draw from.
        Point prevPoint = new Point(double.NegativeInfinity, 
                                    double.NegativeInfinity);

        // Draw linear gradient ellipses between 
        // all the StylusPoints in the Stroke.
        for (int i = 0; i < this.StylusPoints.Count; i++)
        {
            Point pt = (Point)this.StylusPoints[i];
            Vector v = Point.Subtract(prevPoint, pt);

            // Only draw if we are at least 4 units away 
            // from the end of the last ellipse. Otherwise, 
            // we're just redrawing and wasting cycles.
            if (v.Length > 4)
            {
                // Set the thickness of the stroke 
                // based on how hard the user pressed.
                double radius = this.StylusPoints[i].PressureFactor * 10d;
                drawingContext.DrawEllipse(brush, pen, pt, radius, radius);
                prevPoint = pt;
            }
        }
    }
}

Implementando um InkCanvas personalizado

A maneira mais fácil de usar seu DynamicRenderer e traço personalizados é implementar uma classe que seja derivada de InkCanvas e usar essas classes. O InkCanvas possui uma propriedade DynamicRenderer que especifica como o traço é renderizado quando o usuário está desenhando-o.

Para renderizar traços de modo personalizado em um InkCanvas faça o seguinte:

O seguinte código C# demonstra uma classe InkCanvas personalizada que usa um DynamicRenderer personalizado e coleta traços personalizados.

Public Class CustomRenderingInkCanvas
    Inherits InkCanvas

    Private customRenderer As New CustomDynamicRenderer()

    Public Sub New()
        ' Use the custom dynamic renderer on the
        ' custom InkCanvas.
        Me.DynamicRenderer = customRenderer

    End Sub 'New

    Protected Overrides Sub OnStrokeCollected(ByVal e As InkCanvasStrokeCollectedEventArgs)

        ' Remove the original stroke and add a custom stroke.
        Me.Strokes.Remove(e.Stroke)
        Dim customStroke As New CustomStroke(e.Stroke.StylusPoints)
        Me.Strokes.Add(customStroke)

        ' Pass the custom stroke to base class' OnStrokeCollected method.
        Dim args As New InkCanvasStrokeCollectedEventArgs(customStroke)
        MyBase.OnStrokeCollected(args)

    End Sub 'OnStrokeCollected 
End Class 'CustomRenderingInkCanvas
public class CustomRenderingInkCanvas : InkCanvas
{
    CustomDynamicRenderer customRenderer = new CustomDynamicRenderer();

    public CustomRenderingInkCanvas() : base()
    {
        // Use the custom dynamic renderer on the
        // custom InkCanvas.
        this.DynamicRenderer = customRenderer;
    }

    protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
    {
        // Remove the original stroke and add a custom stroke.
        this.Strokes.Remove(e.Stroke);
        CustomStroke customStroke = new CustomStroke(e.Stroke.StylusPoints);
        this.Strokes.Add(customStroke);

        // Pass the custom stroke to base class' OnStrokeCollected method.
        InkCanvasStrokeCollectedEventArgs args = 
            new InkCanvasStrokeCollectedEventArgs(customStroke);
        base.OnStrokeCollected(args);

    }

}

Um InkCanvas pode ter mais de um DynamicRenderer. Você pode adicionar vários objetos DynamicRenderer ao InkCanvas adicionando-os à propriedade StylusPlugIns.

Conclusão

Você pode personalizar a aparência de tinta criando suas próprias classes DynamicRenderer, Stroke e InkCanvas. Juntas, essas classes garantem que a aparência do traço seja consistente quando o usuário desenha o traço e depois que ele é coletado.

Consulte também

Outros recursos

Advanced Ink Handling