webp fix v1
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
# TamaBox-v4 迁移 Cloudflare Workers + D1 可行性评估
|
||||
|
||||
- 日期:2026-09-27
|
||||
- 评估对象:将 TamaBox-v4(Go + Flamego + GORM + PG/Redis)改造为 **TypeScript + Cloudflare Workers + D1** 的 serverless 形态
|
||||
- 参照项目:[OpenListTeam/OpenList-Worker](https://github.com/OpenListTeam/OpenList-Worker)
|
||||
- 范围:**只读评估,未改动任何源码**
|
||||
|
||||
---
|
||||
|
||||
## 0. 结论速览
|
||||
|
||||
| 项 | 结论 |
|
||||
|---|---|
|
||||
| 技术可行性 | 可行 —— 没有理论上的不可能 |
|
||||
| 工程性价比 | 🔴 **低** —— 是「整体重写」,不是「改造」 |
|
||||
| 主要阻碍 | ① 图像管线(cgo WebP / cwebp 子进程 / freetype 中文渲染)② 进程内有状态子系统 ③ PG 专有 SQL |
|
||||
| 建议 | **方案 A(不迁移)**;若必须上边缘,走**方案 B(静态+只读下沉)** |
|
||||
|
||||
一句话:**OpenList 是「无状态代理」,TamaBox 是「有状态 Web 应用」,两者的边缘适配成本差一个数量级。**
|
||||
|
||||
---
|
||||
|
||||
## 1. 先看清参照物:OpenList-Worker 为什么能做成
|
||||
|
||||
| 维度 | OpenList(Go 原版) | OpenList-Worker(TS) |
|
||||
|---|---|---|
|
||||
| 仓库体积 | 9.8 MB | 21.7 MB(**比原版还大**) |
|
||||
| 语言 | Go | TypeScript |
|
||||
| 代码量 | — | 416 个 `.ts`,约 2.6 MB 源码 |
|
||||
| Star | 24,800 | 372 |
|
||||
| 立项 | — | 2025-08-23(约一年) |
|
||||
| 维护方 | 官方团队 | 官方团队 |
|
||||
|
||||
即使是**官方团队 + 24.8k star** 的项目,重写一年后 Worker 版代码量反而超过原版。而更关键的是**应用形态**:
|
||||
|
||||
**OpenList 的本质是无状态的文件列表 + 反代:**
|
||||
|
||||
- 没有多用户会话状态(只有一个管理员)
|
||||
- 没有服务端可变状态(限流 / 封禁 / 去重全都不需要)
|
||||
- 存储是「可插拔后端」,官方甚至支持降级到 `map` / KV,并明说 *serverless 下禁止回退到内存*
|
||||
- 没有图像处理、没有服务端模板渲染 —— 前端是现成 SPA,Worker 只负责注入 CDN 地址
|
||||
|
||||
所以它干的其实是「**把无状态代理逻辑搬到边缘**」—— 这正是边缘计算最擅长的形态。
|
||||
|
||||
**TamaBox 恰恰相反,它是一个有状态的多用户 Web 应用。**
|
||||
|
||||
---
|
||||
|
||||
## 2. TamaBox 的形态画像
|
||||
|
||||
代码规模:**34,664 行 Go**
|
||||
|
||||
| 模块 | LOC | 说明 |
|
||||
|---|---|---|
|
||||
| `route/` | 7,645 | 业务逻辑(vote 1297 / share 970 / profile 606 …) |
|
||||
| `internal/db` | 6,950 | GORM 模型 + 仓储,15 张表 |
|
||||
| `internal/i18n` | 3,689 | 四语言字典(以数据为主) |
|
||||
| `internal/security` | 2,675 | 审核、自动封禁、IP 情报 |
|
||||
| `internal/route` | 1,653 | 中间件与路由装配 |
|
||||
| `internal/template` | 825 | 模板函数 / Markdown / minify |
|
||||
| `internal/shared` | 758 | IP、去重、并发工具 |
|
||||
| `internal/conf` | 587 | 配置(ini + 单环境变量) |
|
||||
| `internal/cmd` | 482 | CLI 子命令(web/censor/uid/ban/restore) |
|
||||
| `internal/storage` | 440 | R2 / OSS 上传 + WebP 转换 |
|
||||
| 模板 | 42 个 html | 服务端 `html/template` 渲染 |
|
||||
| 中间件 | — | Session / CSRF / Redis cache / reCAPTCHA |
|
||||
|
||||
---
|
||||
|
||||
## 3. 分层可行性矩阵
|
||||
|
||||
### P0 —— 硬阻断(必须重构,无法直搬)
|
||||
|
||||
| # | 阻断点 | 位置 | 为什么在 Workers 上不行 |
|
||||
|---|---|---|---|
|
||||
| 1 | **WebP 编码是 cgo** | `internal/storage/s3.go:16,23,192` | `chai2010/webp` 依赖 libwebp 的 C 代码(`//go:build cgo`)。Workers 是 V8 isolate,跑不了 cgo → 只能换 `@jsquash/webp`(WASM) |
|
||||
| 2 | **GIF→WebP 走 `cwebp` 子进程** | `internal/storage/s3.go:200-233` | `exec.LookPath("cwebp")` + `os.CreateTemp`。Workers 无子进程、无可写磁盘 → **完全不可实现** |
|
||||
| 3 | **分享图 freetype 渲染中文** | `route/question/share.go:121-131, 387-720` | `os.ReadFile(font_path)` 读磁盘 OTF + freetype 光栅化 + jpeg 编码。必须整体重写;且大图中文字形光栅化是 CPU 密集,易撞 Workers CPU 上限 |
|
||||
| 4 | **进程内状态**(Session / 缓存 / 限流 / 封禁 / 去重 / 统计) | `internal/route/route.go:386-395`、`internal/cache`、`internal/security/autoban`、`internal/shared/duplicate.go`、`internal/route/ratelimit.go` | Workers 请求间无共享可变状态 → 全部要迁 Durable Objects / KV / D1,成本明显上升 |
|
||||
| 5 | **cache 中间件硬编码 Redis** | `internal/route/route.go:386-395` | 无条件走 `cacheRedis.Initer()`,不判断配置是否为空。Workers 无法用原生 TCP Redis |
|
||||
| 6 | **启动期副作用** | `internal/cmd/web.go:57-105`、`internal/db/db.go:58-127` | `ListenAndServe` 常驻监听 + `AutoMigrate` + `fixPostgresSequences` + backfill。serverless 无常驻启动流程 |
|
||||
| 7 | **常驻 goroutine 定时任务** | `ratelimit.go:41`、`autoban.go:250`、`duplicate.go:34`、`share.go:207` | `for { sleep; cleanup }` 模式 → 需改 Cron Triggers + DO alarm |
|
||||
| 8 | **PG 专有 SQL** | `db.go:211-234`(序列 `nextval/setval`)、`follow_ups.go:68`(`FOR UPDATE`)、各处 `datatypes.JSON`(JSONB) | D1 是 SQLite:**无序列、无 `FOR UPDATE`、无 JSONB** |
|
||||
| 9 | **SMTP 发信** | `internal/mail/mail.go:129-147` | Workers 禁止裸 TCP 25/465/587 → 须换 Resend / SendGrid 等 HTTP 邮件 API |
|
||||
| 10 | **服务端 Go 模板** | `templates/` 42 个 + `internal/template/template.go:29-229` | `html/template` 的**上下文敏感转义**是安全边界,迁到 JS 模板引擎需谨慎等价实现 |
|
||||
| 11 | **OTel/Uptrace gRPC 上报** | `internal/tracing/tracing.go:15-27`、`internal/cmd/web.go:41-48` | Workers 无 gRPC、无后台 batch 上报协程 |
|
||||
|
||||
### P1 —— 需换库 / 改写(有明确对应物)
|
||||
|
||||
- **S3/R2 上传** → `@aws-sdk/client-s3`(R2 兼容 S3 协议,可行)
|
||||
- **七牛 / 阿里云文本审核** → `fetch` 直调或 JS SDK(阿里云 `tea`/`darabonba` 运行时需重做)
|
||||
- **Excel 导出 `excelize`** → `exceljs`
|
||||
- **CSV 导出** `encoding/csv` → 手写(简单)
|
||||
- **QR `skip2/go-qrcode`** → `qrcode` JS 库
|
||||
- **Markdown `blackfriday` + 自研 XSS 白名单** → `marked` + `DOMPurify`(**白名单规则需逐条复刻**,属安全关键)
|
||||
- **i18n 的 `fmt` 风格 `%s` 占位** → 需实现等价格式化
|
||||
- **软删除 + 唯一索引语义** → SQLite 下槽位行为需重新设计
|
||||
|
||||
### P2 —— 基本可直接移植
|
||||
|
||||
- `internal/imagecheck`(纯标准库像素校验)
|
||||
- `internal/captcha/math.go`(**JSON 算数题,非图片、无字体**)
|
||||
- `internal/i18n` 字典(纯数据)
|
||||
- CSRF(无状态 HMAC,本身可移植)
|
||||
- `internal/security/ipinfo`(离线签名,零网络、零 DNS)
|
||||
- `internal/shared/ip.go`(IP 解析)
|
||||
- `bodylimit` / `ipblacklist`
|
||||
- `embed.FS`(转 Assets / 模块)
|
||||
|
||||
> 补充:`internal/template/minify.go` 当前**未被路由装配实际使用**,迁移时可直接丢弃。
|
||||
|
||||
---
|
||||
|
||||
## 4. 成本量化
|
||||
|
||||
- 源码 **34.6k 行 Go** → 等价重写预计 **40k+ 行 TS**
|
||||
- `templates/` **42 个模板 + 25 个模板函数**全部重写
|
||||
- 全部测试重写(含 `route/vote/vote_stats_test.go` 这类带 DB 的门控测试)
|
||||
- 参考标定:OpenList 官方团队 + 24.8k star,重写约 **一年**,产出 416 个 TS 文件 / 2.6 MB
|
||||
- TamaBox 的形态比 OpenList **更「重」**(有状态 + 关系型 DB + 图像 + 模板),且为**单人维护**
|
||||
|
||||
---
|
||||
|
||||
## 5. 容易被忽略的坑
|
||||
|
||||
1. **中国大陆可用性**:Workers 是海外节点。文本审核走七牛/阿里云(国内 API),海外边缘回源国内延迟高;面向大陆用户还有备案/合规问题(cf. 项目已有 `edgeone-noicp-cf-tunnel-guide`、`edgeone-origin-whitelist-guide`)
|
||||
2. **D1 限额**:SQLite 单库上限(付费 10 GB)、写并发受限;`questions` / `vote_submissions` 属写密集
|
||||
3. **Workers CPU 限额**:免费 10 ms、付费 30 s/请求 且**按 CPU 计费**。分享图渲染、Excel 导出都吃 CPU
|
||||
4. **Durable Objects 是付费项**,按请求 + 时长计费 → 把 Session / 限流 / 封禁搬过去后,成本模型从「一台 1C1G」变成「按量」
|
||||
5. **当前生产是 1C1G 小服务器**:迁移的收益(免运维 / 弹性)对这个体量并不显著
|
||||
|
||||
---
|
||||
|
||||
## 6. 建议方案
|
||||
|
||||
### 方案 A(推荐):不迁移
|
||||
|
||||
形态不匹配 + 单人维护 + 当前 1C1G 成本已很低。收益(免运维、弹性)不足以覆盖重写成本与风险。
|
||||
|
||||
### 方案 B(若确要上边缘,渐进式)
|
||||
|
||||
只把**无状态**的部分下沉边缘:
|
||||
|
||||
- **下沉**:静态资源(`static/`、模板产物)+ 只读接口(公开提问页、投票公开页、`/explore`)→ EdgeOne Pages / Workers
|
||||
- **保留源站**:写路径、图像处理、审核、邮件、导出 → 继续跑 Go
|
||||
|
||||
项目已有 EdgeOne 回源白名单脚本,这套「边缘 + 源站」形态其实已在用。**风险最低、可随时回退。**
|
||||
|
||||
### 方案 C(完全重写)
|
||||
|
||||
仅当能接受**功能裁剪**(砍分享图 / Excel 导出 / 聊天 / 投票表单)且投入 2–3 个月专职。且建议优先 **EdgeOne(腾讯云,有大陆节点)** 而非纯 Cloudflare Workers。
|
||||
|
||||
---
|
||||
|
||||
## 附:评估依据
|
||||
|
||||
- 全部源码只读探查:`internal/**`(110 个 Go 文件)、`route/**`(22 个)、`templates/**`(42 个模板)
|
||||
- 依赖清单:`go.mod`(含 `chai2010/webp`、`disintegration/imaging`、`golang/freetype`、`xuri/excelize`、`uptrace/uptrace-go` 等)
|
||||
- 对照数据来自 GitHub API 与 OpenList-Worker `README` / 提交历史
|
||||
@@ -0,0 +1,186 @@
|
||||
# WebP 压缩链路评审(2026-10-15)
|
||||
|
||||
范围:`internal/storage/s3.go`(上传管线)、`route/question/page.go`(提问/回答图片)、
|
||||
依赖 `github.com/chai2010/webp v1.4.0` + `disintegration/imaging v1.6.2`。
|
||||
|
||||
结论:**提升空间很大,且主要不在「调 quality」上,而在「按用途分档 + 修方向 + 兜底回退 + 编码器能力上限」四件事。**
|
||||
最肥的一块:头像服务端不缩到实际展示尺寸,实测多传 ~7 倍字节。
|
||||
|
||||
---
|
||||
|
||||
## 一、现状事实(已核对代码)
|
||||
|
||||
| 位置 | 现状 |
|
||||
|---|---|
|
||||
| `s3.go:186-195` | `maxWidth=1920` 对**所有**用途统一生效;`webp.Options{Quality: 80}`,无 `Lossless`、无 `Exact` |
|
||||
| `s3.go:113-133` | GIF 走 `cwebp -q 80 -mixed -mt`;静态走 `ConvertStaticImageToWebP`;失败回退原图 |
|
||||
| `s3.go:200-203` | `cwebp` 不在 PATH 时静默回退原始 GIF(无日志) |
|
||||
| `route/question/page.go:713-734` | GIF **完全不转换**,直接原样上传 —— 与 `s3.go` 行为不一致 |
|
||||
| `route/question/page.go:740-748` | 该分支 PutObject **未设 CacheControl**(`s3.go` 有 immutable) |
|
||||
| `route/question/page.go:721` | 提问图片与头像共用同一函数,同样 1920/80 |
|
||||
|
||||
`chai2010/webp` 的 `Options` 只有三个字段:
|
||||
|
||||
```go
|
||||
type Options struct { Lossless bool; Quality float32; Exact bool }
|
||||
```
|
||||
|
||||
即 **method(0-6)、sns_strength、filter_strength、alpha_quality、near_lossless 全部不可控**。
|
||||
C 层是裸 `WebPEncodeRGB` / `WebPEncodeRGBA`(`internal/src/webp.c:194-215`),等价于 cwebp 默认档
|
||||
(method=4)。
|
||||
|
||||
---
|
||||
|
||||
## 二、体积实测(本机 Pillow 绑定同一份libwebp 1.4.0,样本取自 outputs/ 真实截图)
|
||||
|
||||
### 2.1 method 的影响(q80 固定)
|
||||
|
||||
| 样本 | 原 PNG | m4 q80(=现状) | m6 q80 | m6 q75 | m6 q85 |
|
||||
|---|---|---|---|---|---|
|
||||
| tb-prod-test-qr.png | 343,674 | 31,856 | **29,916** (-6.1%) | 24,274 (-23.8%) | 37,448 |
|
||||
| prod-tamakyi-snrat-check.png | 635,502 | 151,888 | **147,498** (-2.9%) | 126,766 (-16.5%) | 172,434 |
|
||||
| admin6-cdn-check.png | 375,873 | 51,202 | **48,664** (-5.0%) | 41,122 (-19.7%) | 59,222 |
|
||||
|
||||
→ **method=6 平均再省 3~6%**,纯 CPU 换体积,无画质损失。
|
||||
|
||||
### 2.2 按用途分档的收益(当前完全没分档)
|
||||
|
||||
模拟一张典型手机照片:
|
||||
|
||||
| 用途 | 现状(1920/q80/m4) | 建议档位 | 建议档实测 | 节省 |
|
||||
|---|---|---|---|---|
|
||||
| **头像**(前端 cropper 输出512) | 83,366 | 512 / q78 / m6 | 11,390 | **-86%** |
|
||||
| 头像(列表缩略) | — | 320 / q78 | 4,966 | -94% |
|
||||
| 背景16:9 | 26,222 | 1920 / q80 | 26,222 | — |
|
||||
| 正文配图 | — | 1600 / q80 | — | -20~25% |
|
||||
|
||||
注意:前端 `profile.html:1390` 头像 `maxWidth:512`,但**服务端把 1920 传给编码器**,
|
||||
意味着直传(绕过前端 cropper)的大图会被以 1920 全尺寸存下,而头像展示最大也就 256px。
|
||||
|
||||
### 2.3 有损 vs 无损(透明图)
|
||||
|
||||
| 类型 | 体积 |
|
||||
|---|---|
|
||||
| 带 alpha 图 lossy q80 | 49,636 |
|
||||
| 带 alpha 图 lossless | 253,128 |
|
||||
|
||||
→ 无脑lossless 是灾难(5x),**不要**走这条路。但存在反向问题:
|
||||
|
||||
### 2.4 「编码后不一定更小」
|
||||
|
||||
对已经压得很狠的小 PNG/图标,WebP 有损可能**比原图还大**(WebP 对纯色/线框图有损效率差)。
|
||||
**当前代码没有任何 `len(webpData) > len(fileData)` 的兜底判断** —— 转换失败会回退,但转换「成功却更大」不会回退。
|
||||
|
||||
### 2.5 重复编码(代际损失)
|
||||
|
||||
已是 WebP 的图再走一次管线 → 二次有损。实测 160,896 → 139,866(体积反而小了 13%,
|
||||
因为 q82→q80),**画质白白损失一代,体积收益不确定**。
|
||||
|
||||
---
|
||||
|
||||
## 三、问题清单
|
||||
|
||||
### P0 🔴 EXIF 方向未处理 —— 手机竖拍 JPEG 会横躺
|
||||
|
||||
`image.Decode`(`s3.go:99`、`page.go:707`)**不读 EXIF Orientation**。
|
||||
前端 cropper 走 canvas 会正确应用方向,但 `templates/question/item.html:206,238` 的
|
||||
`accept="image/*"` 是**原始文件直传**,没有 canvas 归一化 → 手机竖拍照片转 WebP 后躺倒。
|
||||
|
||||
这不是压缩问题,但和转换管线同源,且用户可见。修EXIF 时顺带可在转码前
|
||||
`image.NewRGBA` 重绘归一,比让 libwebp 处理更可控。
|
||||
|
||||
### P1 🟠 按用途分档缺失(最大体积收益)
|
||||
|
||||
`s3.go:186` 的 `maxWidth=1920` + `Quality:80` 一刀切。已有 `aspect` 参数做比例分档的先例
|
||||
(`UploadAvatarToS3` / `UploadBackgroundToS3`),扩展成 `{maxW, maxH, quality}` 成本极低。
|
||||
预期头像场景存储与流量降 **~85%**。
|
||||
|
||||
### P1 🟠 method 不可控,白丢 3~6%
|
||||
|
||||
`chai2010/webp` 不暴露 method。三条路:
|
||||
- (a) `cwebp -m 6`(GIF 分支已经在用 `cwebp`,静态图也可统一走 CLI,还能顺带拿到
|
||||
`-alpha_q`/`-sns`/`-near_lossless`/`-pass`)—— 需保证 Dockerfile 装 webp 包(**待核**);
|
||||
- (b) 换 `gen2brain/go-webp`,API 直接暴露完整 `WebPConfig`;
|
||||
- (c) 维持现状。
|
||||
|
||||
⚠️ method=6 编码耗时约为 method=4 的 **5~10 倍**。1C1G 生产机上批量上传场景
|
||||
需实测编码耗时再定,不能只看体积。
|
||||
|
||||
### P1 🟠 无「转换后更大」的兜底
|
||||
|
||||
`s3.go:125-132`、`page.go:721-733` 都缺`if len(webp) >= len(orig) { 用原图 }`。
|
||||
小图标/线框图/已优化 JPEG 场景会越转越大。
|
||||
|
||||
### P1 🟠 已是 WebP 还再编一代
|
||||
|
||||
`format == "webp"` 走 default 分支二次有损。建议:已是 WebP 且宽度合规 → 直接透传,
|
||||
只在超宽时才 resize 后重编。
|
||||
|
||||
### P2 🟠 GIF 转换在生产上从未生效(已实证)
|
||||
|
||||
`Dockerfile:16` 是 Alpine `apk add tzdata`,**未安装 `webp` 包** → 生产容器内没有 `cwebp`。
|
||||
`s3.go:201` 的 `exec.LookPath("cwebp")` 必然失败 → GIF 全部走 `uploadData = fileData` 原图上传,
|
||||
且**无任何日志**。即`s3.go:115` 的动画 WebP 分支是死代码,线上动画图一直是原 GIF。
|
||||
|
||||
(另外 `page.go:714-718` GIF 不转,与 `s3.go` 不一致;但因为上述原因,两条路实际殊途同归。)
|
||||
|
||||
若要启用:Dockerfile 加 `RUN apk add --no-cache webp`(Alpine 包名即 `webp`,含 `cwebp`/`img2webp`/`gif2webp`),
|
||||
并在 `LookPath` 失败时补 Warn。
|
||||
|
||||
### P2 🟠 GIF 分支其余问题
|
||||
|
||||
`s3.go:223` `cwebp -q 80 -mixed -mt` 缺 `-m 6`、`-lossless`、`-metadata none`,
|
||||
也缺 GIF 帧数/总像素上限(`imagecheck.CheckPixelLimit` 只看逻辑屏幕宽高,
|
||||
500 帧 2000×2000 的 GIF 不会被拦)。
|
||||
|
||||
### P2 走 cwebp CLI 需先改 Dockerfile
|
||||
|
||||
若采纳批次 B 的 method=6 方案 (a)(静态图统一走 CLI),必须先在 Dockerfile 装 webp 包,
|
||||
否则会把「能用但慢」变成「不能用的静默回退」。
|
||||
|
||||
### P2 🟠 峰值内存
|
||||
|
||||
`s3.go:95-99` 解码一次拿format,`s3.go:181` 又完整解码一次,**同一张图解码两遍**;
|
||||
`imaging.Resize` 再全量分配。4096×4096 上限下 RGBA 峰值约 64MB/张,
|
||||
并发上传时 1C1G 有 OOM 风险。可合并为「一次解码 → 校验 → resize → 编码」。
|
||||
|
||||
### P2 提问图片分支缺 CacheControl
|
||||
|
||||
`page.go:740-748` 未设 `CacheControl`,与 `s3.go:158` 不一致(后者 immutable 一年)。
|
||||
|
||||
### P2 maxWidth 只限宽不限高
|
||||
|
||||
`s3.go:187` 只判 `Dx() > 1920`。竖图 1080×10000(像素总量在4096² 内?1080*10000
|
||||
= 10.8M < 16.7M,通过)→ 不缩放,长边 10000px 下发,首屏极慢。
|
||||
|
||||
---
|
||||
|
||||
## 四、建议的落地方案(待批准,未动代码)
|
||||
|
||||
### 批次 A(低风险,先做)
|
||||
1. `ConvertStaticImageToWebP` 增加参数:`maxWidth int, quality float32`;
|
||||
`uploadPictureToS3` 按 `aspect` 传入不同档位(头像 512/q78、背景 1920/q80、通用 1600/q80)。
|
||||
2. 加 `len(webp) >= len(fileData) && format != "gif"` 回退原图。
|
||||
3. `format == "webp"` 且尺寸合规 → 直接透传,不重编。
|
||||
4. maxWidth 改为同时约束长边(`maxSide`)。
|
||||
5. `page.go` PutObject 补 `CacheControl`。
|
||||
6. GIF 分支加`cwebp` 缺失时的 Warn 日志。
|
||||
|
||||
### 批次 B(需实测/依赖改动)
|
||||
7. method=6:先在 dev 环境用真实图测编码耗时,确认 1C1G 可接受再上;
|
||||
路线倾向 (a) 统一走 `cwebp`(Dockerfile 需确认 webp 包,见下)。
|
||||
8. EXIF 方向归一(转码前重绘)。
|
||||
9. 统一 GIF 双实现。
|
||||
10. 合并重复解码降内存。
|
||||
|
||||
### 待核实(已全部核实完毕)
|
||||
- ✅ `Dockerfile` 用 Alpine apk,**未装 `webp`** → `cwebp` 线上不存在,GIF 转换是死代码。
|
||||
|
||||
---
|
||||
|
||||
## 五、当前无法验证的部分
|
||||
|
||||
- 本机无 libwebp/GCC,**未编译运行 Go 侧实测**。体积数据来自 Pillow 绑定同一份
|
||||
libwebp 1.4.0、同样 quality/method 组合,作为体积基线可信;但
|
||||
**`imaging.Resize` 的 Lanczos 与 Pillow LANCZOS 的输出差异未纳入实测**,实际体积会有小幅出入。
|
||||
- method=6 的**编码耗时**未实测(这是决定是否采用的关键约束)。
|
||||
Reference in New Issue
Block a user