みんなの「教えて(疑問・質問)」にみんなで「答える」Q&Aコミュニティ

こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

ビットマップを直接編集して背景画像を波打たせる

質問(1)
pixelsは一次元配列で1ラインのx方向の要素を左方向や右方向にシフト量sだけ離れた所へコピーすることにより1ラインの画像のピクセルデータをシフト量sだけずらして波打たせる効果を持たせていることは想像つきますが、これではy方向を考慮した全てのラインをずらすことにはなっていない様な感じがします。y方向はどうなっているのですか?
質問(2)
int line = y*bmpdata.Stride/4;で4で割っているのは何故ですか?
質問(3)
if(s<0){
//左へコピー
for(int x=-s; x<BITMAP_W; x++){
if(x+s>=0){
pixels[line+x+s] = pixels[line+x];
}
}
}
でこのif文の中の条件式x+s>=0はint x=-s;→int x+s=0;…;x++により明らかに常に真ではないですか?もし、そうだとしたらこのif文は必要ないのではないですか?
質問(4)
} else if(s>0){
//右へコピー
for(int x=BITMAP_W; x>=s; x--){
if(x+s<BITMAP_W){
pixels[line+x] = pixels[line+x-s];
}
}
}
}
でこのif文の中の条件式x+s<BITMAP_Wの意味が良く分かりません。
質問(5)(6)(7)
for(int y=0; y<BITMAP_H; y++){

int line = y*bmpdata.Stride/4;の意味が良く分かりません。質問(5)

pixels[line+x+s] = pixels[line+x];でlineを加えているのは何故ですか?質問(6)

pixels[line+x] = pixels[line+x-s];でlineを加えているのは何故ですか?質問(7)


C言語、C++言語初心者なので分かりやすく教えて下さい。
宜しくお願いします。


プログラムコード(animation.cpp)
//ウェーブエフェクト
UINT g_wavecount;
const int BITMAP_H = 480;
const int BITMAP_W = 320;
float g_wavelines[BITMAP_H];
//ウェーブエフェクト初期化
void ResetWave(){
g_wavecount = 0;
float r=0, rdelta=3.14f/12;//波の数
float waveh = 3;//波の高さ
for(int i=0; i<BITMAP_H; i++){
g_wavelines[i] = sinf(r) * waveh;
r += rdelta;
}
}
//ウェーブエフェクト描画
void RenderWave(Gdiplus::Bitmap *bmp){
g_wavecount++;
int topline = g_wavecount % BITMAP_H;
//ビットマップのロック
Gdiplus::BitmapData bmpdata;
Gdiplus::Status status = bmp->LockBits(
&Gdiplus::Rect(0,0,320,480),
Gdiplus::ImageLockModeWrite,
bmp->GetPixelFormat(),
&bmpdata);
if(status != Gdiplus::Ok) return;
UINT* pixels = (UINT*)bmpdata.Scan0; //←この行から下のコードが全部分からない。
//ビットマップを加工する
for(int y=0; y<BITMAP_H; y++){
//シフト量を取得
int s = (int)g_wavelines[(topline+y)%BITMAP_H];
int line = y*bmpdata.Stride/4;
//ピクセルコピー
if(s<0){
//左へコピー
for(int x=-s; x<BITMAP_W; x++){
if(x+s>=0){
pixels[line+x+s] = pixels[line+x];
}
}
} else if(s>0){
//右へコピー
for(int x=BITMAP_W; x>=s; x--){
if(x+s<BITMAP_W){
pixels[line+x] = pixels[line+x-s];
}
}
}
}
bmp->UnlockBits(&bmpdata);
}

投稿日時 - 2011-08-18 03:07:37

QNo.6949976

困ってます

質問者が選んだベストアンサー

プログラムを読むときは、一気に読もうとしないで、各部分の機能に分けて少しずつ読むといいですよ。

「//左へコピー」「//右へコピー」の部分が理解できたら、

if(s<0){
 //左へコピー
} else {
 //右へコピー
}

と考えれば、このifの意味もわかるし、このifが「//ピクセルコピー」と理解できれば「//ビットマップを加工する」という部分は
for(int y=0; y<BITMAP_H; y++){
 // シフト量を取得
 // ピクセルコピー
}
という構造になっていることが理解できるのではないでしょうか。

> これではy方向を考慮した全てのラインをずらすことにはなっていない様な感じがします。y方向はどうなっているのですか?

これはどちらの意味で使ってますか?
・全てのyについて、シフトさせる処理をしているのか?
・y方向にはシフトさせないのか?

前者なら、プログラムの構造をよく確認してください。特にforループがどうなっているか。
後者なら、このプログラムでは「シフトさせない」です。

> 質問(2)
> int line = y*bmpdata.Stride/4;で4で割っているのは何故ですか?

まず、二次元の画像などを一次元配列で表現する方法は理解できていますか?
よくあるのは,座標(x,y)に対応するデータを[ x + y * 次のラインへのずれ]にすることです。
lineは「y * 次のラインへのずれ」を先に計算しておくものです。(質問(6)(7))

では、bmpdata.Strideがどんな意味を持った値なのかを調べましょう。
bmpdataはGdiplus::BitmapData型で、Gdiplus::Bitmap型のポインタbmpからbmp->LockBitsを使って設定しているようです。であれば、Gdiplus::BitmapDataやGdiplus::Bitmapのマニュアルを読んでみます。
Gdiplus::BitmapDataのStrideは「次のラインへのずれ、単位はバイト」だとわかります。

ここで、実際に使いたいのはUINT*のpixelsです。pixels[x+y*bmpdata.Stride]としてしまうと、bmpdata.Strideバイト数ずれるのではなく、UINTの幅 * bmpdata.Strideバイトだけずれてしまいます。(配列とポインタは理解できてますか?)
そこで、UINTの幅である4で割って、ずれ幅をbmpdata.Strideになるようにしています。

※ UINTの幅を4と決めるのは、本来はよくありません。WindowsのVC++に限定すれば正しいかもしれませんが、他の環境でどうなっているかは、その環境次第です。今回はGdiPlus,UINTを使うなどVC++限定と考えてよいですが。
sizeof(UINT)とかsizeof(pixels[0])とかにするのが環境に依存せず、また値の意味もわかりやすいと思います。


私なら、Strideを割ったりとかがややこしいので、次のようにします。
> UINT* pixels = (UINT*)bmpdata.Scan0;
> //ビットマップを加工する
> for(int y=0; y<BITMAP_H; y++){

> //ビットマップを加工する
> for(int y=0; y<BITMAP_H; y++){
> // yに対応するアドレスも求める
> UINT* pixels_y = (UINT*)(bmpdata.Scan0 + y * bmpdata.Stride);
....
> pixels_y[x+s] = pixels_y[x] // yに対応したアドレスになっているので、x方向だけ指定すればよい

> 質問(3)
たしかにいらないですね。
予想なんですが、最初はsをfloatのままにしてあって、intのxとの間で誤差が出て、実際にx+sが<0になったのではないでしょうか。その後、sもintにして、今では必要無くなったけどコードはそのまま、という状態では。
または、もとはfor(x=0;... となっていたか。

> 質問(4)
条件が間違ってますね。これでは、右のsピクセルが変更されないままです。
条件に意味を持たせるのなら
for(int x=BITMAP_W; x>=0; x--){ //終了はx=0
 if(x+s<BITMAP_W){
  pixels[line+x+s] = pixels[line+x];
 }
}

条件式を正すなら
for(int x=BITMAP_W; x>=s; x--){
 if(x<BITMAP_W){
  pixels[line+x] = pixels[line+x-s];
 }
}

ですね。xの範囲を調整すればifがいらなくなるのも同様です

> 質問(5)
質問(2)とダブってます

> 質問(6)
> 質問(7)
質問(2)についてのところで説明したように、yに対応する位置へずらすためのものです。

投稿日時 - 2011-08-18 08:53:33

お礼

>これはどちらの意味で使ってますか?
>・全てのyについて、シフトさせる処理をしているのか?
>・y方向にはシフトさせないのか?
勿論全てのyについて、シフトさせる処理をしています。

>まず、二次元の画像などを一次元配列で表現する方法は理解できていますか?
そうなっていることに今まで気がつきませんでした。ご指摘どうもありがとうございます。

>Gdiplus::BitmapDataのStrideは「次のラインへのずれ、単位はバイト」だとわかります。
>ここで、実際に使いたいのはUINT*のpixelsです。pixels[x+y*bmpdata.Stride]としてしまうと、>bmpdata.Strideバイト数ずれるのではなく、UINTの幅 * bmpdata.Strideバイトだけずれてしまいま
>す。(配列とポインタは理解できてますか?)
>そこで、UINTの幅である4で割って、ずれ幅をbmpdata.Strideになるようにしています。
配列とポインタは理解しています。
UINT型の配列pixelsの要素は4バイトずつ離れているので、1ラインあたりのずれはbmpdata.Strideバイトであり、1ラインあたりの要素数(配列の添え字)のずれはbmpdata.Stride/4ですね。yラインの要素数(配列の添え字)のずれは、
for(int y=0; y<BITMAP_H; y++){
int line = y*bmpdata.Stride/4;

pixels[line+x+s] = pixels[line+x];

pixels[line+x] = pixels[line+x-s];
で表せますね。

>私なら、Strideを割ったりとかがややこしいので、次のようにします。
> //ビットマップを加工する
> for(int y=0; y<BITMAP_H; y++){
> // yに対応するアドレスも求める
> UINT* pixels_y = (UINT*)(bmpdata.Scan0 + y * bmpdata.Stride);
....
> pixels_y[x+s] = pixels_y[x] // yに対応したアドレスになっているので、x方向だけ指定すればよい
そう言われてみるとこれも正しいですね。どうもありがとうございます。

どうもありがとうございました。

投稿日時 - 2011-08-18 17:26:02

ANo.1

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(1)

あなたにオススメの質問