手機護眼概論及OLED屏幕降低頻閃原理介紹

来源:https://www.cnblogs.com/cjyyx/p/18268818
-Advertisement-
Play Games

影響護眼的因素 藍光 目前手機大多已經實現硬體低藍光,而且藍光也可以通過護眼模式輕易剋服。 偏振光 偏振光指振動方向與傳播方向不對稱的光,主要分為圓偏振光與線偏振光兩種。 線偏振光測試方法為:透過偏振片看屏幕,旋轉偏振片,若存在某個角度屏幕發出的光線無法透過偏振片,則是線偏振光。一些墨鏡鏡片、相機的 ...


影響護眼的因素

藍光

目前手機大多已經實現硬體低藍光,而且藍光也可以通過護眼模式輕易剋服。

偏振光

偏振光指振動方向與傳播方向不對稱的光,主要分為圓偏振光與線偏振光兩種。

線偏振光測試方法為:透過偏振片看屏幕,旋轉偏振片,若存在某個角度屏幕發出的光線無法透過偏振片,則是線偏振光。一些墨鏡鏡片、相機的cpl鏡都可以作為偏振片使用。

大部分LCD屏幕是線偏振光[1],少部分OLED屏幕(如紅米note12turbo)也是線偏振光。

2009年閆曉林等人讓隨機分組的測試者觀看圓偏振光電視和普通液晶電視,進行分級視力和眨眼頻率測試。通過分級視力變化和眨眼頻率反映視疲勞程度。分析得出,當觀看兩種偏振光電視一段時間後,不論是兒童還是成人都會產生暫時視力下降。圓偏振光與線偏振光相比,引起的視覺疲勞程度會小一些。研究者推測是由於圓偏振光本身與線偏振光相比,其振動面不只限於某一固定方向,而是圍繞光的前進方向轉動,旋轉電矢量端點描出均勻圓軌跡,這與自然光的振動面在各個方向上均勻分佈是比較接近的,因而可能產生的視疲勞較輕。[2]

在屏幕貼膜之後,線偏振光的成分會減少,因此可以作為護眼的手段。

眩光

眩光是一種影響視覺的機制。它是指視野中亮度分佈不均勻、亮度範圍變化不適宜、或者時間和空間上存在極端對比,造成人眼在觀看時的不舒適之感或觀察細部物體能力降低的現象。按照眩光產生的標準,可以分為直接眩光和反射眩光。

貼AR抗反射膜,可以減少反射眩光。

屏幕的頻閃

頻閃的度量

目前手機屏幕頻閃的度量主要有兩種方式,一種是用低快門時間的相機拍攝手機屏幕,觀察黑色條紋;另一種是高時間解析度的照度探頭,測出屏幕上指定區域的亮度隨時間變化曲線,再通過一定的公式計算出頻閃效應可見性度量值(SVM, Stroboscopic effect visibility measure)。

相機拍攝

相機拍攝的方式相當簡單,只要有一部手機,就可以觀察頻閃程度。具體方法為,將手機相機調到專業模式,將快門時間調到 1/4000 秒以下,對準被測手機屏幕,然後可以看到黑色條紋,如圖所示。

一般來說,黑色條紋越寬、顏色越深、越稀疏,頻閃程度越強。

SVM 計算方法[3]

感測器測得的照度隨時間變化產生波形。將波形歸一化,使時間平均值等於 1 ,得到相對照度波形,記為 \(y(t)\),併進行三角傅里葉級數展開

\[y(t) = \dfrac{a_0}{2} + \sum\limits_{m=1}^{\infty} \left[ a_m\cos\left(\dfrac{2\pi m t}{T}\right) + b_m\sin\left(\dfrac{2\pi m t}{T}\right) \right] \]

相對照度波形的第 \(m\) 個傅里葉分量的相對幅度記為 \(C_{m} = \sqrt{a_m^2 + b_m^2}\),頻率記為 \(f_{m} = \dfrac{m}{T}\)

考慮頻閃效應對比度閾值函數(stroboscopic effect contrast threshold function)

\(T_{m}\) 為頻率 \(f_{m}\) 對應的頻閃效應對比度閾值函數值。

則 SVM 計算公式如下

\[\mathrm{SVM}=\left[~\sum\limits_{m=1}^{\infty}\left(\dfrac{C_{m}}{T_{m}}\right)^{3.7}~\right]^{1/3.7} \]

