feat: 裁剪组裁剪跟随选择组移动
This commit is contained in:
@@ -1,13 +1,12 @@
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { LayerType, OperationType, createBitmapLayer } from "./layerHelper";
|
||||
// 导入新的复合命令
|
||||
import { CreateImageLayerCommand } from "../commands/LayerCommands";
|
||||
// 导入新的命令
|
||||
import {
|
||||
ChangeFixedImageCommand,
|
||||
AddImageToLayerCommand,
|
||||
} from "../commands/LayerCommands";
|
||||
import { ChangeFixedImageCommand, AddImageToLayerCommand } from "../commands/LayerCommands";
|
||||
import { generateId } from "./helper";
|
||||
import { isBoolean } from "lodash-es";
|
||||
|
||||
/**
|
||||
* 加载并处理图片
|
||||
@@ -101,9 +100,7 @@ export async function createImageLayer({
|
||||
if (isBoolean(undoable)) createImageLayerCmd.undoable = undoable; // 是否撤销
|
||||
|
||||
// 执行复合命令
|
||||
const newLayerId = await layerManager.commandManager.execute(
|
||||
createImageLayerCmd
|
||||
);
|
||||
const newLayerId = await layerManager.commandManager.execute(createImageLayerCmd);
|
||||
|
||||
return newLayerId;
|
||||
} catch (error) {
|
||||
@@ -120,11 +117,7 @@ export async function createImageLayer({
|
||||
* @param {Object} options.fabricImage - 新的图像对象
|
||||
* @returns {Promise<boolean>} 是否成功更改
|
||||
*/
|
||||
export async function changeFixedImage({
|
||||
layerManager,
|
||||
fixedLayerId,
|
||||
fabricImage,
|
||||
} = {}) {
|
||||
export async function changeFixedImage({ layerManager, fixedLayerId, fabricImage } = {}) {
|
||||
if (!layerManager || !fixedLayerId || !fabricImage) {
|
||||
console.error("更改固定图层图像:参数无效");
|
||||
return false;
|
||||
@@ -141,9 +134,7 @@ export async function changeFixedImage({
|
||||
});
|
||||
|
||||
// 通过命令管理器执行
|
||||
const result = await layerManager.commandManager.execute(
|
||||
changeFixedImageCmd
|
||||
);
|
||||
const result = await layerManager.commandManager.execute(changeFixedImageCmd);
|
||||
|
||||
if (result) {
|
||||
console.log(`✅ 成功更改固定图层 "${fixedLayerId}" 的图像`);
|
||||
@@ -192,9 +183,7 @@ export async function addImageToLayer({
|
||||
});
|
||||
|
||||
// 通过命令管理器执行
|
||||
const resultLayerId = await layerManager.commandManager.execute(
|
||||
addImageToLayerCmd
|
||||
);
|
||||
const resultLayerId = await layerManager.commandManager.execute(addImageToLayerCmd);
|
||||
|
||||
if (resultLayerId) {
|
||||
if (targetLayerId) {
|
||||
@@ -219,10 +208,7 @@ export async function addImageToLayer({
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {Promise<string>} 新图层ID的Promise
|
||||
*/
|
||||
export function loadImageUrlToLayer(
|
||||
{ imageUrl, layerManager, canvas, toolManager },
|
||||
options = {}
|
||||
) {
|
||||
export function loadImageUrlToLayer({ imageUrl, layerManager, canvas, toolManager }, options = {}) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!imageUrl || !layerManager || !canvas) {
|
||||
reject(new Error("参数无效"));
|
||||
@@ -231,9 +217,7 @@ export function loadImageUrlToLayer(
|
||||
|
||||
try {
|
||||
// 查找背景图层以获取尺寸
|
||||
const bgLayer = layerManager.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
// 设置最大宽高为背景图层的尺寸
|
||||
const maxWidth = bgLayer?.canvasWidth || canvas.width;
|
||||
@@ -304,9 +288,7 @@ export function uploadImageAndCreateLayer(
|
||||
reader.onload = async (e) => {
|
||||
try {
|
||||
// 查找背景图层以获取尺寸
|
||||
const bgLayer = layerManager.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
// 设置最大宽高为背景图层的尺寸
|
||||
const maxWidth = bgLayer?.canvasWidth || canvas.width;
|
||||
@@ -361,9 +343,7 @@ export function safeLoadImage(imageSource, options = {}) {
|
||||
.then(resolve)
|
||||
.catch((error) => {
|
||||
if (attempt < retries) {
|
||||
console.warn(
|
||||
`图片加载失败,正在重试 (${attempt + 1}/${retries})...`
|
||||
);
|
||||
console.warn(`图片加载失败,正在重试 (${attempt + 1}/${retries})...`);
|
||||
setTimeout(() => attemptLoad(attempt + 1), 500);
|
||||
} else {
|
||||
reject(error);
|
||||
@@ -426,12 +406,7 @@ export function loadImageAndChangeFixedLayer({
|
||||
* @param {Object} options.imageOptions - 图片加载选项
|
||||
* @returns {Promise<string>} 新图像对象ID的Promise
|
||||
*/
|
||||
export function uploadImageAndChangeFixedLayer({
|
||||
file,
|
||||
layerManager,
|
||||
layerId,
|
||||
imageOptions = {},
|
||||
}) {
|
||||
export function uploadImageAndChangeFixedLayer({ file, layerManager, layerId, imageOptions = {} }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!file || !layerManager || !layerId) {
|
||||
reject(new Error("参数无效:需要文件、图层管理器和图层ID"));
|
||||
@@ -449,9 +424,7 @@ export function uploadImageAndChangeFixedLayer({
|
||||
reader.onload = async (e) => {
|
||||
try {
|
||||
// 查找目标固定图层以获取尺寸信息
|
||||
const targetLayer = layerManager.layers.value.find(
|
||||
(layer) => layer.id === layerId
|
||||
);
|
||||
const targetLayer = layerManager.layers.value.find((layer) => layer.id === layerId);
|
||||
|
||||
if (!targetLayer) {
|
||||
throw new Error(`找不到图层 ID: ${layerId}`);
|
||||
@@ -463,9 +436,7 @@ export function uploadImageAndChangeFixedLayer({
|
||||
}
|
||||
|
||||
// 查找背景图层以获取画布尺寸
|
||||
const bgLayer = layerManager.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
const maxWidth = bgLayer?.canvasWidth || layerManager.canvas.width;
|
||||
const maxHeight = bgLayer?.canvasHeight || layerManager.canvas.height;
|
||||
@@ -490,14 +461,10 @@ export function uploadImageAndChangeFixedLayer({
|
||||
});
|
||||
|
||||
// 通过命令管理器执行
|
||||
const newImageId = await layerManager.commandManager.execute(
|
||||
changeFixedImageCmd
|
||||
);
|
||||
const newImageId = await layerManager.commandManager.execute(changeFixedImageCmd);
|
||||
|
||||
if (newImageId) {
|
||||
console.log(
|
||||
`✅ 成功更改固定图层 "${targetLayer.name}" 的图像,新图像ID: ${newImageId}`
|
||||
);
|
||||
console.log(`✅ 成功更改固定图层 "${targetLayer.name}" 的图像,新图像ID: ${newImageId}`);
|
||||
resolve(newImageId);
|
||||
} else {
|
||||
throw new Error("更改固定图层图像失败");
|
||||
@@ -826,9 +793,7 @@ export class AdvancedImageManager {
|
||||
total: imageUrls.length,
|
||||
successful: results.filter((r) => r.success).length,
|
||||
failed: results.filter((r) => !r.success).length,
|
||||
cacheHitRate:
|
||||
this.performanceMetrics.cacheHits /
|
||||
this.performanceMetrics.imageLoads,
|
||||
cacheHitRate: this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads,
|
||||
averageLoadTime: this.performanceMetrics.averageLoadTime,
|
||||
},
|
||||
};
|
||||
@@ -884,13 +849,10 @@ export class AdvancedImageManager {
|
||||
// 立即执行模式
|
||||
for (const operation of operations) {
|
||||
try {
|
||||
const result = await this.canvasManager.changeFixedImage(
|
||||
operation.imageUrl,
|
||||
{
|
||||
targetLayerType: operation.layerType,
|
||||
...operation.options,
|
||||
}
|
||||
);
|
||||
const result = await this.canvasManager.changeFixedImage(operation.imageUrl, {
|
||||
targetLayerType: operation.layerType,
|
||||
...operation.options,
|
||||
});
|
||||
operationResults.push({ success: true, ...result, operation });
|
||||
} catch (error) {
|
||||
operationResults.push({
|
||||
@@ -1038,10 +1000,7 @@ export class AdvancedImageManager {
|
||||
}
|
||||
|
||||
// 替换模板中的变量
|
||||
const operations = this.replaceTemplateVariables(
|
||||
template.operations,
|
||||
variables
|
||||
);
|
||||
const operations = this.replaceTemplateVariables(template.operations, variables);
|
||||
|
||||
// 执行操作
|
||||
const result = await this.batchAddImagesToLayers(operations);
|
||||
@@ -1141,8 +1100,7 @@ export class AdvancedImageManager {
|
||||
this.performanceMetrics.imageLoads++;
|
||||
this.performanceMetrics.totalLoadTime += loadTime;
|
||||
this.performanceMetrics.averageLoadTime =
|
||||
this.performanceMetrics.totalLoadTime /
|
||||
this.performanceMetrics.imageLoads;
|
||||
this.performanceMetrics.totalLoadTime / this.performanceMetrics.imageLoads;
|
||||
}
|
||||
|
||||
getSummary(results) {
|
||||
@@ -1170,12 +1128,9 @@ export class AdvancedImageManager {
|
||||
// 替换字符串中的变量 {{variable}}
|
||||
Object.keys(newOp).forEach((key) => {
|
||||
if (typeof newOp[key] === "string") {
|
||||
newOp[key] = newOp[key].replace(
|
||||
/\{\{(\w+)\}\}/g,
|
||||
(match, varName) => {
|
||||
return variables[varName] || match;
|
||||
}
|
||||
);
|
||||
newOp[key] = newOp[key].replace(/\{\{(\w+)\}\}/g, (match, varName) => {
|
||||
return variables[varName] || match;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1223,10 +1178,7 @@ export class AdvancedImageManager {
|
||||
generatePerformanceRecommendations() {
|
||||
const recommendations = [];
|
||||
|
||||
if (
|
||||
this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads <
|
||||
0.3
|
||||
) {
|
||||
if (this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads < 0.3) {
|
||||
recommendations.push("考虑增加缓存大小以提高缓存命中率");
|
||||
}
|
||||
|
||||
@@ -1298,12 +1250,7 @@ export const ImageUtils = {
|
||||
* @param {string} targetLayerId - 目标图层ID (可选)
|
||||
* @returns {Promise<Object>} 执行结果
|
||||
*/
|
||||
quickAddImageToLayer: (
|
||||
file,
|
||||
layerManager,
|
||||
toolManager,
|
||||
targetLayerId = null
|
||||
) => {
|
||||
quickAddImageToLayer: (file, layerManager, toolManager, targetLayerId = null) => {
|
||||
return uploadImageAndAddToLayerSimple({
|
||||
file,
|
||||
layerManager,
|
||||
@@ -1376,12 +1323,7 @@ export function rasterizeCanvasObjects(options = {}) {
|
||||
function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const {
|
||||
trimWhitespace = true,
|
||||
trimPadding = 10,
|
||||
quality = 1,
|
||||
format = "png",
|
||||
} = options;
|
||||
const { trimWhitespace = true, trimPadding = 10, quality = 1, format = "png" } = options;
|
||||
|
||||
// 保存原始状态
|
||||
const originalObjects = canvas.getObjects();
|
||||
@@ -1389,9 +1331,7 @@ function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
|
||||
const originalZoom = canvas.getZoom();
|
||||
|
||||
// 临时隐藏其他对象,只显示要栅格化的对象
|
||||
const objectsToHide = originalObjects.filter(
|
||||
(obj) => !objects.includes(obj)
|
||||
);
|
||||
const objectsToHide = originalObjects.filter((obj) => !objects.includes(obj));
|
||||
|
||||
// 隐藏不需要的对象
|
||||
objectsToHide.forEach((obj) => {
|
||||
@@ -1415,9 +1355,7 @@ function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
|
||||
const pixelRatio = canvas.getRetinaScaling();
|
||||
|
||||
// 复制画布元素(这会保持所有变换状态)
|
||||
const copiedCanvas = fabric.util.copyCanvasElement(
|
||||
canvas.lowerCanvasEl
|
||||
);
|
||||
const copiedCanvas = fabric.util.copyCanvasElement(canvas.lowerCanvasEl);
|
||||
|
||||
let finalCanvas = copiedCanvas;
|
||||
let trimOffset = { x: 0, y: 0 };
|
||||
@@ -1496,7 +1434,7 @@ function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
|
||||
*/
|
||||
function _restoreObjectVisibility(objects) {
|
||||
objects.forEach((obj) => {
|
||||
if (obj.hasOwnProperty("_originalVisible")) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, "_originalVisible")) {
|
||||
obj.set("visible", obj._originalVisible);
|
||||
delete obj._originalVisible;
|
||||
}
|
||||
@@ -1521,9 +1459,7 @@ function _rasterizeUsingDataURL(canvas, objects, options = {}) {
|
||||
const originalObjects = canvas.getObjects();
|
||||
|
||||
// 临时移除其他对象
|
||||
const objectsToRemove = originalObjects.filter(
|
||||
(obj) => !objects.includes(obj)
|
||||
);
|
||||
const objectsToRemove = originalObjects.filter((obj) => !objects.includes(obj));
|
||||
objectsToRemove.forEach((obj) => {
|
||||
canvas.remove(obj);
|
||||
});
|
||||
@@ -1770,12 +1706,7 @@ function _trimCanvas(canvas, padding = 0) {
|
||||
trimmedCanvas.height = trimHeight;
|
||||
|
||||
// 复制裁剪区域(包含padding)
|
||||
const trimmedImageData = ctx.getImageData(
|
||||
paddedMinX,
|
||||
paddedMinY,
|
||||
trimWidth,
|
||||
trimHeight
|
||||
);
|
||||
const trimmedImageData = ctx.getImageData(paddedMinX, paddedMinY, trimWidth, trimHeight);
|
||||
trimmedCtx.putImageData(trimmedImageData, 0, 0);
|
||||
|
||||
return {
|
||||
@@ -1955,8 +1886,7 @@ export function smartRasterize(options = {}) {
|
||||
function _analyzeRasterizationContext(canvas, objects) {
|
||||
const zoom = canvas.getZoom();
|
||||
const viewportTransform = canvas.viewportTransform;
|
||||
const hasTransform =
|
||||
zoom !== 1 || viewportTransform[4] !== 0 || viewportTransform[5] !== 0;
|
||||
const hasTransform = zoom !== 1 || viewportTransform[4] !== 0 || viewportTransform[5] !== 0;
|
||||
|
||||
// 分析对象类型分布
|
||||
const objectTypes = objects.reduce((acc, obj) => {
|
||||
@@ -1971,9 +1901,7 @@ function _analyzeRasterizationContext(canvas, objects) {
|
||||
// 计算画布利用率
|
||||
const canvasArea = canvas.width * canvas.height;
|
||||
const objectsBounds = _calculateObjectsBounds(objects);
|
||||
const objectsArea = objectsBounds
|
||||
? objectsBounds.width * objectsBounds.height
|
||||
: 0;
|
||||
const objectsArea = objectsBounds ? objectsBounds.width * objectsBounds.height : 0;
|
||||
const utilization = objectsArea / canvasArea;
|
||||
|
||||
return {
|
||||
@@ -1985,9 +1913,7 @@ function _analyzeRasterizationContext(canvas, objects) {
|
||||
utilization,
|
||||
canvasSize: { width: canvas.width, height: canvas.height },
|
||||
objectsBounds,
|
||||
supportsCanvasCopy: !!(
|
||||
fabric.util.copyCanvasElement && canvas.lowerCanvasEl
|
||||
),
|
||||
supportsCanvasCopy: !!(fabric.util.copyCanvasElement && canvas.lowerCanvasEl),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2223,12 +2149,7 @@ function _getAlternativeMethods(analysis) {
|
||||
* @param {number} params.canvasWidth - 画布宽度
|
||||
* @param {number} params.canvasHeight - 画布高度
|
||||
*/
|
||||
export const imageModeHandler = ({
|
||||
imageMode,
|
||||
newImage,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
}) => {
|
||||
export const imageModeHandler = ({ imageMode, newImage, canvasWidth, canvasHeight }) => {
|
||||
switch (imageMode) {
|
||||
case "stretch":
|
||||
// 拉伸模式 - 填充整个画布
|
||||
@@ -2253,10 +2174,8 @@ export const imageModeHandler = ({
|
||||
// 这里可以添加裁剪逻辑,如果需要的话
|
||||
// 例如使用fabric.Image.clipPath来裁剪图像
|
||||
break;
|
||||
case "contains":
|
||||
// 包含模式 - 保证图像在画布内完整显示
|
||||
// 既要考虑画布的宽高比,也要考虑图像的宽高比
|
||||
// 图片缩放后要保证最长边能完全显示在画布内
|
||||
case "contains": {
|
||||
// 图片缩放后要保证最长边能完全显示在画布内 // 既要考虑画布的宽高比,也要考虑图像的宽高比 // 包含模式 - 保证图像在画布内完整显示
|
||||
const canvasAspect = canvasWidth / canvasHeight;
|
||||
const imageAspect = newImage.width / newImage.height;
|
||||
// 保证图像在画布内完整显示 - 既要考虑画布的宽高比,也要考虑图像的宽高比
|
||||
@@ -2269,5 +2188,6 @@ export const imageModeHandler = ({
|
||||
newImage.scaleToHeight(canvasHeight);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user