版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Kurozaki_Kun/article/details/79724146
先来一张效果图
这些头像都来自微信好友的,放大看这不过是一张众多头像拼成的大图,缩小或远看能够发现这些头像其实拼出了一个有趣的图案。这个实现思路并不复杂(这个思路暂时只针对黑白的图片,如果要支持彩图会更复杂一点,可以了解下 foto-mosaik-edda)
- 首先需要一张“参考图片”和大量小图片(最好都是方形的,头像是个很好的选择)
- 计算每一张方形小图片的平均灰度值,计算公式为 gray = (77*red + 150*green + 29*blue + 128) >> 8,这个公式是根据人对于不同色彩敏感程度而得出的
- 每张图片计算出的平均灰度值都在0~255,再按照灰度值区间分成4组,每个区间的跨度是256/4=64,当然也可以多一点,这是为了更容易匹配对应的像素点。
- 将参考图片压缩成较小的尺寸,上面这张图是压成50x50,计算每个像素点的灰度值,同样划分为4个区间,最后在区间对应分组里随机选择一张图片摆在对应的像素点上。
- 这个随机选择的算法也是有一定讲究的,我们希望的效果是尽量避免相同的图片相邻摆放
代码实现
GraphicsMosaicHandler.java(用于处理主要逻辑)
package com.yotwei.core;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by YotWei on 2018/3/26.
*/
public class GraphicsMosaicHandler {
public int handle(String srcPath, String dstPath) throws Exception {
//read origin image
Util.message("正在读入图片...");
BufferedImage inputImg = ImageIO.read(new File(srcPath));
BufferedImage outputImg = new BufferedImage(
inputImg.getWidth() * GMConfig.MOSAIC_WIDTH,
inputImg.getHeight() * GMConfig.MOSAIC_HEIGHT,
BufferedImage.TYPE_INT_RGB);
//read mosaic images
Util.message("正在读入马赛克...");
Map<String, List<String>> filenames = new HashMap<>();
Map<String, FileNameList> fnameMap = new HashMap<>();
File pDir = new File(GMConfig.MOSAIC_PROC_PATH);
for (String name : pDir.list()) {
String key = name.substring(0, name.indexOf("_"));
if (!filenames.containsKey(key)) {
filenames.put(key, new ArrayList<>());
}
filenames.get(key).add(name);
}
for (Map.Entry<String, List<String>> entry : filenames.entrySet()) {
fnameMap.put(entry.getKey(), new FileNameList(entry.getValue()));
}
int rgb, gray;
String k;
Util.message("正在绘制...");
for (int y = 0; y < inputImg.getHeight(); y++) {
for (int x = 0; x < inputImg.getWidth(); x++) {
rgb = inputImg.getRGB(x, y);
gray = ((77 * ((rgb & 0xff0000) >> 16))
+ (150 * ((rgb & 0x00ff00) >> 8))
+ (29 * (rgb & 0x0000ff))) >> 8;
k = "L" + (gray / GMConfig.GRAY_LEVEL_INTERVAL);
BufferedImage mosaicImg = ImageIO
.read(new File(GMConfig.MOSAIC_PROC_PATH, fnameMap.get(k).next()));
outputImg.getGraphics().drawImage(mosaicImg,
x * GMConfig.MOSAIC_WIDTH,
y * GMConfig.MOSAIC_HEIGHT,
GMConfig.MOSAIC_WIDTH,
GMConfig.MOSAIC_HEIGHT, null);
}
}
Util.message("绘制完成,正在写入文件...");
ImageIO.write(outputImg, "jpg", new File(dstPath, "out_" + System.currentTimeMillis() + ".jpg"));
Util.message("写入成功!");
return 0;
}
public void processOriginResources() throws IOException {
String procPath = GMConfig.MOSAIC_PROC_PATH;
File dir = new File(procPath);
if (!dir.isDirectory() || dir.list().length == 0) {
if (!dir.exists() && !dir.mkdirs()) {
throw new IOException("无法创建文件夹: " + procPath);
}
String originPath = GMConfig.MOSAIC_ORIGIN_PATH;
File originDir = new File(originPath);
if (!originDir.isDirectory()) {
throw new IOException(String.format("文件夹\"%s\"不存在", originPath));
}
//开始读取图片源地
ExecutorService executor = Executors.newFixedThreadPool(10);
for (File file : originDir.listFiles()) {
BufferedImage readImg = ImageIO.read(file);
if (readImg == null) {
continue;
}
executor.execute(new CompressTaskRunnable(readImg));
}
executor.shutdown();
}
}
static class FileNameList {
public FileNameList(List<String> list) {
if (list != null) {
this.list = list;
Collections.shuffle(list);
}
}
public String next() {
if (list == null) {
return null;
}
if (counter == list.size()) {
counter = 0;
Collections.shuffle(list);
}
return list.get(counter++);
}
private List<String> list;
private int counter = 0;
}
static class CompressTaskRunnable implements Runnable {
private BufferedImage src;
CompressTaskRunnable(BufferedImage src) {
this.src = src;
}
@Override
public void run() {
BufferedImage compress = Util.compressImage(src, GMConfig.MOSAIC_WIDTH, GMConfig.MOSAIC_HEIGHT);
int gray = Util.getAvgGray(compress);
try {
String fileName = "L" + (gray / GMConfig.GRAY_LEVEL_INTERVAL) + "_" +
System.currentTimeMillis() + ".jpg";
ImageIO.write(compress, "jpg", new File(GMConfig.MOSAIC_PROC_PATH, fileName));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Util.java
package com.yotwei.core;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Collections;
/**
* 工具类
*/
public class Util {
public static BufferedImage compressImage(BufferedImage src, int dstWidth, int dstHeight) {
BufferedImage compress = new BufferedImage(dstWidth, dstHeight, BufferedImage.TYPE_INT_RGB);
compress.getGraphics().drawImage(
src.getScaledInstance(dstWidth, dstHeight, Image.SCALE_SMOOTH),
0, 0, dstWidth, dstHeight, null);
return compress;
}
/**
* 平均灰度值
*/
public static int getAvgGray(BufferedImage img) {
int x, y, temp, graySum = 0;
for (y = 0; y < img.getHeight(); y++) {
for (x = 0; x < img.getWidth(); x++) {
temp = img.getRGB(x, y);
graySum += (((77 * ((temp & 0xff0000) >> 16))
+ (150 * ((temp & 0x00ff00) >> 8))
+ (29 * (temp & 0x0000ff))) >> 8);
}
}
return graySum / (img.getWidth() * img.getHeight());
}
public static void message(String message) {
System.out.println(message);
}
}
CMConfig.java(常量都在这里)
package com.yotwei.core;
/**
* Created by YotWei on 2018/3/26.
*/
public class GMConfig {
public static final String MOSAIC_ORIGIN_PATH = "weixinAvatars/"; //头像原文件夹
public static final String MOSAIC_PROC_PATH = "process-resources/"; //对文件进一步处理后把图片放在这里
public static final int MOSAIC_WIDTH = 32; //马赛克图片的宽度
public static final int MOSAIC_HEIGHT = 32; //马赛克图片的高度
public static final int GRAY_LEVEL_INTERVAL = 256 / 4;
}
调用
package com.yotwei.client;
import com.yotwei.core.GraphicsMosaicHandler;
/**
* Created by YotWei on 2018/3/26.
*/
public class GMClient {
public static void main(String[] args) throws Exception {
GraphicsMosaicHandler handler = new GraphicsMosaicHandler();
System.out.println("GM process with return code: " + handler.handle("imgs/trust.png","imgs/"));
}
}
最后再放几张效果图