SVM 值越高,頻閃程度越高。而且 SVM 值是可以進行精確計算的,因此可以把 SVM 作為頻閃分析的理論依據。

觀察 SVM 的計算過程,可以發現其取值與屏幕亮度絕對值無關,只與亮度隨時間變化曲線的形狀有關

亮度越高,頻閃越低

這個結論非常容易驗證。最直接的,B站up主低調的山用相機拍攝過大量OLED屏幕,都有在高亮度下低頻閃,在低亮度下高頻閃的現象[4]

更進一步的,up主Navis-慢點評測展示了OLED手機屏幕 SVM 隨屏幕亮度變化曲線[5]

up主先看評測製作APP先看頻閃,同樣展示了OLED手機屏幕 SVM 隨屏幕亮度變化曲線[6]

因此可以得出結論,一般情況下,OLED 屏幕亮度越高,頻閃越低。結合分析 SVM 計算過程得到的結論,有降低屏幕頻閃的方法:維持屏幕在高亮度,通過增加一個不透明度可調節的黑色濾鏡來控制屏幕實際亮度,從而實現在低亮度下也有低頻閃,這就是通過屏幕濾鏡降低手機頻閃的原理。

屏幕濾鏡的局限

之前提到,屏幕濾鏡可以降低屏幕亮度,同時維持亮度隨時間變化曲線的形狀不變。而 SVM 只取決於亮度隨時間變化曲線的形狀。因此,屏幕濾鏡可以讓 SVM 一直處於最小值即頻閃最低的同時降低屏幕亮度。

但是,真實情況並非如此。屏幕頻閃由pwm調光和像素刷新共同決定。高亮度下pwm調光占主導,超低亮度下像素刷新占主導地位。這是因為超低亮度的時候pwm調光的波動不如像素刷新的波動大,120Hz 刷新率,每一次刷新就需要關閉再點亮一次像素,這個重新點亮就意味著頻閃[7]

所以,即使使用了屏幕濾鏡,在超低亮度下也會存在 120Hz 的頻閃!

屏幕濾鏡在安卓系統的實現

幸運的是,安卓系統給出了足夠的 api,使我們能夠實現屏幕濾鏡。

首先,app 需要打開無障礙服務,獲取顯示在整個屏幕上的許可權。

參考:https://developer.android.com/guide/topics/ui/accessibility/service

開啟無障礙服務後,利用無障礙服務的上下文獲取整個屏幕的視窗管理器,往視窗管理器添加純黑色、透明度可調的視圖對象,和相應的參數對象,就實現了屏幕濾鏡。

將無障礙服務上下文傳入下麵代碼的 FilterViewManager 對象,即可在屏幕上顯示一個透明度可調的黑色濾鏡。

import android.content.Context;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.view.WindowManager;

public class FilterViewManager {

    private final Context context;
    private final WindowManager windowManager;
    private final WindowManager.LayoutParams layoutParams;
    private final FilterView filterView;
    /**
     * 濾鏡處於開啟狀態,為 true
     */
    public boolean isOpen;
    private float alpha = 0f;
    private float hardwareBrightness = 0f;

    public FilterViewManager(Context c) {
        // 這裡假設傳入的 Context 有無障礙許可權,後面的代碼不對無障礙許可權進行檢驗

        isOpen = false;
        context = c;
        windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        layoutParams = new WindowManager.LayoutParams();
        filterView = new FilterView(context);

        layoutParams.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
        // width 和 height 儘可能大,從而覆蓋屏幕
        layoutParams.width = 4000;
        layoutParams.height = 4000;
        layoutParams.format = PixelFormat.TRANSLUCENT;
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
    }

    public void open() {
        new Handler(Looper.getMainLooper()).post(() -> {
            // 在UI線程中更新UI組件
            if (!isOpen) {
                windowManager.addView(filterView, layoutParams);
                isOpen = true;
            }
        });
    }

    public void close() {
        new Handler(Looper.getMainLooper()).post(() -> {
            // 在UI線程中更新UI組件
            if (isOpen) {
                windowManager.removeView(filterView);
                isOpen = false;
            }
        });
    }

    public float getAlpha() {
        if (isOpen) {
            return alpha;
        } else {
            return -1f;
        }
    }

    public void setAlpha(float alpha) {
        new Handler(Looper.getMainLooper()).post(() -> {
            if (isOpen) {
                float a = Math.min(1f, Math.max(0f, alpha));
                // 在UI線程中更新UI組件
                filterView.setAlpha(a);
                this.alpha = a;
            }
        });
    }

