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

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

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

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

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

  • 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 х 10,98 дюйма (с использованием 87 точек на дюйм) вместо 8,5 х 11 дюймов (с использованием 88 точек на дюйм).

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

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

4 ответа

Решение

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

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

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

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 Экземпляр всегда будет иметь DPI 96 точек на дюйм. (Если бы мне пришлось угадывать, он всегда установлен на логический DPI, а не на физический.)

Вы можете вообразить ту веселость, которая возникает, когда вы рисуете Graphics Экземпляр, который работает под 96 точек на дюйм и записи на Metafile экземпляр, который имеет 87,9231 точек на дюйм "записано" в заголовке. (Я говорю "записано", потому что оно рассчитывается по другим значениям.) "Пиксели" метафайла (помните, что команды 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 различаются, и я не могу вспомнить, когда в последний раз это происходило.

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

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

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

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

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

5. В панели "Сводка" в проводнике Windows происходит обрезание значений при отображении рассчитанного значения DPI.

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

Выводы

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

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

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

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

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

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

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

Другие вопросы по тегам