Tag Archives

52 Articles

HSPで高DPI対応ソフトを作ろう

こんにちは!

久しぶりにHSPの記事を書きたいと思います!
今回は、HSPの高DPI対応です。


はじめに

高DPIとはなんぞや、という説明をします。

昔のディスプレイは、1ドットのサイズが細かくないため、
1ピクセル=1ドットのような設計がほとんどでした。
そして、1ピクセルの大きさも、どのディスプレイもだいたい同じようなサイズ(96dpi)でした。
従って、ソフトを作る際に、1ドット=1ピクセルの前提で、GUIの設計をしていました。

ただ5、6年前ぐらいから、スマホが高繊細化してきており、
ディスプレイもRatinaディスプレイや4kディスプレイなど、高繊細なものが登場してきました。

例をあげると、次のようなものがあります。
Apple MacBook Pro 解像度 2560×1600、227dpi
Microsoft Surface Pro 解像度 2736×1824 、267 dpi
iiyama 4Kディスプレイ B2875UHSU-B1A 解像度 3840×2160、157 dpi

このように、96dpiより大きなものを高DPI(High DPI)と呼びます。

高DPIに対応していないソフトの問題点

このような高密度なディスプレイで、
これまで作成したソフトを実行するとどうなるでしょう。

高DPIで動作することを考えていないソフトの場合は、
OSがソフトのGUIを自動的にスケーリングして表示することとなります。

しかし、このスケーリングが問題でして、
ASCII.jp Windows 10+高解像度ディスプレイでのアプリのボケはRS2で解消される」のように
文字や画像がボケてしまうのです。

高DPI化に対応する方法と問題点

まず前提として、アプリ自身が「高DPIに対応してますよ!」と宣言しないといけません。
この宣言方法は、2種類あり、マニフェストで宣言するか、
アプリ内で「SetProcessDPIAware」を呼び出す方法があります。
これらの宣言方法については、「SetProcessDPIAware function」に記載されています。

一見、上記の方法だけで対応できるなら簡単じゃないかと思えるかもしれません。
実際の問題点は、高DPI対応宣言後の処理となります。
具体的には、座標、サイズ、位置などについて、
高DPIを考慮した数値で設定する必要が出てきます。

これから作るソフトならまだよいですが、今まで作ったソフトで高DPI化させようとした場合、
ソースコード上の全ての位置やサイズ情報を書き直す必要があるのです。

現実的な解決策

実は、今までのソースコード書き直しせず、簡単に解決することが可能です。
それは、標準関数の置き換えです。

以下に、いくつかの標準関数を置き換えたモジュールをおきます。
高DPI対応用のモジュールを作成する
(このモジュールは、実際に私が作ったツールで利用しています。)

このように、標準関数を置き換えたモジュールを
ソースコードの最初でincludeすることで、
全コードが高DPI対応用の命令で実行させるということです。

おわりに

これで、高DPI対応に関する記事は終わりです。
ありがとうございました。

ちなみに、高DPI対応を試験する際の注意点として
ディスプレイの設定から、「拡大縮小とレイアウト」で「100%」から変更させるという方法があるのですが、
一旦、サインアウト・サインインをやり直す必要があります。

サインアウト・サインインをしないと、
GetDeviceCaps の HIGHDPI_LOGPIXELSX / HIGHDPI_LOGPIXELSY
で取得できる数値が96のままになってしまいます。
気が付かずに、すこしはまってしまいました……。

マップオンデマンドの地図更新でパスワードが入力できない

by なたで 10 Comments

車を購入時にカーナビとしてNSZT-W61Gを付けたのですが、
カーナビ購入してから3年間はマップ情報のバージョンアップが無料ということで、
バージョンアップをしようとしました。

