I am in the need to cache Bitmap
's in a memory-optimized way because the API I am building will need to process many colored Bitmap
's in parallel and can be used in x86 or x64 compiled applications.
If the API is being used in x86 I can't just store the Bitmap
's as they are but need to store them as compressed MemoryStream
's otherwise the API would throw an OutOfMemoryException
pretty fast.
After storing the "Bitmap
's" they will be processed by multiple threads hence thread-safety is a major point.
Any feedback is welcome.
public static class ImageCache
{
private static int currentId = 0;
private static readonly object addImageLock = new object();
private static readonly object releaseImageLock = new object();
private static readonly ConcurrentDictionary<int, MemoryStream> images = new ConcurrentDictionary<int, MemoryStream>();
/// <summary>
/// Release an image based on its id.
/// </summary>
/// <param name="id"></param>
public static void ReleaseIamge(int id)
{
lock (releaseImageLock)
{
ReleaseMemoryStream(id);
}
}
private static void ReleaseMemoryStream(int id)
{
MemoryStream ms = null;
if (images.TryGetValue(id, out ms) && ms != null)
{
images[id].Dispose();
images[id] = null;
}
}
/// <summary>
/// Releases all Images
/// </summary>
public static void ReleaseAllImages()
{
lock (releaseImageLock)
{
lock (addImageLock)
{
foreach (var id in images.Keys)
{
ReleaseMemoryStream(id);
}
images.Clear();
}
}
}
/// <summary>
/// Returns a Bitmap from the cache which is identified by an id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static Bitmap GetBitmap(int id)
{
lock (releaseImageLock)
{
MemoryStream ms = null;
if (images.TryGetValue(id, out ms))
{
if (ms != null)
{
return (Bitmap)Image.FromStream(ms);
}
}
}
return null;
}
/// <summary>
/// Adds an Bitmap to the cache
/// </summary>
/// <param name="bitmap"></param>
/// <returns>0 if the Bitmap is null, otherwise a uique id</returns>
public static int Add(Bitmap bitmap)
{
if (bitmap == null)
{
return 0;
}
var ms = new MemoryStream();
bitmap.Save(ms, ImageFormat.Tiff);
var id = 0;
lock (addImageLock)
{
// If the dictionary is empty we can reset the currentId
if (images.Count == 0)
{
currentId = 0;
id = Interlocked.Increment(ref currentId);
images.TryAdd(id, ms);
return id;
}
// We don't know how long an application using this is running and how many
// images having been stored but we don't want to reach int.MaxValue here
// hence we recycle the Value of a KeyValuePair of the dictionary if the Value
// will be null.
id = images.Where(item => item.Value == null).FirstOrDefault().Key;
if (id == 0)
{
id = Interlocked.Increment(ref currentId);
}
images[id] = ms;
}
return id;
}
}
Clarifying comments:
Why don't you use a Guid as id and key in the dictionary?
Well the problem with this is that I need to "mimic" an API we use for our main application in such a way that I can interchange this API and the other API. The other api is a comercial imaging sdk whichs licence doesn't permits the use of it for a sdk/api.
Guid
as id and key in the dictionary? \$\endgroup\$