【问题标题】:Bitmap to PrinterDocument page resolution is not matching位图到 PrinterDocument 页面分辨率不匹配
【发布时间】:2016-07-26 06:40:42
【问题描述】:

我遇到的问题是在PrinterDocumentPrintPage 事件中使用DrawImageUnscaled() 使新位图与打印机输出的大小正确同步。

我在另一篇文章中提出了将图像渲染为来自 cmets 的页面集合的想法,其中我询问了如何以更传统的风格使用打印机(NewPage之前绘制项目调用 Print 等),这在 .NET 框架中不存在。在我第一次尝试使用图像集合时,我注意到即使在将位图 Dpi 设置为与打印机对象相同的 Dpi 后,使用 .DrawImage() 时它们也有一些颗粒感,我发现无需使用 PrinterSettings.CreateMeasurementGraphics() 图形对象进行打印即可发现通过反复试验(很多错误。

到目前为止,这项努力的结果是下面的类(它有一些我一直在玩的“测试”代码,但我已经清理了大部分,所以在这里更形象)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Windows.Forms.Design;

using PdfFileWriter;
using System.Drawing.Printing;
using System.ComponentModel;

using System.IO;

class PDF : PrintDocument {
    /// <summary>
    /// Logo to display on invoice
    /// </summary>
    public Image Logo { get; set; }

    /// <summary>
    /// Current X position on canvas
    /// </summary>
    public int X { get; set; }

    /// <summary>
    /// Current Y position on canvas
    /// </summary>
    public int Y { get; set; }

    /// <summary>
    /// Set the folder where backups, downloads, etc will be stored or retrieved from
    /// </summary>
    [Editor( typeof( System.Windows.Forms.Design.FolderNameEditor ), typeof( System.Drawing.Design.UITypeEditor ) )]
    public string Folder { get { return directory; } set { directory=value; } }

    /// <summary>
    /// Current font used to print
    /// </summary>
    public Font Font { get; set; }

    /// <summary>
    /// Current font color
    /// </summary>
    public Color ForeColor { get; set; }

    private int CurrentPagePrinting { get; set; }

    /// <summary>
    /// Set printer margins
    /// </summary>
    public Margins PrintMargins {
        get { return DefaultPageSettings.Margins; }
        set { DefaultPageSettings.Margins = value; }
    }

    /// <summary>
    /// Pages drawn in document
    /// </summary>
    private List<Image> Pages;

    /// <summary>
    /// The current selected page number. 0 if nothing selected
    /// </summary>
    private int CurrentPage;

    /// <summary>
    /// The current working directory to save files to
    /// </summary>
    private string directory;

    /// <summary>
    /// The currently chosen filename
    /// </summary>
    private string file;

    /// <summary>
    /// Public acceisble object to all paperSizes as set
    /// </summary>
    public List<PrintPaperSize> paperSizes { get; private set; }

    /// <summary>
    /// Object for holding papersizes
    /// </summary>
    public class PrintPaperSize {
        public string Name { get; set; }
        public double Height { get; set; }
        public double Width { get; set; }

        public PrintPaperSize() {
            Height = 0;
            Width = 0;
            Name = "";
        }
    }

    /// <summary>
    /// Current papersize selected. used for some calculations
    /// </summary>
    public PrintPaperSize CurrentPaperSize { get; private set; }

    public PDF() {
        // set the file name without extension to something safe
        file = (string)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds.ToString();

        // set the save directory to MyDocuments
        directory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        CurrentPage = 0;

        // initialize pages array
        Pages = new List<Image>();

        // Set the initial font and color
        Font = new System.Drawing.Font("Arial", (float)11.25);
        ForeColor = Color.Black;

        // set the printer to Microsoft's PDF printer and generate and ensure it will save to a file
        PrinterSettings = new PrinterSettings() {
            PrinterName = "Microsoft Print to PDF",
            PrintToFile = true,
            PrintFileName = Path.Combine(directory, file + ".pdf"),
        };

        // hide the notice 'printing' while spooling job.
        PrintController = new StandardPrintController();

        // set the printer quality to maximum so we can use this for getting the dpi at this setting
        DefaultPageSettings.PrinterResolution.Kind = PrinterResolutionKind.High;

        // store all paper sizes at 1 dpi [ reference: https://social.msdn.microsoft.com/Forums/vstudio/en-US/05169a47-04d5-4890-9b0a-7ad11a6a87f2/need-pixel-width-for-paper-sizes-a4-a5-executive-letter-legal-executive?forum=csharpgeneral ]
        paperSizes = new List<PrintPaperSize>();
        foreach ( PaperSize P in PrinterSettings.PaperSizes ) {
            double W=P.Width/100.0;
            double H=P.Height/100.0;

            paperSizes.Add(
                new PrintPaperSize() {
                    Height = H,
                    Width = W,
                    Name = P.PaperName
                }
            );

            if ( P.PaperName=="Letter" ) {
                CurrentPaperSize = paperSizes[paperSizes.Count-1];
            }
        }

        // setup the initial page type, orientation, margins, 
        using ( Graphics g=PrinterSettings.CreateMeasurementGraphics() ) {
            DefaultPageSettings = new PageSettings(PrinterSettings) {
                PaperSize=new PaperSize( CurrentPaperSize.Name, (Int32)(CurrentPaperSize.Width*g.DpiX), (Int32)(CurrentPaperSize.Height*g.DpiY) ),
                Landscape = false,
                Margins = new Margins(left: 10, right: 10, top: 10, bottom: 10),
                PrinterResolution=new PrinterResolution() {
                    Kind = PrinterResolutionKind.High
                }
            };
        }

        // constrain print within margins
        OriginAtMargins = true;
    }


    public void SetPaperSize( PaperKind paperSize ) {
        // TODO: Use Linq on 
    }

    /// <summary>
    /// Get specific page
    /// </summary>
    /// <param name="page">page number. 1 based array</param>
    /// <returns></returns>
    public Image GetPage( int page ) {
        int p = page - 1;
        if ( p<0||p>Pages.Count ) { return null; }
        return Pages[p];
    }

    /// <summary>
    /// Get the current page
    /// </summary>
    /// <returns>Image</returns>
     public Image GetCurrentPage() {
        return GetPage(CurrentPage);
    }

    /// <summary>
    /// Before printing starts
    /// </summary>
    /// <param name="e">PrintEventArgs</param>
    protected override void OnBeginPrint( PrintEventArgs e ) {
         CurrentPagePrinting=0;
         base.OnBeginPrint( e );
    }

    /// <summary>
    /// Print page event
    /// </summary>
    /// <param name="e">PrintPageEventArgs</param>
    protected override void OnPrintPage( PrintPageEventArgs e ) {
        CurrentPagePrinting++;

        // if page count is max exit print routine
        if ( CurrentPagePrinting>=Pages.Count ) { e.HasMorePages=false; base.OnPrintPage( e ); return; }

        // ensure high resolution / clarity of image so text doesn't fuzz
        e.Graphics.CompositingMode=CompositingMode.SourceOver;
        e.Graphics.CompositingQuality=CompositingQuality.HighQuality;

        // Draw image and respect margins (unscaled in addition to the above so text doesn't fuzz)
        e.Graphics.DrawImageUnscaled(
            Pages[CurrentPagePrinting-1],
            new Point(
                DefaultPageSettings.Margins.Top,
                DefaultPageSettings.Margins.Left
            )
        );
        base.OnPrintPage( e );
    }

    /// <summary>
    /// After printing has been completed
    /// </summary>
    /// <param name="e">PrintEventArgs</param>
    protected override void OnEndPrint( PrintEventArgs e ) {
        base.OnEndPrint( e );
    }

    /// <summary>
    /// Add a new page to the document
    /// </summary>
    public void NewPage() {
        // Add a new page to the page collection and set it as the current page

        Bitmap bmp;
        using(Graphics g = PrinterSettings.CreateMeasurementGraphics()) {
            float dpiscaleX;
            float dpiscaleY;

            // measure default bitmap dpi on this system and use to calculate print dpi
            using ( Bitmap b=new Bitmap( 1, 1 ) ) {
                dpiscaleX = b.HorizontalResolution;
                dpiscaleY = b.VerticalResolution;
            };

            bmp = new Bitmap( 
                (Int32)(((DefaultPageSettings.PrintableArea.Width-( DefaultPageSettings.Margins.Left+DefaultPageSettings.Margins.Right )) / dpiscaleX) * g.DpiX),
                (Int32)(((DefaultPageSettings.PrintableArea.Height-( DefaultPageSettings.Margins.Top+DefaultPageSettings.Margins.Bottom )) / dpiscaleY) * g.DpiY)
            );
            bmp.SetResolution(g.DpiX, g.DpiY);
        }
        Pages.Add( bmp );
        CurrentPage++;
    }

    /// <summary>
    /// Add a new string to the current page
    /// </summary>
    /// <param name="text">The string to print</param>
    /// <param name="align">Optional alignment of the string</param>
    public void DrawString(string text, System.Windows.TextAlignment align = System.Windows.TextAlignment.Left ) {
        // add string to document
        using ( Graphics g=Graphics.FromImage( Pages[CurrentPage - 1] ) ) {
            g.CompositingQuality = CompositingQuality.HighQuality;
            switch ( align ) {
                case System.Windows.TextAlignment.Left:
                case System.Windows.TextAlignment.Justify:
                    g.DrawString( text, Font, new SolidBrush( ForeColor ), new PointF( X, Y ) );
                    Y+=(Int32)g.MeasureString( "X", Font ).Height;
                    break;
                case System.Windows.TextAlignment.Right:
                    g.DrawString( text, Font, new SolidBrush( ForeColor ), new PointF( Pages[CurrentPage - 1].Width - g.MeasureString( text, Font ).Width, Y ) );
                    Y += (Int32)g.MeasureString( "X", Font ).Height;
                    break;
                case System.Windows.TextAlignment.Center:
                    g.DrawString( text, Font, new SolidBrush( ForeColor ), new PointF( ( Pages[CurrentPage-1].Width+g.MeasureString( text, Font ).Width )/2, Y ) );
                    Y+=(Int32)g.MeasureString( "X", Font ).Height;
                    break;
            }
        }
    }
}

PDF 输出说明

如您所见,画布(我班级中的Page 属性)超出了界限。如果我只使用.DrawImage() 绘制图像,则缩放会拉伸它,它看起来只是颗粒状,所以我必须使用.DrawImageUnscaled()

上面的行按顺序是以下代码的结果:

// Initialize the custom print class
PDF p = new PDF();

// Add a new page to the document
p.NewPage();

// Draw some strings.  p.Y value is automatically incremented
p.DrawString( "Hello" );
p.DrawString( "Hello", System.Windows.TextAlignment.Right );
p.DrawString( "Hello", System.Windows.TextAlignment.Center );
p.DrawString( "Hello pure awesomeness" );

// Uncomment the following and add a picture box to the form
// pictureBox1.Height = 1100;
// pictureBox1.Width = 850;
// pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
// pictureBox1.Image = p.GetCurrentPage();

// Send all pages to the "printer"
p.Print();

如果您取消注释pictureBox1 行,并注释掉p.Print(),则结果是正确的(请记住,由于为页面设置了边距,因此图像小于打印文档页面。

PictureBox 输出示意图

如果你使用下面的代码(代替上面的 pictureBox 代码),它在功能上等同于类内的 Dpi 缩放,所有内容都会在 pictureBox 中正确显示(只是比我的 ' High' 设置解析为 600 DPI,其中新创建的位图在调用图像上的 SetResolution() 方法之前为 72 DPI。

Image img = p.GetCurrentPage();

pictureBox1.Height=(Int32)(p.CurrentPaperSize.Height*img.VerticalResolution);
pictureBox1.Width = (Int32)(p.CurrentPaperSize.Width*img.HorizontalResolution);
pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
pictureBox1.Image = img;

【问题讨论】:

  • 使用WPF打印,WinForms通常有DPI问题。 code.msdn.microsoft.com/windowsdesktop/…
  • @VojtěchDohnal - 谢谢,但这不是问题。我对如何计算边距有疑问。打印机设置边距为 100 英寸,我只将该值视为“整体”边距。现在添加一个自动添加新页面的事件,并允许用户控件在打印下一行之前向其打印内容(还允许用户更改事件内的 X / Y 位置。(非常适合处理完整的标题/ 等)。将我的发现发布为其他人的解决方案:)
  • @VojtěchDohnal - 已发布解决方案 :)

标签: c# printing graphics bitmap dpi


【解决方案1】:

测量 DefaultPageSettings.Margins 的计算错误,因为 Margins 以 100 英寸 according to this MSDN document 表示。对此进行调整的正确方法是除以 100。

x = DefaultPageSettings.Margins/100

以下是 OP 的 NewPage() 方法用正确的计算重写:

/// <summary>
/// Add a new page to the document
/// </summary>
public void NewPage() {
    // Add a new page to the page collection and set it as the current page

    Bitmap bmp;
    using(Graphics g = PrinterSettings.CreateMeasurementGraphics()) {
        int w=(Int32)( CurrentPaperSize.Width*g.DpiX )-(Int32)( ( ( DefaultPageSettings.Margins.Left+DefaultPageSettings.Margins.Right )/100 )*g.DpiX );
        int h=(Int32)( CurrentPaperSize.Height*g.DpiY )-(Int32)( ( ( DefaultPageSettings.Margins.Top+DefaultPageSettings.Margins.Bottom )/100 )*g.DpiY );
        bmp = new Bitmap( w, h );
        bmp.SetResolution(g.DpiX, g.DpiY);
    }

    // reset X and Y positions
    Y=0;
    X=0;

    // Add new page to the collection
    Pages.Add( bmp );
    CurrentPage++;
}

【讨论】:

    猜你喜欢
    • 2014-12-25
    • 2015-03-15
    • 2016-06-08
    • 2011-10-11
    • 2017-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多