Special Windows 10 issue 2015

Volume 30 Number 11

デジタル インク - Windows 10 における手描き入力操作

Connor Weins | Windows 2015

今回は、手描き入力機能を使って、自然なユーザー操作を可能にする方法を取り上げます。デジタル インクは、紙の上にペンを滑らすように、デジタル ペン デバイスのペン先、スタイラス、指、またはマウスの動きに合わせて画面上にレンダリングします。Windows 10 でデジタル インクと手描き入力機能を使い始めるにあたって、まずアプリで手描き入力を扱う重要性について考えます。

人間は、何世紀にもわたって、自分の考えやアイデアを手書きすることで伝えてきました。マウスやキーボードが発明されたにもかかわらず、オフィスでは付箋やホワイトボード、学校ではノート、子どもたちの手には塗り絵の本といった具合に、日常生活では紙とペンが今でも重要な役割を果たしています。

紙とペンは、直接的で形にとらわれず、人それぞれに独自性があります。紙とペンは、創造力を掻き立て、感情を表現するのに理想的です。2013 年に Psychological Science (bit.ly/1tKDrhv、英語) が発表した研究によると、考えを文字に、図表をメモに変換する行為は、これを手書きにすることによって、思考力、記憶力、学習能力を向上させるそうです。Princeton と UCLA の研究者は、この研究の中で、長期にわたる理解には、タイプ入力よりも手書きのメモの方が優れていることがわかったと記しています。

では、従来のキーボード入力に勝る手描き入力機能を利用して、紙とペンで描くようにデバイス上で手描き入力が可能になり、コンピューターの処理能力を生かして実社会では不可能なことも実現できるとしたらどうでしょう。デジタル インクでは、実社会とまったく同じようにインクの色や外観を簡単に変化させることができます。しかし、それだけにはとどまらず、手描きした内容や形状を分析してメタデータを提供することや、手描き入力をテキスト、図形、コマンドなどの別のコンテンツに変換することも可能です。その結果、日常使うノートでは再現できないまったく新しい次元の手描き入力が可能になり、アプリでの描画、メモ書き、注釈付け、操作にとってデジタル インクが強力なツールになります。ペン対応デバイスやタッチ対応デバイスの市場は拡大を続けているため、手描き入力機能はユーザーにとってもアプリ開発者にとっても対話的操作に不可欠な方法となっていくでしょう。

Windows 10 では、DirectInk プラットフォームにより、アプリにデジタル インク機能を容易に取り込めるようになっています。DirectInk は、リッチで拡張可能な Windows ランタイム (WinRT) API のセットを提供し、ユニバーサル Windows プラットフォーム (UWP) アプリで手描き入力を収集、表示、管理できるようにします。DirectInk を使用することによって、Microsoft Edge ブラウザー、ユニバーサル OneNote、および手書きパネルで使われているのと同じ優れた手描き入力機能とパフォーマンスが得られます。以下に、DirectInk がアプリに提供する機能の概要を簡単に示します。

  • 美しい手描き入力: DirectInk は入力スムージング アルゴリズムとベジエ レンダリング アルゴリズムを使って、タッチ入力でもペン入力でも手描き入力の見た目を常に鮮明かつ美しく表現します。
  • 短い遅れと少ないメモリ: DirectInk は優先度の高いバックグラウンド スレッドと入力予測を使って、手描き入力がいつでも即座にユーザーの動きに反応するようにしています。そのため、リソースを効率的に管理し、アプリのオーバーヘッドを低く保っています。
  • シンプルかつ拡張可能な API サーフェス: DirectInk は、手描き入力の収集と管理をすぐに開始できるように InkCanvas や InkPresenter などの API を提供しています。これらの API は、リッチで複雑な機能をアプリで構築できるようにする、高度な機能を提供しています。

そろそろアプリでデジタル インクを使い始めたくなったのではありませんか。ここからは、アプリで DirectInk プラットフォームを利用して、ユーザーに手描き入力の優れたエクスペリエンスを提供する方法を見ていきます。

