新增图片操作分类,支持调整大小、旋转、添加水印、裁剪、格式转换、图片信息、png2ico

This commit is contained in:
fofolee 2025-01-10 01:37:38 +08:00
parent f9a1aefff6
commit 4c279e8309
15 changed files with 1195 additions and 5 deletions

View File

@ -8,6 +8,7 @@ const quickcomposer = {
math: require("./quickcomposer/math"),
ui: require("./quickcomposer/ui"),
audio: require("./quickcomposer/audio"),
image: require("./quickcomposer/image"),
};
module.exports = quickcomposer;

View File

@ -0,0 +1,492 @@
const fs = require("fs");
const exif = require("exif-reader");
/**
* 加载图片
* @param {string} file 图片文件路径
* @returns {Promise<HTMLImageElement>} 图片元素
*/
async function loadImage(file) {
if (!fs.existsSync(file)) {
throw new Error(`图片文件不存在: ${file}`);
}
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = `file://${file}`;
});
}
/**
* 读取EXIF信息
* @param {string} file 图片文件路径
* @returns {Promise<Object>} EXIF信息
*/
async function readExif(file) {
try {
const buffer = fs.readFileSync(file);
// 检查是否是JPEG文件
if (buffer[0] !== 0xff || buffer[1] !== 0xd8) {
console.warn("不是JPEG文件");
return null;
}
// 查找APP1段
let offset = 2;
while (offset < buffer.length) {
if (buffer[offset] === 0xff && buffer[offset + 1] === 0xe1) {
// 获取段长度
const segmentLength = buffer[offset + 2] * 256 + buffer[offset + 3];
// 提取EXIF数据
const exifData = buffer.slice(offset + 4, offset + 2 + segmentLength);
// 检查是否是EXIF数据
if (exifData.slice(0, 6).toString() === "Exif\0\0") {
try {
// 提取实际的EXIF数据跳过Exif\0\0
const metadata = exif(exifData.slice(6));
return metadata;
} catch (e) {
console.warn("解析EXIF数据失败:", e);
return null;
}
}
}
offset++;
}
console.warn("未找到EXIF数据");
return null;
} catch (error) {
console.warn("读取EXIF信息失败:", error);
return null;
}
}
/**
* 获取图片颜色信息
* @param {HTMLImageElement} img 图片元素
* @returns {Object} 颜色信息
*/
function getColorInfo(img) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
let r = 0,
g = 0,
b = 0,
a = 0;
// 计算平均颜色
for (let i = 0; i < data.length; i += 4) {
r += data[i];
g += data[i + 1];
b += data[i + 2];
a += data[i + 3];
}
const pixels = data.length / 4;
return {
averageColor: {
r: Math.round(r / pixels),
g: Math.round(g / pixels),
b: Math.round(b / pixels),
a: Math.round(a / pixels) / 255,
},
isTransparent: Math.round(a / pixels) < 255,
hasAlphaChannel: true, // Canvas总是包含alpha通道
};
}
/**
* 分析图片信息
* @param {string} file 图片文件路径
* @returns {Promise<Object>} 图片信息
*/
async function analyze(file) {
const img = await loadImage(file);
const stats = fs.statSync(file);
const ext = file.split(".").pop().toLowerCase();
const exifData = await readExif(file);
const colorInfo = getColorInfo(img);
// 计算图片大小的可读格式
const formatSize = (bytes) => {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
return (bytes / (1024 * 1024)).toFixed(2) + " MB";
};
// 获取图片类型
const getImageType = (ext) => {
const types = {
jpg: "JPEG图片",
jpeg: "JPEG图片",
png: "PNG图片",
webp: "WebP图片",
gif: "GIF图片",
bmp: "BMP图片",
};
return types[ext] || "未知类型";
};
// 格式化EXIF信息
const formatExif = (exif) => {
if (!exif) return null;
return {
相机信息: {
制造商: exif.Image?.Make,
型号: exif.Image?.Model,
软件: exif.Image?.Software,
方向: exif.Image?.Orientation,
分辨率: {
X: exif.Image?.XResolution,
Y: exif.Image?.YResolution,
单位: exif.Image?.ResolutionUnit,
},
},
拍摄信息: {
拍摄时间: exif.Photo?.DateTimeOriginal,
曝光时间: exif.Photo?.ExposureTime,
光圈值: exif.Photo?.FNumber,
ISO感光度: exif.Photo?.ISOSpeedRatings,
焦距: exif.Photo?.FocalLength,
焦距35mm: exif.Photo?.FocalLengthIn35mmFilm,
闪光灯: exif.Photo?.Flash,
白平衡: exif.Photo?.WhiteBalance,
曝光程序: exif.Photo?.ExposureProgram,
曝光补偿: exif.Photo?.ExposureBiasValue,
测光模式: exif.Photo?.MeteringMode,
亮度值: exif.Photo?.BrightnessValue,
场景类型: exif.Photo?.SceneCaptureType,
镜头信息: {
制造商: exif.Photo?.LensMake,
型号: exif.Photo?.LensModel,
},
},
GPS信息: exif.GPSInfo
? {
纬度: {
参考: exif.GPSInfo.GPSLatitudeRef,
: exif.GPSInfo.GPSLatitude,
},
经度: {
参考: exif.GPSInfo.GPSLongitudeRef,
: exif.GPSInfo.GPSLongitude,
},
海拔: {
参考: exif.GPSInfo.GPSAltitudeRef,
: exif.GPSInfo.GPSAltitude,
},
时间戳: exif.GPSInfo.GPSTimeStamp,
日期戳: exif.GPSInfo.GPSDateStamp,
方向: {
参考: exif.GPSInfo.GPSImgDirectionRef,
: exif.GPSInfo.GPSImgDirection,
},
}
: null,
缩略图信息: exif.Thumbnail
? {
宽度: exif.Thumbnail.ImageWidth,
高度: exif.Thumbnail.ImageLength,
压缩: exif.Thumbnail.Compression,
方向: exif.Thumbnail.Orientation,
分辨率: {
X: exif.Thumbnail.XResolution,
Y: exif.Thumbnail.YResolution,
单位: exif.Thumbnail.ResolutionUnit,
},
}
: null,
其他信息: {
描述: exif.Image?.ImageDescription,
作者: exif.Image?.Artist,
版权: exif.Image?.Copyright,
创建时间: exif.Photo?.DateTimeDigitized,
修改时间: exif.Image?.DateTime,
色彩空间: exif.Photo?.ColorSpace,
图像处理: exif.Photo?.CustomRendered,
对比度: exif.Photo?.Contrast,
饱和度: exif.Photo?.Saturation,
锐度: exif.Photo?.Sharpness,
},
};
};
return {
// 基本信息
width: img.width, // 宽度(像素)
height: img.height, // 高度(像素)
aspectRatio: img.width / img.height, // 宽高比
resolution: img.width * img.height, // 分辨率(总像素)
// 文件信息
type: getImageType(ext), // 图片类型
format: ext.toUpperCase(), // 文件格式
size: formatSize(stats.size), // 文件大小
bytes: stats.size, // 字节数
// 时间信息
createTime: stats.birthtime, // 创建时间
modifyTime: stats.mtime, // 修改时间
accessTime: stats.atime, // 访问时间
// 路径信息
path: file, // 完整路径
filename: file.split("/").pop(), // 文件名
// 颜色信息
colorInfo: {
averageColor: colorInfo.averageColor, // 平均颜色
isTransparent: colorInfo.isTransparent, // 是否包含透明
hasAlphaChannel: colorInfo.hasAlphaChannel, // 是否有Alpha通道
},
// EXIF信息
exif: formatExif(exifData),
rawExif: exifData, // 原始EXIF数据
// 其他信息
naturalWidth: img.naturalWidth, // 原始宽度
naturalHeight: img.naturalHeight, // 原始高度
};
}
/**
* 调整图片大小
* @param {string} inputFile 输入文件路径
* @param {string} outputFile 输出文件路径
* @param {number} width 宽度
* @param {number} height 高度
* @param {boolean} keepAspectRatio 保持宽高比
* @param {number} quality 图片质量 (0-1)
*/
async function resize(
inputFile,
outputFile,
width = null,
height = null,
keepAspectRatio = true,
quality = 0.92
) {
const img = await loadImage(inputFile);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (keepAspectRatio) {
if (width && !height) {
height = width * (img.height / img.width);
} else if (height && !width) {
width = height * (img.width / img.height);
}
}
canvas.width = width || img.width;
canvas.height = height || img.height;
// 绘制调整大小后的图片
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// 获取图片数据
const format = outputFile.split(".").pop() || "jpeg";
const dataURL = canvas.toDataURL(`image/${format}`, quality);
// 保存文件
const base64Data = dataURL.replace(/^data:image\/\w+;base64,/, "");
fs.writeFileSync(outputFile, Buffer.from(base64Data, "base64"));
}
/**
* 旋转图片
* @param {string} inputFile 输入文件路径
* @param {string} outputFile 输出文件路径
* @param {number} angle 旋转角度
* @param {number} quality 图片质量
*/
async function rotate(inputFile, outputFile, angle = 90, quality = 0.92) {
const img = await loadImage(inputFile);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const angleRad = (angle * Math.PI) / 180;
const sin = Math.abs(Math.sin(angleRad));
const cos = Math.abs(Math.cos(angleRad));
// 计算旋转后的画布大小
canvas.width = img.width * cos + img.height * sin;
canvas.height = img.width * sin + img.height * cos;
// 移动到画布中心并旋转
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(angleRad);
ctx.drawImage(img, -img.width / 2, -img.height / 2);
// 保存文件
const format = outputFile.split(".").pop() || "jpeg";
const dataURL = canvas.toDataURL(`image/${format}`, quality);
const base64Data = dataURL.replace(/^data:image\/\w+;base64,/, "");
fs.writeFileSync(outputFile, Buffer.from(base64Data, "base64"));
}
/**
* 裁剪图片
* @param {string} inputFile 输入文件路径
* @param {string} outputFile 输出文件路径
* @param {number} x 起始X坐标
* @param {number} y 起始Y坐标
* @param {number} width 裁剪宽度
* @param {number} height 裁剪高度
* @param {number} quality 图片质量
*/
async function crop(
inputFile,
outputFile,
x = 0,
y = 0,
width = null,
height = null,
quality = 0.92
) {
const img = await loadImage(inputFile);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
width = width || img.width;
height = height || img.height;
canvas.width = width;
canvas.height = height;
// 绘制裁剪区域
ctx.drawImage(img, x, y, width, height, 0, 0, width, height);
// 保存文件
const format = outputFile.split(".").pop() || "jpeg";
const dataURL = canvas.toDataURL(`image/${format}`, quality);
const base64Data = dataURL.replace(/^data:image\/\w+;base64,/, "");
fs.writeFileSync(outputFile, Buffer.from(base64Data, "base64"));
}
/**
* 添加水印
* @param {string} inputFile 输入文件路径
* @param {string} outputFile 输出文件路径
* @param {string} text 水印文字
* @param {string} font 字体设置
* @param {string} color 文字颜色
* @param {string} position 位置(topLeft/topRight/bottomLeft/bottomRight/center)
* @param {number} margin 边距
* @param {number} opacity 不透明度
* @param {number} quality 图片质量
*/
async function watermark(
inputFile,
outputFile,
text = "水印文字",
font = "24px Arial",
color = "rgba(255, 255, 255, 0.5)",
position = "bottomRight",
margin = 20,
opacity = 0.5,
quality = 0.92
) {
const img = await loadImage(inputFile);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
// 绘制原图
ctx.drawImage(img, 0, 0);
// 设置水印样式
ctx.font = font;
ctx.fillStyle = color;
ctx.globalAlpha = opacity;
const metrics = ctx.measureText(text);
const textWidth = metrics.width;
const textHeight = parseInt(ctx.font); // 近似值
// 计算水印位置
let x, y;
switch (position) {
case "topLeft":
x = margin;
y = margin + textHeight;
break;
case "topRight":
x = canvas.width - textWidth - margin;
y = margin + textHeight;
break;
case "bottomLeft":
x = margin;
y = canvas.height - margin;
break;
case "bottomRight":
x = canvas.width - textWidth - margin;
y = canvas.height - margin;
break;
case "center":
default:
x = (canvas.width - textWidth) / 2;
y = (canvas.height + textHeight) / 2;
}
// 绘制水印
ctx.fillText(text, x, y);
// 保存文件
const format = outputFile.split(".").pop() || "jpeg";
const dataURL = canvas.toDataURL(`image/${format}`, quality);
const base64Data = dataURL.replace(/^data:image\/\w+;base64,/, "");
fs.writeFileSync(outputFile, Buffer.from(base64Data, "base64"));
}
/**
* 转换图片格式
* @param {string} inputFile 输入文件路径
* @param {string} outputFile 输出文件路径
* @param {string} format 输出格式
* @param {number} quality 图片质量
*/
async function convert(inputFile, outputFile, format = "jpeg", quality = 0.92) {
const img = await loadImage(inputFile);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
// 绘制图片
ctx.drawImage(img, 0, 0);
// 保存为新格式
const dataURL = canvas.toDataURL(`image/${format}`, quality);
const base64Data = dataURL.replace(/^data:image\/\w+;base64,/, "");
fs.writeFileSync(outputFile, Buffer.from(base64Data, "base64"));
}
module.exports = {
analyze,
resize,
rotate,
crop,
watermark,
convert,
};

