Why would CGImageSource.FromUrl return null?

Hi all,

I have a piece of code that essentially is designed to load an image from gallery and retrieve the metadata from said image. I have followed the instructions in the recipe guide Access Image Metadata, but when I get to imageSource = CGImageSource.FromUrl(url, null); it always returns null.

The full code I am using to read the metadata is below, where photoPath is a string variable containing something like {assets-library://asset/asset.JPG?id=[letters-and-numbers]&ext=JPG}:

UIImage iosImage = null;
var assetLib = new AssetsLibrary.ALAssetsLibrary();
NSUrl newImage = new NSUrl(photoPath);
assetLib.AssetForUrl(newImage, delegate (AssetsLibrary.ALAsset asset)
{
    try
    {
        // UIImage strips the metadata
        iosImage = new UIImage(asset.DefaultRepresentation.GetFullScreenImage());
    }
    catch
    {
        return;
    }

    var fileNameUrl = new NSUrl(asset.DefaultRepresentation.Filename, false); // not null
    CGImageSource myImageSource;
    // myImageSource = CGImageSource.FromUrl(fileNameUrl, null); // returns null, always!
    myImageSource = CGImageSource.FromUrl(asset.AssetUrl, null); // returns null, always!
    var ns = new NSDictionary();
    var imageProperties = myImageSource.CopyProperties(ns, 0);
    var width = imageProperties[CGImageProperties.PixelWidth];
    var height = imageProperties[CGImageProperties.PixelHeight];
    Console.WriteLine("Dimensions: {0}x{1}", width, height);
});

I have tried creating the URL from both the physical filename and the asset URL, but both return null. Neither the filename or the asset URL are null themselves (checked in debug).

Why am I getting null? Is this a bug with CGImageSource or am I doing something wrong?

Thanks

Answers

  • JohnSmith.0620JohnSmith.0620 USMember ✭✭

    No-one?

  • JohnSmith.0620JohnSmith.0620 USMember ✭✭

    @Charwaka , thanks for your reply!

    I have looked at your first link, and it is no different from what I am doing (aside from the filepath). Looking at the second link (for CGImageSource.cs), it seems at line 183 it returns null if it has determined the result as IntPtr.Zero.

    I guess I'm getting a null because the filename is wrong? Given that the filepath is coming from an AssetUrl, what would be the correct filepath to use?

  • ChrisDAnnunzioChrisDAnnunzio USMember ✭✭

    I'm also seeing this behavior and haven't found a solution yet.

    I'm using the Photos Framework for accessing the Photo Library instead of ALAssetsLibrary, but getting the same result using the FullSizeImageUrl I'm obtaining from the PHContentEditingInput. The image file url looks valid, but CGImageSource.FromUrl is returning null.

  • DooksDooks ZAMember ✭✭

    I have also had an issue with this.

    I created an iOS app which should look and work the same as the existing Android app I have.

    The Android app uses a RecyclerView which loads a list of rather large images so I implemented an AsyncTask to load the images which will not slow the ListView down (It is okay if the image only appears a while after the actual View is displayed on screen as long as the frame rate is still above 60fps)
    I have a method to get the image metadata to preset the ImageView width and height so the list doesn't jump when the image is finally loaded. (I don't like the image loaders out there, I find them very heavy on memory and final APK size that is why I am doing all the work manually)

    My plan was to do the same for iOS after I realised that the images are still big and still creates a lag in the UITableView.
    iOS does not have an AsyncTask so I had to create my own AsyncTask to extend to:

    public class AsyncTask<T>
    {
        public void Execute(params T[] parameters)
        {
            PreExecute();
    
            object result;
    
            DispatchQueue.GetGlobalQueue(DispatchQueuePriority.Default).DispatchAsync(() =>
            {
                result = DoInBackground(parameters);
    
                DispatchQueue.MainQueue.DispatchAsync(() =>
                {
                    PostExecute(result);
                });
            });
        }
    
        public virtual void PreExecute()
        {
        }
    
        public virtual object DoInBackground(params T[] parameters)
        {
            return null;
        }
    
        public virtual void PostExecute(object result)
        {
        }
    }
    

    Within the list screen I created a BannerLoaderTask:

    public class BannerLoaderTask : AsyncTask<int>
    {
        private WeakReference<UIImageView> imgBannerWeakReference;
        private string BannerPath { get; set; }
        private CGSize BannerSize { get; set; }
    
        public BannerLoaderTask(UIImageView view, string BannerPath, CGSize BannerSize)
        {
            imgBannerWeakReference = new WeakReference<UIImageView>(view);
            this.BannerPath = BannerPath;
            this.BannerSize = BannerSize;
        }
    
        public override void PreExecute()
        {
            base.PreExecute();
        }
    
        public override object DoInBackground(params int[] parameters)
        {
            try
            {
                var imgBannerFile = new UIImage(BannerPath);
    
                return Methods.ResizeImage(imgBannerFile, BannerSize);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
    
                return null;
            }
        }
    
        public override void PostExecute(object result)
        {
            if (result != null && imgBannerWeakReference.TryGetTarget(out UIImageView imgBanner))
            {
                imgBanner.Image = (UIImage)result;
            }
    
            base.PostExecute(result);
        }
    }
    

    Then in the UITableViewSource in GetCell(...) I call:

    public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
    {
        ...
    
        var imgBannerFilePath = ...;
    
        // I needed to get the ImageSize without loading the image to properly set the UIImageView width and height
        // See this methods contents below
        var imgSize = Methods.GetImageSizeFromPath(imgBannerFilePath);
    
        // I scale the image for the screen size
        var newWidth = ScreenLoader.ScreenWidth - 10;
        var imgBannerSize = new CGSize(newWidth, newWidth / imgSize.Width * imgSize.Height);
    
        // The UIImageView width and height will be ready for the image once it is loaded
        var imgBanner = new UIImageView(new CGRect(5, 5, imgBannerSize.Width, imgBannerSize.Height));
    
        // I finally start the AsyncTask to set the image asynchronously 
        new BannerLoaderTask(imgBanner, imgBannerFilePath, imgBannerSize).Execute(0);
    
        ....
    }
    

    GetImageSizeFromPath:

    public static CGSize GetImageSizeFromPath(string FilePath)
    {
        // Make sure to add
        // using ImageIO;
    
        using (var src = CGImageSource.FromUrl(NSUrl.FromFilename(FilePath)))
        {
            var imgStatus = src.GetStatus(0);
            var options = new CGImageOptions() { ShouldCache = false };
    
            // MAKE SURE to provide an index or else the PixelWidth and PixelHeight will be NULL
            var properties = src.GetProperties(0, options);
    
            return new CGSize((int)properties.PixelWidth, (int)properties.PixelHeight);
        }
    }
    

    Hope this works for someone

  • RognikRognik Member ✭✭

    Solved or not?

Sign In or Register to comment.