.log

miscellaneous memorandum

壁紙チェンジャー

May 18, 2012

最近ぼちぼちC#でプログラミングをしてまして、今は壁紙を変更するツールを作ってます。

そういうツールは山ほどあるし、実際John’s Background Switcherというのを使ってるんですが、Rainmeterとかでデスクトップになにか情報を表示していると、特定の範囲に壁紙を表示させたくないんですよね。かと言って壁紙が途切れたり、一部領域が隠れてしまったりするのは気持ち悪いし、壁紙の色合いが変わって、Rainmeterの表示が見えにくくなるのも不細工。なので、今使っているツールで満足している機能+壁紙をデスクトップの特定の範囲内にリサイズして表示させる機能を持つツールを自作しているわけです。

1.壁紙を表示/変更する

単に壁紙を変更するだけなら、SystemParametersInfoというAPIを呼び出すだけでOKなのですが……

[csharp] const int SPI_SETDESKWALLPAPER = 20; const int SPIF_UPDATEINIFILE = 0x01;

const int SPIF_SENDWININICHANGE = 0x02;

[DllImport(“user32.dll”, CharSet = CharSet.Auto)] static extern int SystemParametersInfo(

int uAction, int uParam, string lpvParam, int fuWinIni);

SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, path, SPIF_UPDATEINIFILE ‘ SPIF_SENDWININICHANGE); [/csharp]

要素としてはこんな感じです。非常に簡単なのですが、コードが実行されるとぱちっと壁紙が変わってしまうので、ちょっと趣がないのですね。Windows 7標準の壁紙切替も、 John’s Background Switcherもフェード効果で切り替わってくれていたので同じようにしたいものです。

調べてみたら、フェードで切り替わるのはActiveDesktopの機能らしいのですね。SetWallpaperという関数です。 これを使うためには、COM呼び出しの手続きを踏んでIActiveDesktopインターフェース介することになります。 自分で一から書くのは大変なので、http://www.koders.com/csharp/fidE41C4D1C88A4F95306F63E23DFE2109B692BC6DC.aspxこういう所からコードを借りてきます。 そうすると、こういう感じになります。

[csharp] IActiveDesktop desktop = (IActiveDesktop)new ActiveDesktop();

desktop.SetWallpaper(path, 0); desktop.ApplyChanges(AD_APPLY.ALL ‘ AD_APPLY.FORCE ‘ AD_APPLY.BUFFERED_REFRESH); [/csharp]

これだけでOKです。そうすると、フェード効果付きで壁紙が変わります。 あと、ActiveDesktopの方でやると、JPEGファイルも指定できるようになります。 (SystemParametersInfoはBMPだけ)

補足事項

http://forums.whirlpool.net.au/archive/1802418 このページを見ると、同じことを求めている人がいて、結局ActiveDesktopでできるようになったとコード付きで書いてくれているのですが、ここには「SendMessageAPIでProgman(プログラムマネージャ)を終了させる(そして勝手に再起動される)」というようなコードも書いてあります。これでアニメーションが機能してフェード効果が付与されるようなのです (ちなみに、John’s Background Switcherを逆コンパイルしてコードを追ってみたら、同じようにプログラムマネージャにメッセージを送っているらしき部分がありました)

しかしこのSendMessageの部分、なくても動くんですよね。 環境の違いが何かあるのか、動かすときの状況により何か変わるのか、ちょっとわかりません。 そもそも、Progmanを再起動させると何でフェード効果が有効になるか、と言った部分がいまいち良く分かってないので……

追記2(2012.5.20)

どうやら、このSendMessageの部分、一回走ったら再起動されるまでは不要っぽいです。 再起動後試してみたら、ここがないとフェード効果が有効になりませんでした。

2.壁紙を所定の位置/範囲に表示する

この部分は、非常に単純なやり方にしています。

「デスクトップのサイズと同じ大きさのBMPファイルを作って、指定した位置に壁紙用に取ってきた画像を描画、そのBMPファイルを壁紙に指定する」

他に方法があるのかどうか調べることすらしてませんが、こうすることで画像に枠をつけたりとか、画像の情報を描画したりとか、複数の画像をレイアウトして表示したりとか、色々複雑な表示ができるようになりますね。

[csharp] Bitmap source = new Bitmap(path);

Bitmap bitmap = new Bitmap( Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format24bppRgb);

Graphics g = Graphics.FromImage(bitmap); g.Clear(Color.Black); g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.DrawImage(source, x, y, w, h); g.Dispose();

bitmap.Save(“wallpaper.bmp”, ImageFormat.Bmp); [/csharp]

要点だけ抜き出すとこんな感じです。 x,y,w,hには、事前にどの位置と範囲に表示するかを計算した結果が入っていますよ。 表示範囲と壁紙用の画像のサイズを比較して、「縦横比を変えない、拡大表示しない」という条件の下で適切なサイズを計算しています。 実際は、他にも写真のタイトルや作者、オリジナル画像のサイズなどの情報も表示させていますが、その辺はGraphicsのDrawStringなんかで普通に描画しています。

3.flickrから写真を取ってくる

壁紙チェンジャーなのですし、いろんな画像、特に写真を表示したい。しかもわざわざネットをさまよって気に入った写真を集めるというのも手間なので、自動的に取ってきてくれるといいですね。 John’s Background Switcherを使っていたのはまさにこの機能が充実していたからです。 最近は、flickrのInterestingnessで表示される写真を見るのが楽しいので、自作ツールはここから取ってこれるようにしています。

この部分も、APIが用意されているので非常に簡単な上、flickr.netという、flickrのAPIをラッピングした.net用のライブラリがありますのでさらに簡単です。とりあえず、http://flickrnet.codeplex.com/releases/view/84287あたりからライブラリをダウンロードして、FlickrNet.dllをプロジェクトから参照するようにします。

[csharp] Flickr flickr = new Flickr(apikey); PhotoCollection collection = flickr.InterestingnessGetList();

HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest .Create(collection[index].LargeUrl); HttpWebResponse httpWebReponse = (HttpWebResponse)httpWebRequest.GetResponse(); Stream stream = httpWebReponse.GetResponseStream();

Image image = Image.FromStream(stream); [/csharp]

要点はこれだけ。 実際は、取得したCollectionからランダムに写真を取ってくるとか、タイトルなどの情報を取ってきてインデックス情報を作ったりとか、ファイルのダウンロードは別スレッドにしたりとかしてます。

PhotoCollectionからPhotoオブジェクトを取り出して、プロパティを参照するとタイトルや写真をアップしたユーザーID等々写真に関する情報がとれます。上の例では、Largeサイズの写真を取ってきていますが、小さいのから、flickrっぽいスクエアのサムネイルのURLなどもありますよ。

4.以上

あとは、タイマーでぐるぐる壁紙を取ってきては書き換える、タスクトレイに居座る、取得した写真とインデックスを保存して、過去分も見られるようにする……など、必要最低限の機能をつくって完成です。 別に人に見せるわけじゃないので、例外処理をはしょっていたり、UIがやっつけだったりしますが、壁紙がきれいに変わればそれでいいんです、おかしければ直せばいいし。

で、表示されてる様子。

写真には白い枠をつけて、下には、タイトル、ユーザー名、画像のサイズを表示しています。 別に写真を見るならWebなりで見れば良いんですが、なんかぼーっと壁紙が変わるのを待っていたりします。

今は、iPadのFlipboardでflickrを見たときのように、複数の写真をレイアウトして表示させるようにしようと頑張っていますが、なかなかいい感じになりません。あまり並べすぎるとごちゃごちゃして汚いし、写真にも縦横があればサイズもそれぞれだし、その辺をうまく考慮して写真を組み合わせるというあたりでなんかめんどくさくなってます。