View File

@ -0,0 +1,7 @@
const image = require("./image");
const png2icon = require("./png2icon");
module.exports = {
...image,
...png2icon,
};

View File

@ -0,0 +1,36 @@
const png2icons = require("png2icons");
const fs = require("fs");
const path = require("path");
const pngToIcon = (input, outputDir, type = "ico") => {
if (input instanceof Array) {
input.forEach((input) => {
pngToIcon(input, outputDir, type);
});
return;
}
let icon, outputFile, basename;
console.log(input);
if (input.startsWith("data:image/png;base64,")) {
input = Buffer.from(input.split(",")[1], "base64");
basename = new Date().getTime().toString();
} else {
basename = path.basename(input, ".png");
input = fs.readFileSync(input);
}
if (type == "ico") {
icon = png2icons.createICO(input, png2icons.BICUBIC, 0, false);
outputFile = path.join(outputDir, basename + ".ico");
} else {
icon = png2icons.createICNS(input, png2icons.BILINEAR, 0);
outputFile = path.join(outputDir, basename + ".icns");
}
if (!icon) return;
fs.writeFile(outputFile, icon, (err) => {
if (err) throw err;
});
};
module.exports = {
pngToIcon,
};

View File

@ -7,8 +7,10 @@
"dependencies": {
"axios": "^1.7.9",
"crypto-js": "^4.2.0",
"exif-reader": "^2.0.1",
"iconv-lite": "^0.6.3",
"node-forge": "^1.3.1",
"png2icons": "^2.0.1",
"ses": "^1.10.0",
"sm-crypto": "^0.3.13",
"tree-kill": "^1.2.2"
@ -64,6 +66,12 @@
"node": ">=0.4.0"
}
},
"node_modules/exif-reader": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/exif-reader/-/exif-reader-2.0.1.tgz",
"integrity": "sha512-gCQ/86RiAWSjeSlalj1G99IC6XnxbwkvB91HLqhh8somj/YBtC/2xuplvyjDjlfO7NsmYREPPElu/Syuy/H52g==",
"license": "MIT"
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
@ -145,6 +153,15 @@
"node": ">= 6.13.0"
}
},
"node_modules/png2icons": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/png2icons/-/png2icons-2.0.1.tgz",
"integrity": "sha512-GDEQJr8OG4e6JMp7mABtXFSEpgJa1CCpbQiAR+EjhkHJHnUL9zPPtbOrjsMD8gUbikgv3j7x404b0YJsV3aVFA==",
"license": "MIT",
"bin": {
"png2icons": "png2icons-cli.js"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@ -222,6 +239,11 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"exif-reader": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/exif-reader/-/exif-reader-2.0.1.tgz",
"integrity": "sha512-gCQ/86RiAWSjeSlalj1G99IC6XnxbwkvB91HLqhh8somj/YBtC/2xuplvyjDjlfO7NsmYREPPElu/Syuy/H52g=="
},
"follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
@ -268,6 +290,11 @@
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
},
"png2icons": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/png2icons/-/png2icons-2.0.1.tgz",
"integrity": "sha512-GDEQJr8OG4e6JMp7mABtXFSEpgJa1CCpbQiAR+EjhkHJHnUL9zPPtbOrjsMD8gUbikgv3j7x404b0YJsV3aVFA=="
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",

View File

@ -2,8 +2,10 @@
"dependencies": {
"axios": "^1.7.9",
"crypto-js": "^4.2.0",
"exif-reader": "^2.0.1",
"iconv-lite": "^0.6.3",
"node-forge": "^1.3.1",
"png2icons": "^2.0.1",
"ses": "^1.10.0",
"sm-crypto": "^0.3.13",
"tree-kill": "^1.2.2"

View File

@ -6,6 +6,8 @@
filled
:label="label"
:placeholder="placeholder"
:max="max"
:min="min"
class="number-input"
>
<template v-slot:prepend>
@ -20,7 +22,7 @@
icon="keyboard_arrow_up"
size="xs"
class="number-btn"
@click="updateNumber(100)"
@click="updateNumber(step)"
/>
<q-btn
flat
@ -28,7 +30,7 @@
icon="keyboard_arrow_down"
size="xs"
class="number-btn"
@click="updateNumber(-100)"
@click="updateNumber(-step)"
/>
</div>
</template>
@ -64,6 +66,18 @@ export default defineComponent({
type: String,
default: "",
},
max: {
type: Number,
default: 1000000000,
},
min: {
type: Number,
default: -1000000000,
},
step: {
type: Number,
default: 1,
},
},
emits: ["update:modelValue"],
computed: {
@ -75,14 +89,28 @@ export default defineComponent({
if (value === null || value === undefined || value === "") {
this.$emit("update:modelValue", null);
} else {
this.$emit("update:modelValue", value);
const numValue = Number(value);
if (numValue > this.max) {
this.$emit("update:modelValue", this.max);
} else if (numValue < this.min) {
this.$emit("update:modelValue", this.min);
} else {
this.$emit("update:modelValue", numValue);
}
}
},
},
},
methods: {
updateNumber(delta) {
this.$emit("update:modelValue", (this.localValue || 0) + delta);
const newValue = (this.localValue || 0) + delta;
if (newValue > this.max) {
this.$emit("update:modelValue", this.max);
} else if (newValue < this.min) {
this.$emit("update:modelValue", this.min);
} else {
this.$emit("update:modelValue", newValue);
}
},
},
});

View File

@ -63,6 +63,8 @@ export const dataCommands = {
key: "start",
label: "起始位置",
type: "numInput",
min: 0,
step: 1,
icon: "first_page",
width: 3,
},
@ -71,6 +73,8 @@ export const dataCommands = {
label: "结束位置",
type: "numInput",
icon: "last_page",
min: 0,
step: 1,
width: 3,
},
],
@ -155,6 +159,7 @@ export const dataCommands = {
{
label: "起始位置",
type: "numInput",
step: 1,
icon: "first_page",
width: 4,
},
@ -162,6 +167,7 @@ export const dataCommands = {
label: "结束位置",
type: "numInput",
icon: "last_page",
step: 1,
width: 4,
},
],

View File

@ -0,0 +1,572 @@
import { newVarInputVal } from "js/composer/varInputValManager";
// 图片格式选项
const IMAGE_FORMATS = [
{ label: "JPEG", value: "jpeg" },
{ label: "PNG", value: "png" },
{ label: "WebP", value: "webp" },
];
// 水印位置选项
const WATERMARK_POSITIONS = [
{ label: "左上角", value: "topLeft" },
{ label: "右上角", value: "topRight" },
{ label: "左下角", value: "bottomLeft" },
{ label: "右下角", value: "bottomRight" },
{ label: "居中", value: "center" },
];
export const imageCommands = {
label: "图片操作",
icon: "image",
defaultOpened: false,
commands: [
{
value: "quickcomposer.image.analyze",
label: "图片信息",
desc: "分析图片基本信息",
icon: "analytics",
isAsync: true,
config: [
{
key: "file",
label: "图片文件",
type: "varInput",
icon: "image",
width: 12,
options: {
dialog: {
type: "open",
options: {
title: "选择图片",
filters: [
{
name: "图片文件",
extensions: ["jpg", "jpeg", "png", "webp"],
},
],
properties: ["openFile", "showHiddenFiles"],
},
},
},
},
],
},
{
value: "quickcomposer.image.resize",
label: "调整大小",
desc: "调整图片尺寸",
icon: "aspect_ratio",
isAsync: true,
config: [
{
key: "inputFile",
label: "输入文件",
type: "varInput",
icon: "image",
width: 12,
options: {
dialog: {
type: "open",
options: {
title: "选择图片",
filters: [
{
name: "图片文件",
extensions: ["jpg", "jpeg", "png", "webp"],
},
],
properties: ["openFile", "showHiddenFiles"],
},
},
},
},
{
key: "outputFile",
label: "输出文件",
type: "varInput",
icon: "save",
width: 12,
options: {
dialog: {
type: "save",
options: {
title: "保存图片",
filters: [
{
name: "图片文件",
extensions: ["jpg", "jpeg", "png", "webp"],
},
],
},
},
},
},
{
key: "width",
label: "宽度(像素)",
type: "numInput",
icon: "compare_arrows",
width: 6,
min: 1,
step: 10,
defaultValue: "",
},
{
key: "height",
label: "高度(像素)",
type: "numInput",
icon: "height",
width: 6,
min: 1,
step: 10,
defaultValue: "",
},
{
key: "keepAspectRatio",
label: "保持宽高比",
type: "select",
icon: "aspect_ratio",
width: 6,
defaultValue: "true",
options: [
{ label: "是", value: "true" },
{ label: "否", value: "false" },
],
},
{
key: "quality",
label: "图片质量(0-1)",
type: "numInput",
icon: "high_quality",
width: 6,
max: 1,
min: 0,
step: 0.05,
defaultValue: 0.92,
},
],
},
{
value: "quickcomposer.image.rotate",
label: "旋转图片",
desc: "旋转图片角度",
icon: "rotate_right",
isAsync: true,
config: [
{
key: "inputFile",
label: "输入文件",
type: "varInput",
icon: "image",
width: 12,
options: {
dialog: {
type: "open",
options: {
title: "选择图片",
filters: [
{
name: "图片文件",
extensions: ["jpg", "jpeg", "png", "webp"],
},
],
properties: ["openFile", "showHiddenFiles"],
},
},
},
},
{
key: "outputFile",
label: "输出文件",
type: "varInput",
icon: "save",
width: 12,
options: {
dialog: {
type: "save",
options: {
title: "保存图片",
filters: [
{
name: "图片文件",
extensions: ["jpg", "jpeg", "png", "webp"],
},
],
},
},
},
},
{
key: "angle",
label: "旋转角度",
type: "numInput",
icon: "rotate_right",
width: 6,
step: 90,
defaultValue: 90,
},
{
key: "quality",
label: "图片质量(0-1)",
type: "numInput",
icon: "high_quality",
width: 6,
max: 1,
min: 0,
step: 0.05,
defaultValue: 0.92,
},
],
},
{
value: "quickcomposer.image.crop",
label: "裁剪图片",
desc: "裁剪图片区域",
icon: "crop",
isAsync: true,
config: [
{
key: "inputFile",
label: "输入文件",
type: "varInput",
icon: "image",
width: 12,
options: {
dialog: {
type: "open",
options: {
title: "选择图片",
filters: [
{
name: "图片文件",
extensions: ["jpg", "jpeg", "png", "webp"],
},
],
properties: ["openFile", "showHiddenFiles"],
},
},
},
},
{
key: "outputFile",
label: "输出文件",
type: "varInput",
icon: "save",
width: 12,
options: {
dialog: {
type: "save",
options: {
title: "保存图片",
filters: [
{
name: "图片文件",
extensions: ["jpg", "jpeg", "png", "webp"],
},
],
},
},
},
},
{
key: "x",
label: "起始X坐标",
type: "numInput",
icon: "arrow_right",
width: 6,
min: 0,
step: 10,
defaultValue: 0,
},
{
key: "y",
label: "起始Y坐标",
type: "numInput",
icon: "arrow_downward",
width: 6,
min: 0,
step: 10,
defaultValue: 0,
},
{
key: "width",
label: "裁剪宽度",
type: "numInput",
icon: "compare_arrows",
width: 6,
min: 1,
step: 10,
defaultValue: "",
},
{
key: "height",
label: "裁剪高度",
type: "numInput",
icon: "height",
width: 6,
min: 1,
step: 10,
defaultValue: "",
},
{
key: "quality",
label: "图片质量(0-1)",
type: "numInput",
icon: "high_quality",
width: 6,
max: 1,
min: 0,
step: 0.05,
defaultValue: 0.92,
},
],
},
{
value: "quickcomposer.image.watermark",
label: "添加水印",
desc: "添加文字水印",
icon: "format_color_text",
isAsync: true,
config: [
{
key: "inputFile",
label: "输入文件",
type: "varInput",
icon: "image",
width: 12,
options: {
dialog: {
type: "open",
options: {
title: "选择图片",
filters: [
{
name: "图片文件",
extensions: ["jpg", "jpeg", "png", "webp"],
},
],
properties: ["openFile", "showHiddenFiles"],
},
},
},
},
{
key: "outputFile",
label: "输出文件",
type: "varInput",
icon: "save",
width: 12,
options: {
dialog: {
type: "save",
options: {
title: "保存图片",
filters: [
{
name: "图片文件",
extensions: ["jpg", "jpeg", "png", "webp"],
},
],
},
},
},
},
{
key: "text",
label: "水印文字",
type: "varInput",
icon: "text_fields",
width: 12,
defaultValue: newVarInputVal("var", "水印文字"),
},
{
key: "font",
label: "字体设置",
type: "varInput",
icon: "font_download",
width: 6,
defaultValue: newVarInputVal("var", "24px Arial"),
},
{
key: "color",
label: "文字颜色",
type: "varInput",
icon: "format_color_text",
width: 6,
defaultValue: newVarInputVal("var", "rgba(255, 255, 255, 0.5)"),
},
{
key: "position",
label: "位置",
type: "select",
icon: "place",
width: 6,
defaultValue: "bottomRight",
options: WATERMARK_POSITIONS,
},
{
key: "margin",
label: "边距",
type: "numInput",
icon: "space_bar",
min: 0,
step: 10,
width: 6,
defaultValue: 20,
},
{
key: "opacity",
label: "不透明度(0-1)",
type: "numInput",
icon: "opacity",
max: 1,
min: 0,
step: 0.05,
width: 6,
defaultValue: 0.5,
},
{
key: "quality",
label: "图片质量(0-1)",
type: "numInput",
icon: "high_quality",
max: 1,
min: 0,
step: 0.05,
width: 6,
defaultValue: 0.92,
},
],
},
{
value: "quickcomposer.image.convert",
label: "格式转换",
desc: "转换图片格式",
icon: "transform",
isAsync: true,
config: [
{
key: "inputFile",
label: "输入文件",
type: "varInput",
icon: "image",
width: 12,
options: {
dialog: {
type: "open",
options: {
title: "选择图片",
filters: [
{
name: "图片文件",
extensions: ["jpg", "jpeg", "png", "webp"],
},
],
properties: ["openFile", "showHiddenFiles"],
},
},
},
},
{
key: "outputFile",
label: "输出文件",
type: "varInput",
icon: "save",
width: 12,
options: {
dialog: {
type: "save",
options: {
title: "保存图片",
filters: [
{
name: "图片文件",
extensions: ["jpg", "jpeg", "png", "webp"],
},
],
},
},
},
},
{
key: "format",
label: "输出格式",
type: "select",
icon: "transform",
width: 6,
defaultValue: "jpeg",
options: IMAGE_FORMATS,
},
{
key: "quality",
label: "图片质量(0-1)",
type: "numInput",
icon: "high_quality",
width: 6,
max: 1,
min: 0,
step: 0.05,
defaultValue: 0.92,
},
],
},
{
value: "quickcomposer.image.pngToIcon",
label: "PNG转图标",
desc: "将PNG图片转换为图标",
icon: "transform",
config: [
{
key: "inputFile",
label: "PNG路径/Base64",
type: "varInput",
icon: "image",
width: 12,
options: {
dialog: {
type: "open",
options: {
title: "选择图片",
filters: [
{
name: "图片文件",
extensions: ["png"],
},
],
properties: ["openFile", "multiSelections"],
},
},
},
},
{
key: "outputDir",
label: "输出目录",
type: "varInput",
icon: "save",
width: 9,
defaultValue: newVarInputVal("str", window.utools.getPath("desktop")),
options: {
dialog: {
type: "save",
options: {
title: "选择输出目录",
defaultPath: ".",
properties: ["openDirectory"],
},
},
},
},
{
key: "type",
label: "输出格式",
type: "select",
icon: "transform",
width: 3,
defaultValue: "ico",
options: ["ico", "icns"],
},
],
},
],
};

