Example #1
0
        private static void DrawContent(CalloutStyle callout, SKCanvas canvas)
        {
            // Draw content
            if (callout.Content >= 0)
            {
                var strokeWidth = callout.StrokeWidth < 1 ? 1 : callout.StrokeWidth;
                var offsetX     = callout.ShadowWidth + strokeWidth * 2 + (callout.Padding.Left < callout.RectRadius * 0.5 ? callout.RectRadius * 0.5f : (float)callout.Padding.Left);
                var offsetY     = callout.ShadowWidth + strokeWidth * 2 + (callout.Padding.Top < callout.RectRadius * 0.5 ? callout.RectRadius * 0.5f : (float)callout.Padding.Top);

                switch (callout.ArrowAlignment)
                {
                case ArrowAlignment.Left:
                    offsetX += callout.ArrowHeight;
                    break;

                case ArrowAlignment.Top:
                    offsetY += callout.ArrowHeight;
                    break;
                }

                var offset = new SKPoint(offsetX, offsetY);

                if (callout.Type == CalloutType.Custom)
                {
                    // Get size of content
                    var bitmapInfo = BitmapHelper.LoadBitmap(BitmapRegistry.Instance.Get(callout.Content));

                    switch (bitmapInfo?.Type)
                    {
                    case BitmapType.Bitmap:
                        canvas.DrawImage(bitmapInfo.Bitmap, offset);
                        break;

                    case BitmapType.Sprite:
                        throw new Exception();

                    case BitmapType.Svg:
                        if (bitmapInfo.Svg != null)
                        {
                            using var skPaint = new SKPaint()
                                  {
                                      IsAntialias = true
                                  };
                            canvas.DrawPicture(bitmapInfo.Svg.Picture, offset, skPaint);
                        }

                        break;
                    }
                }
                else if (callout.Type == CalloutType.Single || callout.Type == CalloutType.Detail)
                {
                    var picture = (SKPicture)BitmapRegistry.Instance.Get(callout.Content);
                    using var skPaint = new SKPaint()
                          {
                              IsAntialias = true
                          };
                    canvas.DrawPicture(picture, offset, skPaint);
                }
            }
        }
Example #2
0
        /// <summary>
        /// Calc the size which is needed for the canvas
        /// </summary>
        /// <returns></returns>
        private static (double, double) CalcSize(CalloutStyle callout, double contentWidth, double contentHeight)
        {
            var strokeWidth = callout.StrokeWidth < 1 ? 1 : callout.StrokeWidth;
            // Add padding around the content
            var paddingLeft   = callout.Padding.Left < callout.RectRadius * 0.5 ? callout.RectRadius * 0.5 : callout.Padding.Left;
            var paddingTop    = callout.Padding.Top < callout.RectRadius * 0.5 ? callout.RectRadius * 0.5 : callout.Padding.Top;
            var paddingRight  = callout.Padding.Right < callout.RectRadius * 0.5 ? callout.RectRadius * 0.5 : callout.Padding.Right;
            var paddingBottom = callout.Padding.Bottom < callout.RectRadius * 0.5 ? callout.RectRadius * 0.5 : callout.Padding.Bottom;
            var width         = contentWidth + paddingLeft + paddingRight + 1;
            var height        = contentHeight + paddingTop + paddingBottom + 1;

            // Add length of arrow
            switch (callout.ArrowAlignment)
            {
            case ArrowAlignment.Bottom:
            case ArrowAlignment.Top:
                height += callout.ArrowHeight;
                break;

            case ArrowAlignment.Left:
            case ArrowAlignment.Right:
                width += callout.ArrowHeight;
                break;
            }

            // Add StrokeWidth to all sides
            width  += strokeWidth * 2;
            height += strokeWidth * 2;

            // Add shadow to all sides
            width  += callout.ShadowWidth * 2;
            height += callout.ShadowWidth * 2;

            return(width, height);
        }
