Теперь я узнал о метафайлах больше, чем хотел знать.
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
всегда будет иметь разрешение 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 точек на дюйм, этот пиксель должен быть $%$^! записан как пиксель № 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 в контексте устройства, ответ Марка — хороший ответ.
person
Nicholas Piasecki
schedule
07.10.2009