原因:
其他的工具类求图片主色 一会儿边缘梯度啊,一会儿HSL,一会儿HSV啊,脑袋大.
就自己搞了个工具类.
原理:
你需要了解颜色立方体
原理如下:
1.将RGB颜色立方体 切割为125 个小正方体.
2.将图片的颜色数据 扔到 125个小正方体里面
3.最后统计出哪些小正方体里面最多,然后排序取前8个正方体的中心点,就得出了图片的主色
以上就是原理,但是里面还是包括了一些细节,比如说:
- 图片数据太大,肯定要吃很多内存,这里先做了图片缩略处理.
- 分割立方体方式,肯定不完全准确,但是大部分情况下,不仔细对比,一般肉眼看不出.
- 如果一个图片是个纯色,但该颜色处于小正方体的边缘,那么最终求出来的却是小正方体的中心点,那么是可以看出区别的.
- 由于白色正方体 的中心点是灰白色的, 和白色 肉眼查看是有明显区别的,所以在代码里面,额外添加了一个白色正方体,一共125 + 1 = 126个白色正方体供接受图片颜色数据
依赖
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
工具类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 获取图片主色的工具类
*/
@Slf4j
public class ImgColorUtil {
//将色系分成5格
public static final int SPLIT_COUNT = 5;
//取结果的前8个
public static final int RES_MAX_COUNT = 8;
//图片压缩比例
public static final float IMG_SCALE = 0.2f;
public static RgbInfoBuilder rgbBuilder = RgbInfoBuilder.buildSplit(SPLIT_COUNT);
public static void main(String[] args) throws IOException {
List<String> colors = getColors(new File("D:\\aaa\\aaa.jpg"));
colors.forEach(System.out::println);
RgbInfo containsInfo = rgbBuilder.getContainsInfo("#FFFFF1");
System.out.println(containsInfo);
}
/**
* 通过图片file获取主色
* @param file
* @return
* @throws IOException
*/
public static List<String> getColors(File file) throws IOException {
return getColors(new FileInputStream(file));
}
/**
* 通过文件流获取主色
* @param inputStream
* @return
*/
public static List<String> getColors(InputStream inputStream){
List<Rgb> rgbs = null;
try {
rgbs = getRgbs(inputStream);
List<RgbInfoCount> resolving = rgbBuilder.resolving(rgbs);
return resolving.subList(0, RES_MAX_COUNT).stream().map(s -> s.getInfo().getCenter()).collect(Collectors.toList());
} catch (Exception e) {
return null;
}
}
/**
* 将图片字节 转换成rgb对象数组
* @param inputStream
* @return
* @throws IOException
*/
private static List<Rgb> getRgbs(InputStream inputStream) throws IOException {
//将图片压缩,降低图片质量,减少图片字节数量,否则可能会溢出。
BufferedImage bufferedImage = Thumbnails.of(inputStream)
.scale(IMG_SCALE).asBufferedImage();
//获取压缩后的图片的所有rgb字符串比如“FF00FF”放到一个list:rgbStrs里面
int height = bufferedImage.getHeight();
int width = bufferedImage.getWidth();
List<String> rgbStrs = new ArrayList<>();
for (int i =0 ; i < width ;i ++){
for (int j =0;j<height;j++){
int rgb = bufferedImage.getRGB(i, j);
try{
//为什么substring(2)
//因为字节是00FF00FF,首个00 是透明度,剩下3个才是rgb
rgbStrs.add(Integer.toHexString(rgb).substring(2));
}catch (Exception e){
log.error("加入rgb数据错误:{},{},{}",i,j,Integer.toHexString(rgb));
}
}
}
// 封装成rgb的对象组装list并返回
List<Rgb> rgbs = new ArrayList<>();
for(String rgbStr:rgbStrs){
rgbs.add(getRgb10(rgbStr));
}
return rgbs;
}
/**
* 字符串16进制rgb 转换成10 进制的对象
* @param rgb16 FF00FF
* @return
*/
public static Rgb getRgb10(String rgb16){
rgb16 = rgb16.trim().replace("#","");
String rstring = rgb16.substring(0, 2);
String gstring = rgb16.substring(2, 4);
String bstring = rgb16.substring(4);
return new Rgb(Integer.parseInt(rstring,16),Integer.parseInt(gstring,16)
,Integer.parseInt(bstring,16));
}
/**
* RGB 对象
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
static class Rgb{
int r;
int g;
int b;
}
@Data
@AllArgsConstructor
static class RgbInfoCount{
RgbInfo info;
int count;
}
/**
* 颜色正方体 类
*/
@Data
@EqualsAndHashCode
public static class RgbInfo{
//额外的白色正方体
public static RgbInfo WHITE = new RgbInfo(250,250,250,10);
//正方体 前边界
int preRBorder;
int preGBorder;
int preBBorder;
// 正方体 中心点
int centerR;
int centerG;
int centerB;
// 正方体后边界
int afterRBorder;
int afterGBorder;
int afterBBorder;
// 正方体中心点的 16进制字符串
String center;
//根据前边界 和 正方体边长,获取一个正方体
public RgbInfo(int preRBorder, int preGBorder, int preBBorder,int len) {
this.preRBorder = preRBorder;
this.preGBorder = preGBorder;
this.preBBorder = preBBorder;
this.afterRBorder = preRBorder + len;
this.afterGBorder = preGBorder + len;
this.afterBBorder = preBBorder + len;
this.centerR = preRBorder + len/2;
this.centerG = preGBorder + len/2;
this.centerB = preBBorder + len/2;
this.center ="#"+ Integer.toHexString(centerR) + Integer.toHexString(centerG)+Integer.toHexString(centerB);
}
}
/**
* 正方体处理类
*/
@Data
public static class RgbInfoBuilder{
// 颜色rgb表示的最大值,两个16进制 ,比如 FF = 255,包括0
private static final int MAXSIZE = 255;
// 切割后的颜色正方体
private List<RgbInfo> infos;
/**
* 切割正方体 初始化
* @param size 如果size 是5 ,那么 会切割成5*5*5=125块
* @return
*/
public static RgbInfoBuilder buildSplit(int size){
if (MAXSIZE% size != 0){
throw new RuntimeException("size必须能整除于"+MAXSIZE);
}
List<RgbInfo> infos = new ArrayList<>();
int len = MAXSIZE/size;
for (int r = 0;r < size ;r++){
for (int g = 0;g < size ;g++){
for (int b = 0;b < size ;b++){
RgbInfo rgbInfo = new RgbInfo(r * len, g * len, b * len, len);
infos.add(rgbInfo);
}
}
}
RgbInfoBuilder rgbInfoBuilder = new RgbInfoBuilder();
rgbInfoBuilder.setInfos(infos);
return rgbInfoBuilder;
}
/**
* 根据 传递来的rgb信息,将rgb 信息放入小颜色正方体 中,然后计算 命中的 小颜色正方体及个数
* @param rgbs
* @return
*/
public List<RgbInfoCount> resolving(List<Rgb> rgbs){
Map<RgbInfo,Integer> infoCounts = new HashMap<>();
for (Rgb rgb:rgbs){
RgbInfo info = getContainsInfo(rgb.getR(), rgb.getG(), rgb.getB());
if (infoCounts.containsKey(info)){
infoCounts.put(info,infoCounts.get(info)+1);
}else {
infoCounts.put(info,1);
}
}
List<RgbInfoCount> list = new ArrayList<>();
infoCounts.forEach((k,v) ->{
list.add(new RgbInfoCount(k,v));
});
list.sort((o1,o2) ->{
return o2.getCount()-o1.getCount();
});
return list;
}
/**
* 根据rgb信息,得出命中 哪个小正方体,注意,额外添加一个白色正方体.
* @param r
* @param g
* @param b
* @return
*/
public RgbInfo getContainsInfo(int r, int g, int b){
boolean white = isContains(r, g, b, RgbInfo.WHITE);
if (white){
return RgbInfo.WHITE;
}
for(RgbInfo info : infos){
boolean contains = isContains(r, g, b, info);
if (contains) return info;
}
return null;
}
/**
* 根据rgb信息字符串,得出命中 哪个小正方体,注意,额外添加一个白色正方体.
* @param rgb
* @return
*/
public RgbInfo getContainsInfo(String rgb){
Rgb rgb10 = getRgb10(rgb);
return getContainsInfo(rgb10.getR(),rgb10.getG(),rgb10.getB());
}
/**
* 如何命中小正方体
* @param r
* @param g
* @param b
* @param info
* @return
*/
private boolean isContains(int r, int g, int b, RgbInfo info) {
if (r >= info.getPreRBorder()
&& r <= info.getAfterRBorder()
&& g >= info.getPreGBorder()
&& g <= info.getAfterGBorder()
&& b >= info.getPreBBorder()
&& b <= info.getAfterBBorder()){
return true;
}
return false;
}
}
}