Direct2D --- 画像読み込み

Direct2Dにおいて画像ファイルを読み込んで表示する場合、Windows Imaging Component (WIC)を利用します。

今回は実際にそれを実装してみたいと思います。

画像ファイルを32bitRGBAで読み込み

まずIWICImagingFactoryを作成し、factory経由でデコードを行います。

また32bitRGBAに変換するため、IWICFormatConverterを利用します。

#include <wincodec.h>
#include <wrl\client.h>
using namespace Microsoft::WRL;

ComPtr<IWICBitmapSource> WICBitmapFromFile(wchar_t* path)
{
    ComPtr<IWICImagingFactory> factory;
    AssertHR(CoCreateInstance(
        CLSID_WICImagingFactory,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&factory)
    ));

    ComPtr<IWICBitmapDecoder> decoder;
    AssertHR(factory->CreateDecoderFromFilename(
        path,
        NULL,
        GENERIC_READ,
        WICDecodeMetadataCacheOnDemand,
        &decoder
    ));

    ComPtr<IWICBitmapFrameDecode> frame;
    AssertHR(decoder->GetFrame(0, &frame));

    // 32bit RGBAに変換
    ComPtr<IWICFormatConverter> converter;
    AssertHR(factory->CreateFormatConverter(&converter));
    AssertHR(converter->Initialize(
        frame.Get(),
        GUID_WICPixelFormat32bppPBGRA,
        WICBitmapDitherTypeNone,
        NULL,
        0.0f,
        WICBitmapPaletteTypeCustom
    ));

    return converter;
}


Direct2Dアプリケーションで利用

ID2D1RenderTargetのDrawBitmapを利用して描画します。

ComPtr<ID2D1Bitmap> image = WICBitmapFromFile(L"Parrots.bmp");
renderTarget->DrawBitmap(image.Get(), D2D1::RectF(0, 0, width, height));


ただしWICを利用するに際し、アプリケーションの始めにCoInitializeを呼んでおく必要があります。

AssertHR(CoInitialize(NULL));
...
CoUninitialize();


Direct2Dの基本的な使い方は下記を参照してみてください。
Direct2D導入(ID2D1HwndRenderTarget) - 何でもプログラミング
Direct2D導入(ID2D1DeviceContext) - 何でもプログラミング






SDL導入

画像処理コードの確認の際、C++で簡単にウィンドウを表示したいと思い、SDLを少し使ってみました。

SDLダウンロード

下記ページよりdllをダウンロードします。
Simple DirectMedia Layer - SDL version 2.0.7 (stable)
f:id:any-programming:20171202132413p:plain

shared_ptrに変換する関数定義(任意)

SDLはCのライブラリのため、ポインタの開放は手動でやる必要があります。(例:SDL_CreateWindow → SDL_DestroyWindow)

ポインタの管理を楽にするため、shared_ptrに変換する関数を準備します。

shared_ptr作成時に対応するデストラクタを渡すようにしています。

#define DEEFINE_TO_SHARED_PTR(type, destructor) std::shared_ptr<type> ToSharedPtr(type* p) { return std::shared_ptr<type>(p, destructor); }
DEEFINE_TO_SHARED_PTR(SDL_Window,   SDL_DestroyWindow)
DEEFINE_TO_SHARED_PTR(SDL_Renderer, SDL_DestroyRenderer)
DEEFINE_TO_SHARED_PTR(SDL_Surface,  SDL_FreeSurface)
DEEFINE_TO_SHARED_PTR(SDL_Texture,  SDL_DestroyTexture)


簡単な実装

画像を読み込んで表示するアプリケーションを実装してみます。

ついでにマウスの位置に正方形も描画してみます。

f:id:any-programming:20171202173056p:plain

#include <SDL.h>
#pragma comment(lib, "SDL2.lib")
#pragma comment(lib, "SDL2main.lib")

