Basic considerations and optimized printing for graphics-centered applications

The purpose of this code snippet is
  • the selection of a suitable technology for graphics-centered applications (vector graphics)

  • with a special focus on printing the graphics.

Basic considerations for selecting a suitable technology for graphics-centered applications

When planning a graphics-centric application (for vector graphics), two aspects should be considered from the very beginning:
  • What is the user experience if the graphic is complex - i.e. many drawing operations are required to display the graphic?

  • How can the printing of the graphic be realized?

User experience when displaying complex graphics

In order to be able to reliably exclude residues (leftover pixels) from previous drawing operations, each new drawing of the graphic must include a complete deletion of the background and a complete run-through of all drawing operations. This also applies - in a transferable sense - to sections of the graphic, e.g. if only parts of the graphic are displayed due to a corresponding zoom-in.

However, this complete run-through of all drawing operations can take longer than the refresh rate of the monitor allows (for LCD monitors usually 60 Hz = 1/60 s). If the drawing is not synchronized with the refresh rate of the monitor, probably only a fraction of the time of 1/60 s is available.

To avoid displaying an incompletely drawn graphic on one monitor image and the fully drawn graphic on the following monitor image 1/60 s later (which would irritate the user's eye), double buffering is used. This means that the graphics card displays the "old" graphic until the "new" graphic is completely drawn. To do this, the "new" graphic is drawn outside the graphics card's display memory and then faded into the graphics card's display memory using "Refresh". Especially if the memory for double buffering is also located on the graphics card, this process is very fast and can be carried out by the graphics card in synchronization with the refresh rate of the monitor that the user's eye is never presented with an incompletely drawn graphic. However, this requires a second memory area outside the graphics card's display memory - the graphics buffer.

Double buffering of the graphics

To be able to use the advantages of double buffering, either a control that automatically supports double buffering must be selected to display the graphic or double buffering must be provided manually. The selected control must also support all events that are required for editing the graphic (e.g. mouse events for selecting graphic elements).

If you select a control that automatically supports double buffering, you are dependent on the drawing operations supported by the control to display the graphic. I have not investigated whether DrawingArea is a good choice in this respect. Such a control will enable quick initial successes - but the risk that restrictions will occur later and complex workarounds will have to be programmed is real to great.

If you choose a control that does not support double buffering, you have greater freedom in the choice of drawing operations for displaying the graphic and the risk of restrictions occurring later is significantly lower. However, this comes at the cost of additional effort at the beginning. I have had good experience with PictureBox so far.

Gambas provides three libraries with professional drawing operations: gb.cairo, gb.opengl and gb.sdl2. All three graphic libraries are hardware-accelerated. If the graphic does not need to support 3D functions (volume model, lighting, camera positioning, ...), gb.cairo is a good choice. Due to its restriction to 2D, gb.cairo is simpler and offers a nice extra: The PDF backend CairoPdfSurface and SVG backend CairoSvgSurface. This allows vector graphics (instead of bitmaps) to be written to PDF and a better quality for SVG to be achieved.

If a control is selected that does not support double buffering, the graphic must be drawn outside the graphics card's display memory and then faded into the graphics card's display memory using "Refresh". For the PictureBox, you can first draw in a Image object or Picture object and this object can then be assigned via Image property or Picture property and displayed using the Refresh method.

Display lists for the graphics commands

Another approach to accelerate the drawing of graphics is the use of display lists. Display lists are used to store finally calculated (coordinates, colors, ...) drawing operations that then only need to be processed - which can also be hardware-accelerated by the libraries. Especially when only parts of the graphic change, display lists reduce the system load considerably. gb.opengl already offers display lists with NewList and EndList. If the choice falls on gb.cairo, the implementation of display lists should be seriously considered. In addition to the accelerated drawing and the low system load, there is another advantage: With display lists, all specific backends (CairoPdfSurface and CairoSvgSurface) can be used in parallel in the same application with minimal effort - this significantly improves printing to an PDF or SVG file.

Optimized printing

If gb.cairo is selected as the graphics library, the CairoPdfSurface and CairoSvgSurface backends can be used to achieve optimum printout quality. My Manjaro Linux offers a "Print to file" printer that can be used to print PFD, PS and SVG.

The following code snippet shows the Gambas class that handles the optimized printing.

' Gambas class file

''' This class provides all the printing functionality for a **Cairo** based drawing application.

'' Buffer the printer, that provides the printing functionality.
Private $oPrinter As Printer

'' Buffer the image to print to paper, to PDF and PS.
Private $oImage As Image

'' Buffer the model to print to SVG.
Private $oModel As FlowDiagram

'' Buffer the file name for an print to SVG buffer-file.
Private $oBufferFile As String

'' The only method, that must be called from outside.
''
'' The **oImage** defines the image to print to Paper, PDF and PS.
'' The **oModel** defines the data-nodel (diagram) to print to SVG.
Public Sub Print(oImage As Image, oModel As FlowDiagram)
  $oImage = oImage
  $oModel = oModel
  $oBufferFile = ""
  $oPrinter = New Printer As "ctrlPrinter"

  $oPrinter.Paper = Printer.A4
  $oPrinter.Orientation = Printer.Landscape

  If $oPrinter.Configure() Then
    $oModel = Null
    $oImage = Null
    $oPrinter = Null
    Return
  Endif

  $oPrinter.Print()
  $oModel = Null
  $oImage = Null
  $oPrinter = Null

  ' We have to overwrite the standard print function result with the separate file for optimized printing.
  If $oBufferFile <> "" Then
    Try Move $oBufferFile Kill Replace$($oBufferFile, "_buffer.", ".")
  Endif
End

'' Event handler to set the number of pages.
'' Public to provide access for the event loop. Don't call this handler manually.
Public Sub ctrlPrinter_Begin()
  $oPrinter.Count = 1
End

'' Event handler to execute printing.
'' Public to provide access for the event loop. Don't call this handler manually.
Public Sub ctrlPrinter_Draw()
  If $oPrinter.Name = "Print to File" Then
    ' We have to use a separate file for optimized printing to a file. Otherwise, the standard print function would overwrite the result.
    If Right($oPrinter.OutputFile, 4) = ".svg" Then
      $oBufferFile = Replace$($oPrinter.OutputFile, ".svg", "_buffer.svg")
      Dim oSvgSurface As CairoSvgSurface = New CairoSvgSurface($oBufferFile, $oPrinter.PaperWidth, $oPrinter.PaperHeight)

      Cairo.Begin(oSvgSurface)
      CairoHelper.RunDisplayLists($oModel)
      Cairo.Save()
      Cairo.End()

      oSvgSurface = Null
      Return
    Else If Right($oPrinter.OutputFile, 4) = ".pdf" Then
      $oBufferFile = Replace$($oPrinter.OutputFile, ".pdf", "_buffer.pdf")
      Dim oPdfSurface As CairoPdfSurface = New CairoPdfSurface($oBufferFile, $oPrinter.PaperWidth, $oPrinter.PaperHeight)

      Cairo.Begin(oPdfSurface)
      CairoHelper.RunDisplayLists($oModel)
      Cairo.Save()
      Cairo.End()

      oPdfSurface = Null
      Return
    Endif
  Endif

  Paint.DrawImage($oImage, 0, 0)
End

The print function in the main form is then only a 3-line code:

Public Sub btnPrint_Click()
  Dim printHelper As PrintHelper = New PrintHelper
  printHelper.Print(ctrlPictureBox.Image, $oDiagram)
  printHelper = Null
End


Page revisions


Basic considerations and optimized printing for graphics-centered applications by: Steffen Ploetz - April 3rd, 2024