バージョンアップ方法の流れ
1. 購入時についていた「マップオンデマンドセットアップディスク」のDVDでセットアップツールをインストール
2. GAZOO」を無料でユーザー登録(「TOYOTA Web Passport」と同一?)
追記「MOD専用G-BOOK ID」では数値のパスワード作れるらしく、本問題自体を解決できるようです!
下記の話はログイン、出来なかったときに書いたものなので無視していいと思います。
3. マップオンデマンドのサイトからログイン
4. 購入時についていた取説の一番後ろのページにある「G-BOOK用車載端末ID」を入力
5. マップをダウンロード
6. あとは、取説通りに進める

といった感じなのですが、重大な問題が発生します。
それは、「マップオンデマンドセットアップディスク」でインストールした
専用アプリケーションに問題がある点です。(恐らく新しいセットアップディスクだと問題は起きません)

マップオンデマンドのツールでは、
G-BOOKか、GAZOO のどちらかのユーザー情報をGUIで入力する必要があるのですが、
このパスワード入力欄が、数値しか受け付けてくれません。

マップオンデマンド」のサイトにはたしかに、

2013年06月11日
【販売店装着オプションでSDナビをご利用のお客様へ】TOYOTA Web PassportのID、パスワード(PW)をご利用の場合、パスワードを「数字のみ」で設定登録してください。SD地図更新アプリを利用する際、PW入力時にエラーメッセージが表示される場合があります(ログイン画面のPW欄が英数対応していないため)。PWを「英数」で設定登録された方は、「数字のみ」に変更し、SD地図更新アプリをご利用下さい。バージョンアップにて英数対応予定です(別途、ご案内します)。

とあるのですが、解決方法の「パスワードを「数字のみ」で設定登録」に関しては、
そもそもパスワードが数字と英字両方を含めないと登録できない制約があるため、不可能なのです。

password

そのため、バージョンアップを待つ必要があると思いますが、
どこでバージョンアップツールをダウンロードできるのか、ちょっとわかりませんでした。
恐らくG-BOOK様に連絡すれば、ダウンロードできるかもしれません。
トヨタ様のディーラーに聞いても、下記のサポートディスクへ聞かないとわからないとのことでした。

マップオンデマンド・サポートデスク
0561-57-6814. 受付時間 9:00∼18:00

更新期日があと1日しかなく聞く時間がなかったため、
結局聞いてはいないのですが、バージョンアップする方法が分かりました。
このツールのメニューバーのヘルプの「最新バージョンを確認」でバージョンアップしてください。
すると、恐らくツールがバージョンアップできるはずです。
新しいバージョンでは英数字が入力可能のため、バージョンアップ後は問題が発生しなくなります。

ただし、私自身後で気が付いたものなので、上の方法ではうまくいかないかもしれません。
「最新バージョンを確認」自体がログインしないとできないものかもしれないからです。


一応、私がとった方法を紹介します。解決方法の1つとしてみてください。
方法とは、パスワードの入力欄の設定の「数値しか受け付けない」を解除させます。

専門的な話になりますが、Windowsの表示は多くが共通の部品によってできています。
この共通の部品は使い方が公開されており、みんなが知ることができます。
今回は、文字の入力用の共通部品であるエディットボックスを使用しており、
このエディットボックスに「数値しか受け付けない」という設定がされているわけです。
共通部品なので、ほかのソフトからもその設定を設定したり、逆に解除することができます。
今回は、その設定を解除することを行います。

1. パスワード入力欄のウィンドウハンドルを調査
2. GetWindowLong でウィンドウハンドルから、スタイル(GWL_STYLE)を取得
3. ウィンドウ情報から ES_NUMBER (0x2000) をビットマスクで削除
4. SetWindowLong でスタイル(GWL_STYLE)を再設定

これで、パスワード入力欄に英数字を入力できるようになります。
ログインをすると、新しいツールの確認が始まり、
新しいマップオンデマンドツールのバージョンアップされます。

上の手順をHSPというプログラミングツールで実行ファイルにしたものを用意しました。
なたでラボ物置から、「数値入力制限解除ツール」を選び、右側の雲のマークからダウンロードして利用できます。

上記の実行ファイルを実行したときに何か発生しても責任をとることはできません。
自己責任でお願いします。


