zoukankan      html  css  js  c++  java
  • 计算照片的面积(UWP篇)

    今天先说UWP应用程序上计算照片面积的方法,改天有空,再说说WPF篇。

    其实计算照片面积的原理真TMD简单,只要你有本事读到照片的像素高度和宽度,以及水平/垂直方向上的分辨率(DPI)就可以了。计算方法也很容易,把像素值除以DPI,得到的是照片的宽度或高度,单位是英寸。

    通常咱们计算面积是按平方米来算(不信你问问数码摄影店的伙计们),也可以按平方厘米来算。没关系,只要算出平方厘米,你就知道怎么转为平方米了。英寸和厘米的换算是:

    1 inch = 2.54 cm

    好,思想工作做完了,接下来就是开工。

    首先,定义一个封装照片信息的类,UI和代码分离嘛,最好这样做,把要用的数据都进行封装,这样程序看起来也高大上,至少装逼是没问题的。

        public sealed class PhotoInfo : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
    
            private void OnPropertyChanged([CallerMemberName]string prop = "")
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
            }
    
    
            #region 私有字段
            // 宽、高,单位像素
            uint m_width = default(uint), m_height = default(uint);
            // 分辨率
            double m_dpix = default(double), m_dpiy = default(double);
            // 面积,单位为平方厘米
            double m_area = default(double);
            // 文件名
            string m_filename = null;
            #endregion
    
            #region    公共属性
            /// <summary>
            /// 照片宽度
            /// </summary>
            public uint Width
            {
                get { return m_width; }
                private set
                {
                    if (m_width != value)
                    {
                        m_width = value;
                        OnPropertyChanged();
                    }
                }
            }
    
            /// <summary>
            /// 照片高度
            /// </summary>
            public uint Height
            {
                get { return m_height; }
                private set
                {
                    if (value != m_height)
                    {
                        m_height = value;
                        OnPropertyChanged();
                    }
                }
            }
    
            /// <summary>
            /// 水平分辨率
            /// </summary>
            public double DpiX
            {
                get { return m_dpix; }
                private set
                {
                    if (value != m_dpix)
                    {
                        m_dpix = value;
                        OnPropertyChanged();
                    }
                }
            }
    
            /// <summary>
            /// 垂直分辨率
            /// </summary>
            public double DpiY
            {
                get { return m_dpiy; }
                private set
                {
                    if (m_dpiy != value)
                    {
                        m_dpiy = value;
                        OnPropertyChanged();
                    }
                }
            }
    
            /// <summary>
            /// 面积,平方厘米
            /// </summary>
            public double Area
            {
                get { return m_area; }
                private set
                {
                    if (m_area != value)
                    {
                        m_area = value;
                        OnPropertyChanged();
                    }
                }
            }
    
            /// <summary>
            /// 文件名
            /// </summary>
            public string FileName
            {
                get { return m_filename; }
                private set
                {
                    if (m_filename != value)
                    {
                        m_filename = value;
                        OnPropertyChanged();
                    }
                }
            }
    
            #endregion
    
            #region 公共方法
            public async Task ProcessFileAsync(StorageFile file)
            {
                FileName = file.Name;
                using (IRandomAccessStream stream = await file.OpenReadAsync())
                {
                    // 解码
                    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(BitmapDecoder.JpegDecoderId, stream);
                    // 读取各个值
                    Width = decoder.PixelWidth;
                    Height = decoder.PixelHeight;
                    DpiX = decoder.DpiX;
                    DpiY = decoder.DpiY;
                }
                // 计算面积
                double w = Width / DpiX;  //英寸
                double h = Height / DpiY; //英寸
                // 1 inch = 2.54 cm
                Area = w * 2.54d * h * 2.54d;
            }
            #endregion
        }

    这个类的代码有点长,不要紧,都是一堆属性,重点的是那个异步方法,从照片文件进行解码,然后读出图片大小、分辨率等信息。

                    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(BitmapDecoder.JpegDecoderId, stream);
                    // 读取各个值
                    Width = decoder.PixelWidth;
                    Height = decoder.PixelHeight;
                    DpiX = decoder.DpiX;
                    DpiY = decoder.DpiY;

    老周相信你没有忘记BitmapDecoder这个东东,如果你忘了,请写一份3万字的检讨书,并提交到应用商店。解码后,从DpiX和DpiY两个属性就能读到分辨率;从PixelWidth和PixelHeight两个属性可以得到用像素表示的宽度和高度。

    好,需要的数据都齐全了,然后计算图片的宽高的英寸表示值。

                double w = Width / DpiX;  //英寸
                double h = Height / DpiY; //英寸

    最后,就可以计算单张照片的面积了,我这里用的单位是平方厘米。

                // 1 inch = 2.54 cm
                Area = w * 2.54d * h * 2.54d;

    OK,这个封装的玩意儿算完成了,下面弄UI部分。大概的XAML如下,我就不解释了,看不懂的话,可以打电话问问憨豆先生。

            <Button Content="选择照片(可多选)"  Margin="10,15,10,8" Click="OnClick"/>
    
            <ListView Name="lv" Grid.Row="1" Margin="5">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <TextBlock FontSize="18" FontWeight="Bold" Text="{Binding FileName}" />
                            <TextBlock>
                                <Run>尺寸(像素):</Run>
                                <Run Text="{Binding Width}" />
                                <Run> × </Run>
                                <Run Text="{Binding Height}" />
                            </TextBlock>
                            <TextBlock>
                                <Run>分辨率:</Run>
                                <Run Text="{Binding DpiX}"/>
                                <Run> × </Run>
                                <Run Text="{Binding DpiY}"/>
                            </TextBlock>
                            <TextBlock>
                                <Run>面积(平方厘米):</Run>
                                <Run Text="{Binding Area}"/>
                            </TextBlock>
                        </StackPanel>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ListView>
    
            <TextBlock Grid.Row="2" Name="tbTotal" Margin="10,6" FontSize="20" Foreground="LightGreen" TextWrapping="Wrap" />

    ListView用来显示每张照片的信息,最后的TextBlock用来显示所有照片的总面积。

    然后,处理按钮事件。

                // 选择文件
                FileOpenPicker picker = new FileOpenPicker();
                picker.FileTypeFilter.Add(".jpg");
                picker.FileTypeFilter.Add(".jpeg");
    
                IReadOnlyList<StorageFile> imgfiles = await picker.PickMultipleFilesAsync();
                photolist.Clear();
                foreach (StorageFile f in imgfiles)
                {
                    try
                    {
                        PhotoInfo info = new PhotoInfo();
                        // 处理数据
                        await info.ProcessFileAsync(f);
                        // 添加到集合中
                        photolist.Add(info);
                    }
                    catch(Exception ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex.Message);
                    }
                }
    
                // 对面积进行合计
                double areaTotal = photolist.Sum(p =>
                 {
                     // 如果单张照片的面积无效,则返回0
                     if (double.IsInfinity(p.Area))
                     {
                         return 0d;
                     }
                     return p.Area;
                 });
    
                // 显示
                tbTotal.Text = $"共扫描了 {photolist.Count} 张照片,总面积为 {areaTotal.ToString("F2")} 平方厘米。";

    photolist是个变量,类型为ObservableCollection<PhotoInfo>,ObservableCollection集合是个好东西,它可以自动更新绑定的集合控件的UI项。

    由于在读取文件和解码图片时用到了异步等待,所以刚才在PhotoInfo类的定义时公开了一个ProcessFileAsync方法,这样确保调用这个类的代码也能继续异步等待,就是等到所有数据都读完了,都计算完了再继续,不然最后计算总面积的时候,因为数据没有准备好,会得到总面积为0的灵异结果。所以,做人别太急,要学会异步等待,这样你才能看看沿途美丽如画的风景。

    所以,在向集合Add项前,要等待数据初始化完成,这个等待是异步的,不会阻止UI线程。

                        PhotoInfo info = new PhotoInfo();
                        // 处理数据
                        await info.ProcessFileAsync(f);
                        // 添加到集合中
                        photolist.Add(info);

    最后计算总面积的时候,就好办了,直接调用集合的Sum扩展方法即可,你要是嫌代码太短了,也可以用LinQ来计算,反正一样的。注意,在Sum里面的Lambda表达式体中,要判断一下,每一个PhotoInfo实例的Area属性是否有效,方法是用double.IsInfinity方法,如果double值为正无穷大或负无穷大,就返回true,这时候应把返回的double值调整为0,不然的话,任何数跟无穷大的数相加后的结果,永远都是无穷大,这样的值没有实际价值。

                double areaTotal = photolist.Sum(p =>
                 {
                     // 如果单张照片的面积无效,则返回0
                     if (double.IsInfinity(p.Area))
                     {
                         return 0d;
                     }
                     return p.Area;
                 });

    为什么要这样验证呢,因为个别照片文件可能由于人品问题,读不出正确的分辨率,这样最后的计算结果就会有问题。

    现在,运行示例,然后选择一堆照片,就能计算它们的面积了,有图有真相。

    好了,本文就写到这里了,改天老周再补上WPF篇。

    示例源代码下载地址

  • 相关阅读:
    类型初始值设定项引发异常的解决方法
    sql修改排序规则,区分大小
    SQLServer查询所有子节点
    Cannot resolve the collation conflict between "Chinese_PRC_CI_AS" and "SQL_L及由于排序规则不同导致查询结果为空的问题
    SQLServer跨库查询--分布式查询
    DataTable对象的操作问题
    .Net插入大批量数据
    SQL修改字段类型
    数据抓包分析
    Qss 皮肤
  • 原文地址:https://www.cnblogs.com/tcjiaan/p/5076593.html
Copyright © 2011-2022 走看看