画布印花合成
This commit is contained in:
BIN
src/assets/images/canvas/xiangao.png
Normal file
BIN
src/assets/images/canvas/xiangao.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
BIN
src/assets/images/canvas/xiangaofenge.png
Normal file
BIN
src/assets/images/canvas/xiangaofenge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
BIN
src/assets/images/canvas/yinhua1.jpg
Normal file
BIN
src/assets/images/canvas/yinhua1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
@@ -52,6 +52,7 @@ export class FillRepeatCommand extends Command {
|
|||||||
console.warn("当前对象不能平铺", object.type);
|
console.warn("当前对象不能平铺", object.type);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
console.log("===========", object.toObject(["id", "layerId", "layerName"]))
|
||||||
this.oldObjects = object;
|
this.oldObjects = object;
|
||||||
const img = await new Promise((resolve, reject) => {
|
const img = await new Promise((resolve, reject) => {
|
||||||
if (object.type === "rect") {
|
if (object.type === "rect") {
|
||||||
|
|||||||
@@ -4499,6 +4499,8 @@ export class SetColorLayerFillCommand extends Command {
|
|||||||
this.layer = this.layerManager?.getLayerById(this.object.layerId);
|
this.layer = this.layerManager?.getLayerById(this.object.layerId);
|
||||||
this.newFill = options.newFill;
|
this.newFill = options.newFill;
|
||||||
this.oldFill = JSON.parse(JSON.stringify(this.object.fill));
|
this.oldFill = JSON.parse(JSON.stringify(this.object.fill));
|
||||||
|
this.layer.blendMode = "multiply";
|
||||||
|
this.object.set("globalCompositeOperation", "multiply");
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(isUndo = false) {
|
async execute(isUndo = false) {
|
||||||
|
|||||||
@@ -884,6 +884,7 @@ const changeCanvas = async (command) => {
|
|||||||
...command, // 传递完整的命令数据
|
...command, // 传递完整的命令数据
|
||||||
};
|
};
|
||||||
emit("changeCanvas", commandData);
|
emit("changeCanvas", commandData);
|
||||||
|
canvasManager.changeCanvas(commandData);
|
||||||
if ((command.canUndo || command.canRedo) && props.enabledRedGreenMode) {
|
if ((command.canUndo || command.canRedo) && props.enabledRedGreenMode) {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
createLayer,
|
createLayer,
|
||||||
LayerType,
|
LayerType,
|
||||||
SpecialLayerId,
|
SpecialLayerId,
|
||||||
|
BlendMode,
|
||||||
} from "../utils/layerHelper";
|
} from "../utils/layerHelper";
|
||||||
import { ObjectMoveCommand } from "../commands/ObjectCommands";
|
import { ObjectMoveCommand } from "../commands/ObjectCommands";
|
||||||
import { AnimationManager } from "./animation/AnimationManager";
|
import { AnimationManager } from "./animation/AnimationManager";
|
||||||
@@ -30,6 +31,7 @@ import {
|
|||||||
fillToCssStyle,
|
fillToCssStyle,
|
||||||
calculateRotatedTopLeftDeg,
|
calculateRotatedTopLeftDeg,
|
||||||
createPatternTransform,
|
createPatternTransform,
|
||||||
|
base64ToCanvas,
|
||||||
} from "../utils/helper";
|
} from "../utils/helper";
|
||||||
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
|
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
|
||||||
import { isFunction } from "lodash-es";
|
import { isFunction } from "lodash-es";
|
||||||
@@ -564,15 +566,14 @@ export class CanvasManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新颜色层信息
|
// 更新颜色层信息
|
||||||
const fixedLayerObj = this.getFixedLayerObject();
|
// const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR);
|
||||||
const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR);
|
// if(colorObject){
|
||||||
if(colorObject && fixedLayerObj){
|
// await this.setObjecCliptInfo(colorObject);
|
||||||
await this.setColorObjectInfo(colorObject, fixedLayerObj);
|
// }
|
||||||
}
|
|
||||||
const groupLayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP);
|
const groupLayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP);
|
||||||
if(groupLayer && fixedLayerObj){
|
if(groupLayer){
|
||||||
const groupRect = new fabric.Rect({});
|
const groupRect = new fabric.Rect({});
|
||||||
await this.setColorObjectInfo(groupRect, fixedLayerObj);
|
await this.setObjecCliptInfo(groupRect);
|
||||||
groupLayer.clippingMask = groupRect.toObject();
|
groupLayer.clippingMask = groupRect.toObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -908,6 +909,7 @@ export class CanvasManager {
|
|||||||
* @param {Object} options 导出选项
|
* @param {Object} options 导出选项
|
||||||
* @param {Boolean} options.isContainBg 是否包含背景图层
|
* @param {Boolean} options.isContainBg 是否包含背景图层
|
||||||
* @param {Boolean} options.isContainFixed 是否包含固定图层
|
* @param {Boolean} options.isContainFixed 是否包含固定图层
|
||||||
|
* @param {Boolean} options.isContainFixedOther 是否包含其他固定图层
|
||||||
* @param {String} options.layerId 导出具体图层ID
|
* @param {String} options.layerId 导出具体图层ID
|
||||||
* @param {Array} options.layerIdArray 导出多个图层ID数组
|
* @param {Array} options.layerIdArray 导出多个图层ID数组
|
||||||
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
|
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
|
||||||
@@ -948,7 +950,7 @@ export class CanvasManager {
|
|||||||
const normalLayerIds =
|
const normalLayerIds =
|
||||||
this.layers?.value
|
this.layers?.value
|
||||||
?.filter(
|
?.filter(
|
||||||
(layer) => !layer.isBackground && !layer.isFixed && layer.visible
|
(layer) => !layer.isBackground && !layer.isFixed && !layer.isFixedOther && layer.visible
|
||||||
)
|
)
|
||||||
?.map((layer) => layer.id) || [];
|
?.map((layer) => layer.id) || [];
|
||||||
|
|
||||||
@@ -1233,7 +1235,7 @@ export class CanvasManager {
|
|||||||
// console.log("图层关联验证结果:", isValidate);
|
// console.log("图层关联验证结果:", isValidate);
|
||||||
// 排序
|
// 排序
|
||||||
// 使用LayerSort工具重新排列画布对象(如果可用)
|
// 使用LayerSort工具重新排列画布对象(如果可用)
|
||||||
await this?.layerManager?.layerSort?.rearrangeObjects();
|
// await this?.layerManager?.layerSort?.rearrangeObjects();
|
||||||
|
|
||||||
this.layerManager.activeLayerId.value = this.layers.value[0]
|
this.layerManager.activeLayerId.value = this.layers.value[0]
|
||||||
.children?.length
|
.children?.length
|
||||||
@@ -1304,10 +1306,15 @@ export class CanvasManager {
|
|||||||
})
|
})
|
||||||
await this.createPrintTrimsLayers(printTrimsLayers, singleLayers);
|
await this.createPrintTrimsLayers(printTrimsLayers, singleLayers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.changeCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setColorObjectInfo(colorRect, fixedLayerObj){
|
// 设置画布对象的裁剪信息
|
||||||
colorRect.set({
|
async setObjecCliptInfo(tagObject, data){
|
||||||
|
const fixedLayerObj = this.getFixedLayerObject();
|
||||||
|
if(!fixedLayerObj) return console.warn("固定图层为空");
|
||||||
|
tagObject.set({
|
||||||
top: fixedLayerObj.top,
|
top: fixedLayerObj.top,
|
||||||
left: fixedLayerObj.left,
|
left: fixedLayerObj.left,
|
||||||
width: fixedLayerObj.width,
|
width: fixedLayerObj.width,
|
||||||
@@ -1322,7 +1329,7 @@ export class CanvasManager {
|
|||||||
if(imageUrl){
|
if(imageUrl){
|
||||||
object = await new Promise((resolve, reject) => {
|
object = await new Promise((resolve, reject) => {
|
||||||
fabric.Image.fromURL(imageUrl, (imgObject) => {
|
fabric.Image.fromURL(imageUrl, (imgObject) => {
|
||||||
colorRect.set({
|
tagObject.set({
|
||||||
width: imgObject.width,
|
width: imgObject.width,
|
||||||
height: imgObject.height,
|
height: imgObject.height,
|
||||||
});
|
});
|
||||||
@@ -1330,14 +1337,14 @@ export class CanvasManager {
|
|||||||
}, { crossOrigin: "anonymous" });
|
}, { crossOrigin: "anonymous" });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const canvas = getObjectAlphaToCanvas(object);
|
const canvas = getObjectAlphaToCanvas(object, data);
|
||||||
const transparentMask = new fabric.Image(canvas, {
|
const transparentMask = new fabric.Image(canvas, {
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
originX: fixedLayerObj.originX,
|
originX: fixedLayerObj.originX,
|
||||||
originY: fixedLayerObj.originY,
|
originY: fixedLayerObj.originY,
|
||||||
});
|
});
|
||||||
colorRect.set('clipPath', transparentMask);
|
tagObject.set('clipPath', transparentMask);
|
||||||
}
|
}
|
||||||
async createColorLayer(color){
|
async createColorLayer(color){
|
||||||
if(!color) return console.warn("颜色为空不需要添加");
|
if(!color) return console.warn("颜色为空不需要添加");
|
||||||
@@ -1351,8 +1358,9 @@ export class CanvasManager {
|
|||||||
layerName: t("Canvas.color"),
|
layerName: t("Canvas.color"),
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isLocked: true,
|
isLocked: true,
|
||||||
|
globalCompositeOperation: BlendMode.MULTIPLY,
|
||||||
});
|
});
|
||||||
await this.setColorObjectInfo(colorRect, fixedLayerObj);
|
// await this.setObjecCliptInfo(colorRect);
|
||||||
const gradientObj = palletToFill(color);
|
const gradientObj = palletToFill(color);
|
||||||
const gradient = new fabric.Gradient({
|
const gradient = new fabric.Gradient({
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
@@ -1370,6 +1378,7 @@ export class CanvasManager {
|
|||||||
locked: colorRect.isLocked,
|
locked: colorRect.isLocked,
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
isFixedOther: true,
|
isFixedOther: true,
|
||||||
|
blendMode: BlendMode.MULTIPLY,
|
||||||
fabricObjects: [colorRect.toObject(["id", "layerId", "layerName"])],
|
fabricObjects: [colorRect.toObject(["id", "layerId", "layerName"])],
|
||||||
})
|
})
|
||||||
const groupIndex = this.layers.value.findIndex(layer => layer.isFixed || layer.isBackground);
|
const groupIndex = this.layers.value.findIndex(layer => layer.isFixed || layer.isBackground);
|
||||||
@@ -1503,7 +1512,7 @@ export class CanvasManager {
|
|||||||
children.push(layer);
|
children.push(layer);
|
||||||
}
|
}
|
||||||
const groupRect = new fabric.Rect({});
|
const groupRect = new fabric.Rect({});
|
||||||
await this.setColorObjectInfo(groupRect, fixedLayerObj);
|
await this.setObjecCliptInfo(groupRect);
|
||||||
// 插入组图层
|
// 插入组图层
|
||||||
const groupIndex = this.layers.value.findIndex(layer => layer.isFixedOther || layer.isFixed || layer.isBackground);
|
const groupIndex = this.layers.value.findIndex(layer => layer.isFixedOther || layer.isFixed || layer.isBackground);
|
||||||
const groupLayer = createLayer({
|
const groupLayer = createLayer({
|
||||||
@@ -1521,6 +1530,34 @@ export class CanvasManager {
|
|||||||
this.layers.value.splice(groupIndex, 0, groupLayer);
|
this.layers.value.splice(groupIndex, 0, groupLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
async changeCanvas(){
|
||||||
|
const fixedLayerObj = this.getFixedLayerObject();
|
||||||
|
if(!fixedLayerObj) return console.warn("固定图层对象不存在", fixedLayerObj)
|
||||||
|
const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR);
|
||||||
|
if(colorObject){
|
||||||
|
const ids = this.layerManager.getBlendModeLayerIds(SpecialLayerId.SPECIAL_GROUP);
|
||||||
|
if(ids.length === 0){
|
||||||
|
ids.unshift(SpecialLayerId.SPECIAL_GROUP);
|
||||||
|
await this.setObjecCliptInfo(colorObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const base64 = await this.exportManager.exportImage({layerIdArray2: ids, isEnhanceImg: true});
|
||||||
|
if(!base64) return console.warn("导出图片失败", base64)
|
||||||
|
const canvas = await base64ToCanvas(base64, fixedLayerObj.scaleX * 2, true);
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const width = fixedLayerObj.width;
|
||||||
|
const height = fixedLayerObj.height;
|
||||||
|
const x = (canvas.width - width) / 2;
|
||||||
|
const y = (canvas.height - height) / 2;
|
||||||
|
const data = ctx.getImageData(x, y, width, height);
|
||||||
|
await this.setObjecCliptInfo(colorObject, data);
|
||||||
|
this.canvas.renderAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 缩放红绿图模式内容以适应当前画布大小
|
* 缩放红绿图模式内容以适应当前画布大小
|
||||||
* 确保衣服底图和红绿图永远在画布内可见
|
* 确保衣服底图和红绿图永远在画布内可见
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ export class ExportManager {
|
|||||||
* @param {Object} options 导出选项
|
* @param {Object} options 导出选项
|
||||||
* @param {Boolean} options.isContainBg 是否包含背景图层
|
* @param {Boolean} options.isContainBg 是否包含背景图层
|
||||||
* @param {Boolean} options.isContainFixed 是否包含固定图层
|
* @param {Boolean} options.isContainFixed 是否包含固定图层
|
||||||
|
* @param {Boolean} options.isContainFixedOther 是否包含其他固定图层
|
||||||
* @param {Boolean} options.isCropByBg 是否使用背景大小裁剪
|
* @param {Boolean} options.isCropByBg 是否使用背景大小裁剪
|
||||||
* @param {String} options.layerId 导出具体图层ID
|
* @param {String} options.layerId 导出具体图层ID
|
||||||
* @param {Array} options.layerIdArray 导出多个图层ID数组
|
* @param {Array} options.layerIdArray 导出多个图层ID数组
|
||||||
|
* @param {Array} options.layerIdArray2 导出多个图层ID数组2
|
||||||
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
|
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
|
||||||
* @param {Boolean} options.restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
|
* @param {Boolean} options.restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
|
||||||
* @param {Boolean} options.isEnhanceImg 是否是增强图片
|
* @param {Boolean} options.isEnhanceImg 是否是增强图片
|
||||||
@@ -31,20 +33,22 @@ export class ExportManager {
|
|||||||
const {
|
const {
|
||||||
isContainBg = false,
|
isContainBg = false,
|
||||||
isContainFixed = false,
|
isContainFixed = false,
|
||||||
|
isContainFixedOther = false, // 是否包含其他固定图层
|
||||||
isCropByBg = false, // 是否使用背景大小裁剪
|
isCropByBg = false, // 是否使用背景大小裁剪
|
||||||
layerId = "",
|
layerId = "",
|
||||||
layerIdArray = [],
|
layerIdArray = [],
|
||||||
|
layerIdArray2 = null,
|
||||||
expPicType = "png",
|
expPicType = "png",
|
||||||
restoreOpacityInRedGreen = true,
|
restoreOpacityInRedGreen = true,
|
||||||
isEnhanceImg, // 是否是增强图片
|
isEnhanceImg, // 是否是增强图片
|
||||||
} = options;
|
} = options;
|
||||||
try {
|
try {
|
||||||
// 查找颜色图层并隐藏
|
// 查找颜色图层并隐藏
|
||||||
const colorLayer = this.layerManager.getLayerById(SpecialLayerId.COLOR);
|
// const colorLayer = this.layerManager.getLayerById(SpecialLayerId.COLOR);
|
||||||
if (colorLayer && colorLayer.visible) {
|
// if (colorLayer && colorLayer.visible) {
|
||||||
colorLayer.visible = false;
|
// colorLayer.visible = false;
|
||||||
await this.layerManager?.updateLayersObjectsInteractivity();
|
// await this.layerManager?.updateLayersObjectsInteractivity();
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 检查是否为红绿图模式
|
// 检查是否为红绿图模式
|
||||||
const isRedGreenMode = this.layerManager?.isInRedGreenMode?.() || false;
|
const isRedGreenMode = this.layerManager?.isInRedGreenMode?.() || false;
|
||||||
@@ -67,6 +71,7 @@ export class ExportManager {
|
|||||||
expPicType,
|
expPicType,
|
||||||
isContainBg,
|
isContainBg,
|
||||||
isContainFixed,
|
isContainFixed,
|
||||||
|
isContainFixedOther, // 是否包含其他固定图层
|
||||||
isRedGreenMode,
|
isRedGreenMode,
|
||||||
restoreOpacityInRedGreen,
|
restoreOpacityInRedGreen,
|
||||||
isCropByBg,
|
isCropByBg,
|
||||||
@@ -79,10 +84,12 @@ export class ExportManager {
|
|||||||
expPicType,
|
expPicType,
|
||||||
isContainBg,
|
isContainBg,
|
||||||
isContainFixed,
|
isContainFixed,
|
||||||
|
isContainFixedOther, // 是否包含其他固定图层
|
||||||
isRedGreenMode,
|
isRedGreenMode,
|
||||||
restoreOpacityInRedGreen,
|
restoreOpacityInRedGreen,
|
||||||
isCropByBg,
|
isCropByBg,
|
||||||
isEnhanceImg, // 是否是增强图片
|
isEnhanceImg, // 是否是增强图片
|
||||||
|
layerIdArray2,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("导出图片失败:", error);
|
console.error("导出图片失败:", error);
|
||||||
@@ -155,6 +162,7 @@ export class ExportManager {
|
|||||||
* @param {String} expPicType 导出类型
|
* @param {String} expPicType 导出类型
|
||||||
* @param {Boolean} isContainBg 是否包含背景图层
|
* @param {Boolean} isContainBg 是否包含背景图层
|
||||||
* @param {Boolean} isContainFixed 是否包含固定图层
|
* @param {Boolean} isContainFixed 是否包含固定图层
|
||||||
|
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
|
||||||
* @param {Boolean} isRedGreenMode 是否为红绿图模式
|
* @param {Boolean} isRedGreenMode 是否为红绿图模式
|
||||||
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
|
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
|
||||||
* @param {Boolean} isCropByBg 是否使用背景大小裁剪
|
* @param {Boolean} isCropByBg 是否使用背景大小裁剪
|
||||||
@@ -167,6 +175,7 @@ export class ExportManager {
|
|||||||
expPicType,
|
expPicType,
|
||||||
isContainBg,
|
isContainBg,
|
||||||
isContainFixed,
|
isContainFixed,
|
||||||
|
isContainFixedOther, // 是否包含其他固定图层
|
||||||
isRedGreenMode,
|
isRedGreenMode,
|
||||||
restoreOpacityInRedGreen,
|
restoreOpacityInRedGreen,
|
||||||
isCropByBg, // 是否使用背景大小裁剪
|
isCropByBg, // 是否使用背景大小裁剪
|
||||||
@@ -180,7 +189,8 @@ export class ExportManager {
|
|||||||
const objectsToExport = this._collectObjectsByLayerOrder(
|
const objectsToExport = this._collectObjectsByLayerOrder(
|
||||||
layerIdArray,
|
layerIdArray,
|
||||||
isContainBg,
|
isContainBg,
|
||||||
isContainFixed
|
isContainFixed,
|
||||||
|
isContainFixedOther, // 是否包含其他固定图层
|
||||||
);
|
);
|
||||||
|
|
||||||
if (objectsToExport.length === 0) {
|
if (objectsToExport.length === 0) {
|
||||||
@@ -212,10 +222,12 @@ export class ExportManager {
|
|||||||
* @param {String} expPicType 导出类型
|
* @param {String} expPicType 导出类型
|
||||||
* @param {Boolean} isContainBg 是否包含背景图层
|
* @param {Boolean} isContainBg 是否包含背景图层
|
||||||
* @param {Boolean} isContainFixed 是否包含固定图层
|
* @param {Boolean} isContainFixed 是否包含固定图层
|
||||||
|
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
|
||||||
* @param {Boolean} isRedGreenMode 是否为红绿图模式
|
* @param {Boolean} isRedGreenMode 是否为红绿图模式
|
||||||
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
|
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
|
||||||
* @param {Boolean} isCropByBg 是否使用背景大小裁剪
|
* @param {Boolean} isCropByBg 是否使用背景大小裁剪
|
||||||
* @param {Boolean} isEnhanceImg 是否是增强图片
|
* @param {Boolean} isEnhanceImg 是否是增强图片
|
||||||
|
* @param {Array} layerIdArray 导出多个图层ID数组2
|
||||||
* @returns {String} 图片数据URL
|
* @returns {String} 图片数据URL
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@@ -223,16 +235,19 @@ export class ExportManager {
|
|||||||
expPicType,
|
expPicType,
|
||||||
isContainBg,
|
isContainBg,
|
||||||
isContainFixed,
|
isContainFixed,
|
||||||
|
isContainFixedOther, // 是否包含其他固定图层
|
||||||
isRedGreenMode,
|
isRedGreenMode,
|
||||||
restoreOpacityInRedGreen,
|
restoreOpacityInRedGreen,
|
||||||
isCropByBg, // 是否使用背景大小裁剪
|
isCropByBg, // 是否使用背景大小裁剪
|
||||||
isEnhanceImg, // 是否是增强图片
|
isEnhanceImg, // 是否是增强图片
|
||||||
|
layerIdArray,
|
||||||
) {
|
) {
|
||||||
// 按图层顺序收集对象(从底到顶)
|
// 按图层顺序收集对象(从底到顶)
|
||||||
const objectsToExport = this._collectObjectsByLayerOrder(
|
const objectsToExport = this._collectObjectsByLayerOrder(
|
||||||
null, // 导出所有图层
|
layerIdArray, // 导出所有图层
|
||||||
isContainBg,
|
isContainBg,
|
||||||
isContainFixed,
|
isContainFixed,
|
||||||
|
isContainFixedOther, // 是否包含其他固定图层
|
||||||
);
|
);
|
||||||
|
|
||||||
if (objectsToExport.length === 0) {
|
if (objectsToExport.length === 0) {
|
||||||
@@ -389,10 +404,11 @@ export class ExportManager {
|
|||||||
* @param {Array|null} layerIdArray 图层ID数组,null表示所有图层
|
* @param {Array|null} layerIdArray 图层ID数组,null表示所有图层
|
||||||
* @param {Boolean} isContainBg 是否包含背景图层
|
* @param {Boolean} isContainBg 是否包含背景图层
|
||||||
* @param {Boolean} isContainFixed 是否包含固定图层
|
* @param {Boolean} isContainFixed 是否包含固定图层
|
||||||
|
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
|
||||||
* @returns {Array} 按正确顺序排列的真实对象数组
|
* @returns {Array} 按正确顺序排列的真实对象数组
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_collectObjectsByLayerOrder(layerIdArray, isContainBg, isContainFixed) {
|
_collectObjectsByLayerOrder(layerIdArray, isContainBg, isContainFixed, isContainFixedOther) {
|
||||||
const objectsToExport = [];
|
const objectsToExport = [];
|
||||||
const allLayers = this._getAllLayersFlattened(); // 获取扁平化的图层列表
|
const allLayers = this._getAllLayersFlattened(); // 获取扁平化的图层列表
|
||||||
|
|
||||||
@@ -404,7 +420,7 @@ export class ExportManager {
|
|||||||
if (layerIdArray && !layerIdArray.includes(layer.id)) continue;
|
if (layerIdArray && !layerIdArray.includes(layer.id)) continue;
|
||||||
|
|
||||||
// 检查图层类型过滤条件
|
// 检查图层类型过滤条件
|
||||||
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed))
|
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (layer.visible) {
|
if (layer.visible) {
|
||||||
@@ -1019,10 +1035,11 @@ export class ExportManager {
|
|||||||
* @param {Object} layer 图层对象
|
* @param {Object} layer 图层对象
|
||||||
* @param {Boolean} isContainBg 是否包含背景图层
|
* @param {Boolean} isContainBg 是否包含背景图层
|
||||||
* @param {Boolean} isContainFixed 是否包含固定图层
|
* @param {Boolean} isContainFixed 是否包含固定图层
|
||||||
|
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
|
||||||
* @returns {Boolean} 是否应该包含
|
* @returns {Boolean} 是否应该包含
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_shouldIncludeLayer(layer, isContainBg, isContainFixed) {
|
_shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther) {
|
||||||
if (!layer) return false;
|
if (!layer) return false;
|
||||||
|
|
||||||
// 检查背景图层
|
// 检查背景图层
|
||||||
@@ -1035,6 +1052,11 @@ export class ExportManager {
|
|||||||
return isContainFixed;
|
return isContainFixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查其他固定图层
|
||||||
|
if (layer.isFixedOther) {
|
||||||
|
return isContainFixedOther;
|
||||||
|
}
|
||||||
|
|
||||||
// 普通图层总是包含
|
// 普通图层总是包含
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3436,4 +3436,22 @@ export class LayerManager {
|
|||||||
|
|
||||||
console.log("🎨 已设置组遮罩移动同步 - 使用 object:modified 事件");
|
console.log("🎨 已设置组遮罩移动同步 - 使用 object:modified 事件");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取印花和颜色图层设置了blendMode的图层ID
|
||||||
|
* @returns {string[]} - 包含blendMode的图层ID数组
|
||||||
|
*/
|
||||||
|
getBlendModeLayerIds() {
|
||||||
|
const blendModeLayerIds = [];
|
||||||
|
this.layers.value.forEach(layer => {
|
||||||
|
if(layer.id === SpecialLayerId.SPECIAL_GROUP){
|
||||||
|
layer.children.forEach(child => {
|
||||||
|
if(child.blendMode && child.blendMode !== BlendMode.NORMAL){
|
||||||
|
blendModeLayerIds.push(child.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return blendModeLayerIds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -959,3 +959,32 @@ export function getTransformScaleAngle(Transform) {
|
|||||||
const angle = Math.round(Math.atan2(b, a) * 180 / Math.PI);
|
const angle = Math.round(Math.atan2(b, a) * 180 / Math.PI);
|
||||||
return { scale, angle };
|
return { scale, angle };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片转换为canvas
|
||||||
|
* @param {String} base64 - 图片base64编码
|
||||||
|
* @param {Number} scale - 缩放比例
|
||||||
|
* @param {Boolean} sr - 缩放反转,默认false
|
||||||
|
* @returns {Promise<HTMLCanvasElement>} canvas元素
|
||||||
|
*/
|
||||||
|
export async function base64ToCanvas(base64, scale = 1, sr = false) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const image = new Image();
|
||||||
|
image.src = base64;
|
||||||
|
image.crossOrigin = 'anonymous';
|
||||||
|
image.onload = () => {
|
||||||
|
image.width = image.width;
|
||||||
|
image.height = image.height;
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const width = (sr ? image.width / scale : image.width * scale);
|
||||||
|
const height = sr ? image.height / scale : image.height * scale;
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
ctx.drawImage(image, 0, 0, width, height);
|
||||||
|
resolve(canvas);
|
||||||
|
};
|
||||||
|
image.onerror = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,11 +61,14 @@ export async function restoreFabricObject(serializedObject, canvas) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取对象黑白通道画布
|
* 获取对象黑白通道画布
|
||||||
|
* @param {fabric.Object} object - 要处理的 fabric 对象
|
||||||
|
* @param {ImageData} revData - 相反的ImageData,白通道的相同位置是否为透明,revData为白色为透明,黑色为不透明
|
||||||
|
* @returns {HTMLCanvasElement|null} 包含黑白通道的画布,或 null 如果失败
|
||||||
*/
|
*/
|
||||||
export function getObjectAlphaToCanvas(object) {
|
export function getObjectAlphaToCanvas(object, revData) {
|
||||||
const image = object.getElement();
|
const image = object.getElement();
|
||||||
const { width, height } = image;
|
const { width, height } = image;
|
||||||
if(!width || !height){
|
if (!width || !height) {
|
||||||
console.warn("对象没有元素");
|
console.warn("对象没有元素");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -80,12 +83,23 @@ export function getObjectAlphaToCanvas(object) {
|
|||||||
const g = data.data[i + 1];
|
const g = data.data[i + 1];
|
||||||
const b = data.data[i + 2];
|
const b = data.data[i + 2];
|
||||||
const a = data.data[i + 3];
|
const a = data.data[i + 3];
|
||||||
|
const revR = revData?.data[i + 0] || 0;
|
||||||
|
const revG = revData?.data[i + 1] || 0;
|
||||||
|
const revB = revData?.data[i + 2] || 0;
|
||||||
|
const revA = revData?.data[i + 3] || 0;
|
||||||
if (r || g || b || a) {
|
if (r || g || b || a) {
|
||||||
data.data[i + 0] = 255;
|
if (revR || revG || revB || revA) {
|
||||||
data.data[i + 1] = 255;
|
data.data[i + 0] = 0;
|
||||||
data.data[i + 2] = 255;
|
data.data[i + 1] = 0;
|
||||||
data.data[i + 3] = 255;
|
data.data[i + 2] = 0;
|
||||||
}else{
|
data.data[i + 3] = 0;
|
||||||
|
} else {
|
||||||
|
data.data[i + 0] = 255;
|
||||||
|
data.data[i + 1] = 255;
|
||||||
|
data.data[i + 2] = 255;
|
||||||
|
data.data[i + 3] = 255;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
data.data[i + 0] = 0;
|
data.data[i + 0] = 0;
|
||||||
data.data[i + 1] = 0;
|
data.data[i + 1] = 0;
|
||||||
data.data[i + 2] = 0;
|
data.data[i + 2] = 0;
|
||||||
|
|||||||
@@ -184,10 +184,16 @@ const createClippedDataURLByCanvas = async ({
|
|||||||
console.log("🖼️ 使用图像遮罩裁剪方法生成DataURL");
|
console.log("🖼️ 使用图像遮罩裁剪方法生成DataURL");
|
||||||
|
|
||||||
// 使用优化后的边界计算,确保包含描边区域
|
// 使用优化后的边界计算,确保包含描边区域
|
||||||
const optimizedBounds = calculateOptimizedBounds(
|
// const optimizedBounds = calculateOptimizedBounds(
|
||||||
clippingObject,
|
// clippingObject,
|
||||||
fabricObjects
|
// fabricObjects
|
||||||
);
|
// );
|
||||||
|
const optimizedBounds = {
|
||||||
|
left: clippingObject.left - clippingObject.width / 2,
|
||||||
|
top: clippingObject.top - clippingObject.height / 2,
|
||||||
|
width: clippingObject.width,
|
||||||
|
height: clippingObject.height,
|
||||||
|
}
|
||||||
|
|
||||||
// 使用高分辨率以保证质量
|
// 使用高分辨率以保证质量
|
||||||
const pixelRatio = window.devicePixelRatio || 1;
|
const pixelRatio = window.devicePixelRatio || 1;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import ToolButton from "@/component/Canvas/ExistsImageList/ToolButton.vue";
|
|||||||
const canvasEditor = ref();
|
const canvasEditor = ref();
|
||||||
const currentView = ref("canvasEditor"); // 默认显示红绿图示例 canvasEditor redGreenExample
|
const currentView = ref("canvasEditor"); // 默认显示红绿图示例 canvasEditor redGreenExample
|
||||||
|
|
||||||
const clothingImageUrl = "https://www.minio-api.aida.com.hk/aida-collection-element/24299/Printboard/4eba03bd-4367-4c69-b1a3-3f3177a1be1f.jpg?response-content-type=image%2Fjpeg&response-content-disposition=inline&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20251217%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251217T081126Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=b8223ba37625370c9716024536a08ce1ee5c5a7aaefc47d9baf8bd1e0e4d2d91";
|
const clothingImageUrl = "/src/assets/images/canvas/xiangao.png";
|
||||||
const clothingImageUrlInit = "/src/assets/work/5.PNG";
|
const clothingImageUrlInit = "/src/assets/images/canvas/xiangaofenge.png";
|
||||||
|
|
||||||
const imageData = [
|
const imageData = [
|
||||||
{
|
{
|
||||||
@@ -71,7 +71,7 @@ const editorConfig = {
|
|||||||
const exportImage = async () => {
|
const exportImage = async () => {
|
||||||
if (canvasEditor.value) {
|
if (canvasEditor.value) {
|
||||||
const base64 = await canvasEditor.value.exportImage({
|
const base64 = await canvasEditor.value.exportImage({
|
||||||
isContainFixed: true, // 是否导出底图
|
isContainFixed: false, // 是否导出底图
|
||||||
isContainBg: false, // 是否导出背景
|
isContainBg: false, // 是否导出背景
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -272,6 +272,31 @@ const customToolsList = ref([
|
|||||||
class: "export-btn",
|
class: "export-btn",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
const otherData = {
|
||||||
|
color: {rgba: {r:255,g:0,b:0,a:1}},
|
||||||
|
printObject: {
|
||||||
|
prints: [
|
||||||
|
{
|
||||||
|
ifSingle: true,
|
||||||
|
level2Type: "Pattern",
|
||||||
|
designType: "Library",
|
||||||
|
path: "/src/assets/images/canvas/yinhua1.jpg",
|
||||||
|
location: [250, 780],
|
||||||
|
scale: [0.5 * 0.7, 0.272541 * 0.7],
|
||||||
|
angle: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ifSingle: true,
|
||||||
|
level2Type: "Pattern",
|
||||||
|
designType: "Library",
|
||||||
|
path: "/src/assets/images/canvas/yinhua1.jpg",
|
||||||
|
location: [300, 500],
|
||||||
|
scale: [0.5 * 0.4, 0.272541 * 0.4],
|
||||||
|
angle: 0,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -301,6 +326,8 @@ const customToolsList = ref([
|
|||||||
v-if="currentView === 'canvasEditor'"
|
v-if="currentView === 'canvasEditor'"
|
||||||
:config="editorConfig"
|
:config="editorConfig"
|
||||||
:clothingImageUrl="clothingImageUrl"
|
:clothingImageUrl="clothingImageUrl"
|
||||||
|
:clothingImageUrl2="clothingImageUrlInit"
|
||||||
|
:otherData="otherData"
|
||||||
:clothing-image-opts="{
|
:clothing-image-opts="{
|
||||||
imageMode: 'contains', // 设置底图包含在画布内
|
imageMode: 'contains', // 设置底图包含在画布内
|
||||||
}"
|
}"
|
||||||
|
|||||||
Reference in New Issue
Block a user