Memory Leaks : ListView with Bitmaps - c#

I have a clearly memory leak problem and I need some help/advices to solve it.
My scenario has a simple ListView that displays image and title. This image is a bitmap Image downloaded from server.
After scrolling up and down so FAST this ListView crashes my app, and if i inspect the console i have a OOM Exception like that:
[art] Clamp target GC heap from 111MB to 96MB
[art] Alloc concurrent mark sweep GC freed 3(96B) AllocSpace objects, 0(0B) LOS objects, 0% free, 95MB/96MB, paused 1.187ms total 38.840ms
To avoid that OOM i implemented a LRUCache and DiskCache for store downloaded bitmaps into device, and get this files instead download images again.
This is my ListView Adapter:
public class LazyLoadAdapter : BaseAdapter
{
Activity _activity;
List _products;
BitmapCache cache;
ImageView _imgView;
Dictionary<string, Task> pendingFetch = new Dictionary<string, Task> ();
Bitmap NoMapPicture;
public LazyLoadAdapter(Activity activity, List<CouponExchange> products)
{
_activity = activity;
_products = products;
NoMapPicture = PrepareNoMapPicture (Resource.Drawable.default_coupon);
this.cache = BitmapCache.CreateCache (activity, "MapCache");
}
public override int Count
{
get { return _products.Count; }
}
public override Java.Lang.Object GetItem(int position)
{
return position;
}
public override long GetItemId(int position)
{
return position;
}
public override Android.Views.View GetView(int position, Android.Views.View convertView, Android.Views.ViewGroup parent)
{
if (convertView == null)
{
convertView = _activity.LayoutInflater.Inflate(Resource.Layout.ShopItemList, parent, false);
}
CouponExchange product = _products[position];
TextView txtProductName = convertView.FindViewById<TextView>(Resource.Id.textView24);
txtProductName.Text = product.CouponTitle;
TextView txtProductCost = convertView.FindViewById<TextView>(Resource.Id.textView24b);
txtProductCost.Text = product.Cost.ToString();
_imgView = convertView.FindViewById<ImageView>(Resource.Id.imgProduct);
GetPersonPicture (product.CouponImageUrl);
return convertView;
}
Bitmap DownloadoCacher (string url)
{
Bitmap map = null;
using(map){
map = cache.TryGet2 (url);
if (map!=null)
return map;
byte[] bytes;
using (var wc = new WebClient ()) {
bytes = wc.DownloadData (url);
};
if (bytes != null && bytes.Length > 0) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.InJustDecodeBounds = true;
map = DecodeSampledBitmapFromResource (bytes, 400, 200);
} else {
return map;
}
cache.AddOrUpdate (url, map, TimeSpan.FromDays (1));
return map;
};
}
public static Bitmap DecodeSampledBitmapFromResource(byte[] bytes,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
BitmapFactory.Options options = new BitmapFactory.Options();
options.InJustDecodeBounds = true;
BitmapFactory.DecodeByteArray(bytes, 0, bytes.Length, options);
// Calculate inSampleSize
options.InSampleSize = CalculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
return BitmapFactory.DecodeByteArray(bytes, 0, bytes.Length, options);
}
public static int CalculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
int height = options.OutHeight;
int width = options.OutWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
int halfHeight = height / 2;
int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
Bitmap PrepareNoMapPicture (int baseImage)
{
return BitmapFactory.DecodeResource (_activity.Resources, baseImage);
}
Bitmap GetPersonPicture (string url){
if (_imgView == null)
return null;
Bitmap map = null;
using (map) {
map = cache.TryGet2 (url);
if (map!=null) {
_imgView.SetImageBitmap (map);
} else {
_imgView.SetImageBitmap (NoMapPicture);
Action doMapSetting = () => {
_activity.RunOnUiThread (() => {
if (map == null){
map = cache.TryGet2 (url);
}
_imgView.SetImageBitmap (map);
});
};
if (pendingFetch.ContainsKey (url))
pendingFetch [url].ContinueWith (t => doMapSetting (), TaskContinuationOptions.ExecuteSynchronously);
else
pendingFetch[url] = SerialScheduler.Factory.StartNew (() => {
map = DownloadoCacher (url);
doMapSetting ();
});
}
return map;
};
}
Once images are downloaded, my cache gets images from device file.
After scroll up and down so fast, cacheDisk try get images from files and throws OOM Exception:
try {
bmp = Android.Graphics.BitmapFactory.DecodeFile (Path.Combine (basePath, key));
} catch (Exception e) {
var err = e.Message;
return null;
}
All replies would be appreciate. Thanks you

