CPF/CPF/Controls/Picture.cs
2024-01-15 22:53:10 +08:00

537 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Text;
using CPF.Drawing;
using System.IO;
using System.Threading;
using System.Net;
using System.Diagnostics;
using CPF.Styling;
using System.ComponentModel;
namespace CPF.Controls
{
/// <summary>
/// 显示图像支持路径、Url、Image、Bitmap、Stream、byte[]、支持GIF播放
/// </summary>
[Description("显示图像支持路径、Url、Image、Bitmap、Stream、byte[]GIF播放")]
[DefaultProperty(nameof(Source))]
public class Picture : UIElement
{
/// <summary>
/// 显示图像支持路径、Url、Image、Bitmap、Stream、byte[]、支持GIF播放
/// </summary>
public Picture()
{
}
/// <summary>
/// 加载中显示的图片
/// </summary>
[TypeConverter(typeof(StringConverter)), CPF.Design.FileBrowser(".png;.jpg;.bmp;.gif")]
[PropertyMetadata(null)]
[Description("加载中显示的图片")]
public Image LoadingImage
{
get { return GetValue<Image>(); }
set { SetValue(value); }
}
/// <summary>
/// 图片源可以是路径、Url、Drawing.Image对象、Stream、byte[]
/// </summary>
[TypeConverter(typeof(StringConverter)), CPF.Design.FileBrowser(".png;.jpg;.bmp;.gif")]
[PropertyMetadata(null)]
[Description("图片源可以是路径、Url、Drawing.Image对象、Stream、byte[]")]
public object Source
{
get { return GetValue<object>(); }
set { SetValue(value); }
}
/// <summary>
/// 图片缩放模式
/// </summary>
[UIPropertyMetadata(Stretch.None, UIPropertyOptions.AffectsMeasure), Description("图片缩放模式")]
public Stretch Stretch
{
get { return GetValue<Stretch>(); }
set { SetValue(value); }
}
/// <summary>
/// 描述如何对内容应用缩放,并限制对已命名像素类型的缩放。
/// </summary>
[Description("描述如何对内容应用缩放,并限制对已命名像素类型的缩放。")]
[UIPropertyMetadata(StretchDirection.Both, UIPropertyOptions.AffectsMeasure)]
public StretchDirection StretchDirection
{
get { return GetValue<StretchDirection>(); }
set { SetValue(value); }
}
/// <summary>
/// 获取加载成功了的图片
/// </summary>
/// <returns></returns>
public Image GetImage()
{
return img;
}
/// <summary>
/// 播放GIF
/// </summary>
public void Play()
{
if (timer != null)
{
timer.Start();
}
}
/// <summary>
/// 停止播放gif
/// </summary>
public void Stop()
{
if (timer != null)
{
timer.Stop();
}
}
Threading.DispatcherTimer timer;
bool needDisposeImg = false;
Image img;
int index = 0;
int count = 0;
void SetImage(Image img)
{
index = 0;
if (this.img != null && needDisposeImg)
{
this.img.Dispose();
}
this.img = img;
InvalidateMeasure();
Invalidate();
if (img != null && img.FrameCount > 1)
{
count = (int)img.FrameCount;
if (Root != null)
{
if (timer == null)
{
timer = new Threading.DispatcherTimer();
timer.Tick += Timer_Tick;
timer.Interval = TimeSpan.FromMilliseconds(25);
}
timer.Start();
}
}
else
{
count = 0;
if (timer != null)
{
timer.Dispose();
timer = null;
}
}
if (img != null && img != ResourceManager.ErrorImage && img != LoadingImage)
{
RaiseEvent(EventArgs.Empty, nameof(ImageLoaded));
}
}
//int frameTimer = 0;
private void Timer_Tick(object sender, EventArgs e)
{
if (Root != null && Root.LayoutManager.IsVisible(this) && !DesignMode)
{
if (img is Image image)
{
//frameTimer += 50;
//if (frameTimer >= image.FrameDelay[index])
//{
// index++;
// if (index >= count)
// {
// index = 0;
// }
// frameTimer = 0;
// image.Index = (uint)index;
//}
if (image.NextFrame == null)
{
image.NextFrame = CPF.Platform.Application.Elapsed + TimeSpan.FromMilliseconds(image.FrameDelay[0]);
image.Index = 0;
}
else
{
if (image.NextFrame <= CPF.Platform.Application.Elapsed)
{
index++;
if (index >= count)
{
index = 0;
}
image.NextFrame = image.NextFrame + TimeSpan.FromMilliseconds(image.FrameDelay[index]);
image.Index = (uint)index;
}
}
}
Invalidate();
}
}
[PropertyChanged(nameof(LoadingImage))]
void OnLoadingImage(object newValue, object oldValue, PropertyMetadataAttribute attribute)
{
var load = newValue as Image;
if (load != null && img == null)
{
SetImage(load);
}
}
[PropertyChanged(nameof(Source))]
void OnSource(object newValue, object oldValue, PropertyMetadataAttribute attribute)
{
if (newValue != null)
{
if (newValue is Image image)
{
SetImage(image);
needDisposeImg = false;
}
else if (newValue is Bitmap bitmap)
{
SetImage(bitmap);
needDisposeImg = false;
}
else if (newValue is string)
{
var s = newValue as string;
var load = LoadingImage;
if (load != null)
{
SetImage(load);
}
ResourceManager.GetImage(s, a =>
{
Invoke(() =>
{
SetImage(a);
needDisposeImg = false;
if (a == null)
{
SetImage(ResourceManager.ErrorImage);
RaiseEvent(EventArgs.Empty, nameof(ImageFailed));
}
});
});
}
else if (newValue is Stream)
{
try
{
SetImage(Image.FromStream(newValue as Stream));
needDisposeImg = true;
}
catch (Exception e)
{
Debug.WriteLine("加载图片失败:" + e.Message);
SetImage(ResourceManager.ErrorImage);
RaiseEvent(EventArgs.Empty, nameof(ImageFailed));
}
}
else if (newValue is byte[])
{
try
{
SetImage(Image.FromBuffer((byte[])newValue));
needDisposeImg = true;
}
catch (Exception e)
{
Debug.WriteLine("加载图片失败:" + e.Message);
SetImage(ResourceManager.ErrorImage);
RaiseEvent(EventArgs.Empty, nameof(ImageFailed));
}
}
}
else
{
SetImage(null);
}
}
protected override void OnAttachedToVisualTree()
{
base.OnAttachedToVisualTree();
if (count > 1 && img != null)
{
if (timer == null)
{
timer = new Threading.DispatcherTimer();
timer.Tick += Timer_Tick;
timer.Interval = TimeSpan.FromMilliseconds(50);
}
timer.Start();
}
}
protected override void OnDetachedFromVisualTree()
{
base.OnDetachedFromVisualTree();
if (timer != null)
{
timer.Dispose();
timer = null;
}
}
protected override Size MeasureOverride(in Size availableSize)
{
if (img != null && img.ImageImpl == null)
{
img = null;
}
if (img != null)
{
var naturalSize = new Size(img.Width, img.Height);
var size = availableSize;
var maxW = MaxWidth;
if (!maxW.IsAuto && maxW.Unit == Unit.Default)
{
size.Width = maxW.Value;
}
var maxH = MaxHeight;
if (!maxH.IsAuto && maxH.Unit == Unit.Default)
{
size.Height = maxH.Value;
}
Size scaleFactor = ComputeScaleFactor(size,
naturalSize,
this.Stretch, StretchDirection);
// Returns our minimum size & sets DesiredSize.
return new Size(naturalSize.Width * scaleFactor.Width, naturalSize.Height * scaleFactor.Height);
}
return base.MeasureOverride(availableSize);
}
//protected override Size ArrangeOverride(in Size finalSize)
//{
// if (img != null)
// {
// var naturalSize = new Size(img.Width, img.Height);
// Size scaleFactor = ComputeScaleFactor(finalSize,
// naturalSize,
// this.Stretch, StretchDirection);
// // Returns our minimum size & sets DesiredSize.
// return new Size(naturalSize.Width * scaleFactor.Width, naturalSize.Height * scaleFactor.Height);
// }
// return base.ArrangeOverride(finalSize);
//}
/// <summary>
/// This is a helper function that computes scale factors depending on a target size and a content size
/// </summary>
/// <param name="availableSize">Size into which the content is being fitted.</param>
/// <param name="contentSize">Size of the content, measured natively (unconstrained).</param>
/// <param name="stretch">Value of the Stretch property on the element.</param>
/// <param name="stretchDirection">Value of the StretchDirection property on the element.</param>
internal static Size ComputeScaleFactor(Size availableSize,
Size contentSize,
Stretch stretch, StretchDirection stretchDirection)
{
// Compute scaling factors to use for axes
float scaleX = 1.0f;
float scaleY = 1.0f;
bool isConstrainedWidth = !float.IsPositiveInfinity(availableSize.Width);
bool isConstrainedHeight = !float.IsPositiveInfinity(availableSize.Height);
if ((stretch == Stretch.Uniform || stretch == Stretch.UniformToFill || stretch == Stretch.Fill)
&& (isConstrainedWidth || isConstrainedHeight))
{
// Compute scaling factors for both axes
scaleX = (FloatUtil.IsZero(contentSize.Width)) ? 0f : availableSize.Width / contentSize.Width;
scaleY = (FloatUtil.IsZero(contentSize.Height)) ? 0f : availableSize.Height / contentSize.Height;
if (!isConstrainedWidth) scaleX = scaleY;
else if (!isConstrainedHeight) scaleY = scaleX;
else
{
// If not preserving aspect ratio, then just apply transform to fit
switch (stretch)
{
case Stretch.Uniform: //Find minimum scale that we use for both axes
float minscale = scaleX < scaleY ? scaleX : scaleY;
scaleX = scaleY = minscale;
break;
case Stretch.UniformToFill: //Find maximum scale that we use for both axes
float maxscale = scaleX > scaleY ? scaleX : scaleY;
scaleX = scaleY = maxscale;
break;
case Stretch.Fill: //We already computed the fill scale factors above, so just use them
break;
}
}
//Apply stretch direction by bounding scales.
//In the uniform case, scaleX=scaleY, so this sort of clamping will maintain aspect ratio
//In the uniform fill case, we have the same result too.
//In the fill case, note that we change aspect ratio, but that is okay
switch (stretchDirection)
{
case StretchDirection.UpOnly:
if (scaleX < 1.0) scaleX = 1f;
if (scaleY < 1.0) scaleY = 1f;
break;
case StretchDirection.DownOnly:
if (scaleX > 1.0) scaleX = 1f;
if (scaleY > 1.0) scaleY = 1f;
break;
case StretchDirection.Both:
break;
default:
break;
}
}
//Return this as a size now
return new Size(scaleX, scaleY);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (img != null && needDisposeImg)
{
img.Dispose();
img = null;
}
if (timer != null)
{
timer.Dispose();
timer = null;
}
}
/// <summary>
/// 图片加载失败
/// </summary>
public event EventHandler ImageFailed
{
add { AddHandler(value); }
remove { RemoveHandler(value); }
}
/// <summary>
/// 图片加载
/// </summary>
public event EventHandler ImageLoaded
{
add { AddHandler(value); }
remove { RemoveHandler(value); }
}
protected override void OnOverrideMetadata(OverrideMetadata overridePropertys)
{
base.OnOverrideMetadata(overridePropertys);
overridePropertys.Override(nameof(ClipToBounds), new UIPropertyMetadataAttribute(true, UIPropertyOptions.AffectsRender));
overridePropertys.Override(nameof(IsAntiAlias), new UIPropertyMetadataAttribute(true, UIPropertyOptions.AffectsRender));
}
protected override void OnRender(DrawingContext dc)
{
if (img != null)
{
var size = ActualSize;
var w = img.Width;
var h = img.Height;
switch (this.Stretch)
{
case Stretch.None:
dc.DrawImage(img, new Rect((size.Width - w) / 2, (size.Height - h) / 2, w, h), new Rect(0, 0, w, h));
break;
case Stretch.Fill:
dc.DrawImage(img, new Rect(0, 0, size.Width, size.Height), new Rect(0, 0, w, h));
break;
case Stretch.Uniform:
var ww = size.Width;
var hh = size.Height;
if (w / size.Width > h / size.Height)
{
hh = size.Width * h / w;
}
else
{
ww = size.Height * w / h;
}
dc.DrawImage(img, new Rect((size.Width - ww) / 2, (size.Height - hh) / 2, ww, hh), new Rect(0, 0, w, h));
break;
case Stretch.UniformToFill:
var www = size.Width;
var hhh = size.Height;
if (w / size.Width < h / size.Height)
{
hhh = size.Width * h / w;
}
else
{
www = size.Height * w / h;
}
dc.DrawImage(img, new Rect((size.Width - www) / 2, (size.Height - hhh) / 2, www, hhh), new Rect(0, 0, w, h));
break;
}
}
base.OnRender(dc);
}
}
public enum Stretch : byte
{
/// <summary>
/// 内容保持其原始大小。
/// </summary>
None,
/// <summary>
/// 调整内容大小以填充目标尺寸。 不保留纵横比。
/// </summary>
Fill,
/// <summary>
/// 在保留内容原有纵横比的同时调整内容的大小,以适合目标尺寸。
/// </summary>
Uniform,
/// <summary>
/// 在保留内容原有纵横比的同时调整内容的大小,以填充目标尺寸。 如果目标矩形的纵横比不同于源矩形的纵横比,则对源内容进行剪裁以适合目标尺寸。
/// </summary>
UniformToFill,
}
public enum StretchDirection : byte
{
/// <summary>
/// 内容仅在小于父级时扩展。 如果内容较大,则不执行缩放。
/// </summary>
UpOnly,
/// <summary>
/// 内容仅在大于父级时缩放。 如果内容较小,则不会执行任何扩展。
/// </summary>
DownOnly,
/// <summary>
/// 内容根据 Stretch 模式进行拉伸以适合父项的大小。
/// </summary>
Both
}
}