feat: update psd.js & run on WebWorker

This commit is contained in:
ShawnPhang 2024-08-17 23:48:07 +08:00
parent ebf8fa9bba
commit 2878064b76
8 changed files with 97 additions and 32179 deletions

23
package-lock.json generated
View File

@ -14,7 +14,6 @@
"@palxp/color-picker": "workspace:*",
"@palxp/image-extraction": "workspace:*",
"@scena/guides": "^0.18.1",
"@webtoon/psd": "^0.4.0",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"cropperjs": "^1.6.1",
@ -30,6 +29,7 @@
"nanoid": "^3.1.23",
"normalize.css": "^8.0.1",
"pinia": "^2.1.7",
"psd.js": "^3.9.0",
"qr-code-styling": "^1.6.0-rc.1",
"selecto": "^1.13.0",
"throttle-debounce": "^3.0.1",
@ -1515,11 +1515,6 @@
}
}
},
"node_modules/@webtoon/psd": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@webtoon/psd/-/psd-0.4.0.tgz",
"integrity": "sha512-ztriE8oFOamRrV9opBURDy+JMiyhur2//vOXsC5CgdnYCB0L1Lnaag4NzP8N+NFCj7uNz9JRYtPmAbQMSDLIsQ=="
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
@ -3397,6 +3392,14 @@
}
}
},
"node_modules/pngjs": {
"version": "3.4.0",
"resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-3.4.0.tgz",
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/postcss": {
"version": "8.4.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
@ -3445,6 +3448,14 @@
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
"optional": true
},
"node_modules/psd.js": {
"version": "3.9.0",
"resolved": "https://registry.npmmirror.com/psd.js/-/psd.js-3.9.0.tgz",
"integrity": "sha512-gTNUszC/hjS+F3O0JkdWOdN7dVZzOaYfyF7X1VD0UOue5iWOaFzfxFbfZgYnXtYS1pze9V10Hd/K0pWY3My54g==",
"dependencies": {
"pngjs": "^3.4.0"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",

View File

@ -17,7 +17,6 @@
"@palxp/color-picker": "workspace:*",
"@palxp/image-extraction": "workspace:*",
"@scena/guides": "^0.18.1",
"@webtoon/psd": "^0.4.0",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"cropperjs": "^1.6.1",
@ -33,6 +32,7 @@
"nanoid": "^3.1.23",
"normalize.css": "^8.0.1",
"pinia": "^2.1.7",
"psd.js": "^3.9.0",
"qr-code-styling": "^1.6.0-rc.1",
"selecto": "^1.13.0",
"throttle-debounce": "^3.0.1",

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,8 @@
* @Author: ShawnPhang
* @Date: 2023-07-11 14:21:33
* @Description:
* @LastEditors: ShawnPhang <site: book.palxp.com>
* @LastEditTime: 2023-07-11 17:01:37
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-17 15:56:39
*/
import Vue, { VNode } from 'vue'
@ -41,6 +41,7 @@ declare module 'dayjs'
declare module 'fontfaceobserver'
declare module 'throttle-debounce'
declare module 'html2canvas'
declare module 'psd.js'
declare module 'vue/types/vue' {
interface Vue {

View File

@ -0,0 +1,21 @@
/*
* @Author: ShawnPhang
* @Date: 2024-08-17 18:45:24
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-17 18:46:58
*/
export function createBase64(src: any, { width, height }: any) {
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const context: any = canvas.getContext('2d', { willReadFrequently: true })
const imageData = context.getImageData(0, 0, width, height)
const pixelData = imageData.data
const len = src.length
for (let i = 0; i < len; i++) {
pixelData[i] = src[i]
}
context.putImageData(imageData, 0, 0)
return canvas.toDataURL('image/png')
}

View File

@ -1,7 +1,10 @@
// import { colorer } from './color'
import PSD from 'psd.js'
import { RGBA2HexA } from './color/color'
const colorer = { RGBA2HexA }
import * as helper from './helper'
export const createBase64 = helper.createBase64
export const CLOUD_TYPE = {
text: 'text',
image: 'image',
@ -12,7 +15,7 @@ export const WRITING_MODE = {
}
export async function parsePSDFromURL(url: string) {
return await (window as any).PSD.fromURL(url)
return await PSD.fromURL(url)
}
function toRGBAColor(data: number[]) {
@ -129,9 +132,9 @@ function toCloudTextConfig(data: any, layer: any) {
function toCloudImageConfig(data: any, layer: any) {
// const { type, b64 } = splitBase64(layer.image.toBase64());
// const src = URL.createObjectURL(b64toBlob(b64, type));
return {
src: layer?.image?.toBase64(),
src: layer?.image?.pixelData,
// src: layer?.image?.toBase64(),
// src: layer.image.toPng(),
type: CLOUD_TYPE.image,
width: data.width,
@ -152,7 +155,7 @@ function toCloud(data: any, layer: any) {
export async function convertPSD2Page(psd: any) {
const { children, document: doc } = psd.tree().export()
console.log(psd.tree().export())
console.log('PSD_tree_export', psd.tree().export())
// const node = psd.tree().childrenAtPath('taylor-vick-M5tzZtFCOfs-unsplash')[0]
// console.log(node)
@ -221,31 +224,9 @@ export async function convertPSD2Page(psd: any) {
return page
}
function file2Base64(file: any) {
return new Promise((resolve) => {
// const blob = new Blob(file)
// resolve(URL.createObjectURL(blob))
// const reader = new FileReader()
// reader.onload = function(e: any) {
// resolve(e.target.result)
// }
// reader.readAsDataURL(blob)
let binary = ''
const bytes = new Uint8Array(file)
const len = bytes.byteLength
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i])
}
resolve('data:image/png;base64,' + window.btoa(binary))
// resolve('data:image/png;base64,' + btoa(new Uint8Array(file).reduce((data, byte) => data + String.fromCharCode(byte), '')))
})
}
export async function processPSD2Page(file: File) {
const url = URL.createObjectURL(file)
const psd = await parsePSDFromURL(url)
URL.revokeObjectURL(url)
return convertPSD2Page(psd)
}

View File

@ -3,19 +3,26 @@
* @Date: 2023-09-14 11:33:44
* @Description: PSD解析
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2023-09-14 18:15:44
* @LastEditTime: 2024-08-17 18:57:15
*/
import Psd from '@webtoon/psd'
// import Psd from '@webtoon/psd'
// onmessage = async (e) => {
// const result = await e.data.arrayBuffer()
// const rawPsdFile = Psd.parse(result)
// console.log(111, rawPsdFile)
// const { width, height } = rawPsdFile
// const psdFile = { width, height }
// const compositeBuffer = await rawPsdFile.composite()
// self.postMessage({ psdFile, compositeBuffer })
// }
import { processPSD2Page } from '@/utils/plugins/psd'
onmessage = async (e) => {
const result = await e.data.arrayBuffer()
const rawPsdFile = Psd.parse(result)
console.log(111, rawPsdFile)
const { width, height } = rawPsdFile
const psdFile = { width, height }
const compositeBuffer = await rawPsdFile.composite()
self.postMessage({ psdFile, compositeBuffer })
const data = await processPSD2Page(e.data)
self.postMessage({ data })
}

View File

@ -3,7 +3,7 @@
* @Date: 2022-01-10 14:57:53
* @Description: Psd文件解析
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-15 18:00:32
* @LastEditTime: 2024-08-17 18:53:35
-->
<template>
<div id="page-design-index" ref="pageDesignIndex">
@ -27,13 +27,7 @@
<design-board class="page-design-wrap" pageDesignCanvasId="page-design-canvas">
<div v-if="state.isDone" class="shelter" :style="{ width: (dPage.width * dZoom) / 100 + 'px', height: (dPage.height * dZoom) / 100 + 'px' }"></div>
<uploader v-else accept=".psd" :hold="true" :drag="true" class="uploader" @load="selectFile">
<div class="uploader__box">
<img
style="margin-right: 1rem"
src="https://cdn.dancf.com/design/svg/icon_psdimport.37e6f23e.svg"
alt="upload"
/> PSD
</div>
<div class="uploader__box"><img style="margin-right: 1rem" src="https://cdn.dancf.com/design/svg/icon_psdimport.37e6f23e.svg" alt="upload" /> 在此拖入或选择 PSD 文件</div>
</uploader>
</design-board>
<style-panel v-show="state.isDone"></style-panel>
@ -45,11 +39,7 @@
<!-- 旋转缩放组件 -->
<Moveable />
<!-- 遮罩百分比进度条 -->
<ProgressLoading
:percent="state.downloadPercent" :text="state.downloadText"
:cancelText="state.cancelText" :msg="state.downloadMsg"
@cancel="cancel" @done="state.downloadPercent = 0"
/>
<ProgressLoading :percent="state.downloadPercent" :text="state.downloadText" :cancelText="state.cancelText" :msg="state.downloadMsg" @cancel="cancel" @done="state.downloadPercent = 0" />
</div>
</template>
@ -68,8 +58,9 @@ import designBoard from '@/components/modules/layout/designBoard/index.vue'
import zoomControl from '@/components/modules/layout/zoomControl/index.vue'
import HeaderOptions, { TEmitChangeData } from './components/UploadTemplate.vue'
import ProgressLoading from '@/components/common/ProgressLoading/index.vue'
// import MyWorker from '@/utils/plugins/webWorker'
import { processPSD2Page } from '@/utils/plugins/psd'
import MyWorker from '@/utils/plugins/webWorker'
// import { processPSD2Page } from '@/utils/plugins/psd'
import { createBase64 } from '@/utils/plugins/psd'
// import { useSetupMapGetters } from '@/common/hooks/mapGetters'
import { wTextSetting } from '@/components/modules/widgets/wText/wTextSetting'
import { useCanvasStore, useControlStore, useWidgetStore, useGroupStore } from '@/store'
@ -106,23 +97,17 @@ const { dZoom } = storeToRefs(useCanvasStore())
const zoomControlRef = ref<typeof zoomControl | null>()
let loading: ReturnType<typeof useLoading> | null = null
// const myWorker = new MyWorker('loadPSD')
const myWorker = new MyWorker('loadPSD')
onMounted(async () => {
groupStore.initGroupJson(JSON.stringify(wGroupSetting))
await nextTick()
if (zoomControlRef.value){
if (zoomControlRef.value) {
zoomControlRef.value.screenChange()
}
state.isDone = false
})
function loadJS() {
const link_element = document.createElement('script')
link_element.setAttribute('src', '/psd.js')
document.head.appendChild(link_element)
}
async function selectFile(file: File) {
loading = useLoading()
await loadPSD(file)
@ -130,31 +115,27 @@ async function selectFile(file: File) {
state.isDone = true
}
async function loadPSD(file: File) {
// const { compositeBuffer, psdFile } = await myWorker.start(file)
const data = await processPSD2Page(file)
setTimeout(async () => {
const types: any = {
const types: any = {
text: wTextSetting,
image: wImageSetting,
}
}
async function loadPSD(file: File) {
// const { compositeBuffer, psdFile } = await myWorker.start(file)
const { data }: any = await myWorker.start(file)
// const data = await processPSD2Page(file)
for (let i = 0; i < data.clouds.length; i++) {
const x: any = data.clouds[i]
const rawData = JSON.parse(JSON.stringify(types[x.type])) || {}
delete x.type
x.src && (x.imgUrl = x.src) && delete x.src
x.src && (x.imgUrl = createBase64(x.src, { width: x.width, height: x.height })) && delete x.src
widgetStore.addWidget(Object.assign(rawData, x))
// store.dispatch('addWidget', Object.assign(rawData, x))
}
const { width, height, background: bg } = data
pageStore.setDPage(Object.assign(pageStore.dPage, { width, height, backgroundColor: bg.color, backgroundImage: bg.image }))
// store.commit('setDPage', Object.assign(store.getters.dPage, { width, height, backgroundColor: bg.color, backgroundImage: bg.image }))
pageStore.setDPage(Object.assign(pageStore.dPage, { width, height, backgroundColor: bg.color, backgroundImage: createBase64(bg.image, { width, height }) }))
await loadDone()
}, 10)
}
async function clear() {
@ -178,7 +159,7 @@ const cancel = () => {
window.open(`${window.location.protocol + '//' + window.location.host}/home?id=${route.query.id}`)
}
const {handleKeydowm, handleKeyup, dealCtrl} = shortcuts.methods
const { handleKeydowm, handleKeyup, dealCtrl } = shortcuts.methods
// ...mapGetters(['dPage', 'dZoom']),
@ -188,7 +169,6 @@ onMounted(() => {
const instance = getCurrentInstance()
document.addEventListener('keydown', handleKeydowm(controlStore, checkCtrl, instance, dealCtrl), false)
document.addEventListener('keyup', handleKeyup(controlStore, checkCtrl), false)
loadJS()
})
onBeforeMount(() => {
@ -218,7 +198,6 @@ function jump2word() {
}
defineExpose({
loadJS,
selectFile,
clear,
cancel,