Skip to content

Instantly share code, notes, and snippets.

@bczhc
Created November 12, 2025 16:47
Show Gist options
  • Select an option

  • Save bczhc/09ac13aeb06bcbc0164216543766e398 to your computer and use it in GitHub Desktop.

Select an option

Save bczhc/09ac13aeb06bcbc0164216543766e398 to your computer and use it in GitHub Desktop.
字体简单聚类(范围U+3400到U+9FA5)
typst c 13.typ --input=start=$((0x3400)) --input=end=$((0x9fa5)) --ppi=1000 --format=png /mnt/nvme/imgs/'{p}'.png

效果一般吧。不是多么好。

330
簑簔
䅮稹
岨詛
襄鷥
漬潰
䷕䷚
攚櫿
䛌麭
墖壋
墖墤
鈼飵
䲶爽
䲶曾
篕簋
㬵胶
朌肦
䔁藟
䓺䔁
䔁莫
䔁蘽
䔁蓁
䔁葈
嗚鳴
懁瘒
慊瘒
䊸釫
曐蔞
髪髮
傽價
傽僓
侓傽
搷撗
橃襏
撥橃
㥔情
嗶嘾
嗶噹
喳嗶
㩒㯲
擐檡
擐檈
壠瓏
㬺幐
㰍蠪
摾襁
䑎膱
慊懁
晚晩
䷌䷘
嘾噹
喳嘾
进追
噩鏀
䚤鰅
犆犦
㘙巗
㖈䎛
䷖䷳
㼀璉
撴橔
酐酑
䓺藟
莫藟
藟蘽
蓁藟
葈藟
䭞鐸
閳闓
閳闐
寅眚
㫩眚
㱴㱵
萺蔮
䆺棄
臝贏
䇔贏
贏鸁
嬴贏
贏赢
闠闦
䦣闠
鮾鯘
鐥饍
撥襏
堾璹
㫺昔
㾘㿁
逈逍
攞欏
䃝礅
拁枷
㙑軰
䭍䭑
䋆鈩
氭氳
囊曩
憓憽
䕊蟇
㚂爊
墉爊
攦欐
㢆厘
攎櫨
㣬逞
䕂蓮
䭓鎧
䔿蒪
挒栵
鮍鲏
㮶搠
暖暧
崽罳
莗蓳
莗蕇
楽韭
堇藁
䷂䷾
晳皙
朣膧
㢈麜
捿槏
槏欀
撪槏
棲槏
㨧槏
搛槏
槏橦
䘞祃
䂀瞋
蓌藑
㥀懍
㥀憛
䦨聞
鬟鬤
㰜攧
祌衶
鍕餫
蓳蕇
䶺肭
鈢鉢
壙爌
鱑鱰
䷐䷰
㭺掩
㜼嬙
菕薡
䓿薡
蓋薡
堭煌
䋜䋯
䗂錿
謙譠
壈壞
晶瞐
亲奈
錆錹
鮮鲜
檖襚
䴊鸝
蔁輂
鶯鷪
摍樎
捿欀
捿撪
捿棲
㨧捿
捿搛
捿橦
鍠餭
䦰闒
撪欀
棲欀
㨧欀
搛欀
橦欀
鏌饃
䃽衼
賸鰧
并茾
井茾
闾阃
䷁䷎
僓價
侓價
侓僓
䓍謩
䓍萰
㬻䐠
䇔臝
臝鸁
嬴臝
臝赢
曾爽
束東
㘽㦳
慒憎
䷔䷝
鋂鱶
䓺莫
䓺蘽
䓺蓁
䓺葈
攏櫳
揍揸
䷤䷩
䳛䳨
墤壋
錛餴
魛鱽
䪖韠
䊯穬
审鼂
䍢鼂
䇔鸁
䇔嬴
䇔赢
㫷棊
日曰
莫蘽
莫蓁
莫葈
巋鎽
侠俠
䤶餣
柪袎
涑灙
牅矑
䓰草
䓰菙
䓰荲
撪棲
㨧撪
搛撪
撪橦
墫壿
䦣闦
蓁蘽
葈蘽
㩰㾯
䷇䷦
鍡餵
䉑羀
䦚閤
䘋孅
㫚曶
艆躴
䰀鬘
䔝蕠
兽嘼
䐦膡
鏿饓
基茥
䷗䷣
䲧塢
䧶真
祛袪
嬴鸁
赢鸁
谭遣
谭遭
葈蓁
嬴赢
癞癩
草菙
草荲
遣遭
稴穕
䔸萕
㚂墉
䓿菕
菕蓋
鰜鳒
某莑
偣傄
䷓䷴
业棐
业栗
㻲塿
堳瑂
䉈籔
䆎䊱
㮣槩
鋒韸
㯭擄
蘖蘗
䓋䓜
㫩寅
䵄饐
䒥芾
檈檡
祢袮
䦵闣
䍢审
善害
䕼藪
祩袾
榆褕
䷲䷶
栗棐
茉荣
荲菙
祇衹
喳噹
㨧棲
搛棲
棲橦
菓萧
菒萧
䔬萧
䓿蓋
憛懍
庼廎
䕞蔫
鱉鳖
蕌蕪
㨶摀
䶽朡
萰謩
穖穣
㨧搛
㨧橦
㩹㰙
㨣㮬
菒菓
䔬菓
䠀踃
䔬菒
搛橦
闐闓
井并
use std::collections::HashSet;
use image_hasher::{HashAlg, HasherConfig, ImageHash};
use indicatif::{ProgressBar, ProgressStyle};
use rayon::prelude::*;
use std::fs;
use std::path::{Path, PathBuf};
pub fn stylized_progress_bar(len: u64) -> ProgressBar {
let pb = ProgressBar::new(len);
pb.set_style(
#[allow(clippy::literal_string_with_formatting_args)]
ProgressStyle::with_template("[{elapsed_precise}] {wide_bar} {pos:>}/{len:7} {eta}")
.unwrap()
.progress_chars(">>-"),
);
pb
}
const THRESHOLD: u32 = 0;
fn main() {
let try1 = process_with_alg(HashAlg::Blockhash);
println!("{}", try1.len());
for x in try1 {
println!("{}{}",x.0,x.1);
}
}
fn intersection(v1: &Vec<(char, char)>, v2: &Vec<(char, char)>) -> Vec<(char, char)> {
let set1 = v1.iter().collect::<HashSet<_>>();
let set2 = v2.iter().collect::<HashSet<_>>();
set1.intersection(&set2).map(|x| **x).collect::<Vec<_>>()
}
fn process_with_alg(alg: HashAlg) -> Vec<(char, char)> {
let dir = "/mnt/nvme/imgs";
let threshold = THRESHOLD; // 距离阈值,小于等于此值认为相似
let hasher = HasherConfig::new()
.hash_alg(alg)
// .hash_alg(HashAlg::Blockhash)
.to_hasher();
// 获取图片路径
let mut image_paths: Vec<PathBuf> = fs::read_dir(dir)
.unwrap()
.filter_map(|e| {
let path = e.ok()?.path();
let ext = path.extension()?.to_string_lossy().to_lowercase();
if ["png", "jpg", "jpeg"].contains(&ext.as_str()) {
Some(path)
} else {
None
}
})
.collect();
eprintln!("共找到 {} 张图片", image_paths.len());
// 并行计算哈希
let pb = stylized_progress_bar(image_paths.len() as u64);
let hashes: Vec<Option<ImageHash>> = image_paths
.par_iter()
.map(|path| {
let res = image::ImageReader::open(path)
.and_then(|r| Ok(r.decode().unwrap()))
.map(|img| hasher.hash_image(&img))
.map_err(|e| e.to_string());
pb.inc(1);
match res {
Ok(hash) => Some(hash),
Err(err) => {
eprintln!("跳过 {:?}: {}", path, err);
None
}
}
})
.collect();
pb.finish_with_message("哈希计算完成");
// 去除无效项
let valid: Vec<(PathBuf, ImageHash)> = image_paths
.into_iter()
.zip(hashes.into_iter())
.filter_map(|(p, h)| h.map(|hash| (p, hash)))
.collect();
eprintln!("开始比较相似图片...");
let n = valid.len();
let pb2 = stylized_progress_bar((n * (n - 1) / 2) as u64);
let pb2_ref = &pb2;
// 并行比较
let results: Vec<(PathBuf, PathBuf, u32)> = (0..n)
.into_par_iter()
.flat_map(|i| {
let valid = valid.clone();
(i + 1..n).into_par_iter().filter_map(move |j| {
let dist = valid[i].1.dist(&valid[j].1);
pb2_ref.inc(1);
// eprintln!("dist: {dist}");
if dist <= threshold {
Some((valid[i].0.clone(), valid[j].0.clone(), dist))
} else {
None
}
})
})
.collect();
pb2.finish_with_message("比较完成");
let mut collected = Vec::new();
for (a, b, d) in results {
let c1 = filename_to_char(&a);
let c2 = filename_to_char(&b);
if c1 < c2 {
collected.push((c1, c2));
} else {
collected.push((c2, c1));
}
}
collected
}
fn filename_to_char(name: impl AsRef<Path>) -> char {
let offset = name
.as_ref()
.file_stem()
.unwrap()
.to_string_lossy()
.as_ref()
.parse::<u32>()
.unwrap()
- 1;
let cp = 0x3400 + offset;
let c = char::from_u32(cp).unwrap_or(char::REPLACEMENT_CHARACTER);
c
}
/// 用于生成字形图片们
#set page(width: 1em, height: 1em)
#set text(font: (
"Noto Sans CJK SC",
// "Plangothic P1",
// "Plangothic P2",
))
#let new-page(char) = {
align(center + horizon, char.codepoints().first())
}
#let inputs = sys.inputs
#let start = int(inputs.at("start", default: 0x3400))
#let end = int(inputs.at("end", default: start + 10))
#for cp in range(start, end + 1) {
new-page(str.from-unicode(cp))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment