flnxi-Wu/lib/psd-processor.ts
WuYongJie f646b71813
Some checks failed
CI Check / check (push) Has been cancelled
chore: 初始化本地仓库并同步最近改动
2025-11-18 17:50:16 +08:00

178 lines
4.7 KiB
TypeScript
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.

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')
}