Example #3
0
        public static void RenderCallout(CalloutStyle callout)
        {
            if (callout.Content < 0)
            {
                return;
            }

            // Get size of content
            double contentWidth  = 0;
            double contentHeight = 0;

            if (callout.Type == CalloutType.Custom)
            {
                var bitmapInfo = BitmapHelper.LoadBitmap(BitmapRegistry.Instance.Get(callout.Content));

                contentWidth  = bitmapInfo?.Width ?? 0;
                contentHeight = bitmapInfo?.Height ?? 0;
            }
            else if (callout.Type == CalloutType.Single || callout.Type == CalloutType.Detail)
            {
                var picture = (SKPicture)BitmapRegistry.Instance.Get(callout.Content);

                contentWidth  = picture.CullRect.Width;
                contentHeight = picture.CullRect.Height;
            }

            (var width, var height) = CalcSize(callout, contentWidth, contentHeight);

            // Create a canvas for drawing
            using (var rec = new SKPictureRecorder())
                using (var canvas = rec.BeginRecording(new SKRect(0, 0, (float)width, (float)height)))
                {
                    (var path, var center) = CreateCalloutPath(callout, contentWidth, contentHeight);
                    // Now move Offset to the position of the arrow
                    callout.Offset = new Offset(-center.X, -center.Y);

                    // Draw path for bubble
                    DrawCallout(callout, canvas, path);

                    // Draw content
                    DrawContent(callout, canvas);

                    // Create SKPicture from canvas
                    var picture = rec.EndRecording();

                    if (callout.BitmapId < 0)
                    {
                        callout.BitmapId = BitmapRegistry.Instance.Register(picture);
                    }
                    else
                    {
                        BitmapRegistry.Instance.Set(callout.BitmapId, picture);
                    }
                }

            callout.Invalidated = false;
        }
Example #4
0
        private static void DrawCallout(CalloutStyle callout, SKCanvas canvas, SKPath path)
        {
            using var shadow = new SKPaint { IsAntialias = true, Style = SKPaintStyle.Stroke, StrokeWidth = 1.5f, Color = SKColors.Gray, MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, callout.ShadowWidth) };
            using var fill   = new SKPaint { IsAntialias = true, Style = SKPaintStyle.Fill, Color = callout.BackgroundColor.ToSkia() };
            using var stroke = new SKPaint { IsAntialias = true, Style = SKPaintStyle.Stroke, Color = callout.Color.ToSkia(), StrokeWidth = callout.StrokeWidth };

            canvas.DrawPath(path, shadow);
            canvas.DrawPath(path, fill);
            canvas.DrawPath(path, stroke);
        }
Example #5
0
        public static void Draw(SKCanvas canvas, IReadOnlyViewport viewport,
                                float opacity, Point destination, CalloutStyle calloutStyle)
        {
            if (calloutStyle.BitmapId < 0 || calloutStyle.Invalidated)
            {
                if (calloutStyle.Content < 0 && calloutStyle.Type == CalloutType.Custom)
                {
                    return;
                }

                if (calloutStyle.Invalidated)
                {
                    UpdateContent(calloutStyle);
                }

                RenderCallout(calloutStyle);
            }

            // Now we have the complete callout rendered, so we could draw it
            if (calloutStyle.BitmapId < 0)
            {
                return;
            }

            var picture = (SKPicture)BitmapRegistry.Instance.Get(calloutStyle.BitmapId);

            // Calc offset (relative or absolute)
            var symbolOffsetX = calloutStyle.SymbolOffset.IsRelative ? picture.CullRect.Width * (float)calloutStyle.SymbolOffset.X : (float)calloutStyle.SymbolOffset.X;
            var symbolOffsetY = calloutStyle.SymbolOffset.IsRelative ? picture.CullRect.Height * (float)calloutStyle.SymbolOffset.Y : (float)calloutStyle.SymbolOffset.Y;

            var rotation = (float)calloutStyle.SymbolRotation;

            if (calloutStyle.RotateWithMap)
            {
                rotation += (float)viewport.Rotation;
            }

            // Save state of the canvas, so we could move and rotate the canvas
            canvas.Save();

            // Move 0/0 to the Anchor point of Callout
            canvas.Translate((float)destination.X - symbolOffsetX, (float)destination.Y - symbolOffsetY);
            canvas.Scale((float)calloutStyle.SymbolScale, (float)calloutStyle.SymbolScale);

            // 0/0 are assumed at center of image, but Picture has 0/0 at left top position
            canvas.RotateDegrees(rotation);
            canvas.Translate((float)calloutStyle.Offset.X, (float)calloutStyle.Offset.Y);

            canvas.DrawPicture(picture, new SKPaint()
            {
                IsAntialias = true
            });

            canvas.Restore();
        }
Example #6
0
        public static void Draw(SKCanvas canvas, IReadOnlyViewport viewport, SymbolCache symbolCache,
                                float opacity, Point destination, CalloutStyle calloutStyle)
        {
            if (calloutStyle.BitmapId < 0 || calloutStyle.Invalidated)
            {
                if (calloutStyle.Content < 0 && calloutStyle.Type == CalloutType.Custom)
                {
                    return;
                }

                if (calloutStyle.Invalidated)
                {
                    UpdateContent(calloutStyle);
                }

                RenderCallout(calloutStyle);
            }
            // Reuse ImageStyleRenderer because the only thing we need to do is to draw an image
            ImageStyleRenderer.Draw(canvas, calloutStyle, destination, symbolCache, opacity, (float)viewport.Rotation);
        }
Example #7
0
        public static void RenderCallout(CalloutStyle callout)
        {
            if (callout.Content < 0)
            {
                return;
            }

            // Get size of content
            var bitmapInfo = BitmapHelper.LoadBitmap(BitmapRegistry.Instance.Get(callout.Content));

            double contentWidth  = bitmapInfo.Width;
            double contentHeight = bitmapInfo.Height;

            (var width, var height) = CalcSize(callout, contentWidth, contentHeight);

            // Create a canvas for drawing
            var info = new SKImageInfo((int)width, (int)height);

            using (var surface = SKSurface.Create(info))
            {
                var canvas = surface.Canvas;

                (var path, var center) = CreateCalloutPath(callout, contentWidth, contentHeight);
                // Now move SymbolOffset to the position of the arrow
                callout.SymbolOffset = new Offset(callout.Offset.X + (width * 0.5 - center.X), callout.Offset.Y - (height * 0.5 - center.Y));

                // Draw path for bubble
                DrawCallout(callout, canvas, path);

                // Draw content
                DrawContent(callout, canvas, bitmapInfo);

                // Create image from canvas
                var image = surface.Snapshot();
                var data  = image.Encode(SKEncodedImageFormat.Png, 100);

                callout.BitmapId = BitmapRegistry.Instance.Register(data.AsStream(true));
            }

            callout.Invalidated = false;
        }
Example #8
0
        private static void DrawContent(CalloutStyle callout, SKCanvas canvas, BitmapInfo bitmapInfo)
        {
            // Draw content
            if (callout.Content >= 0)
            {
                var strokeWidth = callout.StrokeWidth < 1 ? 1 : callout.StrokeWidth;
                var offsetX     = callout.ShadowWidth + strokeWidth * 2 + (callout.Padding.Left < callout.RectRadius * 0.5 ? callout.RectRadius * 0.5f : (float)callout.Padding.Left);
                var offsetY     = callout.ShadowWidth + strokeWidth * 2 + (callout.Padding.Top < callout.RectRadius * 0.5 ? callout.RectRadius * 0.5f : (float)callout.Padding.Top);

                switch (callout.ArrowAlignment)
                {
                case ArrowAlignment.Left:
                    offsetX += callout.ArrowHeight;
                    break;

                case ArrowAlignment.Top:
                    offsetY += callout.ArrowHeight;
                    break;
                }

                var offset = new SKPoint(offsetX, offsetY);

                switch (bitmapInfo.Type)
                {
                case BitmapType.Bitmap:
                    canvas.DrawImage(bitmapInfo.Bitmap, offset);
                    break;

                case BitmapType.Sprite:
                    throw new Exception();

                case BitmapType.Svg:
                    canvas.DrawPicture(bitmapInfo.Svg.Picture, offset);
                    break;
                }
            }
        }
Example #9
0
        /// <summary>
        /// Update path
        /// </summary>
        private static (SKPath, SKPoint) CreateCalloutPath(CalloutStyle callout, double contentWidth, double contentHeight)
        {
            var strokeWidth   = callout.StrokeWidth < 1 ? 1 : callout.StrokeWidth;
            var paddingLeft   = callout.Padding.Left < callout.RectRadius * 0.5 ? callout.RectRadius * 0.5 : callout.Padding.Left;
            var paddingTop    = callout.Padding.Top < callout.RectRadius * 0.5 ? callout.RectRadius * 0.5 : callout.Padding.Top;
            var paddingRight  = callout.Padding.Right < callout.RectRadius * 0.5 ? callout.RectRadius * 0.5 : callout.Padding.Right;
            var paddingBottom = callout.Padding.Bottom < callout.RectRadius * 0.5 ? callout.RectRadius * 0.5 : callout.Padding.Bottom;
            var width         = (float)contentWidth + (float)paddingLeft + (float)paddingRight;
            var height        = (float)contentHeight + (float)paddingTop + (float)paddingBottom;
            var halfWidth     = width * callout.ArrowPosition;
            var halfHeight    = height * callout.ArrowPosition;
            var bottom        = height + callout.ShadowWidth + strokeWidth * 2;
            var left          = callout.ShadowWidth + strokeWidth;
            var top           = callout.ShadowWidth + strokeWidth;
            var right         = width + callout.ShadowWidth + strokeWidth * 2;
            var start         = new SKPoint();
            var center        = new SKPoint();
            var end           = new SKPoint();

            // Check, if we are to near at corners
            if (halfWidth - callout.ArrowWidth * 0.5f - left < callout.RectRadius)
            {
                halfWidth = callout.ArrowWidth * 0.5f + left + callout.RectRadius;
            }
            else if (halfWidth + callout.ArrowWidth * 0.5f > width - callout.RectRadius)
            {
                halfWidth = width - callout.ArrowWidth * 0.5f - callout.RectRadius;
            }
            if (halfHeight - callout.ArrowWidth * 0.5f - top < callout.RectRadius)
            {
                halfHeight = callout.ArrowWidth * 0.5f + top + callout.RectRadius;
            }
            else if (halfHeight + callout.ArrowWidth * 0.5f > height - callout.RectRadius)
            {
                halfHeight = height - callout.ArrowWidth * 0.5f - callout.RectRadius;
            }

            switch (callout.ArrowAlignment)
            {
            case ArrowAlignment.Bottom:
                start  = new SKPoint(halfWidth + callout.ArrowWidth * 0.5f, bottom);
                center = new SKPoint(halfWidth, bottom + callout.ArrowHeight);
                end    = new SKPoint(halfWidth - callout.ArrowWidth * 0.5f, bottom);
                break;

            case ArrowAlignment.Top:
                top    += callout.ArrowHeight;
                bottom += callout.ArrowHeight;
                start   = new SKPoint(halfWidth - callout.ArrowWidth * 0.5f, top);
                center  = new SKPoint(halfWidth, top - callout.ArrowHeight);
                end     = new SKPoint(halfWidth + callout.ArrowWidth * 0.5f, top);
                break;

            case ArrowAlignment.Left:
                left  += callout.ArrowHeight;
                right += callout.ArrowHeight;
                start  = new SKPoint(left, halfHeight + callout.ArrowWidth * 0.5f);
                center = new SKPoint(left - callout.ArrowHeight, halfHeight);
                end    = new SKPoint(left, halfHeight - callout.ArrowWidth * 0.5f);
                break;

            case ArrowAlignment.Right:
                start  = new SKPoint(right, halfHeight - callout.ArrowWidth * 0.5f);
                center = new SKPoint(right + callout.ArrowHeight, halfHeight);
                end    = new SKPoint(right, halfHeight + callout.ArrowWidth * 0.5f);
                break;
            }

            // Create path
            var path = new SKPath();

            // Move to start point at left/top
            path.MoveTo(left + callout.RectRadius, top);

            // Top horizontal line
            if (callout.ArrowAlignment == ArrowAlignment.Top)
            {
                DrawArrow(path, start, center, end);
            }

            // Top right arc
            path.ArcTo(new SKRect(right - callout.RectRadius, top, right, top + callout.RectRadius), 270, 90, false);

            // Right vertical line
            if (callout.ArrowAlignment == ArrowAlignment.Right)
            {
                DrawArrow(path, start, center, end);
            }

            // Bottom right arc
            path.ArcTo(new SKRect(right - callout.RectRadius, bottom - callout.RectRadius, right, bottom), 0, 90, false);

            // Bottom horizontal line
            if (callout.ArrowAlignment == ArrowAlignment.Bottom)
            {
                DrawArrow(path, start, center, end);
            }

            // Bottom left arc
            path.ArcTo(new SKRect(left, bottom - callout.RectRadius, left + callout.RectRadius, bottom), 90, 90, false);

            // Left vertical line
            if (callout.ArrowAlignment == ArrowAlignment.Left)
            {
                DrawArrow(path, start, center, end);
            }

            // Top left arc
            path.ArcTo(new SKRect(left, top, left + callout.RectRadius, top + callout.RectRadius), 180, 90, false);

            path.Close();

            return(path, center);
        }
