Указание DPI контекста устройства GDI

У меня есть приложение, которое генерирует метафайлы (EMF). Он использует эталонное устройство (также известное как экран) для рендеринга этих метафайлов, поэтому DPI метафайла изменяется в зависимости от того, на какой машине выполняется код.

Допустим, мой код предназначен для создания метафайла размером 8,5 x 11 дюймов. Используя мою рабочую станцию ​​для разработки в качестве эталона, я получаю EMF, который имеет

  • rclFrame {0, 0, 21590, 27940} (размеры метафайла в тысячных долях миллиметра)
  • a szlDevice из { 1440, 900 } (размеры эталонного устройства в пикселях)
  • a szlMillimeters { 416, 260 } (размеры эталонного устройства в мм)

Итак, rclFrame говорит мне, что размер EMF должен быть

  • 21590 / 2540 = 8,5 дюйма в ширину
  • 27940 / 2540 = 11 дюймов в высоту

Право на. Используя эту информацию, мы также можем определить физический DPI моего монитора, если мои расчеты верны:

  • (1440 * 25,4) / 416 = 87,9231 точек на дюйм по горизонтали
  • (900 * 25,4) / 260 = 87,9231 точек на дюйм по вертикали

Проблема

Все, что воспроизводит этот метафайл — преобразование EMF в PDF, страница «Сводка» при щелчке правой кнопкой мыши по EMF в проводнике Windows и т. д. — похоже, обрезает рассчитанное значение DPI, отображая 87 вместо 87,9231 (даже 88 будет нормально).

Это приводит к тому, что страница имеет физический размер 8,48 x 10,98 дюйма (при использовании 87 точек на дюйм) вместо 8,5 x 11 дюймов (при использовании 88 точек на дюйм) при воспроизведении метафайла.

  • Можно ли изменить DPI эталонного устройства так, чтобы информация, хранящаяся в метафайле, используемом для расчета DPI, выходила в целое число?
  • Могу ли я создать свой собственный контекст устройства и указать его DPI? Или мне действительно нужно использовать принтер для этого?

Спасибо за понимание.


person Nicholas Piasecki    schedule 06.10.2009    source источник
comment
Правильно ли отображается размер на сводной странице? 748х968 или 747х967?   -  person Vanuan    schedule 22.11.2011
comment
то, что математика изложена так, как вы сделали в этом вопросе, имеет для меня неизмеримую ценность   -  person bkwdesign    schedule 29.01.2016


Ответы (4)


Мне любопытно, как Windows знает физический размер вашего монитора. Вы, должно быть, изменили конфигурацию где-то? Возможно, вы можете изменить его на более удобные значения, которые хорошо распределяются.

Как следует из названия, «контекст устройства» должен быть подключен к системному устройству. Однако это не обязательно должен быть аппаратный драйвер, это может быть эмулятор устройства, такой как драйвер печати PDF Writer. Я видел по крайней мере один, который позволяет вам установить произвольный DPI.

person Mark Ransom    schedule 06.10.2009
comment
Windows не знает физического размера монитора, хотя вы можете получить его из EDID, см. stackoverflow.com/questions/577736 . Насколько я понимаю, если вы установите на мониторе исходное разрешение, вы почти всегда получите (очень близко) 96 точек на дюйм, если это плоская панель. - person Robert Harvey; 07.10.2009
comment
Что ж, это интересно. Казалось бы, это указывает на то, что GdipRecordMetafileStream, который вызывает конструктор класса .NET Metafile, выясняет это. (Мой монитор имеет исходное разрешение — Dell E198WFP — и я взял рулетку, и, конечно же, эти миллиметровые размеры верны!) - person Nicholas Piasecki; 07.10.2009

Теперь я узнал о метафайлах больше, чем хотел знать.

1. Некоторые перегрузки конструктора класса Metafile работают плохо и будут работать с усеченным значением DPI.

Рассмотрим следующее:

protected Graphics GetNextPage(SizeF pageSize)
{
    IntPtr deviceContextHandle;
    Graphics offScreenBufferGraphics;
    Graphics metafileGraphics;
    MetafileHeader metafileHeader;

    this.currentStream = new MemoryStream();
    using (offScreenBufferGraphics = Graphics.FromHwnd(IntPtr.Zero))
    {
        deviceContextHandle = offScreenBufferGraphics.GetHdc();
        this.currentMetafile = new Metafile(
            this.currentStream,
            deviceContextHandle,
            new RectangleF(0, 0, pageSize.Width, pageSize.Height),
            MetafileFrameUnit.Inch,
            EmfType.EmfOnly);

        metafileGraphics = Graphics.FromImage(this.currentMetafile);

        offScreenBufferGraphics.ReleaseHdc();
    }

    return metafileGraphics;
}

Если вы передали SizeF из {8.5, 11}, вы можете ожидать получить Metafile с rclFrame из {21590, 27940}. В конце концов, перевести дюймы в миллиметры несложно. Но вы, вероятно, не будете. В зависимости от вашего разрешения GDI+, похоже, будет использовать усеченное значение DPI при преобразовании параметра дюймов. Чтобы сделать это правильно, я должен сделать это сам в сотых долях миллиметра, которые GDI+ просто пропускает, поскольку именно так он изначально хранится в заголовке метафайла:

this.currentMetafile = new Metafile(
    this.currentStream,
    deviceContextHandle,
    new RectangleF(0, 0, pageSize.Width * 2540, pageSize.Height * 2540),
    MetafileFrameUnit.GdiCompatible,
    EmfType.EmfOnly);

Ошибка округления № 1 устранена — rclFrame в моем метафайле теперь правильный.

2. DPI для экземпляра Graphics, записывающего в Metafile, всегда неправильный.

Видите ту переменную metafileGraphics, которую я установил, вызвав Graphics.FromImage() в метафайле? Что ж, кажется, что этот экземпляр Graphics всегда будет иметь разрешение 96 dpi. (Насколько мне известно, для него всегда используется логический DPI, а не физический.)

Вы можете себе представить, какое веселье возникает, когда вы рисуете на экземпляре Graphics, работающем с разрешением 96 dpi, и записываете на экземпляр Metafile, в заголовке которого «записано» 87,9231 dpi. (Я говорю «записано», потому что оно вычисляется из других значений.) «Пиксели» метафайла (помните, что команды GDI, хранящиеся в метафайле, указываются в пикселях) больше, поэтому вы ругаетесь и бормотать, почему ваш призыв нарисовать что-то длиной в один дюйм в итоге оказывается длиной в один-и-что-то-сверх дюймов.

Решение состоит в том, чтобы уменьшить масштаб экземпляра Graphics:


metafileGraphics = Graphics.FromImage(this.currentMetafile);
metafileHeader = this.currentMetafile.GetMetafileHeader();
metafileGraphics.ScaleTransform(
    metafileHeader.DpiX / metafileGraphics.DpiX,
    metafileHeader.DpiY / metafileGraphics.DpiY);

Разве это не фигня? Но, похоже, это работает.

Исправлена ​​ошибка «округления» № 2 — когда я говорю нарисовать что-то с размером «1 дюйм» и разрешением 88 точек на дюйм, этот пиксель должен быть $%$^! записан как пиксель № 88.

3. szlMillimeters может сильно различаться; Удаленный рабочий стол доставляет массу удовольствия.

Итак, мы обнаружили (согласно ответу Марка), что иногда Windows запрашивает EDID вашего монитора и фактически знает, насколько он физически велик. GDI+ полезно использует это (HORZSIZE и т. д.) при заполнении свойства szlMillimeters.

Теперь представьте, что вы идете домой отлаживать этот код удаленного рабочего стола. Предположим, что на вашем домашнем компьютере установлен широкоэкранный монитор с соотношением сторон 16:9.

Очевидно, Windows не может запросить EDID удаленного дисплея. Таким образом, он использует старое значение по умолчанию 320 x 240 мм, что было бы хорошо, за исключением того, что это соотношение сторон 4:3, и теперь точно такой же код генерирует метафайл на дисплее, который предположительно не имеет квадратные физические пиксели: горизонтальный DPI и вертикальный DPI разные, и я не могу вспомнить, когда в последний раз видел это.

Мой обходной путь для этого на данный момент: «Ну, не запускайте его под удаленным рабочим столом».

4. Инструмент EMF-to-PDF, который я использовал, имел ошибку округления при просмотре заголовка rclFrame.

Это было основной причиной моей проблемы, которая вызвала этот вопрос. Мой метафайл был «правильным» все время (ну, правильным после того, как я исправил первые две проблемы), и все эти поиски создания метафайла «высокого разрешения» были отвлекающим маневром. Это правда, что некоторая точность воспроизведения теряется при записи метафайла на устройстве отображения с низким разрешением; это потому, что команды GDI, указанные в метафайле, указаны в пикселях. Неважно, что это векторный формат и его можно увеличивать или уменьшать, некоторая информация теряется во время фактической записи, когда GDI+ решает, к какому «пикселю» привязать операцию.

Я связался с продавцом, и они дали мне исправленную версию.

Ошибка округления №3 устранена.

5. Панель «Сводка» в проводнике Windows просто усекает значения при отображении рассчитанного DPI.

Так уж получилось, что это усеченное значение представляло то же самое ошибочное значение, которое использовалось внутри инструмента EMF-to-PDF. Кроме этого, эта причуда не вносит ничего значимого в дискуссию.

Выводы

Поскольку мой вопрос был о том, как возиться с DPI в контексте устройства, ответ Марка — хороший ответ.

person Nicholas Piasecki    schedule 07.10.2009
comment
Имейте в виду, что вы можете изменить конфигурацию монитора Windows, чтобы 96 DPI больше не было логическим DPI. Опять же, это может быть проблемой только для XP, так как они изменили ситуацию для Vista. Опять же, использование GDI+ еще немного меняет ситуацию. Ознакомьтесь с msdn.microsoft.com/en-us/library. /ms701681(VS.85).aspx - person Mark Ransom; 07.10.2009

Обратите внимание, что я все время работаю с разрешением 120 dpi на WXP (крупные шрифты), что означает, что metafileGraphics.DpiX будет возвращать 120.

Файл EMF, по-видимому, не записывает, какое значение dpi было для эталонного контекста (120 в данном случае, 96 для большинства других людей).

Чтобы сделать вещи более интересными, можно создать EMF, нарисовав растровое изображение в памяти, для которого SetResolution() установлено, скажем, на 300 dpi. В этом случае я считаю, что коэффициент масштабирования должен быть равен 300, а не тому, который может использовать монитор (86.x) или Windows (120).

person Toni Cassisi    schedule 21.07.2010

Похоже, значения на сводной странице неверны. Они рассчитываются как:

Size = round(precize_size)+1
Resolution = trunc(precize_resolution)

Где точные значения рассчитываются без округления или усечения.

person Vanuan    schedule 22.11.2011