追記
Yahoo知恵袋 「トヨタ純正カーナビで、データ更新をしようと思い、マップオンデマンドソフトを添…
fukushun1994様による解決方法の回答がありました。
MOD専用G-BOOK ID」というものをとるといいとのことです。


関連するブログ
MapOnDemandでちょいイラ‐(NSZT-W61G編)カッパっぱ、るっパッパ
ナビの地図データ更新&Gracenoteメディアデータベース更新*YOU*のページ

指定した範囲の乱数を作成したい(実戦編)

みなさん、こんにちは。
さて、前回前々回にわたって、
指定した範囲の乱数の作成の仕方を書きました。
改めて奥が深いですね。

読み忘れた方のためにリンクしますっ(^o^)
指定した範囲の乱数を作成したい(前編)
指定した範囲の乱数を作成したい(後編)
指定した範囲の乱数を作成したい(実戦編)←いまここっ!


これまでの話では、
小さなより精度が低い乱数発生器を使用していたため、
実用上は、そんなに関係ないんじゃないかな。
と思う方も多いかもしれません。

今回の話はかなり短いですが、
実際の実用例を考えて、
乱数の落とし穴にはまっていきたいと思います。(^o^)丿

あなたは、ネットワークゲームを設計しています。
モンスターを倒すと、レアアイテムを落とします。
確率は、0.01%刻みで設定できるようにしたいです。
言語は、VC++、またはHSPを使用することとします。

ほら、ちょっと本当にありそうな雰囲気になってきましたよね。
さて、どのように設計するでしょうか、
VC++の乱数発生器だったら、みんな使ってるし大丈夫だろう。
乱数作成の高速化も考えて、 x = rand() % 10000 こんな感じにして、
で、if(x < 1000) で 10% みたいに計算しちゃおう。

では、実際にコードを書いてみましょう。
とりあえず、HSPが好きなので、HSPで書きます。
まず、 HSP には rnd(n) という関数があるのですが、
これはCで rand() % n という文章にすぎません。

#define multiplier 214013
#define addend 2531011

x = 1

goto *start

#defcfunc rnd2 int s
	x = x * multiplier + addend
	return (((x >> 16) & 0x7FFF) \ s)

*start

mes "HSPの rnd(n) は、余りを使用して乱数を切り出している。"
mes "HSPの rnd -> " + rnd(100) + " は"
mes "上記の rnd2 -> " + rnd2(100) + " と同じ。"

上記をふまえて、
乱数の値がどのように分布するか度数分布表を作ってみましょう。

size = 100000
dim bin, 10

repeat size
	x = rnd(10000)
	bin(int(x / 1000))++	
loop

repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

実行すると、

範囲回数
[ 0, 1000)12401
[1000, 2000)12176
[2000, 3000)11618
[3000, 4000)9079
[4000, 5000)9088
[5000, 6000)9107
[6000, 7000)9150
[7000, 8000)9048
[8000, 9000)9316
[9000,10000)9017

つまり、10%を作るつもりが、12%になってしまいます!!
さらに、if(rnd(10000) < 3000) で 30.00% みたいに計算しちゃおうと思うと 36%になります。

棒グラフを作ると分かりやすい。
histogram

そんなこんなで、
今回は、ちょっと身近に感じそうな話を作ってみました。
みなさん、乱数には十分に気を付けましょう!

ちなみに、HSPの機能の中でより正しい乱数を作りたい場合は、
これまでの話をふまえると下のような感じ。(色々と追加しました。)

#module

#defcfunc rnd15 int s
	// 15ビットの実数の乱数を作成
	return int(((double(rnd(0x8000))) / 0x8000) * s)

#defcfunc rnd30 int s
	// 30ビットの実数の乱数を作成
	return int(((32768.0 * rnd(0x8000) + rnd(0x8000)) / 0x40000000) * s)

#defcfunc rnd52 int s, local a
	// 52ビットの実数の乱数を作成
	a = double(rnd(0x8000))
	repeat 3
		a *= 32768
		a += rnd(0x8000)
	loop
	return int(a / 1152921504606846976.0 * s)

#defcfunc rnd4 int s, local a, local b
	// 繰り返して範囲外は捨てる
	repeat
		a = rnd(0x8000)
		b = a \ s
		if(a - b + s <= 0x8000) {
			break
		}
	loop
	return b

#global

font "MS ゴシック", 12
pos 5, 5

// 作成する乱数の範囲によって、
// 乱数のビットを大きいものを選ぶか、
// 上記の rnd4() を利用するといいです。

randomize
size = 1000000

mes "通常版"
dim bin, 10
repeat size
	x = rnd(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

mes "工夫したもの"
dim bin, 10
repeat size
	x = rnd15(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

mes "誤差が少ない"
dim bin, 10
repeat size
	x = rnd30(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

pos 200, 5

mes "最も誤差が少ない"
dim bin, 10
repeat size
	x = rnd52(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

mes "正しい乱数"
dim bin, 10
repeat size
	x = rnd4(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

上記の「正しい乱数」のアルゴリズムについては、下で紹介していますが、前編から読むとより勉強になるかもしれません。
指定した範囲の乱数を作成したい(後編)

指定した範囲の乱数を作成したい(後編)

みなさん、こんにちは。
今日は、昨日の続き、「指定した範囲の乱数を作りたい」を続きに話していきたいと思います。
というか、解答編みたいな感じです。

指定した範囲の乱数を作成したい(前編)
指定した範囲の乱数を作成したい(後編)←いまここっ!
指定した範囲の乱数を作成したい(実戦編)

それでは、ちょっとおさらいをしながら進めていきます。
今回は、3ビット(0, 1, 2, 3, 4, 5, 6, 7 の計8種類の数値)の
一様分布の値を出力する乱数生成器random8()を利用して、
0~2の3種類の数字を出力してみましょう。

この3ビットの乱数生成器random8()の性能を表にまとめます。

乱数値01234567
確率1/81/81/81/81/81/81/81/8

それでは、前回と同じように3で割った余りを使用します。
y = random8() mod 3

乱数に対して、3で割った余りを求めて

乱数値01234567
y (3で割った余り)01201201

確率をまとめると、

y012
確率3/83/82/8

やはり、前回の2ビット乱数発生器と同様にだめでした。
まあ、ただ2ビットの乱数発生器に比べて誤差は減っています。
乱数発生器のビット数が大きいほど誤差は少なくなるようですね。

さて、では3ビットの乱数生成器では、
当確率で、0から2の数字を出力することができないのでしょうか。
いいえ、出来ます。

気付いている人も多いかと思いますが、
毎回、問題になっているのは「はみ出している」部分なのです。
「はみ出している」というのは、下記の黄色の箇所です。

乱数値01234567
y (3で割った余り)01201201

つまり、3ビットの乱数生成器で0~7の値が出力されるが、
そのうち6か、7が出た場合は、
もう1度、3ビットの乱数生成器で出力するのです。

もちろん、計算コストが上がります。
例えば、3ビットの乱数生成器では

乱数0~56~7
確率6/82/8
利用するしない

それぞれの数字を出力する確率は上記のようになり、
6~7という数字が、2/8の確率で出力され、
もう1度計算する必要が出てくるのです。
しかも、次の乱数も、6~7なら、また乱数作成の計算が必要です((+_+))

さて、次はアルゴリズムの話に移ります。
出力された値が、6~7かどうかはどのように判断したらいいでしょうか。
上記のように、「0~7の8種出力する乱数から、0~2までの3種類の乱数が欲しい!」
と決め打ちされているならば簡単ですが、
乱数のビット数、いくつ未満の数字が欲しいか、いくつもの場合が考えられます。
でも、色々な場合とか考えると、大変なので、やっぱり簡単な乱数で勉強しましょう。

とりあえず、次の表を見てください。

乱数値 a01234567
b=a mod301201201
c = a – b00033366
d = c + 333366699
e = d > 8falsefalsefalsefalsefalsefalsetruetrue

ピーン(^o^)っときませんか?
上の表に出てきている
3は、必要な乱数の種類、0~2までの3種類の3です。
8は、3ビットの乱数発生器の性能、2の3乗の8です。
もうお分かり頂けたと思います。

ちょっと、C言語で上記の考え方で、乱数を作ってみましょう。
C言語では、乱数をrand()で出力するのですが、
この出力する範囲は「0~RAND_MAX」となり、「RAND_MAX + 1」種類の数字を出力します。

int TRUE = 1;
unsigned long n = 3; // 欲しい乱数の種類
unsigned long y = 0; // 出力値
unsigned long random = 0;
while(TRUE) {
   random = rand();
   y = random % n;
   if( ( random - y + n ) > (RAND_MAX + 1) ) {
	  continue;
   }
   break;
}

まあ、とりあえず説明にあったような流れで、無理やり書いて、
上記のようなコードになりました。
do文を利用するともっとすっきり書けます。

実は1つだけ落とし穴があります。
それは、RAND_MAXの値が非常に大きい場合や、
( random – y + n )の値が実は巨大な数字の場合です。
signed long などで計算している場合は、負の値になってしまいますし
unsigned longを使用していたとしても、オーバーフローして、小さな値になってしまう可能性があります。

例として、「signed short」16ビットの正負の範囲を表す整数値を使用しており
さらに、RAND_MAX は 0x7fff の場合、RAND_MAX + 1 は -32768 となり負になってしまいます。
そのため、IF文の結果が思った結果と異なる場合があります。
アルゴリズム自体は分かったと思いますので、
上記の点だけ注意して、値がおかしくならないように設計して下さい。

さて、最後なのですが、
JavaScriptのように、0以上、1未満の数字を出力する
Math.random()しかない場合は、どうすればいいでしょうか。
この時も、前編で話したように同様の問題を抱えています。

この場合も、一度整数に戻すしかありません。
疑似乱数は、通常ビット単位で作成されます。
(これまでで話したように、任意の数までの乱数を作るのは面倒だからです。)
そのため、Math.random() を作成している元となる乱数も
元々は、あるビット長の整数を元にしていると思われます。
この場合は、Math.floor(Math.random() * 0x8000) とすれば、
0から32768未満の整数値を、均等な確率で出力します。
あとは、この整数値を利用して、上のC言語で説明したように作成すれば
しっかりとした乱数が作成できます。

ちなみに、Javaの場合は、RandomクラスnextInt(n)を使用すれば、問題ありません。
さすがJavaです。速度も速いですし。
とりあえず、「指定した範囲の乱数を作成したい」はここで終わりです。

話は、少し変わりますが乱数は作成する方法がたくさん考案されてます。
疑似乱数の質とか考えると、よく利用される線形合同法はマズいです。
利用する目的にもよると思いますが、
乱数の種類などを勉強しておくといいかもしれません。
メルセンヌ・ツイスタは、日本人が開発したもので、乱数の質が高いです。
(ただ、MTは、簡単に自分の手で実装できないのがデメリットがあります><)

続きは、HSPを使ったより実践的な話です。
指定した範囲の乱数を作成したい(実戦編)

指定した範囲の乱数を作成したい(前編)

みなさん。
指定した範囲の乱数を作りたい場合は、どうしているでしょうか。
今日は、指定した範囲の乱数生成の小ネタを紹介します。

指定した範囲の乱数を作成したい(前編)←いまここっ!
指定した範囲の乱数を作成したい(後編)
指定した範囲の乱数を作成したい(実戦編)

さて、例えばサイコロを作りたいと思います。
サイコロの目は1~6ですね。

そして乱数生成器が用意されていると思います。
この乱数生成器は、4バイト=32ビットの一様分布する整数値が出力されるもので、
random32()と関数を呼び出せば、出力されるものとします。
( random32() は、C言語の乱数 rand()と似たような関数だと思ってください。 )

どのような、コードを思い浮かべるでしょうか。
恐らく最も簡単なコードは、次のようなコードです。

サイコロの目 = random32() mod 6 + 1
(mod 6 は、6で割った余りの数とします)

はい。
たしかに、「random32() mod 6」で0~5の範囲の値を作成して、
1を足して、1~6の乱数が出ると思います。

今度は、実数の乱数生成器で、サイコロを作る話です。
この実数の乱数生成器は、64ビットのdouble型の実数を作成するもので
[0, 1) = {x | 0 ≤ x < 1} の乱数、つまり0以上、1未満の乱数を得ることができます。
randomf()と関数を呼び出せば、出力されるものとします。
( randomf() は、JavaScript の Math.random() と似たような関数だと思ってください。)

この場合も、どのような、コードを思い浮かべるでしょうか。
最も簡単なコードは、次のようなコードです。

サイコロの目 = floor(6 * randomf()) + 1
(floor() は、床関数です)

はい。
これも、たしかに、「6 * randomf()」で[0, 6)の乱数を作成して、
床関数で0~5の整数値にして、1を足すことで、1~6の乱数が出力されます。

簡単で、すぐに乱数が欲しいという目的であれば、これまでの話は正しいと思います。

ただし、知っている方も多いかと思いますが、
本当に本当に正確な乱数を欲しい場合は、問題があるコードなのです。
何が問題なのでしょうか。

まずは、整数の乱数を使用した場合です。

問題を分かりやすく考えるため誤差を大きくしましょう。
2ビットの乱数生成器(0~3までの4パターンの数字を出力する)random4()が用意されており、
ここから0~2まで、3パターンの数字を出力する乱数を作り出したいと思います。

この2ビットの乱数生成器の性能を表にまとめます。

乱数値0123
確率1/41/41/41/4

それでは、先ほど同じような考え方で
y = random4() mod 3
を計算してみるとどうでしょうか

乱数値0123
y (3で割った余り)0120

つまり、出力される0~2の値は、
「random4() mod 3」の計算で作成すると、
下記のように0が出る確率が高くなってしまうのです。

残念な乱数出力結果1

y012
確率2/41/41/4

今度は、実数の乱数生成器の話です。
「 floor(6 * randomf()) + 1」とサイコロを作成した場合、同じように偏りが生まれる問題があるのです。
浮動小数点型に限界があるのです。
一様乱数な0から1未満の浮動小数点を作成したとしても、仮数部のビット数分の種類しかないのです。
(ただ、出力値がdouble型ならば、
先ほどの、randomf()は、約2^52ビット=9007兆の数字のパターンを持っています。
これだけ大きければ実際は、問題はないと思います。)

分かりやすく考えるため、しょぼい乱数生成器を使用します。
2ビット整数の乱数生成器の話と同じように仮数部が、
2ビットの実数の乱数生成器randomf4()を使用して、0~3の整数を作ってみましょう。
(randomf4()は、0から1未満を生成するが、0.00 0.25 0.50 0.75の4パターンしか出力されない)

この仮数部2ビットの実数乱数生成器の性能を表にまとめます。

乱数値0/41/42/43/4
確率1/41/41/41/4

それでは、先ほど同じように
y = randomf4() * 3
z = floor(y)
を計算してみるとどうでしょうか

乱数値0/41/42/43/4
y (3倍した)0/43/46/49/4
z (床関数)0012

つまり、出力される0~2の値は、
「random4() % 3」の計算で作成すると、
0が出る確率が高くなってしまうのです。

残念な乱数出力結果2

y012
確率2/41/41/4

では、どうすればいいんじゃあって思うかもしれません。
まあ、普通に使用する分は、最初の話があったような方法で十分です。
ただし、使う人を選ばない乱数生成器を作成したいとか、
どのような乱数生成器の性能でも、しっかりした乱数を作りたいといった場合は、
工夫して上記の問題を解決する必要があります。

指定した範囲の乱数を作成したい(後編)へ続くッ!!!

%d人のブロガーが「いいね」をつけました。