Example #10
0
        /// <summary>
        /// Update content for single and detail
        /// </summary>
        public static void UpdateContent(CalloutStyle callout)
        {
            if (callout.Type == CalloutType.Custom)
            {
                return;
            }

            if (callout.Title == null)
            {
                callout.Content = -1;
                return;
            }

            var styleSubtitle     = new Topten.RichTextKit.Style();
            var styleTitle        = new Topten.RichTextKit.Style();
            var textBlockTitle    = new TextBlock();
            var textBlockSubtitle = new TextBlock();

            if (callout.Type == CalloutType.Detail)
            {
                styleSubtitle.FontFamily = callout.SubtitleFont.FontFamily;
                styleSubtitle.FontSize   = (float)callout.SubtitleFont.Size;
                styleSubtitle.FontItalic = callout.SubtitleFont.Italic;
                styleSubtitle.FontWeight = callout.SubtitleFont.Bold ? 700 : 400;
                styleSubtitle.TextColor  = callout.SubtitleFontColor.ToSkia();

                textBlockSubtitle.AddText(callout.Subtitle, styleSubtitle);
                textBlockSubtitle.Alignment = callout.SubtitleTextAlignment.ToRichTextKit();
            }
            styleTitle.FontFamily = callout.TitleFont.FontFamily;
            styleTitle.FontSize   = (float)callout.TitleFont.Size;
            styleTitle.FontItalic = callout.TitleFont.Italic;
            styleTitle.FontWeight = callout.TitleFont.Bold ? 700 : 400;
            styleTitle.TextColor  = callout.TitleFontColor.ToSkia();

            textBlockTitle.Alignment = callout.TitleTextAlignment.ToRichTextKit();
            textBlockTitle.AddText(callout.Title, styleTitle);

            textBlockTitle.MaxWidth = textBlockSubtitle.MaxWidth = (float)callout.MaxWidth;
            // Layout TextBlocks
            textBlockTitle.Layout();
            textBlockSubtitle.Layout();
            // Get sizes
            var width  = Math.Max(textBlockTitle.MeasuredWidth, textBlockSubtitle.MeasuredWidth);
            var height = textBlockTitle.MeasuredHeight + (callout.Type == CalloutType.Detail ? textBlockSubtitle.MeasuredHeight + (float)callout.Spacing : 0f);

            // Now we have the correct width, so make a new layout cycle for text alignment
            textBlockTitle.MaxWidth = textBlockSubtitle.MaxWidth = width;
            textBlockTitle.Layout();
            textBlockSubtitle.Layout();
            // Create bitmap from TextBlock
            using (var rec = new SKPictureRecorder())
                using (var canvas = rec.BeginRecording(new SKRect(0, 0, width, height)))
                {
                    // Draw text to canvas
                    textBlockTitle.Paint(canvas, new TextPaintOptions()
                    {
                        IsAntialias = true
                    });
                    if (callout.Type == CalloutType.Detail)
                    {
                        textBlockSubtitle.Paint(canvas, new SKPoint(0, textBlockTitle.MeasuredHeight + (float)callout.Spacing), new TextPaintOptions()
                        {
                            IsAntialias = true
                        });
                    }
                    // Create a SKPicture from canvas
                    var picture = rec.EndRecording();
                    if (callout.InternalContent >= 0)
                    {
                        BitmapRegistry.Instance.Set(callout.InternalContent, picture);
                    }
                    else
                    {
                        callout.InternalContent = BitmapRegistry.Instance.Register(picture);
                    }
                    callout.Content = callout.InternalContent;
                }
        }
