2024-08-03 12:46:12 +08:00
|
|
|
|
import path from 'path'
|
|
|
|
|
|
import sharp from 'sharp'
|
|
|
|
|
|
import { CheckFileOrDirExist } from './file'
|
|
|
|
|
|
import fs from 'fs'
|
|
|
|
|
|
import https from 'https'
|
|
|
|
|
|
import Compressor from 'compressorjs';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将指定的图片的尺寸修改,返回修改后的图片数据(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);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 压缩图片到指定的大小
|
|
|
|
|
|
* @param file 图片的Blob对象
|
|
|
|
|
|
* @param maxSizeInBytes 最大文件大小,单位字节
|
|
|
|
|
|
* @returns 返回一个Promise,解析为压缩后的Blob对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function CompressImageToSize(base64: string, maxSizeInBytes: number): Promise<Blob> {
|
|
|
|
|
|
let mimeType = this.getMimeType(base64);
|
|
|
|
|
|
let byteCharacters = atob(base64.split(',')[1]);
|
|
|
|
|
|
let byteNumbers = new Array(byteCharacters.length);
|
|
|
|
|
|
for (let i = 0; i < byteCharacters.length; i++) {
|
|
|
|
|
|
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
|
|
|
|
}
|
|
|
|
|
|
let byteArray = new Uint8Array(byteNumbers);
|
|
|
|
|
|
let imageBlob = new Blob([byteArray], { type: mimeType });
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const compress = (quality: number) => {
|
|
|
|
|
|
new Compressor(imageBlob as Blob, {
|
|
|
|
|
|
quality,
|
|
|
|
|
|
success(result) {
|
|
|
|
|
|
if (result.size <= maxSizeInBytes || quality <= 0.1) {
|
|
|
|
|
|
resolve(result);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 递归降低质量
|
|
|
|
|
|
compress(quality - 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
error(err) {
|
|
|
|
|
|
reject(err);
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
// 从较高的质量开始
|
|
|
|
|
|
compress(0.9);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成图片蒙板
|
|
|
|
|
|
* 将选中的区域涂白,其他区域涂黑(这个颜色可以变)
|
|
|
|
|
|
* @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)
|
|
|
|
|
|
await this.tools.checkFolderExistsOrCreate(out_folder)
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|