【问题标题】:How to serialize a Class contains BitmapImage?如何序列化包含 BitmapImage 的类?
【发布时间】:2015-04-01 17:09:40
【问题描述】:

我有一个 DeepCopy 方法,它将传入参数的对象序列化并返回反序列化的对象以进行深度复制。

我的方法是:

public static class GenericCopier<T>
{     
           public static T DeepCopy(object objectToCopy)
            {
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    BinaryFormatter binaryFormatter = new BinaryFormatter();
                    binaryFormatter.Serialize(memoryStream, objectToCopy);
                    memoryStream.Seek(0, SeekOrigin.Begin);
                    return (T)binaryFormatter.Deserialize(memoryStream);
                }
            }
}

如果传递给参数的对象不包含任何 BitmapImage 字段和属性,则效果很好。

public class MyClass
{
  public string TestString {get; set;}
  public BitmapImage TestImage { get; set;}
}

如果我制作 MyClass 的 DeepCopy,

MyClass orginal = new MyClass(){ TestString = "Test"};
MyClass copy = GenericCopier<MyClass>.DeepCopy(orginal);

抛出异常

Assembly 中的“System.Windows.Media.Imaging.BitmapImage”类型未标记为可序列化

我找到了一种序列化BitmapImage的方法here

但是,我如何混合两种类型的序列化(BinaryFormatter 和 PngBitmapEncoder)来序列化 MyClass?

【问题讨论】:

  • 如果可能的话,我建议将您的图像存储为 byte[] 字段,然后在属性中重新创建它。请注意,BinaryFormatter 仅序列化字段。
  • 如果我使用 byte[] 类型而不是 BitmapImage ,我如何在 xaml 中的 Image 控件的 Source 属性中绑定 byte[]?
  • 使用 BitmapImage 属性从属性 getter 中的 byte[] 字段重建图像,这里有如何从字节数组重建图像的示例。此外,对于您的情况,这也不是最佳方式,您应该重复使用您的图像而不是复制它们,有人可以更好地解释
  • 抱歉,创建位图的深层副本有什么意义?您或许应该解释一下 GenericCopier 类的用例。肯定有比序列化更好的方法。
  • 使用序列化进行克隆/复制可能是最低效的方式。除非必须(例如跨越域或进程边界),否则不要使用序列化。

标签: c# wpf serialization deserialization bitmapimage


【解决方案1】:

你有两个选择:

选项 1:实现 ISerializable 和快照到 PNG

您必须在这里做的是让所有包含您的BitmapImage 的类实现ISerializable 接口,然后在GetObjectData 中返回一个表示图像编码的字节数组,例如PNG。然后在deserialization constructor 中将PNG 解码为新的BitmapImage

请注意,这会对图像进行快照,因此可能会丢失一些 WPF 数据。

由于您可能有多个包含BitmapImage 的类,因此最简单的方法是引入一些包装结构,其中包含与BitmapImage 的隐式转换,如下所示:

[Serializable]
public struct SerializableBitmapImageWrapper : ISerializable
{
    readonly BitmapImage bitmapImage;

    public static implicit operator BitmapImage(SerializableBitmapImageWrapper wrapper)
    {
        return wrapper.BitmapImage;
    }

    public static implicit operator SerializableBitmapImageWrapper(BitmapImage bitmapImage)
    {
        return new SerializableBitmapImageWrapper(bitmapImage);
    }

    public BitmapImage BitmapImage { get { return bitmapImage; } }

    public SerializableBitmapImageWrapper(BitmapImage bitmapImage)
    {
        this.bitmapImage = bitmapImage;
    }

    public SerializableBitmapImageWrapper(SerializationInfo info, StreamingContext context)
    {
        byte[] imageBytes = (byte[])info.GetValue("image", typeof(byte[]));
        if (imageBytes == null)
            bitmapImage = null;
        else
        {
            using (var ms = new MemoryStream(imageBytes))
            {
                var bitmap = new BitmapImage();
                bitmap.BeginInit();
                bitmap.CacheOption = BitmapCacheOption.OnLoad;
                bitmap.StreamSource = ms;
                bitmap.EndInit();
                bitmapImage = bitmap;
            }
        }
    }

    #region ISerializable Members

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        byte [] imageBytes;
        if (bitmapImage == null)
            imageBytes = null;
        else
            using (var ms = new MemoryStream())
            {
                BitmapImage.SaveToPng(ms);
                imageBytes = ms.ToArray();
            }
        info.AddValue("image", imageBytes);
    }

    #endregion
}

public static class BitmapHelper
{
    public static void SaveToPng(this BitmapSource bitmap, Stream stream)
    {
        var encoder = new PngBitmapEncoder();
        SaveUsingEncoder(bitmap, stream, encoder);
    }

