我试图找到将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);
}
另一个更好的解决方案?