アプリでの手描き入力の収集

デジタル インクを使い始めるには、まず、入力を収集して手描き入力としてレンダリングできるサーフェスをセットアップします。Windows 8.1 ストア アプリでは、アプリに手描き入力機能を取り込むのはやや手間がかかるプロセスで、キャンバスを作成し、入力イベントをリッスンして、独自のレンダリング コードを使ってストロークを 1 つずつレンダリングする必要がありました。ユニバーサル Windows アプリの場合、次のように InkCanvas をアプリにドロップするだけで手描き入力に着手できます。

<Grid>
  <InkCanvas x:Name="myInkCanvas"/>
</Grid>

この 1 行のコードだけで、ペン入力の収集と、その入力を黒のボールペンとしてレンダリングを開始する透明のオーバーレイが提供されます (図 1 参照)。ペンの消しゴム ボタンを使うと、消しゴムが接触した部分の収集済み手描き入力を消すこともできます。手描き入力機能を使い始めたばかりであれば、これでもすばらしいと感じますが、手描き入力の収集方法や表示方法を変更する場合はどうすればよいでしょう。

InkCanvas を使った黒のボールペンを模した手描き入力の収集
図 1 InkCanvas を使った黒のボールペンを模した手描き入力の収集

InkCanvas を通じて、InkPresenter にアクセスできます。InkPresenter は、手描き入力の見た目や入力構成を制御する機能を公開します。手描き入力にはペン入力が最高の UX を提供しますが、ペンを標準装備するシステムはあまり多くはありません。InkPresenter により、ペン、タッチ、マウスの各入力の任意に組み合わせで手描き入力を収集できるようになります。選択していない種類の入力は、単に InkCanvas の XAML 要素へのポインター イベントとして提供されます。InkPresenter では、InkCanvas で収集した手描き入力の既定の描画属性を管理することもでき、ブラシのサイズや色などを変更できるようになります。これらの機能の例として、以下のように InkCanvas を構成して、ペン、タッチ、マウスの各入力の手描き入力を収集し、カリグラフィ (草書) ブラシのエミュレーションを行います。

InkPresenter myPresenter = myInkCanvas.InkPresenter;
myPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Pen |
                               Windows.UI.Core.CoreInputDeviceTypes.Touch |
                               Windows.UI.Core.CoreInputDeviceTypes.Mouse;
InkDrawingAttributes myAttributes = myPresenter.CopyDefaultDrawingAttributes();
myAttributes.Color = Windows.UI.Colors.Crimson;
myAttributes.PenTip = PenTipShape.Rectangle;
myAttributes.PenTipTransform =
  System.Numerics.Matrix3x2.CreateRotation((float) Math.PI/4);
myAttributes.Size = new Size(2,6);
myPresenter.UpdateDefaultDrawingAttributes(myAttributes);

この結果は、図 2 のようになります。

InkPresenter の DrawingAttributes を使ったカリグラフィ ブラシのエミュレーション
図 2 InkPresenter の DrawingAttributes を使ったカリグラフィ ブラシのエミュレーション

DirectInk は、手描き入力の入力とレンダリングに多くの組み込み構成をサポートします。こうした構成を利用して、蛍光ペンでのレンダリング、1 ストローク収集時のイベントの受信、詳細入力イベントへのアクセス、高度な構成での複数のポインターによる手描き入力などが可能になります。

手描き入力の編集、保存、および読み込み

手描き入力を収集したところで、今度はこれを操作する方法を考えます。ユーザーは、多くの場合、収集した手描き入力の消去や編集、あるいは後からアクセスするための保存が必要になります。このエクスペリエンスをユーザーに提供するには、DirectInk が画面にレンダリングしたストロークの手描き入力データにアクセスして変更しなければなりません。

