void canvas_Draw(CanvasControl sender, CanvasDrawEventArgs args) { // We animate the source image by changing which character is highlighted in yellow. // Therefore there can be two changed regions: the highlighted character has changed from // white to yellow, while the previous highlight has changed from yellow back to white. // Look up the bounds of the two changed characters. var highlightBounds = GetCharacterBounds(highlightedCharacter); var previousBounds = GetCharacterBounds(previousHighlight); // Tell our effects that the highlighted character region has changed. blurEffect.InvalidateSourceRectangle(args.DrawingSession, 0, highlightBounds); shadowEffect.InvalidateSourceRectangle(args.DrawingSession, 0, highlightBounds); // Query what part of the output image will change as a result. var highlightInvalidRects = compositeEffect.GetInvalidRectangles(args.DrawingSession); var highlightInvalidUnion = GetRectangleUnion(highlightInvalidRects); // Also tell the effects about the previously highlighted character. blurEffect.InvalidateSourceRectangle(args.DrawingSession, 0, previousBounds); shadowEffect.InvalidateSourceRectangle(args.DrawingSession, 0, previousBounds); // Query the output region again. This will return a superset of highlightInvalidRects, // as it now accounts for the change to previousBounds as well as highlightBounds. var totalInvalidRects = compositeEffect.GetInvalidRectangles(args.DrawingSession); var totalInvalidUnion = GetRectangleUnion(totalInvalidRects); // We can also look up in the opposite direction: given that we are going to redraw only // the totalInvalidUnion area, what portion of each source image is needed to do that? // When using filter kernels like blur, this will be larger than just highlightBounds+previousBounds. var requiredSourceRects = compositeEffect.GetRequiredSourceRectangles(args.DrawingSession, totalInvalidUnion, new ICanvasEffect[] { blurEffect, shadowEffect }, new uint[] { 0, 0 }, new Rect[2] { sourceRenderTarget.Bounds, sourceRenderTarget.Bounds }); // How about if we were going to redraw only highlightBounds, skipping previousBounds? // (we don't actually do this, but do display what source regions it would require). var blurSourceRect = compositeEffect.GetRequiredSourceRectangle(args.DrawingSession, highlightInvalidUnion, blurEffect, 0, sourceRenderTarget.Bounds); var shadowSourceRect = compositeEffect.GetRequiredSourceRectangle(args.DrawingSession, highlightInvalidUnion, shadowEffect, 0, sourceRenderTarget.Bounds); // Draw text into the source rendertarget. using (var drawingSession = sourceRenderTarget.CreateDrawingSession()) { // To make sure the correct requiredSourceRects were reported, we clear the background outside // that region to magenta. If everything is working correctly, this should never be picked up by // effect drawing, as we only leave magenta in the areas we don't expect the effects to read from. drawingSession.Clear(Colors.Magenta); // Clear the requiredSourceRects to transparent. drawingSession.Blend = CanvasBlend.Copy; foreach (var r in requiredSourceRects) { drawingSession.FillRectangle(r, Colors.Transparent); } // Draw the text characters. drawingSession.Blend = CanvasBlend.SourceOver; for (int i = 0; i < characterLayouts.Count; i++) { var color = (i == highlightedCharacter) ? Colors.Yellow : Colors.White; drawingSession.DrawTextLayout(characterLayouts[i], characterPositions[i], color); } } // Draw the effect graph (which reads from sourceRenderTarget) into destRenderTarget. using (var drawingSession = destRenderTarget.CreateDrawingSession()) { // Slightly darken down the existing contents of the output rendertarget. This causes everything // except totalInvalidUnion to gradually fade out, so we can see which areas are getting redrawn. // If this FillRectangle was removed, the result of redrawing only the changed region would be // identical to if we redrew the whole thing every time (by removing the CreateLayer call). drawingSession.FillRectangle(destRenderTarget.Bounds, Color.FromArgb(16, 0, 0, 0)); // Clip our drawing to the totalInvalidUnion rectangle, // which should be the only part of the output that has changed. using (var layer = drawingSession.CreateLayer(1, totalInvalidUnion)) { drawingSession.Clear(Colors.CornflowerBlue); drawingSession.DrawImage(compositeEffect, totalInvalidUnion, totalInvalidUnion); } } if (!ThumbnailGenerator.IsDrawingThumbnail) { args.DrawingSession.Transform = Matrix3x2.CreateTranslation(gap, gap); // Display sourceRenderTarget. args.DrawingSession.DrawImage(sourceRenderTarget); // Display highlightBounds, blurSourceRect, and shadowSourceRect. args.DrawingSession.DrawRectangle(highlightBounds, Colors.Gray); args.DrawingSession.DrawRectangle(blurSourceRect, Colors.Blue); args.DrawingSession.DrawRectangle(shadowSourceRect, Colors.Blue); } args.DrawingSession.Transform = Matrix3x2.CreateTranslation(gap, gap * 2 + height); // Display destRenderTarget. args.DrawingSession.DrawImage(destRenderTarget); // Display highlightInvalidRects. foreach (var i in highlightInvalidRects) { args.DrawingSession.DrawRectangle(i, Colors.DarkBlue); } previousHighlight = highlightedCharacter; // When generating thumbnails, repeat the first draw a bunch of times to reach a more interesting image. if (ThumbnailGenerator.IsDrawingThumbnail && highlightedCharacter < characterLayouts.Count * 5 / 6) { highlightedCharacter++; canvas_Draw(sender, args); } }