잉크 스트로크를 텍스트로 변환하는 방법(HTML)
[ 이 문서는 Windows 런타임 앱을 작성하는 Windows에서 8.x 및 Windows Phone 8.x 개발자를 대상으로 합니다. Windows 10용으로 개발하는 경우에는 최신 설명서를 참조하세요.]
JavaScript를 사용하는 Windows 스토어 앱에서 필기 인식을 사용하고 잉크 스트로크를 텍스트로 변환하는 방법을 알아봅니다.
Windows 8.1용 업데이트: Windows 8.1에는 다양한 업데이트와 포인터 입력 API의 개선 사항이 포함되어 있습니다. 자세한 내용은 Windows 8.1의 API 변경 사항을 참조하세요.
이 예제에서는 InkManager 개체의 기본 제공 인식 기능(RecognizeAsync)을 사용하여 모든 잉크 스트로크를 텍스트로 변환하고 결과를 관리자에 저장합니다.
인식이 필요한 경우 InkStrokeContainer 개체를 InkManager 대신 사용합니다. 이전 인식 결과는 GetRecognitionResults를 통해 계속 사용할 수 있습니다.
인식만 필요하고 저장소는 필요하지 않으면 InkRecognizerContainer를 InkManager 대신 사용합니다.
알아야 할 사항
기술
사전 요구 사항
이 방법은 빠른 시작: 잉크 데이터 캡처를 기반으로 합니다.
사용자가 JavaScript용 Windows 라이브러리 템플릿을 사용하고 JavaScript로 작성된 기본 Windows 스토어 앱을 만들 수 있다고 가정합니다.
- 첫 번째 Windows 스토어 앱을 만드는 방법에 대한 자세한 내용은 JavaScript로 작성된 첫 번째 Windows 스토어 앱 만들기를 참조하세요.
- WinJS 개체와 컨트롤 사용에 대한 자세한 내용은 빠른 시작: WinJS 컨트롤 및 스타일 추가를 참조하세요.
지침
단계 1: 잉크 관리자 만들기
포인터 입력을 통해 획득한 잉크 관련 데이터를 처리하고 조작할 InkManager 개체를 초기화합니다.
// Create an ink manager.
// InkManager is documented at https://go.microsoft.com/fwlink/?LinkID=260648.
var inkManager = new Windows.UI.Input.Inking.InkManager();
단계 2: 인식을 위한 기본 언어 설정
기본 인식기 언어를 설정합니다. Windows 8의 경우 각 언어 팩에는 관련된 인식 언어 집합이 포함되어 있습니다. 유효한 인식 언어에 대한 자세한 내용은 Name을 참조하세요.
기본 언어를 설정하지 않으면 언어 제어판에서 식별된 기본 언어를 기반으로 하여 자동으로 선택됩니다. 기본 언어에 인식 언어가 포함되어 있지 않으면 보조 언어 중 하나 또는 EN-US가 사용됩니다.
참고
다음은 기본 언어를 Microsoft 영어(미국) 필기 인식기로 설정하는 방법입니다.
// Set default recognition language.
if (!setRecognizerByName("Microsoft English (US) Handwriting Recognizer")) {
statusMessage.innerText += "\nRecognition: Failed to find English (US) recognizer.";
}
else {
statusMessage.innerText += "\nRecognition: English (US) recognizer.";
}
GetRecognizers를 사용하여 사용자 시스템에서 사용할 수 있는 인식 언어를 검색합니다. 이 목록을 반복하여 원하는 언어를 찾으면 SetDefaultRecognizer를 호출합니다. 원하는 언어를 찾지 못하면 오류를 표시하고 대신 시스템 기본값을 사용합니다.
/// <summary>
/// Finds a specific recognizer, and sets the inkManager's default to that recognizer.
/// Returns true if successful.
/// </summary>
/// <param name="recognizerName">The name of the handwriting recognizer.</param>
function setRecognizerByName(recognizerName) {
try {
// recognizers is a normal JavaScript array
var recognizers = inkManager.getRecognizers();
for (var i = 0, len = recognizers.length; i < len; i++) {
if (recognizerName === recognizers[i].name) {
inkManager.setDefaultRecognizer(recognizers[i]);
return true;
}
}
}
catch (e) {
displayError("setRecognizerByName: " + e.toString());
}
return false;
}
단계 3: 인식 사용
사용자가 인식을 시작할 수 있는 방법을 제공합니다.
먼저 모드 단추 집합을 선언합니다.
<body>
<div id="applicationTitle">Ink sample</div>
<div>
<canvas id="inkCanvas"></canvas>
<div>
<button id="load">Load</button>
<button id="save">Save</button>
<button id="draw">Draw</button>
<button id="select">Select</button>
<button id="selectall">Select all</button>
<button id="erase">Erase</button>
<button id="eraseAll">Erase all</button>
<button id="recognize" value="selected">Handwriting recognition</button>
</div>
</div>
<div id="modeMessage"></div>
<div id="deviceMessage"></div>
<div id="statusMessage"></div>
</body>
그런 다음 이벤트 수신기를 선언합니다.
get("recognize").addEventListener("click", recognizeStrokes, false);
단계 4: 모든 스트로크 처리
마지막으로 인식 요청을 처리하고 인식 엔진을 통해 잉크 관리자에서 관리하는 모든 스트로크를 처리합니다.
이 예제에서는 간단히 all의 InkRecognitionTarget을 지정하여 모든 스트로크를 처리합니다. recent의 InkRecognitionTarget을 지정하면 마지막으로 RecognizeAsync를 호출한 이후에 추가된 스트로크만 처리됩니다. 이는 일반적으로 모든 스트로크가 필기로 간주되고 사용자 명령 대신 pointerup 등의 이벤트를 기반으로 하여 스트로크를 동적으로 인식해야 할 경우에 유용합니다. 선택한 스트로크만 처리하려면 selected 를 사용합니다.
/// <summary>
/// Invoked when the "Handwriting recognition" button is pressed.
/// The ink manager processes all strokes through recognizeAsync, which returns
/// the number of words detected and a set of recognition results for each word.
/// </summary>
/// <param name="evt">The event object.</param>
function recognizeStrokes(evt) {
// Ensure ink strokes exist before calling recognizeAsync.
if (inkManager.getStrokes().length > 0) {
// recognizeAsync fails if other recognition tasks are in progress.
try {
// The ink manager is used to store the recognition results.
// recognizeAsync has 3 modes: all | selected | recent.
// For this example, we process all strokes.
// recognizeAsync is documented at https://go.microsoft.com/fwlink/?LinkID=265172.
inkManager.recognizeAsync(Windows.UI.Input.Inking.InkRecognitionTarget.all).done
(
// The recognitionResult object returned by recognizeAsync exposes the
// bounding rect, strokes, and text candidates for each word.
// In this example, we simply display the word count and recognition results.
function (results) {
// recognizeAsync does not automatically update existing recognition results in the ink manager.
// updateRecognitionResults is documented at https://go.microsoft.com/fwlink/?LinkID=265175.
inkManager.updateRecognitionResults(results);
var x = inkManager.getRecognizers();
// Display the number of words returned in results.
statusMessage.innerText = "Words recognized: " + results.length.toString();
// Iterate through each word and display the ranked list of possible matches.
for (var i = 0; i < results.length; i++) {
statusMessage.innerText += "\nWord" + (i+1).toString() + ":";
var alts = results[i].getTextCandidates();
for (var j = 0; j < alts.length; j++) {
statusMessage.innerText += " " + alts[j].toString();
}
}
},
function (e) {
displayError("InkManager::recognizeAsync: " + e.toString());
}
);
}
catch (e) {
displayError("recognize: " + e.toString());
}
}
else {
statusMessage.innerText = "No strokes to recognize.";
}
}
RecognizeAsync에서 반환된 InkRecognitionResult 개체의 컬렉션을 통해 노출된 각 단어의 경계 직사각형 및 텍스트 후보를 사용하는 방법의 예를 보려면 입력: 잉크 샘플(영문)을 참조하세요. 인식이 완료된 후에 단어를 탭하여 단어 옆의 플라이아웃에 선택 가능한 텍스트 후보 목록을 표시합니다.
전체 예제
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//// PARTICULAR PURPOSE.
////
//// Copyright (c) Microsoft Corporation. All rights reserved
// Windows Store app that demonstrates the use of the Windows.UI.Input.Inking APIs.
// Ink functionality is documented at https://go.microsoft.com/fwlink/?LinkID=260649.
// User interaction functionality is documented at https://go.microsoft.com/fwlink/?LinkID=260650.
// Ink APIs are documented at https://go.microsoft.com/fwlink/?LinkID=260652.
// Pointer APIs are documented at https://go.microsoft.com/fwlink/?LinkID=260653.
(function ()
{
"use strict";
//
// Global variables
//
// UI object references.
var inkCanvas;
var inkContext;
var modeMessage;
var deviceMessage
var statusMessage;
// Create an ink manager.
// InkManager is documented at https://go.microsoft.com/fwlink/?LinkID=260648.
var inkManager = new Windows.UI.Input.Inking.InkManager();
// Initial pointer values.
var pointerId = -1;
var pointerDeviceType = null;
// Initial stroke property values.
var strokeColor;
var strokeWidth;
//
// End global variables
//
// Obtain reference to the specified element.
function get(elementId)
{
return document.getElementById(elementId);
}
function initialize()
{
// Set up the UI.
inkCanvas = get("inkCanvas");
inkContext = inkCanvas.getContext("2d");
inkContext.lineCap = "round";
inkContext.lineKJoin = "round";
inkCanvas.width = window.innerWidth - 10;
inkCanvas.height = window.innerHeight * 0.5;
deviceMessage = get("deviceMessage");
deviceMessage.innerText = "Undefined";
modeMessage = get("modeMessage");
modeMessage.innerText = inkManager.mode;
statusMessage = get("statusMessage");
statusMessage.innerText = "No pointer input detected."
// Set initial ink mode.
drawStrokes();
// Set default recognition language.
if (!setRecognizerByName("Microsoft English (US) Handwriting Recognizer")) {
statusMessage.innerText += "\nRecognition: Failed to find English (US) recognizer.";
}
else {
statusMessage.innerText += "\nRecognition: English (US) recognizer.";
}
// Set up the handlers for input processing.
inkCanvas.addEventListener("pointerdown", onPointerDown, false);
inkCanvas.addEventListener("pointermove", onPointerMove, false);
inkCanvas.addEventListener("pointerup", onPointerUp, false);
get("save").addEventListener("click", saveStrokes, false);
get("load").addEventListener("click", loadStrokes, false);
get("draw").addEventListener("click", drawStrokes, false);
get("select").addEventListener("click", selectStrokes, false);
get("selectall").addEventListener("click", selectAllStrokes, false);
get("erase").addEventListener("click", eraseStrokes, false);
get("eraseAll").addEventListener("click", eraseAllStrokes, false);
get("recognize").addEventListener("click", recognizeStrokes, false);
}
document.addEventListener("DOMContentLoaded", initialize, false);
function getPointerDeviceType(pId)
{
var pointerDeviceType;
var pointerPoint = Windows.UI.Input.PointerPoint.getCurrentPoint(pId);
switch (pointerPoint.pointerDevice.pointerDeviceType)
{
case Windows.Devices.Input.PointerDeviceType.touch:
pointerDeviceType = "Touch";
break;
case Windows.Devices.Input.PointerDeviceType.pen:
pointerDeviceType = "Pen";
break;
case Windows.Devices.Input.PointerDeviceType.mouse:
pointerDeviceType = "Mouse";
break;
default:
pointerDeviceType = "Undefined";
}
deviceMessage.innerText = pointerDeviceType;
return pointerDeviceType;
}
// Occurs when the pointer (touch, pen, mouse) is detected by the canvas.
// Each stroke begins with onPointerDown.
function onPointerDown(evt)
{
// Get the device type for the pointer input.
pointerDeviceType = getPointerDeviceType(evt.pointerId);
// Process pen and mouse (with left button) only. Reserve touch for manipulations.
if ((pointerDeviceType === "Pen") || ((pointerDeviceType === "Mouse") && (evt.button === 0)))
{
statusMessage.innerText = pointerDeviceType + " pointer down: Start stroke. "
// Process one pointer at a time.
if (pointerId === -1)
{
var current = evt.currentPoint;
// Start drawing the stroke.
inkContext.beginPath();
inkContext.lineWidth = strokeWidth;
inkContext.strokeStyle = strokeColor;
inkContext.moveTo(current.position.x, current.position.y);
// Add current pointer to the ink manager (begin stroke).
inkManager.processPointerDown(current);
// The pointer id is used to restrict input processing to the current stroke.
pointerId = evt.pointerId;
}
}
else
{
// Process touch input.
}
}
// Mouse: Occurs when the pointer moves.
// Pen/Touch: Occurs at a steady rate (approx. 100 messages/second) whether the pointer moves or not.
function onPointerMove(evt)
{
// Process pen and mouse (with left button) only. Reserve touch for manipulations.
if ((pointerDeviceType === "Pen") || ((pointerDeviceType === "Mouse") && (evt.button === -1)))
{
statusMessage.innerText = pointerDeviceType + " pointer move: Draw stroke as lines. "
// The pointer Id is used to restrict input processing to the current stroke.
// pointerId is updated in onPointerDown().
if (evt.pointerId === pointerId)
{
var current = evt.currentPoint;
// Draw stroke in real time.
inkContext.lineTo(current.rawPosition.x, current.rawPosition.y);
inkContext.stroke();
// Add current pointer to the ink manager (update stroke).
inkManager.processPointerUpdate(current);
}
}
else
{
// Process touch input.
}
}
// Occurs when the pointer (touch, pen, mouse) is lifted from the canvas.
// Each stroke ends with onPointerUp.
function onPointerUp(evt)
{
// Process pen and mouse (with left button) only. Reserve touch for manipulations.
if ((pointerDeviceType === "Pen") || ((pointerDeviceType === "Mouse") && (evt.button === 0)))
{
statusMessage.innerText = pointerDeviceType + " pointer up: Finish stroke. "
if (evt.pointerId === pointerId) {
// Add current pointer to the ink manager (end stroke).
inkManager.processPointerUp(evt.currentPoint);
// End live drawing.
inkContext.closePath();
// Render strokes using bezier curves.
renderAllStrokes();
// Reset pointer Id.
pointerId = -1;
}
}
else
{
// Process touch input.
}
}
// Render all strokes using bezier curves instead of line segments.
function renderAllStrokes()
{
statusMessage.innerText += "Render strokes as bezier curves."
// Clear the drawing surface of existing strokes.
inkContext.clearRect(0, 0, inkCanvas.width, inkCanvas.height);
// Iterate through each stroke.
inkManager.getStrokes().forEach(
function (stroke)
{
inkContext.beginPath();
if (stroke.selected) {
inkContext.lineWidth = stroke.drawingAttributes.size.width * 2;
inkContext.strokeStyle = "green";
} else {
inkContext.lineWidth = stroke.drawingAttributes.size.width;
inkContext.strokeStyle = "black";
}
// Enumerate through each line segment of the stroke.
var first = true;
stroke.getRenderingSegments().forEach(
function (segment)
{
// Move to the starting screen location of the stroke.
if (first)
{
inkContext.moveTo(segment.position.x, segment.position.y);
first = false;
}
// Calculate the bezier curve for the segment.
else
{
inkContext.bezierCurveTo(segment.bezierControlPoint1.x,
segment.bezierControlPoint1.y,
segment.bezierControlPoint2.x,
segment.bezierControlPoint2.y,
segment.position.x, segment.position.y);
}
}
);
// Draw the stroke.
inkContext.stroke();
inkContext.closePath();
}
);
}
// Set up draw mode.
function drawStrokes() {
inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.inking;
strokeColor = "black";
strokeWidth = 2;
modeMessage.innerText = inkManager.mode;
}
// Set up selection mode.
function selectStrokes() {
inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.selecting;
strokeColor = "red";
strokeWidth = 1;
modeMessage.innerText = inkManager.mode;
}
// Set up erase mode.
function eraseStrokes() {
inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.erasing;
strokeColor = "gold";
strokeWidth = 1;
modeMessage.innerText = inkManager.mode;
}
// Select all strokes handler.
function selectAllStrokes() {
inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.selecting;
strokeColor = "red";
strokeWidth = 1;
modeMessage.innerText = "Select all strokes.";
// Iterate through each stroke.
inkManager.getStrokes().forEach(
function (stroke) {
stroke.selected = 1;
}
);
renderAllStrokes();
}
// Select all strokes handler.
function eraseAllStrokes() {
inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.inking;
strokeColor = "black";
strokeWidth = 2;
modeMessage.innerText = "Erase all strokes.";
// Iterate through each stroke.
inkManager.getStrokes().forEach(
function (stroke) {
stroke.selected = 1;
}
);
inkManager.deleteSelected();
renderAllStrokes();
}
// Save all strokes owned by inkManager.
function saveStrokes()
{
// Ensure that strokes exist before calling saveAsync.
if (inkManager.getStrokes().size > 0)
{
// Set up the file save screen.
var savePicker = Windows.Storage.Pickers.FileSavePicker();
savePicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
savePicker.fileTypeChoices.insert("GIF with embedded ISF", [".gif"]);
savePicker.defaultFileExtension = ".gif";
// Set up the stream.
var saveStream = null;
// Asynchronously save the ink data to the stream.
savePicker.pickSaveFileAsync().done(
function (file)
{
if (null !== file)
{
file.openAsync(Windows.Storage.FileAccessMode.readWrite).then(
function (stream)
{
saveStream = stream;
return inkManager.saveAsync(saveStream);
}
).then(
function ()
{
return saveStream.flushAsync();
},
function (e) {
// Override the standard saveAsync error with our own.
throw new Error("saveAsync");
}
).done(
function ()
{
statusMessage.innerText = "Strokes saved as GIF with embedded ISF (.gif).";
saveStream.close();
},
function (e) {
statusMessage.innerText = "Save: " + e.toString();
// Close the stream if open.
if (saveStream) {
saveStream.close();
}
}
);
}
}
);
}
else
{
statusMessage.innerText = "No strokes to save.";
}
}
// Load strokes into an inkManager.
function loadStrokes()
{
// Set up the file open screen.
var openPicker = Windows.Storage.Pickers.FileOpenPicker();
openPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
openPicker.fileTypeFilter.replaceAll([".gif"]);
// Set up the stream.
var loadStream = null;
// Asynchronously load the ink data from the stream.
openPicker.pickSingleFileAsync().done(
function (file)
{
if (null != file)
{
file.openAsync(Windows.Storage.FileAccessMode.read).then(
function (stream) {
loadStream = stream;
return inkManager.loadAsync(loadStream);
}).done(
function()
{
var strokes = inkManager.getStrokes().length;
if (strokes === 0)
{
statusMessage.innerText = "No strokes in file.";
}
else
{
statusMessage.innerText = strokes + " strokes loaded.";
}
renderAllStrokes();
loadStream.close();
},
function (e)
{
statusMessage.innerText = "Load failed.";
if (loadStream)
{
// Close the stream if open.
loadStream.close();
}
});
}
});
}
/// <summary>
/// Finds a specific recognizer, and sets the inkManager's default to that recognizer.
/// Returns true if successful.
/// </summary>
/// <param name="recognizerName">The name of the handwriting recognizer.</param>
function setRecognizerByName(recognizerName) {
try {
// recognizers is a normal JavaScript array
var recognizers = inkManager.getRecognizers();
for (var i = 0, len = recognizers.length; i < len; i++) {
if (recognizerName === recognizers[i].name) {
inkManager.setDefaultRecognizer(recognizers[i]);
return true;
}
}
}
catch (e) {
displayError("setRecognizerByName: " + e.toString());
}
return false;
}
/// <summary>
/// Invoked when the "Handwriting recognition" button is pressed.
/// The ink manager processes all strokes through recognizeAsync, which returns
/// the number of words detected and a set of recognition results for each word.
/// </summary>
/// <param name="evt">The event object.</param>
function recognizeStrokes(evt) {
// Ensure ink strokes exist before calling recognizeAsync.
if (inkManager.getStrokes().length > 0) {
// recognizeAsync fails if other recognition tasks are in progress.
try {
// The ink manager is used to store the recognition results.
// recognizeAsync has 3 modes: all | selected | recent.
// For this example, we process all strokes.
// recognizeAsync is documented at https://go.microsoft.com/fwlink/?LinkID=265172.
inkManager.recognizeAsync(Windows.UI.Input.Inking.InkRecognitionTarget.all).done
(
// The recognitionResult object returned by recognizeAsync exposes the
// bounding rect, strokes, and text candidates for each word.
// In this example, we simply display the word count and recognition results.
function (results) {
// recognizeAsync does not automatically update existing recognition results in the ink manager.
// updateRecognitionResults is documented at https://go.microsoft.com/fwlink/?LinkID=265175.
inkManager.updateRecognitionResults(results);
var x = inkManager.getRecognizers();
// Display the number of words returned in results.
statusMessage.innerText = "Words recognized: " + results.length.toString();
// Iterate through each word and display the ranked list of possible matches.
for (var i = 0; i < results.length; i++) {
statusMessage.innerText += "\nWord" + (i+1).toString() + ":";
var alts = results[i].getTextCandidates();
for (var j = 0; j < alts.length; j++) {
statusMessage.innerText += " " + alts[j].toString();
}
}
},
function (e) {
displayError("InkManager::recognizeAsync: " + e.toString());
}
);
}
catch (e) {
displayError("recognize: " + e.toString());
}
}
else {
statusMessage.innerText = "No strokes to recognize.";
}
}
// Returns true if any strokes inside the ink manager are selected; false otherwise.
function anySelected() {
var strokes = inkManager.getStrokes();
var len = strokes.length;
for (var i = 0; i < len; i++) {
if (strokes[i].selected) {
return true;
}
}
return false;
}
})();
관련 항목
개념
참조
샘플(DOM)
샘플(Windows 스토어 앱 API)