int main(int, char**)
{
    SDL_assert(SDL_Init(SDL_INIT_VIDEO) == 0);

    std::shared_ptr<SDL_Window> window = ToSharedPtr(SDL_CreateWindow(
        "SDL Sample", 
        SDL_WINDOWPOS_UNDEFINED, 
        SDL_WINDOWPOS_UNDEFINED, 
        640, 
        480, 
        0
    ));
    SDL_assert(window);
    
    std::shared_ptr<SDL_Renderer> renderer = ToSharedPtr(SDL_CreateRenderer(window.get(), -1, 0));
    SDL_assert(renderer);

    std::shared_ptr<SDL_Surface> surface = ToSharedPtr(SDL_LoadBMP("Parrots.bmp"));
    SDL_assert(surface);

    std::shared_ptr<SDL_Texture> texture = ToSharedPtr(SDL_CreateTextureFromSurface(renderer.get(), surface.get()));
    SDL_assert(texture);
    
    while (1)
    {
        SDL_Event event;
        if (SDL_PollEvent(&event) == 0)
            continue;

        if (event.type == SDL_QUIT)
            break;
        else if (event.type == SDL_MOUSEMOTION)
        {
            SDL_assert(SDL_RenderClear(renderer.get()) == 0);

            // 画像描画
            SDL_assert(SDL_RenderCopy(renderer.get(), texture.get(), nullptr, nullptr) == 0);
            
            // マウスの位置に正方形描画
            SDL_Rect rect = { event.motion.x, event.motion.y, 50, 50 };
            SDL_assert(SDL_SetRenderDrawColor(renderer.get(), 255, 255, 255, 255) == 0);
            SDL_assert(SDL_RenderFillRect(renderer.get(), &rect) == 0);

            SDL_RenderPresent(renderer.get());
        }
    }

    SDL_Quit();

    return 0;
}






複数のキーを利用できるConditionalWeakTableクラス

下記記事にて、2つのキーを利用できるWeakTableを実装してみました。
2つのキーを利用できるConditionalWeakTableクラス - 何でもプログラミング

今回は3,4個と拡張しやすいように、複数個のキーを利用できるWeakTableを実装してみたいと思います。

Keysクラス

前回は2つのWeakReferenceプロパティをもっていましたが、今回はWeakReference配列を扱うよう変更しました。

EqualsとGetHashCodeを、WeakReferenceではなくTargetで行うところは変わっていません。

class Keys
{
    public WeakReference[] WeakKeys { get; }
    int _hash;

    public bool IsAlive()
    {
        return WeakKeys.All(x => x.IsAlive);
    }

    public Keys(object[] keys)
    {
        WeakKeys = keys.Select(x => new WeakReference(x)).ToArray();

        _hash = keys.Aggregate(365011897, (hash, key) => hash * -1521134295 + key.GetHashCode());
    }

    public override bool Equals(object obj)
    {
        var keys = obj as Keys;
        return keys != null
            && WeakKeys.Length == keys.WeakKeys.Length
            && WeakKeys.Zip(keys.WeakKeys, TargetEquals).All(x => x);
    }

    bool TargetEquals(WeakReference weakRef1, WeakReference weakRef2)
    {
        var ref1 = weakRef1.Target;
        var ref2 = weakRef2.Target;
        return ref1 != null
            && ref2 != null
            && Equals(ref1, ref2);
    }

    public override int GetHashCode()
    {
        return _hash;
    }
}


WeakTableクラス

まず、一般的はkey配列に対応したクラスを作成します。

class WeakTable<TValue>
{
    object _lockObj = new object();
    Dictionary<Keys, TValue> _dictionary = new Dictionary<Keys, TValue>();
    public WeakTable()
    {
        WeakEventManager<GCNotifier, EventArgs>.AddHandler(null, nameof(GCNotifier.Collected), 
            (s, e) => CheckReferences());
    }

    public void Add(object[] keys, TValue value)
    {
        lock (_lockObj)
        {
            _dictionary[new Keys(keys)] = value;
        }
    }

    public bool TryGetValue(object[] keys, ref TValue value)
    {
        lock (_lockObj)
        {
            var key = new Keys(keys);
            if (_dictionary.ContainsKey(key) == false)
                return false;
            value = _dictionary[key];
            return true;
        }
    }

    public void CheckReferences()
    {
        lock (_lockObj)
        {
            _dictionary = _dictionary
                .Where(x => x.Key.IsAlive())
                .ToDictionary(x => x.Key, x => x.Value);
        }
    }
}


これを利用して、例えば2つのキーのWeakTableは下記のように定義します。