View File

@ -13,13 +13,14 @@ import { userdataCommands } from "./userdataCommands";
import { utoolsCommands } from "./utoolsCommand";
import { screenCommands } from "./screenCommands";
import { audioCommands } from "./audioCommands";
console.log(audioCommands);
import { imageCommands } from "./imageCommands";
export const commandCategories = [
fileCommands,
networkCommands,
systemCommands,
audioCommands,
imageCommands,
notifyCommands,
utoolsCommands,
dataCommands,

View File

@ -11,6 +11,8 @@ export const otherCommands = {
key: "ms",
label: "延迟的毫秒数",
type: "numInput",
min: 0,
step: 100,
icon: "schedule",
defaultValue: 500,
},

View File

@ -121,12 +121,16 @@ export const simulateCommands = {
label: "X坐标留空则原地点击",
icon: "drag_handle",
type: "numInput",
min: 0,
step: 10,
width: 6,
},
{
label: "Y坐标留空则原地点击",
icon: "drag_handle",
type: "numInput",
min: 0,
step: 10,
width: 6,
},
],
@ -162,6 +166,8 @@ export const simulateCommands = {
icon: "drag_handle",
defaultValue: 0,
type: "numInput",
min: 0,
step: 10,
width: 6,
},
{
@ -169,6 +175,8 @@ export const simulateCommands = {
icon: "drag_handle",
defaultValue: 0,
type: "numInput",
min: 0,
step: 10,
width: 6,
},
],

View File

@ -370,6 +370,8 @@ export const systemCommands = {
{
label: "进程ID",
type: "numInput",
min: 0,
step: 100,
icon: "developer_board",
width: 7,
},

View File

@ -118,6 +118,8 @@ export const uiCommands = {
{
label: "显示时间(ms)",
type: "numInput",
min: 0,
step: 100,
width: 6,
placeholder: "0为手动关闭留空按文本长度调整",
},
@ -152,6 +154,8 @@ export const uiCommands = {
{
label: "宽度",
type: "numInput",
min: 0,
step: 100,
defaultValue: 450,
width: 3,
placeholder: "对话框宽度",

View File

@ -26,6 +26,8 @@ export const utoolsCommands = {
key: "height",
label: "高度",
type: "numInput",
min: 0,
step: 100,
icon: "straighten",
width: 12,
},