178 lines
4.7 KiB
TypeScript
178 lines
4.7 KiB
TypeScript
import { readPsd, initializeCanvas } from 'ag-psd'
|
||
import sharp from 'sharp'
|
||
import fs from 'fs/promises'
|
||
|
||
// 尝试初始化 canvas(如果可用)
|
||
try {
|
||
// 动态导入 canvas(可选依赖)
|
||
const canvas = require('canvas')
|
||
if (canvas && canvas.createCanvas) {
|
||
initializeCanvas(canvas.createCanvas)
|
||
console.log('[PSD] Canvas initialized successfully')
|
||
}
|
||
} catch (error) {
|
||
console.log('[PSD] Canvas not available, using fallback method')
|
||
}
|
||
|
||
interface PsdProcessResult {
|
||
success: boolean
|
||
previewPath?: string
|
||
width?: number
|
||
height?: number
|
||
error?: string
|
||
}
|
||
|
||
/**
|
||
* 从PSD文件生成预览图
|
||
* @param psdFilePath PSD文件路径
|
||
* @param outputPath 输出预览图路径
|
||
* @param maxSize 最大尺寸(宽或高),默认800px
|
||
* @param maxHeight 最大高度限制,长图裁剪,默认1200px
|
||
* @returns 处理结果
|
||
*/
|
||
export async function generatePsdPreview(
|
||
psdFilePath: string,
|
||
outputPath: string,
|
||
maxSize: number = 800,
|
||
maxHeight: number = 1200
|
||
): Promise<PsdProcessResult> {
|
||
try {
|
||
// 读取PSD文件
|
||
const psdBuffer = await fs.readFile(psdFilePath)
|
||
|
||
console.log(`[PSD] 开始解析PSD文件,大小: ${psdBuffer.length} bytes`)
|
||
|
||
// 解析PSD - 不跳过合成图像数据
|
||
const psd = readPsd(psdBuffer, {
|
||
skipLayerImageData: true, // 跳过图层数据
|
||
skipCompositeImageData: false, // 不跳过合成图像数据(关键!)
|
||
skipThumbnail: false
|
||
})
|
||
|
||
if (!psd) {
|
||
return {
|
||
success: false,
|
||
error: 'Failed to parse PSD file'
|
||
}
|
||
}
|
||
|
||
console.log(`[PSD] 解析成功,尺寸: ${psd.width}x${psd.height}`)
|
||
|
||
// 获取PSD的宽高
|
||
const width = psd.width || 0
|
||
const height = psd.height || 0
|
||
|
||
if (width === 0 || height === 0) {
|
||
return {
|
||
success: false,
|
||
error: 'Invalid PSD dimensions'
|
||
}
|
||
}
|
||
|
||
// 检查是否有合成图像数据
|
||
if (!psd.imageData || !psd.imageData.data) {
|
||
console.log('[PSD] 未找到合成图像数据')
|
||
return {
|
||
success: false,
|
||
error: 'No composite image data found in PSD file. Please manually upload a preview image.'
|
||
}
|
||
}
|
||
|
||
console.log(`[PSD] 找到合成图像数据: ${psd.imageData.width}x${psd.imageData.height}`)
|
||
const imageData = psd.imageData
|
||
|
||
// 确保有图像数据
|
||
if (!imageData.data || imageData.data.length === 0) {
|
||
return {
|
||
success: false,
|
||
error: 'Image data is empty'
|
||
}
|
||
}
|
||
|
||
console.log(`[PSD] 图像数据大小: ${imageData.data.length} bytes`)
|
||
|
||
// 将PSD图像数据转换为Buffer
|
||
const rawBuffer = Buffer.from(imageData.data)
|
||
|
||
// 创建Sharp实例
|
||
let sharpInstance = sharp(rawBuffer, {
|
||
raw: {
|
||
width: imageData.width,
|
||
height: imageData.height,
|
||
channels: 4 // RGBA
|
||
}
|
||
})
|
||
|
||
// 如果是长图(高度超过maxHeight),先裁剪顶部区域
|
||
if (imageData.height > maxHeight) {
|
||
console.log(`[PSD] 检测到长图 ${imageData.width}x${imageData.height},裁剪到顶部${maxHeight}px`)
|
||
sharpInstance = sharpInstance.extract({
|
||
left: 0,
|
||
top: 0,
|
||
width: imageData.width,
|
||
height: maxHeight
|
||
})
|
||
}
|
||
|
||
// 然后进行缩放处理
|
||
await sharpInstance
|
||
.resize(maxSize, maxSize, {
|
||
fit: 'inside',
|
||
withoutEnlargement: true
|
||
})
|
||
.jpeg({
|
||
quality: 90,
|
||
mozjpeg: true
|
||
})
|
||
.toFile(outputPath)
|
||
|
||
console.log(`[PSD] 预览图生成成功: ${outputPath}`)
|
||
|
||
return {
|
||
success: true,
|
||
previewPath: outputPath,
|
||
width,
|
||
height
|
||
}
|
||
} catch (error) {
|
||
console.error('Error generating PSD preview:', error)
|
||
return {
|
||
success: false,
|
||
error: error instanceof Error ? error.message : 'Unknown error'
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查文件是否为PSD格式
|
||
* @param filePath 文件路径
|
||
* @returns 是否为PSD文件
|
||
*/
|
||
export async function isPsdFile(filePath: string): Promise<boolean> {
|
||
try {
|
||
const buffer = await fs.readFile(filePath, { encoding: null })
|
||
// PSD文件签名:8BPS
|
||
return buffer.length > 4 &&
|
||
buffer[0] === 0x38 && // '8'
|
||
buffer[1] === 0x42 && // 'B'
|
||
buffer[2] === 0x50 && // 'P'
|
||
buffer[3] === 0x53 // 'S'
|
||
} catch {
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从文件扩展名判断是否为PSD相关格式
|
||
* @param filename 文件名
|
||
* @returns 是否为PSD相关文件(支持.psd、.tif、.tiff、.psb)
|
||
*/
|
||
export function hasPsdExtension(filename: string): boolean {
|
||
const lowerName = filename.toLowerCase()
|
||
return lowerName.endsWith('.psd') ||
|
||
lowerName.endsWith('.tif') ||
|
||
lowerName.endsWith('.tiff') ||
|
||
lowerName.endsWith('.psb')
|
||
}
|
||
|