Navigation:  I Want To... > Imaging >

Implement a custom renderer

Previous pageReturn to chapter overviewNext page

Use Cases

You want to render using some other technology, such as OpenGL or DirectX.
You've subclassed both PresentationImage and Graphic and you need a renderer that knows how to render the new Graphic derived objects in your scene graph.
You have a third party control that you want to integrate into CC.

Relevant Architecture

The rendering object model
Inside a renderer
How rendering is triggered

Relevant Types

IRenderer
IRenderingSurface
PresentationImage
DrawArgs

Sample Code

using System;

using System.Drawing;

using ClearCanvas.ImageViewer;

using ClearCanvas.ImageViewer.Rendering;

 

namespace MyPlugin.Imaging

{

   public class MyPresentationImage : PresentationImage

   {

      public MyPresentationImage() {}

 

      public override IRenderer ImageRenderer

      {

         get

         {

            if (base.ImageRenderer == null)

               base.ImageRenderer = new MyPresentationImageRenderer();

 

            return base.ImageRenderer;

         }

      }

 

      public override IPresentationImage CreateFreshCopy()

      {

         // TODO: implement a copy method

         throw new Exception("The method or operation is not implemented.");

      }

   }

 

   public class MyPresentationImageRenderer : IRenderer

   {

      public MyPresentationImageRenderer() {}

 

      #region IRenderer members

 

      public IRenderingSurface GetRenderingSurface(IntPtr windowID, int width, int height)

      {

         return new MyRenderingSurface(windowID, width, height);

      }

 

      public void Draw(DrawArgs drawArgs)

      {

         if (drawArgs.DrawMode == DrawMode.Render)

            Render(drawArgs);

         else

            Refresh(drawArgs);

      }

 

      #endregion

 

      private void Render(DrawArgs drawArgs)

      {

         MyRenderingSurface surface = drawArgs.RenderingSurface as MyRenderingSurface;

 

         // Recursively render the nodes in the scene graph to the surface here   

      }

 

      private void Refresh(DrawArgs drawArgs)

      {

         // "Flip" the surface to the actual WinForms control

         MyRenderingSurface surface = drawArgs.RenderingSurface as MyRenderingSurface;

 

         // Get the Graphics from the HDC and draw the back buffer image to it.

         Graphics graphics = Graphics.FromHdc(surface.ContextID);

         graphics.DrawImage(surface.Bitmap, 00);

         graphics.Dispose();

      }

 

      #region IDisposable Members

 

      public void Dispose()

      {

         // dispose of any remaining unmanaged resources here

      }

 

      #endregion

   }

 

   public class MyRenderingSurface : IRenderingSurface

   {

      private IntPtr _windowID;

      private IntPtr _contextID;

      private Bitmap _bitmap;

      private Rectangle _clientRectangle;

      private Rectangle _clipRectangle;

 

      public MyRenderingSurface(IntPtr windowID, int width, int height)

      {

         _windowID = windowID;

         _bitmap = new Bitmap(width, height);

      }

 

      #region IRenderingSurface Members

 

      public IntPtr WindowID

      {

         get { return _windowID; }

         set { _windowID = value; }

      }

 

      public IntPtr ContextID

      {

         get { return _contextID; }

         set { _contextID = value; }

      }

 

      #endregion

 

      // The offscreen buffer we'll use to render our scene graph to

      public Bitmap Bitmap

      {

         get { return _bitmap; }

      }

 

      #region IRenderingSurface Members

 

      public Rectangle ClientRectangle

      {

         get { return _clientRectangle; }

         set { _clientRectangle = value; }

      }

 

      public Rectangle ClipRectangle

      {

         get { return _clipRectangle; }

         set { _clipRectangle = value; }

      }

 

      #endregion

 

      #region IDisposable Members

 

      public void Dispose()

      {

         // dispose of any remaining unmanaged resources here

         _bitmap.Dispose();

      }

 

      #endregion

   }

}

Remarks

Double Buffering

To implement your own custom renderer, you must implement two interfaces: IRenderer and IRenderingSurface.  One way of thinking of IRenderingSurface is as an offscreen buffer on which you render the presentation image's scene graph.  That initial rendering is done when the Framework calls IRenderer.Draw with DrawArgs.DrawMode = DrawMode.Render.  When complete, the Framework will call IRenderer.Draw again, but with DrawArgs.DrawMode = DrawMode.Refresh, which will "flip" the surface (i.e. the offscreen buffer) to the screen.  This two step drawing process is known as double buffering which results in smooth, flicker-free rendering.  Of course, since you are responsible for implementing IRenderer and IRenderingSurface, you can choose not to perform double buffering if you don't want to.  You could, for example, ignore the IRenderer.Draw call when DrawArgs.DrawMode = DrawMode.Render, and only render when DrawArgs.DrawMode = DrawMode.Refresh.  It's up to you; the point is that the concept of double buffering is supported by the Framework, should you want to implement it.

Using Different Rendering Technologies

Note that the particular rendering technology you use--be it OpenGL, DirectX, GDI, etc.--is really an implementation detail.  It is completely hidden behind the IRenderer and IRenderingSurface interfaces.  They are all the Framework ever sees.

The sample above is a much simplified GDI+ based renderer.  The "offscreen buffer" is just a simple bitmap.  That bitmap is then flipped to the screen in the Refresh() method using the GDI+ DrawImage() API call.  Note that in this case, the window ID (i.e. the hwnd) is not used for anything in the implementation of IRenderingSurface.  However, other technologies, such as OpenGL, need the window handle to perform its rendering.

3rd Party Control Integration

These days, it is not uncommon for say, 3D vendors to offer controls and SDKs that allow integration of their 3D technology into other software.  Such integration can be done relatively easily in CC, again using the IRenderer and IRenderingSurface interfaces.  Here's a typical approach:

Make the 3rd party control a member variable in your implementation of IRenderingSuface.  If necessary, use IRenderingSurface.WindowID as the control's parent window handle.
When your implementation of IRenderer.Draw() is called, get the IRenderingSurface from DrawArgs.RenderingSurface.
Cast the IRenderingSurface to your implementation of IRenderingSurface.
Get the 3rd party control from your implementation of IRenderingSurface and call whatever draw method the control exposes.

Examples in Code Base

ClearCanvas.ImageViewer.Rendering.GdiRenderer (a GDI+ renderer)
ClearCanvas.ImageViewer.Tools.Volume.VTK.VolumePresentationImageRenderer (an example of 3rd party control integration using VTK)