普通のUS配列かJIS配列のキーボードを使い、左手だけで入力を可能にするAutoHotkeyスクリプト、RibbitPad v1.00を公開しました。
RibbitPad
コンパイル済みexeとahkが入手可能です。
スペースバーと右Altキー(または変換キー)を2種の修飾キーとして用います。
配列図は以下の通り。

普通のUS配列かJIS配列のキーボードを使い、左手だけで入力を可能にするAutoHotkeyスクリプト、RibbitPad v1.00を公開しました。
RibbitPad
コンパイル済みexeとahkが入手可能です。
スペースバーと右Altキー(または変換キー)を2種の修飾キーとして用います。
配列図は以下の通り。

引き続きSIMDの練習。
モノクロやネガのようにリニアに読み書きすればいい処理と違って、y方向近傍を読まざるを得ない座標変換が入ると、キャッシュミスによる遅延が効いてきそうですが、避けて通るわけにも行かないので挑戦してみます。
まずは画像のスケール変換を書いてみました。
static wxImage ResizeImage(wxImage &src_image, double size) {
int src_real_w = src_image.GetWidth();
int src_real_h = src_image.GetHeight();
int dst_real_w = src_real_w*size;
int dst_real_h = src_real_h*size;
// 実際のビットマップの大きさ
int dst_w = std::min<int>(src_real_w, src_real_w*size);
int dst_h = std::min<int>(src_real_h, src_real_h*size);
int src_w = dst_w*src_real_w/dst_real_w;
int src_h = dst_h*src_real_h/dst_real_h;
if (dst_w*dst_h<=4)
return wxImage();
unsigned char* src = src_image.GetData();
unsigned char* src4 = (unsigned char*)malloc(src_w*src_h*3*4);
int aligned_dst_size = dst_w*dst_h*3;
aligned_dst_size += 3-(aligned_dst_size+3)%4;
unsigned char* dst = (unsigned char*)malloc(aligned_dst_size);
unsigned char* dst4 = (unsigned char*)malloc(dst_w*dst_h*3*4);
// byte => DWORD
for (int v=0; v<src_h; v++)
for (int u=0; u<src_w; u++) {
int p_s = (v*src_real_w + u)*3;
int p_s4 = (v*src_w + u)*3*4;
src4[p_s4 + 0] = src[p_s + 0];
src4[p_s4 + 4] = src[p_s + 1];
src4[p_s4 + 8] = src[p_s + 2];
}
switch (0) {
case 0: {
// bilinear
// 参照先srcを越えない範囲
int v_to = std::min<int>(dst_h-1, (src_h-1)*size);
__m128 xmm_dummy;
float dummy = 0;
/* 水平用 */
xmm_dummy = _mm_load1_ps(&dummy);
// RGB一括ロードのマスク
__m128i xmm_mask;
xmm_mask.m128i_i32[0] = 0x000000ff;
xmm_mask = _mm_shuffle_epi32(xmm_mask, 0);
xmm_mask.m128i_i32[3] = 0;
char *buf = (char*)_mm_malloc(16, sizeof(__m128i));
for (int v=1; v<v_to; v++) {
long l = v/size;
long l1 = l+1;
double wv = v/size - l;
double wv1 = 1 - wv;
// プリフェッチ
_mm_prefetch((const char*)(src4 + l*src_w*3*4), _MM_HINT_T2);
_mm_prefetch((const char*)(src4 + l1*src_w*3*4), _MM_HINT_T2);
// 参照先srcを越えない範囲
int u_to = std::min<int>(dst_w-1, (src_w-1)*size);
for (int u=1; u<u_to; u++) {
long k = u/size;
long k1 = k+1;
double wu = u/size - k;
double wu1 = 1 - wu;
long p_s_0_0 = (l*src_w + k)*3*4; /* (k, l) */
long p_s_0_1 = (l*src_w + k1)*3*4; /* (k+1, l) */
long p_s_1_0 = (l1*src_w + k)*3*4; /* (k, l+1) */
long p_s_1_1 = (l1*src_w + k1)*3*4; /* (k+1, l+1) */
long p_d = (v*dst_w+u)*3*4;
/*
dst4[p_d + 0] = (src4[p_s_0_0 + 0]*wu1 + src4[p_s_0_1 + 0]*wu)*wv1
+ (src4[p_s_1_0 + 0]*wu1 + src4[p_s_1_1 + 0]*wu)*wv;
dst4[p_d + 4] = (src4[p_s_0_0 + 4]*wu1 + src4[p_s_0_1 + 4]*wu)*wv1
+ (src4[p_s_1_0 + 4]*wu1 + src4[p_s_1_1 + 4]*wu)*wv;
dst4[p_d + 8] = (src4[p_s_0_0 + 8]*wu1 + src4[p_s_0_1 + 8]*wu)*wv1
+ (src4[p_s_1_0 + 8]*wu1 + src4[p_s_1_1 + 8]*wu)*wv;
*/
__m128 xmm_dst, xmm_wu, xmm_wv;
// DWORD転送 xmm_src_all_d: 0 [b] [g] [r]
__m128i xmm_all_tmp;
__m128 xmm_src_all_0, xmm_src_all_1, xmm_src_all_2, xmm_src_all_3;
xmm_all_tmp = _mm_loadu_si128((__m128i *)(src4 + p_s_0_0));
xmm_all_tmp = _mm_and_si128(xmm_mask, xmm_all_tmp);
xmm_src_all_0 = _mm_cvtepi32_ps(xmm_all_tmp);
xmm_all_tmp = _mm_loadu_si128((__m128i *)(src4 + p_s_0_1));
xmm_all_tmp = _mm_and_si128(xmm_mask, xmm_all_tmp);
xmm_src_all_1 = _mm_cvtepi32_ps(xmm_all_tmp);
xmm_all_tmp = _mm_loadu_si128((__m128i *)(src4 + p_s_1_0));
xmm_all_tmp = _mm_and_si128(xmm_mask, xmm_all_tmp);
xmm_src_all_2 = _mm_cvtepi32_ps(xmm_all_tmp);
xmm_all_tmp = _mm_loadu_si128((__m128i *)(src4 + p_s_1_1));
xmm_all_tmp = _mm_and_si128(xmm_mask, xmm_all_tmp);
xmm_src_all_3 = _mm_cvtepi32_ps(xmm_all_tmp);
// 色ごとに集める xmm_rtmp: r3 r2 r1 r0
__m128 xmm_colors_tmp[3]; // r, g, b
xmm_colors_tmp[0] = _mm_shuffle_ps(xmm_src_all_0, xmm_src_all_0, SHUFFLEBITS(3, 3, 3, 0)); // 0 0 0 r0
xmm_colors_tmp[0] = _mm_or_ps(xmm_colors_tmp[0], _mm_shuffle_ps(xmm_src_all_1, xmm_src_all_1, SHUFFLEBITS(3, 3, 0, 3))); // 0 0 r1 0
xmm_colors_tmp[0] = _mm_or_ps(xmm_colors_tmp[0], _mm_shuffle_ps(xmm_src_all_2, xmm_src_all_2, SHUFFLEBITS(3, 0, 3, 3))); // 0 r2 0 0
xmm_colors_tmp[0] = _mm_or_ps(xmm_colors_tmp[0], _mm_shuffle_ps(xmm_src_all_3, xmm_src_all_3, SHUFFLEBITS(0, 3, 3, 3))); // r3 0 0 0
xmm_colors_tmp[1] = _mm_shuffle_ps(xmm_src_all_0, xmm_src_all_0, SHUFFLEBITS(3, 3, 3, 1));
xmm_colors_tmp[1] = _mm_or_ps(xmm_colors_tmp[1], _mm_shuffle_ps(xmm_src_all_1, xmm_src_all_1, SHUFFLEBITS(3, 3, 1, 3)));
xmm_colors_tmp[1] = _mm_or_ps(xmm_colors_tmp[1], _mm_shuffle_ps(xmm_src_all_2, xmm_src_all_2, SHUFFLEBITS(3, 1, 3, 3)));
xmm_colors_tmp[1] = _mm_or_ps(xmm_colors_tmp[1], _mm_shuffle_ps(xmm_src_all_3, xmm_src_all_3, SHUFFLEBITS(1, 3, 3, 3)));
xmm_colors_tmp[2] = _mm_shuffle_ps(xmm_src_all_0, xmm_src_all_0, SHUFFLEBITS(3, 3, 3, 2));
xmm_colors_tmp[2] = _mm_or_ps(xmm_colors_tmp[2], _mm_shuffle_ps(xmm_src_all_1, xmm_src_all_1, SHUFFLEBITS(3, 3, 2, 3)));
xmm_colors_tmp[2] = _mm_or_ps(xmm_colors_tmp[2], _mm_shuffle_ps(xmm_src_all_2, xmm_src_all_2, SHUFFLEBITS(3, 2, 3, 3)));
xmm_colors_tmp[2] = _mm_or_ps(xmm_colors_tmp[2], _mm_shuffle_ps(xmm_src_all_3, xmm_src_all_3, SHUFFLEBITS(2, 3, 3, 3)));
/* wu1 wu wu1 wu */
xmm_wu.m128_f32[0] = wu1;
xmm_wu.m128_f32[1] = wu;
xmm_wu = _mm_shuffle_ps(xmm_wu, xmm_wu, SHUFFLEBITS(1, 0, 1, 0));
/* wv1 wv1 wv wv */
xmm_wv.m128_f32[0] = wv1;
xmm_wv.m128_f32[2] = wv;
xmm_wv = _mm_shuffle_ps(xmm_wv, xmm_wv, SHUFFLEBITS(2, 2, 0, 0));
#define BILINEAR_LINE_N(COLORINDEX) { \
/* 積 */ \
xmm_dst = _mm_mul_ps(xmm_colors_tmp[COLORINDEX], xmm_wu); \
xmm_dst = _mm_mul_ps(xmm_dst, xmm_wv); \
/* 水平 */ \
xmm_dst = _mm_hadd_ps(xmm_dst, xmm_dummy); \
xmm_dst = _mm_hadd_ps(xmm_dst, xmm_dummy); \
/* 結果を非テンポラルに書き込む */ \
xmm_all_tmp = _mm_cvtps_epi32(xmm_dst); \
_mm_stream_si128((__m128i*)(buf), xmm_all_tmp); \
dst4[p_d + COLORINDEX*4] = buf[0]; \
}
BILINEAR_LINE_N(0);
BILINEAR_LINE_N(1);
BILINEAR_LINE_N(2);
#undef BILINEAR_LINE_N
}
}
_mm_free(buf);
} break;
}
// DWORD => byte
for (int v=0; v<dst_h; v++)
for (int u=0; u<dst_w; u++) {
int p_d = (v*dst_w + u)*3;
int p_d4 = p_d*4;
dst[p_d + 0] = dst4[p_d4 + 0];
dst[p_d + 1] = dst4[p_d4 + 4];
dst[p_d + 2] = dst4[p_d4 + 8];
}
free((void*)src4);
free((void*)dst4);
wxImage result = wxImage(dst_w, dst_h);
result.SetData(dst);
return result;
}
とりあえず動くことは動く。絶望的に遅いので特に評価はしていませんが…
プリフェッチ命令とか非テンポラルストアとか、意味があるのか分からないものの使ってみました。
命令とコストの対応が分かってないので、何をやるにしても方針が立ちません。やはり一度真面目に勉強するべきか。
追記: float(ps)は遅そうなので、ふつうのCでSSE使わずに、256倍して整数で書いてみました。結果は2倍以上高速に…
やはり命令の使い方の前にコストを知る必要がありそうです。
カラーホイールを回すとCPUに負荷が掛かるようなので、彩度・輝度空間の描画部分にSIMD命令を使って高速化を図ってみました。
MMXとかSSE2を使うのにはインラインアセンブラしかないと思いこんでいたが、
#include <emmintrin.h>
#include <xmmintrin.h>
#include <mmintrin.h>
これらのヘッダファイルをインクルードすることで、イントリンシック記法が使えるそうです。(参考:SIMD命令セットの記述方法)
高速化する前のコード
for (unsigned long i=0; i<svw; i++)
for (unsigned long j=0; j<svw; j++) {
unsigned long s = (i*0xff)/svw;
unsigned long v = (j*0xff)/svw;
unsigned long vff = v*0xff;
unsigned long vffff = vff*0xff;
unsigned long vs = v*s;
unsigned long vsff = v*s*0xff;
unsigned long fvs = f*vs;
unsigned long P = (vff-vs)>>8; // v*(0xff-s)
unsigned long q = (vffff-fvs)>>16; // v*(0xff-f*s)
unsigned long t = (vffff-vsff+fvs)>>16; // v*(0xff-s+f*s)
unsigned long rgbmap[6][3] = {
{v, t, P},
{q, v, P},
{P, v, t},
{P, q, v},
{t, P, v},
{v, P, q},
};
unsigned long svpos = (svw2-j*svw-svw+i)*3;
sv_data[svpos] = rgbmap[modh][0];
sv_data[svpos + 1] = rgbmap[modh][1];
sv_data[svpos + 2] = rgbmap[modh][2];
}
XMMレジスタにおいてワード整数のMMX命令セット PMULLW, PSUBW, PSRLWを使って高速化したコード
#define COLORWHEEL(RR, GG, BB) {\
__m128i m_f; \
m_f.m128i_i32[0] = 0; m_f.m128i_i32[1] = 256; m_f.m128i_i32[2] = f; m_f.m128i_i32[3] = f1; \
for (unsigned long c=0,p=0; c<svw2-1; c++,p+=3) { \
unsigned long i = c%svw; \
unsigned long j = svw-1-c/svw; \
__m128i m_a, m_b, m_v, m_s; \
unsigned long s = (i*256)/svw; \
unsigned long v = (j*256)/svw; \
m_v.m128i_i32[0] = v; /* - v v v */ \
m_v = _mm_shuffle_epi32(m_v, 0); \
m_a = _mm_slli_epi32(m_v, 8); \
m_s.m128i_i32[0] = s; /* - s s s */ \
m_s = _mm_shuffle_epi32(m_s, 0); \
m_s = _mm_mullo_epi16(m_s, m_f); \
m_s = _mm_srli_epi32(m_s, 8); \
m_s = _mm_mullo_epi16(m_v, m_s); \
m_b = _mm_sub_epi16(m_a, m_s); \
m_s = _mm_srli_epi32(m_b, 8); \
unsigned long P = m_s.m128i_i32[1]; \
unsigned long q = m_s.m128i_i32[2]; \
unsigned long t = m_s.m128i_i32[3]; \
sv_data[p] = RR; \
sv_data[p + 1] = GG; \
sv_data[p + 2] = BB; \
}}
switch (modh) {
case 0: COLORWHEEL(v, t, P) break;
case 1: COLORWHEEL(q, v, P) break;
case 2: COLORWHEEL(P, v, t) break;
case 3: COLORWHEEL(P, q, v) break;
case 4: COLORWHEEL(t, P, v) break;
case 5: COLORWHEEL(v, P, q) break;
}
PMULLW(ワード単位の積)に相当するダブルワードの演算がSSE4.1にならないと使えない(多分)らしく、
E6300までしか持っていないので、計算を2回に分けてワード単位に納めました。
しばらくデバッグし、なんとかMMXでも前と同じものが描画できるようになりました。
喜び勇んでホイールをくるくるすると…
変わらない。
CPUが50% (2コア中1コアのmax)近くまで使われます。配列部分のストアが悪いのか、とおもって描画サイズを上げてみると、驚愕の事実が。
どれだけ大きくしてもCPU負荷変わらず。しかも前のコードでもほぼ同じくらい。SV空間描画部分は実はたいして重くなかったようです。
引き続きwxWidgets 2.8.8を弄っていますが、妙なマクロを多用したり、移植性を高めるためのトリックを駆使しているらしく、綺麗にカスタムドローできない。
まあいざとなればWindows専用(wxMSW~)に限定して、WindowsAPIを生で叩けばいいんですが。
そのwxWidgetsを使う大きな利点の一つ、ドック/分離自在なwxAUIが時々落ちます。分離は問題無いものの、ドック時がやや弱いらしい。
さて、前回色相環とSV平面によるカラーピッカーを作りましたが、今回はRGBスライダ、混色スライダ、前景背景色セレクタ等を追加し、それらを連動出来るようにしてみました。
![]()
非Observerのリスト破棄なんかは呼び出しが何度も往復して頭が痛くなりそうです。
今はテストということで、マルチレイヤのファイルとしてはwxImageとそのラッパーをwxFileStreamで繋げただけのフォーマットを自作していますが、最終的にはPSDを扱いたい。しかしどうもWebにサンプルコードや最新のPhotoshopの資料が少なく、やっと見つけたMyPSDを試してみたところ複数のレイヤを別々に読むのは無理そうです。
そろそろ実際に描画するところを真面目に書くべきなんでしょうが、Undoとかマウス/タブレットの併存、過去のサンプリング点を用いた手ブレ補正など、かなり設計を練り込まないとあとで酷いことになりそうです。
そんな訳で久しぶりにデザインパターンの本を復習したりしていました。いつまで経ってもFactoryとかTemplateとつくようなパタンの名前が覚えられない。
使えれば良いという気もしますが、名付けることで構造を発見しやすくもなるはずで、記憶力のなさに辟易します。
タブレットのAPI, WinTabを使ってみました。
あまりまとまった資料がなく、
などを参考に、試行錯誤しました。
クワッドディスプレイの上、システムの座標原点が中央にあるという、まるでテスト用にわざわざ作ったかのような環境で開発しているので、初っぱなから座標の関係が分からなくて苦労しました。
一応、簡単なポーリングモードのラッパー(C++)を置いておきます。tablet_20080725.zip
Favoしか持っていないので、傾きやら方向やらは未テスト。
ついでに(というかこちらが主目的?)作っている、ペイントアプリの概観。筆圧が効いています。
C++ + wxWidget. wxAUIというのがDock周りを全てやってくれるので、こういうソフトを書くのに向いていそう。
![]()
このカラーピッカー作るのには骨が折れました。C++の範囲では高速化したものの、MMXとかSSEの拡張命令は使っていないので、ホイール回すとSV空間の描画に少しもたつきます。
AutoHotkeyでWin + カーソルでウィンドウ移動、Win+Shift+カーソルでウィンドウサイズ変更するスクリプト。
; --------------------------------------------------
; Window Move
WinMoveStep(XD,YD) {
WinGet,win_id,ID,A
WinGetPos,x,y,,,ahk_id %win_id%
Step := 24
x := x + (XD * Step)
y := y + (YD * Step)
WinMove,ahk_id %win_id%,,%x%,%y%
return
}
#Left::WinMoveStep(-1,0)
#Right::WinMoveStep(1,0)
#Up::WinMoveStep(0,-1)
#Down::WinMoveStep(0,1)
; --------------------------------------------------
; Window Size
WinSizeStep(XD,YD) {
WinGet,win_id,ID,A
WinGetPos,,,w,h,ahk_id %win_id%
Step := 24
w := w + (XD * Step)
h := h + (YD * Step)
WinMove,ahk_id %win_id%,,,,%w%,%h%
return
}
+#Left::WinSizeStep(-1,0)
+#Right::WinSizeStep(1,0)
+#Up::WinSizeStep(0,-1)
+#Down::WinSizeStep(0,1)
今までHHKBはHHKモード(SW1, SW2 OFF)で使っていて、余った右◇キーを
AutoHotkeyでRWinにして
使っていました。vkFFsc079::RWin
CraftLaunch起動に割り当てているWin+Aとか、標準のWin+EとかWin+Lでは問題ないんですが、
Win+Sでウィンドウシェードするスクリプトを入れたら問題発生。
◇+S -> シェード -> S離す -> ◇離す
でスタートメニューが開くんですね。ほかのキーでも試してみたところ、
どうやらRWinが本物ではないのが悪いようです。
どうしたものかと調べていると、Lite Ext.モードに行き当たりました。
これはFnキー押下時に細々とした違いが出るくらいのものと思っていたのですが、
◇をWinにするという重要な差違が。
これで解決。ついでに、間違ってWinを押してメニューが開くのは気持ち悪いので、
で単独Winを殺します。RWin::return
RWin & RWin::return
今までの妙なWin Lock現象も起きないし、少し快適になりました。
cygwinやMinGWにpdh.hが含まれていないらしいので、Windows SDKを入れてみました。
WinSDK-x86.msi
WinSDKBuild-x86.msi
WinSDKDocWin32-x86.msi
WinSDKWin32Tools-x86.msi
なぜpdh.hが必要かといえば、Windowsタスクマネージャの代わりにコンソールでCPU使用率順のプロセス一覧を表示させたいから。試しにC#で書いてみたら、それ自体が2%もCPUを使うので本末転倒。仕方なくネイティブで書くことになりました。
…と思ったら、なぜかv6.0Aがすでにインストールされていました。(v6.0が今回入れた物)
しかもcygwinでmakeしてもエラーばかりで全然通らない。仕方ないのでVisual C++ 2008 Express Editionに切り替えてみた。普通にWin32コンソールアプリケーションがある…
pdh.hもpdh.libもある…
ということでcygwinを捨ててVC++になりました。前は.NETじゃないとセットアップが面倒だった気がしますが、最近のMSは粋なことをしてくれます。
IDEが重いしviキーバインドにならないのと、非マネージドのインテリセンスがあまり便利じゃないのが玉に瑕ですが。
遊んでいる暇は無いのに…最近コーディングしてないので、雨模様ということもあって土日を使ってしまいそう。
とりあえず400プロセス以内、2コアに対応し、PIDとCPU使用率、イメージ名を1秒おきに書き続けるtopもどきが出来ました。
LLになれるとC++でソートとか面倒すぎる…
一応zip置いておきます。wintop.zip
Cygwin等の\fで画面クリアが効く端末でないと使えません。
TrollTechのGUIツールキット、Qt4.4.0 (GPL版)を試してみました。
最終的には
Eclipse CDT + Cygwin(-mno-cygwin) + OpenGL + ODE + Qt
で開発しようかなと思っていましたが、QtをCygwinで扱うのが面倒そうだったので、とりあえずついでにセットアップされたMinGWを使ってみました。
1. test.cppを作成
#include <QApplication.h>
#include <QFont.h>
#include <QTextCodec.h>
#include <QLabel.h>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
QFont font("Meiryo", 12);
QLabel label("Qtのテスト", NULL);
label.setFont(font);
label.resize(100, 50);
label.show();
return app.exec();
}
2. Makefile作成
qmake -project
qmake test.pro
3. make
make release
4. 実行
release\test.exe
まあ日本語も通ることが分かったので、しばらく色々弄ってみよう。
参考:
http://calmlight.s2.zmx.jp/Qt4Examples/Japanese.html
http://doc.trolltech.com/4.1/designer-using-a-component.html#the-single-inheritance-approach
QWERTYとDvorak間で切り替えを可能にしました。
Ctrl+Alt+A -> Toggle Mode
Ctrl+Alt+D -> Dvorak Mode
Ctrl+Alt+F -> QWERTY Mode
起動時はDvorak Modeです。
ダウンロード: dvorak_20080430.zip (.ahk)
dvorak_exe_20080430.zip (.exe)
詳細: AutoHotKey @ 7bit