    public float getHardwareBrightness() {
        if (isOpen) {
            return hardwareBrightness;
        } else {
            return -1f;
        }
    }

    public void setHardwareBrightness(float brightness) {
        new Handler(Looper.getMainLooper()).post(() -> {
            if (isOpen) {
                float b = Math.min(1f, Math.max(0f, brightness));
                // 在UI線程中更新UI組件
                // layoutParams.screenBrightness 會覆蓋系統亮度設置
                layoutParams.screenBrightness = b;
                windowManager.updateViewLayout(filterView, layoutParams);
                hardwareBrightness = b;
            }
        });
    }

    private static class FilterView extends View {

        public FilterView(Context context) {
            super(context);
            setBackgroundColor(Color.BLACK);
            setAlpha(0f);
        }

        @Override
        public void setAlpha(float alpha) {
            super.setAlpha(alpha);
            invalidate();
        }
    }
}

開源 APP:濾鏡護眼防頻閃

github 項目:https://github.com/cjyyx/ScreenFilter

對於 OLED 屏幕的手機,一般情況下,屏幕亮度越低,頻閃越強。本應用控制屏幕具有較高的亮度,並通過給屏幕添加一層不透明度可調的黑色濾鏡來調節實際亮度,從而實現低亮度下也有低頻閃的效果。

註意:

  1. 支持直接拖動系統狀態欄亮度條來控制亮度
  2. 當環境光照較高時,應用會自動關閉屏幕濾鏡並打開系統自動亮度,從而使屏幕能夠達到最大激發亮度
  3. 最低支持版本安卓10
  4. 本應用在開發時沒有考慮相容性,目前只能保證在我的手機上正常運行。我的手機系統是 MIUI14
  5. 開啟濾鏡時不要開啟系統紙質護眼,否則會造成花屏

下載鏈接 1:github release
https://github.com/cjyyx/ScreenFilter/releases

下載鏈接 2:123雲盤
https://www.123pan.com/s/Be4Hjv-fUUtv.html


  1. 圓偏振光和線偏振光測試, https://www.bilibili.com/video/BV15n4y1R7pp ↩︎

  2. 張平奇,王丹,呂振華,等.健康顯示的影響因素綜述[J].液晶與顯示,2020,35(09):981-990. ↩︎

  3. 維基百科, https://en.wikipedia.org/wiki/Stroboscopic_effect ↩︎

  4. 低調的山, https://space.bilibili.com/394790691 ↩︎

  5. Navis-慢點評測, https://space.bilibili.com/8986182 ↩︎

  6. 先看頻閃, https://www.bilibili.com/video/BV1K14y1D7mg ↩︎

  7. 像素刷新稀釋, https://www.bilibili.com/opus/781396590214512640 ↩︎


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 使用Percona Toolkit的pt-duplicate-key-checker工具時,偶爾會遇到"Error checking xxx: Wide character in print at /usr/bin/pt-duplicate-key-checker line 5248."這類錯誤。如 ...
  • 1.前言 因為小程式是由js代碼編寫的,我js學得不是特別的好,所以,剛開始以為js跟java一行,一行一行的執行,後面才發現,完全不是,所以有時候,我們在獲取用戶信息和openId的時候,要向後臺發送請求,所以有時有可能請求還沒有返回數據,小程式這邊已經賦值了,只能得到一個undifine,很桑心 ...
  • UINavigationController 是 iOS 中用於管理視圖控制器層次結構的一個重要組件,通常用於實現基於堆棧的導航。它提供了一種用戶界面,允許用戶在視圖控制器之間進行層次化的導航,例如從列表視圖到詳細視圖。 UINavigationController 的主要功能 管理視圖控制器堆棧: ...
  • UITabBarController 是 iOS 中用於管理和顯示選項卡界面的一個視圖控制器。它允許用戶在多個視圖控制器之間進行切換,每個視圖控制器對應一個選項卡。 主要功能 管理多個視圖控制器: UITabBarController 管理一個視圖控制器數組,每個視圖控制器對應一個選項卡。 顯示選項 ...
  • 在MVC模型中,V指view,負責用戶界面的顯示、處理用戶輸入,並將輸入傳遞給控制器。C是指ViewController,充當模型和視圖之間的中介。控制器接收用戶輸入,處理用戶請求,並將結果傳遞給視圖以更新顯示。本文詳細介紹在iOS開發中UIView與UIViewController的生命周期。 U ...
  • 目錄前言一、Jetpack Compose 中處理嵌套滾動的思想二、Modifier.nestedScroll2.1 NestedScrollConnection2.2 NestedScrollDispatcher三、實操講解3.1 父組件消費子組件給過來的事件——NestedScrollConne ...
  • 目錄一、點按手勢1.1 Modifier.clickable1.2 Modifier.combinedClickable二、滾動手勢2.1 滾動修飾符 Modifier.verticalScorll / Modifier.horizontalScorll2.2 可滾動修飾符 Modifier.scr ...
  • OpenGrok是一個源碼搜索及交叉引用查詢引擎,OpenGrok的引入可以幫助我們更好地在浩如煙海的源碼里找到自己需要的那坨代碼。 ...
