もちろん、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画面のアプリであれば「新しいアプリケーション」を選択したくなりますが、そのようなときにお勧めなのは「グリッドアプリケーション」テンプレートです。
不要なファイルを削除
「グリッドアプリケーション」テンプレートでは、「GroupDataPage.xaml」「GroupedItemPage.xaml」「ItemDeteilPage.xaml」という3つのページも自動生成されますが、1画面アプリでは不要なので削除します。そして、ソリューションエクスプローラーでプロジェクトを右クリックして[追加]‐[新しい項目]メニューで、「基本ページ」を追加します。
これでCommonフォルダに必要なクラスファイルが設定され、App.xaml.vbにもサスペンドなどに必要なコードが入った初期状態が完成しました。もちろんこれはVisual Basicだけの話ではなく、C#を使った場合でも、この方法が一番簡単に初期状態を作り出す手順です。
初期状態での実行
この初期状態でもWindowsストアアプリとして動作します。
LEADTOOLSを配置する
LEADTOOLSの参照設定
初期状態が完成したら、プロジェクトでLEADTOOLSを使えるようにしましょう。
LEADTOOLSを使えるようにするには参照設定が必要ですが、その前に少しだけ準備が必要です。ソリューションエクスプローラーでプロジェクトを右クリックして[追加]‐[参照]メニューで「Windows XAMLコンポーネント」を開いても、LEADTOOLSのコンポーネントは表示されません。
ダイアログにある[参照]ボタンをクリックして、LEADTOOLSのインストールフォルダ配下のコンポーネントを指定します。
Windowsストアアプリ向けのコンポーネントは、x86用、x64用、ARM用と3タイプが用意されています。今回はx86用の「V17.05.00.62_WinRT」フォルダを開きます。
そして、winmdファイルすべてとLeadTools.Controls.dllを選択して[追加]ボタンをクリックします。これでLEADTOOLSの機能がプロジェクトに取り込まれました。
プロジェクトのプラットフォーム
x86用のコンポーネントを指定したので、[ビルド]‐[構成マネージャー]でプラットフォームをx86に切り替えます。
XAMLエディタを使った配置
LEADTOOLSのコンポーネントをVisual Studioで配置するときは、XAMLエディタでPageタグにLeadTools.Controls引用を定義します。
このように定義しておけば、「<custom:」と打てばインテリセンスが効いて、LEADTOOLSのコンポーネントが表示されます。
Blendを使った配置
Visual Studio 2013に付属しているBlendは、XAML専用の高機能画面エディタです。Blend単体でも起動できますが、Visual StudioのソリューションエクスプローラーでXAMLファイルを右クリックから[Blend]メニューでも起動できます。
LEADTOOLSの配置はBlendも可能で、参照設定が終わっていれば[アセット]‐[場所]にLeadTools.Control.dllが表示されます。その中でImageViewerとRasterImageViewerが選べるようになるので、それをページ上にドラッグ&ドロップして配置することができます。
Visual Studioよりも、さらにXAML編集がしやすいツールなので、慣れると強い味方になります。
XAMLの定義
XAMLエディタまたはBlendで定義する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)
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は、次のようになります。
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の先頭に次のようなコードを記述しておきます。
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行を追加します。
Me.DataContext = App.MainVM
画像ライブラリを利用可能にする
今回のサンプルでは、画像ライブラリにある画像の表示のために、マニフェストファイルには「画像ライブラリ」機能を宣言します。
実行結果
完成したサンプルは、アップバーから画像取得ボタンを使ってFilePickerを呼び出して1画像ファイルを指定すると左右に画像ファイル表示します。
左が標準のImageコンポーネントであり、右がLEADTOOLSのRasterImageViewerコンポーネントでの表示結果になります。
図形フィルター機能を追加する
LEADTOOLSコンポーネントで画像を表示できました。ここまでの手順は通常のImageコンポーネントを使った場合と比較しても、大きな違いはありません。
しかし、それが重要なのです。大差ない手順で準備ができたLEADTOOLSコンポーネントが威力を発揮するのは、画像表示のコードができたあとに必要になってくるところからです。
それでは、画像にエフェクトをかける処理を追加してみましょう。表示領域を変形したり回転させたりするのはXAMLの機能で可能ですが、「エッジを抽出する」という課題であれば、どうすればよいでしょうか。
この課題の解決を標準機能だけで行うのは大変ですが、LEADTOOLSコンポーネントを使用すると、あらかじめ用意されている豊富なフィルター機能に「EdgeDetectEffect」も存在するため、コードを少し追加するだけで解決できます。
画像作成時にエフェクト適用
LEADTOOSの機能を適用する場所としては、RasterImageを作成するときに変換してしまう方法があります。
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を使ってエッジを抽出する処理を行います。これでパブリックプロパティには、エッジを抽出した画像が設定できます。
コンバーターを使ってエフェクト適用
画像作成時に変換してしまう方法は、試しにフィルターをかけたいときやデザイン担当者側でエフェクトを選定したい時などには不向きです。
そこで提案したいのが、XAMLのコンバーターを使ってMainPage.xaml側でエフェクトをかけてしまう方法です。
PictureModelに施した変更を元に戻してから、コンバーターを作成します。
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にコンバーターを適用します。
プロパティウィンドウでRasterImageViewerのImageプロパティ欄にある□をクリックして、[データバインディング作成]メニューを選択します。そして、コンバーター欄に「ImageConverter」を指定すれば設定完了です。
ストア申請
不要な参照設定の削除
ストアに申請する前に、使っていない参照設定を削除してパッケージをコンパクトにしましょう。
プロジェクトのプロパティから[参照]を選び[未使用の参照]ボタンをクリックすると、使用していないLEADTOOLSへの参照が一覧表示されるので、[削除]とすれば参照が外れて配布用パッケージからも除外されます。
Windowsストアへの申請
LEADTOOLSはx64、x86、ARM版と実行ライブラリが分かれています。x86版であればx64環境でも動作するのでx86版のみをストア申請しても良いのですが、そうするとSurface 2などで利用できないWindowsストアアプリとなってしまいます。最低でもx86とARM版の作成、そして可能であれば3つの版のアプリパッケージを作成しましょう。
作成したアプリパッケージは1つのWindowsストア申請に含めることができて、申請が通って公開されたときは、適したパッケージが自動的に選択されてインストールされることになります。
まとめ
既存のImageコントロールとほぼ同じ使い勝手で、途中に画像処理用のコマンドを挟み込んで変換するという手法は、今までのノウハウで作業しつつ、追加したい画像処理方法だけをコード的にもノウハウ的にも追加すればよいため、対応時間が短縮できるという利点があります。
Windowsストアアプリのようにストアを通して全世界に配布できること、多くのライバルアプリがあることを考えると、高機能な特徴を追加できる市販コントロールの利用は、人気アプリになるための一つの手段になるかもしれません。
ぜひトライアル版を使用して、自分のアプリにLEADTOOLSの機能を追加した時の出来上がりを体感してみてください。