I use this Picasso Binding library for Xamarin :
https://github.com/jacksierkstra/Picasso
This powerful image downloading and caching library allows you to simplify your Image management.
Official documentation :
http://square.github.io/picasso/
Hope this helps

Related

How to draw GIFs better in DrawingVisual?

I am trying to draw giftu in DrawingVisual.
Here are some codes that I have tried,But the performance is very poor.
Used up to 22% of cpu (AMD Ryzen 7 5800H) and 50% of gpu(rtx 3060) and 4GB of RAM when drawing a 1924*934, 434 fps gif.
And it produces a very strange trailing shadow:
Screenshots
public void InitImage(string path)
{
isAnima = false;
uriBitmap = BitmapDecoder.Create(
new Uri(path, UriKind.Relative),
BitmapCreateOptions.None,
BitmapCacheOption.Default);
if (uriBitmap.Frames.Count > 1)
{
isAnima = true;
//Get the information of each frame of the gif here
frameInfos = new List<FrameInfo>();
for (int i = 0; i < uriBitmap.Frames.Count; i++)
{
frameInfos.Add(GetFrameInfo(uriBitmap.Frames[i]));
}
frameIndex = 0;
try
{
if (animationThread == null)
{
animationThread = new Thread(ChangeGifFrame);
animationThread.Start();
}
}
catch (Exception)
{
Debug.WriteLine("线程关闭");
}
}
}
private void DrawImage(Point location, Size size)
{
var drawing = this.dvc.drawingVisual.RenderOpen();
drawing.PushTransform(rotate);
if (!isAnima)
{
drawing.DrawImage(baseSource, new Rect(location, size));
}
else
{
//Since the FrameDisposalMethod is Combine, I need to draw all the frames before this frame
for (int i = 0; i < frameIndex; i++)
{
var frame = uriBitmap.Frames[i];
var info = frameInfos[i];
drawing.DrawImage(frame, new Rect(new Point(location.X + info.Left * zoom, location.Y + info.Top * zoom), new Size(info.Width * zoom, info.Height * zoom)));
}
}
drawing.Close();
}
public void ChangeGifFrame()
{
while (true)
{
if (isAnima)
{
this.Dispatcher.Invoke(new Action(() =>
{
frameIndex++;
if (frameIndex >= uriBitmap.Frames.Count)
{
frameIndex = 0;
}
DrawImage(drawPoint, drawSize);
}));
}
Thread.Sleep(30);
}
}
I also tried mixing BitmapSource using the following code, but the performance is worse than drawing directly
public BitmapSource MixBitmapSource(BitmapSource bs1, BitmapSource bs2)
{
DrawingVisual dv = new DrawingVisual();
RenderTargetBitmap render = new RenderTargetBitmap(bs1.PixelWidth, bs2.PixelHeight, bs1.DpiX, bs2.DpiY, PixelFormats.Default);
DrawingContext dc = dv.RenderOpen();
dc.DrawImage(bs1, new Rect(0, 0, bs1.PixelWidth, bs1.PixelHeight));
dc.DrawImage(bs2, new Rect(0, 0, bs1.PixelWidth, bs1.PixelHeight));
dc.Close();
render.Render(dv);
return render;
}
Is there a better way to draw gifs in DrawingVisual?
Thanks for all your help!

UWP - How to create a CompositionSurfaceBrush with a repeated pattern

