Inicio Android Colores: Librería Palette

Colores: Librería Palette

por sgoliver

Material Design da mucha importancia a la forma, posición y comportamiento de los diferentes elementos gráficos que conforman la interfaz de usuario de una aplicación. Y como parte de este aspecto visual el color juega un papel fundamental en todo el conjunto.

Ya hemos visto en algún artículo anterior cómo definir los colores principales de nuestra aplicación. Por ejemplo en los artículos sobre la action bar vimos cómo especificar, en tiempo de diseño, los colores básicos (primary, accent, …) a utilizar en diferentes elementos de la interfaz.

Sin embargo, hay ocasiones en las que nos interesaría poder elegir o modificar, de forma dinámica, en tiempo de ejecución, los colores de nuestra interfaz para adaptarlos a otros elementos sobre los que no tenemos control cuando diseñamos la aplicación, por ejemplo imágenes obtenidas de la red o de alguna API.

Para ayudarnos en esta tarea, Android ofrece una librería de soporte llamada palette que nos permite realizar exactamente la tarea indicada, es decir, analizar una imagen y seleccionar por nosotros sus colores principales, de forma que podamos hacer uso de ellos para colorear el resto de nuestra interfaz, por ejemplo el texto, los títulos, el tintado de algunos componentes, o el color de la propia action bar.

El uso de esta librería es bastante sencillo. En primer lugar tendremos que añadir su referencia al proyecto de forma análoga a cómo ya hemos hecho en otras ocasiones con otras librerías. En el fichero build.gradle de nuestro módulo principal añadiremos su referencia a la sección de dependencias:

dependencies {
    //...
    compile 'com.android.support:palette-v7:22.2.1'
}

Hecho esto, ya estaríamos en disposición de hacer una llamada al método de generación de colores de la librería pasándole como parámetro una imagen en forma de Bitmap.

Existen dos posibilidades a la hora de utilizar esta librería, de forma síncrona o asíncrona. La elección de un método u otro dependerá de nuestra aplicación. Por ejemplo, si estamos utilizando algún método/librería para la obtención de las imágenes desde la red, y este mecanismo ya utiliza un hilo independiente para realizar dicha tarea (caso típico cuando utilizamos librerías tipo Volley, Picasso, Glide…), lo normal sería utilizar la alternativa síncrona dentro del propio proceso de obtención de las imágenes, que ya de por sí se ejecuta en un hilo distinto al hilo principal.

//Llamada síncrona:
Palette p = Palette.from(bitmap).generate();

En otros casos, puede utilizarse la llamada asíncrona para no bloquear el hilo principal durante el análisis de la imagen y la generación de la paleta de colores. Como es habitual en este tipo de llamadas asíncronas, nos basaremos en la definición de un listener que pasaremos como parámetro en la llamada. Al finalizarse la tarea se llamará al método/s de dicho listener con el resultado obtenido.

//Llamada asíncrona:
Palette.from(bitmap).generate(new PaletteAsyncListener() {
    public void onGenerated(Palette p) {
        //Aquí haríamos uso de los colores generados en la paleta, por ejemplo
        //para modificar el color de otros componentes de la interfaz
    }
});

En mi caso de ejemplo utilizaré esta opción por simplicidad, ya que usaré imágenes locales añadidas como recursos del proyecto en la carpeta /res/drawable.

Como vemos en cualquiera de los dos fragmentos de código anteriores, utilizaremos el método estático from() de la clase Palette para indicar la imagen que queremos analizar para generar la paleta con sus colores principales. Tras esto llamamos al método generate() para comenzar dicho análisis. En el caso asíncrono el método generate() recibe como parámetro un listener de tipo PaletteAsyncListener que implementa el método onGenerated(), que se ejecutará automáticamente cuando finalice la generación de la paleta de colores a partir de la imagen indicada.

Como ya he indicado, en nuestro caso de ejemplo utilizaré imágenes incluidas en el propio proyecto como recursos de tipo drawable. Para convertir dichos recursos en un Bitmap podemos utilizar la clase BitmapFactory, y más concretamente su método estático decodeResource(), pasándole el ID del recurso:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.flores_2);

Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() {
     public void onGenerated(Palette p) {
           //...
     }
}

