ストロークの DrawingAttributes プロパティを使用すると、ストロークのサイズ、色、形状などの外観を指定できますが、DrawingAttributes で可能な指定以上の外観にカスタマイズする必要が生じる場合もあります。 インクの外観は、エアブラシや油絵の具などのさまざまな効果の外観でレンダリングすることでカスタマイズできます。 Windows Presentation Foundation (WPF) では、カスタムの DynamicRenderer オブジェクトと Stroke オブジェクトを実装することで、インクのカスタム レンダリングを実行できます。
このトピックは、次の内容で構成されています。
アーキテクチャ
動的レンダラーの実装
Implementing a Custom Stroke
カスタム InkCanvas の実装
まとめ
アーキテクチャ
インクのレンダリングは 2 回行われます。ユーザーがインクをインク サーフェイスに書き込んだときと、ストロークがインク対応サーフェイスに追加された後です。 DynamicRenderer はユーザーがタブレット ペンをデジタイザー上で動かしたときにインクをレンダリングし、Stroke は要素に追加されたときにレンダリングされます。
インクを動的にレンダリングするときに実装するクラスは 3 つあります。
DynamicRenderer : DynamicRenderer から派生するクラスを実装します。 このクラスは、ストロークを描画されたとおりにレンダリングする特殊な StylusPlugIn です。 DynamicRenderer は個別のスレッドでレンダリングを実行するため、アプリケーションのユーザー インターフェイス (UI) スレッドがブロックされても、インクを収集するためにインク サーフェイスが表示されます。 スレッド処理モデルの詳細については、「インク スレッド モデル」を参照してください。 ストロークの動的レンダリングをカスタマイズするには、OnDraw メソッドをオーバーライドします。
Stroke : Stroke から派生するクラスを実装します。 このクラスは、StylusPoint データが Stroke オブジェクトに変換された後に、そのデータの静的レンダリングを実行します。 ストロークの静的レンダリングが動的レンダリングと一貫するようにするには、DrawCore メソッドをオーバーライドします。
InkCanvas : InkCanvas から派生するクラスを実装します。 カスタマイズされた DynamicRenderer を DynamicRenderer プロパティに割り当てます。 OnStrokeCollected メソッドをオーバーライドし、カスタム ストロークを Strokes プロパティに追加します。 これにより、インクの外観の一貫性が確保されます。
動的レンダラーの実装
DynamicRenderer クラスは WPF の標準クラスですが、特殊なレンダリングを実行するには、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 から派生するクラスを実装します。 このクラスは、StylusPoint データが Stroke オブジェクトに変換された後に、そのデータのレンダリングを実行します。 実際の描画を実行するには、DrawCore クラスをオーバーライドします。
AddPropertyData メソッドを使用して、Stroke クラスにカスタム データを格納することもできます。 このデータは、永続化される場合、ストローク データと共に格納されます。
Stroke クラスは、ヒット テストを実行することもできます。 また、現在のクラスで HitTest メソッドをオーバーライドして、独自のヒット テスト アルゴリズムを実装することもできます。
次の C# のコードは、StylusPoint データを 3-D ストロークとしてレンダリングするカスタムの 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 でストロークのカスタム レンダリングを行うには、次の操作を実行します。
InkCanvas から派生するクラスを作成します。
カスタマイズされた DynamicRenderer を InkCanvas.DynamicRenderer プロパティに割り当てます。
OnStrokeCollected メソッドをオーバーライドします。 このメソッドで、InkCanvas に追加された元のストロークを削除します。 次に、カスタム ストロークを作成して Strokes プロパティに追加し、カスタム ストロークを含む新しい InkCanvasStrokeCollectedEventArgs を使用して基本クラスを呼び出します。
次の 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);
}
}
InkCanvas には、複数の DynamicRenderer を追加できます。 複数の DynamicRenderer オブジェクトを InkCanvas に追加するには、それらのオブジェクトを StylusPlugIns プロパティに追加します。
まとめ
インクの外観をカスタマイズするには、独自の DynamicRenderer、Stroke、および InkCanvas クラスを派生させます。 これらのクラスを合わせて、ユーザーがストロークを描画したときとそのストロークが収集された後のストロークの外観の一貫性を確保することができます。