Files
FiDA_Front/src/components/Canvas/DepthCanvas/manager/LayerManager.ts
2026-03-27 09:29:29 +08:00

476 lines
14 KiB
TypeScript
Raw 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 { ref } from 'vue'
import { fabric } from 'fabric-with-all'
import { createId } from '../../tools/tools'
import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod'
import { OperationType, BlendMode } from '../tools/layerHelper'
import { getArrowPath, getLinePath, cloneObjects, getStarArr } from '../tools/canvasMethod'
import i18n from '@/lang/index'
const t = i18n.global.t
export class LayerManager {
stateManager: any
canvasManager: any
layers: any
activeID: any
constructor(options) {
this.stateManager = options.stateManager;
this.canvasManager = options.canvasManager;
this.layers = ref([])
this.activeID = ref("")
}
onMounted() { }
setActiveID(id: string, isActive = true) {
const layer = this.getLayerById(id)
// if (layer?.type === "group") {
// this.activeID.value = ""
// } else {
this.activeID.value = id
// }
if (isActive) {
this.canvasManager.setActiveObjectById(id)
this.stateManager.toolManager.setTool(OperationType.SELECT)
}
}
getActiveLayer() {
return this.getLayerById(this.activeID.value)
}
getLayerById(id) {
function call(arr) {
for (let i = 0; i < arr.length; i++) {
let v = arr[i]
if (v.info.id === id) return v
if (v.children) {
let layer = call(v.children)
if (layer) return layer
}
}
return null
}
return call(this.layers.value)
}
setLayerNameById(id, name: string) {
const layer = this.getLayerById(id)
if (layer) {
layer.info.name = name
}
const object = this.canvasManager.getObjectById(id)
if (object) {
object.info.name = name
this.canvasManager.renderAll()
}
}
/** 设置图层显示状态 */
setLayerVisibleById(id, visible: boolean) {
const layer = this.getLayerById(id)
const object = this.canvasManager.getObjectById(id)
const call = (layer, object) => {
if (!layer || !object) return
layer.visible = visible
object.set({
visible: visible
})
}
call(layer, object)
if (object.type === "group") {
const children = this.canvasManager.getChildObjectsById(id)
children.forEach(v => {
const layer = this.getLayerById(v.info.id)
call(layer, v)
})
}
this.canvasManager.renderAll()
this.stateManager.recordState()
}
/** 设置图层锁定状态 */
setLayerLockById(id, lock: boolean) {
const layer = this.getLayerById(id)
const object = this.canvasManager.getObjectById(id)
const call = (layer, object) => {
if (!layer || !object) return
layer.info.lock = !!lock
layer.evented = !lock
object.info.lock = !!lock
if (object.type !== "group") {
object.set({
evented: !lock,
selectable: !lock,
})
}
}
call(layer, object)
if (object.type === "group") {
const children = this.canvasManager.getChildObjectsById(id)
children.forEach(v => {
const layer = this.getLayerById(v.info.id)
call(layer, v)
})
}
if (lock) {
// 取消选中对象
const e = this.canvasManager.getSelectedObject()
if (e) {
const objects = [...(e._objects || [e])]
if (objects.some(v => v.info?.id === object.info.id)) {
this.canvasManager.discardActiveObject()
}
}
}
this.canvasManager.renderAll()
this.stateManager.recordState()
}
/** 删除指定图层 */
deleteLayerById(id, isActive = true) {
const layer = this.getLayerById(id)
if (layer.children) {
layer.children.forEach(v => this.canvasManager.deleteObjectById(v.info.id, false))
}
this.canvasManager.deleteObjectById(id)
if (id === this.activeID.value && isActive) {
this.setActiveID(this.layers.value[0]?.info?.id || "")
}
if (isActive) this.stateManager.recordState()
}
/** 复制指定图层 */
copyLayerById(id) {
const object = this.canvasManager.getObjectById(id)
if (!object) return console.warn('复制图层失败对象不存在ID:', id)
this.canvasManager.discardActiveObject()
cloneObjects([object]).then(objects => {
const newObject = objects[0]
const info = JSON.parse(JSON.stringify(newObject.info))
info.id = createId("copylayer")
// info.name = info.name
newObject.set({
top: newObject.top + 15,
left: newObject.left + 15,
info: info,
})
this.canvasManager.add(newObject)
this.setActiveID(newObject.info.id)
})
}
/** 根据layers排序图层 */
async sortLayers(isRecord?: boolean) {
const ids = [];
call(this.layers.value)
await this.canvasManager.sortObjectByIds(ids.reverse(), isRecord)
function call(arr) {
arr.forEach(v => {
ids.push(v.info.id)
if (v.children) call(v.children)
})
}
}
// 更新图层列表
async updateLayers(isSort = false) {
const objects = this.canvasManager.getObjects().map(v => v.toObject()).filter(v => !!v.info?.id).reverse()
objects.forEach(v => {
if (v.type === "group") {
if (!v.children) v.children = []
return;
}
const parentId = v.info?.parentId
if (!parentId) return
objects.forEach((obj: any) => {
if (obj.info?.id !== parentId) return
if (!obj.children) obj.children = []
obj.children.push(v)
})
})
const layers = objects.filter(v => !v.info?.parentId)
this.layers.value = layers
if (isSort) await this.sortLayers()
}
/** 设置图层位置-不设置默认居中 */
setLayerPosition(object, options?: any) {
const width = this.canvasManager.canvas.clipPath.width
const height = this.canvasManager.canvas.clipPath.height
if (options && options.top !== undefined) {
object.set({ top: options.top })
} else {
object.set({ top: height / 2 - object.height * object.scaleY / 2 })
}
if (options && options.left !== undefined) {
object.set({ left: options.left })
} else {
object.set({ left: width / 2 - object.width * object.scaleX / 2 })
}
}
/** 创建空图层 */
async createEmptyLayer(isRecord = true, isActive = false) {
const emptyObject = new fabric.Rect({
width: 0,
height: 0,
fill: 'transparent',
info: {
id: createId("image"),
name: t('DepthCanvas.emptyLayer'),
}
})
this.setLayerPosition(emptyObject)
await this.canvasManager.add(emptyObject, isRecord)
if (isActive) this.setActiveID(emptyObject.info.id, false)
return emptyObject
}
/** 创建组图层 */
async createGroupLayer(options?: any, isRecord = true, isActive = false) {
const groupObject = new fabric.Group([], {
...(options || {}),
width: 1,
height: 1,
hasControls: false, // 不显示控制点
hasBorders: false, // 不显示边框
selectable: false, // 不可选中(可选)
evented: false,
info: {
id: createId("group"),
name: t('DepthCanvas.aiGroupLayer'),
showChildren: true,
...(options?.info || {}),
}
})
this.setLayerPosition(groupObject, options)
await this.canvasManager.add(groupObject, isRecord)
if (isActive) this.setActiveID(groupObject.info.id, false)
return groupObject
}
/** 创建文本图层 */
async createTextLayer(text: string, options?: any) {
const textObject = new fabric.IText(text, {
fontSize: 24,
fill: '#000',
...(options || {}),
info: {
id: createId("text"),
name: t('DepthCanvas.textLayer'),
...(options?.info || {}),
}
})
this.setLayerPosition(textObject, options)
await this.canvasManager.add(textObject)
return textObject
}
/** 创建矩形图层 */
async createRectLayer(options?: any, isRecord = true, isActive = true) {
const rectObject = new fabric.Rect({
width: 100,
height: 100,
fill: '#000',
strokeWidth: 0,
...(options || {}),
info: {
id: createId("rect"),
name: t('DepthCanvas.rectLayer'),
...(options?.info || {}),
}
})
this.setLayerPosition(rectObject, options)
await this.canvasManager.add(rectObject, isRecord)
if (isActive) this.setActiveID(rectObject.info.id)
return rectObject
}
/** 创建直线图层 */
async createLineLayer(options?: any, isRecord = true, isActive = true) {
const width = options?.width || 100
const height = options?.height || 2
delete options.width
delete options.height
const arrowObject = new fabric.Path(getLinePath(width, height), {
stroke: '#000', // 只设置边框颜色
strokeWidth: 2, // 边框宽度
fill: 'transparent', // 不填充
strokeLineCap: 'round',
strokeLineJoin: 'round',
...(options || {}),
info: {
id: createId("line"),
name: t('DepthCanvas.lineLayer'),
...(options?.info || {}),
}
});
this.setLayerPosition(arrowObject, options)
await this.canvasManager.add(arrowObject, isRecord)
if (isActive) this.setActiveID(arrowObject.info.id)
return arrowObject
}
/** 创建椭圆图层 */
async createEllipseLayer(options?: any, isRecord = true, isActive = true) {
const ellipseObject = new fabric.Ellipse({
fill: '#000',
strokeWidth: 0,
...(options || {}),
info: {
id: createId("ellipse"),
name: t('DepthCanvas.ellipseLayer'),
...(options?.info || {}),
}
})
this.setLayerPosition(ellipseObject, options)
await this.canvasManager.add(ellipseObject)
if (isActive) this.setActiveID(ellipseObject.info.id)
return ellipseObject
}
/** 创建三角形图层 */
async createTriangleLayer(options?: any, isRecord = true, isActive = true) {
const triangleObject = new fabric.Triangle({
width: 100,
height: 100,
fill: '#000',
strokeWidth: 0,
...(options || {}),
info: {
id: createId("triangle"),
name: t('DepthCanvas.triangleLayer'),
...(options?.info || {}),
}
})
this.setLayerPosition(triangleObject, options)
await this.canvasManager.add(triangleObject, isRecord)
if (isActive) this.setActiveID(triangleObject.info.id)
return triangleObject
}
/** 创建五角星图层 */
async createStarLayer(options?: any, isRecord = true, isActive = true) {
const width = options?.width || 100
const height = options?.height || 100
delete options.points
const starObject = new fabric.Polygon(getStarArr(width, height), {
fill: '#000',
strokeWidth: 0,
...(options || {}),
info: {
id: createId("star"),
name: t('DepthCanvas.starLayer'),
...(options?.info || {}),
}
})
this.setLayerPosition(starObject, options)
await this.canvasManager.add(starObject, isRecord)
if (isActive) this.setActiveID(starObject.info.id)
return starObject
}
/** 创建箭头图层 */
async createArrowLayer(options?: any, isRecord = true, isActive = true) {
const width = options?.width || 100
const strokeWidth = options?.strokeWidth || 4
delete options.width
delete options.strokeWidth
const arrowObject = new fabric.Path(getArrowPath(width, strokeWidth), {
stroke: '#000', // 只设置边框颜色
strokeWidth, // 边框宽度
fill: 'transparent', // 不填充
strokeLineCap: 'round',
strokeLineJoin: 'round',
...(options || {}),
info: {
id: createId("arrow"),
name: t('DepthCanvas.arrowLayer'),
...(options?.info || {}),
}
});
this.setLayerPosition(arrowObject, options)
await this.canvasManager.add(arrowObject, isRecord)
if (isActive) this.setActiveID(arrowObject.info.id)
return arrowObject
}
/** 创建图片图层 */
async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true, isActive = true) {
const { canvasWidth, canvasHeight } = this.canvasManager.getCanvasSize();
const imageObject = await new Promise((resolve) => {
const url = typeof imgOrUrl === 'string' ? imgOrUrl : imgOrUrl.src
fabric.Image.fromURL(url, (img) => {
const width = img.width
const height = img.height
const scaleX = width > canvasWidth ? canvasWidth * 0.8 / width : 1
const scaleY = height > canvasHeight ? canvasHeight * 0.8 / height : 1
const scale = Math.min(scaleX, scaleY)
img.set({
scaleX: scale,
scaleY: scale,
...(options || {}),
info: {
id: createId("image"),
name: t('DepthCanvas.imageLayer'),
...(options?.info || {}),
}
})
resolve(img)
})
}) as fabric.Object
this.setLayerPosition(imageObject, options)
await this.canvasManager.add(imageObject, isRecord)
if (isActive) this.setActiveID(imageObject.info.id)
return imageObject
}
/** 合并图层 */
async imageMergeToLayer(targetLayer: fabric.Object, fabricImage: fabric.Object) {
const info = await exportObjectsToImage([targetLayer, fabricImage], true)
const mergedImage = await new Promise((resolve) => {
fabric.Image.fromURL(info.url, (img) => {
img.set({
left: info.left,
top: info.top,
info: {
...(targetLayer?.info || {}),
id: createId("image"),
name: targetLayer?.info?.name || t('DepthCanvas.mergeLayer'),
}
})
resolve(img)
}, { crossOrigin: 'anonymous' })
})
const index = this.canvasManager.getObjects().indexOf(targetLayer);
this.deleteLayerById(targetLayer.info.id, false)
const nid = mergedImage.info.id
await this.canvasManager.add(mergedImage, false);
this.setActiveID(nid, false)
this.canvasManager.canvas.moveTo(mergedImage, index);
// this.stateManager.objectManager.setBlendMode(nid, BlendMode.MULTIPLY)
// this.stateManager.objectManager.setFillRepeat(nid, false)
this.canvasManager.renderAll()
this.updateLayers()
this.stateManager.recordState()
return true;
}
/** 设置激活对象可擦除 */
setActiveObjectErasable() {
const objects = this.canvasManager.getObjects()
objects.forEach((item: any) => {
item.set({
erasable: (item.info.id === this.activeID.value && item.type !== "group")
})
})
}
/** 设置所有对象擦除属性 */
setAllObjectsErasable(isErasable = false) {
const objects = this.canvasManager.getObjects()
objects.forEach((item: any) => {
item.set({ erasable: isErasable })
})
}
/** 更新图层缩略图 */
async updateLayerThumbnailsById(id: string, thumbnail?: string, isUpdate = true) {
const object = this.canvasManager.getObjectById(id);
if (!object) return;
const url = thumbnail || await exportObjectToThumbnail(object);
object.thumbnail = url
if (isUpdate) this.updateLayers()
}
dispose() { }
}