自定义合成图片的 Mirai 插件 / 独立程序 / onebot 插件, 灵感/部分数据来自 nonebot-plugin-petpet。
使用底层接口, 多线程优化: 轻量, 高性能, 易拓展
下载 最新版本 petpet.jar
或 petpet-no-ws.jar
下载 模板素材
将模板素材放入 ./data/xmmt.dituon.petpet/
目录
运行 start.bat
或 start.sh
, 可自行更改配置文件 config.json
, 重启后生效
部署 Mirai 机器人框架
下载 最新版本
将插件放入 Mirai/plugins/
下载 模板素材
将模板素材放入 Mirai/data/xmmt.dituon.petpet/
启动 Mirai
, 可自行更改配置文件 Petpet.yml
, 重启后生效 (参考 配置项说明)
30%
的概率触发; 或发送 pet @xxx
pet key @xxx
或key @xxx
可返回指定图片 例如pet kiss @xxx
kiss @xxx
可通过发送的图片生成Petpet
kiss [图片]
, 支持GIF可通过回复构造图片, 例如
[图片]
->[回复[图片]] 对称
可使用
pet
指令 获取keyList
Warning
此功能处于测试阶段, 目前仅能通过
key
生成图片, 请期待后续开发!
部署gocq-http 或其它实现 OneBot标准 机器人框架, 设置正向 WebSocket 监听 (默认端口为8080
)
更改 gocq-http
配置项 message.post-format
为 array
下载 最新版本 petpet.jar
下载 模板素材
将模板素材放入 ./data/xmmt.dituon.petpet/
目录
运行 start
脚本, 或 cd ./
java -jar petpet.jar -gocq
, 可自行更改配置文件 gocq-config.json
, 重启后生效
修改后重启 Mirai 以重新加载
群主或管理员使用
pet on
pet off
以 启用/禁用 戳一戳
pet on/off
指令控制的事件可在配置文件中更改
可在配置文件中禁用指定key, 被禁用的key不会随机触发, 但仍可以通过指令使用
./data/xmmt.dituon.petpet/
下的目录名为 key
,插件启动时会遍历 ./data/xmmt.dituon.petpet/$key/data.json
data.json
是模板配置文件, 程序解析此文件以生成图像
{
"type": "GIF",
"avatar": [],
"text": [],
"delay": 50,
"alias": [ "别名1", "别名2" ]
}
属性 | 类型 | 注释 | 默认值 |
---|---|---|---|
type | 模板类型枚举 | 图片类型枚举, IMG 或GIF |
必须 |
avatar | Avatar 数组 |
头像配置数组, 见下文 | 必须 |
text | Text 数组 |
文本配置数组, 见下文 | 必须 |
inRandomList | 布尔值 | 是否在随机列表中 | false |
reverse | 布尔值 | GIF是否倒放 | false |
delay | 整数 | 帧间延时 (毫秒) | 65 |
background | Background |
背景配置, 见下文 | null |
alias | 字符串数组 | 别名数组 | [] |
hidden | 布尔值 | 是否隐藏 | false |
GIF
动图, 程序会读取目录下所有.png
格式的图像IMG
静态图片, 程序会读取目录下随机.png
格式的图像程序支持复杂的图像处理, 包括裁切, 旋转, 透明度, 滤镜等
{
"avatar": [
{
"type": "FROM",
"pos": [[92, 64, 40, 40], [135, 40, 40, 40], [84, 105, 40, 40]],
"round": true,
"rotate": false,
"avatarOnTop": true,
"angle": 90
},
{
"type": "TO",
"pos": [[5, 8], [60, 90], [50, 90], [50, 0], [60, 120]],
"posType": "DEFORM",
"opacity": 0.5
}
]
}
属性 | 类型 | 注释 | 默认值 |
---|---|---|---|
type | 头像类型枚举 | 见下文, 例如FROM 或TO |
必须 |
pos | 坐标数组 | 头像的坐标信息 | 必须 |
posType | 坐标格式枚举 | 坐标格式枚举, ZOOM 或DEFORM |
ZOOM |
round | 布尔值 | 头像是否裁切为圆形 | false |
avatarOnTop | 布尔值 | 头像图层是否在背景之上 | true |
angle | 整数 | 头像的初始角度 | 0 |
origin | 旋转原点枚举 | 头像的旋转原点 | DEFAULT |
opacity | 浮点数 | 头像的不透明度 | 1.0 |
rotate | 布尔值 | GIF类型的头像是否旋转 | false |
fit | 填充模式枚举 | 填充模式枚举, 可以是CONTAIN 或FILL |
FILL |
crop | 裁切坐标数组 | 头像裁切坐标信息 | null |
cropType | 裁切格式枚举 | 见下文 | NONE |
style | 风格化枚举数组 | 风格化枚举数组, 见下文 | [] |
filter | 滤镜对象数组 | 滤镜数组, 见下文 | [] |
antialias | 布尔值 | 是否使用抗锯齿算法, 默认跟随全局配置 | null |
resampling | 布尔值 | 是否使用重采样缩放, 默认跟随全局配置 | null |
头像类型枚举 type
FROM
发送者头像TO
接收者头像, 或构造的图片GROUP
群头像BOT
机器人头像RANDOM
随机头像 (随机从群聊成员中选择, 不会重复)坐标格式枚举posType
ZOOM
缩放, 通过 x, y, width, height 表示图像DEFORM
变形, 通过四点坐标来表示图像ZOOM
缩放坐标的基本组成单位是 4长度 int[]
数组
其中,前两项为 左上角顶点坐标, 后两项为 宽度和高度
例:
[65, 128, 77, 72]
即 头像的左上角顶点坐标是 (65,128)
, 宽度为 77
, 高度为 72
如果是 GIF
类型,坐标应为二维数组,GIF
的每一帧视为单个图像文件
```json lines { // pos的元素对应GIF的4帧 “pos”: [[65, 128, 77, 72], [67, 128, 73, 72], [54, 139, 94, 61], [57, 135, 86, 65]] }
如果是`IMG`类型, 可以使用一维数组
```json lines
{
"pos": [0, 0, 200, 200]
}
坐标支持变量运算, 例如 [100,100,"width/2","height*1.5^2"]
坐标变量
width
原图宽度height
原图高度DEFORM
仿射变换坐标格式为 [[x1,y1],[x2,y2],[x3,y3],[x4,y4],[x_anchor,y_anchor]]
;
分别对应图片的[[左上角],[左下角],[右下角],[右上角],[锚点]]
,四角坐标用相对于锚点的偏移量表示
旋转原点枚举 origin
DEFAULT
左上角CENTER
中心图片裁切坐标 [x1, y1, x2, y2]
, [0, 0, x2, y2]
可简写为 [x2, y2]
裁切格式枚举 cropType
NONE
不裁切PIXEL
按像素裁切PERCENT
按百分比裁切填充模式 fit
CONTAIN
缩小以适应画布, 不改变原比例COVER
裁切以适应画布, 不改变原比例FILL
拉伸, 改变原比例风格化枚举 style
MIRROR
水平镜像FLIP
上下翻转GRAY
灰度化BINARIZATION
二值化filter
通过滤镜实现头像特效, Java 与 JavaScript 版本实现有偏差, 并非完全相同
{
"filter": [
{
"type": "SWIRL",
"radius": 200,
"angle": 5.0
}
]
}
滤镜可使用数组实现动画, 例如:
```json lines { “type”: “GIF”, “background”: { “length”: 5, // GIF 长度是 5 帧 “size”: [“avatar0Width”, “avatar0Height”] // 画布的尺寸为第一个头像的尺寸 }, “avatar”: [{ “type”: “TO”, “pos”: [0, 0, “width”, “height”], // 图像占满画布 //省略… “filter”: [{ “type”: “SWIRL”, “angle”: [0, 1.2, 2.4, 3.6, 4.8] // 对应每一帧的动画 }] }] //省略… }
<details><summary>实现差异对比</summary>
![实现差异对比](https://s2.loli.net/2023/11/01/n5dZ2SyQkNFWzCu.jpg)
</details>
##### 滤镜参数
- `SWIRL` , 漩涡滤镜
| **属性** | **类型** | **注释** | **默认值** |
|------------|--------|------------------|---------|
| **radius** | 浮点数 | 涡旋半径, 值为0时表示图片半径 | `0.0` |
| **angle** | 浮点数 | 涡旋角度 | `3.0` |
| **x** | 浮点数 | 中心点X坐标百分比 | `0.5` |
| **y** | 浮点数 | 中心点Y坐标百分比 | `0.5` |
- `BULGE` , 膨胀收缩滤镜
| **属性** | **类型** | **注释** | **默认值** |
|--------------|--------|---------------------------|---------|
| **radius** | 浮点数 | 膨胀半径, 值为0时表示图片半径 | `0.0` |
| **strength** | 浮点数 | 膨胀强度 `[-1, 1]`, 负数时产生收缩效果 | `0.5` |
| **x** | 浮点数 | 中心点X坐标百分比 | `0.5` |
| **y** | 浮点数 | 中心点Y坐标百分比 | `0.5` |
- `SWIM` , 水下滤镜
| **属性** | **类型** | **注释** | **默认值** |
|----------------|--------|--------|---------|
| **scale** | 浮点数 | 缩放系数 | `32.0` |
| **stretch** | 浮点数 | 拉伸系数 | `1.0` |
| **angle** | 浮点数 | 角度 | `0.0` |
| **amount** | 浮点数 | 强度 | `10.0` |
| **turbulence** | 浮点数 | 湍流系数 | `1.0` |
| **time** | 浮点数 | 时间 | `0.0` |
- `BLUR` , 模糊滤镜
| **属性** | **类型** | **注释** | **默认值** |
|------------|--------|--------|---------|
| **radius** | 浮点数 | 模糊半径 | `10.0` |
- `CONTRAST` , 亮度对比度滤镜
| **属性** | **类型** | **注释** | **默认值** |
|----------------|--------|--------|---------|
| **brightness** | 浮点数 | 亮度 | `0.0` |
| **contrast** | 浮点数 | 对比度 | `0.0` |
- `HSB` , 相对HSB (色相, 饱和度, 亮度) 滤镜
| **属性** | **类型** | **注释** | **默认值** |
|----------------|--------|--------|---------|
| **hue** | 浮点数 | 色相 | `0.0` |
| **saturation** | 浮点数 | 饱和度 | `0.0` |
| **brightness** | 浮点数 | 亮度 | `0.0` |
- `HALFTONE` , 半色调滤镜 (模仿彩色印刷的CMYK色彩)
| **属性** | **类型** | **注释** | **默认值** |
|------------|--------|-----------|---------|
| **angle** | 浮点数 | 角度 | `0.0` |
| **radius** | 浮点数 | 半径 | `4.0` |
| **x** | 浮点数 | 中心点X坐标百分比 | `0.5` |
| **y** | 浮点数 | 中心点Y坐标百分比 | `0.5` |
- `DOT_SCREEN` , 单色点阵滤镜 (模仿黑白印刷品)
| **属性** | **类型** | **注释** | **默认值** |
|------------|--------|-----------|---------|
| **angle** | 浮点数 | 角度 | `0.0` |
| **radius** | 浮点数 | 半径 | `4.0` |
| **x** | 浮点数 | 中心点X坐标百分比 | `0.5` |
| **y** | 浮点数 | 中心点Y坐标百分比 | `0.5` |
- `NOISE` , 噪声滤镜
| **属性** | **类型** | **注释** | **默认值** |
|------------|--------|--------|---------|
| **amount** | 浮点数 | 噪声强度 | `0.25` |
- `DENOISE` , 降噪滤镜
| **属性** | **类型** | **注释** | **默认值** |
|--------------|--------|--------|---------|
| **exponent** | 浮点数 | 指数 | `20.0` |
- `OIL` , 油画滤镜
| **属性** | **类型** | **注释** | **默认值** |
|------------|--------|--------|---------|
| **skip** | 浮点数 | 迭代跨越系数 | `4.0` |
| **range** | 浮点数 | 迭代范围 | `12.0` |
| **levels** | 浮点数 | 等级 | `8.0` |
### 文字
如果你想在图片上添加文字,可以编辑 `text`
```json lines
{
"text": [
{
"text": "Petpet!", // 文字内容
"color": "#66ccff", // 颜色, 默认为#191919
"pos": [100, 100], // 坐标
"size": 24 // 字号, 默认为12
},
{
"text": "发送者: $from, 接收者: $to", // 支持变量
"pos": [20, 150], // 坐标
"position": ["CENTER", "BOTTOM"], //坐标计算基准([x, y])
"font": "宋体", // 字体, 默认为黑体
"strokeColor": "#ffffff", // 描边颜色
"strokeSize": 2 // 描边宽度
},
{
"text": "$txt1[我]超市$txt2[你]!", // 支持关键词变量
"pos": [0,200,300], // 第三个值为文本最大宽度
"align": "CENTER", // 对齐方式, 默认为LEFT
"wrap": "ZOOM", // 显示设置, 默认为NONE
"style": "BOLD" // 字体样式, 默认为PLAIN
}
]
}
属性 | 类型 | 注释 | 默认值 |
---|---|---|---|
text | 字符串 | 文本内容 | 必须 |
pos | 数组 | 文本的坐标信息 | 必须 |
color | 字符串 | 文本颜色 | #191919 |
size | 整数 | 文本字号 | 12 |
angle | 整数 | 头像的初始角度 | 0 |
origin | 旋转原点枚举 | 文字的旋转原点 | DEFAULT |
position | 数组 | 文本坐标计算基准 | [LEFT , TOP ] |
font | 字符串 | 字体 | 黑体 |
strokeColor | 字符串 | 文本描边颜色 | null |
strokeSize | 整数 | 文本描边宽度 | 0 |
align | 字符串 | 文本对齐方式 | LEFT |
wrap | 字符串 | 文本显示设置 | NONE |
style | 字符串 | 字体样式 | PLAIN |
greedy | 布尔值 | 是否贪婪匹配多余的关键词 | false |
变量
$from
: 发送者, 会被替换为发送者昵称$to
: 接收者, 被戳或At的对象$group
: 群名称$txt(i)[(xxx)]
: 文本变量, 可用于生成meme图, i为关键词索引, xxx为默认值; 例: $txt1[我]超市$txt2[你]
指令为 pet [key] 我 你
font
在data/fonts
目录下的字体文件会注册到环境中
align
LEFT
: 左对齐, 文本基线是标准的字母基线RIGHT
: 右对齐, 文本基线是标准的字母基线CENTER
: 居中对齐, 文本基线在文本块的中间wrap
NONE
: 不换行BREAK
: 自动换行, 超过最大宽度的文本会显示在下一行ZOOM
: 自动缩放, 缩放字体大小以填充最大宽度使用BREAK
或ZOOM
时, maxWidth
默认为 200
style
PLAIN
: 默认BOLD
: 粗体ITALIC
: 斜体BOLD_ITALIC
: 粗体与斜体position
LEFT
: 左定位(默认)RIGHT
: 右定位TOP
: 上定位(默认)BOTTOM
: 下定位CENTER
: 居中定位background
程序支持动态创建画布
```json lines { “background”: { “size”: [“avatar0Width*2”,”avatar0Height”], //支持变量运算 “color”: “#f0f0f0” } }
**坐标变量**
- `avatar(i)Width` `i`号头像(`i`为定义头像时的顺序, 从`0`开始)处理后的宽度
- `avatar(i)Height` `i`号头像处理后的高度
- `text(i)Width` `i`号文本渲染后的宽度
- `text(i)Height` `i`号文本渲染后的高度
### MessageHook
本特性仅适用于 **Mirai 插件**, 消息注入, 插件会检查将要发送的消息 解析后注入图片, 可配合各类消息回复插件使用
`<pet></pet>` 标签中的`JSON`会被解析, 请求格式参考 [`WebServer.POST`](#post)
用例:
```text
这段文字之后的标签会变成一张图片发送<pet>{
"key": "petpet",
"to": {
"qq": 2544193782
},
"textList": [
"text1"
]
}</pet>消息的顺序会被正确处理, 支持多张图片
不同于 POST
请求格式, 你可以用 "qq"
令程序自动获取头像和昵称, 也可以自定义"name"
"avatar"
(更推荐自定义的做法, 程序可能在某些情况下无法推断出正确的"name"
)
被
"hidden": true
隐藏的模板会正常调用此功能默认禁用, 需在配置文件中启用
messageHook: true
程序可作为http服务器 / API单独运行, 被其它项目/语言使用
java -jar petpet.jar
启动时会生成 config.json
:
```json lines
{
“port”: 2333, // 监听端口
“webServerThreadPoolSize”: 10, // HTTP服务器线程池容量
“dataPath”: “data/xmmt.dituon.petpet”, // PetData路径
“preview”: false, // 启用动态预览 (启动时生成所有模板预览)
“antialias”: true, // 启用抗锯齿, 详见上文
“resampling”: true, // 启用重采样, 详见上文
“gifMaxSize”: [200, 200, 32], // GIF缩放阈值, 详见上文
“gifEncoder”: “ANIMATED_LIB”, // GIF编码器, 详见上文
“gifQuality”: 5, // GIF质量, 详见上文
“threadPoolSize”: 0, // GIF编码器线程池容量, 详见上文
“headless”: true // 使用headless模式
}
**程序使用`com.sun.net.httpserver`实现`http服务器`**
### `PetServer API`
访问 `127.0.0.1:2333/petpet` 以获取 `PetDataList`
### `GET`
使用 `GET` 传递参数, 例如 `127.0.0.1:2333/petpet?key=petpet&toAvatar=$avatarUrl`
`127.0.0.1:2333/petpet?key=osu&textList=hso!`
**结构**
<details>
<summary>展开/收起</summary>
- `key` (str): 对应`PetData`,例如`kiss` `rub`
- `fromAvatar` `toAvatar` `groupAvatar` `botAvatar` (url): 头像URL地址, `encodeURIComponent(rawUrl)`
- `randomAvatarList` (url[]): 随机头像列表, 使用`,`分割多个url
- `fromName` `toName` `groupName` (str): 昵称, 有默认值
- `textList` (str): 根据空格分割此字符串, 作为额外数据
</details>
### `POST`
使用 `POST` 传递参数, 例如 `127.0.0.1:2333/petpet`
```json
{
"key": "petpet",
"to": {
"name":"d2n",
"avatar":"https://q1.qlogo.cn/g?b=qq&nk=2544193782&s=640"
},
"randomAvatarList": [
"url"
],
"textList": [
"text"
]
}
其中, key
为必须项, 其它可以省略
form-data
可直接将图片二进制文件上传至服务器进行处理
类似于 GET
数据结构, 使用 multipart/form-data
可参考
example-script
中的代码实现请求
语言 | 示例 |
---|---|
javascript |
post.js get.js |
python |
example.py |
php |
example.php |
启动WebServer
后即可使用WebUI
启用preview
配置项以加载WebUI
模板预览 (可选, 默认关闭)
server-config.json
preview: true
检查 Mirai 登录协议, 仅
ANDORID_PHONE
可以收到 戳一戳 消息
Mirai 2.11.0
提供了新的JavaAutoSaveConfig
方法, 请更新Mirai版本至2.11.0
(不是2.11.0-M1
), 旧版本不支持自定义配置项
Could not initialize class java.awt.Toolkit
?
对于无输入输出设备的服务器 需要启用
headless
Github 在中国境内被防火墙拦截
修改
Petpet.yml
中repositoryUrl
的值为`
'https://ghp.ci/https://raw.githubusercontent.com/Dituon/petpet/main/'
(由ghproxy.com
提供的镜像)
data.json
出错?
自动更新时网络出错导致, 删除出错的文件 重新获取即可
若此文档无法解决您的问题, 欢迎提交
issue
程序使用底层 java.awt
类合成图片, 渲染时使用多线程, 静态图片渲染时间一般不会超过1ms
对GIF编码器的分析, 转换, 映射部分进行多线程优化, 速度极快
Android JVM 没有实现java.awt
, 推荐使用JDK 11+
版本
如果你想分享自定义模板, 欢迎Pr
在别的项目二次开发: mirai-simplepetpet-plugin
如果此程序和您预期的一样正常工作,请给我一个 star
欢迎提交任何请求
交流群: 961494251