27 - Canvas - drawImage

Hemos visto en conceptos anteriores que cuando queremos mostrar una única imagen, utilizamos la función composable 'Image'. Ahora veremos como podemos mostrar imágenes dentro de un Canvas.

.

Disponemos de la función drawImage que puede ser llamada dentro de Canvas.

Problema

Confeccionar una interfaz que muestre un tablero de ajedrez, disponer en su interior solo los peones blancos y negros. Recuperar las imágenes de dos archivo png.

Crearemos un proyecto llamado: 'Compose29'

La interfaz visual a implementar debe ser similar a:

Canvas drawImage tablero ajedrez Jetpack Compose

El código a implementar en Kotlin para obtener dicha funcionalidad es:

package com.tutorialesprogramacionya.compose29

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.sp

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Column() {
                Row {
                    Image(
                        bitmap = ImageBitmap.imageResource(id = R.drawable.peonblanco),
                        contentDescription = ""
                    )
                    Image(
                        bitmap = ImageBitmap.imageResource(id = R.drawable.peonnegro),
                        contentDescription = ""
                    )
                    Text(text = "Peones blancos y negros", fontSize = 30.sp)
                }
                TableroAjedrez()
            }
        }
    }
}

@Composable
fun TableroAjedrez() {
    var peonBlanco = ImageBitmap.imageResource(id = R.drawable.peonblanco)
    var peonNegro = ImageBitmap.imageResource(id = R.drawable.peonnegro)
    Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
        val ancho = size.width
        val alto = size.height
        val anchoCuadrado = if (alto > ancho) ancho / 8f else alto / 8f
        var columna = 0f
        var fila = 0f
        var colorActual = Color.DarkGray
        for (fil in 1..8) {
            for (col in 1..8) {
                drawRect(
                    topLeft = Offset(x = columna, y = fila),
                    size = Size(anchoCuadrado, anchoCuadrado),
                    color = colorActual
                )
                colorActual = if (colorActual == Color.DarkGray) Color.LightGray else Color.DarkGray
                if (fil == 2)
                    drawImage(
                        image = peonBlanco,
                        dstOffset = IntOffset(columna.toInt(), fila.toInt()),
                        dstSize = IntSize(anchoCuadrado.toInt(), anchoCuadrado.toInt())
                    )
                if (fil == 7)
                    drawImage(
                        image = peonNegro,
                        dstOffset = IntOffset(columna.toInt(), fila.toInt()),
                        dstSize = IntSize(anchoCuadrado.toInt(), anchoCuadrado.toInt())
                    )
                columna += anchoCuadrado
            }
            fila += anchoCuadrado
            columna = 0f
            colorActual = if (colorActual == Color.DarkGray) Color.LightGray else Color.DarkGray
        }
    })
}

En la parte superior de la pantalla recordamos como mostrar imágenes empleando la función componible Image, luego llamamos a la función componible TableroAjedrez:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Column() {
                Row {
                    Image(
                        bitmap = ImageBitmap.imageResource(id = R.drawable.peonblanco),
                        contentDescription = ""
                    )
                    Image(
                        bitmap = ImageBitmap.imageResource(id = R.drawable.peonnegro),
                        contentDescription = ""
                    )
                    Text(text = "Peones blancos y negros", fontSize = 30.sp)
                }
                TableroAjedrez()
            }
        }
    }
}

En la función TableroAjedrez procedemos a recuperar las dos imágenes llamando a la función componible 'imageResource' (tengamos en cuenta que si bien la función TableroAjedrez luego puede llamarse varias veces por el API de Compose, la misma librería administra la lectura eficiente del recurso):

fun TableroAjedrez() {
    var peonBlanco = ImageBitmap.imageResource(id = R.drawable.peonblanco)
    var peonNegro = ImageBitmap.imageResource(id = R.drawable.peonnegro)

Primero llamamos a la función Canvas, ocupando todo el espacio disponible (Modifier.fillMaxSize()), como debemos dibujar 64 rectángulos (casillas del tablero) disponemos dos for anidados y mediante la función drawRect dibujamos cada cuadrado. Para que las celdas alternen su color, empleamos la variable colorActual y dentro del for vamos alternando entre LightGray y DarkGray:

    Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
        val ancho = size.width
        val alto = size.height
        val anchoCuadrado = if (alto > ancho) ancho / 8f else alto / 8f
        var columna = 0f
        var fila = 0f
        var colorActual = Color.DarkGray
        for (fil in 1..8) {
            for (col in 1..8) {
                drawRect(
                    topLeft = Offset(x = columna, y = fila),
                    size = Size(anchoCuadrado, anchoCuadrado),
                    color = colorActual
                )
                colorActual = if (colorActual == Color.DarkGray) Color.LightGray else Color.DarkGray
                if (fil == 2)
                    drawImage(
                        image = peonBlanco,
                        dstOffset = IntOffset(columna.toInt(), fila.toInt()),
                        dstSize = IntSize(anchoCuadrado.toInt(), anchoCuadrado.toInt())
                    )
                if (fil == 7)
                    drawImage(
                        image = peonNegro,
                        dstOffset = IntOffset(columna.toInt(), fila.toInt()),
                        dstSize = IntSize(anchoCuadrado.toInt(), anchoCuadrado.toInt())
                    )
                columna += anchoCuadrado
            }
            fila += anchoCuadrado
            columna = 0f
            colorActual = if (colorActual == Color.DarkGray) Color.LightGray else Color.DarkGray
        }
    })
}

Para dibujar una imagen llamamos a la función drawImage y le pasamos como primer parámetro la referencia de la imagen a mostrar, el segundo parámetro es la columna y fila donde debe aparecer la imagen con respecto a las dimensiones del Canvas y el tercer parámetro que configuramos es el ancho y alto que debe aparecer la imagen:

                    drawImage(
                        image = peonBlanco,
                        dstOffset = IntOffset(columna.toInt(), fila.toInt()),
                        dstSize = IntSize(anchoCuadrado.toInt(), anchoCuadrado.toInt())
                    )

Las imágenes de los peones deben aparecer solo en las filas 2 y 7 del tablero:

Utilizamos los contadores columna y fila para ubicar cada cuadrado del tablero y la coordenada de la imagen.

Este proyecto lo puede descargar en un zip desde este enlace: Compose29.zip