計算照片的面積(WPF篇)


昨天,老周突發其想地給大伙伴們說了一下UWP應用中計算照片面積的玩法,而且老周也表示會提供WPF版本的示例。所以,今天就給大伙們補上吧。

WPF是集成在.net框架中,屬於.net的一部分,千萬不要跟我說你學.net不學WPF,那是不對的,包括ASP.NET、WCF、WF等都是.net框架的一部分,它們在本質上並沒有脫離.net。

廢話少扯,扯了也沒人聽,咱們說正題吧。

 

WPF庫中與UWP的不太一樣,圖像解碼編碼API似乎不像UWP中那么強大,大概是因為桌面程序可以調用Win32 API和COM的原因吧。不過,老周必須告訴你一個事,經過測試,用WPF的方法計算照片面積,在性能上遠遠超過GDI的方式,尤其是對大型照片更是如此。在WPF出現前,在System.Drawing命名空間下有個Iamge類,也派生出了一個Bitmap類,這些類都可以用來計算照片面積。老周在N年前做過一個照片面積計算器,除了計算面積外,還可以輸入單價(每平方米多少錢),然后計算沖印照片的面積和最終的價格,並具有簡單的票據打印功能。這個程序是基於GDI,即用System.Drawing命名空間下的東東搞的,因為那個時代,WPF還沒有問世。

WPF把圖像的解碼/編碼類都內置到UI相關的媒體功能中,就是位於System.Windows.Media.Imaging命名空間下,里面有個BitmapImage類,它可以讀到照片的像素寬高,以及分辨率,有了這些參數就可以計算面積了。

但是,你必須注意WPF的線程安全模型是相對嚴格的,為了保護UI線程不被無意破壞,BitmapImage類是間接繼承DispatcherObject類,凡是有Dispatcher的對象,你得明白,它是不能跨線程操作的。如果你要在UI上顯示它們,那么位圖對象的實例必須屬於UI線程。

 

如果你使用數據綁定時擔心性能受影響,可以開啟異步綁定,WPF的Binding有個IsAsync屬性,開啟它,UI線程在調度時會使用輔助線程來加載數據。實驗表明,你的CPU核數越多,處理起來越快,到底還是要看配置啊。尤其是處理多媒體,像視頻這些,你不能拿一台50年前的電腦來談性能優化,你應該至少拿一台跟得上時代的電腦來評估。你總不能用春秋戰國時期的社會生產力來跟大唐盛世比。

 

在使用圖像時,為了節省開銷,你可以設置DecodePixelWidth或DecodePixelHeight

屬性,這兩個屬性不要同時設置,你設置其中一個就行了,這樣圖像在呈現時可以自動計算比例,不然會使圖像變形,當然了,如果你希望圖像變形,那就另當別論了。

設置這兩個屬性,並不影響我們讀取圖像的真實像素,要獲取圖像寬高,應訪問PixelWidth和PixelHeight屬性,DecodePixelHeight/Width不會影響這兩個屬性的值。另外,通過DpiX和DpiY屬性就能得到水平和垂直方向上的分辨率。

 

好,同樣地,還是先封裝一個數據類。

    public class PhotoData : INotifyPropertyChanged
    {
        #region 私有字段
        int width = default(int), height = default(int);
        double dpix = default(double), dpiy = default(double);
        BitmapImage bitmap = null;
        double area;
        #endregion

        #region INotifyPropertyChanged成員
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion

        #region 構造函數
        public PhotoData(string filePath)
        {
            // 實例化圖像對象
            PhotoImage = new BitmapImage();
            PhotoImage.DecodePixelWidth = 100;
            // 初始化圖像
            PhotoImage.BeginInit();
            PhotoImage.UriSource = new Uri(filePath);
            PhotoImage.EndInit();

            // 獲取需要的參數
            Width = PhotoImage.PixelWidth;
            Height = PhotoImage.PixelHeight;
            DpiX = PhotoImage.DpiX;
            DpiY = PhotoImage.DpiY;
            // 計算面積
            ComputeArea();
        }
        #endregion

        #region 方法
        private void OnPropertyChanged([CallerMemberName]string prpname = "")
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prpname));
        }

        /// <summary>
        /// 計算面積
        /// </summary>
        private void ComputeArea()
        {
            // 先將寬度和高度轉為英寸
            double inchW = Width / DpiX;
            double inchH = Height / DpiY;
            /*
                面積單位為平方米
                1英寸 = 2.54厘米
            */
            Area = (inchW * 2.54d) * (inchH * 2.54d) / 10000d;
        }
        #endregion

        #region 屬性
        /// <summary>
        /// 照片寬度
        /// </summary>
        public int Width
        {
            get { return width; }
            private set
            {
                if (value != width)
                {
                    width = value;
                    OnPropertyChanged();
                }
            }
        }

        /// <summary>
        /// 照片高度
        /// </summary>
        public int Height
        {
            get { return height; }
            private set
            {
                if (value != height)
                {
                    height = value;
                    OnPropertyChanged();
                }
            }
        }

        /// <summary>
        /// 水平分辨率
        /// </summary>
        public double DpiX
        {
            get { return dpix; }
            private set
            {
                if (dpix != value)
                {
                    dpix = value;
                    OnPropertyChanged();
                }
            }
        }

        /// <summary>
        /// 垂直分辨率
        /// </summary>
        public double DpiY
        {
            get { return dpiy; }
            private set
            {
                if (value != dpiy)
                {
                    dpiy = value;
                    OnPropertyChanged();
                }
            }
        }

        /// <summary>
        /// 圖像實例
        /// </summary>
        public BitmapImage PhotoImage
        {
            get { return bitmap; }
            private set
            {
                if (bitmap != value)
                {
                    bitmap = value;
                    OnPropertyChanged();
                }
            }
        }

        /// <summary>
        /// 面積(平方米)
        /// </summary>
        public double Area
        {
            get { return area; }
            private set
            {
                if (value != area)
                {
                    area = value;
                    OnPropertyChanged();
                }
            }
        }
        #endregion
    }

 

這個我不多解釋了,應該比昨天那個更好懂。注意我今天用的單位是平方米,計算平方厘米后,要除以10000。

類似的方法,我們先計算單個照片的面積,並放入到一個集合中。

                string[] files = openFileDlg.FileNames;
                // 開始計算
                photolist.Clear();
                isRunning = true;
                foreach (string f in files)
                {
                    try
                    {
                        PhotoData data = new PhotoData(f);
                        photolist.Add(data);
                    }
                    catch (Exception ex)
                    {
                        // 記錄異常信息
                        Trace.WriteLine($"{DateTime.Now.ToLongTimeString()} -- {ex.Message}");
                        continue;
                    }
                }

 

然后,統計總面積。

                double totalArea = photolist.Sum(d =>
                {
                    if (double.IsInfinity(d.Area))
                    {
                        return 0d;
                    }
                    return d.Area;
                });

 

為了使這個程序更加生動可愛,更具有內涵,老周還使用了語音朗讀API,在System.Speech.Synthesis命名空間下。

                // 語音朗讀
                Task.Run(() =>
                {
                    using (SpeechSynthesizer symthedizer = new SpeechSynthesizer())
                    {
                        symthedizer.Speak(msg);
                    }
                });

使用Task來異讀朗讀,不影響UI上呈現內容。

 

最后結果請看下圖。

 

好了,該開飯了,今天的牛皮就吹到這里,有空繼續吹。

 

示例源代碼下載

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM