2021年08月28日

RegisterHotKeyがWindowsの標準ホットキーに先を越されて登録できない場合

あれ登録してるのExplorerなので、Explorerを全部落としてみましょう。
explorerをtaskkillして、自分のを登録してから再度explorer立ち上げれば良いです。
※タスクバー再復活の処理してないプログラムはアイコン消えます(昔のによくある)
タグ:Program
posted by Nick at 20:46| Comment(0) | TrackBack(0) | プログラミング

2019年02月22日

色々メモっておきたい

うーんひさしぶり。実に4年半ぶり。

まあもともと自分用のメモ的に使ってるブログですが、いくつか書いておきたいことが出来たので。

1.サクラエディタのマクロ
2.threejs
3.Unity

ここらへん。まあ暇を見ておいおい追加。
posted by Nick at 23:42| Comment(0) | TrackBack(0) | プログラミング

2013年05月31日

なぜテクスチャサイズは2の累乗なのか

作ってて普通に192x192のテクスチャでいい感じだったんで
別にそんな必要ねーじゃんとか思ってたんですが、
いろいろやってて、なんとなく理由が分かって来ました。

1.ポリゴンのテクスチャは明示するまで同じテクスチャを使う

これびっくりしました。3Dってのは、テクスチャをポリゴンに指定して、
後はそのポリゴンをごちゃごちゃいじるんだと思ってたので。
Direct3Dはそうではなく、テクスチャステージってのがあって、
そこにテクスチャと描写方法を指定して、全ポリゴンがそれを使うって処理なんですよ。

2.1.の理由によりテクスチャを使いまわす必要がある

毎フレームごとにテクスチャ変えてちゃしんどいでしょうね。
まあポリゴンに色(含透明度)と位置を指定できるんで、
大体は使いまわせそうではあります。

3.2.の位置指定がfloatである

これ。これが問題。浮動小数点です。(0.0〜1.0で指定)

4.浮動小数点(IEEE754)は分母が2の累乗でなければ有限小数として表現できない

意味不明な感じですが、まあ要するにそういうこと。
浮動小数点で==判定するなってやつですね。こんなの。

// 1/100をセット
double denom = 1.0 / 100, val = 0;

// 1/100を100回足す
for( int i = 0; i < 100; i ++ ) val += denom;

// 判定
if( val == 1 ) { /* ここは絶対に処理されない */ }


処理されない理由は、1/100を2進数で表現すると循環小数になるので、
変数に代入する際、丸めないと入りません。(四捨五入だったかな)
その時点で誤差になるので、それを足しても誤差があるままだよってことです。

で、これをテクスチャに用いると、その誤差分ずれることがある。
この誤差は浮動小数点の仕様なんで仕方ないです。

でも、誤差を出させない方法があります。

それが有限小数だけ使うこと。
2進数における有限小数になる条件は、分母が2の累乗である分数です。

てことで、テクスチャのサイズは2の累乗なんですねえ。
位置を整数/サイズで指定する限り、必ず有限小数になるので、
ずれないですもんねえ。なるほどなあ。

※)有限小数についてもう少し調べたら、「分母が基数の素因数で構成されていること」
らしいです。なるほど、2進数は2しかないし、10進数だと2と5になるんですね。

※2)蛇足ですが、この手の説明をする時、必ず5の倍数かつ2の累乗でない数を分母にするはずです。
posted by Nick at 20:27| Comment(0) | TrackBack(0) | プログラミング

2010年07月22日

スレッドの同期

突然ですが、こんなソースがあったとしたら、
結果に何が表示されるか分かりますか?
volatile int g = 0; // メモリから読むことを強制

WORD WINAPI tp( LPVOID v ) {
    for( int i = 0; i < 10000; i ++ ) g++;
    return 0;
}

int WINAPI WinMain(HINSTANCE hi, HINSTANCE hpi, LPSTR command, int show) {
    const int tmax = 64;
    HANDLE th[ tmax ];
    for( int i = 0; i < tmax; i ++ ) {
        DWORD tid;
        th[ i ] = CreateThread( 0, 0, (LPTHREAD_START_ROUTINE)tp, 0, 0, &tid );
    }
    // 終了待ち
    WaitForMultipleObjects( tmax, th, TRUE, INFINITE );

    // 止めてgを確認
    __asm int 3;
    return 0;
}

tpはgを10000回インクリメントするだけ。
WinMainはgを実行するスレッドを64個作るだけです。

やってみれば分かりますが、答えは「環境によって不定」です。
マルチスレッドを知らない人は「640000」だと思ったんじゃないでしょうか。

VC++2008付属コンパイラだと、これを
mov eax, 0x2710 // 10進で10000
mov ecx, 1
loop:
add [g], ecx
sub eax, ecx
jne loop
ret

