Указание 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 Горизонтальный dpi
  • (900 * 25,4)/260 = 87,9231 по вертикали на дюйм

Проблема

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

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

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

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

Ответ 1

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

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

Ответ 2

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

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. (Если бы я должен был догадаться, он всегда устанавливал логический 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 и т.д.) При заполнении свойства 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 в контекстах устройства, Mark - хороший ответ.

Ответ 3

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

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

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

Ответ 4

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

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

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