magic-img
Version:
617 lines (609 loc) • 18.8 kB
JavaScript
// 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
};