简体   繁体   中英

Artifacts in UIImage when loaded in background

I'm trying to load images for GMGridView cells. The issue is that the image loading process is not that fast so I decided to go multithreading. I created a good all-in-one class for background image loading. Here are it's contents:

    public void LoadImageIntoView (string imageURL, UIImageView imageView, int index)
    {
        rwl.AcquireReaderLock (Timeout.Infinite);
        if (disposed)
            return;

        UIImage image;
        lock (locker) {
            cache.TryGetValue (imageURL, out image);
        }
        if (image != null)
            imageView.Image = image;
        else {
            new Thread (() => {
                if (MediaLoader.IsFileCached (imageURL))
                    LoadImage (index, imageURL);
                else {
                    MediaLoader loader = new MediaLoader ();
                    loader.OnCompleteDownload += (object sender, OnCompleteDownloadEventArgs e) => {
                        if (e.Success)
                            LoadImage (index, e.FileURL);
                    };
                    loader.GetFileAsync (imageURL, false, DownloadPriority.Low);
                }
            }).Start ();
        }
        rwl.ReleaseReaderLock ();
    }

    private void LoadImage (int index, string imageURL)
    {
        rwl.AcquireReaderLock (Timeout.Infinite);
        if (disposed)
            return;

        string pathToFile = MediaLoader.GetCachedFilePath (imageURL);

        UIImage uiImage = UIImage.FromFile (pathToFile);;

        // Load the image
        if (uiImage != null) {
            lock (locker) {
                cache [imageURL] = uiImage;
            }
            BeginInvokeOnMainThread (() => InsertImage (false, index, uiImage));
        }
        rwl.ReleaseReaderLock ();
    }

    private void InsertImage (bool secondTime, int index, UIImage image)
    {
        rwl.AcquireReaderLock (Timeout.Infinite);
        if (disposed)
            return;

        UIImageView imageView = FireGetImageViewCallback (index);

        if (imageView != null) {
            CATransition transition = CATransition.CreateAnimation ();
            transition.Duration = 0.3f;
            transition.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseInEaseOut);
            transition.Type = CATransition.TransitionFade;
            imageView.Layer.AddAnimation (transition, null);

            imageView.Image = image;
        } else {
            if (!secondTime) {
                new Thread (() => {
                    Thread.Sleep (150);
                    BeginInvokeOnMainThread (() => InsertImage (true, index, image));
                }).Start ();
            }
        }
        rwl.ReleaseReaderLock ();
    }

I have also tried this code for image loading inside the LoadImage method:

        UIImage loadedImage = UIImage.FromFile (pathToFile);
        CGImage image = loadedImage.CGImage;
        if (image != null) {
            CGColorSpace colorSpace = CGColorSpace.CreateDeviceRGB ();
            // Create a bitmap context from the image's specifications
            CGBitmapContext bitmapContext = new CGBitmapContext (null, image.Width, image.Height, image.BitsPerComponent, image.Width * 4, colorSpace, CGImageAlphaInfo.PremultipliedFirst);
            bitmapContext.ClearRect (new System.Drawing.RectangleF (0, 0, image.Width, image.Height));
            // Draw the image into the bitmap context and retrieve the
            // decompressed image

            bitmapContext.DrawImage (new System.Drawing.RectangleF (0, 0, image.Width, image.Height), image);
            CGImage decompressedImage = bitmapContext.ToImage ();

            // Create a UIImage
            uiImage = new UIImage (decompressedImage);

            // Release everything
            colorSpace.Dispose ();
            decompressedImage.Dispose ();
            bitmapContext.Dispose ();
            image.Dispose ();
        }

When I build and try my app it appears that from time to time images returned by my ImageLoader have artifacts inside them. Sometimes it can be white rectangles at random locations, sometimes it can be some unexpectedly colored pixels. I'll be very happy to hear a solution to this problem as the app is about to go to AppStore and this issue is a big headache.

PS FireGetImageViewCallback returns an UIImageView via a delegate which I set in the class's constructor. Cache is a Dictionary , locker is just an object, rwl is a ReaderWriterLock instance.

The problem was solved by using GCD instead of usual C# threading. It puts the task into the queue which makes them run one after another, but not simultaneously. This worked perfect except the fact that when you scroll down the huge list of images and all of them go into the queue, it will take much time for currently visible rows to be filled with images. That's why I applied some sort of optimization: when my ImageLoader's LoadImageIntoView method is called, it is also provided with an index, so ImageLoader knows which row was acquired last. In the task I check whether the cell, the image of which is going to be downloaded, is currently visible and if not it simply returns, allowing the next task to execute. Here's some code that illustrates this approach:

    private void LoadImage (int index, string imageURL)
    {
        DispatchQueue.GetGlobalQueue (DispatchQueuePriority.Low).DispatchAsync (() => {
            rwl.AcquireReaderLock (Timeout.Infinite);
            if (disposed)
                return;

            bool shouldDownload = false;
            lastAcquiredIndexRwl.AcquireReaderLock (Timeout.Infinite);
            shouldDownload = index <= (lastAcquiredIndex + visibleRange) && index >= (lastAcquiredIndex - visibleRange);
            lastAcquiredIndexRwl.ReleaseReaderLock ();

            if (shouldDownload) {
                string pathToFile = MediaLoader.GetCachedFilePath (imageURL);

                UIImage uiImage = null;

                // Load the image
                CGDataProvider dataProvider = new CGDataProvider (pathToFile);

                CGImage image = null;
                if (pathToFile.IndexOf (".png") != -1)
                    image = CGImage.FromPNG (dataProvider, null, false, CGColorRenderingIntent.Default);
                else
                    image = CGImage.FromJPEG (dataProvider, null, false, CGColorRenderingIntent.Default);

                if (image != null) {
                    CGColorSpace colorSpace = CGColorSpace.CreateDeviceRGB ();

                    // Create a bitmap context from the image's specifications
                    CGBitmapContext bitmapContext = new CGBitmapContext (null, image.Width, image.Height, image.BitsPerComponent, image.Width * 4, colorSpace, CGImageAlphaInfo.PremultipliedFirst | (CGImageAlphaInfo)CGBitmapFlags.ByteOrder32Little);
                    colorSpace.Dispose ();  

                    bitmapContext.ClearRect (new System.Drawing.RectangleF (0, 0, image.Width, image.Height));

                    // Draw the image into the bitmap context and retrieve the
                    // decompressed image   
                    bitmapContext.DrawImage (new System.Drawing.RectangleF (0, 0, image.Width, image.Height), image);
                    image.Dispose ();
                    CGImage decompressedImage = bitmapContext.ToImage ();
                    bitmapContext.Dispose ();

                    uiImage = new UIImage (decompressedImage);
                    decompressedImage.Dispose ();
                }

                if (uiImage != null) {
                    lock (locker) {
                        cache [imageURL] = uiImage;
                    }
                    DispatchQueue.MainQueue.DispatchAsync (() => InsertImage (false, index, uiImage));
                }
            }
            rwl.ReleaseReaderLock ();
        });
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM