From 72f08cd7c98deafc484a3351be85cd5489382b9b Mon Sep 17 00:00:00 2001 From: tamakyi Date: Thu, 22 Oct 2026 23:36:02 +0800 Subject: [PATCH] =?UTF-8?q?perf(share):=20=E7=94=A8=E3=80=8C=E5=90=8C?= =?UTF-8?q?=E7=94=BB=E5=B8=83=E6=94=BE=E5=A4=A7=E4=BA=8C=E7=BB=B4=E7=A0=81?= =?UTF-8?q?=E3=80=8D=E6=9B=BF=E4=BB=A3=202x=20=E6=B8=B2=E6=9F=93=EF=BC=8C?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=204px=20=E2=86=92=205px?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 960×540 的卡被聊天软件缩到 480 宽时,二维码每模块只剩 2px,解码器读不出。 2x 渲染能解决,但实测成本不低(见下),先用零代价的办法把模块做大。 实测矩阵(go-qrcode 纠错 Q / 真实 55 字符链接 / 37 模块,含内嵌头像): 尺寸 148px = 4.0px/模块 -> 960 o 720 o 480 x 320 x 尺寸 190px = 5.0px/模块 -> 960 o 720 o 480 o 320 o 240 x 纠错降到 M(33 模块)只省 11% 模块数,容错却从 25% 掉到 15%,不划算; PNG 只能保住原图,缩到 480 一样解不出,体积还是 JPEG 的 5 倍。 - QRCodeSize 165 → 190(37 模块取整为 5px/模块;生产短域名 33 模块为 165px) - QRCardH 315 → 335 容纳变高的二维码,卡片整体仍垂直居中 - 新增 TestGenerateQRModuleSizeAtLeast5:三种常见链接长度下模块必须 ≥5px, 把 QRCodeSize 改回 165 立刻 FAIL 渲染成本基准(1x vs 2x):JPEG 编码 12 → 48ms、背景 Lanczos 缩放 25 → 41ms、 画布分配 3 → 13ms,二维码生成本身 0.8ms —— 2x 的代价主要在编码与缩放, 与二维码尺寸无关,所以放大二维码不增加任何 CPU。 --- route/question/share.go | 7 ++++--- route/question/share_layout_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/route/question/share.go b/route/question/share.go index c8f57a9..c02e3e7 100644 --- a/route/question/share.go +++ b/route/question/share.go @@ -68,14 +68,15 @@ const ( // 右侧二维码卡片尺寸 const ( QRCardW = 270 - QRCardH = 315 + QRCardH = 335 QRCardX = ContentX + ContentW - QRCardW - 15 QRCardY = ContentY + (ContentH-QRCardH)/2 ) -// 二维码尺寸 +// 二维码尺寸。190 在 37 模块(High 纠错、55 字符链接)下取整为 5px/模块: +// 实测 5px/模块的卡缩到 480、320 宽仍可解码,4px 时 480 就解不出。 const ( - QRCodeSize = 165 + QRCodeSize = 190 ) // 社交信息 diff --git a/route/question/share_layout_test.go b/route/question/share_layout_test.go index 5766c7d..a40133f 100644 --- a/route/question/share_layout_test.go +++ b/route/question/share_layout_test.go @@ -228,6 +228,31 @@ func TestGenerateQRIsModuleAligned(t *testing.T) { } } +// 模块像素是扫码能力的唯一硬指标:实测 5px/模块的卡缩到 320 宽仍可解码, +// 4px 时缩到 480 就解不出。这里锁住常见链接长度下的模块尺寸。 +func TestGenerateQRModuleSizeAtLeast5(t *testing.T) { + for _, url := range []string{ + "https://box.shiroko.one/_/tamakyi", + "https://box-dev-api.337845818.best/_/tbunread1022181904", + "https://box.shiroko.one/_/a-rather-long-handle-name", + } { + img, err := generateQR(url, nil, QRCodeSize) + if err != nil { + t.Fatal(err) + } + probe, err := qrcode.New(url, qrcode.High) + if err != nil { + t.Fatal(err) + } + probe.DisableBorder = true + mods := len(probe.Bitmap()) + if per := img.Bounds().Dx() / mods; per < 5 { + t.Errorf("%s: %d modules in %dpx -> %dpx/module, want >=5", + url, mods, img.Bounds().Dx(), per) + } + } +} + // 视觉自检:TEST_PREVIEW=1 时把链接区渲成 PNG,人工确认断行与对齐。 func TestShareCardLinkPreview(t *testing.T) { if os.Getenv("TEST_PREVIEW") == "" {