Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I have now learned more than I cared to have known about metafiles.</p> <h3>1. Some of the <code>Metafile</code> class's constructor overloads work poorly and will operate on a truncated DPI value.</h3> <p>Consider the following:</p> <pre><code>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; } </code></pre> <p>If you passed in a <code>SizeF</code> of { 8.5, 11 }, you might expect to get a <code>Metafile</code> that has an <code>rclFrame</code> of { 21590, 27940 }. Converting inches to millimeters is not hard, after all. But you probably won't. Depending on your resolution, GDI+, it seems, will use a truncated DPI value when converting the inches parameter. To get it right, I have to do it myself in hundredths of a millimeter, which GDI+ just passes through since that's how it's natively stored in the metafile header:</p> <pre><code>this.currentMetafile = new Metafile( this.currentStream, deviceContextHandle, new RectangleF(0, 0, pageSize.Width * 2540, pageSize.Height * 2540), MetafileFrameUnit.GdiCompatible, EmfType.EmfOnly); </code></pre> <p>Rounding error #1 solved--the <code>rclFrame</code> of my metafile is now correct.</p> <h3>2. The DPI on a <code>Graphics</code> instance recording to a <code>Metafile</code> is always wrong.</h3> <p>See that <code>metafileGraphics</code> variable that I set by calling <code>Graphics.FromImage()</code> on the metafile? Well, it seems that that <code>Graphics</code> instance will always have a DPI of 96 dpi. (If I had to guess, it's always set to the <em>logical</em> DPI, not the <em>physical</em> one.)</p> <p>You can imagine that hilarity that ensues when you are drawing on a <code>Graphics</code> instance that is operating under 96 dpi and recording to a <code>Metafile</code> instance that has 87.9231 dpi "recorded" in its header. (I say "recorded" because its calculated from the other values.) The metafile's "pixels" (remember, the GDI commands stored in the metafile are specified in <em>pixels</em>) are bigger, and so you curse and mutter why your call to draw something one inch long ends up being one-and-something-beyond inches long.</p> <p>The solution is to scale down the <code>Graphics</code> instance:</p> <pre><code> metafileGraphics = Graphics.FromImage(this.currentMetafile); metafileHeader = this.currentMetafile.GetMetafileHeader(); metafileGraphics.ScaleTransform( metafileHeader.DpiX / metafileGraphics.DpiX, metafileHeader.DpiY / metafileGraphics.DpiY); </code></pre> <p>Ain't that a hoot? But it seems to work.</p> <p>"Rounding" error #2 solved--when I say draw something at "1 inch" at 88 dpi, that pixel had better be $%$^! recorded as pixel #88.</p> <h3>3. <code>szlMillimeters</code> can vary wildly; Remote Desktop causes a lot of fun.</h3> <p>So, we discovered (per Mark's answer) that, sometimes, Windows queries the EDID of your monitor and actually knows how big it is physically. GDI+ helpfully uses this (<code>HORZSIZE</code> etc) when filling in the <code>szlMillimeters</code> property.</p> <p>Now imagine that you go home to debug this code of remote desktop. Let's say that your home computer happens to have a 16:9 widescreen monitor.</p> <p>Obviously, Windows can't query the EDID of a remote display. So it uses the age-old default of 320 x 240 mm, which would be fine, except that it happens to be a 4:3 aspect ratio, and now the exact same code is generating a metafile on a display that supposedly has non-square physical pixels: the horizontal DPI and vertical DPI are different, and I can't remember the last time that I saw that happen.</p> <p>My workaround for this for now is: "Well, don't run it under remote desktop."</p> <h3>4. The EMF-to-PDF tool that I was using had a rounding error when looking at the <code>rclFrame</code> header.</h3> <p>This was the principal cause of my problem that triggered this question. My metafile was "correct" all along (well, correct after I fixed the first two issues), and all of this search for creating a "high-resolution" metafile was a red herring. It is true that some fidelity is lost when recording the metafile on a low-resolution display device; that's because the GDI commands specified in the metafile are specified in pixels. It doesn't matter that it's a vector format and can scale up or down, some information is lost <em>during the actual recording</em> when GDI+ decides which "pixel" to snap an operation to.</p> <p>I contacted the vendor and they gave me a corrected version.</p> <p>Rounding error #3 solved.</p> <h3>5. The 'Summary' pane in Windows Explorer just so happens to truncate values when displaying the calculated DPI.</h3> <p>It just so happens that this truncated value represented the same erroneous value that the EMF-to-PDF tool was using internally. Aside from this, this quirk does not contribute anything meaningful to the discussion.</p> <h3>Conclusions</h3> <p>Since my question was about futzing with DPI on device contexts, Mark's is a good answer.</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload