しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Xamarin.Forms で Microsoft Band の壁紙を取得する

Microsoft Band の着せ替え機能で壁紙を取得してみます

ソースコードの一式は下記にあります!

細かい実装などはこちらを参照ください

github.com

※ 順次改修していく予定なので、この記事の内容が現時点のソースより古い可能性があります


問題となる BandImage を扱うことになるので、まずは変換クラスを作ります

/// <summary>
/// iOS 用画像コンバーター
/// </summary>
public static class NativeBandImageConvert
{
    /// <summary>
    /// 画像の変換
    /// </summary>
    /// <param name="image">画像情報</param>
    /// <returns>画像ソース</returns>
    public static StreamImageSource FromNative(Native.Personalization.BandImage image)
    {
        return (StreamImageSource)ImageSource.FromStream(image.UIImage.AsPNG().AsStream);
    }

    /// <summary>
    /// 画像の変換
    /// </summary>
    /// <param name="source">画像ソース</param>
    /// <returns>画像情報</returns>
    public static async Task<Native.Personalization.BandImage> ToNative(StreamImageSource source)
    {
        var stream = await source.Stream.Invoke(new CancellationToken());
        var image = await Task.Run(() =>
        {
            using (var data = NSData.FromStream(stream))
            {
                return UIImage.LoadFromData(data);
            }
        });
        return new Native.Personalization.BandImage(image);
    }
}

これが iOS

/// <summary>
/// Android 用画像コンバーター
/// </summary>
public static class NativeBandImageConvert
{
    /// <summary>
    /// 画像の変換
    /// </summary>
    /// <param name="image">画像情報</param>
    /// <returns>画像ソース</returns>
    public static StreamImageSource FromNative(Bitmap image)
    {
        return (StreamImageSource)ImageSource.FromStream(() =>
        {
            var stream = new MemoryStream();
            image.Compress(Bitmap.CompressFormat.Png, 100, stream);
            stream.Seek(0L, SeekOrigin.Begin);
            return stream;
        });
    }

    /// <summary>
    /// 画像の変換
    /// </summary>
    /// <param name="source">画像ソース</param>
    /// <returns>画像情報</returns>
    public static async Task<Bitmap> ToNative(StreamImageSource source)
    {
        var stream = await source.Stream.Invoke(new CancellationToken());
        return await BitmapFactory.DecodeStreamAsync(stream);
    }
}

これが Android

/// <summary>
/// Windows 用画像コンバーター
/// </summary>
public static class NativeBandImageConvert
{
    /// <summary>
    /// 画像の変換
    /// </summary>
    /// <param name="image">画像情報</param>
    /// <returns>画像ソース</returns>
    public static StreamImageSource FromNative(BandImage image)
    {
        return (StreamImageSource)ImageSource.FromStream(() => image.ToWriteableBitmap().PixelBuffer.AsStream());
    }

    /// <summary>
    /// 画像の変換
    /// </summary>
    /// <param name="source">画像ソース</param>
    /// <returns>画像情報</returns>
    public static async Task<BandImage> ToNative(StreamImageSource source)
    {
        var stream = await source.Stream.Invoke(new CancellationToken());
        using (var ras = stream.AsRandomAccessStream())
        {
            var bitmap = new WriteableBitmap(310, 102);
            await bitmap.SetSourceAsync(ras);

            return bitmap.ToBandImage();
        }
    }
}

そして Windows Phone・・・画像の取り扱いは面倒ですね

この変換クラスを利用して NativePersonalizationManager を改修

/// <summary>
/// iOS 用着せ替え管理クラス
/// </summary>
public class NativeBandPersonalizationManager : IBandPersonalizationImageManager
{
    ~ 中略 ~

    /// <summary>
    /// 壁紙の取得
    /// </summary>
    /// <returns>壁紙画像のソース</returns>
    public async Task<StreamImageSource> GetMeTileImageSourceAsync()
    {
        return NativeBandImageConvert.FromNative(
            await Native.Personalization.BandPersonalizationManagerExtensions.GetMeTileImageTaskAsync(this.manager));
    }
}

AndroidWindows Phone もほぼ同じです

これに合わせて ViewModel も変更

/// <summary>
/// 着せ替え設定 ViewModel
/// </summary>
public class PersonalizeViewModel : BindableBase
{
    #region MeTileImage

    /// <summary>
    /// 壁紙画像ソース
    /// </summary>
    private ImageSource meTileImageSource = ImageSource.FromResource(@"XamarinBandSample.Assets.me-tile.png");

    /// <summary>
    /// 壁紙画像ソース
    /// </summary>
    public ImageSource MeTileImageSource
    {
        get { return this.meTileImageSource; }
        set { this.SetProperty<ImageSource>(ref this.meTileImageSource, value); }
    }

    #endregion //MeTileImage

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="client">接続クライアント</param>
    [InjectionConstructor]
    public PersonalizeViewModel(IBandClient client, IBandPersonalizationImageManager manager)
    {
        this.IsBusy = true;
        this.client = client;
        this.manager = manager;

        this.PullCommand = DelegateCommand.FromAsyncHandler(this.Pull);
        this.ApplyCommand = DelegateCommand.FromAsyncHandler(this.Apply);

        this.BaseColor.SelecedIndex = 1;
        this.HighContrastColor.Color = "#FF9966ff";
        this.HighlightColor.Color = "#FF9966ff";
        this.LowlightColor.Color = "#FF6633cc";
        this.MutedColor.Color = "#FF663399";

        this.SecondaryTextColor.SelecedIndex = 5;

        this.baseColor.PropertyChanged += this.OnBaseColorChanged;
        this.IsBusy = false;
    }
 
    /// <summary>
    /// 設定状況取得
    /// </summary>
    /// <returns>Task</returns>
    private async Task Pull()
    {
        this.IsBusy = true;
        var theme = await this.manager.GetThemeAsync();

        this.BaseColor.Color = ColorToString(theme.Base);
        this.BaseColor.SelecedIndex = -1;
        this.HighContrastColor.Color = ColorToString(theme.HighContrast);
        this.HighlightColor.Color = ColorToString(theme.Highlight);
        this.LowlightColor.Color = ColorToString(theme.Lowlight);
        this.MutedColor.Color = ColorToString(theme.Muted);
        this.SecondaryTextColor.Color = ColorToString(theme.SecondaryText);
        this.SecondaryTextColor.SelecedIndex = -1;

        var source = await this.manager.GetMeTileImageSourceAsync();
        this.MeTileImageSource = source;
        this.IsBusy = false;
    }

    ~ 中略 ~
}

MeTileImageSource というプロパティに壁紙画像の ImageSource を保持するようにし、初期値は適当な画像(既定サイズ 310 x 102 px)を用意して設定しています

最後に XAML

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:cv="clr-namespace:XamarinBandSample.Converters;assembly=XamarinBandSample"
             xmlns:c="clr-namespace:XamarinBandSample.Controls;assembly=XamarinBandSample"
             xmlns:t="clr-namespace:XamarinBandSample.Triggers;assembly=XamarinBandSample"
             xmlns:prismmvvm="clr-namespace:Prism.Mvvm;assembly=XamarinBandSample"
             prismmvvm:ViewModelLocator.AutoWireViewModel="true"
             x:Class="XamarinBandSample.Views.TopPage">
  <ContentPage.Resources>
    <ResourceDictionary>
      <cv:NegativeConverter x:Key="NegativeConverter"/>
      <cv:ColorConverter x:Key="ColorConverter"/>
    </ResourceDictionary>
  </ContentPage.Resources>

  <Grid>

  ~ 中略 ~

    <!-- Persomalize Info Pain-->
    <ScrollView IsVisible="{Binding ShowPersonalize}"
                Orientation="Vertical">
      <StackLayout Orientation="Vertical"
                   BindingContext="{Binding Personalize}"
                   Padding="10"
                   Spacing="10"
                   VerticalOptions="StartAndExpand">
        <StackLayout Orientation="Horizontal"
                     BindingContext="{Binding BaseColor}"
                     Spacing="10">
          <Label Text="Base" 
                 WidthRequest="100"
                 FontSize="Small"/>
          <BoxView WidthRequest="30"
                   HeightRequest="30"
                   BackgroundColor="{Binding Color, Converter={StaticResource ColorConverter}}"/>
          <c:BindablePicker ItemsSource="{Binding ColorLabelSelection}"
                            IsEnabled="{Binding IsBusy, Converter={StaticResource NegativeConverter}}"
                            SelectedIndex="{Binding SelecedIndex, Mode=TwoWay}"/>
        </StackLayout>
        <StackLayout Orientation="Horizontal"
                     BindingContext="{Binding HighContrastColor}"
                     Spacing="10">
          <Label Text="HighContrast"
                 WidthRequest="100"
                 FontSize="Small"/>
          <BoxView WidthRequest="30"
                   HeightRequest="30"
                   BackgroundColor="{Binding Color, Converter={StaticResource ColorConverter}}"/>
        </StackLayout>
        <StackLayout Orientation="Horizontal"
                     BindingContext="{Binding HighlightColor}"
                     Spacing="10">
          <Label Text="Highlight"
                 WidthRequest="100"
                 FontSize="Small"/>
          <BoxView WidthRequest="30"
                   HeightRequest="30"
                   BackgroundColor="{Binding Color, Converter={StaticResource ColorConverter}}"/>
        </StackLayout>
        <StackLayout Orientation="Horizontal"
                     BindingContext="{Binding LowlightColor}"
                     Spacing="10">
          <Label Text="Lowlight"
                 WidthRequest="100"
                 FontSize="Small"/>
          <BoxView WidthRequest="30"
                   HeightRequest="30"
                   BackgroundColor="{Binding Color, Converter={StaticResource ColorConverter}}"/>
        </StackLayout>
        <StackLayout Orientation="Horizontal"
                     BindingContext="{Binding MutedColor}"
                     Spacing="10">
          <Label Text="Muted"
                 WidthRequest="100"
                 FontSize="Small"/>
          <BoxView WidthRequest="30"
                   HeightRequest="30"
                   BackgroundColor="{Binding Color, Converter={StaticResource ColorConverter}}"/>
        </StackLayout>
        <StackLayout Orientation="Horizontal"
                     BindingContext="{Binding SecondaryTextColor}"
                     Spacing="10">
          <Label Text="SecondaryText"
                 WidthRequest="100"
                 FontSize="Small"/>
          <BoxView WidthRequest="30"
                   HeightRequest="30"
                   BackgroundColor="{Binding Color, Converter={StaticResource ColorConverter}}"/>
          <c:BindablePicker ItemsSource="{Binding ColorLabelSelection}"
                            IsEnabled="{Binding IsBusy, Converter={StaticResource NegativeConverter}}"
                            SelectedIndex="{Binding SelecedIndex, Mode=TwoWay}"/>
        </StackLayout>

        <Label Text="Me Tile"
               FontSize="Small"/>
        <Image Source="{Binding MeTileImageSource}" WidthRequest="310"/>
        
        <StackLayout Orientation="Horizontal"
                       Padding="10"
                       Spacing="10"
                       HorizontalOptions="End">
          <Button Text="Pull"
                  IsEnabled="{Binding IsBusy, Converter={StaticResource NegativeConverter}}"
                  Command="{Binding PullCommand}"
                  VerticalOptions="Center"
                  FontSize="Medium"/>
          <Button Text=" Apply"
                  IsEnabled="{Binding IsBusy, Converter={StaticResource NegativeConverter}}"
                  Command="{Binding ApplyCommand}"
                  VerticalOptions="Center"
                  FontSize="Medium"/>
        </StackLayout>
      </StackLayout>
    </ScrollView>

  </Grid>

</ContentPage>

Me Tile の表示領域を追加しています

さてお試し実行!

f:id:matatabi_ux:20150502115048p:plain f:id:matatabi_ux:20150502093255p:plain

結構取得に時間がかかるのですが無事壁紙画像が取得できました