SkiaSharp - SKImage.Encode Causes Memory Leaks

KorayAgdemirKorayAgdemir USMember ✭✭
edited November 2016 in Xamarin.iOS

Hello,

I'm using SkiaSharp in my Xamarin.iOS project. The following lines cause memory leak, and can not understand why it happens at all.

using (var image = SKImage.FromBitmap(bitmap)) {
    using (var data = image.Encode(SKImageEncodeFormat.Png, 100)) {
        using (var stream = File.OpenWrite(path)) {
            data.SaveTo(stream);
        }
    }
}

I have attached screenshot from the instruments window.

Do you have any suggestions or workaround to fix this?

Edit: I'm using SkiaSharp version 1.54.1.

Thanks,
Koray

Best Answer

Answers

  • KorayAgdemirKorayAgdemir USMember ✭✭

    Hello again,

    Investigating further, the issue is being caused by SKCanvas. Once you create an SKCanvas, I could not find any way to release memory used by it.

    The following lines create a memory leak of 150 megs, attached is the allocations screenshot.

    for (int i = 0; i < 10; i++) {
        using (SkiaSharp.SKBitmap bitmap = new SkiaSharp.SKBitmap(2000, 2000, false)) {
            using (var canvas = new SkiaSharp.SKCanvas(bitmap)) {
    
            }
        }
    }
    

    The following releases memory without issues.

    for (int i = 0; i < 10; i++) {
        using (SkiaSharp.SKBitmap bitmap = new SkiaSharp.SKBitmap(2000, 2000, false)) {
    
        }
    }
    

    I have tried, flush, dispose, setting canvas to null, none of them are working. Basically, I'm trying to draw paths and save them as PNG.

    Any ideas?

    Thanks,
    Koray

  • BerayBentesenBerayBentesen TRUniversity ✭✭✭✭

    @KorayAgdemir did you try

       canvas.InvalidateSurface();
    
  • KorayAgdemirKorayAgdemir USMember ✭✭

    No such method, can it be for an earlier version of SkiaSharp?

  • BerayBentesenBerayBentesen TRUniversity ✭✭✭✭

    @KorayAgdemir here is the sample link , also is 2000 x 2000 size of Bitmap ? If so, that is a huge number for Bitmap. Try something smaller, 100x100, 200x200 e.g.

  • KorayAgdemirKorayAgdemir USMember ✭✭

    It's for SKCanvasView from SkiaSharp.Views.Forms, so it is quite irrelevant.

    Link

    I believe 2000 x 2000 is not huge, but it was for testing purposes. 100 x 100 causes the leak also, with a smaller size only.

  • BerayBentesenBerayBentesen TRUniversity ✭✭✭✭
    edited November 2016

    @KorayAgdemir RGB888 bitmap of 2000x2000 pixels will exceed the limit (2000x2000x4bytes=15.625MB) which means 11 x 15.625 = 171.875MB. If you still believe it is not huge, than ask to support for detailed confirmation.

  • KorayAgdemirKorayAgdemir USMember ✭✭

    I know how much memory a 2000 x 2000 pixel bitmap will take.

    Again, the sample code I posted was an example and was a way to replicate the issue. Of course I'm not creating and disposing 10 large bitmaps at once.

    But this is out of the scope of my question. The size of the bitmap is irrelevant. It leaks for every size.

  • KorayAgdemirKorayAgdemir USMember ✭✭

    @mattleibow Great! Looking forward for the fix.

    Thanks,
    Koray

  • mattleibowmattleibow ZAXamarin Team Xamurai

    @KorayAgdemir I am just waiting on CI to finish building, and then I'll send you a link with the new NuGet. You can try that out and confirm that the issue is resolved.

  • KorayAgdemirKorayAgdemir USMember ✭✭

    Hi @mattleibow, any news with the new build? Apologies if I'm being impatient, I have a very tight deadline.

    Thanks again,
    Koray

  • mattleibowmattleibow ZAXamarin Team Xamurai

    @KorayAgdemir I sent you a DM

  • I have memory leaks. I draw a Map on Canvas and draw a location hotspot on top of it. But after few seconds i got
    Exception: Foundation.MonoTouchException: Objective-C exception thrown. Name: NSMallocException Reason: Failed to grow buffer

    `at ObjCRuntime.Runtime.ThrowNSException (System.IntPtr ns_exception) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/13.6.0.12/src/Xamarin.iOS/ObjCRuntime/Runtime.cs:406
    at ObjCRuntime.Runtime.throw_ns_exception (System.IntPtr exc) [0x00000] in /Users/builder/jenkins/workspace/xamarin-macios/xamarin-macios/runtime/Delegates.generated.cs:128
    at (wrapper native-to-managed) ObjCRuntime.Runtime.throw_ns_exception(intptr)
    at (wrapper managed-to-native) SkiaSharp.SkiaApi.sk_typeface_create_from_file(void*,int)
    at Skia
    Sharp.SKTypeface.FromFile (System.String path, System.Int32 index) [0x0002c] in :0
    at OccupancyViewModule.Map.EstateSmartCanvas.get_TextFontFamily () [0x00000] in C:\projects\raboon\EstateMobile\Modules\OccupancyViewModule\Map\EstateSmartCanvas.cs:73
    at OccupancyViewModule.Map.EstateSmartCanvas.DrawLocationHotSpot (SkiaSharp.SKCanvas canvas, SkiaSharp.SKImageInfo info, SkiaSharp.SKMatrix matrix) [0x000e1] in C:\projects\raboon\EstateMobile\Modules\OccupancyViewModule\Map\EstateSmartCanvas.cs:487
    at OccupancyViewModule.Map.EstateSmartCanvas.DrawAllHotSpot (SkiaSharp.SKCanvas canvas, SkiaSharp.SKImageInfo info) [0x00028] in C:\projects\raboon\EstateMobile\Modules\OccupancyViewModule\Map\EstateSmartCanvas.cs:363
    --- End of stack trace from previous location where exception was thrown ---

    at (wrapper managed-to-native) SkiaSharp.SkiaApi.sk_typeface_create_from_file(void*,int)
    at SkiaSharp.SKTypeface.FromFile (System.String path, System.Int32 index) [0x0002c] in :0
    at OccupancyViewModule.Map.EstateSmartCanvas.get_TextFontFamily () [0x00000] in C:\projects\raboon\EstateMobile\Modules\OccupancyViewModule\Map\EstateSmartCanvas.cs:73
    at OccupancyViewModule.Map.EstateSmartCanvas.DrawLocationHotSpot (SkiaSharp.SKCanvas canvas, SkiaSharp.SKImageInfo info, SkiaSharp.SKMatrix matrix) [0x000e1] in C:\projects\raboon\EstateMobile\Modules\OccupancyViewModule\Map\EstateSmartCanvas.cs:487
    at OccupancyViewModule.Map.EstateSmartCanvas.DrawAllHotSpot (SkiaSharp.SKCanvas canvas, SkiaSharp.SKImageInfo info) [0x00028] in C:\projects\raboon\EstateMobile\Modules\OccupancyViewModule\Map\EstateSmartCanvas.cs:363 0 CoreFoundation 0x000000018181d820 FF63481D-FB6A-353B-B2C1-AC4EAECF594D + 1226784`

    My hotspot draw code as below

    `public void DrawAllHotSpot(SKCanvas canvas, SKImageInfo info)
    {
    canvas.Save();
    SKMatrix matrix = _touchSensitiveBitmap.Matrix;
    canvas.Concat(ref matrix);
    try
    {
    DrawBookalbeHotSpot(canvas, info, matrix);
    DrawLocationHotSpot(canvas, info, matrix);
    }
    catch (Exception e)
    {
    Console.WriteLine($"Exception: {e}");
    }

            canvas.Restore();
        }
    
        public void DrawBookalbeHotSpot(SKCanvas canvas, SKImageInfo info, SKMatrix matrix)
        {
            float x = 0, y = 0;
            string text, bookableName;                      
            SKRect textBounds = new SKRect();
            SKRect textBookableBounds = new SKRect();
            using (SKPaint textIconPaint = new SKPaint())
            {
                // Set Style for the character outlines
                textIconPaint.Style = SKPaintStyle.Fill;
                textIconPaint.TextSize = FontSize;
                textIconPaint.IsAntialias = true;
                textIconPaint.Style = SKPaintStyle.Fill;
                textIconPaint.TextEncoding = SKTextEncoding.Utf32;
                textIconPaint.Color = Color.White.ToSKColor();
                // Set TextSize based on screen size
                using (SKPaint buttonFillPaint = new SKPaint())
                {
                    buttonFillPaint.Style = SKPaintStyle.StrokeAndFill;                    
                    using (SKPaint buttonTextPaint = new SKPaint())
                    {
                        buttonTextPaint.Style = SKPaintStyle.StrokeAndFill;
                        buttonTextPaint.Color = Color.Black.ToSKColor();
                        buttonTextPaint.TextSize = 10;
                        buttonTextPaint.IsAntialias = true;
                        buttonTextPaint.TextEncoding = SKTextEncoding.Utf32;
                        buttonTextPaint.Typeface = TextFontFamily;
                        textIconPaint.Typeface = IconFontFamily;
                        var bookables = _quickBookingPopupViewViewModel?.Rows.Where(item => item?.Bookable != null && item?.Bookable?.LocationId == _filterPopupViewModel.ChosenLocation.LocationId);
                        foreach (var item in bookables)
                        {
                            x = (item?.Bookable?.PositionX ?? 0) * _touchSensitiveBitmap.bitmap.Width;
                            y = (item?.Bookable?.PositionY ?? 0) * _touchSensitiveBitmap.bitmap.Height;
                            switch (item?.Bookable?.CurrentAvailability)
                            {
                                case ServiceModule.EstateWebservice.AvailabilityStatus.Available:
                                    text = IconIconFree;
                                    buttonFillPaint.Color = SKColor.Parse(FreeColorHexString);
                                    break;
                                case ServiceModule.EstateWebservice.AvailabilityStatus.Unavailable:
                                    text = IcontIconBusy;
                                    buttonFillPaint.Color = SKColor.Parse(BookedColorHexString);
                                    break;
                                default:
                                    text = IconPartiallyFree;
                                    buttonFillPaint.Color = SKColor.Parse(PartialColorHexString);
                                    break;
                            }
                            bookableName = item?.Bookable?.ObjectName ?? string.Empty;
                            if (bookableName.StartsWith(item?.Bookable?.ObjectLocation?.LocationName))
                            {
                                bookableName = bookableName.Replace(item?.Bookable?.ObjectLocation?.LocationName, String.Empty);
                            }
                            #region Draw Bookable Object Icon
                            textIconPaint.MeasureText(text, ref textBounds);
                            float x1 = (float)(x - textBounds.MidX);
                            float y1 = (float)(y - textBounds.MidY);
                            x1 = x1 > 0 ? x1 : 0;
                            y1 = y1 > 0 ? y1 : 0;
                            SKRect frameRect = textBounds;
                            frameRect.Offset(x1, y1);
                            frameRect.Inflate(2, 1);
                            canvas.DrawRect(frameRect, buttonFillPaint);
                            canvas.DrawText(text, x1, y1, textIconPaint);
                            #endregion
                            #region Draw Bookable Object name if zoom size reached a certain level
                            if (matrix.ScaleX > ZoomThresholdToShowBookableName)
                            {
                                textIconPaint.MeasureText(bookableName, ref textBookableBounds);
                                x1 = (float)(x - textBookableBounds.MidX);
                                y1 = (float)(y + textBookableBounds.MidY);
                                x1 = x1 > 0 ? x1 : 0;
                                y1 = y1 > _touchSensitiveBitmap.Display.Height ? _touchSensitiveBitmap.Display.Height : y1;
                                frameRect = textBookableBounds;
                                frameRect.Offset(x1, y1);
                                frameRect.Inflate(2, 1);
                                //canvas.DrawRect(frameRect, buttonFillPaint);
                                canvas.DrawText(bookableName, x1, y1, buttonTextPaint);
                            }
                            #endregion
                        }
                    }
                }
            }
        }        
    
        public void DrawLocationHotSpot(SKCanvas canvas, SKImageInfo info, SKMatrix matrix)
        {
            float x = 0, y = 0;
            string text,iconText = string.Empty;
            SKRect textBounds = new SKRect();
            SKRect textIconBounds = new SKRect();
            using (SKPaint textIconPaint = new SKPaint())
            {
                // Set Style for the character outlines
                textIconPaint.Style = SKPaintStyle.Fill;
                textIconPaint.TextSize = FontSize;
                textIconPaint.IsAntialias = true;
                textIconPaint.Style = SKPaintStyle.Fill;
                textIconPaint.TextEncoding = SKTextEncoding.Utf32;
                textIconPaint.Color = Color.White.ToSKColor();
                textIconPaint.Typeface = IconFontFamily;
                // Set TextSize based on screen size
                using (SKPaint buttonFillPaint = new SKPaint())
                {
                    buttonFillPaint.Style = SKPaintStyle.StrokeAndFill;
                    buttonFillPaint.Color = SKColor.Parse("96979C");
                    using (SKPaint buttonTextPaint = new SKPaint())
                    {
                        buttonTextPaint.Style = SKPaintStyle.StrokeAndFill;
                        buttonTextPaint.Color = Color.White.ToSKColor();
                        buttonTextPaint.TextSize = FontSize;
                        buttonTextPaint.IsAntialias = true;
                        buttonTextPaint.TextEncoding = SKTextEncoding.Utf32;
                        buttonTextPaint.Typeface = TextFontFamily;
                        using (SKPaint iconBackColor = new SKPaint())
                        {
                            iconBackColor.Style = SKPaintStyle.StrokeAndFill;
    
                            foreach (var item in _quickBookingPopupViewViewModel?.Rows.Where(item => item?.Location != null))
                            {
                                x = (item?.Location?.PositionX ?? 0) * _touchSensitiveBitmap.bitmap.Width;
                                y = (item?.Location?.PositionY ?? 0) * _touchSensitiveBitmap.bitmap.Height;
                                text = (item?.Location?.LocationName?.Length > LocationNamePrefixLenth) ? item?.Location?.LocationName?.Substring(0, LocationNamePrefixLenth) : item?.Location?.LocationName;
                                text = ".." + text;// Add Extra Space to Show (+/-/X) icon
                                iconBackColor.Color = GetLocationButtonColor(item?.Location, ref iconText);
    
                                #region Paint Location Name
                                textIconPaint.MeasureText(text, ref textBounds);
                                float x1 = (float)(x - textBounds.MidX);
                                float y1 = (float)(y - textBounds.MidY);
                                x1 = x1 > 0 ? x1 : 0;
                                y1 = y1 > 0 ? y1 : 0;
                                SKRect frameRect = textBounds;
                                frameRect.Offset(x1, y1);
                                frameRect.Inflate(2, 1);
                                canvas.DrawRect(frameRect, buttonFillPaint);
                                canvas.DrawText(text, x1, y1, buttonTextPaint);
                                #endregion
    
                                #region paint Location Icon (+/-/x)                            
                                iconBackColor.MeasureText(iconText, ref textIconBounds);
                                SKRoundRect frameRoundedRect = new SKRoundRect(textIconBounds, 5, 5);
                                frameRoundedRect.Offset(x1-1, y1-1);
                                frameRoundedRect.Inflate(2, 1);
                                canvas.DrawRoundRect(frameRoundedRect, iconBackColor);
                                canvas.DrawText(iconText, x1-5, y1-1, textIconPaint);
                                #endregion
                            }
                        }
                    }
                }
    
            }
        }`
    

    what did I make wrong?!

Sign In or Register to comment.