Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pack URI to image embedded in a resx file

How do I construct a pack URI to an image that is in a resource file?

I have an assembly called MyAssembly.Resources.dll, it has a folder called Images, then in there is a resource file called Assets.resx. This resource file contains my image (called MyImage.png). The line of code I have is:

uri = new Uri("pack://application:,,,/MyAssembly.Resources,Culture=neutral,PublicKeyToken=null;component/Images/Assets/MyImage.png");

However when I try to supply this URI to the constructor of a new BitmapImage I get an IOException with the message

Cannot locate resource 'images/assets/myimage.png'.

Note that I have other loose images in the same assembly which I can retrieve fine using a pack URI, those images have their build action set to Resource but they are not embedded in a resx file. Should I be including the name of the resx file in the path?

(I am looking to embed images in resx files so that I can leverage UI culture settings to retrieve the right image (the image contains text)).

like image 207
slugster Avatar asked May 07 '13 01:05

slugster


4 Answers

I don't think it's possible using the "pack" protocol scheme. This protocol is related to normalized Open Packaging Conventions specs (http://tools.ietf.org/id/draft-shur-pack-uri-scheme-05.txt for pointers). So the pack uri points to the application package's resources (or parts in OPC terms), not to .NET embedded resources.

However, you can define your own scheme, for example "resx" and use it in WPF component uris. New Uri schemes for such usages can be defined using WebRequest.RegisterPrefix.

Here is an example based on a small Wpf application project named "WpfApplication1". This application has a Resource1.resx file defined (and possibly other localized corresponding Resource1 files, like Resource1.fr-FR.resx for french for example). Each of these ResX files define an Image resource named "img" (note this name is not the same as the image file name the resource is based on).

Here is the MainWindow.xaml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Image Source="resx:///WpfApplication1.Resource1/img" />
</Window>

The uri format is this:

resx://assembly name/resource set name/resource name

and assembly name is optional, so

resx:///resource set name/resource name

is also valid and point to resources in the main assembly (my sample uses this)

This is the code that supports it, in App.xaml.cs or somewhere else, you need to register the new scheme:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        ResXWebRequestFactory.Register();
        base.OnStartup(e);
    }
}

And the scheme implementation:

public sealed class ResXWebRequestFactory : IWebRequestCreate
{
    public const string Scheme = "resx";
    private static ResXWebRequestFactory _factory = new ResXWebRequestFactory();

    private ResXWebRequestFactory()
    {
    }

    // call this before anything else
    public static void Register()
    {
        WebRequest.RegisterPrefix(Scheme, _factory);
    }

    WebRequest IWebRequestCreate.Create(Uri uri)
    {
        return new ResXWebRequest(uri);
    }

    private class ResXWebRequest : WebRequest
    {
        public ResXWebRequest(Uri uri)
        {
            Uri = uri;
        }

        public Uri Uri { get; set; }

        public override WebResponse GetResponse()
        {
            return new ResXWebResponse(Uri);
        }
    }

    private class ResXWebResponse : WebResponse
    {
        public ResXWebResponse(Uri uri)
        {
            Uri = uri;
        }

        public Uri Uri { get; set; }

        public override Stream GetResponseStream()
        {
            Assembly asm;
            if (string.IsNullOrEmpty(Uri.Host))
            {
                asm = Assembly.GetEntryAssembly();
            }
            else
            {
                asm = Assembly.Load(Uri.Host);
            }

            int filePos = Uri.LocalPath.LastIndexOf('/');
            string baseName = Uri.LocalPath.Substring(1, filePos - 1);
            string name = Uri.LocalPath.Substring(filePos + 1);

            ResourceManager rm = new ResourceManager(baseName, asm);
            object obj = rm.GetObject(name);

            Stream stream = obj as Stream;
            if (stream != null)
                return stream;

            Bitmap bmp = obj as Bitmap; // System.Drawing.Bitmap
            if (bmp != null)
            {
                stream = new MemoryStream();
                bmp.Save(stream, bmp.RawFormat);
                bmp.Dispose();
                stream.Position = 0;
                return stream;
            }

            // TODO: add other formats
            return null;
        }
    }
}
like image 56
Simon Mourier Avatar answered Oct 16 '22 19:10

Simon Mourier


There are two ways to "embed" a resource in an assembly. Windows Forms uses the Embedded Resource Build Action. WPF expects resources contained in assemblies to be marked with the Resource Build Action.