こんな感じでコンパイルしてくれますが、
マルチコアCPUだとgへのaddが同時に起こることがあります。
その場合1回のaddと同じ結果になるので、
競合したaddの結果が失われることになり、結果としてインクリメントが失敗します。
つまり正確な答えは、「マルチコア/CPUの場合640000以下、それ以外は640000」
となります。

さて、これをアトミック(重複せずに、1つ1つ、等という意味)に処理しなければ
正常な結果は得られません。これを同期処理と言います。

同期にはミューテックス、セマフォなどなどありまして、
MicrosoftがWin32APIとしていろいろ提供しています。
その中でもとても単純で応用力のあるAPIが
InterlockedExchangeという関数。以下MSDNより。

InterlockedExchange
指定された 1 個の変数の内容ともう 1 つの値の交換を一括して行います。
この関数は、複数のスレッドが同じ変数を同時に使うことを防止します。

LONG InterlockedExchange(
LPLONG Target, // 交換に使われる変数
LONG Value // 新しい値
);
戻り値:Target パラメータが指す変数の、交換前の値が返ります。


この関数は同時に実行されないことが保証されているので、
こんな感じで同期処理が取れます。
long flag = 0;
WORD WINAPI tp( LPVOID v ) {
    for( ;; ) {
        if( InterlockedExchange( &flag, 1 ) == 0 ) break;
        Sleep( 1 );
    }

    for( int i = 0; i < 10000; i ++ ) g++;
    InterlockedExchange( &flag, 0 );
    return 0;
}

例としてスレッド1,2がtpを実行するとし、
スレッド2が先にInterlockedExchangeに入ったとします。

1.スレッド1はInterlockedExchangeで待機状態になります。
2.スレッド2がInterlockedExchangeから戻り値0を取得し、処理続行します。
3.スレッド1がInterlockedExchangeから戻り値1を受け取ります。
4.0でなかったので、一瞬眠って再度InterlockedExchangeを試行します。
 ※)スレッド2がflagを0にするまで3,4を繰り返します。
5.スレッド2がflagを0にします
6.スレッド1がInterlockedExchangeから戻り値0を取得し、処理続行します。

とまあこんな流れになります。ややこしいですけど。

ミューテックスにしろセマフォにしろ、結局は
同時に処理してはいけない状況への対策なので、
上記処理を応用すれば実現できます。たぶん。

余談)
類似関数としてInterlockedExchangeAddってAPIがあるんですが、
MSDNでの説明が酷いんですよね。以下MSDNより。

InterlockedExchangeAdd
加数変数への増分値の原子加算を実行します。
これで意味が分かった人はエスパーだと思います。
posted by Nick at 13:59| Comment(0) | TrackBack(0) | プログラミング

2010年07月12日

拡大縮小

拡大縮小といえば画像処理関係では避けて通れない話題。
きれいに計算する方法は昔からいろいろ議論されてます。

拡大と縮小では計算方法…というか、考え方が変わるので、
(拡大は元データからの穴をいかに補完するかが重要ですし、
 縮小は元データをいかに余さず反映するかが重要です)
これを両方できて、かつきれいなアルゴリズムというのは
結構少なかったりします。

で、そのアルゴリズムの中でも、かなり単純で、実装も簡単で、
ほぼ最速で、縮小も拡大もできる便利なアルゴリズムを紹介します。

考え方としては「転送先画像」を主体として、
転送先画像のピクセルpd(dx,dy)に対して、
最も近い転送元画像のピクセルps(sx,sy)を求め、
それを転送先画像のピクセルとする、となります。

日本語下手ですいませんが、要するにソースで書けばこうなります。
vbpicture vp( L"c:\\a.jpg" );
int sw = vp.W, sh = vp.H;
long *ps = vp.GetpData();

int dw = 600, dh = 800;
vbpicture vd( w, h );
long *pd = vd.GetpData();

// 単純拡大縮小アルゴリズム
for( int y = 0; y < h; y ++ ) {
    for( int x = 0; x < w; x ++ ) {
        int sx = x * sw / dw;
        int sy = y * sh / dh;
        pd[ x + y * w ] = ps[ sx + sy * vp.W ];
    }
}

BitBlt( GetDC( 0 ), 0, 0, vd.W, vd.H, vd.hDC, 0, 0, SRCCOPY );
確かニアストレイバーだとかなんとか言うような気がします。
一応自分で考えたアルゴリズムなんで、正式名称とかよく分かりません。

ちなみに実行速度はCeleron2.2GHz 800x600x32 -> 600x800x32で
BitBltまで含めて3.2msecほど。
StretchBltの高速モード(NOT HALFTONE)と同じぐらいかな。
あれよりマシな画質なはずですし、そこそこ使えるのではないでしょうか。

計算コスト的には、転送先へのシーケンシャルアクセスのコストは
どんなアルゴリズムだろうが絶対に避けられませんので、
まあそれなりに優秀だと思います。
posted by Nick at 12:39| Comment(0) | TrackBack(0) | プログラミング

2010年07月01日

自動レベル補正

普通は全画素をYUV色空間に変換して、輝度のminとmaxを求めて、
各画素pに対して(p-min)*最大輝度/(max-min)みたいな計算をして、
RGB色空間に直して再セット、といったアルゴリズムになります。

ただそれだとノイズだとかでうまく補正できなかったりするので、
上下ある程度は無視するような処理を入れたりもします。
厳密にやるなら全輝度のソートとかするといい感じなんですが、
面倒だしそこまでの画質はいらん、ってなことでさくっと作ったのがこちら。

void PictBrightAdjust( vbpicture *pp ) {

    // 中のレベルを大雑把にまとめる
    const int nsmax = 256;
    int ns[ nsmax ];
    memset( ns, 0, nsmax * sizeof( int ) );

    // 初期化
    int r,g,b;
    long *pd = pp->GetpData();

    // 一度輝度を調べる
    for( int y = 0; y < pp->H; y ++ ) {
        for( int x = 0; x < pp->W; x ++ ) {
            long2RGB( pd[ x + y * pp->W ], r, g, b );
            int n = ( r + g + b ) / 3;
            ns[ n ] ++;
        }
    }

    // nsの一覧から、上下5%を除外
    int min, max, pt, co = pp->W * pp->H;
    pt = 0; for( min = 0; min < nsmax; min ++ ) { pt += ns[ min ]; if( pt >= co * 0.05 ) break; }
    pt = 0; for( max = nsmax - 1; max >= 0; max -- ) { pt += ns[ max ]; if( pt >= co * 0.05 ) break; }
    if( max - min <= 0 ) return; // min, maxチェック
    
    // 変換
    for( int y = 0; y < pp->H; y ++ ) {
        for( int x = 0; x < pp->W; x ++ ) {
            long2RGB( pd[ x + y * pp->W ], r, g, b );
            r = ( r - min ) * nsmax / ( max - min ); if( r > 255 ) { r = 255; } else if( r < 0 ) { r = 0; }
            g = ( g - min ) * nsmax / ( max - min ); if( g > 255 ) { g = 255; } else if( g < 0 ) { g = 0; }
            b = ( b - min ) * nsmax / ( max - min ); if( b > 255 ) { b = 255; } else if( b < 0 ) { b = 0; }
            pd[ x + y * pp->W ] = r << 16 | g << 8 | b;
        }
    }
}

輝度の計算に(R+G+B)/3とか使ってます。あほです。
でも適当ならこれでいいです。自然画ならそれなりに見栄えもします。

ただ見栄えはそれなりなんですが、問題は意外(当たり前?)にも遅かったこと。
CPU2GHzのCeleronで800x600x32の画像処理に30msec近くかかってました。

HALFTONEのStretchBlt並に遅いとかどんだけ無駄処理なのよってことで
さくっと高速化してみました。(変換のとこだけ)

    // 変換
    int ptrmax = pp->H * pp->W;
    int nstemp = ( nsmax << 12 ) / ( max - min );
    for( int ptr = 0; ptr < ptrmax; ptr ++ ) {
        long2rgb( pd[ ptr ], r, g, b );
        r = ( ( r - min ) * nstemp ) >> 12; if( r > 255 ) { r = 255; } else if( r < 0 ) { r = 0; }
        g = ( ( g - min ) * nstemp ) >> 12; if( g > 255 ) { g = 255; } else if( g < 0 ) { g = 0; }
        b = ( ( b - min ) * nstemp ) >> 12; if( b > 255 ) { b = 255; } else if( b < 0 ) { b = 0; }
        pd[ ptr ] = r << 16 | g << 8 | b;
    }

まずptrを計算するのをやめ。どうせy,xの要素は使わないんでインクリメントで代用しました。
あと割り算はやっぱり超遅いので、掛けて割る分をまとめてキャッシュ。
割り算をキャッシュする場合、整数演算ですから普通はオーバーフローしちゃうんですけど
このアルゴリズムだと最大で255*255の16ビットまでしか使わないので、
残りの12ビット(64ビットCPU限定ならもっと使えますが、まあそこまでいらないし)を
オーバーフロー対策として使用してます。

それなりに効果はあったみたいで、12〜13msecぐらいまで削れました。
さくっと高速化するならあとはMMX使うのがいいですね。桁あふれ対策もあるし。
posted by Nick at 20:40| Comment(0) | TrackBack(0) | プログラミング

2010年06月17日

vbpicture

使い方は
vbpicture vp( ファイル名 );
で初期化するだけで、

BitBlt( GetDC( 0 ), 0, 0, vp.W, vp.H, vp.hDC, 0, 0, SRCCOPY );
とかでコピーできます。さらに
long* pd = vp.GetpData();
で、32ビットDIBピクセルデータのアドレスが取得できまして、
for( int y = 0; y < vp.H; y ++ ) {
    for( int x = 0; x < vp.W; x ++ ) {
        pd[ x + y * vp.W ] = x * 255 / vp.W;
    }
}
とかしてBitBltしちゃうと、変更したピクセルが反映されて出力されます。
いやほんと便利。32ビットDIB限定なのでめんどくさいワードアラインメントだとか
15ビットと16ビットの誤差修正だとかないし
応用すればアルファチャンネルも使えちゃいます。

ソースはこれ。まず宣言部
#include "windows.h"
#include "olectl.h"

class vbpicture {
public:
    // 読み取り専用メンバ
    // コンストラクタでだけは書き込んでるけどね
    const HDC hDC;    
    const int W;
    const int H;

    // 書き込み可能ポインタ(32ビット)
    long* GetpData();

    vbpicture( WCHAR *fnm );
    ~vbpicture();
private:
    long *pdata;
    HBITMAP hbmp;
    BITMAP bm;
};

次にソース
long* vbpicture::GetpData() { return pdata; }
vbpicture::vbpicture( WCHAR* fnm ) : W(0), H(0), hDC(0) {

    // clear
    hbmp = 0;
    memset( &bm, 0, sizeof( bm ) );
    pdata = 0;

    // OLEのLoadPictureで読み込み
    VARIANT v;
    int slen = wcslen( fnm );
    v.bstrVal = SysAllocStringLen( 0, slen );
    memcpy( v.bstrVal, fnm, slen * 2 );
    v.vt = VT_BSTR;
    
    IDispatch *id;
    IPicture *ip;
    HRESULT hr = OleLoadPictureFile( v, &id );
    SysFreeString( v.bstrVal );
    if( hr != S_OK ) return;
    hr = id->QueryInterface( IID_IPicture, (void**)&ip );
    id->Release();
    if( hr != S_OK ) return;

    // 読み取り専用 W, H に値をセット
    OLE_XSIZE_HIMETRIC ow;
    OLE_YSIZE_HIMETRIC oh;
    ip->get_Width( &ow );
    ip->get_Height( &oh );
    HDC hdcdt = GetDC( 0 );
    (int)W = (int)( GetDeviceCaps( hdcdt, LOGPIXELSX )
        / 2540.0 * ow );
    (int)H = (int)( GetDeviceCaps( hdcdt, LOGPIXELSY )
        / 2540.0 * oh );

    // CreateCompatibleBitmap/DCで自前DCを確保
    // 読み取り専用 hDC に値をセット
    (HDC)hDC = CreateCompatibleDC( hdcdt );
    BITMAPINFO bmi; memset( &bmi, 0, sizeof( bmi ) );
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biHeight = H;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biWidth = W;
    bmi.bmiHeader.biSize = sizeof( bmi.bmiHeader );
    hbmp = CreateDIBSection( hDC, &bmi, DIB_RGB_COLORS, (void**)&pdata, 0, 0 );
    HGDIOBJ hg = SelectObject( hDC, hbmp );
    GetObject( hbmp, sizeof( bm ), &bm );

    // 自前DCに描画
    ip->Render( hDC, 0, 0, W, H, 0, oh, ow, -oh, 0 );

    // 掃除
    ip->Release();
    ReleaseDC( 0, hdcdt );
}
vbpicture::~vbpicture() {
    if( hbmp ) DeleteObject( hbmp );
    if( hDC ) DeleteDC( hDC );
}

        
読み取り専用変数ってのが無いような気がしたので、
無理やり実装してみました。(C#だとReadOnlyとかあるんですが)
constをキャストして無理やり書き込んでますが、
これをするのはコンストラクタだけ、という自分ルールを作りました。
まあ使う側がやっちゃったら自己責任だよね、ということで。

あとはOLEのLoadPictureを使ってるのがポイント。
しかしOLEってVBで使うにはほとんど気にしなくていいんですが、
VCで使うとめっちゃ面倒ですよね。便利なんだか不便利なんだか分かりません。
MSって一見簡単そうなものを超複雑に作るのが得意技ですね。

最後にlongポインタ使ってるのもポイントかな?
32ビット限定なんで32ビット変数で返してます。
普通はcharポインタ使うとこですが、まあそれは好みということで。
posted by Nick at 13:12| Comment(0) | TrackBack(0) | プログラミング