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); } } }
/// <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); }
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; }
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); }
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(); }
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); }
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; }
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; } } }
/// <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); }
/// <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; } }
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(); }
/// <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; } }