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

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

предположим, мой код намеревается создать метафайл, который составляет 8.5 В x 11 в. Используя мою рабочую станцию разработки в качестве ссылки, я получаю EMF, который имеет

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

хорошо, поэтому rclFrame говорит мне, что размер EMF должен быть

  • 21590 / 2540 = 8.5 в ширину
  • 27940 / 2540 = 11 в высоту

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

  • (1440 * 25.4) / 416 = 87.9231 горизонтальный dpi
  • (900 * 25.4) / 260 = 87.9231 вертикальный dpi

проблема

все, что воспроизводит этот метафайл-преобразование EMF в PDF, страница "сводка" при щелчке правой кнопкой мыши по EMF в Проводнике Windows и т. д.-Кажется, усекает вычисленное значение DPI, отображая 87 вместо 87.9231 (даже 88 было бы хорошо).

Это приводит к страница, которая физически имеет размер 8.48 в x 10.98 in (используя 87 dpi) вместо 8.5 В x 11 in (используя 88 dpi) при воспроизведении метафайла.

  • можно ли изменить 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 при преобразовании параметра inches. Чтобы получить его правильно, я должен сделать это сам в сотых долях миллиметра, который 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. (Если бы мне пришлось угадать, он всегда установлен на логическое 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 dpi, этот пиксель должен быть $%$^! записал как пиксель #88.

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

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

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

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

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

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

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

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

ошибка округления #3 разрешенный.

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

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

выводы

так как мой вопрос был о futzing с DPI на контекстах устройства, Марк это хороший ответ.


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

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

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


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

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

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