For a game implementation, I have a very large overview map with multiple layers (namely base-map / units / highlight & info / fov-shading) of which the first is CPU-intensive to generate. I have implemented a caching mechanism in which the base layer is drawn once and held in system memory, then drawn on demand by the OnPaint method at the current scale. The subsequent layers are then drawn for the current clipping region:
private bool _isCalculating = false;
/// <inheritdoc/>
protected override async void OnPaint(PaintEventArgs e) {
if(e==null) throw new ArgumentNullException("e");
if (DesignMode) { e.Graphics.FillRectangle(Brushes.Gray, ClientRectangle); return; }
if (_isCalculating) return;
if (BufferCache == null) {
_isCalculating = true;
BufferCache = await PaintedCacheBufferAsync("Cache");
_isCalculating = false;
this.Refresh();
} else {
var mapScale = DataContext.Scales[ScaleIndex];
var location = AutoScrollPosition + Margin.OffsetSize();
BufferCache.Render(BufferMap, mapScale/CacheScale, location);
BufferMap.Render(BufferUnits, mapScale, location, DataContext.Model.PaintUnits);
BufferUnits.Render(BufferBack, mapScale, location, DataContext.Model.PaintHighlight);
BufferShading.Render(BufferBack, mapScale, location, DataContext.Model.PaintShading);
e.Graphics.DrawImageUnscaled(BufferBack, Point.Empty);
}
}
Though it works and is reasonably performant, I am not completely happy with the above. In particular, comments are welcome on:
- My obvious assumption about the thread-safety of
isCalculating
; and - The overall structure of the
OnPaint
method.
For completeness, here are the Extension Methods on Image and Bitmap that allow the code above to be fairly neat:
/// <summary>Renders the supplied <see cref="Image"/> <paramref name="source"/> to the specified
/// <see cref="Image"/> <paramref name="target"/>, scaled and translated.</summary>
/// <param name="source">Source <see cref="Image"/> to be rendered.</param>
/// <param name="target">Target <see cref="Graphics"/> to be rendered to.</param>
/// <param name="scale">Scale at which the source should be drawn</param>
/// <param name="point"><see cref="Point"/> at which to render the <paramref name="source"/>.</param>
public static void Render(this Image source, Image target, float scale, Point point) {
Traces.Paint.Trace("Render source to {0}:",target.Tag);
using (var gBuffer = Graphics.FromImage(target)) source.Render(gBuffer, scale, point);
}
/// <summary>Renders the supplied <see cref="Image"/> <paramref name="source"/> to the specified
/// <see cref="Graphics"/> <paramref name="target"/>, scaled and translated.</summary>
/// <param name="source">Source <see cref="Image"/> to be rendered.</param>
/// <param name="target">Target <see cref="Graphics"/> to be rendered to.</param>
/// <param name="scale">Scale at which the source should be drawn</param>
/// <param name="point"><see cref="Point"/> at which to render the <paramref name="source"/>.</param>
public static void Render(this Image source, Graphics target, float scale, Point point) {
target.Clear(Color.Black);
target.PageUnit = GraphicsUnit.Pixel;
target.TranslateTransform(point.X, point.Y);
target.ScaleTransform(scale, scale);
target.InterpolationMode = InterpolationMode.HighQualityBicubic;
target.DrawImage(source, Point.Empty);
}
/// <summary>Renders, scaled and translated, first the supplied <see cref="Image"/> <paramref name="source"/> to the specified
/// <see cref="Graphics"/> <paramref name="target"/> and then the specified <paramref name="action"/>.</summary>
/// <param name="source">Source <see cref="Image"/> to be rendered.</param>
/// <param name="target">Target <see cref="Graphics"/> to be rendered to.</param>
/// <param name="scale">Scale at which the source should be drawn</param>
/// <param name="point"><see cref="Point"/> at which to render the <paramref name="source"/>.</param>
/// <param name="action">The drawing action to be overlain to target.</param>
public static void Render(this Image source, Image target, float scale, Point point, Action<Graphics> action) {
Traces.Paint.Trace("Render cache to {0}:",target.Tag);
using (var gTarget = Graphics.FromImage(target)) {
gTarget.DrawImageUnscaled(source, Point.Empty);;
gTarget.PageUnit = GraphicsUnit.Pixel;
gTarget.TranslateTransform(point.X, point.Y);
gTarget.ScaleTransform(scale,scale);
action(gTarget);
}
}