Commit 9bde0bff authored by 罗超's avatar 罗超

完成PSD解析

parent c34d7f19
...@@ -20,3 +20,5 @@ pnpm-debug.log* ...@@ -20,3 +20,5 @@ pnpm-debug.log*
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
package-lock.json
yarn.lock
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.1.0", "@element-plus/icons-vue": "^2.1.0",
"@icon-park/vue-next": "^1.4.2", "@icon-park/vue-next": "^1.4.2",
"@types/psd": "^3.4.3",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.6.2", "axios": "^1.6.2",
"chartist": "^1.3.0", "chartist": "^1.3.0",
...@@ -41,6 +42,7 @@ ...@@ -41,6 +42,7 @@
"prosemirror-schema-list": "^1.2.1", "prosemirror-schema-list": "^1.2.1",
"prosemirror-state": "^1.4.1", "prosemirror-state": "^1.4.1",
"prosemirror-view": "^1.27.2", "prosemirror-view": "^1.27.2",
"psd.js": "^3.9.0",
"register-service-worker": "^1.7.2", "register-service-worker": "^1.7.2",
"svg-arc-to-cubic-bezier": "^3.2.0", "svg-arc-to-cubic-bezier": "^3.2.0",
"svg-pathdata": "^6.0.3", "svg-pathdata": "^6.0.3",
......
<template> <template>
<div v-if="isFinish">
<Screen v-if="screening" /> <Screen v-if="screening" />
<Market v-else-if="market"></Market> <Market v-else-if="Market"></Market>
<!-- <NewFile v-else-if="market"></NewFile> -->
<Editor v-else-if="_isPC" /> <Editor v-else-if="_isPC" />
<Mobile v-else /> <Mobile v-else />
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
...@@ -20,6 +21,7 @@ import Editor from './views/Editor/index.vue' ...@@ -20,6 +21,7 @@ import Editor from './views/Editor/index.vue'
import Screen from './views/Screen/index.vue' import Screen from './views/Screen/index.vue'
import Mobile from './views/Mobile/index.vue' import Mobile from './views/Mobile/index.vue'
import Market from './views/Market/Index.vue' import Market from './views/Market/Index.vue'
import NewFile from './views/Market/newFile.vue'
const isFinish = ref(false) const isFinish = ref(false)
const userLoginHandler = async ()=>{ const userLoginHandler = async ()=>{
......
<template>
<el-upload
v-model:file-list="fileList"
class="upload-demo"
:on-change="handleChange"
action=""
multiple
:limit="3"
:on-exceed="handleExceed"
:auto-upload="false"
v-if="!board"
>
<el-button type="primary">Click to upload</el-button>
</el-upload>
<div style="background-color: #eee; position:relative; overflow:hidden;" :style="{width:board?.document.width+'px',height:board?.document.height+'px'}" id="ImageContainer"></div>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue";
import type { UploadProps, UploadUserFile } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import PSD from 'psd.js'
const zIndex = ref<number>(1000)
const fileList = ref<UploadUserFile[]>([])
const board = ref<any>(null)
const prevLayer = ref<any>()
const Ornts:any = {
'Hrzn':'horizontal-tb',
}
const handleChange: UploadProps['onChange'] = (uploadFile, uploadFiles) => {
if(uploadFile){
var url = URL.createObjectURL(uploadFile?.raw)
PSD.fromURL(url).then((psd:any) => {
board.value = psd.tree().export()
// console.log(psd.tree())
// console.log(psd.tree().children())
showElementHandler(psd.tree().children())
})
}
}
const showElementHandler= (items:any)=>{
items.forEach((x:any,i:number)=>{
// if(x.layer.vectorMask){
// //x.layer.vectorMask().export()
// //console.log(x.layer.vectorMask(),x.layer.vectorMask().export())
// const paths = x.layer.vectorMask().export().paths;
// let color = getFillColor(x)
// const convertedPath: any[] = []
// paths.forEach((path:any) => {
// // 转换控制点的位置
// // 这里的 document 为psd.js导出的psd文档对象
// const { recordType, numPoints } = path;
// if(!path.preceding) return
// const {
// preceding,
// anchor,
// leaving
// } = parsePath(path, board.value.document); // 控制点的位置转换成了像素位置
// convertedPath.push({
// preceding,
// anchor,
// leaving
// });
// });
// toPath(convertedPath,color,x.layer)
// }
if(x.layer.visible){
zIndex.value--
if(x.layer.typeTool){
let f = x.layer.typeTool().export()
const { left, top, width, height, value, font,transform } = f
const { colors, styles, alignment,sizes,names,weights } = font
let fontSize = 24.0
if (sizes && sizes[0]) {
if (transform.yy !== 1) {
fontSize = (Math.round((sizes[0] * transform.yy) * 100) * 0.01)
} else { // transform.yy为1时,sizes[0]的值就是字体显示大小的值,不须要计算
fontSize = sizes[0]
}
}
const StyleSheet = x.layer.adjustments.typeTool.obj.engineData.EngineDict.StyleRun.RunArray[0].StyleSheet || {}
const { StyleSheetData } = StyleSheet
const tracking = fontSize * (StyleSheetData.Tracking / 1000)
const lineHeight = StyleSheetData.Leading
let leading = (Math.round((lineHeight * transform.yy) * 100) * 0.01) / fontSize
let objectEFFFects = x.layer.objectEffects?x.layer.objectEffects():null
var div = document.createElement("div");
console.log(f,x,objectEFFFects)
div.innerText = value
div.style.top = x.coords.top+"px"
div.style.right = x.coords.right+"px"
div.style.bottom = x.coords.bottom+"px"
div.style.left = x.coords.left+"px"
div.style.width = (x.layer.width+30)+"px"
div.style.height = x.layer.height+"px"
div.style.zIndex = zIndex.value.toString()
div.style.opacity = (parseFloat(x.layer.opacity)/255.0).toFixed(2)
div.style.textAlign = alignment[0]
div.style.fontSize = fontSize+"px"
div.style.fontFamily = names.join(',')
div.style.writingMode = Ornts[x.layer.adjustments.typeTool.obj.textData.Ornt.value]
div.style.color = `rgba(${colors[0][0]},${colors[0][1]},${colors[0][2]},${(parseFloat(colors[0][3])/255.0).toFixed(2)})`
div.style.letterSpacing = tracking+"px"
div.style.lineHeight = leading+""
div.style.fontWeight = weights[0]
if(font.textDecoration){
div.style.textDecoration = font.textDecoration[0]
div.style.textUnderlineOffset = '20px'
}
if(StyleSheetData.Strikethrough){
div.style.textDecoration += ' line-through'
}
if(objectEFFFects && objectEFFFects.data?.DrSh.enab){
div.style.textShadow=getShadows(objectEFFFects)
}
if(objectEFFFects && objectEFFFects.data?.GrFl.enab){
div.style.backgroundImage = getGradient(objectEFFFects)
//div.style["-webkit-background-clip"] = 'text'
div.className='text-mask'
//div.style.backgroundSize = (objectEFFFects.data.GrFl["Scl "].value/100*x.layer.width)+"px "+(objectEFFFects.data.GrFl["Scl "].value/100*x.layer.height)+"px"
// div.style.backgroundPositionX = objectEFFFects.data.GrFl.Ofst.Hrzn.value+"px"
// div.style.backgroundPositionY = objectEFFFects.data.GrFl.Ofst.Vrtc.value+"px"
div.style.backgroundPosition = 'center center'
div.style.backgroundColor = `rgba(${colors[0][0]},${colors[0][1]},${colors[0][2]},${(parseFloat(colors[0][3])/255.0).toFixed(2)})`
div.style.backgroundRepeat = 'no-repeat'
div.style.color = 'transparent',
div.style.textShadow='unset'
}
div.setAttribute("id","layer_text_"+zIndex.value)
document.getElementById('ImageContainer')?.appendChild(div);
}else if(x.width && x.width>0){
try {
var img = document.createElement("img");
img.src = x.layer.image.toBase64()
img.style.top = x.coords.top+"px"
img.style.right = x.coords.right+"px"
img.style.bottom = x.coords.bottom+"px"
img.style.left = x.coords.left+"px"
img.style.zIndex = zIndex.value.toString()
img.style.opacity = (parseFloat(x.layer.opacity)/255.0).toFixed(2)
img.setAttribute("id","layer_image_"+zIndex.value)
document.getElementById('ImageContainer')?.appendChild(img);
} catch (error) {
}
}
if(x._children && x._children.length>0){
showElementHandler(x._children)
}
}
prevLayer.value=x
})
}
const getShadows = (drsh:any)=>{
const { DrSh } = drsh.data
const clrStr = JSON.stringify(DrSh['Clr ']).split(',')
let r:string = '0'
let g:string = '0'
let b:string = '0'
clrStr.forEach(item => {
if (item.indexOf('Rd') !== -1) {
r = item.replace('"Rd ":', '')
} else if (item.indexOf('Bl') !== -1) {
b = item.replace('"Bl ":', '').replace('}', '')
} else if (item.indexOf('Grn') !== -1) {
g = item.replace('"Grn ":', '')
}
})
const angle = DrSh.lagl.value
const distance = DrSh.Dstn.value
var angleInRadians = angle * Math.PI / 180;
const shadowColor = `rgba(${parseInt(r)},${parseInt(g)},${parseInt(b)},${DrSh.Opct.value / 100})`
const x =Math.round(distance * Math.cos(angleInRadians));
const y = Math.round(distance * Math.sin(angleInRadians));
return x + "px " + y + "px " + DrSh.blur.value + "px " + shadowColor;
}
const getGradient=(obj:any)=>{
const { GrFl } = obj.data
const angle = GrFl.Angl.value-90
let linear = `linear-gradient(${angle}deg `
let intr = GrFl.Grad.Intr
GrFl.Grad.Clrs.forEach((x:any,i:number)=>{
const clrStr = JSON.stringify(x['Clr ']).split(',')
let r:string = '0'
let g:string = '0'
let b:string = '0'
clrStr.forEach(item => {
if (item.indexOf('Rd') !== -1) {
r = item.replace('"Rd ":', '')
} else if (item.indexOf('Bl') !== -1) {
b = item.replace('"Bl ":', '').replace('}', '')
} else if (item.indexOf('Grn') !== -1) {
g = item.replace('"Grn ":', '')
}
})
const color =
linear+=`, rgba(${parseInt(r)},${parseInt(g)},${parseInt(b)},${GrFl.Opct.value / 100})`
//linear-gradient(90deg, #FFF 0%, 18% ,#e5b8a4 36%, 68%, #FFF)
let cent="",posi = ""
if(i<GrFl.Grad.Clrs.length-1){
posi = Math.ceil(x.Lctn*100/intr)+"%"
cent = Math.ceil((x.Lctn + ((GrFl.Grad.Clrs[i+1].Lctn - x.Lctn)*x.Mdpn/100))*100/intr)+"%"
linear+=` ${posi}, ${cent}`
}
})
linear+=")"
console.log(GrFl)
return linear;
}
const getFillColor = (node:any) => {
if(!node.layer.solidColor) return
const solidColorData = node.layer.solidColor();
let rgb = [solidColorData.r,solidColorData.g,solidColorData.b]
return rgbaToHex(rgb)??''
// return colorHex([
// Math.round(clr["Rd "]),
// Math.round(clr["Grn "]),
// Math.round(clr["Bl "]),
// ]);
};
const rgbaToHex = (rgba: number[]): string => {
let hex = '#';
for (const i of rgba) {
hex += i.toString(16).padStart(2, '0');
}
return hex;
}
const signed = (n:any) => {
let num = n;
if (num > 0x8f) {
num = num - 0xff - 1;
}
return num;
}
const getPathPosition = (pathNode:any) => {
const {
vert,
horiz
} = pathNode;
return {
x: signed(horiz),
y: signed(vert)
};
}
const parsePath = function (path:any, {width,height}:any) {
const {
preceding,
anchor,
leaving
} = path;
const precedingPos = getPathPosition(preceding);
const anchorPos = getPathPosition(anchor);
const leavingPos = getPathPosition(leaving);
// relX 和 relY 保留了PSD中原始数据。
return {
preceding: {
relX: precedingPos.x,
relY: precedingPos.y,
x: Math.round(width * precedingPos.x),
y: Math.round(height * precedingPos.y)
},
anchor: {
relX: anchorPos.x,
relY: anchorPos.y,
x: Math.round(width * anchorPos.x),
y: Math.round(height * anchorPos.y)
},
leaving: {
relX: leavingPos.x,
relY: leavingPos.y,
x: Math.round(width * leavingPos.x),
y: Math.round(height * leavingPos.y)
}
};
};
const toPath = (paths:Array<any>, fill:any,node:any) => {
let head:any;
const data = [] as Array<any>;
paths.forEach((path:any, index:number) => {
const { preceding, anchor, leaving } = path;
if (index < paths.length - 1) {
if (index > 0) { // 中间节点
data.push(`${preceding.x}, ${preceding.y} ${anchor.x}, ${anchor.y} ${leaving.x}, ${leaving.y}`);
} else { // 记录第一个节点,用于在关闭路径的时候使用
head = path;
data.push(`M ${anchor.x}, ${anchor.y} C${leaving.x}, ${leaving.y}`);
}
} else {
data.push(`${preceding.x}, ${preceding.y} ${anchor.x}, ${anchor.y} ${leaving.x}, ${leaving.y} ${head.preceding.x}, ${head.preceding.y} ${head.anchor.x}, ${head.anchor.y} Z`);
}
});
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
zIndex.value--
path.setAttribute('d',data.join(" "))
path.setAttribute('fill',fill)
path.setAttribute("width", node.width.toString());
path.setAttribute("height", node.height.toString());
g.appendChild(path);
g.setAttribute("width", node.width.toString());
g.setAttribute("height", node.height.toString());
svg.appendChild(g);
svg.setAttribute('viewBox',`0 0 ${node.width} ${node.height}`);
svg.style.width=node.width+"px"
svg.style.height=node.height+"px"
svg.style.top = node.top+"px"
svg.style.right = node.right+"px"
svg.style.bottom = node.bottom+"px"
svg.style.left = node.left+"px"
svg.style.zIndex = zIndex.value.toString()
let prevNode = document.getElementById("layer_image_"+(zIndex.value+1))
if(prevNode){
var pattern = document.createElementNS("http://www.w3.org/2000/svg", "pattern");
pattern.setAttribute("id", "pattern_"+zIndex.value);
pattern.setAttribute("patternUnits", "userSpaceOnUse");
pattern.setAttribute("width", node.width.toString());
pattern.setAttribute("height", node.height.toString());
var image = document.createElementNS("http://www.w3.org/2000/svg", "image");
image.setAttribute("xlink:href", prevNode.getAttribute("src")??'');
image.setAttribute("width", prevLayer.value.width.toString());
image.setAttribute("height", prevLayer.value.height.toString());
image.setAttribute("x", prevLayer.value.coords.left.toString());
image.setAttribute("y", prevLayer.value.coords.top.toString());
pattern.appendChild(image);
path.setAttribute("fill", "pattern_"+zIndex.value);
svg.appendChild(pattern);
// img.style.top = x.coords.top+"px"
// img.style.right = x.coords.right+"px"
// img.style.bottom = x.coords.bottom+"px"
// img.style.left = x.coords.left+"px"
// img.style.zIndex = zIndex.value.toString()
// img.style.opacity = (parseFloat(x.layer.opacity)/255.0).toFixed(2)
prevNode.remove()
}
//svg.appendChild(path)
//console.log(node.width+"px",node.height+"px")
document.getElementById('ImageContainer')?.appendChild(svg)
//return `<path d="${data.join(' ')}" fill="${fill}" />`;
}
const toPathHandler = (paths:any, fill:any,node:any) => {
let head:any;
const data = [] as Array<any>;
paths.forEach((path:any, index:number) => {
if(!path.preceding || !path.anchor || !path.leaving) return
let newPath = parsePath(path,node)
const { preceding, anchor, leaving } = newPath;
if (index < paths.length - 1) {
if (head) {
// 中间节点
data.push(
`${preceding.x}, ${preceding.y} ${anchor.x}, ${anchor.y} ${leaving.x}, ${leaving.y}`
);
} else {
// 记录第一个节点,用于在关闭路径的时候使用
head = newPath;
data.push(`M ${anchor.x}, ${anchor.y} C${leaving.x}, ${leaving.y}`);
}
} else {
data.push(`${preceding.x}, ${preceding.y} ${anchor.x}, ${anchor.y} ${leaving.x}, ${leaving.y} ${head.preceding.x},
${head.preceding.y} ${head.anchor.x}, ${head.anchor.y} Z`);
}
});
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
zIndex.value--
path.setAttribute('d',data.join(" "))
path.setAttribute('fill',fill)
path.setAttribute("width", node.width.toString());
path.setAttribute("height", node.height.toString());
g.appendChild(path);
g.setAttribute("width", node.width.toString());
g.setAttribute("height", node.height.toString());
svg.appendChild(g);
svg.setAttribute('viewBox',`0 0 ${node.width} ${node.height}`);
svg.style.width=node.width+"px"
svg.style.height=node.height+"px"
svg.style.top = node.top+"px"
svg.style.right = node.right+"px"
svg.style.bottom = node.bottom+"px"
svg.style.left = node.left+"px"
svg.style.zIndex = zIndex.value.toString()
let prevNode = document.getElementById("layer_image_"+(zIndex.value+1))
if(prevNode){
var pattern = document.createElementNS("http://www.w3.org/2000/svg", "pattern");
pattern.setAttribute("id", "pattern_"+zIndex.value);
pattern.setAttribute("patternUnits", "userSpaceOnUse");
pattern.setAttribute("width", node.width.toString());
pattern.setAttribute("height", node.height.toString());
var image = document.createElementNS("http://www.w3.org/2000/svg", "image");
image.setAttribute("xlink:href", prevNode.getAttribute("src")??'');
image.setAttribute("width", prevLayer.value.width.toString());
image.setAttribute("height", prevLayer.value.height.toString());
image.setAttribute("x", prevLayer.value.coords.left.toString());
image.setAttribute("y", prevLayer.value.coords.top.toString());
pattern.appendChild(image);
path.setAttribute("fill", "pattern_"+zIndex.value);
svg.appendChild(pattern);
// img.style.top = x.coords.top+"px"
// img.style.right = x.coords.right+"px"
// img.style.bottom = x.coords.bottom+"px"
// img.style.left = x.coords.left+"px"
// img.style.zIndex = zIndex.value.toString()
// img.style.opacity = (parseFloat(x.layer.opacity)/255.0).toFixed(2)
prevNode.remove()
}
//svg.appendChild(path)
//console.log(node.width+"px",node.height+"px")
document.getElementById('ImageContainer')?.appendChild(svg)
//return `<path d="${data.join(" ")}" fill="${fill}" />`;
};
const handleExceed: UploadProps['onExceed'] = (files, uploadFiles) => {
ElMessage.warning(
`The limit is 3, you selected ${files.length} files this time, add up to ${
files.length + uploadFiles.length
} totally`
)
}
</script>
<style>
#ImageContainer *{
position: absolute;
}
@font-face {
font-family: FZCSJW--GB1-0;
src: local('方正粗宋简体');
}
@font-face {
font-family: MicrosoftYaHeiLight;
src: local('Microsoft YaHei Light');
}
.text-mask{
-webkit-background-clip: text;
}
</style>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment