Windows Ink 스트로크 데이터 저장 및 검색

Windows 잉크를 지원하는 Windows 앱은 ISF(Ink Serialized Format) 파일에 잉크 스트로크를 직렬화 및 역직렬화할 수 있습니다. ISF 파일은 모든 잉크 스트로크 속성 및 동작에 대한 추가 메타데이터가 포함된 GIF 이미지입니다. 잉크를 사용하지 않는 앱은 알파 채널 배경 투명도를 포함하여 정적 GIF 이미지를 볼 수 있습니다.

참고 항목

ISF는 잉크의 가장 압축된 영구 표현입니다. GIF 파일과 같은 이진 문서 형식 내에 포함되거나 클립보드에 직접 배치할 수 있습니다.

ISF(Ink Serialized Format) 사양은 Microsoft 다운로드 센터에서 다운로드할 수 있습니다.

중요 API: InkCanvas, Windows.UI.Input.Inking

파일에 잉크 스트로크 저장

여기서는 InkCanvas 컨트롤에 그려진 잉크 스트로크를 저장하는 방법을 보여 줍니다.

ISF(Ink Serialized Format) 파일에서 잉크 스트로크 저장 및 로드에서 이 샘플을 다운로드하세요.

  1. 먼저 UI를 설정합니다.

    UI에는 "저장", "로드" 및 "지우기" 단추 및 InkCanvas가 포함됩니다.

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0">
            <TextBlock x:Name="Header" 
                       Text="Basic ink store sample" 
                       Style="{ThemeResource HeaderTextBlockStyle}" 
                       Margin="10,0,0,0" />
            <Button x:Name="btnSave" 
                    Content="Save" 
                    Margin="50,0,10,0"/>
            <Button x:Name="btnLoad" 
                    Content="Load" 
                    Margin="50,0,10,0"/>
            <Button x:Name="btnClear" 
                    Content="Clear" 
                    Margin="50,0,10,0"/>
        </StackPanel>
        <Grid Grid.Row="1">
            <InkCanvas x:Name="inkCanvas" />
        </Grid>
    </Grid>
  1. 그런 다음 몇 가지 기본 잉크 입력 동작을 설정합니다.

    InkPresenter는 펜과 마우스의 입력 데이터를 잉크 스트로크(InputDeviceTypes)로 해석하도록 구성되며, 단추의 클릭 이벤트에 대한 수신기가 선언됩니다.

public MainPage()
    {
        this.InitializeComponent();

        // Set supported inking device types.
        inkCanvas.InkPresenter.InputDeviceTypes =
            Windows.UI.Core.CoreInputDeviceTypes.Mouse |
            Windows.UI.Core.CoreInputDeviceTypes.Pen;

        // Listen for button click to initiate save.
        btnSave.Click += btnSave_Click;
        // Listen for button click to initiate load.
        btnLoad.Click += btnLoad_Click;
        // Listen for button click to clear ink canvas.
        btnClear.Click += btnClear_Click;
    }
  1. 마지막으로 저장 단추의 클릭 이벤트 처리기에 잉크를 저장합니다.

    FileSavePicker를 사용하면 사용자가 파일과 잉크 데이터가 저장되는 위치를 모두 선택할 수 있습니다.

    파일이 선택되면 ReadWrite로 설정된 IRandomAccessStream 스트림을 엽니다.

    그런 다음 SaveAsync를 호출하여 InkStrokeContainer에서 관리하는 잉크 스트로크를 스트림으로 직렬화합니다.

// Save ink data to a file.
    private async void btnSave_Click(object sender, RoutedEventArgs e)
    {
        // Get all strokes on the InkCanvas.
        IReadOnlyList<InkStroke> currentStrokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();

        // Strokes present on ink canvas.
        if (currentStrokes.Count > 0)
        {
            // Let users choose their ink file using a file picker.
            // Initialize the picker.
            Windows.Storage.Pickers.FileSavePicker savePicker = 
                new Windows.Storage.Pickers.FileSavePicker();
            savePicker.SuggestedStartLocation = 
                Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary;
            savePicker.FileTypeChoices.Add(
                "GIF with embedded ISF", 
                new List<string>() { ".gif" });
            savePicker.DefaultFileExtension = ".gif";
            savePicker.SuggestedFileName = "InkSample";

            // Show the file picker.
            Windows.Storage.StorageFile file = 
                await savePicker.PickSaveFileAsync();
            // When chosen, picker returns a reference to the selected file.
            if (file != null)
            {
                // Prevent updates to the file until updates are 
                // finalized with call to CompleteUpdatesAsync.
                Windows.Storage.CachedFileManager.DeferUpdates(file);
                // Open a file stream for writing.
                IRandomAccessStream stream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);
                // Write the ink strokes to the output stream.
                using (IOutputStream outputStream = stream.GetOutputStreamAt(0))
                {
                    await inkCanvas.InkPresenter.StrokeContainer.SaveAsync(outputStream);
                    await outputStream.FlushAsync();
                }
                stream.Dispose();

                // Finalize write so other apps can update file.
                Windows.Storage.Provider.FileUpdateStatus status =
                    await Windows.Storage.CachedFileManager.CompleteUpdatesAsync(file);

                if (status == Windows.Storage.Provider.FileUpdateStatus.Complete)
                {
                    // File saved.
                }
                else
                {
                    // File couldn't be saved.
                }
            }
            // User selects Cancel and picker returns null.
            else
            {
                // Operation cancelled.
            }
        }
    }

참고 항목

GIF는 잉크 데이터를 저장하는 데 지원되는 유일한 파일 형식입니다. 그러나 다음 섹션에서 설명한 LoadAsync 메서드는 이전 버전과의 호환성을 위해 추가 형식을 지원합니다.

파일에서 잉크 스트로크 로드

여기서는 파일에서 잉크 스트로크를 로드하고 InkCanvas 컨트롤에 렌더링하는 방법을 보여 줍니다.

ISF(Ink Serialized Format) 파일에서 잉크 스트로크 저장 및 로드에서 이 샘플을 다운로드하세요.

  1. 먼저 UI를 설정합니다.

    UI에는 "저장", "로드" 및 "지우기" 단추 및 InkCanvas가 포함됩니다.

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0">
            <TextBlock x:Name="Header" 
                       Text="Basic ink store sample" 
                       Style="{ThemeResource HeaderTextBlockStyle}" 
                       Margin="10,0,0,0" />
            <Button x:Name="btnSave" 
                    Content="Save" 
                    Margin="50,0,10,0"/>
            <Button x:Name="btnLoad" 
                    Content="Load" 
                    Margin="50,0,10,0"/>
            <Button x:Name="btnClear" 
                    Content="Clear" 
                    Margin="50,0,10,0"/>
        </StackPanel>
        <Grid Grid.Row="1">
            <InkCanvas x:Name="inkCanvas" />
        </Grid>
    </Grid>
  1. 그런 다음 몇 가지 기본 잉크 입력 동작을 설정합니다.

    InkPresenter는 펜과 마우스의 입력 데이터를 잉크 스트로크(InputDeviceTypes)로 해석하도록 구성되며, 단추의 클릭 이벤트에 대한 수신기가 선언됩니다.

public MainPage()
    {
        this.InitializeComponent();

        // Set supported inking device types.
        inkCanvas.InkPresenter.InputDeviceTypes =
            Windows.UI.Core.CoreInputDeviceTypes.Mouse |
            Windows.UI.Core.CoreInputDeviceTypes.Pen;

        // Listen for button click to initiate save.
        btnSave.Click += btnSave_Click;
        // Listen for button click to initiate load.
        btnLoad.Click += btnLoad_Click;
        // Listen for button click to clear ink canvas.
        btnClear.Click += btnClear_Click;
    }
  1. 마지막으로 로드 단추의 클릭 이벤트 처리기에 잉크를 로드합니다.

    FileOpenPicker를 사용하면 사용자가 저장된 잉크 데이터를 검색할 위치와 파일을 모두 선택할 수 있습니다.

    파일이 선택되면 Read로 설정된 IRandomAccessStream 스트림을 엽니다.

    그런 다음 LoadAsync를 호출하여 저장된 잉크 스트로크를 읽고, 직렬화 해제하고, InkStrokeContainer에 로드합니다. InkStrokeContainer에 스트로크를 로드하면 InkPresenter가 해당 스트로크를 InkCanvas에 즉시 렌더링합니다.

    참고 항목

    새 스트로크가 로드되기 전에 InkStrokeContainer의 모든 기존 스트로크가 지워집니다.

// Load ink data from a file.
private async void btnLoad_Click(object sender, RoutedEventArgs e)
{
    // Let users choose their ink file using a file picker.
    // Initialize the picker.
    Windows.Storage.Pickers.FileOpenPicker openPicker =
        new Windows.Storage.Pickers.FileOpenPicker();
    openPicker.SuggestedStartLocation =
        Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary;
    openPicker.FileTypeFilter.Add(".gif");
    // Show the file picker.
    Windows.Storage.StorageFile file = await openPicker.PickSingleFileAsync();
    // User selects a file and picker returns a reference to the selected file.
    if (file != null)
    {
        // Open a file stream for reading.
        IRandomAccessStream stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
        // Read from file.
        using (var inputStream = stream.GetInputStreamAt(0))
        {
            await inkCanvas.InkPresenter.StrokeContainer.LoadAsync(inputStream);
        }
        stream.Dispose();
    }
    // User selects Cancel and picker returns null.
    else
    {
        // Operation cancelled.
    }
}

