android - studio - Colorea bandas y artefactos con degradados a pesar de usar RGBA_8888 en todas partes
surfaceview vs view (4)
Soy consciente de que las bandas de color son un problema antiguo que se ha discutido muchas veces antes con varias soluciones proporcionadas (que esencialmente se reducen al uso de 32 bits o el uso del dithering). De hecho, no hace mucho tiempo, pregunté y luego respondí a mi propia pregunta sobre este tema. En aquel entonces, pensé que la solución que había puesto en la respuesta a esa pregunta (que era aplicar setFormat(PixelFormat.RGBA_8888)
a la Window
y también al Holder
en el caso de un SurfaceView
) había resuelto el problema en mi solicitud para bueno. Al menos, la solución hizo que el gradiente se viera muy bien en los dispositivos que estaba desarrollando en ese entonces (probablemente en Android 2.2).
Ahora estoy desarrollando con un HTC One X (Android 4.0) y un Asus Nexus 7 (Android 4.1). Lo que traté de hacer fue aplicar un degradado de color gris en toda el área de un SurfaceView
. A pesar de que supuestamente me aseguré de que la Window
contenedora y el Holder
estén configurados para el color de 32 bits, obtengo artefactos de bandas horribles. De hecho, en el Nexus 7, incluso veo que se mueven los artefactos . Esto ocurre no solo en SurfaceView
que por supuesto se está dibujando continuamente, sino también en una View
normal que agregué al lado para dibujar exactamente el mismo gradiente para propósitos de prueba, que se habría dibujado una vez. La forma en que estos artefactos están ahí y también parecen moverse por supuesto parece absolutamente horrible, y en realidad es como ver un televisor analógico con una señal pobre . Tanto la View
como la View
SurfaceView
muestran exactamente los mismos artefactos, que se mueven juntos.
Mi intención es usar 32 bits a lo largo, y no usar dithering. Tengo la impresión de que la Window
era de 32 bits por defecto mucho antes de Android 4.0. Al aplicar RGBA_8888
en SurfaceView
, habría esperado que todo fuera de 32 bits, evitando así cualquier artefacto.
Observo que hay otras preguntas sobre SO en las que las personas han observado que RGBA_8888
ya no parece ser efectivo en las plataformas 4.0 / 4.1.
Esta es una captura de pantalla de mi Nexus 7, con una View
normal en la parte superior y una SurfaceView
debajo, ambas aplicando el mismo degradado al Canvas
. Por supuesto, no muestra los artefactos tan bien como lo hacen cuando miran la pantalla, por lo que probablemente sea bastante inútil mostrar esta captura de pantalla. Sin embargo, quiero enfatizar que las bandas realmente se ven terribles en la pantalla del Nexus. Edición: De hecho, la captura de pantalla realmente no muestra los artefactos en absoluto . Los artefactos que veo en el Nexus 7 no son bandas uniformes; se ve al azar en la naturaleza.
La Activity
prueba utilizada para crear lo anterior:
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) {
}
}
}
He instalado una aplicación llamada Display Tester de Google Play. Esta aplicación se puede utilizar para crear gradientes de prueba en la pantalla. Aunque sus gradientes no parecen perfectos, parecen un poco mejores de lo que he podido lograr, lo que me hace preguntarme si hay alguna otra medida que pueda hacer para evitar las bandas.
La otra cosa que observo es que la aplicación Display Tester informa que la pantalla de mi Nexus es de 32 bits.
Para información, estoy habilitando explícitamente la aceleración de hardware. Mis niveles de SDK son:
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15"></uses-sdk>
Otra cosa que observo es que el fondo de degradado predeterminado para la Activity
, que entiendo que es una característica de Holo, también está muy agrupado. Esto tampoco se muestra en absoluto en la captura de pantalla. Y, también acabo de notar que las bandas del fondo se mueven brevemente en mi Nexus 7, en simpatía con el movimiento de bandas en mis dos Views
. Si creo un proyecto de Android completamente nuevo con la Activity
''vacía'' predeterminada, la Activity
muestra un fondo de degradado con bandas en mi Nexus y HTC One X. ¿Es esto normal? Entiendo que este fondo predeterminado de degradado negro / púrpura es lo que tendrá una Activity
si la aceleración de hardware está habilitada. Bueno, independientemente de si la aceleración de hardware está habilitada o no, veo el mismo degradado de fondo de Activity
bandas desagradables. Esto incluso ocurre en mi proyecto de prueba vacío, cuyo SDK de destino es 15. Para aclarar, la forma en que habilito o deshabilito la aceleración de hardware es explícitamente usando android:hardwareAccelerated="true"
y android:hardwareAccelerated="false"
.
No estoy seguro de si mi observación sobre el fondo del gradiente de la Activity
negro / púrpura de Holo tiene algo que ver con mi pregunta principal, pero parece extrañamente relacionada. También es extraño que se vea como de mala calidad (es decir, en bandas) y se vea igual sin importar si la aceleración de hardware está activada. Por lo tanto, una pregunta secundaria sería: Cuando tenga una Activity
con el fondo de degradado Holo predeterminado, y para cada caso de aceleración de hardware habilitada y luego deshabilitada, si este fondo de gradiente (a) está presente y (b) se ve perfectamente suave ? Haría esto en una pregunta separada, pero nuevamente, parece estar relacionado.
Entonces, en resumen: el problema básico que tengo es que la aplicación de un fondo degradado a un SurfaceView
simplemente no se puede hacer, parece, en mi Nexus 7. No es solo el problema de las bandas (que con mucho gusto podría soportar). solo eso); En realidad, es el hecho de que las bandas son de naturaleza aleatoria en cada sorteo. Esto significa que un SurfaceView
que constantemente se vuelve a dibujar termina teniendo un fondo borroso y en movimiento.
¿Ha intentado configurar el formato de píxel para la vista de superficie?
final SurfaceHolder holder = getHolder();
holder.setFormat(PixelFormat.RGBA_8888);
¿Por qué has puesto en falso el dithering en tu pintura. Yo sugeriría activar el dithering
paint.setDither(true);
Android doc dice claramente que bajará tu renderización:
configuración o borrado del bit DITHER_FLAG El desvío afecta la manera en que los colores que tienen una mayor precisión que el dispositivo no se muestrean. Por lo general, ningún dithering es más rápido, pero los colores de mayor precisión solo se truncan (por ejemplo, 8888 -> 565). El dithering intenta distribuir el error inherente en este proceso, para reducir los artefactos visuales.
También puede intentar agregar el FLAG_DITHER a la ventana:
window.setFormat(PixelFormat.RGBA_8888);
window.setFlags(WindowManager.LayoutParams.FLAG_DITHER, WindowManager.LayoutParams.FLAG_DITHER);
Para concluir con una respuesta, la conclusión a la que he llegado es que el Nexus 7 solo tiene un problema de hardware / firmware, lo que significa que es un problema absoluto en los gradientes de renderizado.
Si no ve ningún efecto de configurar el formato de píxel en ICS o superior, lo más probable es que se deba a la aceleración del hardware que siempre se representará en el formato de píxel nativo. Sin embargo, la mayoría de las veces debería ser solo ARGB_8888. Asegúrese de configurar también el formato de píxeles de la ventana de su actividad, no solo el formato de píxeles en SurfaceView.
Puede verificar fácilmente si ese es el caso desactivando la aceleración. Mencionas que probaste eso, pero no mencionas cómo lo hiciste. Establecer el nivel de sdk de destino no es la forma más explícita de hacerlo.
Desde la parte superior de mi cabeza, la representación del software cambió a ARGB_8888 de manera predeterminada en HoneyComb (3.0), pero de nuevo, deberá configurarlo explícitamente para que admita correctamente los dispositivos más antiguos en los que este no es el predeterminado.