I would like to have a Panel with a Background that shows a repeated pattern (e.g. dots, evenly separated of 30 pixels).
So far, I managed to create a subclass of XamlCompositionBrushBase that allows we to create my own shape (e.g. a single dot). but I am failing to understand how to repeat this pattern.
This is my custom Brush:
public sealed class DottedBackgroundBrush : XamlCompositionBrushBase
{
public DottedBackgroundBrush()
{
}
protected override void OnConnected()
{
// Delay creating composition resources until they're required.
if (CompositionBrush == null)
{
var compositor = Window.Current.Compositor;
// Actual Width/Height are going to be returned in effective pixels which
// is going to differ from the size of the bitmap that we'll render from the XAML.
var width = 400;
var height = 400;
// Make our visual:
var spriteVisual = compositor.CreateSpriteVisual();
spriteVisual.Size = new Vector2(width, height);
CanvasDevice device = CanvasDevice.GetSharedDevice();
var graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, device);
CompositionSurfaceBrush drawingBrush = compositor.CreateSurfaceBrush();
var drawingSurface = graphicsDevice.CreateDrawingSurface(
new Size(width, height),
DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
using (var ds = CanvasComposition.CreateDrawingSession(drawingSurface))
{
ds.Clear(Colors.Transparent);
ds.DrawCircle(new Vector2(10, 10), 5, Colors.Black, 3);
}
drawingBrush.Surface = drawingSurface;
CompositionBrush = drawingBrush;
}
}
protected override void OnDisconnected()
{
// Dispose of composition resources when no longer in use.
if (CompositionBrush != null)
{
CompositionBrush.Dispose();
CompositionBrush = null;
}
}
}
How can I enable the circle to be replicated indefinitely, instead of having as single instance?
For this, you want to create a CompositionEffectBrush as your main brush, using a Win2D BorderEffect - which does the actual tiling - and set it's source to be your SurfaceBrush.
Example (adapted from a repo of mine so it might be a bit roundabouts)
public class TilingBrush : XamlCompositionBrushBase
{
protected Compositor _compositor => Window.Current.Compositor;
protected CompositionBrush _imageBrush = null;
protected IDisposable _surfaceSource = null;
protected override void OnConnected()
{
base.OnConnected();
if (CompositionBrush == null)
{
CreateEffectBrush();
Render();
}
}
protected override void OnDisconnected()
{
base.OnDisconnected();
this.CompositionBrush?.Dispose();
this.CompositionBrush = null;
ClearResources();
}
private void ClearResources()
{
_imageBrush?.Dispose();
_imageBrush = null;
_surfaceSource?.Dispose();
_surfaceSource = null;
}
private void UpdateBrush()
{
if (CompositionBrush != null && _imageBrush != null)
{
((CompositionEffectBrush)CompositionBrush).SetSourceParameter(nameof(BorderEffect.Source), _imageBrush);
}
}
protected ICompositionSurface CreateSurface()
{
double width = 20;
double height = 20;
CanvasDevice device = CanvasDevice.GetSharedDevice();
var graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(_compositor, device);
var drawingSurface = graphicsDevice.CreateDrawingSurface(
new Size(width, height),
DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
/* Create Drawing Session is not thread safe - only one can ever be active at a time per app */
using (var ds = CanvasComposition.CreateDrawingSession(drawingSurface))
{
ds.Clear(Colors.Transparent);
ds.DrawCircle(new Vector2(10, 10), 5, Colors.Black, 3);
}
return drawingSurface;
}
private void Render()
{
ClearResources();
try
{
var src = CreateSurface();
_surfaceSource = src as IDisposable;
var surfaceBrush = _compositor.CreateSurfaceBrush(src);
surfaceBrush.VerticalAlignmentRatio = 0.0f;
surfaceBrush.HorizontalAlignmentRatio = 0.0f;
surfaceBrush.Stretch = CompositionStretch.None;
_imageBrush = surfaceBrush;
UpdateBrush();
}
catch
{
// no image for you, soz.
}
}
private void CreateEffectBrush()
{
using (var effect = new BorderEffect
{
Name = nameof(BorderEffect),
ExtendY = CanvasEdgeBehavior.Wrap,
ExtendX = CanvasEdgeBehavior.Wrap,
Source = new CompositionEffectSourceParameter(nameof(BorderEffect.Source))
})
using (var _effectFactory = _compositor.CreateEffectFactory(effect))
{
this.CompositionBrush = _effectFactory.CreateBrush();
}
}
}
For the longest time I've meant to add it to the WindowsCommunityToolkit, but for the longest time I've been slamming into bugs with the Visual Layer that stop me. This particular case should work fine however.

Creating array in loop raise an OutOfMemoryException

I'm working on a software in C# used to manipulate images. I have a lot of images (more than 11000) and when I execute my program after a few minutes, I have an "OutOfMemoryException"
There is my code :
private void GenerateImages()
{
try
{
Parallel.ForEach(listImagesStart, startImg =>
{
bool docontinue = true;
try
{
startImg.LoadImage(_baseFileResults);
}
catch
{
docontinue = false;
}
if (docontinue)
{
//Save image as file
startImg.Save();
// Do rotate
MyImg lastRotate = baseImg;
MyImg imgtmp;
String[] tabRotate = new String[3] { "_90", "_180", "_270"};
foreach (String rotate in tabRotate)
{
imgtmp = new GenImg(baseImg.FileName + rotate + baseImg.FileExtension);
imgtmp.LoadImage(lastRotate);
imgtmp.Rotate90();
imgtmp.Save();
lastRotate = imgtmp;
}
startImg.Dispose();
imgtmp.Dispose();
}
});
}
catch (Exception e)
{
MessageBox.Show(e.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
#if DEBUG
MessageBox.Show(e.StackTrace, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
#endif
}
}
And MyImg :
class myImg
{
public Byte[] Matrix;
public int Height;
public int Width;
public void LoadImage(String filePath)
{
// create new Matrix/Heigth/Width from file
}
public void LoadImage(myImg img)
{
Matrix = new Byte[img.Matrix.Length];
Array.Copy(img.Matrix, Matrix, img.Matrix.Length);
Height = img.Height;
Width = img.Width;
}
public void Rotate90()
{
// Save before
int tmpWidth = Height;
int tmpHeight = Width;
Byte[] tmpMatrix = new Byte[Matrix.Length];
for (int r = 0; r < tmpHeight; r++)
{
for (int c = 0; c < tmpWidth; c++)
{
int prevR = Height - c - 1;
int prevC = r;
tmpMatrix[c + r * tmpWidth] = Matrix[prevC + prevR * Width];
}
}
// Copy new image
Array.Copy(tmpMatrix, Matrix, Matrix.Length);
Width = tmpWidth;
Height = tmpHeight;
}
public void Dispose()
{
SavePath = null;
Matrix = null;
Points = null;
Width = 0;
Height = 0;
GC.Collect();
}
}
The SystemOutOfMemoryException occurs at instructionnew Byte[Length]. I think it's I create too many array but I don't know what to do.
The main issue is that listImagesStart keeps reference of each startImg item. This will keep GC from freeing memory allocated by myImg.LoadImage (with the array Matrix).
A quick solution : you can set Matrix to null to recycle the memory.
public void UnLoadImage()
{
Matrix = null ; // this will allow GC to recycle memory used by Matrix
}
then (I removed the useless docontinue variable) :
try
{
startImg.LoadImage(_baseFileResults);
//Save image as file
startImg.Save();
// Do rotate
MyImg lastRotate = baseImg;
MyImg imgtmp;
String[] tabRotate = new String[3] { "_90", "_180", "_270"};
foreach (String rotate in tabRotate)
{
imgtmp = new GenImg(baseImg.FileName + rotate + baseImg.FileExtension);
imgtmp.LoadImage(lastRotate);
imgtmp.Rotate90();
imgtmp.Save();
lastRotate = imgtmp;
}
startImg.Dispose();
imgtmp.Dispose();
}
catch
{
}
finally
{
startImg.Unload(); // Here is the trick
}

LockScreen.SetImageUri ArgumentException despite working Uri

I am having trouble setting the lock screen to an image, but I think it is not a problem with a Uri but rather with the image itself.
When I try to set the image to a downloaded image that has approximately the right size for the lockscreen it works, also when an image is not too big. But when I try to set the lockscreen image to a photo from the camera (Nokia Lumia 920) it gives me this Argument Exception.
My Code:
// From the StorageHelper class:
public async Task<bool> SavePhotoAsync(Photo photo, WriteableBitmap source, int height, int width)
{
return await Task.Run(() =>
{
try
{
lock (StorageLock)
{
ObservableCollection<Photo> observableCollection = photo.PhotoAlbum.Photos as ObservableCollection<Photo>;
if (observableCollection == null)
{
return false;
}
if (!IsolatedStorageFile.GetUserStoreForApplication().DirectoryExists(photo.PhotoAlbum.Name))
{
IsolatedStorageFile.GetUserStoreForApplication().CreateDirectory(photo.PhotoAlbum.Name);
}
string fileName = photo.PhotoAlbum.Name + "/" + observableCollection.IndexOf(photo).ToString(CultureInfo.InvariantCulture);
if (IsolatedStorageFile.GetUserStoreForApplication().FileExists(fileName))
{
IsolatedStorageFile.GetUserStoreForApplication().DeleteFile(fileName);
}
IsolatedStorageFileStream fileStream =
IsolatedStorageFile.GetUserStoreForApplication().CreateFile(fileName);
int targetWidth = 0;
int targetHeight = 0;
if (source.PixelWidth < width || source.PixelHeight < height)
{
targetWidth = source.PixelWidth;
targetHeight = source.PixelHeight;
}
else
{
if (source.PixelWidth > source.PixelHeight)
{
double percentage = ((double)height)/((double) source.PixelHeight);
targetHeight = height;
targetWidth = (int) (source.PixelWidth * percentage);
}
else
{
double percentage = ((double)width) / ((double)source.PixelWidth);
targetWidth = width;
targetHeight = (int)(source.PixelHeight * percentage);
}
}
source.SaveJpeg(fileStream, targetWidth, targetHeight, 0, 100);
fileStream.Close();
return true;
}
}
catch (Exception e)
{
return false;
}
});
}
// End of StorageHelper
// From my LockscreenManager class:
public static async void SetLockScreenImages()
{
LockScreenRequestResult result = await LockScreenManager.RequestAccessAsync();
if (result == LockScreenRequestResult.Granted)
{
Uri uri = null;
try
{
uri = LockScreen.GetImageUri();
}
catch (Exception)
{
}
List<Uri> uriList = await BuildUriList();
if (uriList.Count == 0)
{
return;
}
int index = 0;
if (uri != null && uriList.Any(uriEntry => uri.ToString() == uriEntry.ToString()))
{
index = (uriList.IndexOf(uri) + 1) % uriList.Count;
}
if (InterfaceProvider.GetIStorageService().FileExists(uriList[index]))
{
try
{
LockScreen.SetImageUri(uriList[index]);
}
catch (Exception)
{
}
}
}
}
// End of Lockscreenmanager class
You can ignore my try to shrink down the image, it didn't help (but the images are still saved correctly). What could I do?

How can I use a single bitmap for many icons in an imagelist? (without splitting it to multiple files)

I have a bitmap that contains 24x24 icons. I would like to use it in an image list for a toolstrip. Is it possible to use the bitmap and select the right portion of it based on the ImageIndex of a ToolStripButton? I would like to avoid splitting it into multiple files.
You could inherit ToolStripButton and provide a new source for the image. Here is a fairly simplistic approach; you might need to expand upon the idea for your exact needs.
public class ToolStripImageButton: ToolStripButton
{
public ToolStripImageButton()
: base()
{
}
private Image iconMap;
private Int32 iconMapIndex;
[RefreshProperties(System.ComponentModel.RefreshProperties.Repaint)]
public Image IconMap
{
get { return this.iconMap; }
set
{
this.iconMap = value;
this.RecalculateImage();
}
}
[RefreshProperties(System.ComponentModel.RefreshProperties.Repaint)]
public Int32 IconMapIndex
{
get { return this.iconMapIndex; }
set
{
this.iconMapIndex = value;
this.RecalculateImage();
}
}
protected void RecalculateImage()
{
if (base.Image != null)
{
base.Image.Dispose();
base.Image = null;
}
if (this.iconMap != null)
{
// assumes each icon is 16x16, you could add more properties for flexibility
Int32 x = this.iconMap.Width / 16;
Int32 y = this.iconMap.Width / 16;
Int32 iconCount = x * y;
if (this.iconMapIndex < 0 || this.iconMapIndex >= iconCount) return;
Int32 offsetX = (this.iconMapIndex % x) * 16;
Int32 offsetY = (this.iconMapIndex / x) * 16;
Bitmap bitmap = new Bitmap(16, 16);
using (Graphics g = Graphics.FromImage(bitmap))
g.DrawImage(
this.iconMap,
new Rectangle(0,0,bitmap.Width,bitmap.Height),
new Rectangle(offsetX, offsetY, bitmap.Width, bitmap.Height),
GraphicsUnit.Pixel);
base.Image = bitmap;
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override Image Image
{
get
{
return base.Image;
}
set
{
// ignore
}
}

Categories

Resources