SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

現役エンジニア直伝! 「現場」で使えるコンポーネント活用術(LEADTOOLS)

「LEADTOOLS」を使ってWindowsストアアプリに画像処理機能を実装しよう

  • X ポスト
  • このエントリーをはてなブックマークに追加

 Windows 8では、従来のデスクトップアプリ以外にWindowsストアアプリという動作フレームワークが一新されているアプリ形式があります。Windowsストアアプリの実行環境は、一般的にはWindowsストアなど限定された配布手順とアプリチェックを前提として、その実行環境の動作制限とアプリの最新化により、コンピュータに詳しくない人が利用していても、ウイルス感染などのリスクがほぼないように考慮されているという利点があります。また、スマートフォンやタブレットなどに使われているARMプロセッサのサポートも特徴です。Windowsストアアプリの実行環境では、従来のデスクトップアプリが動作しないことがマイナス面として取り上げられるケースが多いようですが、一般の人がより気軽に安全にコンピュータを利用するという点については、非常によくできた考え方といえるでしょう。

  • X ポスト
  • このエントリーをはてなブックマークに追加

 もちろん、Windowsストアアプリでも市販コンポーネントの利用が考慮されていて、Windowsストアアプリ対応の市販コンポーネントを使って少ない手順で高度な機能の追加が可能です。しかし、あまり市販コンポーネントの話を聞かないような気がします。その理由の一つは、WindowsフォームとWPFは非画面系のコンポーネントは同一バイナリでも問題ありませんが、Windowsストアアプリの市販コンポーネントは非画面系のコンポーネントであっても従来の形態では利用ができず、Windowsストアアプリでも使えるようにするための設計が必要だということで移植や同時提供が進んでいないという点があるでしょう。もう一つの理由としては、Windowsストアアプリの画面デザインがXAMLというかなり高機能な画面定義体で定義するので、同じくXAMLで画面が定義できるWPFでも同様ですが、市販コンポーネントがなくても済むケースが多いという点が影響しているのかもしれません。

 しかし、だからこそWindowsストアアプリ用の市販コンポーネントは貴重だといえるでしょう。なぜなら、市販コンポーネントとして発売されているということは、標準コンポーネントでは難しいことが実装されている訳ですし、それは世の中に存在するWindowsストアアプリで実装されている例が少ない機能であるとも解釈できます。よって、Windowsストアアプリを企画する上でも、他のアプリとの差別化という点でも非常に興味深いという推論が成り立つからです。

 今回取り上げるLEADTOOLSは、WindowsフォームやWPF向けの画像処理コンポーネントのWindowsストアアプリ対応のコンポーネントで、LEADTOOLS Imaging Pro Suiteを購入した時だけに入手できます。

 なお、今回は、Windows 8.1 Enterprise(x64)+Visual Studio Ultimate 2013 Update 1で動作確認しています。LEADTOOLSのシステム要件としては、正式対応されていない環境であることをあらかじめお断りしておきます。

Windowsストアアプリの作り方

テンプレートを選択

 Windowsストアアプリを新規作成する場合、いくつかのテンプレートが用意されています。1画面のアプリであれば「新しいアプリケーション」を選択したくなりますが、そのようなときにお勧めなのは「グリッドアプリケーション」テンプレートです。

図1 「グリッドアプリケーション」テンプレート
図1 「グリッドアプリケーション」テンプレート

不要なファイルを削除

 「グリッドアプリケーション」テンプレートでは、「GroupDataPage.xaml」「GroupedItemPage.xaml」「ItemDeteilPage.xaml」という3つのページも自動生成されますが、1画面アプリでは不要なので削除します。そして、ソリューションエクスプローラーでプロジェクトを右クリックして[追加]‐[新しい項目]メニューで、「基本ページ」を追加します。

図2 ページの整理
図2 ページの整理

 これでCommonフォルダに必要なクラスファイルが設定され、App.xaml.vbにもサスペンドなどに必要なコードが入った初期状態が完成しました。もちろんこれはVisual Basicだけの話ではなく、C#を使った場合でも、この方法が一番簡単に初期状態を作り出す手順です。