InkCanvas 上で収集された手描き入力は、DirectInk は InkPresenter の内部にある InkStrokeCollection に格納されます。この InkStrokeCollection は現在キャンバス上にある各ストロークを表す WinRT オブジェクトを保持し、アプリからこのコンテナーに加えた変更に応じて、変更されたストロークが画面にレンダリングされます。これにより、プログラムからストロークを追加、削除、または変更できるようになり、DirectInk は画面上でストロークに加えられた変更に関する通知を常に受け取ることができます。ここで、InkPresenter とその InkStrokeContainer 間の接続を使って実装できる一般的なユーザー操作をいくつか見てみましょう。

消去: InkCanvas は既定でペンの消しゴム ボタンを使った消去をサポートしますが、マウスやタッチで入力した手描き入力を消すには、InkPresenter を構成する必要があります。DirectInk は、InputProcessingConfiguration の Mode プロパティを使って、サポート対象の手描き入力を消すための組み込みサポートを提供します。消去モードを設定するボタンの例を以下に示します。

private void Eraser_Click(object sender,
  RoutedEventArgs e)
{
  myInkCanvas.InkPresenter.
    InputProcessingConfiguration.Mode =
    InkInputProcessingMode.Erasing;
}

このボタンをクリックすると、DirectInk が InkCanvas で収集するすべての入力は消しゴムとして扱われます。このモードを設定した後、ユーザーの入力がストロークを横切ると、そのストロークは InkPresenter の InkStrokeContainer から削除され、画面から取り除かれます。ペンを手描き入力モードで使っているときは、消しゴム ボタンは常に消去モードとして処理されます。

選択: 残念ながら、DirectInk は現時点では組み込みの選択をサポートしていません。ただし、UnprocessedInput イベントを使って選択を開発する方法を提供します。リッスンはしても、手描き入力のレンダリングは行わないように指示しているイベントを DirectInk が受け取ると、必ず UnprocessedInput イベントが発生します。この指示はすべての入力に対して実行でき、DirectInk 処理構成モードを None に設定します。また、RightDragAction プロパティを使って、マウスの右ボタンとペンのバレル ボタンだけにこのイベントが発生するように構成することもできます。

myInkCanvas.InkPresenter.InputProcessingConfiguration.RightDragAction =
  InkInputRightDragAction.LeaveUnprocessed;

たとえば、図 3 は、UnprocessedInput イベントを使ってなげなわ選択 (図 4 参照) を行い、画面上のストロークを選択する方法を示しています。

図 3 UnprocessedInput イベントを使ったなげなわ選択

...
myInkCanvas.InkPresenter.UnprocessedInput.PointerPressed += StartLasso;
myInkCanvas.InkPresenter.UnprocessedInput.PointerMoved += ContinueLasso;
myInkCanvas.InkPresenter.UnprocessedInput.PointerReleased += CompleteLasso;
...
private void StartLasso(
  InkUnprocessedInput sender,Windows.UI.Core.PointerEventArgs args)
{
  selectionLasso = new Polyline()
  {
    Stroke = new SolidColorBrush(Windows.UI.Colors.Black),
    StrokeThickness = 2,
    StrokeDashArray = new DoubleCollection() { 7, 3},
  };
  selectionLasso.Points.Add(args.CurrentPoint.RawPosition);
  AddSelectionLassoToVisualTree();
}
private void ContinueLasso(
  InkUnprocessedInput sender, Windows.UI.Core.PointerEventArgs args)
{
  selectionLasso.Points.Add(args.CurrentPoint.RawPosition);
}
private void CompleteLasso(
  InkUnprocessedInput sender, Windows.UI.Core.PointerEventArgs args)
{
  selectionLasso.Points.Add(args.CurrentPoint.RawPosition);
  bounds =
    myInkCanvas.InkPresenter.StrokeContainer.SelectWithPolyLine(
    selectionLasso.Points);
  DrawBoundingRect(bounds);
}

ストロークのなげなわ選択
図 4 ストロークのなげなわ選択

ストロークを選択したら、InkStrokeContainer.MoveSelected メソッドを使ってそのストロークを変換することや、InkStroke.PointTransform プロパティを使ってそのストロークにアフィン変換を適用することが可能です。InkStrokeContainer が管理する 1 本のストロークまたは一連のストロークをこの方法で変換すると、DirectInk がこれらの変更を取得して画面にレンダリングします。

保存と読み込み: DirectInk は Ink Serialized Format (ISF) による手描き入力の保存と読み込みをネイティブにサポートし、共有と編集が単純になるようにベクター形式で手描き入力を保存します。これには、InkStrokeContainer の SaveAsync 関数と LoadAsync 関数を使ってアクセスできます。

SaveAsync は InkStrokeContainer に現在格納されているストローク データを受け取り、ISF データを埋め込んだ GIF ファイルとして保存します。図 5 に、InkStrokeContainer に含まれる手描き入力を保存する方法を示します。

図 5 InkStrokeContainer の手描き入力の保存

var savePicker = new FileSavePicker();
savePicker.SuggestedStartLocation =
  Windows.Storage.Pickers.PickerLocationId.PicturesLibrary;
savePicker.FileTypeChoices.Add("Gif with embedded ISF",
  new System.Collections.Generic.List<string> { ".gif" });
StorageFile file = await savePicker.PickSaveFileAsync();
if (null != file)
{
  try
  {
    using (IRandomAccessStream stream =
      await file.OpenAsync(FileAccessMode.ReadWrite))
    {
      await myInkCanvas.InkPresenter.StrokeContainer.SaveAsync(stream);
    } 
  }
  catch (Exception ex)
  {
    GenerateErrorMessage();
  }
}

LoadAsync は SaveAsync と反対の動作を実行します。既に InkStrokeContainer に格納されているストロークを消去し、ISF データが埋め込まれている ISF ファイルまたは GIF ファイルから新しい一連のストロークを読み込みます。ストロークが InkStrokeContainer に読み込まれると、DirectInk が自動的に画面上に読み込んだストロークをレンダリングします。

手描き入力の高度な機能

画面上で手描き入力を編集および操作する機能は、手描き入力によるユーザーの操作を開発する際に不可欠ですが、すべてのニーズを満たすには十分ではない場合もあります。アプリがサポートする対話的操作の創造性が高いときは、DirectInk が提供する既定の対話的操作のセットをアプリで一新する必要が生じます。開発者が DirectInk を利用してビルドできるリッチで他に類を見ない手描き入力機能をいくつか詳しく見てみましょう。

手描き入力の認識: 手描き入力は単なる画面上のピクセルではありません。ユーザーの手描き入力は、画像、図表、図形、またはテキストとして解釈できます。ユーザーの手描き入力のコンテンツを認識することで、手描き入力をその意味と関連付けたり、手描き入力をそれが表す内容と交換したりできます。たとえば、ユーザーがメモ書きアプリでテキストを書いている場合、アプリでは手描き入力が表すテキストを認識し、ユーザーが検索バーにクエリを入力するときの検索結果の生成に、そのテキスト データを使うことができます。この方法によるテキストの認識は、InkRecognizerContainer によってサポートされます。図 6 に、InkRecognizerContainer を使って手描き入力を簡体字中国語文字として解釈する方法を示します。

図 6 InkRecognizerContainer を使った手描き文字の簡体字中国語文字としての解釈

async void OnRecognizeAsync(object sender, RoutedEventArgs e)
{
  InkRecognizerContainer recoContainer = new InkRecognizerContainer();
  IReadOnlyList<InkRecognizer> installedRecognizers =
    recoContainer.GetRecognizers();
  foreach (InkRecognizer recognizer in installedRecognizers)
  {
    if (recognizer.Name.Equals("Microsoft 中文(简体)手写识别器"))
    {
      recoContainer.SetDefaultRecognizer(recognizer);
      break;
    }
  }
  var results = await recoContainer.RecognizeAsync(
    myInkCanvas.InkPresenter.StrokeContainer,InkRecognitionTarget.All);
  if (results.Count > 0)
  {
    string str = "Result:";
    foreach (var r in results)
    {
      str += " " + r.GetTextCandidates()[0];
    }
  }
}

