Xamarin.Forms: How to load an image from Resources into a byte-array?

23,517

Solution 1

You can easily do this, by reading the resource stream from the assembly:

var assembly = this.GetType().GetTypeInfo().Assembly; // you can replace "this.GetType()" with "typeof(MyType)", where MyType is any type in your assembly.
byte[] buffer;
using (Stream s = assembly.GetManifestResourceStream("SymbolMann.jpg”))
{
    long length = s.Length;
    buffer = new byte[length];
    s.Read(buffer, 0, (int)length);
}
// Do something with buffer

EDIT:

To get the assembly that holds the embedded resources of your project you need to call the GetTypeInfo method on a type included in that assembly. Hence, typeof(X), where "X" is any type in your assembly. For example, if you are using a custom page called MyCustomPage:

public class MyCustomPage : ContentPage {}

... this is what you would pass: typeof(MyCustomPage)

You need any instance of type Type (this is what the typeof keyword returns basically). The alternative is to call the GetType() method on any object that is included in your project.

Note that you will not be able to get the correct assembly if you call typeof(X) on a type that is not included in your project. Eg. calling typeof(ContentPage).GetTypeInfo().Assembly, would return the assembly that the ContentPage type resides in. Which is not the one that includes your resources. I hope this is not confusing. If it is, please let me know.

Now, the GetTypeInfo method is an extension method included in the System.Reflection namespace. When the text editor in Xamarin Studio does not recognize something, it highlights it with red. Right-clicking on that will give you a Resolve option in the context menu:

Right-click in Xamarin Studio

If the type or method can be resolved, it will show that option.

More on images in Xamarin Forms here.

Solution 2

Thanks @FredWenger and @Dimitris - here's my solution based on both your answers:

  1. Add your image to the Xamarin Forms (Portable) project wherever you like. I just went for the root: MyProject\DaffyDuck.jpg
  2. Mark the image as an Embedded Resource (file properties -> build action)
  3. Call the function referencing the Embedded Resource like this: ImageDataFromResource("MyProject.DaffyDuck.jpg")
    Notice that you need to include the project name to get the assembly name *.
  4. This is the function:

    public byte[] ImageDataFromResource(string r)
    {
        // Ensure "this" is an object that is part of your implementation within your Xamarin forms project
        var assembly = this.GetType().GetTypeInfo().Assembly; 
        byte[] buffer = null;
    
        using (System.IO.Stream s = assembly.GetManifestResourceStream(r))
        {
            if (s != null)
            {
                long length = s.Length;
                buffer = new byte[length];
                s.Read(buffer, 0, (int)length);
            }
        }
    
        return buffer;
    }
    
  5. If you need to reference the loaded data in an ImageView control do it thus:
    ImageView.Source = ImageSource.FromStream(() => new MemoryStream(imageData));

*NB: If step 3 doesn't work and you want to check your embedded resource names call this: var names = assembly.GetManifestResourceNames(); and see what is listed in your project.

Solution 3

First, many thanks to Dimitris who has guided me to the right direction! 

Conclusion:

The base:
I work with VS2013 - update 2, Xamarin.Forms (1.4.1-Pre1) and my app is based on the template “Blank App (Xamarin.Forms Shared)” (not PCL) and I use all platforms (Android, iOS, WP).

On a user-registration-page the user can select an image as avatar (that then later is showed to his postings). There are two default-images (one for male one for female), that are showed, depending of the sex of the user. The user then can overtake one of the default-images or select another image from the media-picker.
The default-images were stored on the default-locations for content-images (for Android e.g. under /Resources/Drawable/).
The images were showed in a XF-Standard-Image-Object with Image-Source = “xxx.jpg”.
This has worked without problems.

The problem:
As the user-data (including avatar) are stored on a SQL-Server (over a Json-web-service), I have to convert the image-data to a byte-array (to send it then with the other data as string to the web-service).
The problem was, to load the images from the default-directories and convert it to a byte-array (whereby the file-access was the problem -> not reachable).

The solution:
Thanks Dimitris, that have guided me in the correct direction, I have implemented the following solution:

In all projects (Android, iOS and WP), I have added a new directory “\Embedded\” (directly under the root). Then, I have copied my two jpg’s (“SymbolMann.jpg” and "SymbolFrau.jpg”) in this directory (for all Platforms).
Then, I have changed the resource-type to the images from e.g. “AndroidResource” to “Embedded Resource” (for all platforms the same type).

In my Startup-Code to the app (app.cs), I have added:

//
string cNameSpace = "";
switch (Device.OS)
{
  case TargetPlatform.WinPhone:
    cNameSpace = "MatrixGuide.WinPhone";
    break;
  case TargetPlatform.iOS:
    cNameSpace = "MatrixGuide.iOS";
    break;
  case TargetPlatform.Android:
    cNameSpace = "MatrixGuide.Droid";
    break;
}
GV.cEmbeddedAblage = cNameSpace + ".Embedded.";

where GV.cEmbeddedAblage is simply a global variable.
To checkout the namespace-names, I had to right-click the projects for each platform and have a look at the settled namespace-name (can varies in your project).
The .Embedded. is the new created directory (same name on all platforms).

Additionally, I have created global byte-arrays for the male-image (GV.ByteSymbolMann) and for the female-image (GV.ByteSymbolFrau).

In the startup-Code of the registration-page, I have added:

string cFilename = "";
if (GV.ByteSymbolMann == null) // not yet loaded
{
  cFilename = GV.cEmbeddedAblage + "SymbolMann.jpg";
  var assembly = this.GetType().GetTypeInfo().Assembly; 
  byte[] buffer;
  using (Stream s = assembly.GetManifestResourceStream(cFilename))
   {
     long length = s.Length;
     buffer = new byte[length];
     s.Read(buffer, 0, (int)length);
     GV.ByteSymbolMann = buffer; // Overtake byte-array in global variable for male
   }
}
//
if (GV.ByteSymbolFrau == null) // not yet loaded
{
  cFilename = GV.cEmbeddedAblage + "SymbolFrau.jpg";
  var assembly = this.GetType().GetTypeInfo().Assembly; 
  byte[] buffer;
  using (Stream s = assembly.GetManifestResourceStream(cFilename))
   {
     long length = s.Length;
     buffer = new byte[length];
     s.Read(buffer, 0, (int)length);
     GV.ByteSymbolFrau = buffer; // Overtake byte-array in global variable for female
    }
}

The code to loading the .jpg into the image-object, I had to change from:

//Avatar.Source = "SymbolMann.jpg";  // Load from default directory

to

Avatar.Source = ImageSource.FromStream(() => new MemoryStream(GV.ByteSymbolMann)); // Load (stream) from byte-array

I then takeover the selected byte-array (parallel to showing the image) in a further byte-array (for later upload to the web-service as string)

AvatarErfassung = GV.ByteSymbolMann;

If the user change the selection (e.g. to woman):

Avatar.Source = ImageSource.FromStream(() => new MemoryStream(GV.ByteSymbolFrau)); 
AvatarErfassung = GV.ByteSymbolFrau;

Note: If the user select another image from mediapicker, I have directly access to the stream, so this was never the problem.

This solution works for all platforms (Android, iOS, WP).

I hope this may help some other users… 

And... finally thanks to Dimitris

Solution 4

Adding on to noelicus's answer:

Setup

  1. Add your image to the root folder of the Xamarin.Forms project, e.g. MyProject\pizza.png

  2. In Visual Studio, right-click on the image file and select Build Action -> EmbeddedResource

enter image description here

Code

public byte[] GetImageFromFile(string fileName)
{
    var applicationTypeInfo = Application.Current.GetType().GetTypeInfo();

    byte[] buffer;
    using (var stream = applicationTypeInfo.Assembly.GetManifestResourceStream($"{applicationTypeInfo.Namespace}.fileName"))
    {
        if (stream != null)
        {
            long length = stream.Length;
            buffer = new byte[length];
            stream.Read(buffer, 0, (int)length);
        }
    }

    return buffer;
}

Example

public class MyPage() : ContentPage
{
    public MyPage()
    {
        var imageAsByteArray = GetImageFromFile("pizza.png");

        var pizzaImage = new Image();
        pizzaImage.Source = ImageSource.FromStream(() => new MemoryStream(imageAsByteArray));

        Content = pizzaImage;
    }

    byte[] GetImageFromFile(string fileName)
    {
         var applicationTypeInfo = Application.Current.GetType().GetTypeInfo();

        byte[] buffer;
        using (var stream = applicationTypeInfo.Assembly.GetManifestResourceStream($"{applicationTypeInfo.Namespace}.fileName"))
        {
            if (stream != null)
            {
                long length = stream.Length;
                buffer = new byte[length];
                stream.Read(buffer, 0, (int)length);
            }
        }

        return buffer;
    }
}

Solution 5

I had a lot of problems getting this to work. My project was already using FFImageLoading elsewhere, and I found this to be the easiest way to load the .png as a stream:

using (var stream = await ImageService.Instance.LoadCompiledResource("resource_image.png").AsPNGStreamAsync())
{
    localBitmap = SKBitmap.Decode(stream);
}

Make sure your resource files have the same name in both iOS and Droid projects. They'll probably already have their build action set to 'BundleResource' (iOS) or 'AndroidResource' (Droid).

Sure, you'll have to install the FFImageLoading Nuget, but you'll be glad you did.

Share:
23,517
FredyWenger
Author by

FredyWenger

Updated on July 09, 2022

Comments

  • FredyWenger
    FredyWenger almost 2 years

    I have a (hopefully) simple question (I don’t have found an answer, that fit’s by all of my searches).

    I work with Xamarin.Forms 1.4.1-Pre-1. In my app, I have:

    byte[] AvatarErfassung; // Bytearray, to later update the webservice
    var Avatar = new Image { HeightRequest = 71, WidthRequest = 61, HorizontalOptions = LayoutOptions.Start };
    Avatar.Source = "SymbolMann.jpg";  
    

    where the image "SymbolMann.jpg” is included as project-resource (in each project) and showed on a page (without problems).
    I now want to put the image in a byte-array to send it to our webservice. I don't have found any way to access the image "SymbolMann.jpg" (to load it in a byte-array) or to use (however) the Avatar.Source therefore.

    Question:
    How to get the image “SymbolMann.jpg” into the byte-array “AvatarErfassung” ?

    Thanks for every answer

    Hi Dimitris

    Thanks for your (fast) replay.
    As I wrote, I work with Xamarin.Forms.
    The images are stored as resources: in \Resources\Drawable\ (for Android), \Resources\ (for iOS) and in the root (for WP). I have to load the image on a content-page.

    If I overtake your code, to the line:

    var assembly = this.GetType().GetTypeInfo().Assembly;
    

    I have the error-message (translated to English):
    “System.Type don’t contain a definition for GetTypeInfo method (is a using missing?)”

    Do I have to add a specific using?

    You write: // you can replace "this.GetType()" with "typeof(MyType)", where MyType is any type in your assembly.

    What do I have to place as type?

    = typeof(????).GetTypeInfo().Assembly:

    Thanks for a further reply.

    Update #2:

    Firts, thanks for your patience…

    Unfortunately in VS2013, I don’t find any function “resolve” that shows me the missing using - but I have find out the missing using my self :-)
    In the meantime I have added using System.Reflection;

    Now, the line: var assembly = this.GetType().GetTypeInfo().Assembly;

    is resolved and compiled without error

    Further, I have changed the type of the .jpg from “Android Resource” to “Embedded Ressource”.
    The App then crashes in the line: long length = s.Length; as it seems that s is null (I think the jpg is not found)

    Further - if I change the type to “Embedded Ressource” - the image is not found any more by Xamarin.Forms (Avatar.Source = "SymbolMann.jpg";)

    If I change the type of the .jpg to “Compile” I can’t compile the app.
    Error-message: A namespace can’t contain directly members like fields or methods.

    So… what is the correct type to the resource (so that it can be loaded with assembly.GetManifestResourceStream()?

    Do I have to add something to the filename (SymbolMann.jpg) so that it will be found?

    How do I have to change Avatar.Source = "SymbolMann.jpg" so that it is found (if I change the type from “Android Resource” to anything else)?

    Once again my needs:
    On a registration-page, default-images (symbols) are showed as avatar for man and woman in a standard-Xamarin.Forms image (avatar.source = “SymbolMann.jpg” / “SymbolFrau.jpg”).
    The .jpg’s are stored in the standard-directories for each project (Android, iOS and WP) where the are accessible without problems from the image-object.
    The user then can take one of the default-images or load another one over mediapicker.
    If the user the tap the button “Register”, I have to create a byte-array from the Image to send it via json to our webservice.
    If the user select another image via mediapicker, I have access to the stream, the problem is, to become a byte-array from the default-images (in every platform).

    Once again thanks for your help...