Graphics.DrawImage
ビットマップのストレッチと描画に使用するWinformsアプリケーションがあり、ソースピクセルが宛先にどのようにマップされるかを正確に理解するための支援が必要です。
理想的には、次のような関数を記述したいと思います。
Point MapPixel(Point p, Size src, Size dst)
これは、ソース画像のピクセル座標を取得し、スケーリングされた宛先のそれに対応する「左上」ピクセルの座標を返します。
わかりやすくするために、2x2ビットマップが4x4にスケーリングされる簡単な例を次に示します。
矢印は、ポイント(1,0)をMapPixelにフィードする方法を示しています。
MapPixel(new Point(1, 0), new Size(2, 2), new Size(4, 4))
(2,0)の結果を与える必要があります。
次のようなロジックを使用して、上記の例でMapPixelを機能させるのは簡単です。
double scaleX = (double)dst.Width / (double)src.Width;
x_dst = (int)Math.Round((double)x_src * scaleX);
dst.Width
ただし、この単純な実装では、がの倍数ではない場合、丸めが原因でエラーが発生することに気付きましたsrc.Width
。この場合、DrawImageは、画像をフィットさせるために他のピクセルよりも大きく描画するためにいくつかのピクセルを選択する必要があり、そのロジックを複製するのに問題があります。
次のコードは、2x1ビットマップをいくつかの異なる幅にスケーリングすることで問題を示しています。
Bitmap src = new Bitmap(2, 1);
src.SetPixel(0, 0, Color.Red);
src.SetPixel(1, 0, Color.Blue);
Bitmap[] dst = {
new Bitmap(3, 1),
new Bitmap(5, 1),
new Bitmap(7, 1),
new Bitmap(9, 1)};
// Draw stretched images
foreach (Bitmap b in dst) {
using (Graphics g = Graphics.FromImage(b)) {
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.PixelOffsetMode = PixelOffsetMode.Half;
g.DrawImage(src, 0, 0, b.Width, b.Height);
}
}
元のsrc
画像と出力dst
画像は次のようになり、MapPixelが青いピクセルをどのようにマッピングする必要があるかを示すいくつかの数字が表示されます。
DrawImageがどのピクセルを大きくするかをどのように決定するかを私は一生理解できません。切り上げることもあれば、切り下げることもあるようです。どちらを選択するかは気にしませんが、関数が正しく機能するためには予測可能である必要があります。
上記のMapPixelの例を使用するように変更してみましたが、最も近い奇数MidpointRounding.AwayFromZero
に丸める関数に置き換えました(結果はわずかに改善されますが、それでも完全ではありません)。また、Graphicsクラスにスケーリングを処理させてみました。つまり、設定し、呼び出してから、を使用して座標を変換しようとしました。興味深いことに、TransformPointsメソッドの結果は、DrawImageおよびDrawImageUnscaledの結果とも常に一致するとは限りません。Math.Round
ScaleTransform
DrawImageUnscaled
TransformPoints
また、ヒントを得るためにGDI +を掘り下げてみましたが、まだ有用なものは何も見つかりませんでした。
着地する場所の予測可能性を維持するためだけに、各ピクセルを個別にペイントする必要はありません。
ご参考までに、私が使用している理由はInterpolationMode.NearestNeighbor
アンチエイリアシングを回避するためであり(個々のピクセルの忠実度を維持する必要があります)、PixelOffsetMode.Half
そこにない場合はDrawImageがビットマップを0.5ピクセルパンするため含まれています。
問題点のさらにいくつかの例には、4pxから13pxにスケーリングする場合のx = 7、および4pxから17pxにスケーリングする場合のx=8が含まれます。
必要に応じて、完全な単体テストコードを投稿できます。これにより、MapPixel関数をドロップインして検証できます。これまでのところ、100%の精度を達成できた唯一の方法は、各ピクセルが一意の色に設定された「ヒント」ソース画像を生成する醜いハックを使用することです。ヒント画像の色を確認し、拡大縮小したヒント画像でその色を探すことで、座標をマッピングします。最適化は可能です(たとえば、ヒント画像は1ピクセルの幅または高さであり、上記の単純なロジックを使用しておおよその答えを推測し、そこから外側に向かって作業します)が、それでも醜いです。
誰かがDrawImageの背後にある配管に光を当てて、MapPixelのより単純な(しかしまだ正確な)実装を考え出すのを手伝ってくれたら幸いです。