一周排行
    -Advertisement-
    Play Games
  • 通過WPF的按鈕、文本輸入框實現了一個簡單的SpinBox數字輸入用戶組件並可以通過數據綁定數值和步長。本文中介紹了通過Xaml代碼實現自定義組件的佈局,依賴屬性的定義和使用等知識點。 ...
  • 以前,我看到一個朋友在對一個系統做初始化的時候,通過一組魔幻般的按鍵,調出來一個隱藏的系統設置界面,這個界面在常規的菜單或者工具欄是看不到的,因為它是一個後臺設置的關鍵界面,不公開,同時避免常規用戶的誤操作,它是作為一個超級管理員的入口功能,這個是很不錯的思路。其實Winform做這樣的處理也是很容... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他的程式每次關閉時就會自動崩潰,一直找不到原因讓我幫忙看一下怎麼回事,這位朋友應該是第二次找我了,分析了下 dump 還是挺經典的,拿出來給大家分享一下吧。 二:WinDbg 分析 1. 為什麼會崩潰 找崩潰原因比較簡單,用 !analyze -v 命 ...
  • 在一些報表模塊中,需要我們根據用戶操作的名稱,來動態根據人員姓名,更新報表的簽名圖片,也就是電子手寫簽名效果,本篇隨筆介紹一下使用FastReport報表動態更新人員簽名圖片。 ...
  • 最新內容優先發佈於個人博客:小虎技術分享站,隨後逐步搬運到博客園。 創作不易,如果覺得有用請在Github上為博主點亮一顆小星星吧! 博主開始學習編程於11年前,年少時還只會使用cin 和cout ,給單片機點點燈。那時候,類似async/await 和future/promise 模型的認知還不是 ...
  • 之前在阿裡雲ECS 99元/年的活動實例上搭建了一個測試用的MINIO服務,以前都是直接當基礎設施來使用的,這次準備自己學一下S3相容API相關的對象存儲開發,因此有了這個小工具。目前僅包含上傳功能,後續計劃開發一個類似圖床的對象存儲應用。 ...
  • 目錄簡介快速入門安裝 NuGet 包實體類User資料庫類DbFactory增刪改查InsertSelectUpdateDelete總結 簡介 NPoco 是 PetaPoco 的一個分支,具有一些額外的功能,截至現在 github 星數 839。NPoco 中文資料沒多少,我是被博客園群友推薦的, ...
  • 前言 前面使用 Admin.Core 的代碼生成器生成了通用代碼生成器的基礎模塊 分組,模板,項目,項目模型,項目欄位的基礎功能,本篇繼續完善,實現最核心的模板生成功能,並提供生成預覽及代碼文件壓縮下載 準備 首先清楚幾個模塊的關係,如何使用,簡單畫一個流程圖 前面完成了基礎的模板組,模板管理,項目 ...
  • 假設需要實現一個圖標和文本結合的按鈕 ,普通做法是 直接重寫該按鈕的模板; 如果想作為通用的呢? 兩種做法: 附加屬性 自定義控制項 推薦使用附加屬性的形式 第一種:附加屬性 創建Button的附加屬性 ButtonExtensions 1 public static class ButtonExte ...
  • 在C#中,委托是一種引用類型的數據類型,允許我們封裝方法的引用。通過使用委托,我們可以將方法作為參數傳遞給其他方法,或者將多個方法組合在一起,從而實現更靈活的編程模式。委托類似於函數指針,但提供了類型安全和垃圾回收等現代語言特性。 基本概念 定義委托 定義委托需要指定它所代表的方法的原型,包括返回類 ...