Указание 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 вычисляются без округления или усечения.