4

このコードは次の画像を生成しました。

DrawingVisual visual = new DrawingVisual();
DrawingContext ctx = visual.RenderOpen();

FormattedText txt = new FormattedText("45", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Verdana"), 100, Brushes.Red);
ctx.DrawRectangle(Brushes.White, new Pen(Brushes.White, 10), new System.Windows.Rect(0, 0, 400, 400));
ctx.DrawText(txt, new System.Windows.Point((300 - txt.Width)/2, 10));
ctx.Close();

RenderTargetBitmap bity = new RenderTargetBitmap(300, 300, 40, 40, PixelFormats.Default);
bity.Render(visual);
BitmapFrame frame = BitmapFrame.Create(bity);
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(frame);
MemoryStream ms = new MemoryStream();
encoder.Save(ms);

テスト画像

ビットマップが300x300の場合、白い長方形(0、0、400、400)がその一部しか占めないのはなぜですか テキストが中央に配置されないのは なぜですか?

グーグルの言葉すらわからない。私は知恵を求めています。

4

2 に答える 2

4

注:私の元の答えに加えて、賞金が提供された後にこれを追加する

手始めに、300x300ビットマップのみをレンダリングしているため、400x400の背景の長方形は必要ありません。したがって、最初の変更は次のとおりです。

ctx.DrawRectangle(Brushes.White, new Pen(Brushes.White, 10), new System.Windows.Rect(0, 0, 300, 300));

この変更を行うと、出力はまったく同じになりますが、説明が簡単になります。

可能かつ論理的な場合、WPFはDIP(デバイスに依存しないピクセル)をピクセルではなく測定単位として使用します。これを行うとき:

<Rectangle Width="100" Height="100"/>

必ずしもRectangle100x100の物理ピクセルになるとは限りません。デバイスの物理ピクセル数が96ピクセルより多い(または少ない)場合は、物理ピクセル数が異なります。1インチあたり96ピクセルは、業界標準のようなものだと思います。スマートフォンやタブレットなどの最新のデバイスは、物理的なインチあたりのピクセル数がはるかに多くなっています。WPFが測定単位として物理ピクセルを使用した場合、前述Rectangleのデバイスでは前述のサイズが小さくなります。

ここで、ビットマップ(または、JPEG、PNG、GIFなど)をレンダリングするには、デバイスに依存するピクセルを使用する必要があります。これは、ラスター形式(ベクトル形式ではない)であるためです。RenderTargetBitmapそして、コンストラクターを呼び出すときに指定しているのはそれです。結果のビットマップをDPI40の300x300物理ピクセルにすることを伝えています。ソースのDPIは96(モニターが業界標準であると想定)であり、ターゲットのDPIは40であるため、縮小する必要があります。ターゲットに合うソース。したがって、効果はレンダリングされたビットマップ内の縮小された画像です。

ここで本当にやりたいのは、ソースDPIとターゲットDPIが一致していることを確認することです。96をハードコーディングするほど単純ではありません。これは、説明したように、これは単なる標準であり、ソースは実際にはそれよりも多かれ少なかれDPIを持っている可能性があるためです。残念ながら、WPFはDPIを取得するための優れた方法を提供していません。これは、私の意見ではばかげています。ただし、それを取得するために少しp/invokeを実行できます。

public int Dpi
{
    get
    {
        if (this.dpi == 0)
        {
            var desktopHwnd = new HandleRef(null, IntPtr.Zero);
            var desktopDC = new HandleRef(null, SafeNativeMethods.GetDC(desktopHwnd));

            this.dpi = SafeNativeMethods.GetDeviceCaps(desktopDC, 88 /*LOGPIXELSX*/);

            if (SafeNativeMethods.ReleaseDC(desktopHwnd, desktopDC) != 1 /* OK */)
            {
                // log error
            }
        }

        return this.dpi;
    }
}

private static class SafeNativeMethods
{
    [DllImport("User32.dll")]
    public static extern IntPtr GetDC(HandleRef hWnd);

    [DllImport("User32.dll")]
    public static extern int ReleaseDC(HandleRef hWnd, HandleRef hDC);

    [DllImport("GDI32.dll")]
    public static extern int GetDeviceCaps(HandleRef hDC, int nIndex);
}

これで、関連するコード行を次のように変更できます。

RenderTargetBitmap bity = new RenderTargetBitmap(300, 300, this.Dpi, this.Dpi, PixelFormats.Default);

また、実行しているデバイスに関係なく機能します。最終的には300x300物理ピクセルのビットマップになり、ソースは常にそれを正確に埋めます。

于 2011-12-01T09:17:29.943 に答える
3

96が必要なときに40DPIを指定しました。

RenderTargetBitmap bity = new RenderTargetBitmap(300, 300, 96, 96, PixelFormats.Default);
于 2011-11-27T18:32:39.047 に答える