【问题标题】:Color banding and artifacts with gradients despite using RGBA_8888 everywhere尽管在任何地方都使用 RGBA_8888,但带有渐变的色带和伪影
【发布时间】:2012-08-13 05:07:46
【问题描述】:

我知道色带是一个老问题,之前已经讨论过很多次,并提供了各种解决方案(基本上归结为始终使用 32 位或使用抖动)。事实上不久前我asked and subsequently answered my own SO question对此。那时,我认为我在该问题的答案中提出的解决方案(将setFormat(PixelFormat.RGBA_8888) 应用于Window 以及在SurfaceView 的情况下应用于Holder)已经解决了问题我的申请很好。至少该解决方案使渐变在我当时开发的设备上看起来非常漂亮(很可能是 Android 2.2)。

我现在正在使用 HTC One X (Android 4.0) 和 Asus Nexus 7 (Android 4.1) 进行开发。我试图做的是对SurfaceView 的整个区域应用灰色渐变。尽管我应该确保包含 WindowHolder 配置为 32 位颜色,但我得到了可怕的条带伪影。事实上,在 Nexus 7 上,我什至可以看到工件在移动。这不仅发生在SurfaceView 上,它当然是连续绘制的,而且也发生在一个普通的View 上,我在旁边添加了为了测试目的绘制完全相同的渐变,它本来会绘制一次。这些伪影存在并且似乎四处移动的方式当然看起来非常糟糕,这实际上就像观看信号不佳的模拟电视ViewSurfaceView 都展示了完全相同的工件,它们一起移动。

我的意图是始终使用 32 位,而不是使用抖动。我的印象是,早在 Android 4.0 之前,Window 就默认为 32 位。通过在SurfaceView 中应用RGBA_8888,我原以为一切都是32 位的,从而避免了任何伪影。

我确实注意到关于 SO 的其他一些问题,人们观察到 RGBA_8888 在 4.0 / 4.1 平台上似乎不再有效。

这是我的 Nexus 7 的屏幕截图,顶部是普通的 View,下方是 SurfaceView,两者都对 Canvas 应用相同的渐变。当然,它不会像查看显示器时那样显示伪影,因此显示此屏幕抓取可能毫无意义。我想强调的是,在 Nexus 的屏幕上,条带确实看起来很糟糕。 编辑:事实上,屏幕截图确实没有显示工件。我在 Nexus 7 上看到的伪影不是统一的条带;它本质上看起来是随机的。

用于创建上述内容的测试Activity

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Shader;
import android.os.Bundle;
import android.os.Handler;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.view.SurfaceHolder.Callback;
import android.widget.LinearLayout;

public class GradientTest extends Activity {

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        getWindow().setFormat(PixelFormat.RGBA_8888);
    }


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);      
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
        lp.copyFrom(getWindow().getAttributes());
        lp.format = PixelFormat.RGBA_8888;
        getWindow().setAttributes(lp);     
        LinearLayout ll = new LinearLayout(this);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(500,500);
        params.setMargins(20, 0, 0, 0);
        ll.addView(new GradientView(this), params);    
        ll.addView(new GradientSurfaceView(this), params);
        ll.setOrientation(LinearLayout.VERTICAL);
        setContentView(ll);
    }


    public class GradientView extends View {

        public GradientView(Context context) {
            super(context);     
        }

        @Override
        protected void onDraw(Canvas canvas) {

            Paint paint = new Paint();
            paint.setStyle(Paint.Style.FILL);
            paint.setAntiAlias(false);
            paint.setFilterBitmap(false);
            paint.setDither(false); 
            Shader shader = new LinearGradient(
                    0,
                    0,
                    0,
                    500,
                    //new int[]{0xffafafaf, 0xff414141},
                    new int[]{0xff333333, 0xff555555},
                    null,

                    Shader.TileMode.CLAMP
                    );

            paint.setShader(shader);    
            canvas.drawRect(0,0,500,500, paint);
        }       
    }




    public class GradientSurfaceView extends SurfaceView implements Callback {

        public GradientSurfaceView(Context context) {
            super(context);

            getHolder().setFormat(PixelFormat.RGBA_8888); // Ensure no banding on gradients 
            SurfaceHolder holder = getHolder();
            holder.addCallback(this);    
        }


        Paint paint;
        private GraphThread thread;

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            holder.setFormat(PixelFormat.RGBA_8888); // Ensure no banding on gradients    

            paint = new Paint();
            paint.setStyle(Paint.Style.FILL);
            paint.setAntiAlias(false);
            paint.setFilterBitmap(false);
            paint.setDither(false); 

            Shader shader = new LinearGradient(
                    0,
                    0,
                    0,
                    500,
                    //new int[]{0xffafafaf, 0xff414141},
                    new int[]{0xff333333, 0xff555555},
                    null,
                    Shader.TileMode.CLAMP
                    );


            paint.setShader(shader);  



            thread = new GraphThread(holder, new Handler() );
            thread.setName("GradientSurfaceView_thread");
            thread.start();
        }

        class GraphThread extends Thread {

            /** Handle to the surface manager object we interact with */
            private SurfaceHolder mSurfaceHolder;   

            public GraphThread(SurfaceHolder holder, Handler handler) {
                mSurfaceHolder = holder;
                holder.setFormat(PixelFormat.RGBA_8888); // Ensure no banding on gradients  
            }

            @Override
            public void run() {

                Canvas c = null;

                while (true) {

                    try {
                        c = mSurfaceHolder.lockCanvas();

                        synchronized (mSurfaceHolder) {

                            if (c != null){


                                c.drawRect(0,0,500,500, paint);

                            }
                        }                                 
                    } 

                    finally {
                        if (c != null) {
                            mSurfaceHolder.unlockCanvasAndPost(c);
                        }
                    }
                }
            }    

        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {

        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {

        }
    }


}

