Цветовая гамма и артефакты с градиентами, несмотря на использование 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 который постоянно перерисовывается, заканчивается наличием движущегося нечеткого фона.

Solutions Collecting From Web of "Цветовая гамма и артефакты с градиентами, несмотря на использование RGBA_8888 во всем мире"

Чтобы закончить это с ответом, я пришел к выводу, что 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);