When you use the Resx editor in Visual Studio to add an image, it marks it as an Embedded Resource. Also, it stores it as type System.Drawing.Bitmap. WPF expect a System.Windows.Media.ImageSource type.

If you have a dissembler (like ILSpy) you can look at impact of setting different build actions on the files.

Sample ImagesLib project

Here is a screenshot of a project with two images. It's obvious from the names, the cat_embedded.jpg is using the Embedded Resource Build action and the cat_resource.jpg is using the Resource Build action.

enter image description here

This is what they look like in ILSpy.

enter image description here

See how the cat_resource.jpg file is within the ImageLib.g.resources section? That is where WPF looks for resources. The path to the file is part of the resource name (images/cat_resource.jpg). So when you use a path like:

var uri = new Uri("pack://application:,,,/ImageLib;component/Images/cat_resource.jpg");

you specify the matching path after the word ;component.

The other jpg file is located in a different location in the assembly, and uses periods in the name (ImageLib.Images.cat_embedded.jpg).

You can try many permutations of that string to try and get the cat_embedded.jpg image, but WPF won't find it.

RESX Editor

Here's another project, that has two images, one marked as a resource and one added by the resx editor.

enter image description here

And here is the disassembled screenshot.

enter image description here

As you can see, the resx image is using the same URI location as the earlier embedded image example. It appears in your case, you are not going to be able to get the images from the resx file using the Pack URI.

Localization

From what you said in your question, what you are trying to accomplish is localization of the images right?

Have you looked at this MSDN article?

WPF Globalization and Localization Overview

like image 39
Walt Ritscher Avatar answered Oct 16 '22 19:10

Walt Ritscher


As Walt has correctly stated, what you get out of a resx file is a System.Drawing.Bitmap. So this needs to be converted to a System.Windows.Media.ImageSource or subtype.

I'm not sure if this falls under time wasters for you because it does not employ an URI, but here is how I get images from resx files in another library. I use a simple proxy because the resx designer file only exposes an internal constructor (even if the class is public), then define a ValueConverter that will provide the ImageSource.

enter image description here

<Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:WpfApplication1"
            xmlns:resx="clr-namespace:MyAssembly.Resources;assembly=MyAssembly.Resources"
            Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <resx:AssetsProxy x:Key="Assets" />
        <resx:BitmapToImageSourceConverter x:Key="BitmapConverter" />
    </Window.Resources>

    <Image Source="{Binding myimage, Source={StaticResource Assets}, Converter={StaticResource BitmapConverter}}" />
</Window>

AssetsProxy:

namespace MyAssembly.Resources
{
    public class AssetsProxy : Images.Assets
    {
        public AssetsProxy() : base() { }
    }
}

Bitmap to ImageSource conversion:

using System;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Windows.Data;
using System.Windows.Media.Imaging;

namespace MyAssembly.Resources
{
    /// <summary>
    /// Converts a BitmapImage, as provided by a resx resource, into an ImageSource/BitmapImage
    /// </summary>
    public class BitmapToImageSourceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            BitmapImage bitmapImage = null;
            if (value is System.Drawing.Image)
            {
                bitmapImage = ((System.Drawing.Image)value).ToBitmapImage();
            }
            return bitmapImage;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public static class BitmapExtensions
    {
        /// <summary>
        /// Converts the System.Drawing.Image to a System.Windows.Media.Imaging.BitmapImage
        /// </summary>
        public static BitmapImage ToBitmapImage(this System.Drawing.Image bitmap)
        {
            BitmapImage bitmapImage = null;
            if (bitmap != null)
            {
                using (MemoryStream memory = new MemoryStream())
                {
                    bitmapImage = new BitmapImage();
                    bitmap.Save(memory, ImageFormat.Png);
                    memory.Position = 0;
                    bitmapImage.BeginInit();
                    bitmapImage.StreamSource = memory;
                    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                    bitmapImage.EndInit();
                }
            }
            return bitmapImage;
        }
    }
}
like image 5
Mike Fuchs Avatar answered Oct 16 '22 20:10

Mike Fuchs


I described a component for using resx images in WPF in this blog post: http://wpfglue.wordpress.com/2012/05/31/localization-revisited/ . You will find more posts about using resx resources in WPF under http://wpfglue.wordpress.com/category/localization/

In these posts, I don't use pack uris, but markup extensions.

like image 2
hbarck Avatar answered Oct 16 '22 20:10

hbarck