最近项目组要做这样的一件事,通过访问网站拿到网站的favicon来根据favicon匹配它的颜色,色调然后调用我们自己的绘图板去绘制符合当前网站的图标,当时也在github上面找了下,看看有没有现成能借鉴的,但是失败了,但是无意间发现了android的v7下的palette包,貌似可以实现我想要的效果,但是项目里面不可能引入其他的依赖包,因为v7引用到了v4,一想到我的apk又要增加几十k果断的不干了,所以我就把v7的里面的源码拿出来改了改,然后v4里面的需要的也单独的剥离出来,这样即达到了我需要的效果,也没有给我的apk增加太多的大小。
我们这里先看看我要实现的效果,我们要提取这两个icon里面的主色调,然后看看我们提取的效果,
fuck,你在逗我?怎么会有红色呢?不急不急,听我慢慢道来。
首先我们要做的就是移植v7这个包下面的pattern包,看看我的工程目录,需要移植的就是这几个类。
我这里也不墨迹了,直接上代码吧,google的注释都是很到位的,也很nice我也不说了,我做的工作就是把这几个类整合提取出来,然后屏蔽一些兼容的问题。这里我先给出这几个类的代码。
package com.bobo.picdis.utils;
/*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.graphics.Bitmap;
import android.graphics.Color;
import android.util.SparseIntArray;
import com.bobo.picdis.utils.Palette.Swatch;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
/**
* An color quantizer based on the Median-cut algorithm, but optimized for picking out distinct
* colors rather than representation colors.
*
* The color space is represented as a 3-dimensional cube with each dimension being an RGB
* component. The cube is then repeatedly divided until we have reduced the color space to the
* requested number of colors. An average color is then generated from each cube.
*
* What makes this different to median-cut is that median-cut divided cubes so that all of the cubes
* have roughly the same population, where this quantizer divides boxes based on their color volume.
* This means that the color space is divided into distinct colors, rather than representative
* colors.
*/
final class ColorCutQuantizer {
private static final String LOG_TAG = ColorCutQuantizer.class.getSimpleName();
private final float[] mTempHsl = new float[3];
private static final float BLACK_MAX_LIGHTNESS = 0.05f;
private static final float WHITE_MIN_LIGHTNESS = 0.95f;
private static final int COMPONENT_RED = -3;
private static final int COMPONENT_GREEN = -2;
private static final int COMPONENT_BLUE = -1;
private final int[] mColors;
private final SparseIntArray mColorPopulations;
private final List mQuantizedColors;
/**
* Factory-method to generate a {@link ColorCutQuantizer} from a {@link Bitmap} object.
*
* @param bitmap Bitmap to extract the pixel data from
* @param maxColors The maximum number of colors that should be in the result palette.
*/
static ColorCutQuantizer fromBitmap(Bitmap bitmap, int maxColors) {
final int width = bitmap.getWidth();
final int height = bitmap.getHeight();
final int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
return new ColorCutQuantizer(new ColorHistogram(pixels), maxColors);
}
/**
* Private constructor.
*
* @param colorHistogram histogram representing an image's pixel data
* @param maxColors The maximum number of colors that should be in the result palette.
*/
private ColorCutQuantizer(ColorHistogram colorHistogram, int maxColors) {
final int rawColorCount = colorHistogram.getNumberOfColors();
final int[] rawColors = colorHistogram.getColors();
final int[] rawColorCounts = colorHistogram.getColorCounts();
// First, lets pack the populations into a SparseIntArray so that they can be easily
// retrieved without knowing a color's index
mColorPopulations = new SparseIntArray(rawColorCount);
for (int i = 0; i < rawColors.length; i++) {
mColorPopulations.append(rawColors[i], rawColorCounts[i]);
}
// Now go through all of the colors and keep those which we do not want to ignore
mColors = new int[rawColorCount];
int validColorCount = 0;
for (int color : rawColors) {
if (!shouldIgnoreColor(color)) {
mColors[validColorCount++] = color;
}
}
if (validColorCount <= maxColors) {
// The image has fewer colors than the maximum requested, so just return the colors
mQuantizedColors = new ArrayList();
for (final int color : mColors) {
mQuantizedColors.add(new Swatch(color, mColorPopulations.get(color)));
}
} else {
// We need use quantization to reduce the number of colors
mQuantizedColors = quantizePixels(validColorCount - 1, maxColors);
}
}
/**
* @return the list of quantized colors
*/
List getQuantizedColors() {
return mQuantizedColors;
}
private List quantizePixels(int maxColorIndex, int maxColors) {
// Create the priority queue which is sorted by volume descending. This means we always
// split the largest box in the queue
final PriorityQueue pq = new PriorityQueue(maxColors, VBOX_COMPARATOR_VOLUME);
// To start, offer a box which contains all of the colors
pq.offer(new Vbox(0, maxColorIndex));
// Now go through the boxes, splitting them until we have reached maxColors or there are no
// more boxes to split
splitBoxes(pq, maxColors);
// Finally, return the average colors of the color boxes
return generateAverageColors(pq);
}
/**
* Iterate through the {@link java.util.Queue}, popping
* {@link ColorCutQuantizer.Vbox} objects from the queue
* and splitting them. Once split, the new box and the remaining box are offered back to the
* queue.
*
* @param queue {@link java.util.PriorityQueue} to poll for boxes
* @param maxSize Maximum amount of boxes to split
*/
private void splitBoxes(final PriorityQueue queue, final int maxSize) {
while (queue.size() < maxSize) {
final Vbox vbox = queue.poll();
if (vbox != null && vbox.canSplit()) {
// First split the box, and offer the result
queue.offer(vbox.splitBox());
// Then offer the box back
queue.offer(vbox);
} else {
// If we get here then there are no more boxes to split, so return
return;
}
}
}
private List generateAverageColors(Collection vboxes) {
ArrayList colors = new ArrayList(vboxes.size());
for (Vbox vbox : vboxes) {
Swatch color = vbox.getAverageColor();
if (!shouldIgnoreColor(color)) {
// As we're averaging a color box, we can still get colors which we do not want, so
// we check again here
colors.add(color);
}
}
return colors;
}
/**
* Represents a tightly fitting box around a color space.
*/
private class Vbox {
// lower and upper index are inclusive
private int mLowerIndex;
private int mUpperIndex;
private int mMinRed, mMaxRed;
private int mMinGreen, mMaxGreen;
private int mMinBlue, mMaxBlue;
Vbox(int lowerIndex, int upperIndex) {
mLowerIndex = lowerIndex;
mUpperIndex = upperIndex;
fitBox();
}
int getVolume() {
return (mMaxRed - mMinRed + 1) * (mMaxGreen - mMinGreen + 1) *
(mMaxBlue - mMinBlue + 1);
}
boolean canSplit() {
return getColorCount() > 1;
}
int getColorCount() {
return mUpperIndex - mLowerIndex + 1;
}
/**
* Recomputes the boundaries of this box to tightly fit the colors within the box.
*/
void fitBox() {
// Reset the min and max to opposite values
mMinRed = mMinGreen = mMinBlue = 0xFF;
mMaxRed = mMaxGreen = mMaxBlue = 0x0;
for (int i = mLowerIndex; i <= mUpperIndex; i++) {
final int color = mColors[i];
final int r = Color.red(color);
final int g = Color.green(color);
final int b = Color.blue(color);
if (r > mMaxRed) {
mMaxRed = r;
}
if (r < mMinRed) {
mMinRed = r;
}
if (g > mMaxGreen) {
mMaxGreen = g;
}
if (g < mMinGreen) {
mMinGreen = g;
}
if (b > mMaxBlue) {
mMaxBlue = b;
}
if (b < mMinBlue) {
mMinBlue = b;
}
}
}
/**
* Split this color box at the mid-point along it's longest dimension
*
* @return the new ColorBox
*/
Vbox splitBox() {
if (!canSplit()) {
throw new IllegalStateException("Can not split a box with only 1 color");
}
// find median along the longest dimension
final int splitPoint = findSplitPoint();
Vbox newBox = new Vbox(splitPoint + 1, mUpperIndex);
// Now change this box's upperIndex and recompute the color boundaries
mUpperIndex = splitPoint;
fitBox();
return newBox;
}
/**
* @return the dimension which this box is largest in
*/
int getLongestColorDimension() {
final int redLength = mMaxRed - mMinRed;
final int greenLength = mMaxGreen - mMinGreen;
final int blueLength = mMaxBlue - mMinBlue;
if (redLength >= greenLength && redLength >= blueLength) {
return COMPONENT_RED;
} else if (greenLength >= redLength && greenLength >= blueLength) {
return COMPONENT_GREEN;
} else {
return COMPONENT_BLUE;
}
}
/**
* Finds the point within this box's lowerIndex and upperIndex index of where to split.
*
* This is calculated by finding the longest color dimension, and then sorting the
* sub-array based on that dimension value in each color. The colors are then iterated over
* until a color is found with at least the midpoint of the whole box's dimension midpoint.
*
* @return the index of the colors array to split from
*/
int findSplitPoint() {
final int longestDimension = getLongestColorDimension();
// We need to sort the colors in this box based on the longest color dimension.
// As we can't use a Comparator to define the sort logic, we modify each color so that
// it's most significant is the desired dimension
modifySignificantOctet(longestDimension, mLowerIndex, mUpperIndex);
// Now sort... Arrays.sort uses a exclusive toIndex so we need to add 1
Arrays.sort(mColors, mLowerIndex, mUpperIndex + 1);
// Now revert all of the colors so that they are packed as RGB again
modifySignificantOctet(longestDimension, mLowerIndex, mUpperIndex);
final int dimensionMidPoint = midPoint(longestDimension);
for (int i = mLowerIndex; i <= mUpperIndex; i++) {
final int color = mColors[i];
switch (longestDimension) {
case COMPONENT_RED:
if (Color.red(color) >= dimensionMidPoint) {
return i;
}
break;
case COMPONENT_GREEN:
if (Color.green(color) >= dimensionMidPoint) {
return i;
}
break;
case COMPONENT_BLUE:
if (Color.blue(color) > dimensionMidPoint) {
return i;
}
break;
}
}
return mLowerIndex;
}
/**
* @return the average color of this box.
*/
Swatch getAverageColor() {
int redSum = 0;
int greenSum = 0;
int blueSum = 0;
int totalPopulation = 0;
for (int i = mLowerIndex; i <= mUpperIndex; i++) {
final int color = mColors[i];
final int colorPopulation = mColorPopulations.get(color);
totalPopulation += colorPopulation;
redSum += colorPopulation * Color.red(color);
greenSum += colorPopulation * Color.green(color);
blueSum += colorPopulation * Color.blue(color);
}
final int redAverage = Math.round(redSum / (float) totalPopulation);
final int greenAverage = Math.round(greenSum / (float) totalPopulation);
final int blueAverage = Math.round(blueSum / (float) totalPopulation);
return new Swatch(redAverage, greenAverage, blueAverage, totalPopulation);
}
/**
* @return the midpoint of this box in the given {@code dimension}
*/
int midPoint(int dimension) {
switch (dimension) {
case COMPONENT_RED:
default:
return (mMinRed + mMaxRed) / 2;
case COMPONENT_GREEN:
return (mMinGreen + mMaxGreen) / 2;
case COMPONENT_BLUE:
return (mMinBlue + mMaxBlue) / 2;
}
}
}
/**
* Modify the significant octet in a packed color int. Allows sorting based on the value of a
* single color component.
*
* @see Vbox#findSplitPoint()
*/
private void modifySignificantOctet(final int dimension, int lowerIndex, int upperIndex) {
switch (dimension) {
case COMPONENT_RED:
// Already in RGB, no need to do anything
break;
case COMPONENT_GREEN:
// We need to do a RGB to GRB swap, or vice-versa
for (int i = lowerIndex; i <= upperIndex; i++) {
final int color = mColors[i];
mColors[i] = Color.rgb((color >> 8) & 0xFF, (color >> 16) & 0xFF, color & 0xFF);
}
break;
case COMPONENT_BLUE:
// We need to do a RGB to BGR swap, or vice-versa
for (int i = lowerIndex; i <= upperIndex; i++) {
final int color = mColors[i];
mColors[i] = Color.rgb(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF);
}
break;
}
}
private boolean shouldIgnoreColor(int color) {
ColorUtils.RGBtoHSL(Color.red(color), Color.green(color), Color.blue(color), mTempHsl);
return shouldIgnoreColor(mTempHsl);
}
private static boolean shouldIgnoreColor(Swatch color) {
return shouldIgnoreColor(color.getHsl());
}
private static boolean shouldIgnoreColor(float[] hslColor) {
return isWhite(hslColor) || isBlack(hslColor) || isNearRedILine(hslColor);
}
/**
* @return true if the color represents a color which is close to black.
*/
private static boolean isBlack(float[] hslColor) {
return hslColor[2] <= BLACK_MAX_LIGHTNESS;
}
/**
* @return true if the color represents a color which is close to white.
*/
private static boolean isWhite(float[] hslColor) {
return hslColor[2] >= WHITE_MIN_LIGHTNESS;
}
/**
* @return true if the color lies close to the red side of the I line.
*/
private static boolean isNearRedILine(float[] hslColor) {
return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f;
}
/**
* Comparator which sorts {@link Vbox} instances based on their volume, in descending order
*/
private static final Comparator VBOX_COMPARATOR_VOLUME = new Comparator() {
@Override
public int compare(Vbox lhs, Vbox rhs) {
return rhs.getVolume() - lhs.getVolume();
}
};
}
然后是ColorHistogram这个类
package com.bobo.picdis.utils;
/*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.Arrays;
/**
* Class which provides a histogram for RGB values.
*/
final class ColorHistogram {
private final int[] mColors;
private final int[] mColorCounts;
private final int mNumberColors;
/**
* A new {@link ColorHistogram} instance.
*
* @param pixels array of image contents
*/
ColorHistogram(final int[] pixels) {
// Sort the pixels to enable counting below
Arrays.sort(pixels);
// Count number of distinct colors
mNumberColors = countDistinctColors(pixels);
// Create arrays
mColors = new int[mNumberColors];
mColorCounts = new int[mNumberColors];
// Finally count the frequency of each color
countFrequencies(pixels);
}
/**
* @return number of distinct colors in the image.
*/
int getNumberOfColors() {
return mNumberColors;
}
/**
* @return an array containing all of the distinct colors in the image.
*/
int[] getColors() {
return mColors;
}
/**
* @return an array containing the frequency of a distinct colors within the image.
*/
int[] getColorCounts() {
return mColorCounts;
}
private static int countDistinctColors(final int[] pixels) {
if (pixels.length < 2) {
// If we have less than 2 pixels we can stop here
return pixels.length;
}
// If we have at least 2 pixels, we have a minimum of 1 color...
int colorCount = 1;
int currentColor = pixels[0];
// Now iterate from the second pixel to the end, counting distinct colors
for (int i = 1; i < pixels.length; i++) {
// If we encounter a new color, increase the population
if (pixels[i] != currentColor) {
currentColor = pixels[i];
colorCount++;
}
}
return colorCount;
}
private void countFrequencies(final int[] pixels) {
if (pixels.length == 0) {
return;
}
int currentColorIndex = 0;
int currentColor = pixels[0];
mColors[currentColorIndex] = currentColor;
mColorCounts[currentColorIndex] = 1;
if (pixels.length == 1) {
// If we only have one pixel, we can stop here
return;
}
// Now iterate from the second pixel to the end, population distinct colors
for (int i = 1; i < pixels.length; i++) {
if (pixels[i] == currentColor) {
// We've hit the same color as before, increase population
mColorCounts[currentColorIndex]++;
} else {
// We've hit a new color, increase index
currentColor = pixels[i];
currentColorIndex++;
mColors[currentColorIndex] = currentColor;
mColorCounts[currentColorIndex] = 1;
}
}
}
}
过后是ColorUtils这个类,这个类有些事在v4里面的包里面,需要自己去修改和剔除来。
package com.bobo.picdis.utils;
/*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.graphics.Color;
final class ColorUtils {
private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10;
private static final int MIN_ALPHA_SEARCH_PRECISION = 10;
private ColorUtils() {}
/**
* Composite two potentially translucent colors over each other and returns the result.
*/
private static int compositeColors(int fg, int bg) {
final float alpha1 = Color.alpha(fg) / 255f;
final float alpha2 = Color.alpha(bg) / 255f;
float a = (alpha1 + alpha2) * (1f - alpha1);
float r = (Color.red(fg) * alpha1) + (Color.red(bg) * alpha2 * (1f - alpha1));
float g = (Color.green(fg) * alpha1) + (Color.green(bg) * alpha2 * (1f - alpha1));
float b = (Color.blue(fg) * alpha1) + (Color.blue(bg) * alpha2 * (1f - alpha1));
return Color.argb((int) a, (int) r, (int) g, (int) b);
}
/**
* Returns the luminance of a color.
*
* Formula defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
*/
private static double calculateLuminance(int color) {
double red = Color.red(color) / 255d;
red = red < 0.03928 ? red / 12.92 : Math.pow((red + 0.055) / 1.055, 2.4);
double green = Color.green(color) / 255d;
green = green < 0.03928 ? green / 12.92 : Math.pow((green + 0.055) / 1.055, 2.4);
double blue = Color.blue(color) / 255d;
blue = blue < 0.03928 ? blue / 12.92 : Math.pow((blue + 0.055) / 1.055, 2.4);
return (0.2126 * red) + (0.7152 * green) + (0.0722 * blue);
}
/**
* Returns the contrast ratio between two colors.
*
* Formula defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
*/
private static double calculateContrast(int foreground, int background) {
if (Color.alpha(background) != 255) {
throw new IllegalArgumentException("background can not be translucent");
}
if (Color.alpha(foreground) < 255) {
// If the foreground is translucent, composite the foreground over the background
foreground = compositeColors(foreground, background);
}
final double luminance1 = calculateLuminance(foreground) + 0.05;
final double luminance2 = calculateLuminance(background) + 0.05;
// Now return the lighter luminance divided by the darker luminance
return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2);
}
/**
* Finds the minimum alpha value which can be applied to {@code foreground} so that is has a
* contrast value of at least {@code minContrastRatio} when compared to background.
*
* @return the alpha value in the range 0-255.
*/
private static int findMinimumAlpha(int foreground, int background, double minContrastRatio) {
if (Color.alpha(background) != 255) {
throw new IllegalArgumentException("background can not be translucent");
}
// First lets check that a fully opaque foreground has sufficient contrast
int testForeground = modifyAlpha(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 = modifyAlpha(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;
}
static int getTextColorForBackground(int backgroundColor, float minContrastRatio) {
// First we will check white as most colors will be dark
final int whiteMinAlpha = ColorUtils
.findMinimumAlpha(Color.WHITE, backgroundColor, minContrastRatio);
if (whiteMinAlpha >= 0) {
return ColorUtils.modifyAlpha(Color.WHITE, whiteMinAlpha);
}
// If we hit here then there is not an translucent white which provides enough contrast,
// so check black
final int blackMinAlpha = ColorUtils
.findMinimumAlpha(Color.BLACK, backgroundColor, minContrastRatio);
if (blackMinAlpha >= 0) {
return ColorUtils.modifyAlpha(Color.BLACK, blackMinAlpha);
}
// This should not happen!
return -1;
}
static void RGBtoHSL(int r, int g, int b, float[] hsl) {
final float rf = r / 255f;
final float gf = g / 255f;
final float bf = b / 255f;
final float max = Math.max(rf, Math.max(gf, bf));
final float min = Math.min(rf, Math.min(gf, bf));
final float deltaMaxMin = max - min;
float h, s;
float l = (max + min) / 2f;
if (max == min) {
// Monochromatic
h = s = 0f;
} else {
if (max == rf) {
h = ((gf - bf) / deltaMaxMin) % 6f;
} else if (max == gf) {
h = ((bf - rf) / deltaMaxMin) + 2f;
} else {
h = ((rf - gf) / deltaMaxMin) + 4f;
}
s = deltaMaxMin / (1f - Math.abs(2f * l - 1f));
}
hsl[0] = (h * 60f) % 360f;
hsl[1] = s;
hsl[2] = l;
}
static int HSLtoRGB (float[] hsl) {
final float h = hsl[0];
final float s = hsl[1];
final float l = hsl[2];
final float c = (1f - Math.abs(2 * l - 1f)) * s;
final float m = l - 0.5f * c;
final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f));
final int hueSegment = (int) h / 60;
int r = 0, g = 0, b = 0;
switch (hueSegment) {
case 0:
r = Math.round(255 * (c + m));
g = Math.round(255 * (x + m));
b = Math.round(255 * m);
break;
case 1:
r = Math.round(255 * (x + m));
g = Math.round(255 * (c + m));
b = Math.round(255 * m);
break;
case 2:
r = Math.round(255 * m);
g = Math.round(255 * (c + m));
b = Math.round(255 * (x + m));
break;
case 3:
r = Math.round(255 * m);
g = Math.round(255 * (x + m));
b = Math.round(255 * (c + m));
break;
case 4:
r = Math.round(255 * (x + m));
g = Math.round(255 * m);
b = Math.round(255 * (c + m));
break;
case 5:
case 6:
r = Math.round(255 * (c + m));
g = Math.round(255 * m);
b = Math.round(255 * (x + m));
break;
}
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
return Color.rgb(r, g, b);
}
/**
* Set the alpha component of {@code color} to be {@code alpha}.
*/
static int modifyAlpha(int color, int alpha) {
return (color & 0x00ffffff) | (alpha << 24);
}
}
最后就是我们的重要部分了,对外接口的类Palette
package com.bobo.picdis.utils;
/*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.AsyncTask;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* A helper class to extract prominent colors from an image.
*
* A number of colors with different profiles are extracted from the image:
*
* - Vibrant
* - Vibrant Dark
* - Vibrant Light
* - Muted
* - Muted Dark
* - Muted Light
*
* These can be retrieved from the appropriate getter method.
*
*
* Instances can be created with the synchronous factory methods {@link #generate(Bitmap)} and
* {@link #generate(Bitmap, int)}.
*
* These should be called on a background thread, ideally the one in
* which you load your images on. Sometimes that is not possible, so asynchronous factory methods
* have also been provided: {@link #generateAsync(Bitmap, PaletteAsyncListener)} and
* {@link #generateAsync(Bitmap, int, PaletteAsyncListener)}. These can be used as so:
*
*
* Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
* public void onGenerated(Palette palette) {
* // Do something with colors...
* }
* });
*
*/
public final class Palette {
/**
* Listener to be used with {@link #generateAsync(Bitmap, PaletteAsyncListener)} or
* {@link #generateAsync(Bitmap, int, PaletteAsyncListener)}
*/
public interface PaletteAsyncListener {
/**
* Called when the {@link Palette} has been generated.
*/
void onGenerated(Palette palette);
}
private static final int CALCULATE_BITMAP_MIN_DIMENSION = 100;
private static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
private static final float TARGET_DARK_LUMA = 0.26f;
private static final float MAX_DARK_LUMA = 0.45f;
private static final float MIN_LIGHT_LUMA = 0.55f;
private static final float TARGET_LIGHT_LUMA = 0.74f;
private static final float MIN_NORMAL_LUMA = 0.3f;
private static final float TARGET_NORMAL_LUMA = 0.5f;
private static final float MAX_NORMAL_LUMA = 0.7f;
private static final float TARGET_MUTED_SATURATION = 0.3f;
private static final float MAX_MUTED_SATURATION = 0.4f;
private static final float TARGET_VIBRANT_SATURATION = 1f;
private static final float MIN_VIBRANT_SATURATION = 0.35f;
private static final float WEIGHT_SATURATION = 3f;
private static final float WEIGHT_LUMA = 6f;
private static final float WEIGHT_POPULATION = 1f;
private static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
private static final float MIN_CONTRAST_BODY_TEXT = 4.5f;
private final List mSwatches;
private final int mHighestPopulation;
private Swatch mVibrantSwatch;
private Swatch mMutedSwatch;
private Swatch mDarkVibrantSwatch;
private Swatch mDarkMutedSwatch;
private Swatch mLightVibrantSwatch;
private Swatch mLightMutedColor;
/**
* Generate a {@link Palette} from a {@link Bitmap} using the default number of colors.
*/
public static Palette generate(Bitmap bitmap) {
return generate(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS);
}
/**
* Generate a {@link Palette} from a {@link Bitmap} using the specified {@code numColors}.
* Good values for {@code numColors} depend on the source image type.
* For landscapes, a good values are in the range 12-16. For images which are largely made up
* of people's faces then this value should be increased to 24-32.
*
* @param numColors The maximum number of colors in the generated palette. Increasing this
* number will increase the time needed to compute the values.
*/
public static Palette generate(Bitmap bitmap, int numColors) {
checkBitmapParam(bitmap);
checkNumberColorsParam(numColors);
// First we'll scale down the bitmap so it's shortest dimension is 100px
final Bitmap scaledBitmap = scaleBitmapDown(bitmap);
// Now generate a quantizer from the Bitmap
ColorCutQuantizer quantizer = ColorCutQuantizer.fromBitmap(scaledBitmap, numColors);
// If created a new bitmap, recycle it
if (scaledBitmap != bitmap) {
scaledBitmap.recycle();
}
// Now return a ColorExtractor instance
return new Palette(quantizer.getQuantizedColors());
}
/**
* Generate a {@link Palette} asynchronously. {@link PaletteAsyncListener#onGenerated(Palette)}
* will be called with the created instance. The resulting {@link Palette} is the same as
* what would be created by calling {@link #generate(Bitmap)}.
*
* @param listener Listener to be invoked when the {@link Palette} has been generated.
*
* @return the {@link android.os.AsyncTask} used to asynchronously generate the instance.
*/
public static AsyncTask generateAsync(
Bitmap bitmap, PaletteAsyncListener listener) {
return generateAsync(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS, listener);
}
/**
* Generate a {@link Palette} asynchronously. {@link PaletteAsyncListener#onGenerated(Palette)}
* will be called with the created instance. The resulting {@link Palette} is the same as what
* would be created by calling {@link #generate(Bitmap, int)}.
*
* @param listener Listener to be invoked when the {@link Palette} has been generated.
*
* @return the {@link android.os.AsyncTask} used to asynchronously generate the instance.
*/
public static AsyncTask generateAsync(
final Bitmap bitmap, final int numColors, final PaletteAsyncListener listener) {
checkBitmapParam(bitmap);
checkNumberColorsParam(numColors);
checkAsyncListenerParam(listener);
AsyncTask task = new AsyncTask() {
@Override
protected Palette doInBackground(Bitmap... params) {
return generate(params[0], numColors);
}
@Override
protected void onPostExecute(Palette colorExtractor) {
super.onPostExecute(colorExtractor);
listener.onGenerated(colorExtractor);
}
};
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, bitmap);
// return AsyncTaskCompat.executeParallel(
// new AsyncTask() {
// @Override
// protected Palette doInBackground(Bitmap... params) {
// return generate(params[0], numColors);
// }
//
// @Override
// protected void onPostExecute(Palette colorExtractor) {
// listener.onGenerated(colorExtractor);
// }
// }, bitmap);
return task;
}
private Palette(List swatches) {
mSwatches = swatches;
mHighestPopulation = findMaxPopulation();
mVibrantSwatch = findColor(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
mLightVibrantSwatch = findColor(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
mDarkVibrantSwatch = findColor(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
mMutedSwatch = findColor(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
mLightMutedColor = findColor(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
mDarkMutedSwatch = findColor(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
// Now try and generate any missing colors
generateEmptySwatches();
}
/**
* Returns all of the swatches which make up the palette.
*/
public List getSwatches() {
return Collections.unmodifiableList(mSwatches);
}
/**
* Returns the most vibrant swatch in the palette. Might be null.
*/
public Swatch getVibrantSwatch() {
return mVibrantSwatch;
}
/**
* Returns a light and vibrant swatch from the palette. Might be null.
*/
public Swatch getLightVibrantSwatch() {
return mLightVibrantSwatch;
}
/**
* Returns a dark and vibrant swatch from the palette. Might be null.
*/
public Swatch getDarkVibrantSwatch() {
return mDarkVibrantSwatch;
}
/**
* Returns a muted swatch from the palette. Might be null.
*/
public Swatch getMutedSwatch() {
return mMutedSwatch;
}
/**
* Returns a muted and light swatch from the palette. Might be null.
*/
public Swatch getLightMutedSwatch() {
return mLightMutedColor;
}
/**
* Returns a muted and dark swatch from the palette. Might be null.
*/
public Swatch getDarkMutedSwatch() {
return mDarkMutedSwatch;
}
/**
* Returns the most vibrant color in the palette as an RGB packed int.
*
* @param defaultColor value to return if the swatch isn't available
*/
public int getVibrantColor(int defaultColor) {
return mVibrantSwatch != null ? mVibrantSwatch.getRgb() : defaultColor;
}
/**
* Returns a light and vibrant color from the palette as an RGB packed int.
*
* @param defaultColor value to return if the swatch isn't available
*/
public int getLightVibrantColor(int defaultColor) {
return mLightVibrantSwatch != null ? mLightVibrantSwatch.getRgb() : defaultColor;
}
/**
* Returns a dark and vibrant color from the palette as an RGB packed int.
*
* @param defaultColor value to return if the swatch isn't available
*/
public int getDarkVibrantColor(int defaultColor) {
return mDarkVibrantSwatch != null ? mDarkVibrantSwatch.getRgb() : defaultColor;
}
/**
* Returns a muted color from the palette as an RGB packed int.
*
* @param defaultColor value to return if the swatch isn't available
*/
public int getMutedColor(int defaultColor) {
return mMutedSwatch != null ? mMutedSwatch.getRgb() : defaultColor;
}
/**
* Returns a muted and light color from the palette as an RGB packed int.
*
* @param defaultColor value to return if the swatch isn't available
*/
public int getLightMutedColor(int defaultColor) {
return mLightMutedColor != null ? mLightMutedColor.getRgb() : defaultColor;
}
/**
* Returns a muted and dark color from the palette as an RGB packed int.
*
* @param defaultColor value to return if the swatch isn't available
*/
public int getDarkMutedColor(int defaultColor) {
return mDarkMutedSwatch != null ? mDarkMutedSwatch.getRgb() : defaultColor;
}
/**
* @return true if we have already selected {@code swatch}
*/
private boolean isAlreadySelected(Swatch swatch) {
return mVibrantSwatch == swatch || mDarkVibrantSwatch == swatch ||
mLightVibrantSwatch == swatch || mMutedSwatch == swatch ||
mDarkMutedSwatch == swatch || mLightMutedColor == swatch;
}
private Swatch findColor(float targetLuma, float minLuma, float maxLuma,
float targetSaturation, float minSaturation, float maxSaturation) {
Swatch max = null;
float maxValue = 0f;
for (Swatch swatch : mSwatches) {
final float sat = swatch.getHsl()[1];
final float luma = swatch.getHsl()[2];
if (sat >= minSaturation && sat <= maxSaturation &&
luma >= minLuma && luma <= maxLuma &&
!isAlreadySelected(swatch)) {
float thisValue = createComparisonValue(sat, targetSaturation, luma, targetLuma,
swatch.getPopulation(), mHighestPopulation);
if (max == null || thisValue > maxValue) {
max = swatch;
maxValue = thisValue;
}
}
}
return max;
}
/**
* Try and generate any missing swatches from the swatches we did find.
*/
private void generateEmptySwatches() {
if (mVibrantSwatch == null) {
// If we do not have a vibrant color...
if (mDarkVibrantSwatch != null) {
// ...but we do have a dark vibrant, generate the value by modifying the luma
final float[] newHsl = copyHslValues(mDarkVibrantSwatch);
newHsl[2] = TARGET_NORMAL_LUMA;
mVibrantSwatch = new Swatch(ColorUtils.HSLtoRGB(newHsl), 0);
}
}
if (mDarkVibrantSwatch == null) {
// If we do not have a dark vibrant color...
if (mVibrantSwatch != null) {
// ...but we do have a vibrant, generate the value by modifying the luma
final float[] newHsl = copyHslValues(mVibrantSwatch);
newHsl[2] = TARGET_DARK_LUMA;
mDarkVibrantSwatch = new Swatch(ColorUtils.HSLtoRGB(newHsl), 0);
}
}
}
/**
* Find the {@link Swatch} with the highest population value and return the population.
*/
private int findMaxPopulation() {
int population = 0;
for (Swatch swatch : mSwatches) {
population = Math.max(population, swatch.getPopulation());
}
return population;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Palette palette = (Palette) o;
if (mSwatches != null ? !mSwatches.equals(palette.mSwatches) : palette.mSwatches != null) {
return false;
}
if (mDarkMutedSwatch != null ? !mDarkMutedSwatch.equals(palette.mDarkMutedSwatch)
: palette.mDarkMutedSwatch != null) {
return false;
}
if (mDarkVibrantSwatch != null ? !mDarkVibrantSwatch.equals(palette.mDarkVibrantSwatch)
: palette.mDarkVibrantSwatch != null) {
return false;
}
if (mLightMutedColor != null ? !mLightMutedColor.equals(palette.mLightMutedColor)
: palette.mLightMutedColor != null) {
return false;
}
if (mLightVibrantSwatch != null ? !mLightVibrantSwatch.equals(palette.mLightVibrantSwatch)
: palette.mLightVibrantSwatch != null) {
return false;
}
if (mMutedSwatch != null ? !mMutedSwatch.equals(palette.mMutedSwatch)
: palette.mMutedSwatch != null) {
return false;
}
if (mVibrantSwatch != null ? !mVibrantSwatch.equals(palette.mVibrantSwatch)
: palette.mVibrantSwatch != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = mSwatches != null ? mSwatches.hashCode() : 0;
result = 31 * result + (mVibrantSwatch != null ? mVibrantSwatch.hashCode() : 0);
result = 31 * result + (mMutedSwatch != null ? mMutedSwatch.hashCode() : 0);
result = 31 * result + (mDarkVibrantSwatch != null ? mDarkVibrantSwatch.hashCode() : 0);
result = 31 * result + (mDarkMutedSwatch != null ? mDarkMutedSwatch.hashCode() : 0);
result = 31 * result + (mLightVibrantSwatch != null ? mLightVibrantSwatch.hashCode() : 0);
result = 31 * result + (mLightMutedColor != null ? mLightMutedColor.hashCode() : 0);
return result;
}
/**
* Scale the bitmap down so that it's smallest dimension is
* {@value #CALCULATE_BITMAP_MIN_DIMENSION}px. If {@code bitmap} is smaller than this, than it
* is returned.
*/
private static Bitmap scaleBitmapDown(Bitmap bitmap) {
final int minDimension = Math.min(bitmap.getWidth(), bitmap.getHeight());
if (minDimension <= CALCULATE_BITMAP_MIN_DIMENSION) {
// If the bitmap is small enough already, just return it
return bitmap;
}
final float scaleRatio = CALCULATE_BITMAP_MIN_DIMENSION / (float) minDimension;
return Bitmap.createScaledBitmap(bitmap,
Math.round(bitmap.getWidth() * scaleRatio),
Math.round(bitmap.getHeight() * scaleRatio),
false);
}
private static float createComparisonValue(float saturation, float targetSaturation,
float luma, float targetLuma,
int population, int highestPopulation) {
return weightedMean(
invertDiff(saturation, targetSaturation), WEIGHT_SATURATION,
invertDiff(luma, targetLuma), WEIGHT_LUMA,
population / (float) highestPopulation, WEIGHT_POPULATION
);
}
/**
* Copy a {@link Swatch}'s HSL values into a new float[].
*/
private static float[] copyHslValues(Swatch color) {
final float[] newHsl = new float[3];
System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
return newHsl;
}
/**
* Returns a value in the range 0-1. 1 is returned when {@code value} equals the
* {@code targetValue} and then decreases as the absolute difference between {@code value} and
* {@code targetValue} increases.
*
* @param value the item's value
* @param targetValue the value which we desire
*/
private static float invertDiff(float value, float targetValue) {
return 1f - Math.abs(value - targetValue);
}
private static float weightedMean(float... values) {
float sum = 0f;
float sumWeight = 0f;
for (int i = 0; i < values.length; i += 2) {
float value = values[i];
float weight = values[i + 1];
sum += (value * weight);
sumWeight += weight;
}
return sum / sumWeight;
}
private static void checkBitmapParam(Bitmap bitmap) {
if (bitmap == null) {
throw new IllegalArgumentException("bitmap can not be null");
}
if (bitmap.isRecycled()) {
throw new IllegalArgumentException("bitmap can not be recycled");
}
}
private static void checkNumberColorsParam(int numColors) {
if (numColors < 1) {
throw new IllegalArgumentException("numColors must be 1 of greater");
}
}
private static void checkAsyncListenerParam(PaletteAsyncListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener can not be null");
}
}
/**
* Represents a color swatch generated from an image's palette. The RGB color can be retrieved
* by calling {@link #getRgb()}.
*/
public static final class Swatch {
private final int mRed, mGreen, mBlue;
private final int mRgb;
private final int mPopulation;
private boolean mGeneratedTextColors;
private int mTitleTextColor;
private int mBodyTextColor;
private float[] mHsl;
Swatch(int rgbColor, int population) {
mRed = Color.red(rgbColor);
mGreen = Color.green(rgbColor);
mBlue = Color.blue(rgbColor);
mRgb = rgbColor;
mPopulation = population;
}
Swatch(int red, int green, int blue, int population) {
mRed = red;
mGreen = green;
mBlue = blue;
mRgb = Color.rgb(red, green, blue);
mPopulation = population;
}
/**
* @return this swatch's RGB color value
*/
public int getRgb() {
return mRgb;
}
/**
* Return this swatch's HSL values.
* hsv[0] is Hue [0 .. 360)
* hsv[1] is Saturation [0...1]
* hsv[2] is Lightness [0...1]
*/
public float[] getHsl() {
if (mHsl == null) {
// Lazily generate HSL values from RGB
mHsl = new float[3];
ColorUtils.RGBtoHSL(mRed, mGreen, mBlue, mHsl);
}
return mHsl;
}
/**
* @return the number of pixels represented by this swatch
*/
public int getPopulation() {
return mPopulation;
}
/**
* 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.
*/
public int getTitleTextColor() {
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.
*/
public int getBodyTextColor() {
ensureTextColorsGenerated();
return mBodyTextColor;
}
private void ensureTextColorsGenerated() {
if (!mGeneratedTextColors) {
mTitleTextColor = ColorUtils.getTextColorForBackground(mRgb,
MIN_CONTRAST_TITLE_TEXT);
mBodyTextColor = ColorUtils.getTextColorForBackground(mRgb,
MIN_CONTRAST_BODY_TEXT);
mGeneratedTextColors = true;
}
}
@Override
public String toString() {
return new StringBuilder(getClass().getSimpleName())
.append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']')
.append(" [HSL: ").append(Arrays.toString(getHsl())).append(']')
.append(" [Population: ").append(mPopulation).append(']')
.append(" [Title Text: #").append(Integer.toHexString(mTitleTextColor)).append(']')
.append(" [Body Text: #").append(Integer.toHexString(mBodyTextColor)).append(']')
.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Swatch swatch = (Swatch) o;
return mPopulation == swatch.mPopulation && mRgb == swatch.mRgb;
}
@Override
public int hashCode() {
return 31 * mRgb + mPopulation;
}
}
}
需要注意的我修改的一个比较重要的地方,Palette里面的generateAsync方法,它返回的这一段代码 return AsyncTaskCompat.executeParallel(
// new AsyncTask
// @Override
// protected Palette doInBackground(Bitmap... params) {
// return generate(params[0], numColors);
// }
//
// @Override
// protected void onPostExecute(Palette colorExtractor) {
// listener.onGenerated(colorExtractor);
// }
// }, bitmap);
被我修改成了
AsyncTask task = new AsyncTask() {
@Override
protected Palette doInBackground(Bitmap... params) {
return generate(params[0], numColors);
}
@Override
protected void onPostExecute(Palette colorExtractor) {
super.onPostExecute(colorExtractor);
listener.onGenerated(colorExtractor);
}
};
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, bitmap);
这个貌似不兼容api11以下,11以下可以直接调用task.execute方法,然后传入参数就可以了,我也是看了源码修改的,因为我的需求里面不需要兼容11的api,下面就看看使用的效果吧。上我们的Activity的代码。
ublic class MainActivity extends Activity implements Palette.PaletteAsyncListener {
LinearLayout mRoot;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mRoot = new LinearLayout(this);
mRoot.setOrientation(LinearLayout.VERTICAL);
setContentView(mRoot);
//导入一个图片
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.examlist_naeti);
Palette palette = Palette.generate(bitmap);
//获取色调DarkMuted颜色 没有获取到这个模式下的颜色默认给一个red的红色
// int darkMultedColor = palette.getDarkMutedColor(Color.RED);
// View view = new View(this);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 100);
params.bottomMargin = 5;
// view.setBackgroundColor(darkMultedColor);
// mRoot.addView(view, params);
// 获取色调DarkMuted颜色
// int darkcolorExtractor = palette.getDarkVibrantColor(Color.RED);
// view = new View(this);
// view.setLayoutParams(params);
// view.setBackgroundColor(darkcolorExtractor);
// mRoot.addView(view, params);
//
//获取色调DarkMuted颜色
// int lightMuteColor = palette.getLightMutedColor(Color.RED);
// view = new View(this);
// view.setLayoutParams(params);
// view.setBackgroundColor(lightMuteColor);
// mRoot.addView(view, params);
//获取所有的图片分析结果
List lists = palette.getSwatches();
for (Palette.Swatch swatch : lists) {
int color = swatch.getRgb();
View view = new View(this);
view.setLayoutParams(params);
view.setBackgroundColor(color);
mRoot.addView(view, params);
}
//导入第二张图片异步去分析它
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.examlist_cmep);
//需要注意的是颜色的个数必须要大于1而且lisenler也不能为空。
AsyncTask palette1 = Palette.generateAsync(bitmap1, 3, this);
}
@Override
public void onGenerated(Palette palette) {
int darkMultedColor = palette.getDarkMutedColor(Color.RED);
View view = new View(this);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 100);
view.setBackgroundColor(darkMultedColor);
mRoot.addView(view, params);
int darkcolorExtractor = palette.getDarkVibrantColor(Color.RED);
view = new View(this);
view.setLayoutParams(params);
view.setBackgroundColor(darkcolorExtractor);
mRoot.addView(view, params);
int lightMuteColor = palette.getLightMutedColor(Color.RED);
view = new View(this);
view.setLayoutParams(params);
view.setBackgroundColor(lightMuteColor);
mRoot.addView(view, params);
}
}
用法跟google提供的调色板palette是一样的,其中很多色调的颜色可以根据自己的需要去提取,也可以提取所有的颜色,这样提取到了当前图片的色调颜色,就可以很容易的匹配和选择我们需要的颜色了。源码地址:传送门