【WinRT】【译】【加工】在 XAML 中制作圆形图片

原文地址:http://timheuer.com/blog/archive/2015/05/06/making-circular-images-in-xaml-easily.aspx

前阵子似乎一些比较酷的程序开始使用圆形头像来取代之前方形或者圆角边的显示方式了。我(原文作者。下文中如果没特别提到,均指原文作者)在两年前注意到一些 App 开始这样做的时候,做出了一个偏激的发言:

看看吧,程序里会越来越多圆形的头像了,方形的将不会再有了

——Tim Heuer(@timheuer) 2013 年 5 月 23 日

现在,这似乎变成一种流行的趋势了,因为大家都是这么做,我想使用 XAML 来开发的程序猿们都知道怎么实现的吧。然而,我却仍然看到有很多人在问这个问题,一些人尝试去实现,却变得更加复杂。因此,我想我应该发表一篇博客来阐述这个问题。我曾经看到过愚蠢的人们将原来的图片通过算法来裁剪,然后存储到硬盘,再显示给他们的用户看。这是完全没必要的,其实我们很简单就可以做到这种显示效果。

<Ellipse Width="250" Height="250">
    <Ellipse.Fill>
        <ImageBrush ImageSource="ms-appx:///highfive.jpg" />
    </Ellipse.Fill>
</Ellipse>

你会看见,第三行我们使用一个 ImageBrush 来填充一个 Ellipse。使用一个 Ellipse 可以帮助我们得到一个精确的圆形裁剪,并且不会出现毛边的状况。上面这段代码会显示成如下效果:

现在,尽管这是 ok 的。但是,在 Windows 8.1 中,使用 ImageBrush 将不会得到自动根据需要渲染大小来进行解码的功能。

注意:自动根据需要渲染大小来解码的这个功能是指即使一个图片是比较大的,但只解码需要渲染的大小。所以,如果你有一个 2000 像素乘以 2000 像素大小的图片,但仅仅需要渲染成 100 像素乘以 100 像素大小的时候,我们将图片解码为 100 像素乘以 100 像素的话,就可以节省大量的内存了。

对于绝大部分的程序来说,可能已经存放好符合我们所需要的大小了。这是没问题的。然而,对于社交程序或者其它你不知道图片来源的程序,它们上传到服务器的时候是不会对图片调整大小的。那么,你将会想通过解码到指定的大小来节省你的内存。这是很容易在 XAML 中做到的,仅仅需要复杂一点的语法。将上面那段 XAML 修改成这样子:

<Ellipse Width="250" Height="250">
    <Ellipse.Fill>
        <ImageBrush>
            <ImageBrush.ImageSource>
                <BitmapImage DecodePixelHeight="250" DecodePixelWidth="250" UriSource="ms-appx:///highfive.jpg" />
            </ImageBrush.ImageSource>
        </ImageBrush>
    </Ellipse.Fill>
</Ellipse>

不需要很大的改动,在第 5 行使用 DecodePixelHeightDecodePixelWidth 来告诉系统框架解码的大小。渲染出来的效果跟上面是一样的。当你需要显示比原图小的时候,这个技巧是十分有效的,而不是去使用其它奇淫技巧。

所以你们快点去帮助那些为了显示圆形头像而陷入发狂状态的码农们!希望能够帮到他们。

译者(h82258652)注:本文为意译,尽可能将原文作者想告诉大家的东西翻译过来给大家。如果有任何疑难,请查阅原文。谢谢!

下面进入译者我的加工部分:

一般来说,我们还是比较习惯做成一个控件的,总不可能每次用到圆形图像的话,去写上面这么一大堆。下面我们就来动手干!

在 Visual Studio 中新建一个用户控件(UserControl),我们命名为 CircleImage。

然后在后台代码中定义一个依赖属性 Source,表示图片的源。由于 BitmapImage 的 UriSource 是 Uri 类型的,因此我们的 Source 属性也是 Uri 类型。Source 变化时,我们设置到 XAML 中的 BitmapImage 的 UriSource 属性上去。

public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(Uri), typeof(CircleImage), new PropertyMetadata(null, SourceChanged));

private static void SourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var obj = (CircleImage)d;
    var value = (Uri)e.NewValue;
    obj.bitmapImage.UriSource = value;
}

public Uri Source
{
    get
    {
        return (Uri)GetValue(SourceProperty);
    }
    set
    {
        SetValue(SourceProperty, value);
    }
}

然后开始编写前台 XAML:

<UserControl
    x:Class="MyApp.CircleImage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">
    <Ellipse>
        <Ellipse.Fill>
            <ImageBrush>
                <ImageBrush.ImageSource>
                    <BitmapImage x:Name="bitmapImage"
                                 DecodePixelWidth="1"
                                 DecodePixelHeight="1" />
                </ImageBrush.ImageSource>
            </ImageBrush>
        </Ellipse.Fill>
    </Ellipse>
</UserControl>

将 BitmapImage 命名为 bitmapImage,给上面的后台 cs 代码使用。

这里可能你会奇怪,为什么我将解码的大小写成了 1?这是因为,如果解码大小写成小于 1 的整数的话,就等于没有自动根据渲染大小来解码的功能了,那就跟一开始原文作者的效果一样。所以这里我先写成 1,运行的时候再根据控件的大小来动态调整。

那么既然要动态调整,那么我们必须得完善后台代码了,添加一些代码上去,修改成这样:

public sealed partial class CircleImage : UserControl
{
    public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(Uri), typeof(CircleImage), new PropertyMetadata(null, SourceChanged));

    private static void SourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var obj = (CircleImage)d;
        var value = (Uri)e.NewValue;
        obj.bitmapImage.UriSource = value;
    }

    public Uri Source
    {
        get
        {
            return (Uri)GetValue(SourceProperty);
        }
        set
        {
            SetValue(SourceProperty, value);
        }
    }

    public CircleImage()
    {
        this.InitializeComponent();
        this.SizeChanged += CircleImage_SizeChanged;// 监听控件大小发生变化。
    }

    private void ReSize()
    {
        // 计算新的解码大小,向上取整。
        int width = (int)Math.Ceiling(this.ActualWidth);
        int height = (int)Math.Ceiling(this.ActualHeight);

        // 确保解码大小必须大于 0,因为上面的结果可能为 0。
        bitmapImage.DecodePixelWidth = Math.Max(width, 1);
        bitmapImage.DecodePixelHeight = Math.Max(height, 1);

        // 让 BitmapImage 重新渲染。
        var temp = bitmapImage.UriSource;
        bitmapImage.UriSource = null;
        bitmapImage.UriSource = temp;
    }

    private void CircleImage_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        ReSize();
    }
}

这样我们就完成了这个控件了,接下来我们来测试下究竟这个东西威力有多大。

测试:

测试图片使用我博客的背景图片,足够大的了,1920*1080,相信应该会吃掉不少内存^-^。

图片地址:http://images.cnblogs.com/cnblogs_com/h82258652/693238/o_wallpaper_summer2013_1920X1080.jpg

测试程序代码:

前台 XAML 代码:

<Page
    x:Class="MyApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0" HorizontalAlignment="Center" Orientation="Horizontal">
            <Button Content="加载旧版" Click="BtnOld_Click" />
            <Button Content="加载新版" Click="BtnNew_Click" />
        </StackPanel>
        <GridView Grid.Row="1" x:Name="gvwBigMemory">
            <GridView.ItemTemplate>
                <DataTemplate>
                    <Ellipse Width="100" Height="100">
                        <Ellipse.Fill>
                            <ImageBrush ImageSource="http://images.cnblogs.com/cnblogs_com/h82258652/693238/o_wallpaper_summer2013_1920X1080.jpg" />
                        </Ellipse.Fill>
                    </Ellipse>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>
        <GridView Grid.Row="2" x:Name="gvwMinMemory">
            <GridView.ItemTemplate>
                <DataTemplate>
                    <local:CircleImage Width="100" Height="100" Source="http://images.cnblogs.com/cnblogs_com/h82258652/693238/o_wallpaper_summer2013_1920X1080.jpg" />
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>
    </Grid>
</Page>

都放在 GridView 里面,大小都设定为 100*100。

后台代码:

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
    }

    private void BtnOld_Click(object sender, RoutedEventArgs e)
    {
        gvwBigMemory.ItemsSource = Enumerable.Range(1, 100);
    }

    private void BtnNew_Click(object sender, RoutedEventArgs e)
    {
        gvwMinMemory.ItemsSource = Enumerable.Range(1, 100);
    }
}

很简单,就是让 GridView 加载 100 个。

好,走起!

初始运行占用 40.6 MB 内存,当然这个值可能下次运行就不一样了,多少会有点波动。

接下来我们加载旧版:

令人吃惊,一瞬间就跑到 250.6 MB 内存了。

那我们再来看看新的版本,记得先将上面这个关掉,重新打开,否则影响结果。

新版:

44.6 MB!基本没发生任何的变化。可见威力很强大,说明我们的代码起作用了。

结语:

可以见到这点小小的优化能带来多大的影响。另外由于 GridView 默认使用了虚拟化,所以实际上并没有加载到 100 个,但是仍然可以见到旧版的内存占用十分厉害, 所以根本没法想象真真正正加载 100 个的时候有多壮观。小小的细节可能会引起巨大的变化,考虑到还有众多 512 MB 内存的 Windows Phone 用户,这点小小的细节仍然很有必要去做的。

时间: 2024-10-13 00:45:49

【WinRT】【译】【加工】在 XAML 中制作圆形图片的相关文章

在android中画圆形图片的几种办法

在开发中经常会有一些需求,比如显示头像,显示一些特殊的需求,将图片显示成圆角或者圆形或者其他的一些形状.但是往往我们手上的图片或者从服务器获取到的图片都是方形的.这时候就需要我们自己进行处理,将图片处理成所需要的形状.正如茴香豆的的"茴"写法大于一种,经过我的研究,画出特殊图片的方法也不是一种,我发现了三种,且听我一一道来. 使用Xfermode 两图相交方式 通过查找资料发现android中可以设置画笔的Xfermode即相交模式,从而设置两张图相交之后的显示方式,具体模式见下图,源

winform 制作圆形图片框

1 public partial class CirclePictureBox : PictureBox 2 { 3 public CirclePictureBox() 4 { 5 Circle = true; 6 InitializeComponent(); 7 } 8 9 protected override void OnPaint(PaintEventArgs pe) 10 { 11 base.OnPaint(pe); 12 } 13 14 //圆形大小随控件大小变化 15 protec

(转)在android中画圆形图片的几种办法

http://blog.csdn.net/isming/article/details/39404407

自定义圆形图片

圆形图片相必是项目开发中也是不少用的一个知识点吧. 那么这里学习一下简单的制作圆形图片,主要运用 BitmapShader 类的知识来实现 一.首先,了解一下 BitmapShader 类 BitmapShader是Shader的子类,可以通过Paint.setShader(Shader shader)进行设置 看一下BitmapShader 的构造方法 BitmapShader bitmapShader = new BitmapShader(bitmap,TileMode, TileMode)

Android Glide+CircleImageView实现加载圆形图片列表

需求:要在列表中实现圆形图片的显示,控件可能和加载库会存在冲突 先上代码,至于其中源码,以后有空再分析 MainActivity public class MainActivity extends Activity { ArrayList<String> fileNames = new ArrayList<String>(); // 本地图片路径 ImageAdapter imageAdapter; @Override protected void onCreate(Bundle

Windows Phone 8.1中圆形图片或头像的制作、优化以及Stretch的四个属性值的区别

开篇之前,认识一下Stretch的四个不同的属性值: 现在很多应用的头像或者其他UI设计都偏向于圆形,当然也存在方块形的Metro风格的布局.在Win10手机预览版里的 联系人头像都改成圆形显示了.其实真不知道微软怎么想的,有些自己创造或者发起的东西自己又不坚持了,然后被 苹果安卓的发扬光大,这不是给别人做嫁妆嘛.这样市场怎么起得来,真是恨铁不成钢啊. 算了,废话一大堆,在程序员看来,有需求就必须要实现,也必须有实现的方法.很简单方法如下: 两种方式,前者普遍,后者重在优化. <Grid> &

Android 两种制作圆形/圆角图片的方法

前言: 目前网上有很多圆角图片的实例,Github上也有一些成熟的项目.之前做项目,为了稳定高效都是选用Github上的项目直接用.但这种结束也是Android开发必备技能 ,所以今天就来简单研究一下该技术,分享给大家. 预备知识: Xfermode介绍: 下面是Android ApiDemo里的"Xfermodes"实例,效果图. Xfermode有三个子类,结构如下: public class Xfermode extends Object java.lang.Object ? a

怎样在Ps中制作水晶气泡?这样的方法很合适

因为工作需要会对场景进行布置,那首先要做的就是绘制出效果图,而水晶气泡是绘制过程中不可缺少的重要因素,怎样制作水晶气泡呢?下面小编教你在Ps中制作水晶气泡的操作技巧,希望可以帮助到大家.1.首先打开ps,在左上角文件页面中新建一个画布,尺寸的大小根据自己的需要进行设置就可以.2.气泡的颜色也就是前景色的颜色是要进行设置的,点击如图所示地方,选中喜欢的颜色按住Alt+Delete键对颜色进行填充.3.在左侧工具栏中选择椭圆工具,按住shift键画一个圆,选择前景色为白色进行填充.这个步骤需要新建图

Xaml中string(字符串)常量的定义以及空格的处理

(1)基本用法 xaml中可以实例化各种对象,比如在ResourceDictionary中定义字符串常量: <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system ="clr-namespace:Sy