カスタム レンダリング インク
ストロークの DrawingAttributes プロパティでは、ストロークのサイズ、色、形状などの外観を指定できますが、DrawingAttributes で許可されている以上の外観のカスタマイズをしたい場合があります。 エアブラシ、油絵、およびその他の多くの効果の外観でレンダリングすることで、インクの外観をカスタマイズしたい場合もあります。 Windows Presentation Foundation (WPF) では、カスタムの DynamicRenderer と Stroke オブジェクトを実装することで、インクをカスタム レンダリングできます。
このトピックは、次の内容で構成されています。
アーキテクチャ
インク レンダリングは 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 を示します。
using System;
using System.Windows.Media;
using System.Windows;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Input;
using System.Windows.Ink;
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 : 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.
brush ??= new LinearGradientBrush(Colors.Red, Colors.Blue, 20d);
// Create a new Pen, if necessary.
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;
}
}
}
}
' 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
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
End Class
カスタム ストロークの実装
Stroke から派生するクラスを実装します。 このクラスでは、Stroke オブジェクトに変換された後の StylusPoint データがレンダリングされます。 実際の描画を行うには、DrawCore クラスをオーバーライドします。
Stroke クラスでは、AddPropertyData メソッドを使用することで、カスタム データを格納することもできます。 このデータは、保持されるときにストローク データと共に格納されます。
Stroke クラスでは、ヒット テストも実行できます。 また、現在のクラスで HitTest メソッドをオーバーライドすることにより、独自のヒット テスト アルゴリズムを実装することもできます。
次の C# コードでは、StylusPoint データを 3D ストロークとしてレンダリングするカスタム Stroke クラスを示します。
using System;
using System.Windows.Media;
using System.Windows;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Input;
using System.Windows.Ink;
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 : 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;
}
}
}
}
' 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
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
End Class
カスタム InkCanvas の実装
カスタマイズしたDynamicRenderer とストロークを使用する最も簡単な方法は、InkCanvas の派生クラスを実装し、これらのクラスを使用することです。 InkCanvas には、ユーザーが描画するときにストロークをレンダリングする方法を指定する DynamicRenderer プロパティがあります。
InkCanvas でストロークをカスタム レンダリングするには、次のようにします。
InkCanvas から派生するクラスを作成します。
カスタマイズした DynamicRenderer を InkCanvas.DynamicRenderer プロパティに割り当てます。
OnStrokeCollected メソッドをオーバーライドします。 このメソッドで、InkCanvas に追加された元のストロークを削除します。 次に、カスタム ストロークを作成し、それを Strokes プロパティに追加して、そのカスタム ストロークを含む新しい InkCanvasStrokeCollectedEventArgs で基底クラスを呼び出します。
次の C# コードでは、カスタマイズした InkCanvas を使用してカスタム ストロークを収集するカスタムの DynamicRenderer クラスを示します。
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 オブジェクトを StylusPlugIns プロパティに追加することで、InkCanvas に追加できます。
まとめ
独自の DynamicRenderer クラス、Stroke クラス、InkCanvas クラスを派生させることで、インクの外観をカスタマイズできます。 これらのクラスを合わせて、ユーザーがストロークを描画し、その後ストロークが収集される際に、ストロークの外観を一致させます。
関連項目
.NET Desktop feedback