齐越消息中心
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

705 lines
24 KiB

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Schedulers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Resources;
using dw = System.Drawing;
namespace SJ.Controls
{
public class BAsyncImage : Control
{
#region DependencyProperty
public static readonly DependencyProperty DecodePixelWidthProperty = DependencyProperty.Register("DecodePixelWidth",
typeof(double), typeof(BAsyncImage), new PropertyMetadata(0.0));
public static readonly DependencyProperty LoadingTextProperty =
DependencyProperty.Register("LoadingText", typeof(string), typeof(BAsyncImage), new PropertyMetadata("Loading..."));
public static readonly DependencyProperty IsLoadingProperty =
DependencyProperty.Register("IsLoading", typeof(bool), typeof(BAsyncImage), new PropertyMetadata(false));
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(BAsyncImage));
public static readonly DependencyProperty DefaultUrlSourceProperty = DependencyProperty.Register("DefaultUrlSource", typeof(string), typeof(BAsyncImage), new PropertyMetadata(""));
public static readonly DependencyProperty UrlSourceProperty =
DependencyProperty.Register("UrlSource", typeof(string), typeof(BAsyncImage), new PropertyMetadata(string.Empty, new PropertyChangedCallback((s, e) =>
{
var asyncImg = s as BAsyncImage;
if (asyncImg.LoadEventFlag)
{
asyncImg.Load();
}
})));
public static readonly DependencyProperty FailUrlSourceProperty =
DependencyProperty.Register("FailUrlSource", typeof(string), typeof(BAsyncImage), new PropertyMetadata(string.Empty));
public static readonly DependencyProperty IsCacheProperty = DependencyProperty.Register("IsCache", typeof(bool), typeof(BAsyncImage), new PropertyMetadata(true));
public static readonly DependencyProperty StretchProperty = DependencyProperty.Register("Stretch", typeof(Stretch), typeof(BAsyncImage), new PropertyMetadata(Stretch.Uniform));
public static readonly DependencyProperty CacheGroupProperty = DependencyProperty.Register("CacheGroup", typeof(string), typeof(BAsyncImage), new PropertyMetadata("QLAsyncImage_Default"));
#endregion
#region Property
private static readonly HttpClient httpClient = new HttpClient();
public double StaticImageActualPixelWidth { get; set; } = 0;
public double StaticImageActualPixelHeight { get; set; } = 0;
public const string LocalRegex = @"^([C-J]):\\([^:&]+\\)*([^:&]+).(jpg|jpeg|png|gif)$";
public const string HttpRegex = @"^((https|http):\/\/)?([^\\*+@]+)$";
private Image _image;
private dw.Bitmap gifBitmap;
private bool LoadEventFlag = false;
private static ConcurrentDictionary<string, ImageSource> ImageCacheList;
private static ConcurrentDictionary<string, byte[]> GifImageCacheList;
private static LimitedConcurrencyLevelTaskScheduler httpTaskScheduler;
public double DecodePixelWidth
{
get { return (double)GetValue(DecodePixelWidthProperty); }
set { SetValue(DecodePixelWidthProperty, value); }
}
public string LoadingText
{
get { return GetValue(LoadingTextProperty) as string; }
set { SetValue(LoadingTextProperty, value); }
}
public bool IsLoading
{
get { return (bool)GetValue(IsLoadingProperty); }
set { SetValue(IsLoadingProperty, value); }
}
public string UrlSource
{
get { return GetValue(UrlSourceProperty) as string; }
set { SetValue(UrlSourceProperty, value); }
}
/// <summary>
/// 仅限Resourcesl路径
/// </summary>
public string FailUrlSource
{
get { return GetValue(FailUrlSourceProperty) as string; }
set { SetValue(FailUrlSourceProperty, value); }
}
public ImageSource ImageSource
{
get { return GetValue(ImageSourceProperty) as ImageSource; }
set { SetValue(ImageSourceProperty, value); }
}
public bool IsCache
{
get { return (bool)GetValue(IsCacheProperty); }
set { SetValue(IsCacheProperty, value); }
}
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
public string CacheGroup
{
get { return GetValue(CacheGroupProperty) as string; }
set { SetValue(CacheGroupProperty, value); }
}
#endregion
#region RouteEvent
public delegate void QLAsyncImageLoadCompleteHandler(object sender, QLAsyncImageLoadCompleteEventArgs e);
public static readonly RoutedEvent OnLoadCompleteEvent = EventManager.RegisterRoutedEvent("OnLoadComplete", RoutingStrategy.Bubble, typeof(QLAsyncImageLoadCompleteHandler), typeof(BAsyncImage));
public event QLAsyncImageLoadCompleteHandler OnLoadComplete
{
add { AddHandler(OnLoadCompleteEvent, value); }
remove { RemoveHandler(OnLoadCompleteEvent, value); }
}
#endregion
#region Method
static BAsyncImage()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BAsyncImage), new FrameworkPropertyMetadata(typeof(BAsyncImage)));
ImageCacheList = new ConcurrentDictionary<string, ImageSource>();
GifImageCacheList = new ConcurrentDictionary<string, byte[]>();
httpTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10);
}
public BAsyncImage()
{
Loaded += QLAsyncImage_Loaded;
Unloaded += QLAsyncImage_Unloaded;
}
private void QLAsyncImage_Unloaded(object sender, RoutedEventArgs e)
{
Reset();
LoadEventFlag = false;
}
private void QLAsyncImage_Loaded(object sender, RoutedEventArgs e)
{
Init();
}
public override void OnApplyTemplate()
{
Init();
}
private void Init([CallerMemberName] string eventName = "")
{
if (LoadEventFlag)
return;
_image = GetTemplateChild("image") as Image;
if (_image == null)
return;
LoadEventFlag = true;
Load();
}
/// <summary>
/// Delete local bitmap resource
/// Reference: http://msdn.microsoft.com/en-us/library/dd183539(VS.85).aspx
/// </summary>
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DeleteObject(IntPtr hObject);
/// <summary>
/// 重置图像
/// </summary>
private void Reset()
{
//停止播放Gif动画
if (gifBitmap != null)
StopGif();
SetSource(null);
}
private void Load()
{
if (_image == null)
return;
Reset();
if (!string.IsNullOrEmpty(UrlSource))
{
var url = UrlSource;
var failUrl = FailUrlSource;
var pixelWidth = (int)DecodePixelWidth;
var isCache = IsCache;
var cacheKey = string.Format("{0}_{1}", CacheGroup, url);
IsLoading = !ImageCacheList.ContainsKey(cacheKey) && !GifImageCacheList.ContainsKey(cacheKey);
#region 读取缓存
if (ImageCacheList.ContainsKey(cacheKey))
{
var source = ImageCacheList[cacheKey];
SetSource(source);
SetStaticImageActualPixelSize(source.Width, source.Height);
LoadComplete(string.Empty, url);
return;
}
else if (GifImageCacheList.ContainsKey(cacheKey))
{
PlayGif(GifImageCacheList[cacheKey]);
LoadComplete(string.Empty, url);
return;
}
#endregion
this.Load(url, failUrl, cacheKey, isCache, pixelWidth);
}
}
private void Load(string url, string failUrl, string cacheKey, bool isCache, int pixelWidth)
{
var errorMessage = string.Empty;
//解析路径类型
var pathType = ValidatePathType(url);
if (pathType == PathType.Invalid)
{
LoadFail(failUrl);
LoadComplete(errorMessage, url);
return;
}
if (pathType == PathType.Http)
{
////先加载默认图
//if (!string.IsNullOrEmpty(defaultUrl))
// LoadLocal(defaultUrl, failUrl, PathType.Resources, defaultUrl, true, 0, excuteComplete: false); //默认图不触发加载完毕事件
LoadHttp(url, failUrl, cacheKey, isCache, pixelWidth);
}
else
LoadLocal(url, failUrl, pathType, cacheKey, isCache, pixelWidth);
}
/// <summary>
/// 加载失败图像
/// </summary>
/// <param name="failUrl"></param>
private void LoadFail(string failUrl)
{
byte[] imgBytes = null;
string errorMessage = string.Empty;
var pathType = ValidatePathType(failUrl);
if (pathType == PathType.Invalid)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"LoadFail 无效的路径 {failUrl}");
Console.ResetColor();
return;
}
if (pathType == PathType.Local)
imgBytes = LoadBytesFromLocal(failUrl, out errorMessage);
else if (pathType == PathType.Resources)
imgBytes = LoadBytesFromApplicationResource(failUrl, out errorMessage);
if (string.IsNullOrEmpty(errorMessage) && imgBytes != null)
AnalysisBytes(imgBytes, failUrl, true, 0, out errorMessage);
if (string.IsNullOrEmpty(errorMessage))
return;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"LoadFail {errorMessage} {failUrl}");
Console.ResetColor();
}
private void LoadLocal(string url, string failUrl, PathType pathType, string cacheKey, bool isCache, int pixelWidth, bool excuteComplete = true)
{
byte[] imgBytes = null;
var errorMessage = string.Empty;
if (pathType == PathType.Local)
imgBytes = LoadBytesFromLocal(url, out errorMessage);
else if (pathType == PathType.Resources)
imgBytes = LoadBytesFromApplicationResource(url, out errorMessage);
if (string.IsNullOrEmpty(errorMessage) && imgBytes != null)
AnalysisBytes(imgBytes, cacheKey, isCache, pixelWidth, out errorMessage);
if (!string.IsNullOrEmpty(errorMessage))
{
LoadFail(failUrl);
Console.ForegroundColor = ConsoleColor.Red;
}
Console.WriteLine($"LoadLocal {errorMessage} {url}");
Console.ResetColor();
if (excuteComplete)
LoadComplete(errorMessage, url);
return;
}
private void LoadHttp(string url, string failUrl, string cacheKey, bool isCache, int pixelWidth)
{
Task.Factory.StartNew(() =>
{
//Thread.Sleep(2000);
Console.WriteLine($"LoadHttp Start {url}");
var errorMessage = string.Empty;
var imgBytes = LoadBytesFromHttp(url, out errorMessage);
if (string.IsNullOrEmpty(errorMessage) && imgBytes != null)
AnalysisBytes(imgBytes, cacheKey, isCache, pixelWidth, out errorMessage);
if (!string.IsNullOrEmpty(errorMessage))
{
LoadFail(failUrl);
Console.ForegroundColor = ConsoleColor.Red;
}
Console.WriteLine($"LoadHttp Completed {errorMessage} {url}");
Console.ResetColor();
LoadComplete(errorMessage, url);
return;
}, CancellationToken.None, TaskCreationOptions.None, httpTaskScheduler);
}
private void AnalysisBytes(byte[] imgBytes, string cacheKey, bool isCache, int pixelWidth, out string errorMessage)
{
errorMessage = string.Empty;
#region 读取文件类型
var imgType = GetImageType(imgBytes);
if (imgType == ImageType.Invalid)
{
imgBytes = null;
errorMessage = "Invalid ImageFile";
return;
}
#endregion
#region 加载图像
if (imgType != ImageType.Gif)
{
//加载静态图像
var imgSource = LoadStaticImage(cacheKey, imgBytes, pixelWidth, isCache, out errorMessage);
if (imgSource == null)
return;
SetStaticImageActualPixelSize(imgSource.Width, imgSource.Height);
SetSource(imgSource);
}
else
{
var frameCount = 0;
using (var memoryStream = new System.IO.MemoryStream(imgBytes))
{
//读取gif帧数
var decoder = BitmapDecoder.Create(memoryStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
frameCount = decoder.Frames.Count;
decoder = null;
}
if (frameCount > 1)
{
CacheGifBytes(cacheKey, imgBytes, isCache);
PlayGif(imgBytes);
}
else
{
//gif只有1帧,视为静态图处理
var imgSource = LoadStaticImage(cacheKey, imgBytes, pixelWidth, isCache, out errorMessage);
if (imgSource == null)
return;
SetStaticImageActualPixelSize(imgSource.Width, imgSource.Height);
SetSource(imgSource);
}
}
#endregion
}
/// <summary>
/// 加载静态图像
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="imgBytes"></param>
/// <param name="pixelWidth"></param>
/// <param name="isCache"></param>
/// <returns></returns>
private ImageSource LoadStaticImage(string cacheKey, byte[] imgBytes, int pixelWidth, bool isCache, out string errorMessage)
{
errorMessage = string.Empty;
if (ImageCacheList.ContainsKey(cacheKey))
return ImageCacheList[cacheKey];
var bit = new BitmapImage() { CacheOption = BitmapCacheOption.OnLoad };
try
{
bit.BeginInit();
if (pixelWidth != 0)
{
bit.DecodePixelWidth = pixelWidth;
}
bit.StreamSource = new System.IO.MemoryStream(imgBytes);
bit.EndInit();
bit.Freeze();
if (isCache && !ImageCacheList.ContainsKey(cacheKey))
ImageCacheList.TryAdd(cacheKey, bit);
}
catch (Exception ex)
{
errorMessage = $"LoadStaticImage Error {ex.Message}";
bit = null;
}
return bit;
}
/// <summary>
/// 加载Gif图像动画
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="imgBytes"></param>
/// <param name="pixelWidth"></param>
/// <param name="isCache"></param>
/// <returns></returns>
private void CacheGifBytes(string cacheKey, byte[] imgBytes, bool isCache)
{
if (isCache && !GifImageCacheList.ContainsKey(cacheKey))
GifImageCacheList.TryAdd(cacheKey, imgBytes);
}
private byte[] LoadBytesFromHttp(string url, out string errorMessage)
{
errorMessage = string.Empty;
try
{
return httpClient.GetByteArrayAsync(url).Result;
}
catch (Exception ex)
{
errorMessage = $"Dowdload Error {ex.Message}";
}
return null;
}
private byte[] LoadBytesFromLocal(string path, out string errorMessage)
{
errorMessage = string.Empty;
if (!System.IO.File.Exists(path))
{
errorMessage = "File No Exists";
return null;
}
try
{
return System.IO.File.ReadAllBytes(path);
}
catch (Exception ex)
{
errorMessage = $"Load Local Error {ex.Message}";
return null;
}
}
private byte[] LoadBytesFromApplicationResource(string path, out string errorMessage)
{
errorMessage = string.Empty;
try
{
StreamResourceInfo streamInfo = Application.GetResourceStream(new Uri(path, UriKind.RelativeOrAbsolute));
if (streamInfo.Stream.CanRead)
{
using (streamInfo.Stream)
{
var bytes = new byte[streamInfo.Stream.Length];
streamInfo.Stream.Read(bytes, 0, bytes.Length);
return bytes;
}
}
}
catch (Exception ex)
{
errorMessage = $"Load Resource Error {ex.Message}";
return null;
}
return null;
}
private void SetSource(ImageSource source)
{
Dispatcher.BeginInvoke((Action)delegate
{
if (_image != null)
_image.Source = source;
});
}
/// <summary>
/// 更新图像实际像素
/// </summary>
/// <param name="pixelWidth"></param>
private void SetStaticImageActualPixelSize(double pixelWidth, double pixelHeight)
{
Dispatcher.Invoke(() =>
{
StaticImageActualPixelWidth = pixelWidth;
StaticImageActualPixelHeight = pixelHeight;
});
}
private void PlayGif(byte[] imgBytes)
{
gifBitmap = new dw.Bitmap(new System.IO.MemoryStream(imgBytes));
if (dw.ImageAnimator.CanAnimate(gifBitmap))
{
SetStaticImageActualPixelSize(gifBitmap.Width, gifBitmap.Height);
dw.ImageAnimator.Animate(gifBitmap, OnGifFrameChanged);
}
else
{
gifBitmap.Dispose();
}
}
private void StopGif()
{
dw.ImageAnimator.StopAnimate(gifBitmap, OnGifFrameChanged);
gifBitmap.Dispose();
}
private void OnGifFrameChanged(object sender, EventArgs e)
{
dw.ImageAnimator.UpdateFrames();
var currentFrameImageSource = GetBitmapSource();
if (currentFrameImageSource != null)
currentFrameImageSource.Freeze();
SetSource(currentFrameImageSource);
}
/// <summary>
/// 加载完成
/// </summary>
/// <param name="errorMessage"></param>
/// <param name="url"></param>
private void LoadComplete(string errorMessage, string url)
{
this.Dispatcher.BeginInvoke((Action)delegate
{
IsLoading = false;
var args = new QLAsyncImageLoadCompleteEventArgs(OnLoadCompleteEvent, this)
{
IsSuccess = string.IsNullOrEmpty(errorMessage),
UrlSource = url,
ErrorMessage = errorMessage,
StaticImageActualPixelWidth = StaticImageActualPixelWidth,
StaticImageActualPixelHeight = StaticImageActualPixelHeight
};
this.RaiseEvent(args);
});
}
/// <summary>
/// 从System.Drawing.Bitmap中获得当前帧图像的BitmapSource
/// </summary>
/// <returns></returns>
private ImageSource GetBitmapSource()
{
IntPtr handle = IntPtr.Zero;
try
{
handle = gifBitmap.GetHbitmap();
return Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, System.Windows.Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}
catch
{
return null;
}
finally
{
if (handle != IntPtr.Zero)
{
DeleteObject(handle);
}
}
}
public void Refresh()
{
Load();
}
private PathType ValidatePathType(string path)
{
if (path.StartsWith("pack://"))
return PathType.Resources;
else if (Regex.IsMatch(path, BAsyncImage.LocalRegex, RegexOptions.IgnoreCase))
return PathType.Local;
else if (Regex.IsMatch(path, BAsyncImage.HttpRegex, RegexOptions.IgnoreCase))
return PathType.Http;
else
return PathType.Invalid;
}
private ImageType GetImageType(byte[] bytes)
{
var type = ImageType.Invalid;
try
{
var fileHead = Convert.ToInt32($"{bytes[0]}{bytes[1]}");
if (!Enum.IsDefined(typeof(ImageType), fileHead))
{
type = ImageType.Invalid;
}
else
{
type = (ImageType)fileHead;
}
}
catch (Exception ex)
{
type = ImageType.Invalid;
Console.WriteLine($"获取图片类型失败 {ex.Message}");
}
return type;
}
/// <summary>
/// 清楚缓存
/// </summary>
/// <param name="cacheKey">缓存Key,格式: CacheGroup_UrlSource</param>
public static void ClearCache(string cacheKey = "")
{
if (string.IsNullOrEmpty(cacheKey))
{
ImageCacheList.Clear();
GifImageCacheList.Clear();
return;
}
ImageCacheList.Remove(cacheKey, out _);
GifImageCacheList.Remove(cacheKey, out _);
}
public static ImageSource GetImageCache(string cacheKey)
{
if (ImageCacheList.ContainsKey(cacheKey))
return ImageCacheList[cacheKey];
return null;
}
#endregion
}
public enum PathType
{
Invalid = 0, Local = 1, Http = 2, Resources = 3
}
public enum ImageType
{
Invalid = 0, Gif = 7173, Jpg = 255216, Png = 13780, Bmp = 6677
}
public class QLAsyncImageLoadCompleteEventArgs : RoutedEventArgs
{
public QLAsyncImageLoadCompleteEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { }
public bool IsSuccess { get; set; }
public string UrlSource { get; set; }
public string ErrorMessage { get; set; }
/// <summary>
/// 当加载静态图时的实际像素宽度
/// </summary>
public double StaticImageActualPixelWidth { get; set; }
/// <summary>
/// 当加载静态图时的实际像素高度
/// </summary>
public double StaticImageActualPixelHeight { get; set; }
}
}