Una vez obtenida la paleta de colores, ¿cómo podemos utilizarla? Pues bien, tendremos también varias formas de acceder a los colores obtenidos, dependiendo de nuestras necesidades.

La más sencilla de ellas es acceder a los 6 colores principales que siempre intenta generar Palette para la imagen analizada:

  • Vibrant
  • Dark Vibrant
  • Light Vibrant
  • Muted
  • Dark Muted
  • Light Muted

La librería intenta obtener dos grupos colores, los primeros más «intensos» (vibrant), y los segundos más «tenues» (muted). Dentro de cada grupo, intenta además identificar tres colores: el principal dentro del grupo, uno oscuro (dark) y otro claro (light). Es importante tener en cuenta que no siempre logrará generar estos 6 colores, dependiendo de las características de la imagen analizada es posible que alguno de ellos no se genere.

Para acceder a estos colores podemos utilizar los métodos getXXXColor() de la paleta. En nuestro ejemplo, además de la imagen a analizar, he añadido a la interfaz 6 cuadros de texto que colorearemos con los colores obtenidos:

//Llamada síncrona:
Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() {
    public void onGenerated(Palette p) {

        //Opción 1: Acceso directo a los 6 colores principales
        txtVibrant.setBackgroundColor(p.getVibrantColor(Color.BLACK));
        txtDarkVibrant.setBackgroundColor(p.getDarkVibrantColor(Color.BLACK));
        txtLightVibrant.setBackgroundColor(p.getLightVibrantColor(Color.BLACK));
        txtMuted.setBackgroundColor(p.getMutedColor(Color.BLACK));
        txtDarkMuted.setBackgroundColor(p.getDarkMutedColor(Color.BLACK));
        txtLightMuted.setBackgroundColor(p.getLightMutedColor(Color.BLACK));
    }
}

Como podéis ver, para obtener por ejempo el color Dark Vibrant de la paleta utilizamos el método getDarkVibrantColor(), al que además pasamos como parámetro el color por defecto de devolverá en caso de que no se haya generado ningún color para esta categoría de la paleta. Una vez obtenido el color lo establecemos como color de fondo de los cuadros de texto mediante su método setBackgroundColor().

La segunda alternativa es acceder, no directamente a los colores RGB generados, sino a las estructuras que realmente genera esta librería, llamadas Swatch. Palette intenta generar un Swatch por cada una de las 6 categorías de color indicadas anteriormente (aunque más tarde veremos que no sólo genera éstas seis). Un Swatch contiene por supuesto el color generado para la categoría, pero también más información que puede sernos de mucha utilidad en algunas situaciones, entre otros:

  1. Color del Swatch en formato RGB
  2. Color del Swatch en formato HSL
  3. Número de píxeles de la imagen con este color (population)
  4. Color de texto de título que podría usarse sobre el color de este Swatch (title text color)
  5. Color de texto general que podría usarse sobre el color de este Swatch (body text color)

Los más interesantes, además del primero que es el color asociado al Swatch, son los dos últimos, ya que nos dan información sobre qué color podríamos utilizar para escribir texto sobre el color del Swatch (el del punto 1) asegurando que el contraste entre ambos colores es suficiente para facilitar la lectura. Así, por ejemplo, si el color de Swatch es oscuro se indicará un color de texto claro para asegurar que puede leerse con facilidad.

La diferencia entre el color de texto de título y el de texto general suele estar en la opacidad del mismo. Para el título suele elegirse un calor más «translúcido» que para el texto principal dado que éste suele ser de mayor tamaño y suele requerir menos contraste.

Veamos cómo accederíamos a los swatches principales desde nuestro código:

Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() {
    public void onGenerated(Palette p) {

        //Opción 2: Acceso a los swatches pricipales completos
        setTextViewSwatch(txtVibrant, p.getVibrantSwatch());
        setTextViewSwatch(txtDarkVibrant, p.getDarkVibrantSwatch());
        setTextViewSwatch(txtLightVibrant, p.getLightVibrantSwatch());
        setTextViewSwatch(txtMuted, p.getMutedSwatch());
        setTextViewSwatch(txtDarkMuted, p.getDarkMutedSwatch());
        setTextViewSwatch(txtLightMuted, p.getLightMutedSwatch());
    }
}

//...