참고 항목

GIF는 잉크 데이터를 저장하는 데 지원되는 유일한 파일 형식입니다. 그러나 LoadAsync 메서드는 이전 버전과의 호환성을 위해 다음 형식을 지원합니다.

서식 설명
InkSerializedFormat ISF를 사용하여 유지되는 잉크를 지정합니다. 이는 잉크의 가장 압축된 영구 표현입니다. 이진 문서 형식 내에 포함되거나 클립보드에 직접 배치할 수 있습니다.
Base64InkSerializedFormat ISF를 base64 스트림으로 인코딩하여 유지되는 잉크를 지정합니다. 이 형식은 XML 또는 HTML 파일에서 직접 잉크를 인코딩할 수 있도록 제공됩니다.
Gif ISF가 포함된 GIF 파일을 파일 내에 포함된 메타데이터로 사용하여 유지되는 잉크를 지정합니다. 이렇게 하면 잉크를 사용할 수 없는 애플리케이션에서 잉크를 볼 수 있으며 잉크 사용 애플리케이션으로 반환될 때 전체 잉크 충실도를 유지할 수 있습니다. 이 형식은 HTML 파일 내에서 잉크 콘텐츠를 전송하고 잉크 및 잉크가 아닌 애플리케이션에서 사용할 수 있도록 하는 데 적합합니다.
Base64Gif base64로 인코딩된 강화된 GIF를 사용하여 유지되는 잉크를 지정합니다. 이 형식은 잉크를 XML 또는 HTML 파일에서 직접 인코딩하여 나중에 이미지로 변환할 때 제공됩니다. 가능한 사용은 모든 잉크 정보를 포함하도록 생성된 XML 형식이며 XSLT(Extensible Stylesheet Language Transformations)를 통해 HTML을 생성하는 데 사용됩니다.

잉크 스트로크를 복사하여 클립보드에 붙여넣기

여기서는 클립보드를 사용하여 앱 간에 잉크 스트로크를 전송하는 방법을 보여줍니다.

클립보드 기능을 지원하려면 기본 제공 InkStrokeContainer 잘라내기 및 복사 명령을 사용하려면 하나 이상의 잉크 스트로크를 선택해야 합니다.

이 예제에서는 펜 배럴 단추(또는 마우스 오른쪽 단추)를 사용하여 입력을 수정할 때 스트로크 선택을 사용하도록 설정합니다. 스트로크 선택을 구현하는 방법에 대한 전체 예제는 펜 및 스타일러스 상호 작용의 고급 처리에 대한 통과 입력을 참조하세요.

클립보드에서 잉크 스트로크 저장 및 로드에서 이 샘플을 다운로드하세요.

  1. 먼저 UI를 설정합니다.

    UI에는 InkCanvas 및 선택 캔버스와 함께 "잘라내기", "복사", "붙여넣기" 및 "지우기" 단추가 포함됩니다.

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0">
            <TextBlock x:Name="tbHeader" 
                       Text="Basic ink store sample" 
                       Style="{ThemeResource HeaderTextBlockStyle}" 
                       Margin="10,0,0,0" />
            <Button x:Name="btnCut" 
                    Content="Cut" 
                    Margin="20,0,10,0"/>
            <Button x:Name="btnCopy" 
                    Content="Copy" 
                    Margin="20,0,10,0"/>
            <Button x:Name="btnPaste" 
                    Content="Paste" 
                    Margin="20,0,10,0"/>
            <Button x:Name="btnClear" 
                    Content="Clear" 
                    Margin="20,0,10,0"/>
        </StackPanel>
        <Grid x:Name="gridCanvas" Grid.Row="1">
            <!-- Canvas for displaying selection UI. -->
            <Canvas x:Name="selectionCanvas"/>
            <!-- Inking area -->
            <InkCanvas x:Name="inkCanvas"/>
        </Grid>
    </Grid>
  1. 그런 다음 몇 가지 기본 잉크 입력 동작을 설정합니다.

    InkPresenter는 펜과 마우스의 입력 데이터를 잉크 스트로크(InputDeviceTypes)로 해석하도록 구성됩니다. 선택 기능에 대한 포인터 및 스트로크 이벤트뿐만 아니라 단추의 클릭 이벤트에 대한 수신기도 여기에 선언되어 있습니다.

    스트로크 선택을 구현하는 방법에 대한 전체 예제는 펜 및 스타일러스 상호 작용의 고급 처리에 대한 통과 입력을 참조하세요.