class WeakTable<TKey1, TKey2, TValue>
    where TKey1 : class
    where TKey2 : class
{
    WeakTable<TValue> _table = new WeakTable<TValue>();

    public void Add(TKey1 key1, TKey2 key2, TValue value) =>
        _table.Add(new object[] { key1, key2 }, value);

    public bool TryGetValue(TKey1 key1, TKey2 key2, ref TValue value) =>
        _table.TryGetValue(new object[] { key1, key2 }, ref value);
}






2つのキーを利用できるConditionalWeakTableクラス

.NETのConditionalWeakTableは、キーを弱参照で保持し、キーがGCされたときに自動でキーと値がRemoveされるようになっています。

しかし、キーとして複数の参照を持ち、参照のうちどれかがGCされたらRemoveを行うといった使い方はできません。

今回は複数の参照キーを指定できるConditionalWeakTableのようなものを実装してみたいと思います。

ConditionalWeakTableの中身

内部的にDependentHandleという構造体で管理しているようですが、privateのため利用できません。

Ephemeronというデータ構造を利用しているらしいですが、詳しくは追及していません。

2つのキーを弱参照で保持するクラス

Keyを弱参照で保持し、EqualsやGetHashCodeをもともとのKeyから算出するクラスを作成します。

class Keys
{
    public WeakReference<TKey1> Key1 { get; }
    public WeakReference<TKey2> Key2 { get; }
    int _hash;

    public bool IsAlive()
    {
        return Key1.TryGetTarget(out var key1)
            && Key2.TryGetTarget(out var key2);
    }

    public Keys(TKey1 key1, TKey2 key2)
    {
        Key1 = new WeakReference<TKey1>(key1);
        Key2 = new WeakReference<TKey2>(key2);

        // WeakReferenceではなく、もとのkeyからhash作成
        _hash = 365011897;
        _hash = _hash * -1521134295 + key1.GetHashCode();
        _hash = _hash * -1521134295 + key2.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        // WeakReferenceの比較ではなく、中身の参照の比較
        var keys = obj as Keys;
        return keys != null
            && TargetEquals(Key1, keys.Key1)
            && TargetEquals(Key2, keys.Key2);
    }

    bool TargetEquals<T>(WeakReference<T> weakRef1, WeakReference<T> weakRef2)
        where T : class
    {
        return weakRef1.TryGetTarget(out var ref1)
            && weakRef2.TryGetTarget(out var ref2)
            && Equals(ref1, ref2);
    }

    public override int GetHashCode()
    {
        return _hash;
    }
}


WeakTableクラス

上記のKeysクラスを利用して、WeakTable本体を実装していきます。

class WeakTable<TKey1, TKey2, TValue>
    where TKey1 : class
    where TKey2 : class
{
    Dictionary<Keys, TValue> _dictionary = new Dictionary<Keys, TValue>();

    public void Add(TKey1 key1, TKey2 key2, TValue value)
    {
        _dictionary[new Keys(key1, key2)] = value;
    }

    public bool TryGetValue(TKey1 key1, TKey2 key2, ref TValue value)
    {
        var key = new Keys(key1, key2);
        if (_dictionary.ContainsKey(key) == false)
            return false;
        value = _dictionary[key];
        return true;
    }

    public void CheckReferences()
    {
        // 不必要なレコードを削除
        _dictionary = _dictionary
            .Where(x => x.Key.IsAlive())
            .ToDictionary(x => x.Key, x => x.Value);
    }
}


GCが行われたときにCheckReferencesを呼ぶ

現状のままでは、利用者がCheckReferencesを呼ばないと不必要なレコードが解放されないので、GC時に呼ばれるよう実装してみます。