我从 Google Play 安装了一个名为 Display Tester 的应用程序。此应用程序可用于在屏幕上创建测试梯度。虽然它的渐变看起来并不完美,但它们似乎比我能够实现的要好一些,这让我想知道是否可以采取进一步措施来防止条带化。

我注意到的另一件事是 Display Tester 应用程序报告我的 Nexus 的屏幕是 32 位的。

关于信息,我明确启用硬件加速。我的 SDK 级别是:

 <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15"></uses-sdk>

我注意到的另一件事是Activity 的默认渐变背景,据我所知,这是 Holo 的一个功能,它也是非常带状的。这也根本没有显示在屏幕截图中。而且,我还注意到我的 Nexus 7 上的背景条带短暂移动,这与我的两个 Views 中的条带移动表示同情。如果我使用默认的“空”Activity 创建一个全新的 Android 项目,Activity 在我的 Nexus 和 HTC One X 上都会显示令人讨厌的带状渐变背景。这正常吗?我知道这个黑色/紫色渐变默认背景是Activity 在启用硬件加速时应该拥有的。好吧,无论是否启用了硬件加速,我都会看到同样令人讨厌的带状Activity 背景渐变。这甚至发生在我的空测试项目中,其目标 SDK 为 15。澄清一下,我启用或禁用硬件加速的方式是显式使用 android:hardwareAccelerated="true"android:hardwareAccelerated="false"

我不确定我对 Holo black/purple Activity 渐变背景的观察是否与我的主要问题有关,但它似乎确实相关。奇怪的是它看起来质量如此差(即带状)并且无论是否打开硬件加速看起来都一样。所以,第二个问题是:当您有一个具有默认 Holo 渐变背景的Activity 时,并且对于硬件加速被明确启用然后禁用的每种情况,这个渐变背景 (a) 是否存在并且 (b) 看起来完美光滑?我会在一个单独的问题中问这个问题,但同样,它似乎是相关的。

所以,总而言之:我遇到的基本问题是,在我的 Nexus 7 上似乎根本无法将渐变背景应用于 SurfaceView。这不仅仅是条带问题(如果只是这样,我可以很高兴地忍受);实际上,每次抽签时,条带本质上都是随机的。这意味着不断重绘的SurfaceView 最终会有一个移动的、模糊的背景。

【问题讨论】:

  • 我在同一个问题上苦苦挣扎,而您其他问题的解决方案为我解决了这个问题。我在三星 Galaxy S2 上使用 Android 4.0
  • Trevor 当我应用 getHolder().setFormat(PixelFormat.RGBA_8888); 时,我有一个 Nexus7 (2013);正如您提到的问题中所述,它解决了问题,并且条带消失了。

标签: android colors surfaceview bit-depth


【解决方案1】:

您是否尝试过为表面视图本身设置像素格式?

    final SurfaceHolder holder = getHolder();
    holder.setFormat(PixelFormat.RGBA_8888);

【讨论】:

  • 天哪,终于找到了一个可行的解决方案,拉了我的头发 2 天
【解决方案2】:

只是用一个答案来结束这个问题,我得出的结论是 Nexus 7 只是存在一些硬件/固件问题,这意味着它在渲染渐变方面完全不适应。

