Especificar DPI de un contexto de dispositivo GDI
-
20-09-2019 - |
Pregunta
Tengo una aplicación que genera metafiles (EMF). Utiliza el dispositivo de referencia (también conocido como la pantalla) para representar estos metafiles, por lo que el DPI del metafile cambia dependiendo de la máquina en la que se ejecute el código.
Digamos que mi código tiene la intención de crear un metafile que sea 8.5 en x 11 pulg. Usando mi estación de trabajo de desarrollo como referencia, termino con un EMF que tiene
- Un rClframe de {0, 0, 21590, 27940} (dimensiones del metafile, en milésimas de un mm)
- un SzlDevice de {1440, 900} (dimensiones del dispositivo de referencia, en píxeles)
- un szlmillímetros de {416, 260} (dimensiones del dispositivo de referencia, en mm)
De acuerdo, el rClframe me dice que el tamaño del EMF debería ser
- 21590 /2540 = 8.5 en ancho
- 27940 /2540 = 11 en alto
Tocar el asunto exacto. Usando esta información, también podemos determinar el DPI físico de mi monitor, si mis matemáticas son correctas:
- (1440 * 25.4) / 416 = 87.9231 DPI horizontal
- (900 * 25.4) / 260 = 87.9231 DPI vertical
El problema
Cualquier cosa que reproduzca este metafile, una conversión de EMF a PDF, la página de "resumen" cuando haga clic con el botón derecho en el EMF en Windows Explorer, etc., parece truncar el valor de DPI calculado, que muestra 87 en lugar de 87.9231 (incluso 88 estaría bien).
Esto da como resultado una página que tiene un tamaño físico como 8.48 en x 10.98 en (usando 87 ppp) en lugar de 8.5 en x 11 en (usando 88 dpi) cuando se reproduce el metafile.
- ¿Es posible cambiar el DPI del dispositivo de referencia para que la información almacenada en el metafile utilizada para calcular el DPI salga a un entero agradable?
- ¿Puedo crear el contexto de mi propio dispositivo y especificar su DPI? ¿O realmente tengo que usar una impresora para hacer eso?
Gracias por cualquier idea.
Solución
Tengo curiosidad por saber cómo Windows conoce el tamaño físico de su monitor. ¿Debe haber cambiado una configuración en alguna parte? Quizás pueda cambiarlo a valores más convenientes que se dividan bien.
Como lo implica el nombre, se debe conectar un "contexto del dispositivo" a un dispositivo del sistema. Sin embargo, esto no necesita ser un controlador de hardware, podría ser un emulador de dispositivos como un controlador de impresión de escritor PDF. He visto al menos uno que te permite establecer un DPI arbitrario.
Otros consejos
Ahora he aprendido más de lo que me importaba haber sabido sobre metafiles.
1. Algunos de los Metafile
Las sobrecargas del constructor de la clase funcionan mal y funcionarán con un valor de DPI truncado.
Considera lo siguiente:
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;
}
Si pasaste en un SizeF
de {8.5, 11}, puede esperar obtener un Metafile
que tiene un rclFrame
de {21590, 27940}. La conversión de pulgadas a milímetros no es difícil, después de todo. Pero probablemente no lo harás. Dependiendo de su resolución, GDI+, al parecer, utilizará un valor de DPI truncado al convertir el parámetro de pulgadas. Para hacerlo bien, tengo que hacerlo yo mismo en centésimas de un milímetro, que GDI+ simplemente pasa, ya que así es como se almacena de forma nativa en el encabezado metafile:
this.currentMetafile = new Metafile(
this.currentStream,
deviceContextHandle,
new RectangleF(0, 0, pageSize.Width * 2540, pageSize.Height * 2540),
MetafileFrameUnit.GdiCompatible,
EmfType.EmfOnly);
Error de redondeo #1 resuelto: el rclFrame
de mi metafile ahora es correcto.
2. El DPI en un Graphics
grabación de instancia a un Metafile
siempre está mal.
Mira eso metafileGraphics
variable que configuré llamando Graphics.FromImage()
en el metafile? Bueno, parece que que Graphics
La instancia siempre tendrá un DPI de 96 DPI. (Si tuviera que adivinar, siempre está listo para el lógico DPI, no el físico una.)
Puedes imaginar esa hilaridad que se produce cuando te basas en un Graphics
instancia que está operando bajo 96 dpi y grabando a un Metafile
instancia que tiene 87.9231 DPI "registrado" en su encabezado. (Digo "registrado" porque está calculado a partir de los otros valores). píxeles) son más grandes, por lo que maldiciones y murmuras por qué tu llamado para dibujar algo de una pulgada, termina, termina siendo una pulgada de una sola vez de largo.
La solución es escalar el Graphics
instancia:
metafileGraphics = Graphics.FromImage(this.currentMetafile);
metafileHeader = this.currentMetafile.GetMetafileHeader();
metafileGraphics.ScaleTransform(
metafileHeader.DpiX / metafileGraphics.DpiX,
metafileHeader.DpiY / metafileGraphics.DpiY);
¿No es eso un grito? Pero parece funcionar.
Error #2 de "redondeo" resuelto: cuando digo dibujar algo a "1 pulgada" a 88 dpi, ¡ese píxel sería mejor $%$^! Grabado como Pixel #88.
3. szlMillimeters
puede variar salvajemente; El escritorio remoto causa mucha diversión.
Entonces, descubrimos (según la respuesta de Mark) que, a veces, Windows consulta el EDID de su monitor y en realidad sabe qué tan grande es físicamente. GDI+ usa de manera útil esto (HORZSIZE
etc) al completar el szlMillimeters
propiedad.
Ahora imagine que se va a casa para depurar este código de escritorio remoto. Digamos que la computadora de su hogar tiene un monitor de pantalla panorámica de 16: 9.
Obviamente, Windows no puede consultar el EDID de una pantalla remota. Por lo tanto, utiliza el valor predeterminado antiguo de 320 x 240 mm, lo que estaría bien, excepto que resulta ser una relación de aspecto de 4: 3, y ahora el mismo código exacto está generando un metafile en una pantalla que supuestamente no tiene no tener Píxeles físicos cuadrados: el DPI horizontal y el DPI vertical son diferentes, y no recuerdo la última vez que vi que sucedió.
Mi solución para esto por ahora es: "Bueno, no lo ejecute en escritorio remoto".
4. La herramienta EMF-to PDF que estaba usando tenía un error de redondeo al mirar el rclFrame
encabezamiento.
Esta fue la causa principal de mi problema que desencadenó esta pregunta. Mi metafile fue "correcto" todo el tiempo (bueno, correcto después de solucionar los dos primeros problemas), y toda esta búsqueda de crear un metafile de "alta resolución" fue un arenque rojo. Es cierto que se pierde cierta fidelidad al grabar el metafile en un dispositivo de visualización de baja resolución; Esto se debe a que los comandos GDI especificados en el MetaFile se especifican en píxeles. No importa que sea un formato vectorial y pueda escalar hacia arriba o hacia abajo, se pierde cierta información. Durante la grabación real Cuando GDI+ decide a qué "píxeles" aprovechar una operación.
Me puse en contacto con el proveedor y me dieron una versión corregida.
Error de redondeo #3 resuelto.
5. El panel 'Resumen' en Windows Explorer simplemente trunca los valores al mostrar el DPI calculado.
Sucede que este valor truncado representaba el mismo valor erróneo que la herramienta EMF a PDF estaba utilizando internamente. Aparte de esto, esta peculiaridad no contribuye con nada significativo a la discusión.
Conclusiones
Dado que mi pregunta era sobre el futuro con DPI en contextos del dispositivo, Mark es una buena respuesta.
Tenga en cuenta que ejecuto con 120 dpi en wxp todo el tiempo (fuentes grandes), lo que significa metafilegraphics.dpix regresará 120.
El archivo EMF no parece registrar cuál era el DPI del contexto de referencia (120 en este caso, 96 para la mayoría de las otras personas).
Para hacer las cosas más interesantes, es posible crear un EMF al dibujar en un mapa de bits en memoria que ha tenido setResolution () establecido en, por ejemplo, 300 ppi. En ese caso, creo que el factor de escala debe ser 300 y no lo que el monitor (86.X) o Windows (120) podría estar usando.
Parece que los valores en la página de resumen están equivocados. Se calculan como:
Size = round(precize_size)+1
Resolution = trunc(precize_resolution)
Donde los valores de precisión se calculan sin redondeo o truncamiento.