Example #11
0
        public static void Draw(SKCanvas canvas, IReadOnlyViewport viewport,
                                float opacity, double x, double y, CalloutStyle calloutStyle)
        {
            if (calloutStyle.BitmapId < 0 || calloutStyle.Invalidated)
            {
                if (calloutStyle.Content < 0 && calloutStyle.Type == CalloutType.Custom)
                {
                    return;
                }

                if (calloutStyle.Invalidated)
                {
                    UpdateContent(calloutStyle);
                }

                RenderCallout(calloutStyle);
            }

            // Now we have the complete callout rendered, so we could draw it
            if (calloutStyle.BitmapId < 0)
            {
                return;
            }

            var picture = (SKPicture)BitmapRegistry.Instance.Get(calloutStyle.BitmapId);

            // Calc offset (relative or absolute)
            MPoint symbolOffset = calloutStyle.SymbolOffset.ToPoint();

            if (calloutStyle.SymbolOffset.IsRelative)
            {
                symbolOffset.X *= picture.CullRect.Width;
                symbolOffset.Y *= picture.CullRect.Height;
            }

            var rotation = (float)calloutStyle.SymbolRotation;

            if (viewport.Rotation != 0)
            {
                if (calloutStyle.RotateWithMap)
                {
                    rotation += (float)viewport.Rotation;
                }
                if (calloutStyle.SymbolOffsetRotatesWithMap)
                {
                    symbolOffset = symbolOffset.Rotate(-viewport.Rotation);
                }
            }

            // Save state of the canvas, so we could move and rotate the canvas
            canvas.Save();

            // Move 0/0 to the Anchor point of Callout
            canvas.Translate((float)(x - symbolOffset.X), (float)(y - symbolOffset.Y));
            canvas.Scale((float)calloutStyle.SymbolScale, (float)calloutStyle.SymbolScale);

            // 0/0 are assumed at center of image, but Picture has 0/0 at left top position
            canvas.RotateDegrees(rotation);
            canvas.Translate((float)calloutStyle.Offset.X, (float)calloutStyle.Offset.Y);

            using var skPaint = new SKPaint()
                  {
                      IsAntialias = true
                  };
            canvas.DrawPicture(picture, skPaint);

            canvas.Restore();
        }
