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

Я знаю, что цветовое полотно – это старый каштан проблемы, который обсуждался много раз, когда предлагались различные решения (которые, по существу, сводятся к использованию 32-битных во всем или используют сглаживание). На самом деле не так давно я спросил и впоследствии ответил на мой собственный вопрос о том, что касается этого. Тогда я подумал, что решение, которое я поставил в ответ на этот вопрос (то есть применить 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 который, конечно же, непрерывно 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, не являются равномерным диапазоном; Он выглядит случайным по своей природе.

Введите описание изображения здесь

Тестовая 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 показывает неприятный фон с градиентом полосы как на моем Nexus, так и на HTC One X. Это нормально? Я понимаю, что этот фоновый фон с черным / фиолетовым градиентом по умолчанию – это то, что Activity должно иметь, если аппаратное ускорение включено. Ну, независимо от того, включено ли аппаратное ускорение или нет, я вижу тот же скверный полосатый фоновой градиент Activity . Это даже происходит в моем пустом тестовом проекте, целевой SDK которого составляет 15. Чтобы уточнить, способ включения или отключения аппаратного ускорения явно использует android:hardwareAccelerated="true" и android:hardwareAccelerated="false" .

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

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

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

    Вы пытались настроить pixelformat для самого поверхностного вида?

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

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

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

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

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

     paint.setDither(true); 

    Android doc ясно говорит, что он будет понижать рендеринг:

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

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

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