2024-08-03 12:46:12 +08:00
|
|
|
|
import path from 'path'
|
|
|
|
|
|
import sharp from 'sharp'
|
2024-09-12 14:13:09 +08:00
|
|
|
|
import { CheckFileOrDirExist, CheckFolderExistsOrCreate } from './file'
|
2024-08-03 12:46:12 +08:00
|
|
|
|
import fs from 'fs'
|
|
|
|
|
|
import https from 'https'
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将指定的图片的尺寸修改,返回修改后的图片数据(base64或buffer)
|
|
|
|
|
|
* @param {*} image_path
|
|
|
|
|
|
* @param {*} width
|
|
|
|
|
|
* @param {*} height
|
|
|
|
|
|
* @param {*} type
|
|
|
|
|
|
* @returns 返回修改后的图片数据(base64或buffer)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function ResizeImage(image_path: string, width: number | sharp.ResizeOptions, height: number, type: string) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 检查 type 参数
|
|
|
|
|
|
if (type !== 'base64' && type !== 'buffer') {
|
|
|
|
|
|
throw new Error('type 参数必须是 "base64" 或 "buffer"')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是不是图片文件
|
|
|
|
|
|
if (!image_path.match(/\.(jpg|jpeg|png)$/)) {
|
|
|
|
|
|
throw new Error('输入的文件地址不是图片文件地址')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断文件是否存在
|
|
|
|
|
|
if (!(await CheckFileOrDirExist(image_path))) {
|
|
|
|
|
|
throw new Error('文件不存在')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 修改图片尺寸
|
|
|
|
|
|
const image = sharp(image_path)
|
|
|
|
|
|
image.resize(width, height)
|
|
|
|
|
|
let data = await image.toBuffer()
|
|
|
|
|
|
if (type === 'base64') {
|
|
|
|
|
|
return data.toString('base64')
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return data
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取指定图片文件的宽高
|
|
|
|
|
|
* @param {*} image_path 图片文件的路径
|
|
|
|
|
|
* @returns 返回以一个对象,包含width和height属性
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
export async function GetImageSize(image_path: string) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 判断文件是否存在
|
|
|
|
|
|
if (!(await CheckFileOrDirExist(image_path))) {
|
|
|
|
|
|
throw new Error('文件不存在')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是不是图片文件
|
|
|
|
|
|
if (!image_path.match(/\.(jpg|jpeg|png)$/)) {
|
|
|
|
|
|
throw new Error('输入的文件地址不是图片文件地址')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取图片的宽高
|
|
|
|
|
|
const metadata = await sharp(image_path).metadata()
|
|
|
|
|
|
return {
|
|
|
|
|
|
width: metadata.width,
|
|
|
|
|
|
height: metadata.height
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据文件扩展名获取MIME类型
|
|
|
|
|
|
* @param filePath 文件路径
|
|
|
|
|
|
* @returns MIME类型字符串
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function GetMimeType(filePath: string): string {
|
|
|
|
|
|
const extension = path.extname(filePath).toLowerCase();
|
|
|
|
|
|
const mimeTypes: { [key: string]: string } = {
|
|
|
|
|
|
'.jpg': 'image/jpeg',
|
|
|
|
|
|
'.jpeg': 'image/jpeg',
|
|
|
|
|
|
'.png': 'image/png',
|
|
|
|
|
|
'.gif': 'image/gif',
|
|
|
|
|
|
'.webp': 'image/webp',
|
|
|
|
|
|
// 添加更多文件类型和对应的MIME类型
|
|
|
|
|
|
};
|
|
|
|
|
|
return mimeTypes[extension] || 'application/octet-stream';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将本地文件地址或网络图片地址转换为包含MIME类型的base64字符串
|
|
|
|
|
|
* @param url 本地文件路径或网络图片URL
|
|
|
|
|
|
* @returns Promise<string> 返回一个Promise,解析为包含MIME类型的base64字符串
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function GetImageBase64(url: string): Promise<string> {
|
|
|
|
|
|
if (!url) {
|
|
|
|
|
|
return Promise.reject('URL不能为空');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
https.get(url, (response) => {
|
|
|
|
|
|
const mimeType = response.headers['content-type'] || 'application/octet-stream';
|
|
|
|
|
|
const data: any[] = [];
|
|
|
|
|
|
response.on('data', (chunk) => data.push(chunk));
|
|
|
|
|
|
response.on('end', () => {
|
|
|
|
|
|
const buffer = Buffer.concat(data);
|
|
|
|
|
|
const base64Data = `data:${mimeType};base64,${buffer.toString('base64')}`;
|
|
|
|
|
|
resolve(base64Data);
|
|
|
|
|
|
});
|
|
|
|
|
|
}).on('error', (err) => reject(err));
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
fs.readFile(url, (err, data) => {
|
|
|
|
|
|
if (err) {
|
|
|
|
|
|
reject(err);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const mimeType = GetMimeType(url);
|
|
|
|
|
|
const base64Data = `data:${mimeType};base64,${data.toString('base64')}`;
|
|
|
|
|
|
resolve(base64Data);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 压缩图片到指定的大小
|
2024-09-04 19:49:20 +08:00
|
|
|
|
* @param base64 图片的Blob对象
|
2024-08-03 12:46:12 +08:00
|
|
|
|
* @param maxSizeInBytes 最大文件大小,单位字节
|
|
|
|
|
|
* @returns 返回一个Promise,解析为压缩后的Blob对象
|
|
|
|
|
|
*/
|
2024-09-04 19:49:20 +08:00
|
|
|
|
export async function CompressImageToSize(filePath: string, maxSizeInBytes: number): Promise<Buffer> {
|
|
|
|
|
|
let quality = 100; // 初始质量设置
|
|
|
|
|
|
let outputBuffer;
|
|
|
|
|
|
|
|
|
|
|
|
const image = sharp(filePath);
|
|
|
|
|
|
// 输出图片的大小
|
|
|
|
|
|
const metadata = await image.metadata();
|
|
|
|
|
|
|
|
|
|
|
|
// 迭代压缩过程
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
outputBuffer = await image.jpeg({ quality }).toBuffer();
|
|
|
|
|
|
if (outputBuffer.length <= maxSizeInBytes || quality === 20) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
quality -= 5; // 每次迭代降低质量
|
2024-08-03 12:46:12 +08:00
|
|
|
|
}
|
2024-09-04 19:49:20 +08:00
|
|
|
|
|
|
|
|
|
|
return outputBuffer;
|
2024-08-03 12:46:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成图片蒙板
|
|
|
|
|
|
* 将选中的区域涂白,其他区域涂黑(这个颜色可以变)
|
|
|
|
|
|
* @param inputPath 输入的文件路径
|
|
|
|
|
|
* @param outputPath 输出的文件路径
|
2024-08-08 16:24:47 +08:00
|
|
|
|
* @param regions 范围对象,包含x, y, width, height属性
|
|
|
|
|
|
* @param markColor 标记颜色,默认为白色 { r: 255, g: 255, b: 255 }
|
|
|
|
|
|
* @param backColor 背景颜色,默认为黑色 { r: 0, g: 0, b: 0 }
|
2024-08-03 12:46:12 +08:00
|
|
|
|
*/
|
|
|
|
|
|
export async function ProcessImage(inputPath: string,
|
|
|
|
|
|
outputPath: string,
|
2024-08-08 16:24:47 +08:00
|
|
|
|
regions: { width: any; height: any; x: any; y: any, imageWidth?: any, imageHeight?: any }[],
|
2024-08-03 12:46:12 +08:00
|
|
|
|
markColor: { r: number; g: number; b: number } = { r: 255, g: 255, b: 255 },
|
|
|
|
|
|
backColor: { r: number; g: number; b: number } = { r: 0, g: 0, b: 0 },
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 读取图片并进行处理
|
|
|
|
|
|
const image = sharp(inputPath);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取图片的元数据
|
|
|
|
|
|
const { width, height } = await image.metadata();
|
|
|
|
|
|
|
2024-08-08 16:24:47 +08:00
|
|
|
|
// 创建一个新的黑色图片,背景为白色
|
2024-08-03 12:46:12 +08:00
|
|
|
|
const whiteBackground = await sharp({
|
|
|
|
|
|
create: {
|
|
|
|
|
|
width,
|
|
|
|
|
|
height,
|
|
|
|
|
|
channels: 3, // RGB channels
|
|
|
|
|
|
background: backColor // White color
|
|
|
|
|
|
}
|
|
|
|
|
|
}).png().toBuffer();
|
|
|
|
|
|
|
2024-08-08 16:24:47 +08:00
|
|
|
|
// 创建多个白色的矩形,并进行合成
|
|
|
|
|
|
const composites = await Promise.all(regions.map(async (region) => {
|
|
|
|
|
|
|
|
|
|
|
|
let rateW = undefined;
|
|
|
|
|
|
let rateY = undefined;
|
|
|
|
|
|
let rate = undefined;
|
|
|
|
|
|
if (region.imageWidth != null && region.imageHeight != null) {
|
|
|
|
|
|
rateY = height / region.imageHeight;
|
|
|
|
|
|
rateW = width / region.imageWidth;
|
|
|
|
|
|
rate = rateY;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (rate == null) {
|
|
|
|
|
|
rate = 1;
|
2024-08-03 12:46:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-08 16:24:47 +08:00
|
|
|
|
const regionBuffer = await sharp({
|
|
|
|
|
|
create: {
|
|
|
|
|
|
width: Math.ceil(region.width * rate),
|
|
|
|
|
|
height: Math.ceil(region.height * rate),
|
|
|
|
|
|
channels: 3, // RGB channels
|
|
|
|
|
|
background: markColor // 标记颜色
|
2024-08-03 12:46:12 +08:00
|
|
|
|
}
|
2024-08-08 16:24:47 +08:00
|
|
|
|
}).png().toBuffer();
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
input: regionBuffer,
|
|
|
|
|
|
left: Math.ceil(region.x * rate),
|
|
|
|
|
|
top: Math.ceil(region.y * rate),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
}));
|
2024-08-03 12:46:12 +08:00
|
|
|
|
|
2024-08-08 16:24:47 +08:00
|
|
|
|
// 在背景上叠加所有的矩形区域
|
|
|
|
|
|
await sharp(whiteBackground)
|
|
|
|
|
|
.composite(composites)
|
|
|
|
|
|
.toFile(outputPath);
|
2024-08-03 12:46:12 +08:00
|
|
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
throw err;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将图片的base64写到本地文件
|
|
|
|
|
|
* @param base64 base64字符串
|
|
|
|
|
|
* @param outFilePath 写出的文件路径
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function Base64ToFile(base64: string, outFilePath: string): Promise<void> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
let base64Data = base64.replace(/^data:image\/\w+;base64,/, '')
|
|
|
|
|
|
let dataBuffer = Buffer.from(base64Data, 'base64')
|
|
|
|
|
|
let out_folder = path.dirname(outFilePath)
|
2024-09-12 14:13:09 +08:00
|
|
|
|
await CheckFolderExistsOrCreate(out_folder)
|
2024-08-03 12:46:12 +08:00
|
|
|
|
await fs.promises.writeFile(outFilePath, dataBuffer)
|
|
|
|
|
|
// await this.tools.writeArrayToFile(dataBuffer, out_file);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw new Error('将base64转换为文件失败,失败信息如下:' + error.toString())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将图片分割为4份
|
|
|
|
|
|
* @param inputPath 输入的文件路径
|
|
|
|
|
|
* @param reName 重命名的名字,用做新文件名的前缀
|
|
|
|
|
|
* @param outputDir 输出的文件夹路径
|
|
|
|
|
|
* @returns
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function ImageSplit(inputPath: string, reName: string, outputDir: string) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const metadata = await sharp(inputPath).metadata()
|
|
|
|
|
|
const smallWidth = metadata.width / 2
|
|
|
|
|
|
const smallHeight = metadata.height / 2
|
|
|
|
|
|
let times = new Date().getTime()
|
|
|
|
|
|
let imgs = []
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < 4; i++) {
|
|
|
|
|
|
const xOffset = i % 2 === 0 ? 0 : smallWidth
|
|
|
|
|
|
const yOffset = Math.floor(i / 2) * smallHeight
|
|
|
|
|
|
let out_file = path.join(outputDir, `/${reName}_${times}_${i}.png`)
|
|
|
|
|
|
await sharp(inputPath)
|
|
|
|
|
|
.extract({
|
|
|
|
|
|
left: xOffset,
|
|
|
|
|
|
top: yOffset,
|
|
|
|
|
|
width: smallWidth,
|
|
|
|
|
|
height: smallHeight
|
|
|
|
|
|
})
|
|
|
|
|
|
.resize(smallWidth, smallHeight)
|
|
|
|
|
|
.toFile(out_file)
|
|
|
|
|
|
|
|
|
|
|
|
imgs.push(out_file)
|
|
|
|
|
|
}
|
|
|
|
|
|
return imgs
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
throw err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|