Pregunta Haz un agujero en una superposición rectangular con la aceleración HW habilitada en la vista


Tengo una vista que hace un dibujo básico. Después de esto, quiero dibujar un rectángulo con un agujero perforado para que solo una región del dibujo anterior sea visible. Y me gustaría hacer esto con la aceleración de hardware habilitada para mi vista para un mejor rendimiento.

Actualmente tengo dos métodos que funcionan, pero solo funciona cuando la aceleración del hardware está deshabilitada y el otro es demasiado lento.

Método 1: aceleración SW (lento)

final int saveCount = canvas.save();

// Clip out a circle.
circle.reset();
circle.addCircle(cx, cy, radius, Path.Direction.CW);
circle.close();
canvas.clipPath(circle, Region.Op.DIFFERENCE);

// Draw the rectangle color.
canvas.drawColor(backColor);

canvas.restoreToCount(saveCount);

Esto no funciona con la aceleración de hardware habilitada para la vista porque 'canvas.clipPath' no es compatible en este modo (sé que puedo forzar la representación SW, pero me gustaría evitar eso).

Método 2: aceleración HW (V. lento)

// Create a new canvas.
final Bitmap b = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
final Canvas c = new Canvas(b);

// Draw the rectangle colour.
c.drawColor(backColor);

// Erase a circle.
c.drawCircle(cx, cy, radius, eraser);

// Draw the bitmap on our views  canvas.
canvas.drawBitmap(b, 0, 0, null);

Donde se crea el borrador como

eraser = new Paint()
eraser.setColor(0xFFFFFFFF);
eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

Esto es obviamente lento, un nuevo Bitmap el tamaño de la vista se crea cada llamada de dibujo.

Método 3: aceleración HW (rápido, no funciona en algunos dispositivos)

canvas.drawColor(backColor);
canvas.drawCircle(cx, cy, radius, eraser);

Lo mismo que el método compatible con la aceleración HW, pero no se requiere un lienzo adicional. Sin embargo, hay un gran problema con esto: funciona con forzado de SW forzado, pero en el HTC One X (Android 4.0.4 y probablemente algunos otros dispositivos), al menos con el renderizado de HW, deja el círculo completamente negro. Esto probablemente esté relacionado con 22361.

Método 4: aceleración HW (aceptable, funciona en todos los dispositivos)

Según la sugerencia de Jan para mejorar el método 2, evité crear el mapa de bits en cada llamada a onDraw, en lugar de hacerlo en onSizeChanged:

if (w != oldw || h != oldh) {
    b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    c = new Canvas(b);
}

Y luego solo usé estos en onDraw:

if (overlayBitmap == null) {
   b = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
   c = new Canvas(b);
}
b.eraseColor(Color.TRANSPARENT);
c.drawColor(backColor);
c.drawCircle(cx, cy, radius, eraser);
canvas.drawBitmap(b, 0, 0, null);

El rendimiento no es tan bueno como el método 3, pero es mucho mejor que 2 y ligeramente mejor que 1.

La pregunta

¿Cómo puedo lograr el mismo efecto pero hacerlo de una manera compatible con la aceleración HW (Y que funciona de manera consistente en los dispositivos)? Un método que aumente el rendimiento de representación SW también sería aceptable.

NB: Al mover el círculo, estoy invalidando una región, no todo el lienzo, por lo que no hay margen para una mejora en el rendimiento.


32
2017-11-23 11:49


origen


Respuestas:


En lugar de asignar un nuevo lienzo en cada repintado, debería poder asignarlo una vez y luego volver a usar el lienzo en cada repintar.

en init y en cambiar el tamaño:

Bitmap b = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);

en repintado

b.eraseColor(Color.TRANSPARENT);
    // needed if backColor is not opaque; thanks @JosephEarl
c.drawColor(backColor);
c.drawCircle(cx, cy, radius, eraser);

canvas.drawBitmap(b, 0, 0, null);

17
2017-11-23 13:26