GCの検知の詳細は下記記事を参照してみてください。
Garbage Collectionを検知する(C#) - 何でもプログラミング

今回はこのようなクラスを用意しました。

class GCNotifier
{
    public static event EventHandler Collected;

    static GCNotifier()
    {
        new DummyObject();
    }

    class DummyObject
    {
        ~DummyObject()
        {
            if (!AppDomain.CurrentDomain.IsFinalizingForUnload()
            && !Environment.HasShutdownStarted)
            {
                Collected?.Invoke(null, EventArgs.Empty);
                new DummyObject();
            }
        }
    }
}


GCNotifierを利用して、WeakTableに実装を追加します。

WeakEventManagerでCollectedにアタッチし、lock機構を追加しています。

class WeakTable<TKey1, TKey2, TValue>
    where TKey1 : class
    where TKey2 : class
{
    object _lockObj = new object();
    Dictionary<Keys, TValue> _dictionary = new Dictionary<Keys, TValue>();

    public WeakTable()
    {
        WeakEventManager<GCNotifier, EventArgs>.AddHandler(null, nameof(GCNotifier.Collected), 
            (s, e) => CheckReferences());
    }

    public void Add(TKey1 key1, TKey2 key2, TValue value)
    {
        lock (_lockObj)
        {
            _dictionary[new Keys(key1, key2)] = value;
        }
    }

    public bool TryGetValue(TKey1 key1, TKey2 key2, ref TValue value)
    {
        lock (_lockObj)
        {
            var key = new Keys(key1, key2);
            if (_dictionary.ContainsKey(key) == false)
                return false;
            value = _dictionary[key];
            return true;
        }
    }

    public void CheckReferences()
    {
        lock (_lockObj)
        {
            _dictionary = _dictionary
                .Where(x => x.Key.IsAlive())
                .ToDictionary(x => x.Key, x => x.Value);
        }
    }
}






Garbage Collectionを検知する(C#)

通常のプログラミングにおいて、いつGCされたか気にする必要はありませんが、極まれにGCのタイミングで処理をしたい時があります(設計で回避するべきではありますが…)

今回はGCを検知する方法を調べてみました。

GC.WaitForFullGCComplete

GCクラスの関数を利用する方法です。

これを利用するには、App.configで下記が指定されている必要があります。(コンカレントGCを無効化)

<configuration>
  <runtime>
    <gcConcurrent enabled="false" />
  </runtime>
</configuration>


GCNotifierクラスを定義してみます。

GC.RegisterForFullGCNotificationで通知を有効化し、GC.WaitForFullGCApproachとGC.WaitForFullGCCompleteで通知を待ちます。

GC.RegisterForFullGCNotificationの引数は、大きいほど通知を受け取りやすくなるそうです。

static class GCNotifier
{
    public static event Action Collected;
    public static void Start()
    {
        GC.RegisterForFullGCNotification(10, 10);
        Task.Run(() =>
        {
            while (true)
            {
                GC.WaitForFullGCApproach();
                if (GC.WaitForFullGCComplete() == GCNotificationStatus.Succeeded)
                    Collected?.Invoke();
            }
        });
    }
}


利用は下記のようになります。

GCNotifier.Collected += () => Console.WriteLine("collected");
GCNotifier.Start();


Finalizeを利用

下記のものを簡略化してみます。
Jeffrey Richter: Receiving notifications when garbage collections occur – Microsoft Press blog

DummyクラスのFinalizeでコールバックを呼び、さらに新しいDummyオブジェクトを生成しています。

GCNotifierクラスは下記のようになり、利用方法はGC.WaitForFullGCCompleteの時と同じです。

static class GCNotifier
{
    public static event Action Collected;
    public static void Start()
    {
        new DummyObject();
    }
    class DummyObject
    {
        ~DummyObject()
        {
            if (!AppDomain.CurrentDomain.IsFinalizingForUnload()
            &&  !Environment.HasShutdownStarted)
            {
                Collected?.Invoke();
                new DummyObject();
            }
        }
    }
}


比較

GC.WaitForFullGCCompleteは、GCの後と前で処理を追加できるのが利点ですが、コンカレントGCが使えないのと、全てのGCで通知が来るわけではないのが欠点です。

Finalize利用は、全てのGCを検知できるのが利点ですが、GC前に処理が追加できないのが欠点です。





F#でメモ化(ConditionalWeakTable)

プログラムの高速化のため、一度計算した値を再利用することはしばしばあります。

しかし結果を状態として保存する必要があるため、F#では少し扱いにくいです。

そこで、ある関数を結果を再利用する関数に変換する、memoize関数を定義してみたいと思います。

memoize関数

入力と結果をDictionaryに保存しておく関数に変換しています。

またConditionalWeakTableを利用することにより、selectKeyで寿命を共にするオブジェクトを指定できるようになっています。

let memoize (f : 'Args -> 'Result) (selectKey : 'Args -> 'Key) = 
    let table = ConditionalWeakTable<'Key, Dictionary<'Args, 'Result>>()
    fun (args : 'Args) ->
        let key = selectKey args
        let dictionary = table.GetOrCreateValue(key)
        if dictionary.ContainsKey(args) = false then
            dictionary.[args] <- f args
        dictionary.[args]

利用しやすいように、複数入力に対応した関数を用意しておきます。(寿命は第一引数と同じになります。)

let memoize1 (f : 'Arg -> 'Result) =
    memoize f id

let memoize2 (f : 'Arg1 -> 'Arg2 -> 'Result) =
    let tupled = memoize (fun (x1, x2) -> f x1 x2) fst
    fun x1 x2 -> tupled (x1, x2)

let memoize3 (f : 'Arg1 -> 'Arg2 -> 'Arg3 -> 'Result) =
    let tupled = memoize (fun (x1, x2, x3) -> f x1 x2 x3) (fun (x, _, _) -> x)
    fun x1 x2 x3 -> tupled (x1, x2, x3)


実際の利用

下記のようなNameレコードを例にしてみます。

フルネームを作成するfull関数は、二度目以降は結果を再利用しています。

また計算結果はNameオブジェクトと寿命が同じになります。

type Name =
    { First : string
      Last : string
    }
module Name =
    let full = memoize1 (fun name -> 
        printf "called\n" 
        name.First + name.Last)
let name = { First = "John"; Last = "Smith" }
let full = Name.full name // called
let full2 = Name.full name


入力に参照型がない場合

今回はConditionalWeakTableを利用したため、寿命を共にする参照型の入力が必要となります。

寿命がなく、入力と結果が共にサイズが小さいのであれば、Dictionaryで管理してもいいと思われます。





ネストしたレコードの更新(Functional lenses)

下記記事にて、リフレクションを用いてネストしたレコードの更新を実装してみました。
ネストしたレコードの更新 (F#) - 何でもプログラミング

今回はFunctional lensesを利用して同様の内容を実装してみたいと思います。

Lens型定義

Lens型は対象オブジェクトから値を取得するGetと、値を更新した新しいオブジェクトを返すSetからなります。

type Lens<'Type, 'Field> =
    { Get : 'Type -> 'Field
      Set : 'Field -> 'Type -> 'Type
    }


Lens関数群定義

Lensを合成するcomposeが最も重要なものとなります。

module Lens =
    let create get set = 
        { Get = get; Set = set; }

    let set (lens : Lens<'a, 'b>) (value : 'b) (object : 'a) =
        lens.Set value object

    let update (lens : Lens<'a, 'b>) (f : 'b -> 'b) (object : 'a) =
        lens.Set (lens.Get object |> f) object

    let compose (lens1 : Lens<'a, 'b>) (lens2 : Lens<'b, 'c>) =
        { Get = lens1.Get >> lens2.Get
          Set = lens2.Set >> update lens1
        }

// composeを楽にする演算子
let inline (>-) (lens1 : Lens<'a, 'b>) (lens2 : Lens<'b, 'c>) =
    Lens.compose lens1 lens2


実際の利用

下記のレコードが定義されているとします。

type Container = { Value : int }
type Child = { Container : Container }
type Root = { Child : Child }

let record1 = { Child = { Container = { Value = 100 } } }

まずは各レコードのLensを定義します。

module Container =
    let value = Lens.create (fun x -> x.Value) (fun x y -> { y with Value = x })
module Child =
    let container = Lens.create (fun x -> x.Container) (fun x y -> { y with Container = x })
module Root = 
    let child = Lens.create (fun x -> x.Child) (fun x y -> { y with Child = x })

レコードの更新は、下記のように記述します。

現在値を参照する場合はLens.updateを利用します。

let record2 = record1 |> Lens.set (Root.child >- Child.container >- Container.value) 200


リフレクションと比較

リフレクションを利用したほうがLensの定義も必要なく簡潔に記述できます。

一方Lensを利用するとパフォーマンスを期待でき、リフレクションのない言語でも応用可能です。