public MainPage()
    {
        this.InitializeComponent();

        // Set supported inking device types.
        inkCanvas.InkPresenter.InputDeviceTypes =
            Windows.UI.Core.CoreInputDeviceTypes.Mouse |
            Windows.UI.Core.CoreInputDeviceTypes.Pen;

        // Listen for button click to cut ink strokes.
        btnCut.Click += btnCut_Click;
        // Listen for button click to copy ink strokes.
        btnCopy.Click += btnCopy_Click;
        // Listen for button click to paste ink strokes.
        btnPaste.Click += btnPaste_Click;
        // Listen for button click to clear ink canvas.
        btnClear.Click += btnClear_Click;

        // By default, the InkPresenter processes input modified by 
        // a secondary affordance (pen barrel button, right mouse 
        // button, or similar) as ink.
        // To pass through modified input to the app for custom processing 
        // on the app UI thread instead of the background ink thread, set 
        // InputProcessingConfiguration.RightDragAction to LeaveUnprocessed.
        inkCanvas.InkPresenter.InputProcessingConfiguration.RightDragAction =
            InkInputRightDragAction.LeaveUnprocessed;

        // Listen for unprocessed pointer events from modified input.
        // The input is used to provide selection functionality.
        inkCanvas.InkPresenter.UnprocessedInput.PointerPressed +=
            UnprocessedInput_PointerPressed;
        inkCanvas.InkPresenter.UnprocessedInput.PointerMoved +=
            UnprocessedInput_PointerMoved;
        inkCanvas.InkPresenter.UnprocessedInput.PointerReleased +=
            UnprocessedInput_PointerReleased;

        // Listen for new ink or erase strokes to clean up selection UI.
        inkCanvas.InkPresenter.StrokeInput.StrokeStarted +=
            StrokeInput_StrokeStarted;
        inkCanvas.InkPresenter.StrokesErased +=
            InkPresenter_StrokesErased;
    }
  1. 마지막으로 스트로크 선택 지원을 추가한 후 잘라내기, 복사, 및 붙여넣기 단추의 클릭 이벤트 처리기에서 클립보드 기능을 구현합니다.

    잘라내기의 경우 먼저 InkPresenterInkStrokeContainer에서 CopySelectedToClipboard를 호출합니다.

    그런 다음 DeleteSelected를 호출하여 잉크 캔버스에서 스트로크를 제거합니다.

    마지막으로 선택 캔버스에서 모든 선택 스트로크를 삭제합니다.

private void btnCut_Click(object sender, RoutedEventArgs e)
    {
        inkCanvas.InkPresenter.StrokeContainer.CopySelectedToClipboard();
        inkCanvas.InkPresenter.StrokeContainer.DeleteSelected();
        ClearSelection();
    }
// Clean up selection UI.
    private void ClearSelection()
    {
        var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
        foreach (var stroke in strokes)
        {
            stroke.Selected = false;
        }
        ClearDrawnBoundingRect();
    }

    private void ClearDrawnBoundingRect()
    {
        if (selectionCanvas.Children.Any())
        {
            selectionCanvas.Children.Clear();
            boundingRect = Rect.Empty;
        }
    }

복사의 경우, InkPresenterInkStrokeContainer에서 CopySelectedToClipboard를 호출하면 됩니다.

private void btnCopy_Click(object sender, RoutedEventArgs e)
    {
        inkCanvas.InkPresenter.StrokeContainer.CopySelectedToClipboard();
    }

붙여넣기의 경우, 클립보드의 내용이 잉크 캔버스에 붙여 넣어지도록 하기 위해 CanPasteFromClipboard를 호출합니다.

이 경우, PasteFromClipboard를 호출하여 클립보드 잉크 스트로크를 InkPresenterInkStrokeContainer에 삽입합니다. 그러면 스트로크가 잉크 캔버스에 렌더링됩니다.

private void btnPaste_Click(object sender, RoutedEventArgs e)
    {
        if (inkCanvas.InkPresenter.StrokeContainer.CanPasteFromClipboard())
        {
            inkCanvas.InkPresenter.StrokeContainer.PasteFromClipboard(
                new Point(0, 0));
        }
        else
        {
            // Cannot paste from clipboard.
        }
    }

토픽 샘플

기타 샘플