Подтвердить что ты не робот

Цветовая гамма и артефакты с градиентами, несмотря на использование RGBA_8888 во всем мире

Я знаю, что цветовое полотно - это старый каштан проблемы, который обсуждался много раз, когда предлагались различные решения (которые, по существу, сводятся к использованию 32-битной версии или используют сглаживание). На самом деле не так давно я спросил и впоследствии ответил на мой собственный вопрос SO относительно этого. В то время я думал, что решение, которое я поставил в ответ на этот вопрос (а именно, применить setFormat(PixelFormat.RGBA_8888) к Window, а также к Holder в случае SurfaceView), решило проблему в мое выражение навсегда. По крайней мере, решение сделало градиент очень приятным на устройствах, которые я разрабатывал тогда (скорее всего, Android 2.2).

Сейчас я развиваюсь с помощью HTC One X (Android 4.0) и Asus Nexus 7 (Android 4.1). Я попытался применить серый градиент ко всей области SurfaceView. Хотя я предположительно гарантировал, что содержащие Window и Holder настроены для 32-битного цвета, я получаю ужасные артефакты. На самом деле, на Nexus 7, я даже вижу, что артефакты перемещаются вокруг. Это происходит не только на SurfaceView, который, конечно, непрерывно рисуется, но и в нормальном View, который я добавил рядом, чтобы нарисовать ровно тот же градиент для целей тестирования, который был бы нарисован один раз. Способ, которым эти артефакты существуют, а также, кажется, движется вокруг, выглядит абсолютно ужасно, и на самом деле это похоже на просмотр аналогового телевизора с плохим сигналом. Оба View и SurfaceView демонстрируют те же артефакты, которые перемещаются вместе.

Мое намерение состоит в том, чтобы использовать 32-битную версию и не использовать сглаживание. У меня создается впечатление, что Window был 32-битным по умолчанию задолго до Android 4.0. Применяя RGBA_8888 в SurfaceView, я бы ожидал, что все будет 32-битным, избегая при этом каких-либо артефактов.

Я отмечаю, что есть еще какие-то вопросы о SO, где люди заметили, что RGBA_8888 больше не работает на платформах 4.0/4.1.

Это скриншот от моего Nexus 7, с нормальным View вверху и SurfaceView ниже, оба применяют тот же градиент к Canvas. Конечно, он не показывает артефакты так же, как они делают, глядя на дисплей, и поэтому, вероятно, довольно бессмысленно показывать этот захват экрана. Я хочу подчеркнуть, что полоса на самом деле выглядит ужасно на экране Nexus. Изменить: На самом деле, скриншот на самом деле не показывает артефакты вообще. Артефакты, которые я вижу на Nexus 7, не являются равномерным диапазоном; он выглядит случайным по своей природе.

enter image description here

Тест 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) {

        }
    }


}

Я установил приложение под названием Display Tester из Google Play. Это приложение можно использовать для создания тестовых градиентов на экране. Хотя его градиенты не выглядят идеально, они кажутся немного лучше, чем я мог достичь, что заставляет меня задаться вопросом, есть ли еще одна мера, которую я могу сделать, чтобы предотвратить бандажирование.

Другое замечание: приложение Display Tester сообщает, что экран Nexus 32-бит.

Для получения информации я могу явно включить аппаратное ускорение. Уровни SDK:

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

Еще одна вещь, которую я замечаю, заключается в том, что фон градиента по умолчанию для Activity, который, как я понимаю, является особенностью Holo, также очень ограничен. Это также совсем не отображается на скриншоте. И я также заметил, что на моем Nexus 7 коротко перемещается обтекание фона, сочувствуя движению бандажа в моих двух Views. Если я создаю совершенно новый Android-проект со стандартным "пустым" Activity, Activity показывает неприятный ленточный фон с градиентом как на моем Nexus, так и на HTC One X. Это нормально? Я понимаю, что этот черный/фиолетовый градиент по умолчанию - это то, что имеет значение Activity, если аппаратное ускорение включено. Ну, независимо от того, включено ли аппаратное ускорение или нет, я вижу тот же неприятный полосатый Activity фоновый градиент. Это даже происходит в моем пустом тестовом проекте, целевой SDK которого составляет 15. Чтобы уточнить, способ включения или отключения аппаратного ускорения явно использует android:hardwareAccelerated="true" и android:hardwareAccelerated="false".

Я не уверен, что мое наблюдение о фоне градиента Холо/фиолетового Activity имеет какое-то отношение к моему основному вопросу, но это кажется странным. Также странно, что он выглядит таким низким качеством (т.е. С полосой) и выглядит одинаково независимо от того, включено ли ускорение аппаратного обеспечения. Таким образом, вторичный вопрос: Когда у вас есть Activity с фоном градиента по умолчанию для Holo, и для каждого случая ускорения аппаратного обеспечения, который является включенным и затем отключен, должен ли этот фон градиента (a) присутствовать и (b) смотреть отлично гладкая? Я бы спросил об этом в отдельном вопросе, но опять же, похоже, это связано.

Итак, в резюме: Основная проблема, с которой я сталкиваюсь, заключается в том, что применение фона градиента к SurfaceView просто не может быть сделано, по-моему, на моем Nexus 7. Это не просто объединение, что проблема (с которой я мог бы с радостью согласиться, если бы это было именно так); это на самом деле тот факт, что полоса является случайной по своей природе на каждой ничьей. Это означает, что a SurfaceView, который постоянно перерисовывается, заканчивается наличием движущегося нечеткого фона.

4b9b3361

Ответ 1

Как раз для того, чтобы обернуть это ответом, я пришел к выводу, что Nexus 7 просто имеет некоторые проблемы с аппаратным обеспечением и прошивкой, что означает, что это абсолютно штаны в градиентах рендеринга.

Ответ 2

Пробовал ли вы настройку pixelformat для самого поверхностного вида?

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

Ответ 3

Если вы не видите эффекта настройки формата пикселей в ICS и выше, это, скорее всего, связано с аппаратным ускорением, которое всегда будет отображаться в формате родного пикселя. В большинстве случаев это должно быть просто ARGB_8888. Убедитесь, что вы также задаете формат пикселя окна своей деятельности, а не только формат пикселей в SurfaceView.

Вы можете легко проверить, если это так, отключив ускорение. Вы упомянули, что вы это протестировали, но не упоминаете, как вы это сделали. Установка целевого уровня sdk - это не самый явный способ сделать это.

В верхней части моей головы рендеринг программного обеспечения по умолчанию был включен в HoneyComb (3.0), но вам снова нужно будет явно настроить его для более старых устройств, если это не по умолчанию.

Ответ 4

Почему вы установили ложное сглаживание на вашей краске. Я предлагаю активировать сглаживание

paint.setDither(true);

Android doc ясно говорит, что он снизит рендеринг:

установка или очистка бит DITHER_FLAG Сглаживание влияет на то, как цвета которые являются более высокой точностью, чем устройство с дискретизацией. нет сглаживание обычно быстрее, но более высокие цвета точности (например, 8888 → 565). Сглаживание пытается распределить ошибка, присущая этому процессу, для уменьшения визуальных артефактов.

Вы также можете попытаться добавить FLAG_DITHER в окно:

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