このように手描き入力をテキストとして認識できますが、InkRecognizerContainer は現在 33 種類の言語パックのテキスト認識しかサポートしません。別の言語のテキストを認識することや、記号や図形などの抽象的な手描き入力を解釈を認識する場合は、ロジックをゼロから作成しなくてはなりません。さいわい、InkStroke オブジェクトは GetInkPoints 関数を提供するため、ストロークの構築に使われた各入力点の x と y の位置を取得できます。この位置情報から自由に、1 本のストロークまたは一連のストロークの入力点を分析するアルゴリズムを作成すれば、記号、図形、コマンドなどさまざまなものを解釈できるようになります。

独立入力: DirectInk は、手描き入力の特定のストロークをレンダリングするかどうかという、単純な一連の入力規則に従って手描き入力をレンダリングするための強力なエンジンです。DirectInk はレンダリングするかどうかの判断を下すために、サポートする入力の種類や、モードと適切なドラッグ操作の構成用に指定される入力処理構成を確認します。これだけでは、アプリが手描き入力に提供する多くのコンテキストが足りません。つまり、アプリはキャンバスの特定のセクションでは手描き入力を使えない場合があります。また、あるジェスチャが認識された後は手描き入力を停止する場合もあります。このような判断を可能にするため、DirectInk では独立入力のイベントを使って、処理を開始する前に入力にアクセスできるようにしています。このようなイベントにより、DirectInk が手描き入力をレンダリングする前に入力を検査できるようになるため、手描き入力が許可されない領域で pressed イベントを受け取ったり、手描き入力を停止するジェスチャを完了する move イベントを受け取った場合に、そのイベントを単純に処理済みとしてマークすることができます。

イベントを処理済みとしてマークすると、DirectInk はストロークの処理を停止します。また、ストロークを既に処理中だった場合は、ストロークがキャンセルされるか画面から削除されます。ただし、これらのイベントを使う場合は注意が必要です。それらのイベントは UI スレッドではなく DirectInk バックグラウンド スレッドで発生するため、イベントで実行する処理の負荷が高い場合や、UI スレッドなどの速度が遅いスレッドで実行中のアクティビティを待機している場合は、インクの応答性に影響する遅延が発生する可能性があります。

カスタム ドライ: DirectInk の最も複雑な機能の 1 つにカスタム ドライ モードがあります。このモードにより、アプリは独自の DirectX サーフェスで完成したストロークまたは "ドライ" インク ストロークをレンダリングおよび管理できます。アプリが対応するほとんどのシナリオに DirectInk の既定のドライ モードで対処できますが、次のような少数のシナリオでは開発者が手描き入力を個別に管理する必要があります。

  • z オーダーを維持しながら、手描き入力のコンテンツと手描き入力以外のコンテンツ (テキスト、図形) をインターリーブする
  • 手描き入力の大量のストロークを含む大きな手描き入力キャンバスでパフォーマンスを損なわずにパンやズームを行う
  • インクを直線や図形のような DirectX オブジェクトに同期を取ってドライする

Windows 10 のカスタム ドライ モードは SurfaceImageSource (SIS) または VirtualSurfaceImageSource (VSIS) との同期をサポートします。SIS と VSIS はどちらも、アプリが描画および構成するための DirectX 共有サーフェスを提供しますが、VSIS はパフォーマンスを損なわずにパンやズームを行うために、画面より大きい仮想サーフェスを提供します。こうしたサーフェスの表示更新は XAML の UI スレッドと同期されるため、手描き入力を SIS や VSIS にレンダリングすると同時に、その手描き入力は DirectInk ウェット レイヤーから取り除かれます。カスタム ドライは、手描き入力の SwapChainPanel へのドライもサポートしますが、同期は保証しません。SwapChainPanel は UI スレッドと同期されないため、手描き入力を SwapChainPanel にレンダリングするタイミングと、手描き入力が DirectInk ウェット インク レイヤーから取り除かれるタイミングが若干ずれます。

カスタム ドライをアクティブにすると、DirectInk が既定で提供する機能の大半を細かく制御できるようになるため、ドライ サーフェスで手描き入力をレンダリングおよび消去するロジックを作成し、手描き入力のストローク データをアプリで管理する方法を決定できるようになります。こうした機能の作成を支援するために、多くの DirectInk コンポーネントをスタンドアロンのオブジェクトとして利用して、アプリで機能のギャップを埋められるようにしています。カスタム ドライをアクティブにすると、DirectInk は InkSynchronizer オブジェクトを提供します。このオブジェクトにより、開発者がドライ プロセスを開始および終了できるようになるため、手描き入力をカスタム ドライ レイヤーに追加すると、DirectInk ウェット レイヤーから同期を取って取り除かれます。DirectInk の既定のドライ インク レンダリング ロジックは、InkD2DRender を通じて利用することもでき、ウェット レイヤーとドライ レイヤーの間での手描き入力の外観に一貫性が保たれます。消去する場合、UnprocessedInput イベントを使って、上記の例と同様の消去ロジックを作成できます。

カスタム ドライを使う際の詳細と例については、GitHub (bit.ly/1NkRjt7、英語) で入手できる ComplexInk サンプルを参照してください。

手描き入力による作成の開始

ここまで調べてきた InkCanvas、InkPresenter、および InkStrokeContainer を利用して、さまざまな種類の入力による手描き入力を収集し、画面に表示される手描き入力の外観をカスタマイズして、ストローク データにアクセスし、ストローク データへの変更を DirectInk がレンダリングする手描き入力のストロークに反映できます。単純なレベルの機能により、簡単ないたずら書きから、メモ書きや署名集めなどのシナリオに重点を置いた機能まで、さまざまなユーザー操作を構築できます。また、InkRecognizerContainer、独立入力イベント、カスタム ドライ モードを使ってより複雑な操作を構築するためのツールもあります。

それらのツールを駆使し、デジタル インクが手描き入力機能の優れたエクスペリエンスを通じてユーザーに提供するすべてのメリットをアプリに取り入れることができます。ペン対応デバイスやタッチ対応デバイスの数は増え続けているため、手描き入力の優れたエクスペリエンスを提供することが、ユーザーの満足度とアプリの差別化にとってますます重要になります。少し時間を割いて、デジタル インク機能がアプリでどのように動作するかを考え、DirectInk を試しに使ってみてはどうでしょう。

最後に、手描き入力機能はマイクロソフトにとって重要な投資を行った分野です。今後のリリースで DirectInk プラットフォームを改善および拡張するためには、開発者コミュニティからのフィードバックが非常に大切です。DirectInk の開発過程での質問やご意見、ご感想がありましたら、DirectInk@microsoft.com (英語) までお送りください。


Connor Weins は、デベロッパー エコシステム プラットフォーム グループのペン、スタイラス、および手描き入力機能チームでプログラム マネージャーを努めています。連絡先は、conwei@microsoft.com (英語のみ) です。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Krishnan Menon と Xiao Tu に心より感謝いたします。
Krishnan Menon は、Windows コア入力プラットフォーム チームのシニア ソフトウェア エンジニアです。Windows 10 では、DirectInk プラットフォームと、ペンおよび手描き入力対応のユニバーサル Windows API の設計、開発に従事しました。

Xiao Tu は、Windows コア入力プラットフォーム チームのプリンシパル ソフトウェア エンジニアリング リードを務めています。Windows 10 では、ペンと手描き入力対応のユニバーサル Windows プラットフォーム API を提供することに携わっています。彼は 2004 年からマイクロソフトに勤務しており、Win32 手描き入力プラットフォーム、Windows Presentation Foundation (WPF) 手描き入力プラットフォーム、Windows 7 と Windows 8 のマルチタッチ プラットフォーム、および IE 11 ポインター入力と高 DPI のサポートに取り組んできました。