Use Case
| • | You want to replace the default renderer, either to improve performance or to accommodate specialized hardware, such as a grayscale monitor. |
Relevant Architecture
Relevant Types
| • | BasicPresentationImage |
| • | IRenderingSurface |
| • | RendererFactoryBase |
| • | RendererBase |
Sample Code
Note: To use the following sample code, the Enabled flag must be set to True as described in the comments at the top of the class.
using System;
using System.Drawing;
using ClearCanvas.Common;
using ClearCanvas.ImageViewer;
using ClearCanvas.ImageViewer.Annotations;
using ClearCanvas.ImageViewer.Graphics;
using ClearCanvas.ImageViewer.Rendering;
namespace MyPlugin.Imaging
{
// To enable this custom renderer, change the Enabled flag to True.
// i.e. [ExtensionOf(typeof (BasicPresentationImageRendererFactoryExtensionPoint), Enabled = true)]
[ExtensionOf(typeof (BasicPresentationImageRendererFactoryExtensionPoint), Enabled = false)]
public class MyRendererFactory : RendererFactoryBase
{
protected override RendererBase GetNewRenderer()
{
return new MyRenderer();
}
}
public class MyRenderer : RendererBase
{
public override IRenderingSurface GetRenderingSurface(IntPtr windowID, int width, int height)
{
return new MyRenderingSurface(windowID, width, height);
}
protected override void Refresh()
{
// "Flip" the surface to the actual WinForms control
MyRenderingSurface surface = base.Surface 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, 0, 0);
graphics.Dispose();
}
protected override void DrawImageGraphic(ImageGraphic imageGraphic)
{
// TODO: Implement this
}
protected override void DrawLinePrimitive(LinePrimitive line)
{
// TODO: Implement this
}
protected override void DrawInvariantLinePrimitive(InvariantLinePrimitive line)
{
// TODO: Implement this
}
protected override void DrawCurvePrimitive(CurvePrimitive curve)
{
// TODO: Implement this
}
protected override void DrawRectanglePrimitive(RectanglePrimitive rectangle)
{
// TODO: Implement this
}
protected override void DrawInvariantRectanglePrimitive(InvariantRectanglePrimitive rectangle)
{
// TODO: Implement this
}
protected override void DrawEllipsePrimitive(EllipsePrimitive ellipse)
{
// TODO: Implement this
}
protected override void DrawInvariantEllipsePrimitive(InvariantEllipsePrimitive ellipse)
{
// TODO: Implement this
}
protected override void DrawArcPrimitive(IArcGraphic arc)
{
// TODO: Implement this
}
protected override void DrawPointPrimitive(PointPrimitive pointPrimitive)
{
// TODO: Implement this
}
protected override void DrawTextPrimitive(InvariantTextPrimitive textPrimitive)
{
// TODO: Implement this
}
protected override void DrawAnnotationBox(string annotationText, AnnotationBox annotationBox)
{
// TODO: Implement this
}
protected override void ShowErrorMessage(string message)
{
// TODO: Implement this
}
}
}
Remarks
By default, BasicPresentationImage uses GdiRenderer. However, a GDI based renderer may not be appropriate in some cases, since the GDI renderer assumes an ARGB output pixel format. There are some devices (e.g. some pure grayscale video cards) that use a different pixel format that is not supported by GDI. In such cases, it is helpful to be able to write your own and override the default. The BasicPresentationImageRendererFactoryExtensionPoint allows you to swap in your own renderer.
The sample code above is a modified version of the sample in the Implement a custom renderer section. The main difference being that the custom renderer sample deals with the use case where graphic types that are unknown to the default renderer have been added and we need a way to render them; here we are simply replacing the default renderer.
There are some important differences between the custom renderer sample code and the sample code above:
| • | MyRenderer derives from RendererBase, which uses the Template design pattern to define the skeleton of the rendering algorithm (e.g. traversing the scene graph), but defers the actual implementation to your subclass. RendererBase also stores the important properties of the DrawArgs that is passed to IRenderer.Render so that the values don't have to be passed to each method. The benefit of this is that the method signatures are simplified, but it also means that the renderer is not stateless, and so cannot be shared across threads. Which brings us to our next difference. |
| • | MyRendererFactory derives from RendererFactoryBase. This class uses the Flyweight design pattern to share a single renderer between all presentation images (in actual fact, it allocates one renderer per thread, to minimize issues with concurrent rendering, since RendererFactoryBase is stateful). The intent behind RendererFactoryBase is to help minimize resources used by renderers. Because each PresentationImage essentially owns an IRenderer, if you're not careful, you could end up using a lot of unnecessary memory and system resources. This, of course, all depends on what resources, if any, your renderer requires. RendererFactoryBase also alleviates at least some of the burden on the developer to manage these kinds of issues. |
Note, also, that RendererFactoryBase and RendererBase don't have any direct relationship to BasicPresentationImage; you could simply remove the ExtensionOfAttribute from MyRendererFactory and use it directly from your own custom PresentationImage (for example, in the Implement a custom renderer example).