トップ回答者
StretchDIBits の部分コピーに関して

質問
-
初めての投稿です。よろしくお願いします。
画像を表示するためにDIBのStretchDIBitsを使い、
下記のようにプログラムを組み、全体のコピーでは上手く行っています。
さらに、画像にグラフックをオーバーレイするためにために
DDBのデバイスコンテキストも確保し、TransparentBltを使い
画像と合成しています。
そこで、マウスをドラッグしながら、矩形のグラフック枠を
連続的に描くようなプログラムを作るために、DIBの一部を
コピーしグラフィックを消そうとしています。
倍率が1倍以上の時は上手く行っているのですが、
1倍以下の時、画像が微妙にずれてコピーされてしまいます。
ずれが無いようにするにはどうしたら良いでしょうか?
また、矩形内の画像も僅かに違うようです。
補間の関係なのでしょうか?
// Sx, Sy は矩形のスタートX,Y座標
// Ex, Ey は矩形のエンドX,Y座標
#define LC_MARUME 0.49999 // または 0
void DIB_DispRect( int Sx, int Sy, int Ex, int Ey )
{
double Magnification = 1 / 3; // 拡大率
int Ysize = 480; // 全体の垂直方向の長さ// inは DIB のピクセルビット、 outは デバイスコンテキスト
int inSx, inSy, outSx, outSy;
int inSizeX, inSizeY, outSizeX, outSizeY;
inSx = Sx;
inSy = Ysize - Ey - 1;
inSizeX = Ex - Sx + 1;
inSizeY = Ey - Sy + 1;
outSx = (int)( (double)Sx * Magnification + LC_MARUME );
outSy = (int)( (double)Sy * Magnification + LC_MARUME );
outSizeX = (int)( (double)inSizeX * Magnification + LC_MARUME );
outSizeY = (int)( (double)inSizeY * Magnification + LC_MARUME );
SetStretchBltMode( ag_hDC, COLORONCOLOR );
StretchDIBits( ag_hDC, outSx, outSy, outSizeX, outSizeY,
inSx, inSy, inSizeX, inSizeY,
lg_pPixcelM, // DIB の色データが入ったバイト配列へのポインタ
lg_pBitmapInfoM,
DIB_RGB_COLORS,
SRCCOPY); // ラスタオペレーションコード
}
回答
-
縮小された画像のRECTを求めてから、RECTの幅と高さを outSizeX, outSizeYに代入してはどうでしょう?
このソースだと次のような条件の画像は右下の位置が合わなくなってくると思います。
Sx, Sy, Ex, Ey
画像1 0, 0, 100, 100
画像2 50, 50, 50, 50
画像1の右端の位置: (int)((double) 0 / 3 + 0.5) + (int)((double) 100 / 3 + 0.5) = (int)0 + (int)33.33… = 33
画像2の右端の位置: (int)((double) 50 / 3 + 0.5) + (int)((double) 50 / 3 + 0.5) = (int)16.66… + (int)16.66… = 34
すべての返信
-
中澤さんありがとうございます。
#define LC_MARUME 0.49999
をプラスしていますので、四捨五入になっていると思いますが?四捨五入したとしても、
たとえば、以下 Sxが5,6,7 の場合ですが、
outSx = (int)( (double)Sx * Magnification + LC_MARUME );
5 * 1 / 3 + 0.49999 = 2.16666... => 2
6 * 1 / 3 + 0.49999 = 2.49999... => 2
7 * 1 / 3 + 0.49999 = 2.83333... => 2
とoutSxが同じ値になり、上手く行かないよいな気がしますが、如何ですか?その他に、倍率によりSx等の値を変えやりました。
例えば、1/3の倍率では
Sxが 7の時は3の倍数で 7より小さい 6にし
Exが13の時は3の倍数で13より大きい15にして
上記の計算をしましたが、1/2の倍率では上手く行きましたが、
1/3ではダメでした。
とりあえず分母が整数で1/16まで出来れば良いのですが。
もちろん一般的にできた方が良いです。よろしくお願いします。
-
自分の拡大縮小のルールとStretch...()のルールとを
ある程度同じもの(少なくとも0の近傍で同値となる)しないとうまくいかないと思います。
Stretch...()が0.49999を使用していると確信できるのなら別ですが、
自分なら、まず、+-0.5(四捨五入)を試してみると思います。
丸めで重要なのは、正側の丸めと負側の丸めは0に対して対象であるべきであろうということです。つまり
int Zoom( double zoom, int targetValue)
{
if( targetValue < 0){
// 負の縮小と丸め
}
else{
// 正の拡大縮小と丸め
}
}
てな感じの関数にしてしまいましょう。
つぎに、これでうまくいかない場合は、Stretch...()は特殊な丸めを使用しているものとあきらめて、
実寸の裏(メモリ)ビットマップ上でオペレーションし、その結果を画面上にストレッチする方法に
変更する必要があるかもしれません。この場合は誤差の発生頻度は低下することが期待できます。
これでも誤差が皆無であるとは断言できないのでもし、だめな倍率値が見つかったら、
その倍率値にならないように回避するしかありません。
まぁ、ありがちなことです。
-
縮小された画像のRECTを求めてから、RECTの幅と高さを outSizeX, outSizeYに代入してはどうでしょう?
このソースだと次のような条件の画像は右下の位置が合わなくなってくると思います。
Sx, Sy, Ex, Ey
画像1 0, 0, 100, 100
画像2 50, 50, 50, 50
画像1の右端の位置: (int)((double) 0 / 3 + 0.5) + (int)((double) 100 / 3 + 0.5) = (int)0 + (int)33.33… = 33
画像2の右端の位置: (int)((double) 50 / 3 + 0.5) + (int)((double) 50 / 3 + 0.5) = (int)16.66… + (int)16.66… = 34
-
ありがとうございました。下記プログラムのようにし基本的に解決しました。
> 縮小された画像のRECTを求めてから、RECTの幅と高さを outSizeX, outSizeYに代入してはどうでしょう?
おっしゃるとおりやった後、outからinの方を再計算することで上手く行くようになりました。
また、私の場合、Sy, Ey を求めSizeYを求めていましたが、直接out側からSizeYを求め上手く行きました。
今一つ質問ですが、矩形の高さ(サイズ)に関してですが、今まで、
SizeY = Ey - Sy + 1 のようにドットの数だと思っていました。
そこで、Sy = 全体のサイズ - Ey - 1 のようにしていました。
しかし、下記のプログラムの★のとこのように、上手く行きません。
SizeY = Ey - Sy のように、距離で考えた方が上手く行っています。
定義としてはどちらが正しいのでしょうか?
void DIB_DispRec( int Sx, int Sy, int Ex, int Ey )
{
double Magnification = 1.0 / 3.0; // 拡大率
int Ysize = 480; // 全体の垂直方向の長さ// inは DIB のピクセルビット、 outは デバイスコンテキスト
int inSx, inSy, outSx, outSy;
int outEx, outEy;
int inSizeX, inSizeY, outSizeX, outSizeY;
int rSx = (int)( (double)Sx * Magnification + 0.5 );
int rSy = (int)( (double)Sy * Magnification + 0.5 );
int rEx = (int)( (double)Ex * Magnification + 0.5 );
int rEy = (int)( (double)Ey * Magnification + 0.5 );outSx = rSx;
outSy = rSy;
outEx = rEx;
outEy = rEy;
outSizeX = outEx - outSx;
outSizeY = outEy - outSy + 0; // ★+1にすると、全体画像も1つ下にずれる。inSx = (int)( (double)outSx / Magnification + 0.5 );
inSy = (int)( Ysize - (double)outEy / Magnification + 0.5 - 0.0 ); // ★-1とすると、
// ★全体の画像と比較し部分コピーが1ライン分ずれる。(全体と部分のずれは無い)
inSizeX = (int)( (double)outSizeX / Magnification + 0.5 );
inSizeY = (int)( (double)outSizeY / Magnification + 0.5 );
SetStretchBltMode( ag_hDC, COLORONCOLOR );
StretchDIBits( ag_hDC, outSx, outSy, outSizeX, outSizeY,
inSx, inSy, inSizeX, inSizeY,
lg_pPixcelM, // DIB の色データが入ったバイト配列へのポインタ
lg_pBitmapInfoM,
DIB_RGB_COLORS,
SRCCOPY); // ラスタオペレーションコード
} -
RECT系の描画は基本的に幅は right - left 高さは bottom - top がサイズになります。
下の二つを比べると右下の位置が1pixelずれるのがわかると思います。
RECT rect = { 0, 0, 100, 100 };
// 線分で四角を描く
::MoveToEx( hDC, rect.left, rect.top, NULL );
::LineTo( hDC, rect.left, rect.bottom );
::LineTo( hDC, rect.right, rect.bottom );
::LineTo( hDC, rect.right, rect.top );
::LineTo( hDC, rect.left, rect.top );
// RECT系の命令で四角を描く
::Rectangle( hDC, rect.left, rect.top, rect.right, rect.bottom );
-
確かに1つずれました。しかし、StretchDIBits はRECT系の命令とも言えません。
以下のプログラムの1倍と拡大では、 サイズは inSizeY = Ey - Sy + 1;
のようにしなければ、画像が左上にずれてしまいます。
また、1倍の時、縮小の部分を走らせても、サイズを inSizeY = Ey - Sy
とすると、画像がずれてきます。サイズは inSizeY = Ey - Sy + 1; として改めて、
プログラムを書き換えて上手く行くようになりました。
void OverlayClear( int Sx, int Sy, int Ex, int Ey )
{
double Magnification = 1.0 / 3.0; // 拡大率
int Ysize = 480; // 全体の垂直方向の長さ// inは DIB のピクセルビット、 outは デバイスコンテキスト
int inSizeX, inSizeY, outSizeX, outSizeY;
int inSx, inSy, outSx, outSy, outEx, outEy;
int inSizeX, inSizeY, outSizeX, outSizeY;
if ( Magnification >= 1 { // 1倍と拡大
inSx = Sx;
inSy = Ysize - Ey - 1; // ★
inSizeX = Ex - Sx + 1; // ★
inSizeY = Ey - Sy + 1; // ★
outSx = (int)( (double)Sx * Magnification + 0.5 );
outSy = (int)( (double)Sy * Magnification + 0.5 );
outSizeX = (int)( (double)inSizeX * Magnification + 0.5 );
outSizeY = (int)( (double)inSizeY * Magnification + 0.5 );
}
else {
outSx = (int)( (double)( Sx - ag_PanOffsetX ) * Magnification + 0.5 );
outSy = (int)( (double)( Sy - ag_PanOffsetY ) * Magnification + 0.5 );
outEx = (int)( (double)( Ex - ag_PanOffsetX ) * Magnification + 0.5 );
outEy = (int)( (double)( Ey - ag_PanOffsetY ) * Magnification + 0.5 );
outSizeX = outEx - outSx + 1; // ■
outSizeY = outEy - outSy + 1; // ■
inSizeX = (int)( (double)outSizeX / Magnification + 0.5 );
inSizeY = (int)( (double)outSizeY / Magnification + 0.5 );
inSx = (int)( (double)outSx / Magnification + 0.5 );
inSy = Ysize - inSizeY; // ■
}
SetStretchBltMode( ag_hDC, COLORONCOLOR );
StretchDIBits( ag_hDC, outSx, outSy, outSizeX, outSizeY,
inSx, inSy, inSizeX, inSizeY,
lg_pPixcelM, // DIB の色データが入ったバイト配列へのポインタ
lg_pBitmapInfoM,
DIB_RGB_COLORS,
SRCCOPY); // ラスタオペレーションコード
} -
申し訳ありません。直前の投稿で、■の部分の最後は正しく動きません。
■■に変えてください。
チャント動いていますが、理屈がもう一つ分かっていません。
前々回のサイズを inSizeY = Ey - Sy で考える場合で1倍以下の場合は
正しく動いていますが、こちらも良く理屈がよく分かりません。
void OverlayClear( int Sx, int Sy, int Ex, int Ey )
{
double Magnification = 1.0 / 3.0; // 拡大率
int Ysize = 480; // 全体の垂直方向の長さ// inは DIB のピクセルビット、 outは デバイスコンテキスト
int inSizeX, inSizeY, outSizeX, outSizeY;
int inSx, inSy, outSx, outSy, outEx, outEy;
int inSizeX, inSizeY, outSizeX, outSizeY;
if ( Magnification >= 1 { // 1倍と拡大
inSx = Sx;
inSy = Ysize - Ey - 1; // ★
inSizeX = Ex - Sx + 1; // ★
inSizeY = Ey - Sy + 1; // ★
outSx = (int)( (double)Sx * Magnification );
outSy = (int)( (double)Sy * Magnification );
outSizeX = (int)( (double)inSizeX * Magnification );
outSizeY = (int)( (double)inSizeY * Magnification );
}
else {
outSx = (int)( (double)( Sx - ag_PanOffsetX ) * Magnification + 0.5 );
outSy = (int)( (double)( Sy - ag_PanOffsetY ) * Magnification + 0.5 );
outEx = (int)( (double)( Ex - ag_PanOffsetX ) * Magnification + 0.5 );
outEy = (int)( (double)( Ey - ag_PanOffsetY ) * Magnification + 0.5 );
outSizeX = outEx - outSx + 1; // ■
outSizeY = outEy - outSy + 1; // ■
inSizeX = (int)( (double)outSizeX / Magnification + 0.5 );
inSizeY = (int)( (double)outSizeY / Magnification + 0.5 );
inSx = (int)( (double)outSx / Magnification + 0.5 );
inSy = (int)( lg_Vsize - (int)((double)( outEy + 1 ) / DispMagY + 0.5) - 1.0 ); //■■}
SetStretchBltMode( ag_hDC, COLORONCOLOR );
StretchDIBits( ag_hDC, outSx, outSy, outSizeX, outSizeY,
inSx, inSy, inSizeX, inSizeY,
lg_pPixcelM, // DIB の色データが入ったバイト配列へのポインタ
lg_pBitmapInfoM,
DIB_RGB_COLORS,
SRCCOPY); // ラスタオペレーションコード
} -
RECT(0,0,10,10)の値で四角を描くと実際には(0,0)-(9,9)の範囲に線分が引かれます。
パソコンの画面はピクセルの集まり(ラスタ画像)によって構成されます。
このピクセルは有効な幅を持っているため四角形を描く際に10ピクセル目まで描画すると実際の幅は11ピクセルになります。
このため例えば、10ピクセルごとにグリッド線を引いているとRECTのサイズと合わないことがありますが、実際の画像サイズは10ピクセルなので気にする必要はないはずです。
おそらく「inSizeX = Ex - Sx + 1」しているのはこのあたりのことではないかと思うのですがいかがでしょう?
もしそういうことであれば、深く考えずに幅と高さは(right-left,bottom-top)で計算させたほうがよい結果が得られるのではないかと思います。
外していたらごめんなさい。
0 1 2 3 4 5 6 7 8 910
0■■■■■■■■■■□
1■□□□□□□□□■□
2■□□□□□□□□■□
3■□□□□□□□□■□
4■□□□□□□□□■□
5■□□□□□□□□■□
6■□□□□□□□□■□
7■□□□□□□□□■□
8■□□□□□□□□■□
9■■■■■■■■■■□
10□□□□□□□□□□□
|<--- 10pixel ---> |
実際には存在しないグリッド線の幅も画面上では1ピクセル分として表現される