Repository navigation
jpg.decodedBlock should it release memory after usage? #224
Description
Prerequisites
- I have written a descriptive issue title
- I have verified that I am running the latest version of ImageSharp
- I have verified if the problem exist in both
DEBUGandRELEASEmode - I have searched open and closed issues to ensure it has not already been reported
Description
I was using imagesharp 1 alpha 4 48 previousely and my app for a normal task took up to 4gb, today I updated to latest version and saw it decreased to 3gb. thanks for the improvement.
I just ran a profiler and saw that the type of decoded block and color block from static type changed to normal property so I got some concern if i'm doing something wrong. I assume that it should release the resources when I call the dispose method or it reach end of the using statement.
I checked almost all of my code, i have using and dispose for every usage. also the class that wrapp the imagesharp is implementing IDisposable.
Steps to Reproduce
i'm using imagesharp with DI in startup.cs, configServices method:
services.AddTransient<ImageSharpHelper>();
and the imageSharpHelper is like bellow:
using ImageSharp;
using ImageSharp.Formats;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
namespace namespace1.namespace2.Image
{
public class ImageSharpHelper : IDisposable
{
private readonly ILogger<ImageSharpHelper> _logger;
public ImageSharpHelper(ILogger<ImageSharpHelper> logger)
{
_logger = logger;
}
public Image<Rgba32> Client { get; set; }
public int? CompressRate { get; set; }
public string Mime { get; set; }
public ImageOutPutTypes OutPutTypes { get; set; }
public int? QualityRate { get; set; }
private Dictionary<ImageOutPutTypes, Func<MemoryStream, string>> SaveImageMapper => new Dictionary<ImageOutPutTypes, Func<MemoryStream, string>>()
{
[ImageOutPutTypes.Bmp] = SaveAsBmp,
[ImageOutPutTypes.Jpg] = SaveAsJpg,
[ImageOutPutTypes.Png] = SaveAsPng,
[ImageOutPutTypes.None] = SaveImage
};
public void Compress(int compress, int quality)
{
CompressRate = compress;
QualityRate = quality;
}
public void Crop(int? x, int? y, int? width, int? height)
{
if (x == null || y == null || width == null || height == null)
{
throw new NullReferenceException("Crop- Neither Width or height or X or Y can be null");
}
_logger.LogDebug($"croping with x:{x} y:{y} width:{width} height:{height}");
var rectangle = new Rectangle((int)x, (int)y, (int)width, (int)height);
Client.Crop(rectangle);
}
public void Resize(int? width, int? height)
{
if (width == null || height == null)
{
throw new NullReferenceException("Resize- Neither Width or height can be null");
}
_logger.LogDebug($"croping width:{width} - height:{height}");
Client.Resize((int)width, (int)height);
}
public Stream SaveAs()
{
var fileStream = new MemoryStream();
Func<MemoryStream, string> saveOutputMethod;
if (SaveImageMapper.TryGetValue(OutPutTypes, out saveOutputMethod))
{
Mime = saveOutputMethod(fileStream);
}
else
{
Mime = SaveImage(fileStream);
}
return fileStream;
}
public void SetImage(Stream file)
{
Client?.Dispose();
Client = ImageSharp.Image.Load<Rgba32>(file);
Mime = Client.CurrentImageFormat.MimeType;
OutPutTypes = ImageOutPutTypes.None;
}
private string SaveAsBmp(MemoryStream fileStream)
{
Client.SaveAsBmp(fileStream);
return "image/bmp";
}
private string SaveAsJpg(MemoryStream fileStream)
{
var jpegEncoderOptions = new JpegEncoderOptions()
{
IgnoreMetadata = true,
};
if (QualityRate != null)
{
jpegEncoderOptions.Quality = (int)QualityRate;
}
Client.SaveAsJpeg(fileStream, jpegEncoderOptions);
return "image/jpg";
}
private string SaveAsPng(MemoryStream fileStream)
{
var pngEncoderOptions = new PngEncoderOptions()
{
//Quantizer = new OctreeQuantizer<Color>() {/*Dither = false*/},
IgnoreMetadata = true
};
if (QualityRate != null)
{
pngEncoderOptions.Quality = (int)QualityRate;
}
if (CompressRate != null)
{
pngEncoderOptions.CompressionLevel = (int)CompressRate;
}
Client.SaveAsPng(fileStream, pngEncoderOptions);
return "image/png";
}
private string SaveImage(MemoryStream fileStream)
{
Client.Save(fileStream);
return Client.CurrentImageFormat.MimeType;
}
public void Dispose()
{
Client?.Dispose();
}
}
}
and i'm calling the class like bellow:
using (var imageSharpHelper = _serviceProvider.GetService<ImageSharpHelper>())
{
imageSharpHelper.SetImage(latestAvailableVersion.Image);
imageSharpHelper.Resize(x, y);
return imageSharpHelper.SaveAs();
}
System Configuration
- ImageSharp version: 1.0.0-alpha9-00058
- Other ImageSharp packages and versions:
- Environment (Operating system, version and so on): windows 10
- .NET Framework version: asp.net core 1.1.1
- Additional information:

Activity
antonfirsov commentedon May 25, 2017
@mhamri it looks like duplicate of #151, but thanks for the detailed description of your use-case!
The memory usage drop from 4GB to 3GB is random, we haven't done actual improvements on this. I think it depends on the input images your service is processing. Do your inputs have a common characteristic? (eg: large photos). If yes, it might be helpful to post a few of them.
Implementing #225 and certain points of #192 should help on this. Unfortunately both are big tasks, so we need your patience here.
mhamri commentedon May 25, 2017
well, about the 3gb to 4gb, i'm sure there was some changes, i have a batch of 40 images in one xml file that generates 4gb memory usage. i ran this test file almost 50 times, even before i do update to latest package. after update to the latest package, the usage decreased to 3gb.
i'm not sure it's same with #151, because my question is why the referenced object is not releasing memory if i'm disposing the object correctly? what is keeping a reference to those color and decoder array? I understand the previous memory usage when the decoder and color array was static. but now that is not static why still a reference is kept somewhere to those arrays? you got what do I mean?
antonfirsov commentedon May 25, 2017
It is
ArrayPool. Pools large arrays in a shared (static) pool to reduce gc pressure and prevent LOH issues. We understand this is not the best behavior for many users though, so we want to make this configurable.mhamri commentedon May 25, 2017
antonfirsov commentedon May 25, 2017
There might be an unexpected behavior (missing dispose calls?) that came with #210. Looks like we need to do a deeper investigation here. @JimBobSquarePants I think we really need to find a way to stress test the library with a larger test image pack.
JimBobSquarePants commentedon May 25, 2017
Agreed, we do.
mhamri commentedon May 26, 2017
I hope this help, it's my set of test images, all are stock photos, good quality, and high dpi and pixel
url
JimBobSquarePants commentedon May 26, 2017
Thanks! Really appreciate it.
antonfirsov commentedon Sep 16, 2017
@mhamri If you are still using ImageSharp, check out the beta, the Jpeg decoder uses much less memory now! Let me know if the situation is still critical for you!