다음을 통해 공유


잉크 렌더링 사용자 지정

스트로크의 DrawingAttributes 속성을 사용하여 크기, 색, 형태 등 스트로크의 모양을 지정할 수 있습니다. 하지만 DrawingAttributes로 가능한 것보다 더 많이 모양을 사용자 지정하려는 경우가 있을 수 있습니다. 에어 브러시, 유성 물감 등의 다른 많은 효과로 잉크를 렌더링하여 잉크 모양을 사용자 지정하려는 경우도 있을 수 있습니다. Windows Presentation Foundation (WPF)에서는 사용자 지정 DynamicRendererStroke 개체를 구현하여 잉크 렌더링을 사용자 지정할 수 있습니다.

이 항목에는 다음과 같은 하위 단원이 포함되어 있습니다.

  • 아키텍처

  • DynamicRenderer 구현

  • Implementing a Custom Stroke

  • 사용자 지정 InkCanvas 구현

  • 결론

아키텍처

잉크 렌더링은 사용자가 잉크 표면에 잉크를 쓸 때 한 번 발생하고 잉크 지원 표면에 스트로크가 추가된 후에 다시 한 번 발생합니다. DynamicRenderer는 사용자가 디지타이저에서 태블릿 펜을 이동할 때 잉크를 렌더링하고 Stroke는 요소에 추가된 후 자동으로 렌더링됩니다.

동적으로 잉크를 렌더링하는 경우 세 가지 클래스를 구현해야 합니다.

  1. DynamicRenderer: DynamicRenderer에서 파생되는 클래스를 구현합니다. 이 클래스는 스트로크를 그릴 때 이를 렌더링하는 특수 StylusPlugIn입니다. DynamicRenderer는 별도의 스레드에서 렌더링을 수행하므로 응용 프로그램 UI(사용자 인터페이스) 스레드가 차단된 경우에도 잉크 표면이 잉크를 수집할 수 있습니다. 스레딩 모델에 대한 자세한 내용은 잉크 스레딩 모델을 참조하십시오. 스트로크를 동적으로 렌더링하도록 사용자 지정하려면 OnDraw 메서드를 재정의합니다.

  2. Stroke: Stroke에서 파생되는 클래스를 구현합니다. 이 클래스는 StylusPoint 데이터가 Stroke 개체로 변환된 후 이를 정적으로 렌더링하는 작업을 담당합니다. 스트로크의 정적 렌더링과 동적 렌더링을 일관되게 하려면 DrawCore 메서드를 재정의합니다.

  3. InkCanvas: InkCanvas에서 파생되는 클래스를 구현합니다. DynamicRenderer 속성에 사용자 지정 DynamicRenderer를 할당합니다. OnStrokeCollected 메서드를 재정의하고 Strokes 속성에 사용자 지정 스트로크를 추가합니다. 이를 통해 잉크 모양이 일관되게 유지됩니다.

DynamicRenderer 구현

DynamicRenderer 클래스가 WPF의 표준 요소이기는 하지만 보다 전문적인 렌더링을 수행하려면 DynamicRenderer에서 파생되는 사용자 지정 DynamicRenderer를 만들고 OnDraw 메서드를 재정의해야 합니다.

다음 예제에서는 선형 그라데이션 브러시 효과를 사용하여 잉크를 그리는 사용자 지정 DynamicRenderer를 보여 줍니다.

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

사용자 지정 Stroke 구현

Stroke에서 파생되는 클래스를 구현합니다. 이 클래스는 StylusPoint 데이터가 Stroke 개체로 변환된 후 이를 렌더링하는 작업을 담당합니다. 실제 그리기를 수행하도록 DrawCore 클래스를 재정의합니다.

AddPropertyData 메서드를 사용하면 사용자 지정 Stroke 클래스에 사용자 지정 데이터를 저장할 수도 있습니다. 이 데이터는 보관된 스트로크 데이터와 함께 저장됩니다.

Stroke 클래스로 적중 테스트를 수행할 수도 있습니다. 현재 클래스에 HitTest 메서드를 재정의하여 고유의 적중 테스트 알고리즘을 구현할 수도 있습니다.

다음 C# 코드는 StylusPoint 데이터를 3차원 스트로크로 렌더링하는 사용자 지정 Stroke 클래스를 보여 줍니다.

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

사용자 지정 InkCanvas 구현

사용자 지정 DynamicRenderer와 스트로크를 사용하는 가장 쉬운 방법은 InkCanvas에서 파생되는 클래스를 구현하여 사용하는 것입니다. InkCanvas에는 사용자가 스트로크를 그릴 때 스트로크가 렌더링되는 방법을 지정하는 DynamicRenderer 속성이 있습니다.

InkCanvas의 스트로크 렌더링을 사용자 지정하려면 다음을 수행합니다.

다음 C# 코드는 사용자 지정 DynamicRenderer를 사용하여 사용자 지정 스트로크를 수집하는 사용자 지정 InkCanvas 클래스를 보여 줍니다.

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

    }

}

InkCanvasDynamicRenderer를 두 개 이상 가질 수 있습니다. 원하는 개체를 StylusPlugIns 속성에 추가하여 여러 개의 DynamicRenderer 개체를 InkCanvas에 추가할 수 있습니다.

결론

고유의 DynamicRenderer, Stroke, 및 InkCanvas 클래스를 파생시켜 잉크의 모양을 사용자 지정할 수 있습니다. 이러한 클래스는 모두 사용자가 스트로크를 그릴 때와 스트로크가 수집된 후의 모양이 일관되도록 만듭니다.

참고 항목

기타 리소스

고급 잉크 처리