Spaces:
Running
Running
Download BeadQR/miniprogram/utils/imaging.js from Ethscriptions/cut: direct link, hf CLI and curl.
- Browser
- Download file 3.98 kB
-
https://huggingface.co/spaces/Ethscriptions/cut/resolve/main/BeadQR/miniprogram/utils/imaging.js
- Command line
-
hf download hf://spaces/Ethscriptions/cut/BeadQR/miniprogram/utils/imaging.js
-
curl -L -o imaging.js https://huggingface.co/spaces/Ethscriptions/cut/resolve/main/BeadQR/miniprogram/utils/imaging.js
3.98 kB
| // 图像取样:超采样 + 区域平均,得到干净的格子颜色(旧版一次性缩放会产生噪点和锯齿色) | |
| // 所有结果都用 Typed Array 表示,不再生成 HEX 字符串再反解析 | |
| const PAPER = [247, 241, 230] | |
| function getImageDimensions(image) { | |
| const width = Number(image.width) || Number(image.naturalWidth) || Number(image._w) | |
| const height = Number(image.height) || Number(image.naturalHeight) || Number(image._h) | |
| if (!width || !height) throw new Error('IMAGE_DIMENSIONS_UNAVAILABLE') | |
| return { width, height } | |
| } | |
| // 把图片按裁切参数画到 context 上(cover 语义,zoom≥1) | |
| function drawCropped(context, image, width, height, crop) { | |
| const source = getImageDimensions(image) | |
| const scale = Math.max(width / source.width, height / source.height) * (crop.zoom || 1) | |
| const w = source.width * scale | |
| const h = source.height * scale | |
| const offsetX = (width - w) * ((crop.focusX == null ? 50 : crop.focusX) / 100) | |
| const offsetY = (height - h) * ((crop.focusY == null ? 50 : crop.focusY) / 100) | |
| context.fillStyle = `rgb(${PAPER[0]},${PAPER[1]},${PAPER[2]})` | |
| context.fillRect(0, 0, width, height) | |
| context.save() | |
| context.translate(crop.flipH ? width : 0, crop.flipV ? height : 0) | |
| context.scale(crop.flipH ? -1 : 1, crop.flipV ? -1 : 1) | |
| context.drawImage(image, offsetX, offsetY, w, h) | |
| context.restore() | |
| } | |
| // 返回 { size, rgb: Uint8Array(size*size*3) } | |
| function sampleImage(canvas, image, size, crop) { | |
| // 超采样到约 700px 再按块平均,等价于高质量面积缩放 | |
| const factor = Math.max(1, Math.min(24, Math.floor(720 / size))) | |
| const big = size * factor | |
| canvas.width = big | |
| canvas.height = big | |
| const context = canvas.getContext('2d') | |
| context.imageSmoothingEnabled = true | |
| try { context.imageSmoothingQuality = 'high' } catch (e) { /* 部分基础库不支持 */ } | |
| drawCropped(context, image, big, big, crop || {}) | |
| const pixels = context.getImageData(0, 0, big, big).data | |
| const rgb = new Uint8Array(size * size * 3) | |
| const area = factor * factor | |
| for (let gy = 0; gy < size; gy += 1) { | |
| for (let gx = 0; gx < size; gx += 1) { | |
| let r = 0; let g = 0; let b = 0 | |
| for (let sy = 0; sy < factor; sy += 1) { | |
| let p = ((gy * factor + sy) * big + gx * factor) * 4 | |
| for (let sx = 0; sx < factor; sx += 1) { | |
| r += pixels[p]; g += pixels[p + 1]; b += pixels[p + 2] | |
| p += 4 | |
| } | |
| } | |
| const o = (gy * size + gx) * 3 | |
| rgb[o] = (r / area + 0.5) | 0 | |
| rgb[o + 1] = (g / area + 0.5) | 0 | |
| rgb[o + 2] = (b / area + 0.5) | 0 | |
| } | |
| } | |
| return { size, rgb } | |
| } | |
| // 去除简单纯色背景:四角均色为背景色,从边缘洪泛填充相近颜色(主体内部不受影响) | |
| // 返回 Uint8Array 掩码:1 = 背景(空钉位,不放豆) | |
| function removeSimpleBackground(rgb, size, tolerance) { | |
| const corners = [0, size - 1, (size - 1) * size, size * size - 1] | |
| let br = 0; let bg = 0; let bb = 0 | |
| corners.forEach((i) => { br += rgb[i * 3]; bg += rgb[i * 3 + 1]; bb += rgb[i * 3 + 2] }) | |
| br /= 4; bg /= 4; bb /= 4 | |
| const threshold = (tolerance / 100) * 441.673 | |
| const limit = threshold * threshold | |
| const mask = new Uint8Array(size * size) | |
| const isBg = (i) => { | |
| const dr = rgb[i * 3] - br; const dg = rgb[i * 3 + 1] - bg; const db = rgb[i * 3 + 2] - bb | |
| return dr * dr + dg * dg + db * db <= limit | |
| } | |
| const stack = [] | |
| const push = (x, y) => { | |
| if (x < 0 || y < 0 || x >= size || y >= size) return | |
| const i = y * size + x | |
| if (mask[i] || !isBg(i)) return | |
| mask[i] = 1 | |
| stack.push(i) | |
| } | |
| for (let i = 0; i < size; i += 1) { push(i, 0); push(i, size - 1); push(0, i); push(size - 1, i) } | |
| while (stack.length) { | |
| const i = stack.pop() | |
| const x = i % size | |
| const y = (i - x) / size | |
| push(x + 1, y); push(x - 1, y); push(x, y + 1); push(x, y - 1) | |
| } | |
| return mask | |
| } | |
| module.exports = { PAPER, getImageDimensions, drawCropped, sampleImage, removeSimpleBackground } | |