将位图/颜色与颜色列表匹配

时间:2017-12-30 12:11:36

标签: java android colors bitmap palette

我试图找到将Bitmap /颜色与颜色列表匹配的最佳解决方案。 我正在为我的应用使用Google Material Palette,其目的是:

我的应用中有一些用户,每个用户都可以上传图片(否则他会获得一个随机颜色的占位符);当发生这种情况时,我想找到一种最符合图像的颜色并保存"名称"进入数据库。

当应用程序"读取"该用户(例如:在适配器或简单的"平面"页面中)它将采用颜色的名称并在UI中应用不同的级别(例如:颜色为红色,因此用户的名称将是red_500,其消息的" bubble"将为red_100等。

为了实现我构建了这个单例类。

package it.ivolontari.ivolontari.utils;


import android.content.res.Resources;
import android.graphics.Bitmap;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.graphics.Palette;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import it.ivolontari.ivolontari.R;

/**
 * MaterialSwatches is a Singleton class that wraps all the Google Material Colors.
 * They are ordered by Swatches and PaletteColor.
 * Every Swatch is basically composed by the ColorName and PaletteColor's
 * Every PaletteColor contains the ColorName, the ColorLevel, an (int) value of the color,
 * the corresponding resource of the color and the (int) RGB spectre.
 * This class also provide a method for match a color or a bitmap to the Material Palette.
 */
public final class MaterialSwatches {

    private static final String TAG = "MaterialSwatches";

    /**
     * ColorName is an enum representing the basic name of the color.
     */
    public enum ColorName { none, RED, PINK, PURPLE, DEEP_PURPLE, INDIGO, BLUE, LIGHT_BLUE, CYAN, TEAL,
        GREEN, LIGHT_GREEN, LIME, /*YELLOW, - too bright*/ AMBER, ORANGE, DEEP_ORANGE, BROWN, GREY, BLUE_GREY }

    /**
     * ColorLevel is an enum representing all the levels of the Material Palette.
     */
    public enum ColorLevel { P50, P100, P200, P300, P400, P500, P600, P700, P800, P900, A100, A200, A400, A700 }


    /*
    Singleton instance.
     */
    private static MaterialSwatches instance;

    /**
     * Save and instance of {@link #MaterialSwatches(Resources)} into a strong reference.
     * @param resources the resources needed for get colors from color.xml resource file.
     */
    public static void init(Resources resources) {
        instance = new MaterialSwatches(resources);
    }

    /**
     * @return the singleton {@link #instance};
     */
    public static MaterialSwatches getInstance() {
        return instance;
    }


    /**
     * A BiMap containing all the Swatches paired with their ColorName.
     * We are using a BiMap so we can get value from key, but also key from value.
     */
    private BiMap<ColorName, Swatch> swatches = HashBiMap.create(20);

    /**
     * Default constructor.
     * @param resources the resources needed for get colors from color.xml resource file.
     */
    private MaterialSwatches(Resources resources) {
        swatches.put(ColorName.RED,         getRedSwatch(resources));
        swatches.put(ColorName.PINK,        getPinkSwatch(resources));
        swatches.put(ColorName.PURPLE,      getPurpleSwatch(resources));
        swatches.put(ColorName.DEEP_PURPLE, getDeepPurpleSwatch(resources));
        swatches.put(ColorName.INDIGO,      getIndigoSwatch(resources));
        swatches.put(ColorName.BLUE,        getBlueSwatch(resources));
        swatches.put(ColorName.LIGHT_BLUE,  getLightBlueSwatch(resources));
        swatches.put(ColorName.CYAN,        getCyanSwatch(resources));
        swatches.put(ColorName.TEAL,        getTealSwatch(resources));
        swatches.put(ColorName.GREEN,       getGreenSwatch(resources));
        swatches.put(ColorName.LIGHT_GREEN, getLightGreenSwatch(resources));
        swatches.put(ColorName.LIME,        getLimeSwatch(resources));
        //swatches.put(ColorName.YELLOW,      getYellowSwatch(resources)); - too bright.
        swatches.put(ColorName.AMBER,       getAmberSwatch(resources));
        swatches.put(ColorName.ORANGE,      getOrangeSwatch(resources));
        swatches.put(ColorName.DEEP_ORANGE, getDeepOrangeSwatch(resources));
        swatches.put(ColorName.BROWN,       getBrownSwatch(resources));
        swatches.put(ColorName.GREY,        getGreySwatch(resources));
        swatches.put(ColorName.BLUE_GREY,   getBlueGreySwatch(resources));
    }

    /**
     * Match color.
     * @param color the queried color.
     * @return the closest ColorLevel P500 color to the queried color.
     */
    @Nullable
    public PaletteColor matchColor(final int color) {
        PaletteColor closestColor = null;
        int closestDistance = Integer.MAX_VALUE;

        List<PaletteColor> paletteColorList = getAllColorsForLevel(ColorLevel.P500);
        for (final PaletteColor paletteColor : paletteColorList) {
            final int distance = paletteColor.distanceTo(color);
            if (distance < closestDistance) {
                closestDistance = distance;
                closestColor = paletteColor;
            }
        }
        return closestColor;
    }

    /**
     * Match bitmap.
     * @param bitmap the queried bitmap.
     * Uses Palette library for try to find the vibrant color of the bitmap, if none find,
     * tries to find the dominant color.
     * @return {@link #matchColor(int)};
     */
    @Nullable
    public PaletteColor matchBitmap(final Bitmap bitmap) {
        //Bitmap scaledBMap = Bitmap.createScaledBitmap(bitmap, 1, 1, true);
        //int color = scaledBMap.getPixel(0,0);

        Palette palette = Palette.from(bitmap).generate();
        int color = palette.getVibrantColor(-1);
        if (color == -1) color = palette.getDominantColor(-1);

        return matchColor(color);
    }


    /**
     * Get all PaletteColor's.
     * @return a List of all the PaletteColor's for all the Swatch'es.
     */
    @NonNull
    public List<PaletteColor> getAllColors() {
        List<PaletteColor> colors = new ArrayList<>(20 * 14);
        for (Swatch swatch : swatches.values()) {
            colors.addAll(swatch.getAllPaletteColors());
        }
        return colors;
    }

    /**
     * Get all PaletteColor's for a level.
     * @param colorLevel the queried ColorLevel.
     * @return a List of all the PaletteColor's matching the queried colorLevel.
     */
    @NonNull
    public List<PaletteColor> getAllColorsForLevel(ColorLevel colorLevel) {
        List<PaletteColor> colors = new ArrayList<>(20);
        for (Swatch swatch : swatches.values()) {
            colors.add(swatch.getPaletteColorByLevel(colorLevel));
        }
        return colors;
    }

    /**
     * Get a random Swatch.
     * Creates an ArrayList containing all the Swatches (values of {@link #swatches}).
     * @return a random Swatch.
     */
    @NonNull
    public Swatch getRandomSwatch() {
        List<Swatch> swatchList = new ArrayList<>(swatches.values());
        return swatchList.get(new Random().nextInt(swatchList.size()));
    }

    /**
     * Get Swatch.
     * @param colorName the queried ColorName.
     * @return the Swatch matching the queried ColorName.
     */
    @NonNull
    public Swatch getSwatch(ColorName colorName) {
        return swatches.get(colorName);
    }

    /**
     * Get Swatch.
     * @param colorName the queried String name of the ColorName.
     * Matches the queried String value with the corresponding ColorName.
     * @return {@link #getSwatch(ColorName)};
     */
    @NonNull
    public Swatch getSwatch(String colorName) {
        for (ColorName name : swatches.keySet()) {
            if (name != ColorName.none && name.toString().equalsIgnoreCase(colorName)) {
                return swatches.get(name);
            }
        }
        return getSwatch(ColorName.GREY);
    }


    /**
     * Get a random PaletteColor with a ColorLevel.
     * @param colorLevel the queried ColorLevel.
     * {@link #getRandomSwatch()}
     * @return the PaletteColor for the queried ColorLevel.
     */
    @NonNull
    public PaletteColor getRandomPaletteColor(ColorLevel colorLevel) {
        return getRandomSwatch().getPaletteColorByLevel(colorLevel);
    }

    /**
     * Get PaletteColor.
     * @param colorName the queried ColorName.
     * @param colorLevel the queried ColorLevel.
     * {@link #getSwatch(ColorName)}
     * @return a PaletteColor matching the queried ColorName and ColorLevel.
     */
    @NonNull
    public PaletteColor getPaletteColor(ColorName colorName, ColorLevel colorLevel) {
        return getSwatch(colorName).getPaletteColorByLevel(colorLevel);
    }

    /**
     * Get PaletteColor.
     * @param colorName the queried String name of the ColorName.
     * @param colorLevel the queried ColorLevel.
     * {@link #getSwatch(String)}
     * @return a PaletteColor matching the queried ColorName and ColorLevel.
     */
    @NonNull
    public PaletteColor getPaletteColor(String colorName, ColorLevel colorLevel) {
        return getSwatch(colorName).getPaletteColorByLevel(colorLevel);
    }


    /**
     * Get a random color.
     * @param colorLevel the queried ColorLevel.
     * {@link #getRandomPaletteColor(ColorLevel)};
     * @return the (int) color of the random PaletteColor.
     */
    public int getRandomColor(ColorLevel colorLevel) {
        return getRandomPaletteColor(colorLevel).getColor();
    }

    /**
     * Get color.
     * @param colorName the queried ColorName.
     * @param colorLevel the queried ColorLevel.
     * {@link #getPaletteColor(ColorName, ColorLevel)}.
     * @return the (int) color of the matched PaletteColor.
     */
    public int getColor(ColorName colorName, ColorLevel colorLevel) {
        return getPaletteColor(colorName, colorLevel).getColor();
    }

    /**
     * Get color.
     * @param colorName the queried String name of the ColorName.
     * @param colorLevel the queried ColorLevel.
     * {@link #getPaletteColor(ColorName, ColorLevel)}.
     * @return the (int) color of the matched PaletteColor.
     */
    public int getColor(String colorName, ColorLevel colorLevel) {
        return getPaletteColor(colorName, colorLevel).getColor();
    }


    /**
     * Get a random color resource.
     * @param colorLevel the queried ColorLevel.
     * {@link #getRandomPaletteColor(ColorLevel)};
     * @return the (int) resource color of the random PaletteColor.
     */
    @ColorRes
    public int getRandomColorRes(ColorLevel colorLevel) {
        return getRandomPaletteColor(colorLevel).getColorRes();
    }

    /**
     * Get color resource.
     * @param colorName the queried ColorName.
     * @param colorLevel the queried ColorLevel.
     * {@link #getPaletteColor(ColorName, ColorLevel)}.
     * @return the (int) resource color of the matched PaletteColor.
     */
    @ColorRes
    public int getColorRes(ColorName colorName, ColorLevel colorLevel) {
        return getPaletteColor(colorName, colorLevel).getColorRes();
    }

    /**
     * Get color resource.
     * @param colorName the queried String name of the ColorName.
     * @param colorLevel the queried ColorLevel.
     * {@link #getPaletteColor(String, ColorLevel)}.
     * @return the (int) resource color of the matched PaletteColor.
     */
    @ColorRes
    public int getColorRes(String colorName, ColorLevel colorLevel) {
        return getPaletteColor(colorName, colorLevel).getColorRes();
    }


    /**
     * Swatch class.
     */
    public static final class Swatch {

        /**
         * colorName represent the ColorName for this Swatch.
         */
        private final ColorName colorName;

        /**
         * A BiMap containing all the PaletteColor's paired with their ColorLevel.
         * We are using a BiMap so we can get value from key, but also key from value.
         */
        private final BiMap<ColorLevel, PaletteColor> colorMap;

        /**
         * Default constructor.
         * @param colorName the ColorName for this Swatch.
         * @param colors all the PaletteColor's matching the colorName.
         */
        private Swatch(final ColorName colorName, final PaletteColor... colors) {
            this.colorName = colorName;
            colorMap = HashBiMap.create();
            for (PaletteColor color : colors) {
                colorMap.put(color.colorLevel, color);
            }
        }

        /**
         * Get color name.
         * @return the {{@link #colorName}} of this Swatch.
         */
        @NonNull
        public ColorName getColorName() {
            return colorName;
        }

        /**
         * Get all PaletteColor's
         * @return a List containing all the values of {{@link #colorMap}}.
         */
        @NonNull
        public List<PaletteColor> getAllPaletteColors() {
            return new ArrayList<>(colorMap.values());
        }

        /**
         * Get PaletteColor by ColorLevel.
         * @param colorLevel the queried ColorLevel.
         * @return the PaletteColor paired with the queried ColorLevel in {@link #colorMap}.
         */
        @NonNull
        public PaletteColor getPaletteColorByLevel(ColorLevel colorLevel) {
            return colorMap.get(colorLevel);
        }

        /**
         * Get color by ColorLevel.
         * @param colorLevel the queried ColorLevel.
         * {@link #getPaletteColorByLevel(ColorLevel)}
         * @return the color of the matched ColorPalette.
         */
        public int getColorByLevel(ColorLevel colorLevel) {
            return getPaletteColorByLevel(colorLevel).getColor();
        }

        /**
         * Get color name of a PaletteColor.
         * @param paletteColor the queried PaletteColor.
         * @return the ColorLevel paired with the queried PaletteColor in {{@link #colorMap}}.
         */
        @NonNull
        public ColorLevel getLevelByPaletteColor(PaletteColor paletteColor) {
            return colorMap.inverse().get(paletteColor);
        }
    }


    /**
     * PaletteColor class.
     */
    public static final class PaletteColor {

        /**
         * colorName represent the ColorName for this PaletteColor.
         */
        private final ColorName colorName;

        /**
         * colorLevel represent the ColorLevels for this PaletteColor.
         */
        private final ColorLevel colorLevel;

        /**
         * colorRes represent the ColorRes for this PaletteColor.
         */
        @ColorRes private final int colorRes;

        /**
         * the (int) value of the color and the relative RGB spectre.
         */
        private final int color, r, g, b;

        /**
         * Default constructor.
         * @param resources the resources needed for get colors from color.xml resource file.
         * @param colorName the ColorName for this ColorPalette.
         * @param colorLevel the ColorLevel for this ColorPalette.
         * @param colorRes the resource of the color of this ColorPalette.
         */
        private PaletteColor(Resources resources, final ColorName colorName, final ColorLevel colorLevel, @ColorRes final int colorRes) {
            this.colorRes = colorRes;
            this.colorName = colorName;
            this.colorLevel = colorLevel;

            this.color = colorRes == -1 ? -1 : resources.getColor(colorRes);
            this.r = ((color & 0xff000000) >>> 24);
            this.g = ((color & 0x00ff0000) >>> 16);
            this.b = ((color & 0x0000ff00) >>> 8);
        }

        /**
         * Distance to color.
         * @param color the queried color to match.
         * @return the (int) value of the distance from the queried color and the {{@link #color}} of this
         * ColorPalette using the RGB spectre {{@link #r}} {{@link #g}} {{@link #b}}.
         */
        private int distanceTo(final int color) {
            final int deltaR = this.r - ((color & 0xff000000) >>> 24);
            final int deltaG = this.g - ((color & 0x00ff0000) >>> 16);
            final int deltaB = this.b - ((color & 0x0000ff00) >>> 8);
            return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB);
        }

        /**
         * Get color name.
         * @return the {{@link #colorName}} of this ColorPalette.
         */
        @NonNull
        public ColorName getColorName() {
            return colorName;
        }

        /**
         * Get color level.
         * @return the {{@link #colorLevel}} of this ColorPalette.
         */
        @NonNull
        public ColorLevel getColorLevel() {
            return colorLevel;
        }

        /**
         * Get color resource.
         * @return the {{@link #colorRes}} of this ColorPalette.
         */
        @ColorRes
        public int getColorRes() {
            return colorRes;
        }

        /**
         * Get color.
         * @return the {{@link #color}} of this ColorPalette.
         */
        public int getColor() {
            return color;
        }
    }


    private Swatch getRedSwatch(Resources resources) {
        return new Swatch(ColorName.RED,
                new PaletteColor(resources, ColorName.RED, ColorLevel.P50, R.color.md_red_50),
                new PaletteColor(resources, ColorName.RED, ColorLevel.P100, R.color.md_red_100),
                new PaletteColor(resources, ColorName.RED, ColorLevel.P200, R.color.md_red_200),

        ....
        );
    }

    private Swatch getPinkSwatch(Resources resources) {
        return new Swatch(ColorName.PINK,

        ....
        );
    }

    ....
    ....

}

