xprint/public/preload.js
2025-11-16 19:31:33 +08:00

352 lines
11 KiB
JavaScript
Raw Permalink 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.

const { contextBridge, ipcRenderer } = require("electron");
const fs = require("fs");
const path = require("path");
const { changeDpiBlob } = require("changedpi");
const { Canvas, loadImage } = require("skia-canvas");
const sharp = require("sharp");
//在主进程或渲染进程(需启用 nodeIntegration
// console.log("当前架构:", process.arch);
// 增加 sharp 的像素限制(根据需要调整)
// 默认限制是 268402689 像素 (~16384x16384)
// sharp.concurrency(1); // 降低并发以减少内存使用
// console.log(sharp)
// sharp.setMaxBuffer(1024 * 1024 * 500); // 增加缓冲区大小500MB
const commonApi = {
/**
* 将 Blob 转换为 Buffer
* @param {Blob} blob - 图片 Blob 数据
* @returns {Promise<Buffer>} 转换后的 Buffer
*/
blobToBuffer: async (blob) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
// FileReader 结果为 ArrayBuffer需转换为 Buffer
const buffer = Buffer.from(reader.result);
resolve(buffer);
};
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
});
},
/**
* 将 Blob 对象写入本地文件
* @param {Blob} blob - 要写入的 Blob 对象
* @param {string} filePath - 目标文件路径
* @returns {Promise<void>}
*/
writeBlobToFile: async (blob, filePath) => {
try {
// 将 Blob 转换为 ArrayBuffer
const arrayBuffer = await blob.arrayBuffer();
// 将 ArrayBuffer 转换为 Buffer
const buffer = Buffer.from(arrayBuffer);
// 写入文件
fs.writeFileSync(filePath, buffer);
// console.log(`Blob 已成功写入文件: ${filePath}`);
} catch (error) {
console.error("写入文件时发生错误:", error);
throw error; // 允许调用者捕获错误
}
},
// loadImage: async (path) => {
// const image = await new Promise((resolve, reject) => {
// const img = new Image();
// img.onload = () => resolve(img);
// img.onerror = reject;
// img.src = path;
// });
// return image;
// },
// setDpiMetadata: async (outputPath, dpi) => {
// // // 1. 用 Jimp 处理图像(像素尺寸不变)
// // const image = await Jimp.read(inputPath);
// // // (可选)进行缩放、绘制等操作
// // await image.write(outputPath);
// // 2. 用 exiftool 写入 DPI 元数据
// // console.log(exiftool, dpi);
// await exiftool.write(outputPath, {
// XResolution: dpi, // 水平 DPI
// YResolution: dpi, // 垂直 DPI
// ResolutionUnit: "inches", // 单位为英寸
// });
// // console.log(`已为 ${outputPath} 设置 DPI: ${dpi}`);
// await exiftool.end();
// },
};
const electronAPIInstance = {
// 读取文件功能
readFile: (filePath) => {
try {
return fs.readFileSync(filePath, "utf8");
} catch (err) {
console.error("读取文件失败:", err);
return null;
}
},
// 写入文件功能
writeFile: (filePath, content) => {
try {
// 确保目录存在
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(filePath, content, "utf8");
return true;
} catch (err) {
console.error("写入文件失败:", err);
return false;
}
},
// mergeImages(blobs, filePath) {
// console.log("合并图片", blobs, filePath);
// },
// 通过 IPC 与主进程通信
getAppVersion: () => ipcRenderer.invoke("get-app-version"),
/**
* 使用 Jimp 合并多张图片
* @param {Object} options 合并选项
* @param {string[]} options.blobs 图片Blob数组
* @param {string} options.outputPath 输出图片路径
* @param {string} [options.layout='horizontal'] 布局方式: horizontal/vertical/grid
* @param {number} [options.spacing=10] 图片间距(像素)
* @param {number} [options.gridColumns=2] 网格布局列数
* @param {number} [options.quality=90] 输出质量(0-100)
*/
combineImages: async (options) => {
// try {
// 默认配置
const config = {
layout: "horizontal",
spacing: 10,
gridColumns: 2,
quality: 90,
imageWidth: 100,
imageHeight: 100,
images: [],
quality: 1,
dpi: 72,
contentType: "image/png",
...options,
};
if (!config.images || config.images.length === 0) {
throw new Error("请提供至少一张图片的路径");
}
// // 加载所有图片
// const images = [];
// for (const blob of config.blobs) {
// try {
// // console.log(Jimp);
// const buf = await commonApi.blobToBuffer(blob);
// const image = await Jimp.read(buf);
// images.push({
// image,
// width: image.bitmap.width,
// height: image.bitmap.height,
// buf: buf,
// });
// // console.log(
// // `已加载图片: ${imgPath} (${image.bitmap.width}x${image.bitmap.height})`
// // );
// } catch (err) {
// console.error(`加载图片失败:`, err.message);
// }
// }
// if (images.length === 0) {
// throw new Error("没有成功加载任何图片");
// }
// 计算合成后图片的尺寸
var totalWidth, totalHeight;
const spacing = config.spacing;
const imageWidth = config.imageWidth;
const imageHeight = config.imageHeight;
// if (config.layout === "horizontal") {
// 水平排列
(totalHeight = imageHeight), //Math.max(...images.map((img) => img.height)) + spacing * 2;
(totalWidth = (imageWidth + spacing) * config.images.length);
// images.reduce((sum, img) => sum + img.width, 0) +
// spacing * (images.length + 1);
// }
// else if (config.layout === "vertical") {
// // 垂直排列
// totalWidth = Math.max(...images.map((img) => img.width)) + spacing * 2;
// totalHeight =
// images.reduce((sum, img) => sum + img.height, 0) +
// spacing * (images.length + 1);
// } else {
// // 网格排列
// const gridColumns = Math.max(1, config.gridColumns);
// const rows = Math.ceil(images.length / gridColumns);
// // 计算每列最大宽度
// const columnWidths = [];
// for (let col = 0; col < gridColumns; col++) {
// let maxWidth = 0;
// for (let i = col; i < images.length; i += gridColumns) {
// maxWidth = Math.max(maxWidth, images[i].width);
// }
// columnWidths.push(maxWidth);
// }
// // 计算每行最大高度
// const rowHeights = [];
// for (let row = 0; row < rows; row++) {
// let maxHeight = 0;
// for (let col = 0; col < gridColumns; col++) {
// const index = row * gridColumns + col;
// if (index < images.length) {
// maxHeight = Math.max(maxHeight, images[index].height);
// }
// }
// rowHeights.push(maxHeight);
// }
// // 总尺寸
// totalWidth =
// columnWidths.reduce((sum, width) => sum + width, 0) +
// spacing * (gridColumns + 1);
// totalHeight =
// rowHeights.reduce((sum, height) => sum + height, 0) +
// spacing * (rows + 1);
// }
// 创建空白画布(透明背景)
const canvas = new Canvas(totalWidth, totalHeight);
const ctx = canvas.getContext("2d");
// 根据布局绘制图片
// if (config.layout === "horizontal") {
var currentX = 0;
for (var i = 0; i < config.images.length; i++) {
const img = await loadImage(config.images[i]);
// console.log(img.height, imageHeight);
ctx.drawImage(
img,
0,
0,
img.width,
img.height,
currentX,
0,
imageWidth,
imageHeight
);
currentX += imageWidth + spacing;
}
// for (const img of images) {
// await combined.composite(img.image, currentX, currentY);
// currentX += img.width + spacing;
// }
// } else if (config.layout === "vertical") {
// let currentY = spacing;
// const currentX = spacing;
// for (const img of images) {
// await combined.composite(img.image, currentX, currentY);
// currentY += img.height + spacing;
// }
// } else {
// // 网格布局
// let currentX = spacing;
// let currentY = spacing;
// let currentCol = 0;
// const gridColumns = Math.max(1, config.gridColumns);
// for (let i = 0; i < images.length; i++) {
// const img = images[i];
// await combined.composite(img.image, currentX, currentY);
// currentCol++;
// if (currentCol >= gridColumns) {
// currentCol = 0;
// currentX = spacing;
// // 计算当前行高度
// let rowHeight = 0;
// const rowStart = i - (gridColumns - 1);
// const rowEnd = Math.min(i, images.length - 1);
// for (let j = rowStart; j <= rowEnd; j++) {
// rowHeight = Math.max(rowHeight, images[j].height);
// }
// currentY += rowHeight + spacing;
// } else {
// // 计算当前列宽度
// let colWidth = 0;
// for (let j = currentCol; j < images.length; j += gridColumns) {
// colWidth = Math.max(colWidth, images[j].width);
// }
// currentX += colWidth + spacing;
// }
// }
// }
// 确保输出目录存在
const outputDir = path.dirname(config.outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// 保存合并后的图片
// 根据文件扩展名自动选择格式
// const extension = path.extname(config.outputPath).toLowerCase();
// if (extension === ".png") {
// await combined.write(config.outputPath);
// } else {
// // JPG 或其他格式
// await combined.write(config.outputPath, { quality: config.quality });
// }
return new Promise(async (resolve, reject) => {
const buffer = await canvas.toBuffer(config.contentType, {
quality: config.quality,
});
// 3. 使用 sharp 处理图像并设置 DPI
await sharp(buffer, { limitInputPixels: 1024 * 1024 * 1024 * 5 }) //5G
.withMetadata({
density: config.dpi, // 设置 DPI
xres: config.dpi, // 水平分辨率
yres: config.dpi, // 垂直分辨率
})
.toFile(config.outputPath);
// // 写入文件
// fs.writeFileSync(config.outputPath, buffer);
// // 将 Buffer 转换为 Blob
// var blob = new Blob([buffer], { type: config.contentType });
// console.log("config.dpi:", config.dpi, "blob:", blob);
// blob = await changeDpiBlob(blob, config.dpi);
// console.log("blob:", blob);
// await commonApi.writeBlobToFile(blob, config.outputPath);
// console.log(`图片合并完成,已保存至: ${config.outputPath}`);
resolve(true);
});
// } catch (err) {
// console.error("图片合并失败:", err.message);
// return false;
// }
},
};
// 通过 contextBridge 安全地暴露 API 给渲染进程
contextBridge.exposeInMainWorld("electronAPI", electronAPIInstance);