Search code examples
c#asp.net-coreskiasharp

Delayed rendering of the image - Skia ASP


I'm trying to draw a set of images with SkiaSharp, convert them to byte array, and send to HttpContext stream. For some reason, every image is being displayed in the browser only when the next image has been rendered and sent to the HTTP stream.

  • After rendering the 1st image, browser shows empty white area, no image
  • After rendering the 2nd image, browser shows 1st image
  • After rendering the 3rd image, browser shows 2nd image, and so on

Here is the Web API controller. Uncommenting second drawShapes call will make it work, but I would like to understand why image rendering is delayed. Maybe I just don't call some important method, like Flush or something similar?

Also, this is not an ASP issue, because I tried rendering images with System.Drawing.Common and images were rendered in real time without delay.

Does anybody know what is missing in this method?

[Route("/source")]
public async Task Get()
{
  // Initialize Skia

  var res = new byte[1000];
  var boundary = Guid.NewGuid().ToString();
  var generator = new Random();
  var map = new SKBitmap(250, 250);
  var canvas = new SKCanvas(map);
  var inPaint = new SKPaint
  {
    Color = SKColors.Black,
    Style = SKPaintStyle.Fill,
    FilterQuality = SKFilterQuality.High
  };
  var exPaint = new SKPaint
  {
    Color = SKColors.White,
    Style = SKPaintStyle.Fill,
    FilterQuality = SKFilterQuality.Low
  };

  // Create HTTP stream

  Response.ContentType = "multipart/x-mixed-replace;boundary=" + boundary;

  var outputStream = Response.Body;
  var cancellationToken = Request.HttpContext.RequestAborted;

  // Create method posting images to HTTP stream 

  async Task drawShapes()
  {
    using (var image = SKImage.FromBitmap(map))
    {
      var pos = generator.Next(50, 150);
      canvas.DrawRect(0, 0, 250, 250, exPaint);
      canvas.DrawCircle(pos, pos, 20, inPaint);
      res = image.Encode(SKEncodedImageFormat.Webp, 100).ToArray();
    }

    var header = $"--{ boundary }\r\nContent-Type: image/webp\r\nContent-Length: { res.Length }\r\n\r\n";
    var headerData = Encoding.ASCII.GetBytes(header);

    await outputStream.WriteAsync(headerData);
    await outputStream.WriteAsync(res);
    await outputStream.WriteAsync(Encoding.ASCII.GetBytes("\r\n"));
  }

  // Start HTTP stream

  await Task.Run(async () =>
  {
    //while (true)
    {
      await drawShapes();
      //await drawShapes(); // After uncommenting this shows the image (the previous one)
    }
  });
}

Reproducible sample


Solution

  • This was answered in SkiaSharp discussions. https://github.com/mono/SkiaSharp/discussions/1975

    Possible solutions are to make sure to Encode image only after all drawings are done or to call Encode on bitmap instead of image.

    The simplest one is to replace this line.

    //res = image.Encode(SKEncodedImageFormat.Webp, 100).ToArray();  // wrong
    res = map.Encode(SKEncodedImageFormat.Webp, 100).ToArray();      // correct