UNPKG

magic-img

Version:

让你的图片加载更加优雅

617 lines (609 loc) 18.8 kB
// src/core/draw/canny.ts var CannyJS; var GrayImageData; var Util; Util = {}; Util.generateMatrix = function(w, h, initialValue) { let matrix, x, y, _i, _j, _ref, _ref1; matrix = []; for (x = _i = 0, _ref = w - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; x = 0 <= _ref ? ++_i : --_i) { matrix[x] = []; for (y = _j = 0, _ref1 = h - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; y = 0 <= _ref1 ? ++_j : --_j) { matrix[x][y] = initialValue; } } return matrix; }; GrayImageData = function() { function GrayImageData2(width, height) { this.width = width; this.height = height; this.data = Util.generateMatrix(this.width, this.height, 0); this; } GrayImageData2.prototype.loadCanvas = function(rawdata) { let b, ctx, d, g, i, r, x, y, _i, _len; x = 0; y = 0; for (i = _i = 0, _len = rawdata.length; _i < _len; i = _i += 4) { d = rawdata[i]; r = rawdata[i]; g = rawdata[i + 1]; b = rawdata[i + 2]; this.data[x][y] = Math.round(0.298 * r + 0.586 * g + 0.114 * b); if (x === this.width - 1) { x = 0; y += 1; } else { x += 1; } } return this; }; GrayImageData2.prototype.getNeighbors = function(x, y, size) { let i, j, neighbors, trnsX, trnsY, _i, _j, _ref, _ref1; neighbors = Util.generateMatrix(size, size, 0); for (i = _i = 0, _ref = size - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) { neighbors[i] = []; for (j = _j = 0, _ref1 = size - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; j = 0 <= _ref1 ? ++_j : --_j) { trnsX = x - (size - 1) / 2 + i; trnsY = y - (size - 1) / 2 + j; if (this.data[trnsX] && this.data[trnsX][trnsY]) { neighbors[i][j] = this.data[trnsX][trnsY]; } else { neighbors[i][j] = 0; } } } return neighbors; }; GrayImageData2.prototype.eachPixel = function(neighborSize, func) { let current, neighbors, x, y, _i, _j, _ref, _ref1; for (x = _i = 0, _ref = this.width - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; x = 0 <= _ref ? ++_i : --_i) { for (y = _j = 0, _ref1 = this.height - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; y = 0 <= _ref1 ? ++_j : --_j) { current = this.data[x][y]; neighbors = this.getNeighbors(x, y, neighborSize); func(x, y, current, neighbors); } } return this; }; GrayImageData2.prototype.toImageDataArray = function() { let ary, i, x, y, _i, _j, _k, _ref, _ref1; ary = []; for (y = _i = 0, _ref = this.height - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; y = 0 <= _ref ? ++_i : --_i) { for (x = _j = 0, _ref1 = this.width - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; x = 0 <= _ref1 ? ++_j : --_j) { for (i = _k = 0; _k <= 2; i = ++_k) { ary.push(this.data[x][y]); } ary.push(255); } } return ary; }; GrayImageData2.prototype.copy = function() { let copied, x, y, _i, _j, _ref, _ref1; copied = new GrayImageData2(this.width, this.height); for (x = _i = 0, _ref = this.width - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; x = 0 <= _ref ? ++_i : --_i) { for (y = _j = 0, _ref1 = this.height - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; y = 0 <= _ref1 ? ++_j : --_j) { copied.data[x][y] = this.data[x][y]; } } copied.width = this.width; copied.height = this.height; return copied; }; GrayImageData2.prototype.drawOn = function(canvas) { let color, ctx, i, imgData, _i, _len, _ref; ctx = canvas.getContext("2d"); imgData = ctx.createImageData(canvas.width, canvas.height); _ref = this.toImageDataArray(); for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { color = _ref[i]; imgData.data[i] = color; } return ctx.putImageData(imgData, 0, 0); }; GrayImageData2.prototype.fill = function(color) { let x, y, _i, _ref, _results; _results = []; for (y = _i = 0, _ref = this.height - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; y = 0 <= _ref ? ++_i : --_i) { _results.push(function() { let _j, _ref1, _results1; _results1 = []; for (x = _j = 0, _ref1 = this.width - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; x = 0 <= _ref1 ? ++_j : --_j) { _results1.push(this.data[x][y] = color); } return _results1; }.call(this)); } return _results; }; return GrayImageData2; }(); CannyJS = {}; CannyJS.gaussianBlur = function(imgData, sigmma, size) { let copy, kernel; if (sigmma == null) { sigmma = 1.4; } if (size == null) { size = 3; } kernel = CannyJS.generateKernel(sigmma, size); copy = imgData.copy(); copy.fill(0); imgData.eachPixel(size, function(x, y, current, neighbors) { let i, j, _results; i = 0; _results = []; while (i <= size - 1) { j = 0; while (j <= size - 1) { copy.data[x][y] += neighbors[i][j] * kernel[i][j]; j++; } _results.push(i++); } return _results; }); return copy; }; CannyJS.generateKernel = function(sigmma, size) { let e, gaussian, i, j, kernel, s, sum, x, y, _i, _j, _k, _l, _ref, _ref1, _ref2, _ref3; s = sigmma; e = 2.718; kernel = Util.generateMatrix(size, size, 0); sum = 0; for (i = _i = 0, _ref = size - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) { x = -(size - 1) / 2 + i; for (j = _j = 0, _ref1 = size - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; j = 0 <= _ref1 ? ++_j : --_j) { y = -(size - 1) / 2 + j; gaussian = 1 / (2 * Math.PI * s * s) * Math.pow(e, -(x * x + y * y) / (2 * s * s)); kernel[i][j] = gaussian; sum += gaussian; } } for (i = _k = 0, _ref2 = size - 1; 0 <= _ref2 ? _k <= _ref2 : _k >= _ref2; i = 0 <= _ref2 ? ++_k : --_k) { for (j = _l = 0, _ref3 = size - 1; 0 <= _ref3 ? _l <= _ref3 : _l >= _ref3; j = 0 <= _ref3 ? ++_l : --_l) { kernel[i][j] = (kernel[i][j] / sum).toFixed(3); } } return kernel; }; CannyJS.sobel = function(imgData) { let copy, xFiler, yFiler; yFiler = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]; xFiler = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]]; copy = imgData.copy(); copy.fill(0); imgData.eachPixel(3, function(x, y, current, neighbors) { let ghs, gvs, i, j, _i, _j; ghs = 0; gvs = 0; for (i = _i = 0; _i <= 2; i = ++_i) { for (j = _j = 0; _j <= 2; j = ++_j) { ghs += yFiler[i][j] * neighbors[i][j]; gvs += xFiler[i][j] * neighbors[i][j]; } } return copy.data[x][y] = Math.sqrt(ghs * ghs + gvs * gvs); }); return copy; }; CannyJS.nonMaximumSuppression = function(imgData) { let copy; copy = imgData.copy(); copy.fill(0); imgData.eachPixel(3, function(x, y, c, n) { if (n[1][1] > n[0][1] && n[1][1] > n[2][1]) { copy.data[x][y] = n[1][1]; } else { copy.data[x][y] = 0; } if (n[1][1] > n[0][2] && n[1][1] > n[2][0]) { copy.data[x][y] = n[1][1]; } else { copy.data[x][y] = 0; } if (n[1][1] > n[1][0] && n[1][1] > n[1][2]) { copy.data[x][y] = n[1][1]; } else { copy.data[x][y] = 0; } if (n[1][1] > n[0][0] && n[1][1] > n[2][2]) { return copy.data[x][y] = n[1][1]; } else { return copy.data[x][y] = 0; } }); return copy; }; CannyJS.hysteresis = function(imgData, ht, lt) { let copy, isCandidate, isStrong, isWeak, traverseEdge; copy = imgData.copy(); isStrong = function(edge) { return edge > ht; }; isCandidate = function(edge) { return edge <= ht && edge >= lt; }; isWeak = function(edge) { return edge < lt; }; imgData.eachPixel(3, function(x, y, current, neighbors) { if (isStrong(current)) { return copy.data[x][y] = 255; } else if (isWeak(current) || isCandidate(current)) { return copy.data[x][y] = 0; } }); traverseEdge = function(x, y) { let i, j, neighbors, _i, _results; if (x === 0 || y === 0 || x === imgData.width - 1 || y === imgData.height - 1) { return; } if (isStrong(copy.data[x][y])) { neighbors = copy.getNeighbors(x, y, 3); _results = []; for (i = _i = 0; _i <= 2; i = ++_i) { _results.push(function() { let _j, _results1; _results1 = []; for (j = _j = 0; _j <= 2; j = ++_j) { if (isCandidate(neighbors[i][j])) { copy.data[x - 1 + i][y - 1 + j] = 255; _results1.push(traverseEdge(x - 1 + i, y - 1 + j)); } else { _results1.push(void 0); } } return _results1; }()); } return _results; } }; copy.eachPixel(3, function(x, y) { return traverseEdge(x, y); }); copy.eachPixel(1, function(x, y, current) { if (!isStrong(current)) { return copy.data[x][y] = 0; } }); return copy; }; CannyJS.canny = function({ width, height, data }, ht, lt, sigmma, kernelSize) { let blur, imgData, nms, sobel; if (ht == null) { ht = 100; } if (lt == null) { lt = 50; } if (sigmma == null) { sigmma = 1.4; } if (kernelSize == null) { kernelSize = 3; } imgData = new GrayImageData(width, height); imgData.loadCanvas(data); blur = CannyJS.gaussianBlur(imgData, sigmma, kernelSize); sobel = CannyJS.sobel(blur); nms = CannyJS.nonMaximumSuppression(sobel); return CannyJS.hysteresis(nms, ht, lt); }; // src/core/draw/index.ts import jimp from "jimp"; var directions = [ 0 /* Left */, 1 /* LeftTop */, 2 /* Top */, 3 /* TopRight */, 4 /* Right */, 5 /* RightBottom */, 6 /* Bottom */, 7 /* BottomLeft */ ]; var compose = (...fncs) => (...args) => fncs.reduce((p, n, i) => i ? n(p) : n(...p), args); var getPointByDirection = ([x, y], dirction) => { switch (dirction) { case 0 /* Left */: return [x - 1, y]; case 1 /* LeftTop */: return [x - 1, y - 1]; case 2 /* Top */: return [x, y - 1]; case 3 /* TopRight */: return [x + 1, y - 1]; case 4 /* Right */: return [x + 1, y]; case 5 /* RightBottom */: return [x + 1, y + 1]; case 6 /* Bottom */: return [x, y + 1]; case 7 /* BottomLeft */: return [x - 1, y + 1]; } }; var getPoints = (imgData, w, h) => { const pointMap = /* @__PURE__ */ new Map(); for (let y = h - 1; y >= 0; y--) { for (let x = w - 1; x >= 0; x--) { const i = x * 4 + y * w * 4; if (imgData[i] === 255) pointMap.set(`${x}-${y}`, [x, y]); } } return pointMap; }; var getLines = (pointMap) => { const pointCache = /* @__PURE__ */ new Map(); const lines = []; const { random } = Math; for (const [x, y] of pointMap.values()) { if (pointCache.has(`${x}-${y}`)) continue; directions.sort(() => random() - random()); const line = []; let start = [x, y]; line.push(start); pointCache.set(`${x}-${y}`, true); let i = 0; while (i < directions.length) { const [x2, y2] = getPointByDirection(start, directions[i]); if (!pointMap.has(`${x2}-${y2}`) || pointCache.has(`${x2}-${y2}`)) { i++; continue; } line.push(start = [x2, y2]); pointCache.set(`${x2}-${y2}`, true); } if (line.length > 2) { lines.push(line); } } return lines; }; var createPolyLines = (lines) => { return lines.map((line) => `<polyline style="--offset:${line.length}" points="${line.map((point) => point.join(",")).join(" ")}" fill="none" ></polyline>`).join(""); }; var getPolyLines = compose( getPoints, getLines, createPolyLines ); async function draw_default(filePath, params) { const { w = 400, h = jimp.AUTO } = params; const img = await jimp.read(filePath); const width = img.getWidth(); const height = img.getHeight(); const scaleImg = img.resize(+w, +h); const width_ = scaleImg.getWidth(); const height_ = scaleImg.getHeight(); const canny = CannyJS.canny({ width: width_, height: height_, data: scaleImg.bitmap.data }, 80, 10, 1.4, 3); const content = getPolyLines(canny.toImageDataArray(), width_, height_); return { content, width, height, width_, height_ }; } // src/core/cucoloris.ts import potrace from "potrace"; import jimp2 from "jimp"; var defaultOptions = { background: "#fff", color: "#c7d4d8", threshold: 100 }; async function cucoloris_default(filePath, params) { const { w = 400, h = jimp2.AUTO } = params; const image = await jimp2.read(filePath); const img = image.resize(+w, +h); const trace = new potrace.Potrace(); const options = { ...defaultOptions, ...params }; options.threshold = Number(options.threshold); trace.setParameters(options); const content = await new Promise((r) => { trace.loadImage(img.bitmap, () => { r(trace.getPathTag()); }); }); return { content, width: img.bitmap.width, height: img.bitmap.height }; } // src/core/lqip.ts import jimp3 from "jimp"; async function lqip_default(filePath, params) { const { w = 20, h = jimp3.AUTO } = params; const [[, type]] = Array.from(filePath.matchAll(/\.(jpg|jpeg|png|gif|webp)/g)); const image = await jimp3.read(filePath); const width = image.getWidth(); const height = image.getHeight(); const img = image.resize(+w, +h); const buffer = await img.getBufferAsync(mimeType[type]); const content = `data:${mimeType[type]};base64,${buffer.toString("base64")}`; return { width, height, content }; } // src/core/sqip.ts import sqip from "sqip"; var detaultOptions = { numberOfPrimitives: 20, blur: 2, mode: 0 }; async function sqip_default(filename, params) { const { final_svg: svg, img_dimensions: data } = await sqip({ filename, ...detaultOptions, ...params }); return { content: svg.replace("</svg>", "").replace(/<svg([^<]+)/g, ""), width: data.width, height: data.height }; } // src/core/blurhash.ts import jimp4 from "jimp"; import { encode, decode } from "blurhash"; import UPNG from "upng-js"; import { encode as encode64 } from "base64-arraybuffer"; async function blurhash_default(filePath, params) { const { w = 20, h = jimp4.AUTO, componentX = 4, componentY = 4, punch, hash: defaultHash } = params; const image = await jimp4.read(filePath); const width = image.getWidth(); const height = image.getHeight(); const img = image.resize(+w, +h); const nw = img.getWidth(); const nh = img.getHeight(); const hash = defaultHash || encode(Uint8ClampedArray.from(img.bitmap.data), nw, nh, componentX, componentY); const bitmap = decode(hash, nw, nh, punch); const png = UPNG.encode([bitmap], nw, nh, 256); const content = `data:image/png;base64,${encode64(png)}`; return { width, height, content }; } // src/core/index.ts import webp from "webp-converter"; import * as path from "path"; import { rmSync, mkdirSync, promises as fp } from "fs"; import { createUnplugin } from "unplugin"; import fetch from "node-fetch"; var outputDir = path.join(process.cwd(), "node_modules", "magic-img-output"); var mimeType = /* @__PURE__ */ ((mimeType2) => { mimeType2["jpeg"] = "image/jpeg"; mimeType2["jpg"] = "image/jpeg"; mimeType2["png"] = "image/png"; mimeType2["gif"] = "image/gif"; return mimeType2; })(mimeType || {}); try { rmSync(outputDir, { recursive: true, force: true }); } catch (e) { console.log(e); } finally { mkdirSync(outputDir); } var core_default = createUnplugin((options = {}, meta) => { webp.grant_permission(); const asyncTaskCacheMap = /* @__PURE__ */ new Map(); const cacheMap = /* @__PURE__ */ new Map(); const includeReg = /magic=(draw|cucoloris|lqip|sqip|blurhash)/; const magicReg = /([^?]+)\?(.+)/g; const pathReg = /('|")([^'"?]+)/g; const isRemoteUrl = (path2) => path2.includes("magic-img@"); const transformMap = { draw: draw_default, cucoloris: cucoloris_default, lqip: lqip_default, sqip: sqip_default, blurhash: blurhash_default }; async function loadImg(url) { const res = await fetch(url); const resourceType = res.headers.get("content-type"); const suffix = `.${mimeType[resourceType] || "jpg"}`; const aBuffer = await res.arrayBuffer(); const buffer = Buffer.from(aBuffer); const filePath = path.join(outputDir, `${Date.now()}${suffix}`); await fp.writeFile(filePath, buffer); return filePath; } async function decodeWebp(filePath) { if (!filePath.includes(".webp")) return filePath; const output = path.join(outputDir, `${Date.now()}.jpg`); await webp.dwebp(filePath, output, "-o"); return output; } async function getAsyncRes(taskId, task) { if (!asyncTaskCacheMap.get(taskId)) { asyncTaskCacheMap.set(taskId, task()); } return await asyncTaskCacheMap.get(taskId); } async function transform_(code, id) { if (id.includes("virtual_magic-img")) id = decodeURIComponent(id); if (cacheMap.has(id)) return cacheMap.get(id); const isRemote = isRemoteUrl(id); const [[, filePath, params = {}] = []] = Array.from(id.matchAll(isRemote ? /magic-img@(.+)\?(.+)/g : magicReg)); let convertFilePath = filePath; isRemote && (convertFilePath = await getAsyncRes(convertFilePath, () => loadImg(convertFilePath))); convertFilePath = await getAsyncRes(convertFilePath, () => decodeWebp(convertFilePath)); const { magic, ...customParams } = Object.fromEntries(new URLSearchParams(params).entries()); const { width = 0, height = 0, width_ = 0, height_ = 0, content } = await transformMap[magic](convertFilePath, { ...options[magic], ...customParams }); const [match] = Array.from(code.matchAll(pathReg)); let str = "const src = "; if (isRemote) { str += `"${filePath}"`; } else if (code.includes("__webpack_public_path__")) { str += `__webpack_public_path__ + "${match[2]}"`; } else { str += `"${match[2].endsWith("$_") ? match[2].slice(0, match[2].length - 2) : match[2]}"`; } cacheMap.set(id, ` ${str} export default JSON.stringify({ src, magic: '${magic}', width: ${width}, height: ${height}, width_: ${width_}, height_: ${height_}, content: '${content}', })`); return cacheMap.get(id); } return { name: "unplugin-magic-img", resolveId(source) { if (!isRemoteUrl(source)) return null; return "\0" + source; }, loadInclude(id) { return isRemoteUrl(id); }, async load(id) { return { code: await transform_("", id), moduleSideEffects: false }; }, transformInclude(id) { return includeReg.test(id); }, transform: transform_ }; }); export { core_default };