    public static void SaveUsingEncoder(this BitmapSource bitmap, Stream stream, BitmapEncoder encoder)
    {
        BitmapFrame frame = BitmapFrame.Create(bitmap);
        encoder.Frames.Add(frame);
        encoder.Save(stream);
    }

    public static BitmapImage FromUri(string path)
    {
        var bitmap = new BitmapImage();
        bitmap.BeginInit();
        bitmap.UriSource = new Uri(path);
        bitmap.EndInit();
        return bitmap;
    }
}

然后按如下方式使用:

[Serializable]
public class MyClass
{
    SerializableBitmapImageWrapper testImage;

    public string TestString { get; set; }
    public BitmapImage TestImage { get { return testImage; } set { testImage = value; } }
}

public static class GenericCopier
{
    public static T DeepCopy<T>(T objectToCopy)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(memoryStream, objectToCopy);
            memoryStream.Seek(0, SeekOrigin.Begin);
            return (T)binaryFormatter.Deserialize(memoryStream);
        }
    }
}

选项 2:使用序列化代理直接克隆 BitmapImage

原来BitmapImage 有一个Clone() 方法,所以有理由问:是否有可能以某种方式覆盖二进制序列化以用克隆替换原始,而不实际序列化它?这样做可以避免快照到 PNG 的潜在数据丢失,因此看起来更可取。

事实上,这可以使用serialization surrogates 将位图图像替换为IObjectReference 代理,其中包含代理创建的克隆副本的ID。

public static class GenericCopier
{
    public static T DeepCopy<T>(T objectToCopy)
    {
        var selector = new SurrogateSelector();
        var imageSurrogate = new BitmapImageCloneSurrogate();
        imageSurrogate.Register(selector);

        BinaryFormatter binaryFormatter = new BinaryFormatter(selector, new StreamingContext(StreamingContextStates.Clone));

        using (MemoryStream memoryStream = new MemoryStream())
        {
            binaryFormatter.Serialize(memoryStream, objectToCopy);
            memoryStream.Seek(0, SeekOrigin.Begin);
            return (T)binaryFormatter.Deserialize(memoryStream);
        }
    }
}

class CloneWrapper<T> : IObjectReference
{
    public T Clone { get; set; }

    #region IObjectReference Members

    object IObjectReference.GetRealObject(StreamingContext context)
    {
        return Clone;
    }

    #endregion
}

public abstract class CloneSurrogate<T> : ISerializationSurrogate where T : class
{
    readonly Dictionary<T, long> OriginalToId = new Dictionary<T, long>();
    readonly Dictionary<long, T> IdToClone = new Dictionary<long, T>();

    public void Register(SurrogateSelector selector)
    {
        foreach (var type in Types)
            selector.AddSurrogate(type, new StreamingContext(StreamingContextStates.Clone), this);
    }

    IEnumerable<Type> Types
    {
        get
        {
            yield return typeof(T);
            yield return typeof(CloneWrapper<T>);
        }
    }

    protected abstract T Clone(T original);

    #region ISerializationSurrogate Members

    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        var original = (T)obj;
        long cloneId;
        if (original == null)
        {
            cloneId = -1;
        }
        else
        {
            if (!OriginalToId.TryGetValue(original, out cloneId))
            {
                Debug.Assert(OriginalToId.Count == IdToClone.Count);
                cloneId = OriginalToId.Count;
                OriginalToId[original] = cloneId;
                IdToClone[cloneId] = Clone(original);
            }
        }
        info.AddValue("cloneId", cloneId);
        info.SetType(typeof(CloneWrapper<T>));
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        var wrapper = (CloneWrapper<T>)obj;
        var cloneId = info.GetInt64("cloneId");
        if (cloneId != -1)
            wrapper.Clone = IdToClone[cloneId];
        return wrapper;
    }

    #endregion
}

public sealed class BitmapImageCloneSurrogate : CloneSurrogate<BitmapImage>
{
    protected override BitmapImage Clone(BitmapImage original)
    {
        return original == null ? null : original.Clone();
    }
}

在这个实现中,你的主类保持不变:

[Serializable]
public class MyClass
{
    BitmapImage testImage;

    public string TestString { get; set; }
    public BitmapImage TestImage { get { return testImage; } set { testImage = value; } }
}

尴尬的是,虽然BitmapImage 有一个Clone 方法,但它实际上并没有实现ICloneable 接口。如果有,上面的代码看起来会更干净,因为我们可以简单地克隆每个可克隆的对象,而不是为BitmapImage 调用特定的方法。

【讨论】:

  • Bitmap.Create 函数不适用于我的位图中的相对 URI。我想我不明白图像在 wpf 中是如何工作的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-11
  • 1970-01-01
  • 2011-10-26
  • 1970-01-01
相关资源
最近更新 更多