它的目的很有效,但颜色匹配并不像我想的那样完美;

第一个问题是:实际上我通过此

匹配颜色
/**
 * Match color.
 * @param color the queried color.
 * @return the closest ColorLevel P500 color to the queried color.
 */
@Nullable
public PaletteColor matchColor(final int color) {
    PaletteColor closestColor = null;
    int closestDistance = Integer.MAX_VALUE;

    List<PaletteColor> paletteColorList = getAllColorsForLevel(ColorLevel.P500);
    for (final PaletteColor paletteColor : paletteColorList) {
        final int distance = paletteColor.distanceTo(color);
        if (distance < closestDistance) {
            closestDistance = distance;
            closestColor = paletteColor;
        }
    }
    return closestColor;
}

所以我只匹配P500级别的颜色,我应该匹配所有级别吗?我认为仅仅进行一次改善可能会变得太慢。

第二:实际上我通过这个获得了颜色:

/**
 * Match bitmap.
 * @param bitmap the queried bitmap.
 * Uses Palette library for try to find the vibrant color of the bitmap, if none find,
 * tries to find the dominant color.
 * @return {@link #matchColor(int)};
 */
@Nullable
public PaletteColor matchBitmap(final Bitmap bitmap) {
    //Bitmap scaledBMap = Bitmap.createScaledBitmap(bitmap, 1, 1, true);
    //int color = scaledBMap.getPixel(0,0);

    // Thas one was't that bad...Simple and efficent (more than Palette.getDominantColor()),
    // but all the vibrant was almost completely cutted out,
    // since he merges every pixel in a single color without any "check".

    Palette palette = Palette.from(bitmap).generate();
    int color = palette.getVibrantColor(-1);
    if (color == -1) color = palette.getDominantColor(-1);

    return matchColor(color);
}

我能找到更好的解决方案吗?

第三个也是最后一个:我匹配RGB幽灵:

/**
 * Distance to color.
 * @param color the queried color to match.
 * @return the (int) value of the distance from the queried color and the {{@link #color}} of this
 * ColorPalette using the RGB spectre {{@link #r}} {{@link #g}} {{@link #b}}.
 */
private int distanceTo(final int color) {
    final int deltaR = this.r - ((color & 0xff000000) >>> 24);
    final int deltaG = this.g - ((color & 0x00ff0000) >>> 16);
    final int deltaB = this.b - ((color & 0x0000ff00) >>> 8);
    return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB);
}

另一个更好的解决方案?

0 个答案:

没有答案