初期状態での実行

 この初期状態でもWindowsストアアプリとして動作します。

図3 初期状態での実行
図3 初期状態での実行

LEADTOOLSを配置する

LEADTOOLSの参照設定

 初期状態が完成したら、プロジェクトでLEADTOOLSを使えるようにしましょう。

 LEADTOOLSを使えるようにするには参照設定が必要ですが、その前に少しだけ準備が必要です。ソリューションエクスプローラーでプロジェクトを右クリックして[追加]‐[参照]メニューで「Windows XAMLコンポーネント」を開いても、LEADTOOLSのコンポーネントは表示されません。

図4 Windows XAMLコンポーネント
図4 Windows XAMLコンポーネント

 ダイアログにある[参照]ボタンをクリックして、LEADTOOLSのインストールフォルダ配下のコンポーネントを指定します。

図5 LEADTOOLSの参照設定
図5 LEADTOOLSの参照設定

 Windowsストアアプリ向けのコンポーネントは、x86用、x64用、ARM用と3タイプが用意されています。今回はx86用の「V17.05.00.62_WinRT」フォルダを開きます。

 そして、winmdファイルすべてとLeadTools.Controls.dllを選択して[追加]ボタンをクリックします。これでLEADTOOLSの機能がプロジェクトに取り込まれました。

プロジェクトのプラットフォーム

 x86用のコンポーネントを指定したので、[ビルド]‐[構成マネージャー]でプラットフォームをx86に切り替えます。

図6 プラットフォーム切り替え
図6 プラットフォーム切り替え

XAMLエディタを使った配置

 LEADTOOLSのコンポーネントをVisual Studioで配置するときは、XAMLエディタでPageタグにLeadTools.Controls引用を定義します。

図7 XAMLエディタでの定義
図7 XAMLエディタでの定義

 このように定義しておけば、「<custom:」と打てばインテリセンスが効いて、LEADTOOLSのコンポーネントが表示されます。

Blendを使った配置

 Visual Studio 2013に付属しているBlendは、XAML専用の高機能画面エディタです。Blend単体でも起動できますが、Visual StudioのソリューションエクスプローラーでXAMLファイルを右クリックから[Blend]メニューでも起動できます。

 LEADTOOLSの配置はBlendも可能で、参照設定が終わっていれば[アセット]‐[場所]にLeadTools.Control.dllが表示されます。その中でImageViewerとRasterImageViewerが選べるようになるので、それをページ上にドラッグ&ドロップして配置することができます。

図8 Blendでの定義
図8 Blendでの定義

 Visual Studioよりも、さらにXAML編集がしやすいツールなので、慣れると強い味方になります。

XAMLの定義

 XAMLエディタまたはBlendで定義するXAMLの内容は、次のようになります。

リスト1 MainPage.xaml
<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CZ1401ImagingVB"
    xmlns:common="using:CZ1401ImagingVB.Common"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:custom="using:Leadtools.Controls"
    xmlns:Converter="using:CZ1401ImagingVB.Converter"
    x:Name="pageRoot"
    x:Class="CZ1401ImagingVB.MainPage"
    mc:Ignorable="d">
 
    <Page.BottomAppBar>
        <CommandBar>
            <AppBarButton Label="画像指定" Icon="OpenFile" Command="{Binding OpenCommand}"/>
        </CommandBar>
    </Page.BottomAppBar>
 
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ChildrenTransitions>
            <TransitionCollection>
                <EntranceThemeTransition/>
            </TransitionCollection>
        </Grid.ChildrenTransitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="140"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
 
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="120"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Button x:Name="backButton" Margin="39,59,39,0" 
                Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
                Style="{StaticResource NavigationBackButtonNormalStyle}"
                VerticalAlignment="Top"
                AutomationProperties.Name="Back"
                AutomationProperties.AutomationId="BackButton"
                AutomationProperties.ItemType="Navigation Button"/>
            <TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" 
                Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" 
                IsHitTestVisible="false" TextWrapping="NoWrap" 
                VerticalAlignment="Bottom" Margin="0,0,30,40"/>
        </Grid>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*" />
                <ColumnDefinition Width="1*" />
            </Grid.ColumnDefinitions>
            <Image Grid.Column="0"
                   Stretch="None"
                   Source="{Binding ImageElement}"/>
            <custom:RasterImageViewer
                Grid.Column="1" 
                Image="{Binding RasterElement}"/>
        </Grid>
    </Grid>
</Page>

 LEADTOOLSのコンポーネントは、Imageプロパティに表示したい画像データをBindingしているだけで特に何もプロパティを指定していません。

LEADTOOLSでの読み込みロジックの記述(PictureModel)

リスト2 PictureModel.vb
Public Class PictureModel
    Implements INotifyPropertyChanged
 
    Private _ImageElement As ImageSource = Nothing
    Public Property ImageElement As ImageSource
        Get
            Return Me._ImageElement
        End Get
        Set(value As ImageSource)
            Me._ImageElement = value
            OnPropertyChanged()
        End Set
    End Property
 
    Private _RasterElement As Leadtools.RasterImage
    Public Property RasterElement As Leadtools.RasterImage              ' …
        Get
            Return Me._RasterElement
        End Get
        Set(value As Leadtools.RasterImage)
            Me._RasterElement = value
            OnPropertyChanged()
        End Set
    End Property
 
    Public Async Function Open() As Task
        Dim picker = New Pickers.FileOpenPicker With {                  ' …
            .ViewMode = Pickers.PickerViewMode.Thumbnail,
            .SuggestedStartLocation = Pickers.PickerLocationId.PicturesLibrary
        }
        picker.FileTypeFilter.Clear()
        picker.FileTypeFilter.Add(".jpg")
        picker.FileTypeFilter.Add(".png")
        picker.FileTypeFilter.Add(".tif")
        Dim file As StorageFile = Await picker.PickSingleFileAsync()    ' …
        If file IsNot Nothing Then
            Using fileStream = Await file.OpenAsync(FileAccessMode.Read)
                Dim bitmap = New Windows.UI.Xaml.Media.Imaging.BitmapImage
                bitmap.SetSource(fileStream)
                Me.ImageElement = bitmap
            End Using
            Dim leadStream As Leadtools.ILeadStream = Leadtools.LeadStreamFactory.Create(file)
            Using disposable As IDisposable = TryCast(leadStream, IDisposable)
                Using codecs As New Leadtools.Codecs.RasterCodecs
                    Dim workImage = Await codecs.LoadAsync(leadStream)
                    Me.RasterElement = workImage                        ' …
                End Using
            End Using
        End If
    End Function
 
    Public Event PropertyChanged(sender As Object,
                                 e As PropertyChangedEventArgs) _
                             Implements INotifyPropertyChanged.PropertyChanged
    Private Sub OnPropertyChanged(<CallerMemberName> Optional propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
End Class
  • LEADTOOLSのラスターイメージを格納するパブリックプロパティを定義
  • 画像ライブラリを開くためのFileOpenPickerを定義
  • FileOpenPickerを実行
  • 指定された画像から作成したラスターイメージをパブリックプロパティに設定

ViewModelの定義(MainViewModel)

 PictureModelとMainPage.xamlを結合するためのViewModelは、次のようになります。

リスト3 PictureModel.vb
Public Class MainViewModel
    Implements INotifyPropertyChanged
 
    Private WithEvents Model As New PictureModel              ' …
 
    Public Property ImageElement As ImageSource
        Get
            Return Me.Model.ImageElement
        End Get
        Set(value As ImageSource)
            Me.Model.ImageElement = value
        End Set
    End Property
 
    Public Property RasterElement As Leadtools.RasterImage    ' …
        Get
            Return Me.Model.RasterElement
        End Get
        Set(value As Leadtools.RasterImage)
            Me.Model.RasterElement = value
        End Set
    End Property
 
    Public Async Sub Open()
        Await Me.Model.Open
    End Sub
 
    Private _OpenCommand As Common.RelayCommand
    Public Property OpenCommand As Common.RelayCommand        ' …
        Get
            If Me._OpenCommand Is Nothing Then
                Me._OpenCommand = New Common.RelayCommand(AddressOf Open)
            End If
            Return Me._OpenCommand
        End Get
        Set(value As Common.RelayCommand)
            Me._OpenCommand = value
        End Set
    End Property
 
    Public Event PropertyChanged(sender As Object,
                                 e As PropertyChangedEventArgs) _
                             Implements INotifyPropertyChanged.PropertyChanged
    Private Sub OnPropertyChanged(<CallerMemberName> Optional propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
 
    Private Sub Model_PropertyChanged(sender As Object,
                                      e As PropertyChangedEventArgs) _
                                  Handles Model.PropertyChanged
        OnPropertyChanged(e.PropertyName)
    End Sub
End Class
  • PicureModelクラスをイベント検出付きで宣言
  • ラスターイメージを伝搬するためのパブリックプロパティを定義
  • ファイル読み込むのメソッドを呼び出すためのコマンドプロパティを定義

 また、MainViewModelをページ間での共通ViewModelとするために、App.xaml.vbの先頭に次のようなコードを記述しておきます。

リスト4 App.xaml.vb
NotInheritable Class App
    Inherits Application
 
    Public Shared ReadOnly Property MainVM As MainViewModel
        Get
            If _MainVM Is Nothing Then
                _MainVM = New MainViewModel
            End If
            Return _MainVM
        End Get
    End Property
    Private Shared _MainVM As MainViewModel = Nothing
            :
          (略)
            :

XAMLのコードビハインド定義

 MainPage.xamlのコードビハインド側であるMainPage.xaml.vbには自動生成されたコードがあるので、コンストラクタである「Public Sub New()」の最後に次の1行を追加します。

リスト5 コンストラクタへの追記
Me.DataContext = App.MainVM

画像ライブラリを利用可能にする

 今回のサンプルでは、画像ライブラリにある画像の表示のために、マニフェストファイルには「画像ライブラリ」機能を宣言します。

図9 機能の宣言
図9 機能の宣言

実行結果

 完成したサンプルは、アップバーから画像取得ボタンを使ってFilePickerを呼び出して1画像ファイルを指定すると左右に画像ファイル表示します。

図10 実行結果
図10 実行結果

 左が標準のImageコンポーネントであり、右がLEADTOOLSのRasterImageViewerコンポーネントでの表示結果になります。

図形フィルター機能を追加する

 LEADTOOLSコンポーネントで画像を表示できました。ここまでの手順は通常のImageコンポーネントを使った場合と比較しても、大きな違いはありません。

 しかし、それが重要なのです。大差ない手順で準備ができたLEADTOOLSコンポーネントが威力を発揮するのは、画像表示のコードができたあとに必要になってくるところからです。

 それでは、画像にエフェクトをかける処理を追加してみましょう。表示領域を変形したり回転させたりするのはXAMLの機能で可能ですが、「エッジを抽出する」という課題であれば、どうすればよいでしょうか。

 この課題の解決を標準機能だけで行うのは大変ですが、LEADTOOLSコンポーネントを使用すると、あらかじめ用意されている豊富なフィルター機能に「EdgeDetectEffect」も存在するため、コードを少し追加するだけで解決できます。

画像作成時にエフェクト適用

 LEADTOOSの機能を適用する場所としては、RasterImageを作成するときに変換してしまう方法があります。

リスト6 コンストラクタへの追記
Dim workImage = Await codecs.LoadAsync(leadStream)
Dim command As New Leadtools.ImageProcessing.Effects.EdgeDetectEffectCommand(
    2,
    10,
    Leadtools.ImageProcessing.Effects.EdgeDetectEffectCommandType.Solid)
command.Run(workImage)
Me.RasterElement = workImage

 LoadAsyncしたものをパブリックプロパティに設定する前に、LEADTOOLSのEdgeDetectEffectCommandを使ってエッジを抽出する処理を行います。これでパブリックプロパティには、エッジを抽出した画像が設定できます。

図11 実行結果
図11 実行結果

コンバーターを使ってエフェクト適用

 画像作成時に変換してしまう方法は、試しにフィルターをかけたいときやデザイン担当者側でエフェクトを選定したい時などには不向きです。

 そこで提案したいのが、XAMLのコンバーターを使ってMainPage.xaml側でエフェクトをかけてしまう方法です。

 PictureModelに施した変更を元に戻してから、コンバーターを作成します。

リスト7 コンバーターの作成
Imports Leadtools.ImageProcessing
 
Namespace Converter
    Public Class ImageConverter
        Implements IValueConverter
 
        Public Function Convert(value As Object,
                                targetType As Type,
                                parameter As Object,
                                language As String) As Object _
                            Implements IValueConverter.Convert
            If TypeOf value Is Leadtools.RasterImage Then
                Dim orignal = CType(value, Leadtools.RasterImage)
                Dim command As New Effects.EdgeDetectEffectCommand(
                    2,
                    10,
                    Effects.EdgeDetectEffectCommandType.Solid)
                command.Run(orignal)
                Return orignal
            Else
                Return value
            End If
        End Function
 
        Public Function ConvertBack(value As Object,
                                    targetType As Type,
                                    parameter As Object,
                                    language As String) As Object _
                                Implements IValueConverter.ConvertBack
            Throw New NotImplementedException
        End Function
    End Class
End Namespace

 コンバーターが作成できたら、MainPage.xamlにコンバーターを適用します。

図12 コンバーターの設定
図12 コンバーターの設定

 プロパティウィンドウでRasterImageViewerのImageプロパティ欄にある□をクリックして、[データバインディング作成]メニューを選択します。そして、コンバーター欄に「ImageConverter」を指定すれば設定完了です。

図13 コンバーターによるエフェクト
図13 コンバーターによるエフェクト

ストア申請

不要な参照設定の削除

 ストアに申請する前に、使っていない参照設定を削除してパッケージをコンパクトにしましょう。

図14 未使用な参照の削除
図14 未使用な参照の削除

 プロジェクトのプロパティから[参照]を選び[未使用の参照]ボタンをクリックすると、使用していないLEADTOOLSへの参照が一覧表示されるので、[削除]とすれば参照が外れて配布用パッケージからも除外されます。

Windowsストアへの申請

 LEADTOOLSはx64、x86、ARM版と実行ライブラリが分かれています。x86版であればx64環境でも動作するのでx86版のみをストア申請しても良いのですが、そうするとSurface 2などで利用できないWindowsストアアプリとなってしまいます。最低でもx86とARM版の作成、そして可能であれば3つの版のアプリパッケージを作成しましょう。

図14 Windowsストア開発者ダッシュボードでのパッケージ表示例
図15 Windowsストア開発者ダッシュボードでのパッケージ表示例

 作成したアプリパッケージは1つのWindowsストア申請に含めることができて、申請が通って公開されたときは、適したパッケージが自動的に選択されてインストールされることになります。

まとめ

 既存のImageコントロールとほぼ同じ使い勝手で、途中に画像処理用のコマンドを挟み込んで変換するという手法は、今までのノウハウで作業しつつ、追加したい画像処理方法だけをコード的にもノウハウ的にも追加すればよいため、対応時間が短縮できるという利点があります。

 Windowsストアアプリのようにストアを通して全世界に配布できること、多くのライバルアプリがあることを考えると、高機能な特徴を追加できる市販コントロールの利用は、人気アプリになるための一つの手段になるかもしれません。

 ぜひトライアル版を使用して、自分のアプリにLEADTOOLSの機能を追加した時の出来上がりを体感してみてください。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/7610 2016/03/14 11:39

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング