LaiTool/src/define/Tools/image.ts

285 lines
8.3 KiB
TypeScript
Raw Normal View History

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> PromiseMIME类型的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 PromiseBlob对象
*/
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
}
}