360 lines
9.8 KiB
JavaScript
360 lines
9.8 KiB
JavaScript
|
|
/**
|
|||
|
|
* 笔刷基类
|
|||
|
|
* 所有笔刷类型应继承此基类并实现必要的方法
|
|||
|
|
*/
|
|||
|
|
export class BaseBrush {
|
|||
|
|
/**
|
|||
|
|
* 构造函数
|
|||
|
|
* @param {Object} canvas fabric画布实例
|
|||
|
|
* @param {Object} options 笔刷配置选项
|
|||
|
|
* @param {Object} t 翻译函数
|
|||
|
|
*/
|
|||
|
|
constructor(canvas, options = {}) {
|
|||
|
|
this.canvas = canvas;
|
|||
|
|
this.options = options;
|
|||
|
|
this.t = options.t;
|
|||
|
|
// 基本属性
|
|||
|
|
this.id = options.id || this.constructor.name;
|
|||
|
|
this.name = options.name || "未命名笔刷";
|
|||
|
|
this.description = options.description || "";
|
|||
|
|
this.icon = options.icon || null;
|
|||
|
|
this.category = options.category || "默认";
|
|||
|
|
|
|||
|
|
// 笔刷实例
|
|||
|
|
this.brush = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建笔刷实例(必须由子类实现)
|
|||
|
|
* @returns {Object} fabric笔刷实例
|
|||
|
|
*/
|
|||
|
|
create() {
|
|||
|
|
throw new Error("必须由子类实现create方法");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 配置笔刷(必须由子类实现)
|
|||
|
|
* @param {Object} brush fabric笔刷实例
|
|||
|
|
* @param {Object} options 配置选项
|
|||
|
|
*/
|
|||
|
|
configure(brush, options = {}) {
|
|||
|
|
if (!brush) return;
|
|||
|
|
|
|||
|
|
// 基础属性配置
|
|||
|
|
if (options.width !== undefined) {
|
|||
|
|
brush.width = options.width;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (options.color !== undefined && options.opacity !== undefined) {
|
|||
|
|
// 使用RGBA颜色而不是设置globalAlpha
|
|||
|
|
brush.color = this._createRGBAColor(options.color, options.opacity);
|
|||
|
|
brush.opacity = 1; // 保持fabric层面的opacity为1
|
|||
|
|
} else if (options.color !== undefined) {
|
|||
|
|
brush.color = options.color;
|
|||
|
|
} else if (options.opacity !== undefined) {
|
|||
|
|
// 如果只设置了透明度,基于当前颜色创建RGBA
|
|||
|
|
const currentColor = brush.color || this.options.color || "#000000";
|
|||
|
|
brush.color = this._createRGBAColor(currentColor, options.opacity);
|
|||
|
|
brush.opacity = 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 配置阴影
|
|||
|
|
this.configureShadow(brush, options);
|
|||
|
|
|
|||
|
|
// 确保不使用globalAlpha,避免圆圈绘制问题
|
|||
|
|
if (brush.canvas && brush.canvas.contextTop) {
|
|||
|
|
brush.canvas.contextTop.globalAlpha = 1;
|
|||
|
|
brush.canvas.contextTop.lineCap = "round";
|
|||
|
|
brush.canvas.contextTop.lineJoin = "round";
|
|||
|
|
brush.canvas.contextTop.globalCompositeOperation = "source-over";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 配置笔刷阴影
|
|||
|
|
* @param {Object} brush fabric笔刷实例
|
|||
|
|
* @param {Object} options 配置选项
|
|||
|
|
*/
|
|||
|
|
configureShadow(brush, options = {}) {
|
|||
|
|
if (!brush) return;
|
|||
|
|
|
|||
|
|
// 简化的阴影配置获取方法
|
|||
|
|
let shadowConfig = null;
|
|||
|
|
|
|||
|
|
// 尝试从全局获取BrushStore(在Vue组件中已经导入)
|
|||
|
|
if (typeof window !== "undefined" && window.BrushStore) {
|
|||
|
|
shadowConfig = window.BrushStore.getShadowConfig();
|
|||
|
|
} else {
|
|||
|
|
// 如果没有全局BrushStore,尝试从选项中获取
|
|||
|
|
if (options.shadowEnabled) {
|
|||
|
|
shadowConfig = {
|
|||
|
|
color: options.shadowColor || "#000000",
|
|||
|
|
blur: options.shadowWidth || 0,
|
|||
|
|
offsetX: options.shadowOffsetX || 0,
|
|||
|
|
offsetY: options.shadowOffsetY || 0,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (shadowConfig) {
|
|||
|
|
// 创建fabric.Shadow实例
|
|||
|
|
if (typeof fabric !== "undefined" && fabric.Shadow) {
|
|||
|
|
brush.shadow = new fabric.Shadow(shadowConfig);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 清除阴影
|
|||
|
|
brush.shadow = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新笔刷阴影设置
|
|||
|
|
*/
|
|||
|
|
updateShadow() {
|
|||
|
|
if (this.brush) {
|
|||
|
|
this.configureShadow(this.brush);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建RGBA颜色字符串
|
|||
|
|
* @private
|
|||
|
|
* @param {String} color 十六进制颜色或已有颜色
|
|||
|
|
* @param {Number} opacity 透明度 (0-1)
|
|||
|
|
* @returns {String} RGBA颜色字符串
|
|||
|
|
*/
|
|||
|
|
_createRGBAColor(color, opacity) {
|
|||
|
|
// 如果已经是rgba颜色,先提取RGB部分
|
|||
|
|
if (color.startsWith("rgba")) {
|
|||
|
|
const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
|
|||
|
|
if (rgbaMatch) {
|
|||
|
|
const [, r, g, b] = rgbaMatch;
|
|||
|
|
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果是rgb颜色,提取RGB部分
|
|||
|
|
if (color.startsWith("rgb")) {
|
|||
|
|
const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|||
|
|
if (rgbMatch) {
|
|||
|
|
const [, r, g, b] = rgbMatch;
|
|||
|
|
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理十六进制颜色
|
|||
|
|
if (color.startsWith("#")) {
|
|||
|
|
const hex = color.replace("#", "");
|
|||
|
|
let r, g, b;
|
|||
|
|
|
|||
|
|
if (hex.length === 3) {
|
|||
|
|
r = parseInt(hex[0] + hex[0], 16);
|
|||
|
|
g = parseInt(hex[1] + hex[1], 16);
|
|||
|
|
b = parseInt(hex[2] + hex[2], 16);
|
|||
|
|
} else if (hex.length === 6) {
|
|||
|
|
r = parseInt(hex.substring(0, 2), 16);
|
|||
|
|
g = parseInt(hex.substring(2, 4), 16);
|
|||
|
|
b = parseInt(hex.substring(4, 6), 16);
|
|||
|
|
} else {
|
|||
|
|
// 无效的十六进制颜色,使用默认
|
|||
|
|
r = g = b = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果是其他格式的颜色,尝试转换(例如颜色名称)
|
|||
|
|
// 这里简化处理,实际项目中可以使用更复杂的颜色解析
|
|||
|
|
return color; // fallback到原颜色
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取笔刷的元数据
|
|||
|
|
* @returns {Object} 笔刷元数据
|
|||
|
|
*/
|
|||
|
|
getMetadata() {
|
|||
|
|
return {
|
|||
|
|
id: this.id,
|
|||
|
|
name: this.name,
|
|||
|
|
description: this.description,
|
|||
|
|
icon: this.icon,
|
|||
|
|
category: this.category,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取笔刷预览
|
|||
|
|
* @returns {String|null} 预览图URL或null
|
|||
|
|
*/
|
|||
|
|
getPreview() {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取笔刷可配置属性
|
|||
|
|
* 这个方法返回一个对象数组,每个对象描述一个可配置属性
|
|||
|
|
* 每个属性对象包含:
|
|||
|
|
* - id: 属性标识符
|
|||
|
|
* - name: 属性显示名称
|
|||
|
|
* - type: 属性类型(例如:'slider', 'color', 'checkbox', 'select')
|
|||
|
|
* - defaultValue: 默认值
|
|||
|
|
* - min/max/step: 对于slider类型的限制值
|
|||
|
|
* - options: 对于select类型的选项
|
|||
|
|
* - description: 属性描述
|
|||
|
|
* - category: 属性分类
|
|||
|
|
* - order: 显示顺序(越小越靠前)
|
|||
|
|
* - visibleWhen: 函数或对象,定义何时显示该属性
|
|||
|
|
* - dynamicOptions: 函数,返回动态的选项列表
|
|||
|
|
* @returns {Array} 可配置属性描述数组
|
|||
|
|
*/
|
|||
|
|
getConfigurableProperties() {
|
|||
|
|
// 返回基础属性,所有笔刷都有这些属性
|
|||
|
|
return [
|
|||
|
|
{
|
|||
|
|
id: "size",
|
|||
|
|
name: this.t('Canvas.BrushSize'),
|
|||
|
|
type: "slider",
|
|||
|
|
defaultValue: 5,
|
|||
|
|
min: 0.5,
|
|||
|
|
max: 100,
|
|||
|
|
step: 0.5,
|
|||
|
|
description: this.t('Canvas.BrushDeSize'),
|
|||
|
|
category: this.t('Canvas.basic'),
|
|||
|
|
order: 10,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "color",
|
|||
|
|
name: this.t('Canvas.BrushColor'),
|
|||
|
|
type: "color",
|
|||
|
|
defaultValue: "#000000",
|
|||
|
|
description: this.t('Canvas.BrushDeColor'),
|
|||
|
|
category: this.t('Canvas.basic'),
|
|||
|
|
order: 20,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "opacity",
|
|||
|
|
name: this.t('Canvas.BrushOpacity'),
|
|||
|
|
type: "slider",
|
|||
|
|
defaultValue: 1,
|
|||
|
|
min: 0.05,
|
|||
|
|
max: 1,
|
|||
|
|
step: 0.01,
|
|||
|
|
description: this.t('Canvas.BrushdeOpacity'),
|
|||
|
|
category: this.t('Canvas.basic'),
|
|||
|
|
order: 30,
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 合并特有属性与基本属性
|
|||
|
|
* 子类应该调用此方法来合并自身特有属性与基类提供的基本属性
|
|||
|
|
* @param {Array} specificProperties 特有属性数组
|
|||
|
|
* @returns {Array} 合并后的属性数组
|
|||
|
|
*/
|
|||
|
|
mergeWithBaseProperties(specificProperties) {
|
|||
|
|
const baseProperties = super.getConfigurableProperties();
|
|||
|
|
|
|||
|
|
// 过滤掉同名属性(子类优先)
|
|||
|
|
const basePropsFiltered = baseProperties.filter(
|
|||
|
|
(baseProp) => !specificProperties.some((specificProp) => specificProp.id === baseProp.id)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
return [...basePropsFiltered, ...specificProperties];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新笔刷属性
|
|||
|
|
* @param {String} propId 属性ID
|
|||
|
|
* @param {any} value 属性值
|
|||
|
|
* @returns {Boolean} 是否更新成功
|
|||
|
|
*/
|
|||
|
|
updateProperty(propId, value) {
|
|||
|
|
if (propId === "size") {
|
|||
|
|
if (this.brush) {
|
|||
|
|
this.brush.width = value;
|
|||
|
|
this.configure(this.brush, { width: value });
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
} else if (propId === "color") {
|
|||
|
|
if (this.brush) {
|
|||
|
|
this.brush.color = value;
|
|||
|
|
this.configure(this.brush, { color: value });
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
} else if (propId === "opacity") {
|
|||
|
|
if (this.brush) {
|
|||
|
|
this.brush.opacity = value;
|
|||
|
|
this.configure(this.brush, { opacity: value });
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查属性是否可见
|
|||
|
|
* @param {Object} property 属性对象
|
|||
|
|
* @param {Object} currentValues 当前所有属性的值
|
|||
|
|
* @returns {Boolean} 是否可见
|
|||
|
|
*/
|
|||
|
|
isPropertyVisible(property, currentValues) {
|
|||
|
|
// 如果没有visibleWhen条件,则始终显示
|
|||
|
|
if (!property.visibleWhen) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果visibleWhen是函数,则调用函数判断
|
|||
|
|
if (typeof property.visibleWhen === "function") {
|
|||
|
|
return property.visibleWhen(currentValues);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果visibleWhen是对象,检查条件是否满足
|
|||
|
|
if (typeof property.visibleWhen === "object") {
|
|||
|
|
for (const [key, value] of Object.entries(property.visibleWhen)) {
|
|||
|
|
if (currentValues[key] !== value) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取动态选项
|
|||
|
|
* @param {Object} property 属性对象
|
|||
|
|
* @param {Object} currentValues 当前所有属性的值
|
|||
|
|
* @returns {Array} 选项数组
|
|||
|
|
*/
|
|||
|
|
getDynamicOptions(property, currentValues) {
|
|||
|
|
if (property.dynamicOptions && typeof property.dynamicOptions === "function") {
|
|||
|
|
return property.dynamicOptions(currentValues);
|
|||
|
|
}
|
|||
|
|
return property.options || [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生命周期方法:笔刷被选中
|
|||
|
|
*/
|
|||
|
|
onSelected() {
|
|||
|
|
// 可由子类覆盖
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生命周期方法:笔刷被取消选中
|
|||
|
|
*/
|
|||
|
|
onDeselected() {
|
|||
|
|
// 可由子类覆盖
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 销毁笔刷实例并清理资源
|
|||
|
|
*/
|
|||
|
|
destroy() {
|
|||
|
|
this.brush = null;
|
|||
|
|
}
|
|||
|
|
}
|