一般人将文件转图片使用以下方式:
/// <summary> /// 文件转图片(不占用文件句柄) /// </summary> /// <param name="filePath">文件路径</param> /// <param name="toWidth">生成图像宽度</param> /// <param name="toHeight">生成图像高度</param> /// <returns></returns> public static BitmapImage ToBitmapImage(String filePath, Int32? toWidth = null, Int32? toHeight = null) { if (!File.Exists(filePath)) return null var bmp = new BitmapImage() bmp.BeginInit(); bmp.CacheOption = BitmapCacheOption.OnLoad; bmp.CreateOptions = BitmapCreateOptions.PreservePixelFormat if (toWidth.HasValue) bmp.DecodePixelWidth = toWidth.Value; if (toHeight.HasValue) bmp.DecodePixelHeight = toHeight.Value using (var ms = new MemoryStream(File.ReadAllBytes(filePath))) { bmp.StreamSource = ms; bmp.EndInit(); bmp.Freeze(); return bmp; }
这里使用了OnLoad图像缓存方式,如果要使用None,那就不能释放MemoryStream,否则图像无法显示;这个缓存也不知道存到什么时候,为了能及时释放内存,就不能使用缓存,MemoryStream对象先作为变量存起来,等到Image.Unloaded触发时再销毁,代码如下:
public sealed class FileImageBehavior : Behavior<Image> { #region 依赖属性 public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register ("FilePath", typeof (String), typeof (FileImageBehavior), new PropertyMetadata (OnFilePathPropertyChanged)); private static void OnFilePathPropertyChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) { var obj = (FileImageBehavior) d; if (obj.AssociatedObject != null && obj.AssociatedObject.IsLoaded) obj.OnFilePathChanged ((String) e.NewValue); } /// <summary> /// 文件路径 /// </summary> public String FilePath { get => (String) this.GetValue (FilePathProperty); set => this.SetValue (FilePathProperty, value); } #endregion #region 私有方法 protected override void OnAttached () { base.OnAttached (); this.AssociatedObject.Loaded += OnLoaded; this.AssociatedObject.Unloaded += OnUnloaded; } protected override void OnDetaching () { base.OnDetaching (); this.AssociatedObject.Loaded -= OnLoaded; this.AssociatedObject.Unloaded -= OnUnloaded; } private void OnLoaded (Object sender, RoutedEventArgs e) { OnFilePathChanged (FilePath); } private void OnUnloaded (Object sender, RoutedEventArgs e) { this.Release (); } private void OnFilePathChanged (String file) { this.Release (); if (String.IsNullOrEmpty (file) || !File.Exists (file)) return; ms = new MemoryStream (File.ReadAllBytes (file)); var bmp = new BitmapImage (); bmp.BeginInit (); bmp.CacheOption = BitmapCacheOption.None; bmp.CreateOptions = BitmapCreateOptions.PreservePixelFormat; if (RenderWidth.HasValue) bmp.DecodePixelWidth = RenderWidth.Value; // 设置图像解码后的宽度 if (RenderHeight.HasValue) bmp.DecodePixelHeight = RenderHeight.Value; // 设置图像解码后的高度 bmp.StreamSource = ms; bmp.EndInit (); bmp.Freeze (); this.AssociatedObject.Source = bmp; } private void Release () { this.AssociatedObject.Source = null; if (ms != null) { ms.Close (); ms.Dispose (); ms = null; } } #endregion #region 属性 /// <summary> /// 显示宽度 /// </summary> public Int32? RenderWidth { get; set; } /// <summary> /// 显示高度 /// </summary> public Int32? RenderHeight { get; set; } #endregion #region 字段 private MemoryStream ms; #endregion }
使用方式:
<Image Stretch="Uniform"> <i:Interaction.Behaviors> <wl:FileImageBehavior FilePath="{Binding FilePath}" RenderWidth="180" /> </i:Interaction.Behaviors> </Image>
下面是更新,上面的使用方式过于复杂了,我发现MemoryStrean即使不手动Dispose也能释放,不会造成内存泄漏,所以从文件加载图片,使用下面的方法就可以:
/// <summary> /// 文件转图片(不占用文件句柄) /// </summary> /// <param name="filePath">文件路径</param> /// <returns></returns> public static BitmapFrame ToBitmapFrame (String filePath) { if (!File.Exists (filePath)) return null; var ms = new MemoryStream (File.ReadAllBytes (filePath)); var bmp = BitmapFrame.Create (ms, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None); bmp.Freeze (); return bmp; } /// <summary> /// 文件转图片(不占用文件句柄) /// </summary> /// <param name="filePath">文件路径</param> /// <param name="toWidth">生成图像宽度</param> /// <param name="toHeight">生成图像高度</param> /// <returns></returns> public static BitmapImage ToBitmapImage (String filePath, Int32? toWidth = null, Int32? toHeight = null) { if (!File.Exists (filePath)) return null; var bmp = new BitmapImage (); bmp.BeginInit (); bmp.CacheOption = BitmapCacheOption.None; bmp.CreateOptions = BitmapCreateOptions.PreservePixelFormat; if (toWidth.HasValue) bmp.DecodePixelWidth = toWidth.Value; if (toHeight.HasValue) bmp.DecodePixelHeight = toHeight.Value; var ms = new MemoryStream (File.ReadAllBytes (filePath)); bmp.StreamSource = ms; bmp.EndInit (); bmp.Freeze (); return bmp; }
用转换器封装就是:
public sealed class FilePathToBitmapFrameConverter : IValueConverter { public Object Convert (Object value, Type targetType, Object parameter, CultureInfo culture) { return ImageHelper.ToBitmapFrame ((String) value); } public Object ConvertBack (Object value, Type targetType, Object parameter, CultureInfo culture) { return Binding.DoNothing; } private static Lazy<FilePathToBitmapFrameConverter> lazy = new Lazy<FilePathToBitmapFrameConverter> (); public static FilePathToBitmapFrameConverter Instance => lazy.Value; } public sealed class FilePathToBitmapImageConverter : IValueConverter { public Object Convert (Object value, Type targetType, Object parameter, CultureInfo culture) { Int32? width = null, height = null; if (parameter is String args) { var maps = args.Split ('|'); foreach (var map in maps) { var kv = map.Split (':'); if (kv.Length != 2) continue; if ("w".Equals (kv[0], StringComparison.OrdinalIgnoreCase)) width = Int32.Parse (kv[1]); else if ("h".Equals (kv[0], StringComparison.OrdinalIgnoreCase)) height = Int32.Parse (kv[1]); } } return ImageHelper.ToBitmapImage ((String) value, width, height); } public Object ConvertBack (Object value, Type targetType, Object parameter, CultureInfo culture) { return Binding.DoNothing; } private static Lazy<FilePathToBitmapImageConverter> lazy = new Lazy<FilePathToBitmapImageConverter> (); public static FilePathToBitmapImageConverter Instance => lazy.Value; }
使用起来就是:
<Image Source="{Binding FilePath,Converter={x:Static wl:FilePathToBitmapImageConverter.Instance},ConverterParameter='w:200'}" /> <Image Source="{Binding FilePath,Converter={x:Static wl:FilePathToBitmapFrameConverter.Instance}}" />