Description
I haven't been able to pin down the _exact_ cause, but from stepping through my code I can see that the instant the code hits a call to SKRegion's Intersects() method, having been passed an SKRectI to check intersection against, an access violation is reported and the entire program crashes.
Code
using System;
using SkiaSharp;
namespace Test
{
static class Extensions
{
public static SKRectI ToSKRectI(this SKRect rect)
{
return new SKRectI(
(int)Math.Round(rect.Left),
(int)Math.Round(rect.Top),
(int)Math.Round(rect.Right),
(int)Math.Round(rect.Bottom));
}
}
class Program
{
private static SKSizeI _imageSize { get; } = new SKSizeI(4096, 2304);
private static string[] _wordList { get; } = new[] { "hello", "darkness", "my", "old", "friend" };
private static Random _random = new Random();
private static string _filePath = @"C:\Test\output.svg";
static void Main(string[] args)
{
SKPath wordPath = null;
Console.WriteLine("Image size is {0}", _imageSize.ToString());
try
{
using (SKRegion occupiedSpace = new SKRegion())
using (SKPaint brush = new SKPaint())
using (SKFileWStream streamWriter = new SKFileWStream(_filePath))
using (SKXmlStreamWriter xmlWriter = new SKXmlStreamWriter(streamWriter))
using (SKCanvas canvas = SKSvgCanvas.Create(SKRect.Create(_imageSize), xmlWriter))
{
brush.Color = SKColors.Black;
foreach (string word in _wordList)
{
bool next = false;
brush.TextSize = _imageSize.Height / (10 * (Array.IndexOf(_wordList, word) + 1));
for (float x = 0; x <= _imageSize.Width; x += (float)_random.NextDouble() * _imageSize.Width / 100)
{
for (float y = 0; y <= _imageSize.Width; y += (float)_random.NextDouble() * _imageSize.Width / 100)
{
wordPath = brush.GetTextPath(word, x, y);
if (!occupiedSpace.Intersects(wordPath.Bounds.ToSKRectI()))
{
Console.WriteLine("Found space for word {0} at {1}", word, wordPath.Bounds.Location);
canvas.DrawPath(wordPath, brush);
using (SKRegion wordRegion = new SKRegion())
{
wordRegion.SetRect(SKRectI.Create(_imageSize));
wordRegion.SetPath(wordPath);
occupiedSpace.Op(wordRegion, SKRegionOperation.Union);
next = true;
}
}
if (next) break;
}
if (next) break;
}
}
// All words written, tidy and ensure file is saved
canvas.Flush();
streamWriter.Flush();
}
}
finally
{
wordPath?.Dispose();
}
Console.WriteLine("Drawing complete; press Enter to exit");
// Wait for exit
Console.ReadLine();
Environment.Exit(0);
}
}
}
Expected Behavior
Program will loop through the word list, attempting to draw each in turn. Console should output found positions as it progresses, and eventually an SVG file would be output at the specified location.
Actual Behavior
Program progresses through the first loop and then instantly closes silently. If the application is debugged, an access violation will be reported when the Intersects() method is called.
Basic Information
Reproduction Link
https://github.com/vexx32/PSWordCloud/tree/Cmdlet/Cmdlet
The following PowerShell module attempts to use SkiaSharp to draw a word cloud.
dotnet publish to compile the module DLLImport-Module .\PSWordCloud.psm1 from a PowerShell console with current location at this folder in the repo: https://github.com/vexx32/PSWordCloud/tree/Cmdlet/Cmdlet"Hello darkness my old friend","I've come to talk with you again" | New-WordCloud -Path $env:TEMP\test.svg
Elsewhere in the repo you can find working code (check the latest release) written in PowerShell utilising System.Drawing for the same task and example pictures and commands to do the same with the published version of the module.
Thanks for the detail. I will investigate and get this fixed as soon as possible. Just been flying around and talking at our local #MSIgniteTheTour
Thanks, much appreciated! 馃槃
Have fun! ^^
Hehe, I'll try.
I had a look and I believe that either Google or I changed the signature of the Intersects method on the native side. It was expecting a region, and I gave it a rect. This should not have happened and I am creating more tests so that this will never happen again.
This will be fixed in the next release, but as a temporary workaround, you can just create a region:
// the temporary region - you can cache this for performance
var rectRegion = new SKRegion();
// set the rect
rectRegion.SetRect(wordPath.Bounds.ToSKRectI());
// do the test
if (!occupiedSpace.Intersects(rectRegion))
...
@mattleibow I'll give that a shot and see if it works. Thank you!
I also just wanted to let you know about some cool methods that you can use - not as cool as the extension method, but we do have Round, Ceiling, Floor and Truncate that can be used like this:
SKRect floatRect = ...;
SKRectI intRect = SKRectI.Round(floatRect);
@mattleibow Thank you for the prompt response!
I recompiled my original version just using regions instead of rectangles and it does seem much more stable.
Interestingly, this doesn't actually seem to detect intersections all that well. (example)
I'll have to see if I can figure out whether that's my own code here or the Intersects() method once again... similar code does work with the System.Drawing intersection testing methods, though. I'm wondering if the SKRegion.SetPath() and SKRegion.Op(wordRegion, SKRegionOperation.Union) lines are behaving as they ought, but I'm not sure exactly how to test those... Have to dig a little and see what I can see.
Once again, thank you! Small progress is progress. 馃槃
Good to hear things are just exploding. If you get more information, just open a new issue with any deets!
Most helpful comment
Thanks for the detail. I will investigate and get this fixed as soon as possible. Just been flying around and talking at our local #MSIgniteTheTour