序文
GitHub のランダム ピクセル アバターに似たファイルの md5 を介して、この md5 に関する画像を生成したい. 目的は、この md5 をより直感的にすることであり、GitHub のようにさまざまなユーザー アバターを生成するためにも使用できます。 .
私はインターネットを検索しましたが、既製の方法はありません。参照に使用できる同様の記事が1つしかありませんが、その記事はランダムな文字列であり、私のものはファイルであり、固定された文字列であり、列の数を変更します.私はこれを基礎として使用し、変更するだけです.
参考コンテンツ:Github_github pixel avatar_LLH_Durian のブログに似た、ランダムな形状とランダムな色のピクセル スタイルのアバターを実現する - CSDN ブログ
アルゴリズムの原理
md5 は 32 文字の文字列なので、これまた大騒ぎできますが、私の計算方法は次のとおりです。
0~9桁の平均値をr(赤)、10~19桁の平均値をg(緑)、20~31桁の平均値をb(青)とし、アバターの色が決定しました。
次のステップは、画像のピクセル数を決定することです. 慎重に検討した結果、最終的には 8*16 であると判断しました.つまり, 8 列のピクセルがあり, 各列には 16 行があります. アバターは左右対称、ミラーリング後16×16の画像です。
それが次の図のような状況です。
4 つの 16 進文字列の長さは正確に 16 ビットの 2 進数であるため、1 列を埋めるだけで済みますが、0 ではなく 1 で埋めてから、そこから図を描くことができます。
コード
コードは少し長いですが、多くはコメントです。実行するには Hutool を導入する必要があります
package com.itdct.md5pic;
import org.apache.commons.lang3.StringUtils;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import javax.imageio.ImageIO;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.digest.MD5;
/**
* @author DCTANT
* @version 1.0
* @date 2023/4/28 15:55:23
* @description 用于生成文件MD5图片的方法
*/
public class GenerateMd5Pic {
private MD5 md5 = new MD5();
/**
* 每个格子占据的像素大小
*/
private int blockSize = 250;
/**
* 内边距,默认为两倍的格子宽度
*/
private int padding = blockSize * 2;
/**
* 背景颜色,默认为白色
*/
private Color backgroundColor = new Color(255, 255, 255);
/**
* 输出路径
*/
private String outputPath;
/**
* 输入文件路径
*/
private String filePath;
/**
* 是否输出md5文件
*/
private boolean writeMd5File;
/**
* 通过32位长度的字符串生成图片
*
* @param hexString
*/
public void string32Img(String hexString) {
if (hexString.length() != 32) {
throw new RuntimeException("输入字符串参数长度不是32");
}
// INFO: DCTANT: 2023/4/28 取RGB的总值
String redTotal = hexString.substring(0, 10);
String greenTotal = hexString.substring(10, 20);
String blueTotal = hexString.substring(20, 32);
// INFO: DCTANT: 2023/4/28 获取到平均后的rgb的值
int r = getAverage256Value(redTotal);
int g = getAverage256Value(greenTotal);
int b = getAverage256Value(blueTotal);
// INFO: DCTANT: 2023/4/28 定义每个格子的颜色
Color blockColor = new Color(r, g, b);
// INFO: DCTANT: 2023/4/28 计算图片的总像素宽度
int picSize = 2 * padding + blockSize * 16;
BufferedImage bufferedImage = new BufferedImage(picSize, picSize, BufferedImage.TYPE_INT_RGB);
// INFO: DCTANT: 2023/4/28 获取图片画笔
Graphics2D graphics2D = (Graphics2D) bufferedImage.getGraphics();
// INFO: DCTANT: 2023/4/28 设置背景颜色
graphics2D.setColor(backgroundColor);
// INFO: DCTANT: 2023/4/28 画出整个背景
graphics2D.fillRect(0, 0, picSize, picSize);
boolean[][] blockArray = calculateBlockArray(hexString);
graphics2D.setColor(blockColor);
// INFO: DCTANT: 2023/4/28 绘制每个格子
for (int column = 0; column < blockArray.length; column++) {
boolean[] rows = blockArray[column];
for (int row = 0; row < rows.length; row++) {
boolean isBlock = rows[row];
if (!isBlock) {
continue;
}
// INFO: DCTANT: 2023/4/28 数值为1的,画出方格
int x = padding + column * blockSize;
int y = padding + row * blockSize;
graphics2D.fillRect(x, y, blockSize, blockSize);
}
}
if (StringUtils.isBlank(outputPath)) {
if (StringUtils.isBlank(filePath)) {
outputPath = "./" + hexString + ".jpg";
} else {
outputPath = filePath + ".md5.jpg";
}
}
try {
File file = new File(outputPath);
System.out.println("输出路径为:" + file.getAbsolutePath());
ImageIO.write(bufferedImage, "JPG", new FileOutputStream(outputPath));//保存图片 JPEG表示保存格式
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 通过十六进制字符串,计算出16*16的像素数组
*
* @param hexString
* @return
*/
private boolean[][] calculateBlockArray(String hexString) {
boolean[][] blockArray = new boolean[16][16];
for (int column = 0; column < 8; column++) {
// INFO: DCTANT: 2023/4/28 将32位的md5,每4位切一个下来
String fourHexString = hexString.substring(column * 4, (column + 1) * 4);
// INFO: DCTANT: 2023/4/28 转为十进制
int decimal = HexUtil.hexToInt(fourHexString);
// INFO: DCTANT: 2023/4/28 十进制转二进制
StringBuilder binaryBuilder = new StringBuilder(Integer.toBinaryString(decimal));
// INFO: DCTANT: 2023/4/28 补零
if (binaryBuilder.length() < 16) {
int addZeroCount = 16 - binaryBuilder.length();
for (int i = 0; i < addZeroCount; i++) {
binaryBuilder.insert(0, "0");
}
}
// INFO: DCTANT: 2023/4/28 转为字符数组,用于判断是0还是1
char[] chars = binaryBuilder.toString().toCharArray();
for (int row = 0; row < chars.length; row++) {
char theOneOrZero = chars[row];
if (theOneOrZero == '1') {
blockArray[column][row] = true;
// INFO: DCTANT: 2023/4/28 对称点赋值
blockArray[15 - column][row] = true;
} else {
blockArray[column][row] = false;
// INFO: DCTANT: 2023/4/28 对称点赋值
blockArray[15 - column][row] = false;
}
}
}
return blockArray;
}
/**
* 通过文件生成其MD5图像
*
* @param filePath
*/
public void fileImg(String filePath) {
File file = new File(filePath);
if (!file.exists()) {
throw new RuntimeException("文件不存在");
}
String md5String = md5.digestHex(filePath);
System.out.println("file md5 is " + md5String);
if (writeMd5File) {
FileUtil.writeString(md5String, filePath + ".md5", Charset.defaultCharset());
}
this.filePath = filePath;
string32Img(md5String);
}
/**
* 计算整个十六进制字符串的其中两位的平均值,并四舍五入
*
* @param hex 该十六进制字符串每两位的平均值
* @return
*/
public int getAverage256Value(String hex) {
int loopCount = hex.length() / 2;
if (hex.length() % 2 == 1) {
throw new RuntimeException("hex长度必须为偶数");
}
double total = 0.0;
for (int i = 0; i < loopCount; i++) {
String twoHex = hex.substring(i * 2, (i + 1) * 2);
int value = HexUtil.hexToInt(twoHex);
total += value;
}
double value = total / loopCount;
int result = new BigDecimal(value).setScale(0, RoundingMode.HALF_UP).intValue();
return result;
}
public static void main(String[] args) {
GenerateMd5Pic generateMd5Pic = new GenerateMd5Pic().setWriteMd5File(true);
generateMd5Pic.fileImg("C:\\Tmp\test\\jenkins.war");
}
public String getFilePath() {
return filePath;
}
public GenerateMd5Pic setFilePath(String filePath) {
this.filePath = filePath;
return this;
}
public int getBlockSize() {
return blockSize;
}
public GenerateMd5Pic setBlockSize(int blockSize) {
this.blockSize = blockSize;
return this;
}
public int getPadding() {
return padding;
}
public GenerateMd5Pic setPadding(int padding) {
this.padding = padding;
return this;
}
public String getOutputPath() {
return outputPath;
}
public GenerateMd5Pic setOutputPath(String outputPath) {
this.outputPath = outputPath;
return this;
}
public GenerateMd5Pic setBackgroundColor(Color backgroundColor) {
this.backgroundColor = backgroundColor;
return this;
}
public boolean isWriteMd5File() {
return writeMd5File;
}
public GenerateMd5Pic setWriteMd5File(boolean writeMd5File) {
this.writeMd5File = writeMd5File;
return this;
}
}
結果を達成する
Jenkins の war パッケージを例にすると、生成される効果は次のようになります。
Jenkins が私のものと同じバージョンである場合、生成される画像はまったく同じである必要があります。もちろん、これはアバターとしても使用できます。