Почему все координаты и размеры странные? - PullRequest
4 голосов
/ 27 ноября 2011

Этот код создал следующее изображение.

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);

test image

Если растровое изображение имеет размер 300x300, , почему занимает белый прямоугольник (0, 0, 400, 400) только небольшую его часть? Почему не по центру текста?

Я даже не уверен, какие условия для Google. Я ищу мудрости.

Ответы [ 2 ]

4 голосов
/ 01 декабря 2011

ПРИМЕЧАНИЕ: добавление этого после предлагаемой награды в дополнение к моему первоначальному ответу

Для начала, вам не нужен фоновый прямоугольник 400x400, потому что вы рендеринг только растровое изображение 300x300,Итак, вот первое изменение:

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

С этим изменением результат будет точно таким же, но это упрощает объяснение.

Где это возможно и логично, WPF использует DIP (независимые от устройства пиксели) как единицы измерения, а не пиксели.Когда вы сделаете это:

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

Вы не обязательно получите Rectangle, что составляет 100x100 физических пикселей.Если у вашего устройства больше (или меньше), чем 96 пикселей на физический дюйм, у вас будет другое количество физических пикселей.Думаю, 96 пикселей на дюйм - это своего рода отраслевой стандарт.Современные устройства, такие как смартфоны и планшеты, имеют гораздо больше пикселей на физический дюйм.Если бы WPF использовал физические пиксели в качестве своей единицы измерения, то вышеупомянутое Rectangle будет отображаться меньше на таком устройстве.

Теперь, чтобы отобразить растровое изображение (или JPEG, PNG, GIF, что угодно), пиксели, зависимые от устройства, должны использоваться, потому что это растеризованный формат (не векторный формат).И это то, что вы указываете, когда вызываете конструктор RenderTargetBitmap.Вы говорите, что хотите, чтобы результирующее растровое изображение было 300x300 физических пикселей с DPI 40. Поскольку исходный объект имеет 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 физических пикселей, и источник всегда будет точно его заполнять.

3 голосов
/ 27 ноября 2011

Вы указали 40 DPI, когда захотите 96:

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