【讨论】:

  • 您的全面分析为我节省了大量工作。谢谢!新的 Nexus 7(第 2 代)是否也存在此问题?
  • 我不确定新的 Nexus 7 和我不打算很快获得一个,但知道会很有趣。我应该提到的一件有趣的事情是,有一个名为 Display Tester (on Play) 的应用程序,令人惊讶的是,它显示原始 Nexus 7 可以很好地呈现颜色渐变。试试那个应用程序,看看你的想法。我只是在今天早上才考虑这个问题,我想给开发人员发电子邮件,了解这些渐变是如何渲染的。我想知道他的渐变是否有效,因为他是“手动”渲染它们,而不是使用 API 渐变。
  • HTC One 似乎也有类似的问题。我的渐变在其他 4 台测试设备上看起来完美到接近完美,包括 4 年以上的 800x480 HTC Desire 和 2560x1600 三星平板电脑......但在 HTC One 上它是“裤子”。
【解决方案3】:

您为什么要设置错误的油漆抖动。我建议激活抖动

paint.setDither(true);

Android 文档明确表示它将降级您的渲染:

设置或清除 DITHER_FLAG 位抖动会影响颜色 比设备精度更高的数据被下采样。不 抖动通常更快,但更高精度的颜色只是 截断(例如 8888 -> 565)。抖动尝试分发 此过程中固有的错误,以减少视觉伪影。

您也可以尝试将 FLAG_DITHER 添加到窗口中:

window.setFormat(PixelFormat.RGBA_8888);
window.setFlags(WindowManager.LayoutParams.FLAG_DITHER, WindowManager.LayoutParams.FLAG_DITHER);

【讨论】:

  • 虽然我没有这方面的实际经验,但这听起来像是一个合理的解释。 Windows Phone 对渐变做同样的事情,因为 WP7 上的显示(通常)只支持 RGB656,但您可以在 8888 中渲染。
  • 感谢您的回答。我 100% 确定我已经尝试过禁用和启用抖动,因为这是首先要尝试的最明显的事情之一,但我承认我忽略了把它放在问题中。此外,显示器的原始分辨率显然是 32 位。我稍后会再次检查以确保绝对确定并相应更新。在这个阶段,老实说,我开始怀疑华硕 Nexus 7 的固件问题,因为每次抽奖时的条带都是奇怪的随机性,这会对SurfaceView 造成可怕的移动噪音效果。
  • 只是为了确认,可悲的是抖动绝对不会产生任何明显的视觉差异。现在在我的 Nexus 7 上(即使使用最新的固件),如果我每次使用渐变绘制 SurfaceView 的线程时都删除 Canvas,那么每次绘制时,条带似乎是随机的。跨度>
【解决方案4】:

如果在 ICS 及更高版本中设置像素格式没有任何效果,很可能是由于硬件加速始终会呈现为原生像素格式。 大多数时候应该只是 ARGB_8888。确保您还设置了 Activity 的窗口像素格式,而不仅仅是 SurfaceView 上的像素格式。

您可以通过关闭加速功能轻松验证是否属于这种情况。您提到您对此进行了测试,但没有提及您是如何做到的。设置目标 sdk 级别并不是最明确的方法。

在我看来,软件渲染在 HoneyComb (3.0) 中默认切换为 ARGB_8888,但您需要再次明确设置它以正确支持旧设备,而这不是默认设置。

【讨论】:

  • 窗口正在切换到RGBA_8888,可以在测试Activity 代码开始附近看到。在 Manifest 中分别使用 android:hardwareAccelerated= "true""false" 启用和禁用硬件加速
  • 补充一点:在上面的测试代码中,我也在SurfaceView 中设置了ARGB_8888。 API docs声明Holder上的setFormat()调用必须在表面上操作的Thread内完成,所以我试过了;事实上,我在几个地方都试过这个电话。情况是,尽管设备在 32 位显示器上进行了测试,并且 Window 和表面都设置为 ARGB_8888,但无论是否设置了硬件加速,我都会得到类似的伪像。如果您有 4.0 / 4.1 设备,我很想知道您从我的测试代码中看到的结果。
  • 抱歉,我只浏览了你的代码,如果有时间我会在我的 Galaxy Nexus 上尝试代码。
  • 不知道 HTC One X,但 Nexus 7 以其糟糕的显示而闻名。灰度严重偏离标准 2.2 伽马。固件错误在“某些”设备上进一步降低了显示质量。 (虽然我不知道这是否已解决。)我强烈建议不要使用 Nexus 7 来优化您的代码以提高显示质量。
  • Displaymate 有一些细节:displaymate.com/Tablet_7inch_ShootOut_1.htm
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-25
相关资源
最近更新 更多