深度画布-平铺设置
This commit is contained in:
@@ -46,6 +46,7 @@ export class CanvasManager {
|
||||
this.deviceInfo = detectDeviceType();
|
||||
this.currentZoom = ref(100)
|
||||
}
|
||||
onMounted() { }
|
||||
setCanvasViewSize(options) {
|
||||
this.canvasViewWidth = options.canvasViewWidth || 1920
|
||||
this.canvasViewHeight = options.canvasViewHeight || 1080
|
||||
@@ -106,15 +107,15 @@ export class CanvasManager {
|
||||
// this.stateManager.toolManager.setTool(OperationType.RECTANGLE)
|
||||
}
|
||||
/** 画布添加对象 */
|
||||
async add(obj: any, isUpdate = true) {
|
||||
async add(obj: any, isRecord = true) {
|
||||
this.canvas.add(obj)
|
||||
const id = obj?.info?.id || ""
|
||||
if (isUpdate) {
|
||||
if (id) {
|
||||
this.layerManager.updateLayers()
|
||||
this.renderAll()
|
||||
if (id) await this.layerManager.updateLayerThumbnailsById(id)
|
||||
this.stateManager.recordState()
|
||||
await this.layerManager.updateLayerThumbnailsById(id)
|
||||
}
|
||||
if (isRecord) this.stateManager.recordState()
|
||||
}
|
||||
/** 画布移除对象 */
|
||||
remove(obj: any, isUpdate = true) {
|
||||
@@ -149,26 +150,22 @@ export class CanvasManager {
|
||||
}
|
||||
// 使用动画管理器的缩放方法
|
||||
animateZoom(point, targetZoom, options = {}) {
|
||||
console.log(point, targetZoom, options)
|
||||
this.animationManager.animateZoom(point, targetZoom, options);
|
||||
}
|
||||
zoomIn() {
|
||||
const currentZoom = this.canvas.getZoom()
|
||||
const newZoom = Math.min(currentZoom + 0.1, 20) // 增加20%,最大20倍
|
||||
|
||||
// 使用画布中心作为缩放点
|
||||
const centerPoint = {
|
||||
x: this.canvas.width / 2,
|
||||
y: this.canvas.height / 2
|
||||
}
|
||||
|
||||
this.animateZoom(centerPoint, newZoom)
|
||||
}
|
||||
|
||||
zoomOut() {
|
||||
const currentZoom = this.canvas.getZoom()
|
||||
const newZoom = Math.max(currentZoom - 0.1, 0.1) // 减少20%,最小0.1倍
|
||||
|
||||
// 使用画布中心作为缩放点
|
||||
const centerPoint = {
|
||||
x: this.canvas.width / 2,
|
||||
|
||||
@@ -15,6 +15,7 @@ export class LayerManager {
|
||||
this.layers = ref([])
|
||||
this.activeID = ref("")
|
||||
}
|
||||
onMounted() { }
|
||||
setActiveID(id: string, isActive = true) {
|
||||
this.activeID.value = id
|
||||
if (isActive) {
|
||||
@@ -89,7 +90,7 @@ export class LayerManager {
|
||||
}
|
||||
}
|
||||
/** 创建空图层 */
|
||||
createEmptyLayer(isUpdate = true) {
|
||||
createEmptyLayer(isRecord = true) {
|
||||
const emptyObject = new fabric.Rect({
|
||||
width: 0,
|
||||
height: 0,
|
||||
@@ -100,7 +101,7 @@ export class LayerManager {
|
||||
}
|
||||
})
|
||||
this.setLayerPosition(emptyObject)
|
||||
this.canvasManager.add(emptyObject, isUpdate)
|
||||
this.canvasManager.add(emptyObject, isRecord)
|
||||
return emptyObject
|
||||
}
|
||||
/** 创建文本图层 */
|
||||
@@ -155,7 +156,7 @@ export class LayerManager {
|
||||
return circleObject
|
||||
}
|
||||
/** 创建图片图层 */
|
||||
async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any) {
|
||||
async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true) {
|
||||
const canvasWidth = this.canvasManager.canvasWidth
|
||||
const canvasHeight = this.canvasManager.canvasHeight
|
||||
|
||||
@@ -181,13 +182,11 @@ export class LayerManager {
|
||||
})
|
||||
}) as fabric.Object
|
||||
this.setLayerPosition(imageObject, options)
|
||||
await this.canvasManager.add(imageObject)
|
||||
await this.canvasManager.add(imageObject, isRecord)
|
||||
this.setActiveID(imageObject.info.id)
|
||||
return imageObject
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 合并图层 */
|
||||
async imageMergeToLayer(targetLayer: fabric.Object, fabricImage: fabric.Object) {
|
||||
const info = await exportObjectsToImage([targetLayer, fabricImage], true)
|
||||
|
||||
196
src/components/Canvas/DepthCanvas/manager/ObjectManager.ts
Normal file
196
src/components/Canvas/DepthCanvas/manager/ObjectManager.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import { ref } from 'vue'
|
||||
import { fabric } from 'fabric-with-all'
|
||||
import { createId } from '../../tools/tools'
|
||||
import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod'
|
||||
import { OperationType } from '../tools/layerHelper'
|
||||
|
||||
export const FillSourceToBase64 = (source) => {
|
||||
if (source?.toDataURL) {
|
||||
return source.toDataURL?.();
|
||||
} else if (source?.src) {
|
||||
return source.src;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片添加gap转换
|
||||
* @param {HTMLCanvasElement} image - img元素
|
||||
* @param {Number} gapX - 水平gap
|
||||
* @param {Number} gapY - 垂直gap
|
||||
* @returns {HTMLCanvasElement} 转换后的canvas元素
|
||||
*/
|
||||
export function imageAddGapToCanvas(image, gapX, gapY) {
|
||||
// 创建透明 Canvas
|
||||
const tcanvas = document.createElement('canvas');
|
||||
tcanvas.width = image.width + gapX;
|
||||
tcanvas.height = image.height + gapY;
|
||||
const ctx = tcanvas.getContext('2d', { willReadFrequently: true });
|
||||
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
|
||||
ctx.drawImage(image, 0, 0);
|
||||
return tcanvas;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建缩放+旋转的变换矩阵
|
||||
* @param {number} scale - 缩放比例
|
||||
* @param {number} angle - 旋转角度(度)
|
||||
* @returns {Array} 变换矩阵 [a, b, c, d, e, f]
|
||||
*/
|
||||
export function createPatternTransform(scale, angle) {
|
||||
const angle_ = angle * Math.PI / 180;
|
||||
const cos = Math.cos(angle_);
|
||||
const sin = Math.sin(angle_);
|
||||
|
||||
// 先缩放,后旋转
|
||||
return [
|
||||
scale * cos, // a
|
||||
scale * sin, // b
|
||||
-scale * sin, // c
|
||||
scale * cos, // d
|
||||
0, // e (水平位移)
|
||||
0 // f (垂直位移)
|
||||
];
|
||||
}
|
||||
/**
|
||||
* 获取变换矩阵的缩放、旋转
|
||||
* @param {Array} Transform - 变换矩阵、
|
||||
* @returns {Object} 缩放、旋转角度 {scale, angle}
|
||||
*/
|
||||
export function getTransformScaleAngle(Transform) {
|
||||
const a = Transform[0];
|
||||
const b = Transform[1];
|
||||
const c = Transform[2];
|
||||
const d = Transform[3];
|
||||
const scale = Math.sqrt(a * a + b * b);
|
||||
const angle = Math.round(Math.atan2(b, a) * 180 / Math.PI);
|
||||
return { scale, angle };
|
||||
}
|
||||
|
||||
export class ObjectManager {
|
||||
stateManager: any
|
||||
canvasManager: any
|
||||
layerManager: any
|
||||
constructor(options) {
|
||||
this.stateManager = options.stateManager;
|
||||
this.canvasManager = options.canvasManager;
|
||||
this.layerManager = options.layerManager
|
||||
}
|
||||
onMounted() { }
|
||||
/** 设置平铺状态 */
|
||||
setFillRepeat(id: string) {
|
||||
const object = this.canvasManager.getObjectById(id)
|
||||
if (!object) return console.warn('设置平铺状态失败,对象不存在ID:', id)
|
||||
if (object.type !== 'image') return console.warn('设置平铺状态失败,对象不是图片类型:', id)
|
||||
|
||||
// 创建透明 Canvas
|
||||
const imgEl = object.getElement();
|
||||
const tcanvas = document.createElement('canvas');
|
||||
tcanvas.width = imgEl.width;
|
||||
tcanvas.height = imgEl.height;
|
||||
const ctx = tcanvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
|
||||
ctx.drawImage(imgEl, 0, 0);
|
||||
const fill = {
|
||||
source: FillSourceToBase64(imgEl),
|
||||
gapX: 0,
|
||||
gapY: 0,
|
||||
}
|
||||
const pattern = new fabric.Pattern({
|
||||
source: tcanvas,
|
||||
repeat: 'repeat',
|
||||
patternTransform: createPatternTransform(1, 0),
|
||||
offsetX: 0, // 水平偏移
|
||||
offsetY: 0, // 垂直偏移
|
||||
});
|
||||
const info = {
|
||||
...object.info,
|
||||
fill,
|
||||
}
|
||||
const rect = new fabric.Rect({
|
||||
width: object.width,
|
||||
height: object.height,
|
||||
top: object.top,
|
||||
left: object.left,
|
||||
angle: object.angle,
|
||||
scaleX: object.scaleX,
|
||||
scaleY: object.scaleY,
|
||||
flipX: object.flipX,
|
||||
flipY: object.flipY,
|
||||
originX: "left",
|
||||
originY: "top",
|
||||
info,
|
||||
});
|
||||
rect.set("fill", pattern)
|
||||
this.canvasManager.canvas.remove(object)
|
||||
this.canvasManager.add(rect)
|
||||
}
|
||||
/** 获取填充对象 */
|
||||
getFillRepeatObject(id: string) {
|
||||
const object = this.canvasManager.getObjectById(id)
|
||||
if (!object) {
|
||||
console.warn('getFillRepeatObject 对象不存在ID:', id)
|
||||
return null
|
||||
}
|
||||
if (object.type !== 'rect') {
|
||||
console.warn('getFillRepeatObject 对象不是矩形类型:', id)
|
||||
return null
|
||||
}
|
||||
if (object.fill?.repeat !== 'repeat') {
|
||||
console.warn('getFillRepeatObject 对象不是平铺填充类型:', id)
|
||||
return null
|
||||
}
|
||||
return object
|
||||
}
|
||||
/**
|
||||
* 修改平铺参数
|
||||
* @param id 目标对象ID
|
||||
* @param options 参数
|
||||
* @param options.scale 缩放比例
|
||||
* @param options.angle 旋转角度
|
||||
* @param options.offsetX 水平偏移
|
||||
* @param options.offsetY 垂直偏移
|
||||
*/
|
||||
async updateFillRepeatTransform(id: string, options: any, isRecord: boolean) {
|
||||
const object = this.getFillRepeatObject(id)
|
||||
if (!object) return null
|
||||
const fill = object.get("fill");
|
||||
const scale = options.hasOwnProperty('scale') ? options.scale : getTransformScaleAngle(fill.patternTransform).scale
|
||||
const angle = options.hasOwnProperty('angle') ? options.angle : getTransformScaleAngle(fill.patternTransform).angle
|
||||
const offsetX = options.hasOwnProperty('offsetX') ? options.offsetX : fill.offsetX
|
||||
const offsetY = options.hasOwnProperty('offsetY') ? options.offsetY : fill.offsetY
|
||||
fill.patternTransform = createPatternTransform(scale, angle)
|
||||
fill.offsetX = offsetX
|
||||
fill.offsetY = offsetY
|
||||
object.set("fill", new fabric.Pattern(fill));
|
||||
this.canvasManager.renderAll()
|
||||
if (isRecord) this.stateManager.recordState()
|
||||
}
|
||||
|
||||
/** 修改平铺间隙
|
||||
* @param id 目标对象ID
|
||||
* @param options 间隙参数
|
||||
* @param options.gapX 水平间隙
|
||||
* @param options.gapY 垂直间隙
|
||||
*/
|
||||
async updateFillRepeatGap(id: string, options: any, isRecord: boolean) {
|
||||
const object = this.getFillRepeatObject(id)
|
||||
if (!object) return null
|
||||
const gapX = options.hasOwnProperty('gapX') ? options.gapX : object.info.fill.gapX
|
||||
const gapY = options.hasOwnProperty('gapY') ? options.gapY : object.info.fill.gapY
|
||||
const fill = object.get("fill");
|
||||
const image = new Image();
|
||||
image.crossOrigin = "anonymous";
|
||||
image.src = object.info.fill.source;
|
||||
await image.decode();
|
||||
fill.source = imageAddGapToCanvas(image, gapX, gapY)
|
||||
object.info.fill.gapX = gapX
|
||||
object.info.fill.gapY = gapY
|
||||
object.set("fill", new fabric.Pattern(fill));
|
||||
this.canvasManager.renderAll()
|
||||
if (isRecord) this.stateManager.recordState()
|
||||
}
|
||||
|
||||
dispose() { }
|
||||
}
|
||||
@@ -3,6 +3,23 @@ import { ElMessageBox } from 'element-plus'
|
||||
import i18n from '@/lang'
|
||||
const t = i18n.global.t
|
||||
|
||||
class Event {
|
||||
list: any[]
|
||||
constructor() {
|
||||
this.list = []
|
||||
}
|
||||
add(name, call) {
|
||||
this.list.push({ name, call })
|
||||
}
|
||||
remove(name, call) {
|
||||
this.list = this.list.filter(item => item.name != name && item.call != call)
|
||||
}
|
||||
emit(name, data) {
|
||||
this.list.forEach(item => {
|
||||
if (item.name == name) item.call(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class StateManager {
|
||||
// 历史记录-撤回/重做
|
||||
@@ -11,6 +28,8 @@ export class StateManager {
|
||||
historyIndex: any
|
||||
running: any
|
||||
|
||||
event: Event
|
||||
|
||||
// 管理器
|
||||
canvasManager: any
|
||||
layerManager: any
|
||||
@@ -18,6 +37,7 @@ export class StateManager {
|
||||
toolManager: any
|
||||
brushManager: any
|
||||
keyEventManager: any
|
||||
objectManager: any
|
||||
// 设置管理器
|
||||
setManager(options) {
|
||||
options.eventManager && (this.eventManager = options.eventManager)
|
||||
@@ -26,16 +46,16 @@ export class StateManager {
|
||||
options.toolManager && (this.toolManager = options.toolManager)
|
||||
options.brushManager && (this.brushManager = options.brushManager)
|
||||
options.keyEventManager && (this.keyEventManager = options.keyEventManager)
|
||||
options.objectManager && (this.objectManager = options.objectManager)
|
||||
}
|
||||
constructor(options) {
|
||||
this.mxHistory = ref(50)
|
||||
this.historyList = ref([])
|
||||
this.historyIndex = ref(0)
|
||||
this.running = ref(false)
|
||||
|
||||
|
||||
this.event = new Event()
|
||||
}
|
||||
|
||||
onMounted() { }
|
||||
|
||||
/** 设置是否开始记录状态 */
|
||||
setIsRecord(isRecord: boolean) {
|
||||
@@ -73,6 +93,7 @@ export class StateManager {
|
||||
this.running.value = true
|
||||
this.historyIndex.value = index
|
||||
this.canvasManager.loadJSON(state.canvas, () => {
|
||||
this.event.emit('canvas:undo', state)
|
||||
this.running.value = false
|
||||
})
|
||||
}
|
||||
@@ -85,6 +106,7 @@ export class StateManager {
|
||||
this.running.value = true
|
||||
this.historyIndex.value = index
|
||||
this.canvasManager.loadJSON(state.canvas, () => {
|
||||
this.event.emit('canvas:redo', state)
|
||||
this.running.value = false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export class BrushRegistry {
|
||||
*/
|
||||
register(id, brushClass, metadata = {}) {
|
||||
if (this.brushes.has(id)) {
|
||||
console.warn(`笔刷 ${id} 已存在,请使用其他ID`);
|
||||
// console.warn(`笔刷 ${id} 已存在,请使用其他ID`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -708,34 +708,34 @@ export class CanvasEventManager {
|
||||
|
||||
const updateLayers = (e) => {
|
||||
if (e.target._objects) return;
|
||||
this.layerManager.updateLayers();
|
||||
// this.layerManager.updateLayers();// 先不用数据大了非常卡
|
||||
};
|
||||
|
||||
// 添加对象开始变换时的状态捕获
|
||||
this.canvas.on("object:moving", (e) => {
|
||||
// console.log("object:moving", e);
|
||||
updateLayers(e);
|
||||
// updateLayers(e);
|
||||
});
|
||||
this.canvas.on("object:scaling", (e) => {
|
||||
// console.log("object:scaling", e);
|
||||
updateLayers(e);
|
||||
// updateLayers(e);
|
||||
});
|
||||
this.canvas.on("object:rotating", (e) => {
|
||||
// console.log("object:rotating", e);
|
||||
updateLayers(e);
|
||||
// updateLayers(e);
|
||||
});
|
||||
this.canvas.on("object:skewing", (e) => {
|
||||
// console.log("object:skewing", e);
|
||||
updateLayers(e);
|
||||
// updateLayers(e);
|
||||
});
|
||||
this.canvas.on("object:modified", async (e) => {
|
||||
updateLayers(e);
|
||||
// updateLayers(e);
|
||||
const id = e.target?.info?.id;
|
||||
if (id) await this.layerManager.updateLayerThumbnailsById(id)
|
||||
this.stateManager.recordState();
|
||||
});
|
||||
this.canvas.on("object:removed", (e) => {
|
||||
updateLayers(e);
|
||||
// updateLayers(e);
|
||||
});
|
||||
}
|
||||
setupDoubleClickEvents() {
|
||||
|
||||
@@ -4,6 +4,7 @@ export class KeyEventManager {
|
||||
this.stateManager = options.stateManager;
|
||||
this.registerEvents()
|
||||
}
|
||||
onMounted() { }
|
||||
|
||||
/** 处理键盘事件 */
|
||||
handleKeyDown(event: any) {
|
||||
|
||||
Reference in New Issue
Block a user