読者です 読者をやめる 読者になる 読者になる

酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

C++/Cx で書いたクラスを C#で書いたWindowsストアアプリで使う

画像処理 Windows Store Apps

誰得かわからないけど書く。ちょうど画像処理関係をいじっていることもあって、簡単に実装可能な「セピア調変換 - 酢ろぐ!」をネイティブ(C++/Cx)で書いて、マネージ(C#)で画像の読み込みとImageコントロールへの設定を実装してみようかと思います。

  • アプリ側はC#で実装
  • C++/Cxでライブラリを作成
  • アプリ側からbyte配列でピクセルデータを渡して、ネイティブでセピア調の処理をさせる
  • ネイティブからアプリに画像を渡して、Imageコントロールで表示

プロジェクトの作成

C#でメトロアプリを作成します。アプリの実装はひとまずおいて、ネイティブの画像処理のコードを書きます。

ネイティブ側の実装

ソリューションエクスプローラーを右クリックして、新しいプロジェクトを追加する。「Windows Runtime Component」を探そう。

NativeTestClass.hでメソッドを定義する。

#pragma once

namespace WindowsRuntimeComponent1
{
    public ref class NativeTestClass sealed
    {
    public:
        NativeTestClass();
		void sepia(int width, int height, const Platform::Array<uint8>^ srcBytes, Platform::WriteOnlyArray<uint8>^ dstBytes);
    };
}

NativeTestClass.cppでメソッドを実装する。

// Class1.cpp
#include "pch.h"
#include "NativeTestClass.h"

using namespace WindowsRuntimeComponent1;
using namespace Platform;

NativeTestClass::NativeTestClass()
{
}

void NativeTestClass::sepia(int width, int height, const Platform::Array<uint8>^ srcBytes, Platform::WriteOnlyArray<uint8>^ dstBytes)
{
	int pixels = width * height;
	for (int i = 0; i < pixels; i++) {
		int index = i * 4;

		uint8 b = srcBytes[index + 0];
		uint8 g = srcBytes[index + 1];
		uint8 r = srcBytes[index + 2];
		uint8 a = srcBytes[index + 3];

		// 単純平均法で輝度を求める
        	double y = (double)(r + g + b) / 3;

        	// 輝度に対して指定の比率を掛けてセピア調に変換する
        	uint8 db = y * 0.4;
        	uint8 dg = y * 0.7;
        	uint8 dr = y;

		dstBytes[index + 0] = db;
		dstBytes[index + 1] = dg;
		dstBytes[index + 2] = dr;
		dstBytes[index + 3] = a;
	}
}

アプリ側の実装

画像処理のためにStorageFileとWriteableBitmapを使いやすくするライブラリをMITライセンスで公開しています。

以下のサンプルコードで標準APIにないメソッドがあれったら、俺俺ライブラリを使っていると思ってください。画面は超簡単に、ButtonコントロールとImageコントロールが並んでいるだけ。

<Page
    x:Class="NativeCodeSample.MainPage"
    IsTabStop="false"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:NativeCodeSample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Button Content="Button" HorizontalAlignment="Left" Margin="53,57,0,0" VerticalAlignment="Top" Height="174" Width="325" Click="Button_Click_1"/>
        <Image x:Name="effectImage" HorizontalAlignment="Left" Height="560" Margin="432,57,0,0" VerticalAlignment="Top" Width="859"/>
    </Grid>
</Page>

ボタンを押したら処理開始です。

using System;
using System.Runtime.InteropServices.WindowsRuntime;
using Softbuild.Media;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using WindowsRuntimeComponent1;

namespace NativeCodeSample
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            // 俺俺ライブラリを使って、WriteableBitmapオブジェクトを生成する
            var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Logo.png"));
            var bitmap = await WriteableBitmapExtensions.FromStreamAsync(await file.OpenReadAsync());

            var w = bitmap.PixelWidth;
            var h = bitmap.PixelHeight;

            // 元画像と出力画像用のバッファの生成
            var srcBytes = bitmap.PixelBuffer.ToArray();
            var dstBytes = new byte[srcBytes.Length];

            // C++/Cxのライブラリで画像処理をしてみる
            var nativeLibrary = new NativeTestClass();
            nativeLibrary.sepia(w, h, srcBytes, dstBytes);

            // Imageコントロールにセピア調化した画像を表示する
            effectImage.Source = WriteableBitmapExtensions.FromArray(w, h, dstBytes);
        }
    }
}

アプリ側の実装はこれで終わり。ネイティブ側のクラスを特に意識することなく使えているのが分かります。

ってことでできたー!