koishi-plugin-img-tool 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +15 -0
- package/lib/index.js +258 -0
- package/package.json +1 -1
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Context, Schema } from "koishi";
|
|
2
|
+
export declare const name = "img-tool";
|
|
3
|
+
export declare const using: readonly ["canvas"];
|
|
4
|
+
export interface Config {
|
|
5
|
+
}
|
|
6
|
+
export declare const Config: Schema<Config>;
|
|
7
|
+
declare module "koishi" {
|
|
8
|
+
interface Context {
|
|
9
|
+
canvas: {
|
|
10
|
+
createCanvas: (width: number, height: number) => any;
|
|
11
|
+
loadImage: (data: Buffer) => Promise<any>;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name2 in all)
|
|
10
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
Config: () => Config,
|
|
34
|
+
apply: () => apply,
|
|
35
|
+
name: () => name,
|
|
36
|
+
using: () => using
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(src_exports);
|
|
39
|
+
var import_koishi = require("koishi");
|
|
40
|
+
var import_gif_encoder_2 = __toESM(require("gif-encoder-2"));
|
|
41
|
+
var import_gifuct_js = require("gifuct-js");
|
|
42
|
+
var name = "img-tool";
|
|
43
|
+
var using = ["canvas"];
|
|
44
|
+
var Config = import_koishi.Schema.object({});
|
|
45
|
+
function selectFirstImageElement(content) {
|
|
46
|
+
if (!content) return;
|
|
47
|
+
const nodes = import_koishi.h.parse(content);
|
|
48
|
+
return import_koishi.h.select(nodes, "img") ?? import_koishi.h.select(nodes, "image");
|
|
49
|
+
}
|
|
50
|
+
__name(selectFirstImageElement, "selectFirstImageElement");
|
|
51
|
+
function getImageSrc(el) {
|
|
52
|
+
const src = el?.attrs?.src ?? el?.attrs?.url;
|
|
53
|
+
return typeof src === "string" && src.length ? src : void 0;
|
|
54
|
+
}
|
|
55
|
+
__name(getImageSrc, "getImageSrc");
|
|
56
|
+
function findImageSrc(session) {
|
|
57
|
+
const direct = selectFirstImageElement(session.content);
|
|
58
|
+
const directSrc = getImageSrc(direct);
|
|
59
|
+
if (directSrc) return directSrc;
|
|
60
|
+
const quote = session.quote;
|
|
61
|
+
const quoteContent = quote?.content;
|
|
62
|
+
const quoteEl = selectFirstImageElement(quoteContent);
|
|
63
|
+
const quoteSrc = getImageSrc(quoteEl);
|
|
64
|
+
if (quoteSrc) return quoteSrc;
|
|
65
|
+
const quoteElements = quote?.elements;
|
|
66
|
+
if (quoteElements) {
|
|
67
|
+
const quoteEl2 = import_koishi.h.select(quoteElements, "img") ?? import_koishi.h.select(quoteElements, "image");
|
|
68
|
+
const quoteSrc2 = getImageSrc(quoteEl2);
|
|
69
|
+
if (quoteSrc2) return quoteSrc2;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
__name(findImageSrc, "findImageSrc");
|
|
73
|
+
function isGif(buffer) {
|
|
74
|
+
if (buffer.length < 6) return false;
|
|
75
|
+
const head = buffer.subarray(0, 6).toString("ascii");
|
|
76
|
+
return head === "GIF87a" || head === "GIF89a";
|
|
77
|
+
}
|
|
78
|
+
__name(isGif, "isGif");
|
|
79
|
+
function toArrayBuffer(buffer) {
|
|
80
|
+
return buffer.buffer.slice(
|
|
81
|
+
buffer.byteOffset,
|
|
82
|
+
buffer.byteOffset + buffer.byteLength
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
__name(toArrayBuffer, "toArrayBuffer");
|
|
86
|
+
function mirrorDraw(out, srcCanvas, mode) {
|
|
87
|
+
const w = srcCanvas.width;
|
|
88
|
+
const h2 = srcCanvas.height;
|
|
89
|
+
out.clearRect(0, 0, w, h2);
|
|
90
|
+
if (mode === "left") {
|
|
91
|
+
const rightWidth = Math.floor(w / 2);
|
|
92
|
+
const leftKeepWidth = w - rightWidth;
|
|
93
|
+
out.drawImage(srcCanvas, 0, 0, leftKeepWidth, h2, 0, 0, leftKeepWidth, h2);
|
|
94
|
+
out.save();
|
|
95
|
+
out.translate(w, 0);
|
|
96
|
+
out.scale(-1, 1);
|
|
97
|
+
out.drawImage(srcCanvas, 0, 0, rightWidth, h2, 0, 0, rightWidth, h2);
|
|
98
|
+
out.restore();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (mode === "right") {
|
|
102
|
+
const leftWidth = Math.floor(w / 2);
|
|
103
|
+
const rightKeepWidth = w - leftWidth;
|
|
104
|
+
out.drawImage(
|
|
105
|
+
srcCanvas,
|
|
106
|
+
leftWidth,
|
|
107
|
+
0,
|
|
108
|
+
rightKeepWidth,
|
|
109
|
+
h2,
|
|
110
|
+
leftWidth,
|
|
111
|
+
0,
|
|
112
|
+
rightKeepWidth,
|
|
113
|
+
h2
|
|
114
|
+
);
|
|
115
|
+
out.save();
|
|
116
|
+
out.translate(w, 0);
|
|
117
|
+
out.scale(-1, 1);
|
|
118
|
+
out.drawImage(
|
|
119
|
+
srcCanvas,
|
|
120
|
+
w - leftWidth,
|
|
121
|
+
0,
|
|
122
|
+
leftWidth,
|
|
123
|
+
h2,
|
|
124
|
+
w - leftWidth,
|
|
125
|
+
0,
|
|
126
|
+
leftWidth,
|
|
127
|
+
h2
|
|
128
|
+
);
|
|
129
|
+
out.restore();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (mode === "up") {
|
|
133
|
+
const bottomHeight = Math.floor(h2 / 2);
|
|
134
|
+
const topKeepHeight = h2 - bottomHeight;
|
|
135
|
+
out.drawImage(srcCanvas, 0, 0, w, topKeepHeight, 0, 0, w, topKeepHeight);
|
|
136
|
+
out.save();
|
|
137
|
+
out.translate(0, h2);
|
|
138
|
+
out.scale(1, -1);
|
|
139
|
+
out.drawImage(srcCanvas, 0, 0, w, bottomHeight, 0, 0, w, bottomHeight);
|
|
140
|
+
out.restore();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const topHeight = Math.floor(h2 / 2);
|
|
144
|
+
const bottomKeepHeight = h2 - topHeight;
|
|
145
|
+
out.drawImage(
|
|
146
|
+
srcCanvas,
|
|
147
|
+
0,
|
|
148
|
+
topHeight,
|
|
149
|
+
w,
|
|
150
|
+
bottomKeepHeight,
|
|
151
|
+
0,
|
|
152
|
+
topHeight,
|
|
153
|
+
w,
|
|
154
|
+
bottomKeepHeight
|
|
155
|
+
);
|
|
156
|
+
out.save();
|
|
157
|
+
out.translate(0, h2);
|
|
158
|
+
out.scale(1, -1);
|
|
159
|
+
out.drawImage(
|
|
160
|
+
srcCanvas,
|
|
161
|
+
0,
|
|
162
|
+
h2 - topHeight,
|
|
163
|
+
w,
|
|
164
|
+
topHeight,
|
|
165
|
+
0,
|
|
166
|
+
h2 - topHeight,
|
|
167
|
+
w,
|
|
168
|
+
topHeight
|
|
169
|
+
);
|
|
170
|
+
out.restore();
|
|
171
|
+
}
|
|
172
|
+
__name(mirrorDraw, "mirrorDraw");
|
|
173
|
+
async function mirrorStaticImage(canvas, buffer, mode) {
|
|
174
|
+
const img = await canvas.loadImage(buffer);
|
|
175
|
+
const w = img.width;
|
|
176
|
+
const h2 = img.height;
|
|
177
|
+
const srcCanvas = canvas.createCanvas(w, h2);
|
|
178
|
+
const src = srcCanvas.getContext("2d");
|
|
179
|
+
src.drawImage(img, 0, 0, w, h2);
|
|
180
|
+
const outCanvas = canvas.createCanvas(w, h2);
|
|
181
|
+
const out = outCanvas.getContext("2d");
|
|
182
|
+
mirrorDraw(out, srcCanvas, mode);
|
|
183
|
+
return {
|
|
184
|
+
buffer: outCanvas.toBuffer("image/png"),
|
|
185
|
+
mime: "image/png"
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
__name(mirrorStaticImage, "mirrorStaticImage");
|
|
189
|
+
async function mirrorGifImage(canvas, buffer, mode) {
|
|
190
|
+
const gif = (0, import_gifuct_js.parseGIF)(toArrayBuffer(buffer));
|
|
191
|
+
const frames = (0, import_gifuct_js.decompressFrames)(gif, true);
|
|
192
|
+
if (!frames.length) throw new Error("GIF has no frames");
|
|
193
|
+
const width = gif.lsd?.width ?? frames[0].dims?.width;
|
|
194
|
+
const height = gif.lsd?.height ?? frames[0].dims?.height;
|
|
195
|
+
if (!width || !height) throw new Error("Unable to determine GIF size");
|
|
196
|
+
const srcCanvas = canvas.createCanvas(width, height);
|
|
197
|
+
const src = srcCanvas.getContext("2d");
|
|
198
|
+
const outCanvas = canvas.createCanvas(width, height);
|
|
199
|
+
const out = outCanvas.getContext("2d");
|
|
200
|
+
const encoder = new import_gif_encoder_2.default(width, height);
|
|
201
|
+
encoder.start();
|
|
202
|
+
encoder.setRepeat(0);
|
|
203
|
+
encoder.setQuality?.(10);
|
|
204
|
+
for (const frame of frames) {
|
|
205
|
+
const patch = frame.patch;
|
|
206
|
+
if (!patch || patch.length !== width * height * 4) {
|
|
207
|
+
throw new Error("Unsupported GIF frame patch format");
|
|
208
|
+
}
|
|
209
|
+
const imageData = src.createImageData(width, height);
|
|
210
|
+
imageData.data.set(patch);
|
|
211
|
+
src.putImageData(imageData, 0, 0);
|
|
212
|
+
mirrorDraw(out, srcCanvas, mode);
|
|
213
|
+
const delayCs = typeof frame.delay === "number" ? frame.delay : 0;
|
|
214
|
+
encoder.setDelay(Math.max(0, delayCs * 10));
|
|
215
|
+
encoder.addFrame(out);
|
|
216
|
+
}
|
|
217
|
+
encoder.finish();
|
|
218
|
+
const outBuffer = Buffer.from(encoder.out.getData());
|
|
219
|
+
return { buffer: outBuffer, mime: "image/gif" };
|
|
220
|
+
}
|
|
221
|
+
__name(mirrorGifImage, "mirrorGifImage");
|
|
222
|
+
async function handleMirror(ctx, session, mode) {
|
|
223
|
+
const src = findImageSrc(session);
|
|
224
|
+
if (!src) return "未找到图片:请在消息中附带图片,或引用一条包含图片的消息。";
|
|
225
|
+
const canvas = ctx.canvas;
|
|
226
|
+
if (!canvas?.createCanvas || !canvas?.loadImage) {
|
|
227
|
+
return "未检测到 canvas 服务:请安装并启用 Koishi 的 canvas 服务插件后再使用本功能。";
|
|
228
|
+
}
|
|
229
|
+
const arrayBuffer = await ctx.http.get(src, {
|
|
230
|
+
responseType: "arraybuffer"
|
|
231
|
+
});
|
|
232
|
+
const input = Buffer.from(arrayBuffer);
|
|
233
|
+
const result = isGif(input) ? await mirrorGifImage(canvas, input, mode) : await mirrorStaticImage(canvas, input, mode);
|
|
234
|
+
return import_koishi.h.img(result.buffer, result.mime);
|
|
235
|
+
}
|
|
236
|
+
__name(handleMirror, "handleMirror");
|
|
237
|
+
function apply(ctx, config) {
|
|
238
|
+
ctx.command("左对称", "以左侧为基准生成左右对称图片").action(async ({ session }) => {
|
|
239
|
+
return handleMirror(ctx, session, "left");
|
|
240
|
+
});
|
|
241
|
+
ctx.command("右对称", "以右侧为基准生成左右对称图片").action(async ({ session }) => {
|
|
242
|
+
return handleMirror(ctx, session, "right");
|
|
243
|
+
});
|
|
244
|
+
ctx.command("上对称", "以上侧为基准生成上下对称图片").action(async ({ session }) => {
|
|
245
|
+
return handleMirror(ctx, session, "up");
|
|
246
|
+
});
|
|
247
|
+
ctx.command("下对称", "以下侧为基准生成上下对称图片").action(async ({ session }) => {
|
|
248
|
+
return handleMirror(ctx, session, "down");
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
__name(apply, "apply");
|
|
252
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
253
|
+
0 && (module.exports = {
|
|
254
|
+
Config,
|
|
255
|
+
apply,
|
|
256
|
+
name,
|
|
257
|
+
using
|
|
258
|
+
});
|