Указание 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).