LaiTool/src/define/Tools/image.ts
2024-09-12 14:13:09 +08:00

285 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import path from 'path'
import sharp from 'sharp'
import { CheckFileOrDirExist, CheckFolderExistsOrCreate } from './file'
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);
}
});
});
}
}
/**
* 压缩图片到指定的大小
* @param base64 图片的Blob对象
* @param maxSizeInBytes 最大文件大小,单位字节
* @returns 返回一个Promise解析为压缩后的Blob对象
*/
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; // 每次迭代降低质量
}
return outputBuffer;
}
/**
* 生成图片蒙板
* 将选中的区域涂白,其他区域涂黑(这个颜色可以变)
* @param inputPath 输入的文件路径
* @param outputPath 输出的文件路径
* @param regions 范围对象包含x, y, width, height属性
* @param markColor 标记颜色,默认为白色 { r: 255, g: 255, b: 255 }
* @param backColor 背景颜色,默认为黑色 { r: 0, g: 0, b: 0 }
*/
export async function ProcessImage(inputPath: string,
outputPath: string,
regions: { width: any; height: any; x: any; y: any, imageWidth?: any, imageHeight?: any }[],
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();
// 创建一个新的黑色图片,背景为白色
const whiteBackground = await sharp({
create: {
width,
height,
channels: 3, // RGB channels
background: backColor // White color
}
}).png().toBuffer();
// 创建多个白色的矩形,并进行合成
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;
}
const regionBuffer = await sharp({
create: {
width: Math.ceil(region.width * rate),
height: Math.ceil(region.height * rate),
channels: 3, // RGB channels
background: markColor // 标记颜色
}
}).png().toBuffer();
return {
input: regionBuffer,
left: Math.ceil(region.x * rate),
top: Math.ceil(region.y * rate),
};
}));
// 在背景上叠加所有的矩形区域
await sharp(whiteBackground)
.composite(composites)
.toFile(outputPath);
} 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 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
}
}