Search code examples
mauiandroid-canvasandroid-bitmapskiasharp

Maui.CSharpMath.SkiaSharp Canvas trying to draw too large bitmap


I am using Maui and library CSharpMath.SkiaSharp and MathPainter from there too create latex formulas. I have an editor where the user enters latex. Using MathPainter, I draw the latex formula as a stream and show it to the user in real time with a picture, the source of this picture changes as soon as the formula changes. It looks something like this:

MathPainter mathPainter = new() {FontSize = 25};

private void EditorTextChanged(object sender, EventArgs e)
{
   string formula = e.NewTextValue;
   mathPainter.LaTeX = formula;
   LatexImage.Source = ImageSource.FromStream(() => mathPainter.DrawAsStream())
}

Then, when the user is done and clicks the button, I get the picture again as a stream and save it in the place I need. Sometimes it works as expected, but most of the time I just get the "Canvas trying to draw too large bitmap" exception and the application crashes. How can I fix this problem?


Solution

  • Ok, here we go:

    This is the source code of DrawAsStream:

    public static System.IO.Stream? DrawAsStream<TContent>
      (this Painter<SKCanvas, TContent, SKColor> painter,
       float textPainterCanvasWidth = TextPainter.DefaultCanvasWidth,
       TextAlignment alignment = TextAlignment.TopLeft,
       SKEncodedImageFormat format = SKEncodedImageFormat.Png,
       int quality = 100) where TContent : class {
      var size = painter.Measure(textPainterCanvasWidth).Size;
      // SKSurface does not support zero width/height. Null will be returned from SKSurface.Create.
      if (size.Width is 0) size.Width = 1;
      if (size.Height is 0) size.Height = 1;
      using var surface = SKSurface.Create(new SKImageInfo((int)size.Width, (int)size.Height));
      painter.Draw(surface.Canvas, alignment);
      using var snapshot = surface.Snapshot();
      return snapshot.Encode(format, quality).AsStream();
    }
    

    This line here:

    var size = painter.Measure(textPainterCanvasWidth).Size;
    

    Returns the same result over and over. (This textPainterCanvasWidth says "UNUSED")

    So, what do we do? We copy this method and write our own:

    Stream GetStream(MathPainter painter)
    {
        var size = painter.Measure(100).Size; //This returns the same
        size.Width = 300;
        size.Height = 300;
        if (size.Width is 0) size.Width = 1;
        if (size.Height is 0) size.Height = 1;
        using var surface = SKSurface.Create(new SKImageInfo((int)size.Width, (int)size.Height));
        painter.Draw(surface.Canvas, CSharpMath.Rendering.FrontEnd.TextAlignment.Center);
        using var snapshot = surface.Snapshot();
        return snapshot.Encode(SKEncodedImageFormat.Png, 100).AsStream();
    }
    

    And override the Width and Height to 300 in it.

    In the XAML we have:

    <Image x:Name="myImage" Aspect="AspectFit" WidthRequest="500" HeightRequest="500"/>
    

    And in the code:

    myImage.Source = ImageSource.FromStream(() => GetStream(mathPainter));
    

    It works on my side, more or less.