Example #12
0
        /// <summary>
        /// Update content for single and detail
        /// </summary>
        public static void UpdateContent(CalloutStyle callout)
        {
            if (callout.Type == CalloutType.Custom)
            {
                return;
            }

            if (callout.Title == null)
            {
                return;
            }

            var _styleSubtitle     = new Topten.RichTextKit.Style();
            var _styleTitle        = new Topten.RichTextKit.Style();
            var _textBlockTitle    = new TextBlock();
            var _textBlockSubtitle = new TextBlock();

            if (callout.Type == CalloutType.Detail)
            {
                _styleSubtitle.FontFamily = callout.SubtitleFont.FontFamily;
                _styleSubtitle.FontSize   = (float)callout.SubtitleFont.Size;
                _styleSubtitle.FontItalic = callout.SubtitleFont.Italic;
                _styleSubtitle.FontWeight = callout.SubtitleFont.Bold ? 700 : 400;
                _styleSubtitle.TextColor  = callout.SubtitleFontColor.ToSkia();

                _textBlockSubtitle.AddText(callout.Subtitle, _styleSubtitle);
                _textBlockSubtitle.Alignment = callout.SubtitleTextAlignment.ToRichTextKit();
            }
            _styleTitle.FontFamily = callout.TitleFont.FontFamily;
            _styleTitle.FontSize   = (float)callout.TitleFont.Size;
            _styleTitle.FontItalic = callout.TitleFont.Italic;
            _styleTitle.FontWeight = callout.TitleFont.Bold ? 700 : 400;
            _styleTitle.TextColor  = callout.TitleFontColor.ToSkia();

            _textBlockTitle.Alignment = callout.TitleTextAlignment.ToRichTextKit();
            _textBlockTitle.AddText(callout.Title, _styleTitle);

            _textBlockTitle.MaxWidth = _textBlockSubtitle.MaxWidth = (float)callout.MaxWidth;
            // Layout TextBlocks
            _textBlockTitle.Layout();
            _textBlockSubtitle.Layout();
            // Get sizes
            var width  = Math.Max(_textBlockTitle.MeasuredWidth, _textBlockSubtitle.MeasuredWidth);
            var height = _textBlockTitle.MeasuredHeight + (callout.Type == CalloutType.Detail ? _textBlockSubtitle.MeasuredHeight + callout.Spacing : 0);

            // Now we have the correct width, so make a new layout cycle for text alignment
            _textBlockTitle.MaxWidth = _textBlockSubtitle.MaxWidth = width;
            _textBlockTitle.Layout();
            _textBlockSubtitle.Layout();
            // Create bitmap from TextBlock
            var info = new SKImageInfo((int)width, (int)height, SKImageInfo.PlatformColorType, SKAlphaType.Premul);

            using (var surface = SKSurface.Create(info))
            {
                var canvas    = surface.Canvas;
                var memStream = new MemoryStream();

                canvas.Clear(SKColors.Transparent);
                // surface.Canvas.Scale(DeviceDpi / 96.0f);
                _textBlockTitle.Paint(canvas, new TextPaintOptions()
                {
                    IsAntialias = true
                });
                _textBlockSubtitle.Paint(canvas, new SKPoint(0, _textBlockTitle.MeasuredHeight + (float)callout.Spacing), new TextPaintOptions()
                {
                    IsAntialias = true
                });
                // Create image from canvas
                var image = surface.Snapshot();
                var data  = image.Encode(SKEncodedImageFormat.Png, 100);
                if (callout.InternalContent >= 0)
                {
                    BitmapRegistry.Instance.Set(callout.InternalContent, data.AsStream(true));
                }
                else
                {
                    callout.InternalContent = BitmapRegistry.Instance.Register(data.AsStream(true));
                }
                callout.Content = callout.InternalContent;
            }
        }