private void setTextViewSwatch(TextView tview, Palette.Swatch swatch) {
    if(swatch != null) {
        tview.setBackgroundColor(swatch.getRgb());
        tview.setTextColor(swatch.getBodyTextColor());
        tview.setText("Pixeles: " + swatch.getPopulation());
    }
    else {
        tview.setBackgroundColor(Color.BLACK);
        tview.setTextColor(Color.WHITE);
        tview.setText("(sin definir)");
    }
}

En este caso utilizamos los métodos getXXXSwatch() de la clase Palette para obtener los swatches principales. Por ejemplo para obtener el Swatch correspondiente a la categoría Dark Vibrant utilizaríamos el método getDarkVibrantSwatch(). En este caso no se indica color por defecto, por lo que estos métodos devolverán null cuando el color de dicha categoría no se haya generado.

Para nuestro ejemplo, colorearé el fondo de los cuadros de texto con los 6 colores principales, y escribiré sobre ellos el número de píxeles de cada color utilizando el color elegido como body text color. Para ello he creado un método auxiliar que se encargue de este trabajo. La obtención de los distintos datos es bastante directa, el color del swatch lo obtenemos mediante el método getRgb(), el color del texto principal mediante getBodyTextColor() y el número de píxeles con dicho color mediante getPopulation(). Aunque no lo utilizamos en el ejemplo, también podríamos obtener el color en HSL llamando a getHsl() o el color de texto de títulos con getTitleTextColor().

demo-palette-1

Por último existiría una tercera alternativa para obtener los colores generados a partir de la imagen. Hasta ahora hemos hablado de seis categorías principales, pero realmente Palette no se limita a esos seis colores, sino que por lo general obtiene una paleta más grande, que incluye más colores de la imagen. Para acceder a todos los colores obtenidos podemos utlizar el método getSwatches(), que devuelve los swatches correspondientes a todos los colores generados en la paleta.

Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() {
    public void onGenerated(Palette p) {

        //...

       //Opción 3: Acceso a todos los swatches generados
       for (Palette.Swatch sw : p.getSwatches()) {
            Log.i("Palette",
                "Color: #" + Integer.toHexString(sw.getRgb()) + " (" + sw.getPopulation() + " píxeles)");
        }
    }
});

En nuestra aplicación de ejemplo, accedo a todos los colores generados y los escribo en el log de la aplicación. Para la foto de ejemplo se ha generado una paleta con 15 colores:

Color: #ffbda418 (1005 píxeles)
Color: #ff6a1010 (3275 píxeles)
Color: #ffb4bd9c (46 píxeles)
Color: #ffb4a462 (49 píxeles)
Color: #ff396239 (6496 píxeles)
Color: #ff292910 (6502 píxeles)
Color: #ffb45200 (393 píxeles)
Color: #ff527b5a (837 píxeles)
Color: #ff7b946a (94 píxeles)
Color: #ffe6c508 (605 píxeles)
Color: #ff6a8b62 (189 píxeles)
Color: #ffc59c00 (3226 píxeles)
Color: #ffac4100 (792 píxeles)
Color: #ff9c0808 (1593 píxeles)
Color: #ff62835a (392 píxeles)

¿Pero cómo podemos controlar el número de colores generados por la librería? El número de colores que se intentan generar por defecto son 16. Sin embargo podemos especificar otro número a la hora de llamar al método generate(). Para ello podríamos hacer una llamada previa a maximumColorCount() con el número deseado:

Palette.from(bitmap).maximumColorCount(24).generate(...);

Algunos números recomendados en la propia documentación de la librería son de ~16 para imágenes de paisaje o ~24 para imágenes de personas, avatares o imágenes de perfil.

Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la página del curso en GitHub.

También te puede interesar

3 comentarios

Luis Solis 04/02/2016 - 20:50

Muy utiles tus publicaciones, muchas Gracias

Responder
Leonela 02/01/2018 - 1:34

Hola mucho gusta interezante me preguntaba si esta libreria puede tomar el color mas dominante de una imagen ?

Responder
Lucas 30/03/2018 - 3:26

Muchas gracias por tus aportes. Tus explicaciones son geniales !!

Responder

Responder a Lucas

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información. Aceptar Más Información

Política de Privacidad y Cookies