Offer to come, dig friends take it! I am participating in the 2022 Spring Recruit Punch card activity. Click here for details.

With product MM those things

A new product MM, because it is relatively flat, we call her A sister. A sister to the first day pointed out: the background of the Banner AD at the top of the page is white, too monotonous, people do not like it, according to the content of the AD picture automatically switch the background color, the color should be consistent with the main color of the AD picture. As a qualified coder, I refused directly, saying that our app is focused on simplicity, why the whole frills, waste of money. Sister A didn’t give up. She talked with me overnight and successfully convinced me.

To do this, Google has provided a handy tool ————Palette.

preface

Jetpack also includes Palette, which was released a long time ago. To use it, you need to rely on libraries

implementation 'androidx. The palette: palette: 1.0.0'
Copy the code

This article will show you how to use the Palette to extract color from an image.

Create the Palette

Creating the Palette is actually quite simple, as follows

var builder = Palette.from(bitmap)
var palette = builder.generate()
Copy the code

Thus, we create a Pallete object from a Bitmap.

Note: It is also possible to use Palette. Generate (bitmap) directly, but this method is no longer recommended, as it is still used in many older articles on the web. It is recommended to use Palette.Builder.

The generate() function is synchronous, of course, considering that image processing can be time-consuming, but Android also provides asynchronous functions

public AsyncTask<Bitmap, Void, Palette> generate(
        @NonNull final PaletteAsyncListener listener) {
Copy the code

Fetch the Palette instance via a PaletteAsyncListener, with the following interface:

public interface PaletteAsyncListener {
    /**
     * Called when the {@link Palette} has been generated. {@code null} will be passed when an
     * error occurred during generation.
     */
    void onGenerated(@Nullable Palette palette);
}
Copy the code

Extraction of color

With an instance of Palette, you can retrieve the color in the image from the corresponding function of the Palette object, and more than one color, as listed below:

  • GetDominantColor: Gets the main color of the image
  • GetMutedColor: Gets the soft color of the image
  • GetDarkMutedColor: Gets a soft dark color in the image
  • GetLightMutedColor: Gets soft, bright colors in an image
  • GetVibrantColor: Gets the vibrant color of the picture
  • GetDarkVibrantColor: Gets a vibrant dark color from the picture
  • GetLightVibrantColor: Gets a vibrant light color from an image

Each of these functions needs to provide a default color, which Swatch will use if the color is invalid. This is not intuitive, so let’s test it. The code is as follows:

var bitmap = BitmapFactory.decodeResource(resources, R.mipmap.a)
var builder = Palette.from(bitmap)
var palette = builder.generate()
color0.setBackgroundColor(palette.getDominantColor(Color.WHITE))
color1.setBackgroundColor(palette.getMutedColor(Color.WHITE))
color2.setBackgroundColor(palette.getDarkMutedColor(Color.WHITE))
color3.setBackgroundColor(palette.getLightMutedColor(Color.WHITE))
color4.setBackgroundColor(palette.getVibrantColor(Color.WHITE))
color5.setBackgroundColor(palette.getDarkVibrantColor(Color.WHITE))
color6.setBackgroundColor(palette.getLightVibrantColor(Color.WHITE))
Copy the code

The results after running are as follows:

So you can see the difference between the colors. In addition to the above function, you can also use getColorForTarget as follows:

@ColorInt
public int getColorForTarget(@NonNull final Target target, @ColorInt final int defaultColor) {
Copy the code

This function requires a Target, which provides six static fields, as follows:

/** * A target which has the characteristics of a vibrant color which is light in luminance. */
public static final Target LIGHT_VIBRANT;

/** * A target which has the characteristics of a vibrant color which is neither light or dark. */
public static final Target VIBRANT;

/** * A target which has the characteristics of a vibrant color which is dark in luminance. */
public static final Target DARK_VIBRANT;

/** * A target which has the characteristics of a muted color which is light in luminance. */
public static final Target LIGHT_MUTED;

/** * A target which has the characteristics of a muted color which is neither light or dark. */
public static final Target MUTED;

/** * A target which has the characteristics of a muted color which is dark in luminance. */
public static final Target DARK_MUTED;
Copy the code

In fact, it corresponds to the six colors above except the main tone.

Text color automatically ADAPTS

As you can see in the results above, the text on each color is clearly displayed, and they are not the same color. This is also a feature from the Palette.

Using the following function, we can get the Swatch object corresponding to each hue:

  • getDominantSwatch
  • getMutedSwatch
  • getDarkMutedSwatch
  • getLightMutedSwatch
  • getVibrantSwatch
  • getDarkVibrantSwatch
  • getLightVibrantSwatch

Note: As above, this can be obtained by getSwatchForTarget(@nonnull Final Target Target)

The Swatch class provides the following functions:

  • GetPopulation (): number of pixels in the sample
  • GetRgb (): RBG value of the color
  • GetHsl (): HSL value of the color
  • GetBodyTextColor (): Can all match the Swatch body text color value
  • GetTitleTextColor (): can all match the Swatch title text color value

So we use getBodyTextColor() and getTitleTextColor() to easily get the title and body text colors that are realistic on this color. So the above test code is complete as follows:

var bitmap = BitmapFactory.decodeResource(resources, R.mipmap.a)
var builder = Palette.from(bitmap)
varpalette = builder.generate() color0.setBackgroundColor(palette.getDominantColor(Color.WHITE)) color0.setTextColor(palette.dominantSwatch? .bodyTextColor ? : Color.WHITE) color1.setBackgroundColor(palette.getMutedColor(Color.WHITE)) color1.setTextColor(palette.mutedSwatch? .bodyTextColor ? : Color.WHITE) color2.setBackgroundColor(palette.getDarkMutedColor(Color.WHITE)) color2.setTextColor(palette.darkMutedSwatch? .bodyTextColor ? : Color.WHITE) color3.setBackgroundColor(palette.getLightMutedColor(Color.WHITE)) color3.setTextColor(palette.lightMutedSwatch? .bodyTextColor ? : Color.WHITE) color4.setBackgroundColor(palette.getVibrantColor(Color.WHITE)) color4.setTextColor(palette.vibrantSwatch? .bodyTextColor ? : Color.WHITE) color5.setBackgroundColor(palette.getDarkVibrantColor(Color.WHITE)) color5.setTextColor(palette.darkVibrantSwatch? .bodyTextColor ? : Color.WHITE) color6.setBackgroundColor(palette.getLightVibrantColor(Color.WHITE)) color6.setTextColor(palette.lightVibrantSwatch? .bodyTextColor ? : Color.WHITE)Copy the code

This way the text on each color can be clearly displayed.

So what’s the difference between the title and the body text color, and how do they get there? Let’s have a look at the source code:

/**
 * Returns an appropriate color to use for any 'title' text which is displayed over this
 * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast.
 */
@ColorInt
public int getTitleTextColor(a) {
    ensureTextColorsGenerated();
    return mTitleTextColor;
}

/**
 * Returns an appropriate color to use for any 'body' text which is displayed over this
 * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast.
 */
@ColorInt
public int getBodyTextColor(a) {
    ensureTextColorsGenerated();
    return mBodyTextColor;
}
Copy the code

Can see first to perform ensureTextColorsGenerated (), its source code is as follows:

private void ensureTextColorsGenerated(a) {
    if(! mGeneratedTextColors) {// First check white, as most colors will be dark
        final int lightBodyAlpha = ColorUtils.calculateMinimumAlpha(
                Color.WHITE, mRgb, MIN_CONTRAST_BODY_TEXT);
        final int lightTitleAlpha = ColorUtils.calculateMinimumAlpha(
                Color.WHITE, mRgb, MIN_CONTRAST_TITLE_TEXT);

        if(lightBodyAlpha ! = -1&& lightTitleAlpha ! = -1) {
            // If we found valid light values, use them and return
            mBodyTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha);
            mTitleTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha);
            mGeneratedTextColors = true;
            return;
        }

        final int darkBodyAlpha = ColorUtils.calculateMinimumAlpha(
                Color.BLACK, mRgb, MIN_CONTRAST_BODY_TEXT);
        final int darkTitleAlpha = ColorUtils.calculateMinimumAlpha(
                Color.BLACK, mRgb, MIN_CONTRAST_TITLE_TEXT);

        if(darkBodyAlpha ! = -1&& darkTitleAlpha ! = -1) {
            // If we found valid dark values, use them and return
            mBodyTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha);
            mTitleTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha);
            mGeneratedTextColors = true;
            return;
        }

        // If we reach here then we can not find title and body values which use the same
        // lightness, we need to use mismatched valuesmBodyTextColor = lightBodyAlpha ! = -1? ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha) : ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha); mTitleTextColor = lightTitleAlpha ! = -1
                ? ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha)
                : ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha);
        mGeneratedTextColors = true; }}Copy the code

As you can see from the code, the two text colors are actually either white or black, but the transparency Alpha is different.

There are a key function, namely ColorUtils. CalculateMinimumAlpha () :

public static int calculateMinimumAlpha(@ColorInt int foreground, @ColorInt int background,
        float minContrastRatio) {
    if(Color.alpha(background) ! =255) {
        throw new IllegalArgumentException("background can not be translucent: #"
                + Integer.toHexString(background));
    }

    // First lets check that a fully opaque foreground has sufficient contrast
    int testForeground = setAlphaComponent(foreground, 255);
    double testRatio = calculateContrast(testForeground, background);
    if (testRatio < minContrastRatio) {
        // Fully opaque foreground does not have sufficient contrast, return error
        return -1;
    }

    // Binary search to find a value with the minimum value which provides sufficient contrast
    int numIterations = 0;
    int minAlpha = 0;
    int maxAlpha = 255;

    while (numIterations <= MIN_ALPHA_SEARCH_MAX_ITERATIONS &&
            (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) {
        final int testAlpha = (minAlpha + maxAlpha) / 2;

        testForeground = setAlphaComponent(foreground, testAlpha);
        testRatio = calculateContrast(testForeground, background);

        if (testRatio < minContrastRatio) {
            minAlpha = testAlpha;
        } else {
            maxAlpha = testAlpha;
        }

        numIterations++;
    }

    // Conservatively return the max of the range of possible alphas, which is known to pass.
    return maxAlpha;
}
Copy the code

It calculates the most appropriate Alpha for the foreground based on the background color and foreground color. If it is less than minContrastRatio then -1 is returned indicating that the foreground scenery is inappropriate. The difference between the title and the body text is minContrastRatio.

Back to ensureTextColorsGenerated code you can see, based on the current color first, calculate the white foreground of Alpha, if the two are not to be – 1 Alpha, will return the corresponding color. Otherwise, calculate the Alpha of the black foreground color, and return the corresponding color if none is -1. Otherwise, use white and black for the title and body text, and return the corresponding color.

More functions

From (bitmap) to a Palette.Builder object, which can be used for more tasks, such as:

  • AddFilter: Adds a filter
  • SetRegion: Sets the extraction area on the image
  • MaximumColorCount: The maximum number of colors in the palette

, etc.

conclusion

From the above, we can see that Palette is very powerful, but it is very simple to use, which allows us to easily extract the color in the picture and adapt the appropriate text color. At the same time, note that ColorUtils is public, so when we need automatic color adaptation of text, we can also use several functions of ColorUtils to calculate the dynamic color scheme.