im0o 83f27d9e90 feat: 图库双源接入(Pexels+Unsplash,统一 PhotoProvider)与几何预设扩充
- providers.ts: PHOTO_PROVIDERS 注册表,Pexels 在前(dev 速率高);providerById
- unsplash.ts: 改造成 PhotoProvider(parseUnsplashResponse/unsplashProvider/
  notifyDownload),共享 PhotoProviderError 定义在此叶模块避免注册表互引成环;
  legacy searchUnsplash/unsplashStatus/toAsset/trackDownload 保留(基座旧
  SourcePanel 还在用,删了这轮 typecheck 过不了)
- pexels.ts(新): parsePexelsResponse 纯函数 + pexelsProvider;Authorization 头;
  fullUrl 取 large2x(≈1880)不取原图;无下载统计端点,不实现 notifyDownload
- 没配 key 一律静默 missing,search 抛 PhotoProviderError,模块顶层不抛
- geometry.ts: 新增 GEOMETRY_PRESETS 16 条(固定图案+人工配色,新背景菜单卡片
  数据源)+ 4 个新图案家族(waves/dots/horizon/glow)渲染,复用线性亮度钳制
  保证白字对比度;公开 GeometryKind 不动(旧面板缩略图字典闭合),遗留种子机制
  原样;导出 contrastToWhite/relativeLuminance 供自验
- selfcheck: 手写官方形状样本断言两家解析、缺失态/错误类、预设数量与对比度
2026-09-05 18:24:11 +08:00

cover-studio

im0o 博客的封面 / 头图生成器。自用工具。

替代「为了做一张封面专门开一次 Photoshop」这个动作。产出两类图:

  • 封面(cover) — 带字的索引卡。列表里只有 224px 宽,必须一眼认出是哪篇,所以要有字。 形态是一张相关截图或照片,高斯模糊 + 压暗,居中压 1–2 行标题。
  • 头图(top_img) — 无字的原图。文章页里 780px 宽,读者已经知道在读什么,字是冗余。 这个模式下不模糊不压暗,只裁到目标比例。

跑起来

bun install
bun dev          # http://localhost:5180

这个工具只在 dev 下完整可用。 系统字体的枚举与读取是挂在 Vite 开发服务器上的中间件, vite build 出的静态产物里没有那两个接口。这是刻意的:为「列一下本机字体」单起一个后端进程不值得。

架构

src/
├─ types.ts                  全仓共享的类型契约。并行开发期间不要动它
├─ style.css                 设计 token。面板用这里的变量,不要自带一套颜色
├─ App.vue                   外壳:左工作区 + 右侧栏
├─ editor/
│  ├─ useEditor.ts           Fabric 画布单例、选中态、尺寸、装图装字
│  ├─ canvasSize.ts          尺寸预设与比例锁
│  └─ filters/               滤镜注册表与自定义滤镜
├─ fonts/
│  ├─ vite-plugin-fonts.ts   Vite 中间件:枚举并读取本机字体
│  └─ useFonts.ts            前端:取列表、@font-face 挂载
├─ sources/                  背景来源:上传 / 预设几何 / Unsplash
├─ io/                       导出(WebP/PNG 下载)与模板(localStorage)
└─ components/
   ├─ Workspace.vue          左:画布预览
   ├─ Sidebar.vue            右:面板容器,按选中对象切换
   └─ panels/                五个面板,各归各的开发线

画布是逻辑尺寸(导出像素,如 1200×630),显示时按容器缩放。 Fabric 的 backstore 尺寸和 CSS 尺寸分开设置,所以预览缩放不影响导出分辨率。

右栏面板的显示与否由画布当前选中对象决定。 这是 Fabric 对象模型白送的, 不需要自己做状态机:选中图片出滤镜面板,选中文字出文字面板。

技术选型的理由

  • Fabric.js v7 — 选它不是为了滤镜,是为了对象模型:拖拽 / 缩放 / 裁剪 / 文字框 / 层级全都白送。 而且 canvas.toJSON() / loadFromJSON() 直接就是模板的序列化格式,不用自己设计。
  • 导出用 WebP — 实测同一张图,540×287 PNG = 134 KB,1200×630 WebP = 23 KB。 尺寸翻倍还小 5 倍。旧封面存 PNG 是历史失误:模糊背景 + 少量文字正好是 PNG 最烂的场景。
  • 无独立后端 — 字体接口写成 Vite 插件的 configureServer 中间件,bun dev 一个命令, 没有第二个进程、没有端口要配。

明确不做

选文章 / 回填 frontmatter、自动上传图床、多尺寸派生(只出一份)、内置预设包(模板全靠用户自己存)。

仓库位置

独立 git 仓,远端在 Gitea。不是 blog-workspace 的 submodule——它和博客之间没有代码依赖, 产物是手动拿走的图片文件。它在 blog-workspace 目录下但被 gitignore, 和 blog-v3-draft/ 是同一个处理方式。

S
Description
No description provided
Readme
284 KiB
Languages
TypeScript 57.9%
Vue 40.6%
CSS 1.2%
JavaScript 0.2%