高斯模糊原理、以及如何使用UIVisualEffectView實現模糊效果

這篇文章將介紹如何使用UIVisualEffectView實現 iOS 系統中各種模糊效果(blur effect)、鮮豔度效果(vibrancy effect),以及模糊效果的原理。

自 iOS 7 設計風格改變後,模糊效果在 app 中越來越常見。合理運用可以改善 app 可用性和外觀。

iOS 系統中很多地方使用了模糊效果,最爲明顯的有控制中心、通知中心。控制中心的模糊顯示了當前活躍的app,而非控制中心自身。通知中心沒有給整個背景添加模糊,而是爲每條通知添加了模糊。這樣不僅看起來更美,也讓元素更爲突出。

使用UIVisualEffectView可以實現上述模糊效果。

1. 模糊的原理

模糊起始於圖像。要實現模糊,需對每一像素執行模糊算法。最終,得到原始圖像的均勻模糊版本。模糊算法的樣式、複雜度各不相同,這裏介紹一種常見的模糊算法:高斯模糊(gaussian blur)。

高斯模糊將正態分佈用於圖像處理。本質上,它是一種數據平滑技術(data smoothing),適用於多個場合,圖像處理恰好提供了一個直觀的應用實例。

1.1 高斯模糊原理

模糊可以理解成每一個像素都取周邊像素的平均值。

上圖中,2是中間點,周邊都是1。中間點取周圍點的平均值,就會變成1。如下圖所示:

在數值上,這是一種平滑化。在圖像上,就相當於產生模糊效果,中間點失去細節。

計算平均值時,取值範圍越大,模糊效果越強烈。

上面分別是原圖、模糊半徑3像素、10像素的效果。模糊半徑越大,圖像就越模糊。從數值角度看,就是數值越平滑。

通常,模糊半徑越大,處理圖片所需資源越多。iOS 爲了避免主線程擁堵,會將盡可能多的任務移交給 GPU 處理。

既然每個點都要取周邊像素的平均值,那應如何分配權重呢?如果簡單平均,顯然不盡合理。因爲圖像都是連續的,越靠近的點關係越緊密;反之則疏遠。因此,加權平均更合理,距離越近的點權重越大,距離越遠的點權重越小。

正態分佈就是一種可取的權重分配模式。

正態分佈是一維的,圖像是二維的,因此我們需要二維的正態分佈。

正態分佈的密度函數叫做高斯函數,可以根據高斯函數計算每個點的權重,然後根據每個點的權重計算高斯模糊值。對所有點重複這個過程,就得到了高斯模糊後的圖像。如果原圖是彩色圖像,可以對RGB三個通道分別做高斯模糊。

2. 模糊效果

這裏使用WebKit瀏覽網頁,點擊皇冠時在底部彈出一個視圖,爲彈出的視圖添加模糊效果。這樣後續滑動 webview 時可以看到滾動的模糊效果。

2.1 UIBlurEffect

UIBlurEffect繼承自UIVisualEffect,導航欄、通知中心、控制中心的模糊效果都由UIBlurEffect提供。

下面爲bottomView添加模糊效果:

        // 選用 dark 類型 blur
        let blurEffect = UIBlurEffect(style: .dark)
        // blur effect 需要添加到 UIVisualEffectView
        let blurView = UIVisualEffectView(effect: blurEffect)
        bottomView.addSubview(blurView)
        
        blurView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            blurView.heightAnchor.constraint(equalTo: bottomView.heightAnchor),
            blurView.widthAnchor.constraint(equalTo: bottomView.widthAnchor),
        ])
        
        // 子視圖不能直接添加到blurView,只能添加到contentView。
        blurView.contentView.addSubview(houseLabel)
        houseLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            houseLabel.centerXAnchor.constraint(equalTo: blurView.centerXAnchor),
            houseLabel.centerYAnchor.constraint(equalTo: blurView.centerYAnchor),
        ])

UIBlurEffect.Style有以下這些類型:

  • extraLight:視圖比底層視圖顏色淺。
  • light:視圖與底層視圖亮度類似。
  • dark:視圖比底層視圖顏色深。
  • extraDark:視圖比底層視圖顏色更深。
  • regular:自適應用戶界面。
  • prominent:根據背景色自動使內容更加突出。

此外,iOS 13 中還添加了一些自適應淺色、深色的模糊效果,如systemUltraThinMaterialLight、systemChromeMaterialDark等。

UIVisualEffectView繼承自UIView,需將UIVisualEffectView添加到要添加模糊效果的視圖,視圖層級中其它子視圖需添加到UIVisualEffectViewcontentView中,直接添加到UIVisualEffectView會導致閃退。

運行後效果如下:

2.2 UIVibrancyEffect

UIVibrancyEffect繼承自UIVisualEffectUIVibrancyEffectUIVisualEffectView組合使用,可以讓內容顏色更鮮豔。

下圖顯示了UIVibrancyEffect讓文本、圖標更易識別:

使用以下代碼添加UIVibrancyEffect效果:

        // 使用之前的blurEffect創建UIVibrancyEffect
        let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
        let vibrancyView = UIVisualEffectView(effect: vibrancyEffect)
        // UIVibrancyEffect 必須添加到配置了UIBlurEffect的UIVisualEffectView的contentView上,否則不會有效果。
        blurView.contentView.addSubview(vibrancyView)

        vibrancyView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            vibrancyView.heightAnchor.constraint(equalTo: blurView.heightAnchor),
            vibrancyView.widthAnchor.constraint(equalTo: blurView.widthAnchor),
            vibrancyView.centerXAnchor.constraint(equalTo: blurView.centerXAnchor),
            vibrancyView.centerYAnchor.constraint(equalTo: blurView.centerYAnchor),
        ])
        
        // 將文本添加到 UIVibrancyEffect 視圖的contentView上。添加到vibrancyView上的所有控件都會具有 vibrancy effect。
        vibrancyView.contentView.addSubview(houseLabel)
        houseLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            houseLabel.centerXAnchor.constraint(equalTo: vibrancyView.centerXAnchor),
            houseLabel.centerYAnchor.constraint(equalTo: vibrancyView.centerYAnchor),
        ])

houseLabel添加到vibrancyView前,需先註釋掉將其添加到blurView的代碼:

        blurView.contentView.addSubview(houseLabel)
        houseLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            houseLabel.centerXAnchor.constraint(equalTo: blurView.centerXAnchor),
            houseLabel.centerYAnchor.constraint(equalTo: blurView.centerYAnchor),
        ])

運行後如下:

2.3 UIVisualEffectView

使用UIVisualEffectView時,不能直接向其添加子視圖,子視圖需添加到其contentView

2.3.1 alpha

應避免設置alpha小於1。半透明視圖會導致視圖、子視圖離屏渲染,進而影響性能。UIVisualEffectView或其父視圖alpha小於1會導致模糊效果顯示錯誤,甚至沒有模糊效果。

2.3.2 mask

UIVisualEffectView設置mask後,visual effect view 會將mask轉發給它的內部視圖,包括contentView。也可以直接爲contentView添加 mask。爲UIVisualEffectView的父視圖設置 mask 會導致特效失效,且會拋出異常。

2.3.3 截屏

UIVisualEffectView的許多特效都需要包含該視圖的 window 支持。嘗試只截取UIVisualEffectView會導致截屏不包含任何特效。截取包含UIVisualEffectView特效的視圖層級時,必須截取包含特效的整個UIWindowUIScreen

3. 降低透明度

使用模糊效果時,需考慮用戶在系統設置中開啓了「降低透明度」時怎麼辦?

進入系統設置 > 輔助功能 > 顯示與文字大小 > 降低透明度,開啓降低透明度。返回app後,再次彈出底部視圖,效果如下:

開啓「降低透明度」後,模糊效果會消失。使用UIAccessibility.isReduceTransparencyEnabled方法可以查看當前是否開啓了「降低透明度」。

在設置透明效果前添加以下代碼,即如果開啓了「降低透明度」,就不使用模糊效果。

        // 系統開啓了「降低透明度」
        guard !UIAccessibility.isReduceTransparencyEnabled else {
            bottomView.backgroundColor = .lightGray
            bottomView.addSubview(houseLabel)
            houseLabel.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                houseLabel.centerXAnchor.constraint(equalTo: bottomView.centerXAnchor),
                houseLabel.centerYAnchor.constraint(equalTo: bottomView.centerYAnchor),
            ])
            return
        }

運行app後,開啓「降低透明度」後app也可以正常顯示了。

Demo名稱:VisualEffectView
源碼地址:https://github.com/pro648/BasicDemos-iOS/tree/master/VisualEffectView

參考資料:

  1. 高斯模糊的算法
  2. UIVisualEffectView Tutorial: Getting Started

歡迎更多指正:https://github.com/pro648/tips

本文地址:https://github.com/pro648/tips/blob/master/sources/高斯模糊原理、以及如何使用UIVisualEffectView實現模糊效果.md

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章