Skiasharp: [BUG] SKRegion.Intersects() causes Access Violation and instantly crashes program

Created on 29 Jan 2019  路  8Comments  路  Source: mono/SkiaSharp

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

  • Version with issue: 1.68.0
  • Last known good version: unknown if this ever worked
  • IDE: Visual Studio Code
  • Platform Target Frameworks:

    • Windows Classic: Windows 10

  • Target Devices:

    • PC

Reproduction Link

https://github.com/vexx32/PSWordCloud/tree/Cmdlet/Cmdlet

The following PowerShell module attempts to use SkiaSharp to draw a word cloud.

  1. Use dotnet publish to compile the module DLL
  2. Open a PowerShell or PowerShell Core console
  3. Execute Import-Module .\PSWordCloud.psm1 from a PowerShell console with current location at this folder in the repo: https://github.com/vexx32/PSWordCloud/tree/Cmdlet/Cmdlet
  4. Execute the following command in PowerShell and watch it _burn_ :wink:
"Hello darkness my old friend","I've come to talk with you again" | New-WordCloud -Path $env:TEMP\test.svg
  1. Observe the _entire PowerShell application_ crash due to the access violation without warning.

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.

type-bug

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

All 8 comments

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);

https://docs.microsoft.com/en-us/dotnet/api/skiasharp.skrecti.round?view=skiasharp-1.68.0#SkiaSharp_SKRectI_Round_SkiaSharp_SKRect_

@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!

Was this page helpful?
0 / 5 - 0 ratings