Click here to Skip to main content
Click here to Skip to main content

Output graphics files using your printing code

By , 21 Mar 2005
 

PrintControllerFileDemo

Introduction

Would you like your application to support saving to different graphics file formats? Provided you have implemented Print Preview, it turns out to be easier than you think. This is a small article based around the GDI+ Image.Save method.

Using the code

Using the code could not be easier.

Just add the PrintControllerFile.cs file to your project, and use it like so:

using ( PrintDocument doc = new MyPrintDocument( ... ) )
{
    ImageFormat format = ImageFormat.Jpeg;
    float scale = 1f;
    long quality = 75L;
    string output = "Output";

    PrintController controller = 
      new PrintControllerFile( format, scale, quality, output );
    
    doc.PrintController = 
      new PrintControllerWithStatusDialog( controller, "Exporting" );

    doc.Print();
}

The parameters of the PrintControllerFile constructor are:

  • format: One of the static formats in the ImageFormat class.
  • scale: A simple scaling ratio to control the size of the output.
  • quality: Only used by the JPEG encoder. Default is about 75.
  • output: The base filepath of the output files.

Et voila! The output is a collection of graphics files, one for each page.

How it works

Basically, PrintControllerFile uses PreviewPrintController to generate Metafiles, and then saves these as the output.

This method has the advantage that the output will always look the same as in your Print Preview.

It also saves us a lot of work. If you look at PreviewPrintController in Reflector, you will find that it sets up a Graphics object for each page using various GDI+ methods. We want our output to be identical to the Print Preview, so it makes sense to let PreviewPrintController do this work.

So PrintControllerFile derives from PreviewPrintController, and overrides the OnEndPage method to save each page as it is generated. I could have let PreviewPrintController generate all the pages, and then save them, but I wanted to save each page as soon as it is generated so that if you cancel the print, then you get the output up to that point.

internal class PrintControllerFile : PreviewPrintController
{
    ...
    
    public override void OnEndPage( PrintDocument document, PrintPageEventArgs e )
    {
        base.OnEndPage( document, e );

        // Get the current Metafile
        PreviewPageInfo[] ppia = GetPreviewPageInfo();
        PreviewPageInfo ppi = ppia[ ppia.Length - 1 ];
        Image image = ppi.Image;
        _Metafile = ( Metafile ) image;

        if ( _Format == ImageFormat.Emf )
          { _Metafile.Save( PagePath, _Format ); return; }
        if ( _Format == ImageFormat.Wmf )
          { _Metafile.Save( PagePath, _Format ); return; }

        SaveViaBitmap( document, e );
    }
    
    ...
}

Saving the output for the Enhanced Metafile and Windows Metafile formats is as easy as calling Save on the Metafile instance with the required format.

However, this method doesn't work for the other graphics formats because you have no control over the output. For example, saving a standard page as a bitmap results in a file over 100MB in size. Obviously, this is not what is required.

The solution is to create a Bitmap image with the required properties, play the Metafile onto it, and then save the Bitmap. This gives us the control over the output that we require.

    protected void SaveViaBitmap( PrintDocument document, PrintPageEventArgs e )
    {
        int width  = e.PageBounds.Width  ;
        int height = e.PageBounds.Height ;

        using ( Bitmap bitmap = new Bitmap( ( int ) ( width * _Scale ), 
                                          ( int ) ( height * _Scale ) ) )
        using ( Graphics graphics = Graphics.FromImage( bitmap ) )
        {
            graphics.Clear( Color.White );

            if ( _Scale != 1 ) graphics.ScaleTransform( _Scale, _Scale );

            Point point = new Point( 0, 0 );
            Graphics.EnumerateMetafileProc callback = 
              new Graphics.EnumerateMetafileProc( PlayRecord );
  
            graphics.EnumerateMetafile( _Metafile, point, callback );

            Save( bitmap );
        }
    }

The only parameter I needed to setup the Bitmap object was a scale parameter. The size of the output is set by multiplying the PageBounds of the PrintPageEventArgs by the scale parameter. Actually, you normally want the output to be the standard size, but values of scale from about 0.5 to 2.0 might be useful. If a scale is set, then a ScaleTransform is applied to the Graphics object, so your printing code doesn't have to be modified.

Creating a Graphics object from the Bitmap and playing the metafile onto it is then just boiler-plate stuff.

Saving the Bitmap in the required format is then simple, apart from for the JPEG format, which requires a Quality parameter to be set for the encoder. This value must be in the range from 0 (highest compression, lowest quality), to 100 (lowest compression, highest quality). The default value seems to be about 75.

    protected void Save( Bitmap bitmap )
    {
        if ( _Format == ImageFormat.Jpeg )
        {
            EncoderParameters parameters = new EncoderParameters( 1 );
            EncoderParameter parameter = 
              new EncoderParameter( Encoder.Quality, _Quality );
            parameters.Param[ 0 ] = parameter;

            bitmap.Save( PagePath, _Codec, parameters );
            return;
        }

        bitmap.Save( PagePath, _Format );
    }

The main file, PrintControllerFile.cs, is only about 200 lines long, and is fairly self-explanatory. Take a look, if you're interested.

Points of Interest

The Image.Save method is very powerful, but cannot be used directly on Metafiles. Creating an intermediary Bitmap, and then saving that, gives us the control we need over the output.

It would be nice if there was more control, but if you just want to produce graphics files which you can modify in an image editor, then this just works.

History

22nd March 2005 - Version 1.0

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Nicholas Butler
United Kingdom United Kingdom
Member

I built my first computer, a Sinclair ZX80, on my 11th birthday in 1980.
In 1992, I completed my Computer Science degree and built my first PC.
I discovered C# and .NET 1.0 Beta 1 in late 2000 and loved them immediately.
I have been writing concurrent software professionally, using multi-processor machines, since 1995.
 
In real life, I have spent 3 years travelling abroad,
I have held a UK Private Pilots Licence for 20 years,
and I am a PADI Divemaster.
 
I now live near idyllic Bournemouth in England.
 
If you would like help with multithreading, please contact me via my website:
 
 
I can work 'virtually' anywhere!

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionVB.Net version available?memberRichard Sauceda8 Oct '08 - 5:18 
I am currently working on a windows form project that has to save a one page PrintDocument as an image. This code is exactly what I need for my project. But, I am not familiar with C# programming and do not know how to convert it to VB.Net. Is there anything similar to this in VB.Net?
AnswerRe: VB.Net version available?memberNick Butler8 Oct '08 - 5:29 
Hi Richard,
 
I'm glad you like it.
 
You have two options:
 
1) View the assembly in VB in Reflector and copy the generated source ( may not be exactly correct ).
 
2) Put my C# in a seperate assembly and add a reference to it.
 
HTH
 
Nick
 
----------------------------------
Be excellent to each other Smile | :)

GeneralGreat examplememberBill Seddon16 Nov '07 - 13:55 
Thanks for the post of this little nugget. With TiffManager or one of the PDF libraries this example means documents can be "printed" to file without the need to install a print driver.
GeneralAbout VB version..memberElven Wong28 Mar '07 - 20:24 
I am currently working on a project to convert webpage to image.
 
Webbrowser Control can be called to Print, hence this function will work on my project impressively!!
 
However, I am a novice programmer and developing my project using VB.net Frown | :(
 
Is there anything similiar to this, but in VB.net script?
QuestionResizing the text size of header in a listcontrolmemberVinod Moorkkan23 Aug '06 - 20:26 

Hi all ,
I am working with MFC (Vc++6).Now i have an application , in that i wanted to change the size of text in the header of list control,
Please help me and send some sample code also if you have.
Thanks in advance
vinod
QuestionVery Nice Solutionmemberbhalmadhukar23 Aug '06 - 20:12 
Dear Sir
 
It is very nice solution.
 
But I want to create multpage tiff file
this program create more than 1 tiff file
if text file contains more data.
 
I also want to convert it into tif file
is is possible.

 

Thanks & Regards
Madhukar
QuestionMetafile.Savememberdag@itbase.no28 Apr '06 - 23:38 
If you try to save a metafile using ImageFormat WMF or EMF, the Image.Save method will actually use ImageFormat.PNG. This is useless because the reason to use EMF/WMF is a desire to minimize the size of the output file. After wasting several hours testing, I found that this behaviour is by design: http://support.microsoft.com/default.aspx?scid=kb;en-us;q316563[^]

Any idea on how to create a real emf/wmf file?
 
Dag Haugen

AnswerRe: Metafile.SavememberCathy Grimes26 Jun '07 - 4:42 
I had this problem and found the answer on CodeProject. Have a look at the article "How to use GDI+ to save image in WMF, EXIF or EMF format" at URL:
 
http://www.codeproject.com/vcpp/gdiplus/saveasemfexiwmf.asp

 
Cathy Grimes
GeneralPrinting tiny controls, when paintingmemberjjohn20059 Feb '06 - 21:21 
Hi,
I have a problem in printing, when I paint a control. It displays properly in screen. But when I am printing, the control appears tiny. I am using the following code block:
 
private void PaintCtrl(Control ctrl, Graphics graphics, RectangleF rectF)
{
Bitmap bitmap = null;
PictureBox pic = ctrl as PictureBox;
if (pic != null)
{
if (pic.Image != null)
{
bitmap = pic.Image as Bitmap;
}
}
if (bitmap == null)
{
bitmap = new Bitmap(ctrl .Width, ctrl .Height);
win32.CaptureWindow(ctrl , ref bitmap);
}
 
// Make the rectF the size of the bitmap.
PointF[] points =
{ new PointF(rectF.Location.X, rectF.Location.Y) };
graphics.TransformPoints(
CoordinateSpace.Device, CoordinateSpace.Page, points);
points[0].X += bitmap.Width;
points[0].Y += bitmap.Height;
graphics.TransformPoints(
CoordinateSpace.Page, CoordinateSpace.Device, points);
RectangleF bMapRect = new RectangleF(rectF.Location, rectF.Size);
bMapRect.Height = points[0].Y - rectF.Top;
bMapRect.Width = points[0].X - rectF.Left;
 
graphics.DrawImage(bitmap, bMapRect);
}
 

Can any one help me to fix it? Thanks in advance!
 
jjohn
 

 
John J
GeneralHelpmemberPranfasdfasdf19 Jan '06 - 6:47 
In PrintDocument doc = new MyPrintDocument( ... ) what is MyPrintDocument ? is it a user define class ? Then where it is ? plz reply soon.
GeneralPower of simplicitymemberMe2d9 Dec '05 - 11:55 
Thank U very much - it solved many of my problems!
QuestionCan i save .pcx format?membermassacre1 Dec '05 - 17:48 
Can i save .pcx format?
GeneralNice solution...memberRobert Rohde18 May '05 - 5:04 
... for a problem which could have become a real pain to me. Your code works great. I would never have come to the idea making this like you did. Well done!
GeneralRe: Nice solution...memberNicholas Butler18 May '05 - 19:39 

Thank you - I'm pleased you like it Smile | :)
 
----------------------------
Be excellent to each other Smile | :)
GeneralRe: Nice solution...sussAnonymous13 Jun '05 - 23:29 
Hello,
 
I solve the similar problem (saving image as metafile), but this one doesn't work:
 
Image image = ppi.Image;
_Metafile = ( Metafile ) image;
if ( _Format == ImageFormat.Emf )
{ _Metafile.Save( PagePath, _Format ); return; }
 
it always saves as .PNG, ignoring ImageFormat.Emf completely. Do you know why it happens? Thanks for any advice..
GeneralRe: Nice solution...memberClevedon_Peanut6 Aug '05 - 4:45 
Superb method! Well done for finding it & thnx for putting it on Code Project Big Grin | :-D

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 22 Mar 2005
Article Copyright 2005 by Nicholas Butler
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid