接入画布
This commit is contained in:
234
src/component/Canvas/CanvasEditor/managers/brushes/BaseBrush.js
Normal file
234
src/component/Canvas/CanvasEditor/managers/brushes/BaseBrush.js
Normal file
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* 笔刷基类
|
||||
* 所有笔刷类型应继承此基类并实现必要的方法
|
||||
*/
|
||||
export class BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 笔刷配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
this.canvas = canvas;
|
||||
this.options = options;
|
||||
|
||||
// 基本属性
|
||||
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) {
|
||||
throw new Error("必须由子类实现configure方法");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷的元数据
|
||||
* @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: "笔刷大小",
|
||||
type: "slider",
|
||||
defaultValue: 5,
|
||||
min: 0.5,
|
||||
max: 100,
|
||||
step: 0.5,
|
||||
description: "笔刷的大小(像素)",
|
||||
category: "基本",
|
||||
order: 10,
|
||||
},
|
||||
{
|
||||
id: "color",
|
||||
name: "笔刷颜色",
|
||||
type: "color",
|
||||
defaultValue: "#000000",
|
||||
description: "笔刷的颜色",
|
||||
category: "基本",
|
||||
order: 20,
|
||||
},
|
||||
{
|
||||
id: "opacity",
|
||||
name: "不透明度",
|
||||
type: "slider",
|
||||
defaultValue: 1,
|
||||
min: 0.05,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
description: "笔刷的不透明度",
|
||||
category: "基本",
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
} else if (propId === "color") {
|
||||
if (this.brush) {
|
||||
this.brush.color = value;
|
||||
return true;
|
||||
}
|
||||
} else if (propId === "opacity") {
|
||||
if (this.brush) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* 笔刷注册表
|
||||
* 用于注册、获取和管理所有笔刷
|
||||
*/
|
||||
export class BrushRegistry {
|
||||
constructor() {
|
||||
// 存储所有注册的笔刷类
|
||||
this.brushes = new Map();
|
||||
|
||||
// 按类别组织的笔刷
|
||||
this.brushesByCategory = new Map();
|
||||
|
||||
// 事件监听器
|
||||
this.listeners = {
|
||||
register: [],
|
||||
unregister: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个笔刷
|
||||
* @param {String} id 笔刷唯一标识
|
||||
* @param {Class} brushClass 笔刷类(需要继承BaseBrush)
|
||||
* @param {Object} metadata 笔刷元数据(可选)
|
||||
* @returns {Boolean} 是否注册成功
|
||||
*/
|
||||
register(id, brushClass, metadata = {}) {
|
||||
if (this.brushes.has(id)) {
|
||||
console.warn(`笔刷 ${id} 已存在,请使用其他ID`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 存储笔刷信息
|
||||
const brushInfo = {
|
||||
id,
|
||||
class: brushClass,
|
||||
metadata: {
|
||||
...metadata,
|
||||
id,
|
||||
},
|
||||
};
|
||||
|
||||
this.brushes.set(id, brushInfo);
|
||||
|
||||
// 添加到分类
|
||||
const category = metadata.category || "默认";
|
||||
if (!this.brushesByCategory.has(category)) {
|
||||
this.brushesByCategory.set(category, []);
|
||||
}
|
||||
this.brushesByCategory.get(category).push(brushInfo);
|
||||
|
||||
// 触发事件
|
||||
this._triggerEvent("register", brushInfo);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消注册笔刷
|
||||
* @param {String} id 笔刷ID
|
||||
* @returns {Boolean} 是否成功
|
||||
*/
|
||||
unregister(id) {
|
||||
if (!this.brushes.has(id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const brushInfo = this.brushes.get(id);
|
||||
this.brushes.delete(id);
|
||||
|
||||
// 从分类中移除
|
||||
const category = brushInfo.metadata.category || "默认";
|
||||
if (this.brushesByCategory.has(category)) {
|
||||
const brushes = this.brushesByCategory.get(category);
|
||||
const index = brushes.findIndex((b) => b.id === id);
|
||||
if (index !== -1) {
|
||||
brushes.splice(index, 1);
|
||||
}
|
||||
|
||||
// 如果分类为空,删除该分类
|
||||
if (brushes.length === 0) {
|
||||
this.brushesByCategory.delete(category);
|
||||
}
|
||||
}
|
||||
|
||||
// 触发事件
|
||||
this._triggerEvent("unregister", brushInfo);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷信息
|
||||
* @param {String} id 笔刷ID
|
||||
* @returns {Object|null} 笔刷信息或null
|
||||
*/
|
||||
getBrush(id) {
|
||||
return this.brushes.get(id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有笔刷
|
||||
* @returns {Array} 笔刷信息数组
|
||||
*/
|
||||
getAllBrushes() {
|
||||
return Array.from(this.brushes.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定分类的笔刷
|
||||
* @param {String} category 分类名称
|
||||
* @returns {Array} 笔刷信息数组
|
||||
*/
|
||||
getBrushesByCategory(category) {
|
||||
return this.brushesByCategory.get(category) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有分类
|
||||
* @returns {Array} 分类名称数组
|
||||
*/
|
||||
getCategories() {
|
||||
return Array.from(this.brushesByCategory.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个笔刷实例
|
||||
* @param {String} id 笔刷ID
|
||||
* @param {Object} canvas 画布实例
|
||||
* @param {Object} options 配置选项
|
||||
* @returns {Object|null} 笔刷实例或null
|
||||
*/
|
||||
createBrushInstance(id, canvas, options = {}) {
|
||||
const brushInfo = this.getBrush(id);
|
||||
if (!brushInfo) {
|
||||
console.error(`笔刷 ${id} 不存在`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建笔刷实例
|
||||
return new brushInfo.class(canvas, {
|
||||
...options,
|
||||
id: brushInfo.id,
|
||||
...brushInfo.metadata,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`创建笔刷 ${id} 失败:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
* @param {String} event 事件名称 ('register'|'unregister')
|
||||
* @param {Function} callback 回调函数
|
||||
*/
|
||||
addEventListener(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
* @param {String} event 事件名称
|
||||
* @param {Function} callback 回调函数
|
||||
*/
|
||||
removeEventListener(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
const index = this.listeners[event].indexOf(callback);
|
||||
if (index !== -1) {
|
||||
this.listeners[event].splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @param {String} event 事件名称
|
||||
* @param {*} data 事件数据
|
||||
* @private
|
||||
*/
|
||||
_triggerEvent(event, data) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].forEach((callback) => {
|
||||
try {
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
console.error(`执行 ${event} 事件监听器出错:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
export const brushRegistry = new BrushRegistry();
|
||||
|
||||
// 默认导出单例
|
||||
export default brushRegistry;
|
||||
@@ -0,0 +1,586 @@
|
||||
/**
|
||||
* 材质预设管理器
|
||||
* 负责管理所有材质预设,包括内置预设和用户自定义预设
|
||||
*/
|
||||
export class TexturePresetManager {
|
||||
constructor() {
|
||||
// 内置材质预设
|
||||
this.builtInTextures = [];
|
||||
|
||||
// 用户自定义材质
|
||||
this.customTextures = [];
|
||||
|
||||
// 材质分类
|
||||
this.categories = new Map();
|
||||
|
||||
// 材质缓存
|
||||
this.textureCache = new Map();
|
||||
|
||||
// 事件监听器
|
||||
this.listeners = {
|
||||
textureAdded: [],
|
||||
textureRemoved: [],
|
||||
textureUpdated: [],
|
||||
};
|
||||
|
||||
// 初始化内置材质
|
||||
this._initBuiltInTextures();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化内置材质预设
|
||||
* @private
|
||||
*/
|
||||
_initBuiltInTextures() {
|
||||
// 基于项目中的texture文件夹内容创建预设
|
||||
const textureList = [
|
||||
// 基础纹理
|
||||
{
|
||||
id: "texture0",
|
||||
name: "纸质纹理",
|
||||
category: "基础纹理",
|
||||
path: "/src/assets/texture/texture0.webp",
|
||||
},
|
||||
{
|
||||
id: "texture1",
|
||||
name: "粗糙表面",
|
||||
category: "基础纹理",
|
||||
path: "/src/assets/texture/texture1.webp",
|
||||
},
|
||||
{
|
||||
id: "texture2",
|
||||
name: "细腻纹理",
|
||||
category: "基础纹理",
|
||||
path: "/src/assets/texture/texture2.webp",
|
||||
},
|
||||
{
|
||||
id: "texture3",
|
||||
name: "颗粒质感",
|
||||
category: "基础纹理",
|
||||
path: "/src/assets/texture/texture3.webp",
|
||||
},
|
||||
{
|
||||
id: "texture4",
|
||||
name: "布料纹理",
|
||||
category: "基础纹理",
|
||||
path: "/src/assets/texture/texture4.webp",
|
||||
},
|
||||
{
|
||||
id: "texture5",
|
||||
name: "木质纹理",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture5.webp",
|
||||
},
|
||||
{
|
||||
id: "texture6",
|
||||
name: "石材纹理",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture6.webp",
|
||||
},
|
||||
{
|
||||
id: "texture7",
|
||||
name: "金属质感",
|
||||
category: "金属纹理",
|
||||
path: "/src/assets/texture/texture7.webp",
|
||||
},
|
||||
{
|
||||
id: "texture8",
|
||||
name: "皮革纹理",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture8.webp",
|
||||
},
|
||||
{
|
||||
id: "texture9",
|
||||
name: "水彩纸质",
|
||||
category: "艺术纹理",
|
||||
path: "/src/assets/texture/texture9.webp",
|
||||
},
|
||||
{
|
||||
id: "texture10",
|
||||
name: "画布纹理",
|
||||
category: "艺术纹理",
|
||||
path: "/src/assets/texture/texture10.webp",
|
||||
},
|
||||
{
|
||||
id: "texture11",
|
||||
name: "沙砾质感",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture11.webp",
|
||||
},
|
||||
{
|
||||
id: "texture12",
|
||||
name: "水波纹理",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture12.webp",
|
||||
},
|
||||
{
|
||||
id: "texture13",
|
||||
name: "云朵纹理",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture13.webp",
|
||||
},
|
||||
{
|
||||
id: "texture14",
|
||||
name: "火焰纹理",
|
||||
category: "特效纹理",
|
||||
path: "/src/assets/texture/texture14.webp",
|
||||
},
|
||||
{
|
||||
id: "texture15",
|
||||
name: "烟雾效果",
|
||||
category: "特效纹理",
|
||||
path: "/src/assets/texture/texture15.webp",
|
||||
},
|
||||
{
|
||||
id: "texture16",
|
||||
name: "星空纹理",
|
||||
category: "特效纹理",
|
||||
path: "/src/assets/texture/texture16.webp",
|
||||
},
|
||||
{
|
||||
id: "texture17",
|
||||
name: "大理石纹",
|
||||
category: "石材纹理",
|
||||
path: "/src/assets/texture/texture17.webp",
|
||||
},
|
||||
{
|
||||
id: "texture18",
|
||||
name: "花岗岩纹",
|
||||
category: "石材纹理",
|
||||
path: "/src/assets/texture/texture18.webp",
|
||||
},
|
||||
{
|
||||
id: "texture19",
|
||||
name: "竹纹理",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture19.webp",
|
||||
},
|
||||
{
|
||||
id: "texture20",
|
||||
name: "抽象图案",
|
||||
category: "艺术纹理",
|
||||
path: "/src/assets/texture/texture20.webp",
|
||||
},
|
||||
];
|
||||
|
||||
// 添加内置材质
|
||||
textureList.forEach((texture) => {
|
||||
this.builtInTextures.push({
|
||||
id: texture.id,
|
||||
name: texture.name,
|
||||
category: texture.category,
|
||||
path: texture.path,
|
||||
type: "builtin",
|
||||
preview: texture.path, // 使用原图作为预览
|
||||
description: `内置${texture.category} - ${texture.name}`,
|
||||
tags: [texture.category.replace("纹理", ""), "内置"],
|
||||
created: new Date().toISOString(),
|
||||
// 默认属性
|
||||
defaultSettings: {
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
repeat: "repeat",
|
||||
angle: 0,
|
||||
},
|
||||
});
|
||||
|
||||
// 添加到分类
|
||||
if (!this.categories.has(texture.category)) {
|
||||
this.categories.set(texture.category, []);
|
||||
}
|
||||
this.categories.get(texture.category).push(texture.id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有材质(内置 + 自定义)
|
||||
* @returns {Array} 材质数组
|
||||
*/
|
||||
getAllTextures() {
|
||||
return [...this.builtInTextures, ...this.customTextures];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取材质
|
||||
* @param {String} textureId 材质ID
|
||||
* @returns {Object|null} 材质对象
|
||||
*/
|
||||
getTextureById(textureId) {
|
||||
return (
|
||||
this.getAllTextures().find((texture) => texture.id === textureId) || null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分类获取材质
|
||||
* @param {String} category 分类名称
|
||||
* @returns {Array} 材质数组
|
||||
*/
|
||||
getTexturesByCategory(category) {
|
||||
return this.getAllTextures().filter(
|
||||
(texture) => texture.category === category
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有分类
|
||||
* @returns {Array} 分类名称数组
|
||||
*/
|
||||
getCategories() {
|
||||
const categories = new Set();
|
||||
this.getAllTextures().forEach((texture) => {
|
||||
categories.add(texture.category);
|
||||
});
|
||||
return Array.from(categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义材质
|
||||
* @param {Object} textureData 材质数据
|
||||
* @returns {String} 材质ID
|
||||
*/
|
||||
addCustomTexture(textureData) {
|
||||
const textureId =
|
||||
textureData.id ||
|
||||
`custom_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const texture = {
|
||||
id: textureId,
|
||||
name: textureData.name || "自定义材质",
|
||||
category: textureData.category || "自定义材质",
|
||||
path: textureData.path || textureData.dataUrl,
|
||||
type: "custom",
|
||||
preview: textureData.preview || textureData.path || textureData.dataUrl,
|
||||
description: textureData.description || "用户自定义材质",
|
||||
tags: textureData.tags || ["自定义"],
|
||||
created: new Date().toISOString(),
|
||||
defaultSettings: {
|
||||
scale: textureData.scale || 1,
|
||||
opacity: textureData.opacity || 1,
|
||||
repeat: textureData.repeat || "repeat",
|
||||
angle: textureData.angle || 0,
|
||||
...textureData.defaultSettings,
|
||||
},
|
||||
// 保存原始文件信息
|
||||
file: textureData.file || null,
|
||||
dataUrl: textureData.dataUrl || null,
|
||||
};
|
||||
|
||||
this.customTextures.push(texture);
|
||||
|
||||
// 添加到分类
|
||||
if (!this.categories.has(texture.category)) {
|
||||
this.categories.set(texture.category, []);
|
||||
}
|
||||
this.categories.get(texture.category).push(textureId);
|
||||
|
||||
// 触发事件
|
||||
this._triggerEvent("textureAdded", texture);
|
||||
|
||||
return textureId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除自定义材质
|
||||
* @param {String} textureId 材质ID
|
||||
* @returns {Boolean} 是否删除成功
|
||||
*/
|
||||
removeCustomTexture(textureId) {
|
||||
const index = this.customTextures.findIndex(
|
||||
(texture) => texture.id === textureId
|
||||
);
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const texture = this.customTextures[index];
|
||||
|
||||
// 只能删除自定义材质
|
||||
if (texture.type !== "custom") {
|
||||
console.warn("不能删除内置材质");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.customTextures.splice(index, 1);
|
||||
|
||||
// 从分类中移除
|
||||
if (this.categories.has(texture.category)) {
|
||||
const categoryTextures = this.categories.get(texture.category);
|
||||
const categoryIndex = categoryTextures.indexOf(textureId);
|
||||
if (categoryIndex !== -1) {
|
||||
categoryTextures.splice(categoryIndex, 1);
|
||||
}
|
||||
|
||||
// 如果分类为空且不是内置分类,删除分类
|
||||
if (categoryTextures.length === 0 && texture.category === "自定义材质") {
|
||||
this.categories.delete(texture.category);
|
||||
}
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
this.textureCache.delete(textureId);
|
||||
|
||||
// 触发事件
|
||||
this._triggerEvent("textureRemoved", texture);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新材质信息
|
||||
* @param {String} textureId 材质ID
|
||||
* @param {Object} updates 更新数据
|
||||
* @returns {Boolean} 是否更新成功
|
||||
*/
|
||||
updateTexture(textureId, updates) {
|
||||
const texture = this.getTextureById(textureId);
|
||||
if (!texture || texture.type === "builtin") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新材质属性
|
||||
Object.assign(texture, updates);
|
||||
|
||||
// 触发事件
|
||||
this._triggerEvent("textureUpdated", texture);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取材质预览URL
|
||||
* @param {Object} texture 材质对象
|
||||
* @returns {String} 预览URL
|
||||
*/
|
||||
getTexturePreviewUrl(texture) {
|
||||
if (!texture) return null;
|
||||
|
||||
// 如果有预览图,使用预览图
|
||||
if (texture.preview) {
|
||||
return texture.preview;
|
||||
}
|
||||
|
||||
// 否则使用原图
|
||||
return texture.path || texture.dataUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载材质图像
|
||||
* @param {String} textureId 材质ID
|
||||
* @returns {Promise<HTMLImageElement>} 图像对象
|
||||
*/
|
||||
loadTextureImage(textureId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 检查缓存
|
||||
if (this.textureCache.has(textureId)) {
|
||||
resolve(this.textureCache.get(textureId));
|
||||
return;
|
||||
}
|
||||
|
||||
const texture = this.getTextureById(textureId);
|
||||
if (!texture) {
|
||||
reject(new Error(`材质 ${textureId} 不存在`));
|
||||
return;
|
||||
}
|
||||
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
|
||||
img.onload = () => {
|
||||
// 缓存图像
|
||||
this.textureCache.set(textureId, img);
|
||||
resolve(img);
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
reject(new Error(`材质 ${textureId} 加载失败`));
|
||||
};
|
||||
|
||||
img.src = texture.path || texture.dataUrl;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索材质
|
||||
* @param {String} query 搜索关键词
|
||||
* @returns {Array} 匹配的材质数组
|
||||
*/
|
||||
searchTextures(query) {
|
||||
if (!query) return this.getAllTextures();
|
||||
|
||||
const searchTerm = query.toLowerCase();
|
||||
return this.getAllTextures().filter((texture) => {
|
||||
return (
|
||||
texture.name.toLowerCase().includes(searchTerm) ||
|
||||
texture.category.toLowerCase().includes(searchTerm) ||
|
||||
texture.description.toLowerCase().includes(searchTerm) ||
|
||||
texture.tags.some((tag) => tag.toLowerCase().includes(searchTerm))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存自定义材质到本地存储
|
||||
*/
|
||||
saveCustomTexturesToStorage() {
|
||||
try {
|
||||
const customTexturesData = this.customTextures.map((texture) => ({
|
||||
...texture,
|
||||
// 不保存file对象到localStorage
|
||||
file: null,
|
||||
}));
|
||||
|
||||
localStorage.setItem(
|
||||
"canvasEditor_customTextures",
|
||||
JSON.stringify(customTexturesData)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("保存自定义材质失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从本地存储加载自定义材质
|
||||
*/
|
||||
loadCustomTexturesFromStorage() {
|
||||
try {
|
||||
const stored = localStorage.getItem("canvasEditor_customTextures");
|
||||
if (stored) {
|
||||
const customTexturesData = JSON.parse(stored);
|
||||
this.customTextures = customTexturesData;
|
||||
|
||||
// 重建分类索引
|
||||
this.customTextures.forEach((texture) => {
|
||||
if (!this.categories.has(texture.category)) {
|
||||
this.categories.set(texture.category, []);
|
||||
}
|
||||
if (!this.categories.get(texture.category).includes(texture.id)) {
|
||||
this.categories.get(texture.category).push(texture.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载自定义材质失败:", error);
|
||||
this.customTextures = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建材质预设
|
||||
* @param {String} name 预设名称
|
||||
* @param {Object} settings 材质设置
|
||||
* @returns {String} 预设ID
|
||||
*/
|
||||
createTexturePreset(name, settings) {
|
||||
const presetId = `preset_${Date.now()}_${Math.random()
|
||||
.toString(36)
|
||||
.substr(2, 9)}`;
|
||||
|
||||
const preset = {
|
||||
id: presetId,
|
||||
name: name,
|
||||
type: "preset",
|
||||
category: "材质预设",
|
||||
created: new Date().toISOString(),
|
||||
settings: {
|
||||
textureId: settings.textureId,
|
||||
scale: settings.scale || 1,
|
||||
opacity: settings.opacity || 1,
|
||||
repeat: settings.repeat || "repeat",
|
||||
angle: settings.angle || 0,
|
||||
brushSize: settings.brushSize || 5,
|
||||
brushOpacity: settings.brushOpacity || 1,
|
||||
brushColor: settings.brushColor || "#000000",
|
||||
},
|
||||
};
|
||||
|
||||
this.customTextures.push(preset);
|
||||
this._triggerEvent("textureAdded", preset);
|
||||
|
||||
return presetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用材质预设
|
||||
* @param {String} presetId 预设ID
|
||||
* @returns {Object|null} 预设设置
|
||||
*/
|
||||
applyTexturePreset(presetId) {
|
||||
const preset = this.getTextureById(presetId);
|
||||
if (!preset || preset.type !== "preset") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return preset.settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
* @param {String} event 事件名称
|
||||
* @param {Function} callback 回调函数
|
||||
*/
|
||||
addEventListener(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
* @param {String} event 事件名称
|
||||
* @param {Function} callback 回调函数
|
||||
*/
|
||||
removeEventListener(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
const index = this.listeners[event].indexOf(callback);
|
||||
if (index !== -1) {
|
||||
this.listeners[event].splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @param {String} event 事件名称
|
||||
* @param {*} data 事件数据
|
||||
* @private
|
||||
*/
|
||||
_triggerEvent(event, data) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].forEach((callback) => {
|
||||
try {
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
console.error(`执行 ${event} 事件监听器出错:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有缓存
|
||||
*/
|
||||
clearCache() {
|
||||
this.textureCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
* @returns {Object} 统计信息
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
builtInCount: this.builtInTextures.length,
|
||||
customCount: this.customTextures.length,
|
||||
totalCount: this.getAllTextures().length,
|
||||
categoriesCount: this.getCategories().length,
|
||||
cacheSize: this.textureCache.size,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 创建单例实例
|
||||
const texturePresetManager = new TexturePresetManager();
|
||||
|
||||
// 导出单例
|
||||
export default texturePresetManager;
|
||||
@@ -0,0 +1,720 @@
|
||||
//import { fabric } from "fabric-with-all";
|
||||
import { BrushStore } from "../../store/BrushStore";
|
||||
import { brushRegistry } from "./BrushRegistry";
|
||||
|
||||
// 导入基础笔刷类型
|
||||
import { PencilBrush } from "./types/PencilBrush";
|
||||
import { TextureBrush } from "./types/TextureBrush";
|
||||
|
||||
// 导入集成的笔刷类型
|
||||
import { CrayonBrush } from "./types/CrayonBrush";
|
||||
import { FurBrush } from "./types/FurBrush";
|
||||
import { InkBrush } from "./types/InkBrush";
|
||||
import { LongfurBrush } from "./types/LongfurBrush";
|
||||
import { WritingBrush } from "./types/WritingBrush";
|
||||
import { MarkerBrush } from "./types/MarkerBrush";
|
||||
import { CustomPenBrush } from "./types/CustomPenBrush";
|
||||
import { RibbonBrush } from "./types/RibbonBrush";
|
||||
import { ShadedBrush } from "./types/ShadedBrush";
|
||||
import { SketchyBrush } from "./types/SketchyBrush";
|
||||
import { SpraypaintBrush } from "./types/SpraypaintBrush";
|
||||
|
||||
/**
|
||||
* 笔刷管理器
|
||||
* 负责管理和切换不同的笔刷类型
|
||||
*/
|
||||
export class BrushManager {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} options 配置选项
|
||||
* @param {Object} options.canvas fabric.js画布实例
|
||||
* @param {Object} options.brushStore 笔刷数据存储(可选)
|
||||
* @param {Object} options.layerManager 图层管理器实例(可选)
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this.canvas = options.canvas;
|
||||
this.brushStore = options.brushStore || BrushStore;
|
||||
this.layerManager = options.layerManager; // 添加图层管理器引用
|
||||
|
||||
// 当前活动笔刷
|
||||
this.activeBrush = null;
|
||||
this.activeBrushId = null;
|
||||
|
||||
// 初始化笔刷注册
|
||||
this._registerDefaultBrushes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册默认笔刷
|
||||
* @private
|
||||
*/
|
||||
_registerDefaultBrushes() {
|
||||
// 注册铅笔笔刷
|
||||
brushRegistry.register("pencil", PencilBrush, {
|
||||
name: "铅笔",
|
||||
description: "基础铅笔工具,适合精细线条绘制",
|
||||
category: "基础笔刷",
|
||||
});
|
||||
|
||||
// 注册材质笔刷
|
||||
brushRegistry.register("texture", TextureBrush);
|
||||
|
||||
// 注册集成的笔刷类型
|
||||
brushRegistry.register("crayon", CrayonBrush);
|
||||
brushRegistry.register("fur", FurBrush);
|
||||
brushRegistry.register("ink", InkBrush);
|
||||
brushRegistry.register("longfur", LongfurBrush);
|
||||
brushRegistry.register("writing", WritingBrush);
|
||||
brushRegistry.register("marker", MarkerBrush);
|
||||
brushRegistry.register("pen", CustomPenBrush);
|
||||
brushRegistry.register("ribbon", RibbonBrush);
|
||||
brushRegistry.register("shaded", ShadedBrush);
|
||||
brushRegistry.register("sketchy", SketchyBrush);
|
||||
brushRegistry.register("spraypaint", SpraypaintBrush);
|
||||
|
||||
// 注册喷枪笔刷
|
||||
brushRegistry.register(
|
||||
"spray",
|
||||
class SprayBrush extends PencilBrush {
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "spray",
|
||||
name: "喷枪",
|
||||
description: "模拟喷枪效果,创建散点效果",
|
||||
category: "基础笔刷",
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
this.brush = new fabric.SprayBrush(this.canvas);
|
||||
this.configure(this.brush, this.options);
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
configure(brush, options = {}) {
|
||||
super.configure(brush, options);
|
||||
|
||||
if (options.density !== undefined) {
|
||||
brush.density = options.density;
|
||||
}
|
||||
|
||||
if (options.randomOpacity !== undefined) {
|
||||
brush.randomOpacity = options.randomOpacity;
|
||||
}
|
||||
|
||||
if (options.dotWidth !== undefined) {
|
||||
brush.dotWidth = options.dotWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
// 注册橡皮擦笔刷
|
||||
brushRegistry.register(
|
||||
"eraser",
|
||||
class EraserBrush extends PencilBrush {
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "eraser",
|
||||
name: "橡皮擦",
|
||||
description: "擦除已绘制的内容",
|
||||
category: "工具",
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
// 直接使用 fabric-with-erasing 库提供的 EraserBrush
|
||||
this.brush = new fabric.EraserBrush(this.canvas);
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
// 配置橡皮擦特有属性
|
||||
this.brush.inverted = this.options.inverted || false; // 是否反向擦除(恢复擦除)
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
configure(brush, options = {}) {
|
||||
super.configure(brush, options);
|
||||
|
||||
// 橡皮擦特有配置
|
||||
if (options.inverted !== undefined) {
|
||||
brush.inverted = options.inverted;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置反向擦除模式
|
||||
* @param {Boolean} inverted 是否启用反向擦除(撤销擦除效果)
|
||||
*/
|
||||
setInverted(inverted) {
|
||||
if (this.brush) {
|
||||
this.brush.inverted = inverted;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取橡皮擦配置属性
|
||||
* @returns {Array} 可配置属性数组
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
return [
|
||||
...super.getConfigurableProperties(),
|
||||
{
|
||||
id: "inverted",
|
||||
name: "反向擦除",
|
||||
type: "boolean",
|
||||
description: "启用时可以恢复已擦除的内容",
|
||||
defaultValue: false,
|
||||
min: null,
|
||||
max: null,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新橡皮擦属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
if (propId === "inverted") {
|
||||
this.setInverted(value);
|
||||
} else {
|
||||
super.updateProperty(propId, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 注册水彩笔刷
|
||||
brushRegistry.register(
|
||||
"watercolor",
|
||||
class WatercolorBrush extends PencilBrush {
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "watercolor",
|
||||
name: "水彩",
|
||||
description: "模拟水彩效果,带有流动感和透明感",
|
||||
category: "特效笔刷",
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
// 创建一个自定义的PencilBrush来模拟水彩效果
|
||||
this.brush = new fabric.PencilBrush(this.canvas);
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
// 水彩效果特有的属性
|
||||
this.brush.globalCompositeOperation = "multiply";
|
||||
this.brush.shadow = new fabric.Shadow({
|
||||
color: this.options.color || "#000",
|
||||
blur: 5,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
});
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
configure(brush, options = {}) {
|
||||
super.configure(brush, options);
|
||||
|
||||
// 水彩笔刷特有的配置
|
||||
brush.opacity = Math.min(0.5, options.opacity || 0.3); // 默认透明度30%
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 注册粉笔笔刷
|
||||
brushRegistry.register(
|
||||
"chalk",
|
||||
class ChalkBrush extends PencilBrush {
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "chalk",
|
||||
name: "粉笔",
|
||||
description: "模拟粉笔效果,有颗粒感和不连续性",
|
||||
category: "特效笔刷",
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
this.brush = new fabric.PencilBrush(this.canvas);
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
// 自定义绘画方法来模拟粉笔效果
|
||||
const originalOnMouseMove = this.brush.onMouseMove;
|
||||
this.brush.onMouseMove = function (pointer, options) {
|
||||
// 随机调整坐标位置,增加粉笔质感
|
||||
const jitter = 2;
|
||||
pointer.x += (Math.random() - 0.5) * jitter;
|
||||
pointer.y += (Math.random() - 0.5) * jitter;
|
||||
|
||||
// 调用原始方法
|
||||
originalOnMouseMove.call(this, pointer, options);
|
||||
};
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
configure(brush, options = {}) {
|
||||
super.configure(brush, options);
|
||||
|
||||
// 粉笔特有的设置
|
||||
brush.strokeDashArray = [5, 5]; // 虚线效果
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用笔刷类型
|
||||
* @returns {Array} 笔刷类型数组,包含id、name和description
|
||||
*/
|
||||
getBrushTypes() {
|
||||
// 从注册表获取所有笔刷信息
|
||||
const brushes = brushRegistry.getAllBrushes();
|
||||
|
||||
// 将笔刷信息转换为期望的格式
|
||||
return brushes.map((brushInfo) => ({
|
||||
id: brushInfo.id,
|
||||
name: brushInfo.metadata.name || brushInfo.id,
|
||||
description: brushInfo.metadata.description || "",
|
||||
category: brushInfo.metadata.category || "默认",
|
||||
icon: brushInfo.metadata.icon || null,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化笔刷列表并更新BrushStore
|
||||
*/
|
||||
initializeBrushes() {
|
||||
// 获取所有笔刷
|
||||
const allBrushes = this.getBrushTypes();
|
||||
|
||||
// 更新BrushStore中的可用笔刷列表
|
||||
this.brushStore.setAvailableBrushes(allBrushes);
|
||||
|
||||
// 设置默认笔刷
|
||||
if (!this.activeBrushId && allBrushes.length > 0) {
|
||||
this.setBrushType(allBrushes[0].id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置笔刷类型
|
||||
* @param {String} brushId 笔刷ID
|
||||
* @returns {Object|null} 设置的笔刷实例
|
||||
*/
|
||||
setBrushType(brushId) {
|
||||
// 如果相同笔刷,不做处理
|
||||
if (this.activeBrushId === brushId) {
|
||||
return this.activeBrush;
|
||||
}
|
||||
|
||||
// 销毁当前笔刷
|
||||
if (this.activeBrush) {
|
||||
// 调用生命周期方法
|
||||
if (this.activeBrush.onDeselected) {
|
||||
this.activeBrush.onDeselected();
|
||||
}
|
||||
this.activeBrush.destroy();
|
||||
}
|
||||
// 创建新笔刷实例
|
||||
try {
|
||||
const brushInstance = brushRegistry.createBrushInstance(
|
||||
brushId,
|
||||
this.canvas,
|
||||
{
|
||||
color: brushId === "eraser" ? this.brushStore.state.color : undefined,
|
||||
width: this.brushStore.state.size,
|
||||
opacity: this.brushStore.state.opacity,
|
||||
|
||||
// 材质笔刷特有配置
|
||||
textureEnabled: this.brushStore.state.textureEnabled,
|
||||
texturePath: this.brushStore.state.texturePath,
|
||||
textureScale: this.brushStore.state.textureScale,
|
||||
}
|
||||
);
|
||||
|
||||
if (brushInstance) {
|
||||
// 创建笔刷
|
||||
const fabricBrush = brushInstance.create();
|
||||
|
||||
// 更新画布的当前笔刷
|
||||
if (fabricBrush) {
|
||||
this.canvas.freeDrawingBrush = fabricBrush;
|
||||
}
|
||||
|
||||
// 更新当前笔刷引用
|
||||
this.activeBrush = brushInstance;
|
||||
this.activeBrushId = brushId;
|
||||
|
||||
// 调用生命周期方法
|
||||
if (this.activeBrush.onSelected) {
|
||||
this.activeBrush.onSelected();
|
||||
}
|
||||
|
||||
// 更新Store的笔刷类型
|
||||
this.brushStore.setBrushType(brushId);
|
||||
|
||||
// 更新Store的当前笔刷实例,用于动态属性系统
|
||||
this.brushStore.setCurrentBrushInstance(brushInstance);
|
||||
|
||||
return brushInstance;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`创建笔刷 ${brushId} 失败:`, error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置笔刷颜色
|
||||
* @param {String} color 十六进制颜色值
|
||||
*/
|
||||
setBrushColor(color) {
|
||||
if (!this.canvas.freeDrawingBrush) return;
|
||||
|
||||
// 更新笔刷颜色
|
||||
this.canvas.freeDrawingBrush.color = color;
|
||||
|
||||
// 更新活动笔刷
|
||||
if (this.activeBrush) {
|
||||
this.activeBrush.configure(this.canvas.freeDrawingBrush, { color });
|
||||
}
|
||||
|
||||
// 更新Store
|
||||
this.brushStore.setBrushColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置笔刷大小
|
||||
* @param {Number} size 笔刷大小
|
||||
*/
|
||||
setBrushSize(size) {
|
||||
if (!this.canvas.freeDrawingBrush) return;
|
||||
|
||||
// 限制大小范围
|
||||
const brushSize = Math.max(0.1, Math.min(100, size));
|
||||
|
||||
// 更新笔刷大小
|
||||
this.canvas.freeDrawingBrush.width = brushSize;
|
||||
|
||||
// 更新活动笔刷
|
||||
if (this.activeBrush) {
|
||||
this.activeBrush.configure(this.canvas.freeDrawingBrush, {
|
||||
width: brushSize,
|
||||
});
|
||||
}
|
||||
|
||||
// 更新Store
|
||||
this.brushStore.setBrushSize(brushSize);
|
||||
|
||||
return brushSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加笔刷大小
|
||||
* @param {Number} amount 增加量
|
||||
* @returns {Number} 新的笔刷大小
|
||||
*/
|
||||
increaseBrushSize(amount = 1) {
|
||||
const currentSize = this.brushStore.state.size;
|
||||
return this.setBrushSize(currentSize + amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 减少笔刷大小
|
||||
* @param {Number} amount 减少量
|
||||
* @returns {Number} 新的笔刷大小
|
||||
*/
|
||||
decreaseBrushSize(amount = 1) {
|
||||
const currentSize = this.brushStore.state.size;
|
||||
return this.setBrushSize(currentSize - amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加笔刷透明度
|
||||
* @param {Number} amount 增加量
|
||||
* @returns {Number} 新的笔刷大小
|
||||
*/
|
||||
increaseBrushOpacity(amount = 0.01) {
|
||||
const currentSize = this.brushStore.state.opacity;
|
||||
return this.setBrushOpacity(currentSize + amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 减少笔刷大小
|
||||
* @param {Number} amount 减少量
|
||||
* @returns {Number} 新的笔刷大小
|
||||
*/
|
||||
decreaseBrushOpacity(amount = 0.01) {
|
||||
const currentSize = this.brushStore.state.opacity;
|
||||
return this.setBrushOpacity(currentSize - amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置笔刷透明度
|
||||
* @param {Number} opacity 透明度 (0-1)
|
||||
*/
|
||||
setBrushOpacity(opacity) {
|
||||
if (!this.canvas.freeDrawingBrush) return;
|
||||
|
||||
// 限制透明度范围
|
||||
const brushOpacity = Math.max(0.05, Math.min(1, opacity));
|
||||
|
||||
// 更新笔刷透明度
|
||||
this.canvas.freeDrawingBrush.opacity = brushOpacity;
|
||||
|
||||
// 更新活动笔刷
|
||||
if (this.activeBrush) {
|
||||
this.activeBrush.configure(this.canvas.freeDrawingBrush, {
|
||||
opacity: brushOpacity,
|
||||
});
|
||||
}
|
||||
|
||||
// 更新Store
|
||||
this.brushStore.setBrushOpacity(brushOpacity);
|
||||
|
||||
return brushOpacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置材质缩放
|
||||
* @param {Number} scale 缩放比例
|
||||
*/
|
||||
setTextureScale(scale) {
|
||||
// 限制缩放范围
|
||||
const textureScale = Math.max(0.1, Math.min(10, scale));
|
||||
|
||||
// 更新活动笔刷
|
||||
if (this.activeBrush && this.activeBrush.setTextureScale) {
|
||||
this.activeBrush.setTextureScale(textureScale);
|
||||
}
|
||||
|
||||
// 更新Store
|
||||
this.brushStore.setTextureScale(textureScale);
|
||||
|
||||
return textureScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加材质缩放
|
||||
* @param {Number} amount 增加量
|
||||
*/
|
||||
increaseTextureScale(amount = 0.1) {
|
||||
const currentScale = this.brushStore.state.textureScale;
|
||||
return this.setTextureScale(currentScale + amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 减少材质缩放
|
||||
* @param {Number} amount 减少量
|
||||
*/
|
||||
decreaseTextureScale(amount = 0.1) {
|
||||
const currentScale = this.brushStore.state.textureScale;
|
||||
return this.setTextureScale(currentScale - amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置材质路径
|
||||
* @param {String} path 材质图片路径
|
||||
*/
|
||||
setTexturePath(path) {
|
||||
// 更新活动笔刷
|
||||
if (this.activeBrush && this.activeBrush.setTexturePath) {
|
||||
this.activeBrush.setTexturePath(path);
|
||||
}
|
||||
|
||||
// 更新Store
|
||||
this.brushStore.setTexturePath(path);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用材质
|
||||
* @param {Boolean} enabled 是否启用
|
||||
*/
|
||||
setTextureEnabled(enabled) {
|
||||
// 更新Store
|
||||
this.brushStore.setTextureEnabled(enabled);
|
||||
|
||||
// 如果启用材质,且当前不是材质笔刷,需要切换
|
||||
if (enabled && this.activeBrushId !== "texture") {
|
||||
this.setBrushType("texture");
|
||||
}
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册新笔刷
|
||||
* @param {String} id 笔刷ID
|
||||
* @param {Class} brushClass 笔刷类
|
||||
* @param {Object} metadata 笔刷元数据
|
||||
* @returns {Boolean} 是否注册成功
|
||||
*/
|
||||
registerBrush(id, brushClass, metadata = {}) {
|
||||
const success = brushRegistry.register(id, brushClass, metadata);
|
||||
|
||||
if (success) {
|
||||
// 更新可用笔刷列表
|
||||
this.initializeBrushes();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前笔刷类型
|
||||
* @returns {String} 当前笔刷类型ID
|
||||
*/
|
||||
getCurrentBrushType() {
|
||||
return this.activeBrushId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前笔刷大小
|
||||
* @returns {Number} 当前笔刷大小
|
||||
*/
|
||||
getBrushSize() {
|
||||
return this.brushStore.state.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前笔刷颜色
|
||||
* @returns {String} 当前笔刷颜色
|
||||
*/
|
||||
getBrushColor() {
|
||||
return this.brushStore.state.color;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前笔刷透明度
|
||||
* @returns {Number} 当前笔刷透明度
|
||||
*/
|
||||
getBrushOpacity() {
|
||||
return this.brushStore.state.opacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取材质缩放
|
||||
* @returns {Number} 材质缩放比例
|
||||
*/
|
||||
getTextureScale() {
|
||||
return this.brushStore.state.textureScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建材质笔刷
|
||||
* 这个方法保留用于向下兼容
|
||||
* @deprecated 请使用setBrushType('texture')代替
|
||||
* @returns {Object} 材质笔刷实例
|
||||
*/
|
||||
createTextureBrush() {
|
||||
console.warn('createTextureBrush方法已废弃,请使用setBrushType("texture")');
|
||||
return this.setBrushType("texture");
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷
|
||||
* 根据当前设置应用笔刷属性到画布
|
||||
*/
|
||||
updateBrush() {
|
||||
if (!this.canvas) return;
|
||||
|
||||
// 如果有活动的笔刷实例,重新配置它
|
||||
if (this.activeBrush && this.canvas.freeDrawingBrush) {
|
||||
this.activeBrush.configure(this.canvas.freeDrawingBrush, {
|
||||
color: this.brushStore.state.color,
|
||||
width: this.brushStore.state.size,
|
||||
opacity: this.brushStore.state.opacity,
|
||||
});
|
||||
} else {
|
||||
// 如果没有活动笔刷,创建一个默认的
|
||||
this.setBrushType(this.activeBrushId || "pencil");
|
||||
}
|
||||
|
||||
// 更新画布状态
|
||||
this?.canvas?.renderAll?.();
|
||||
|
||||
return this.canvas.freeDrawingBrush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建橡皮擦
|
||||
* @returns {Object} 橡皮擦笔刷
|
||||
*/
|
||||
createEraser() {
|
||||
return this.setBrushType("eraser");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建吸色工具
|
||||
* @param {Function} callback 选择颜色后的回调函数
|
||||
*/
|
||||
createEyedropper(callback) {
|
||||
// 保存当前状态
|
||||
const previousBrushId = this.activeBrushId;
|
||||
|
||||
// 一次性事件处理程序
|
||||
const handleMouseDown = (event) => {
|
||||
const pointer = this.canvas.getPointer(event.e);
|
||||
const ctx = this.canvas.getContext();
|
||||
|
||||
// 获取点击位置的像素
|
||||
const imageData = ctx.getImageData(pointer.x, pointer.y, 1, 1).data;
|
||||
|
||||
// 将RGB转换为十六进制颜色
|
||||
const color = `#${(
|
||||
(1 << 24) +
|
||||
(imageData[0] << 16) +
|
||||
(imageData[1] << 8) +
|
||||
imageData[2]
|
||||
)
|
||||
.toString(16)
|
||||
.slice(1)}`;
|
||||
|
||||
// 调用回调函数
|
||||
if (typeof callback === "function") {
|
||||
callback(color);
|
||||
}
|
||||
|
||||
// 恢复之前的笔刷
|
||||
this.setBrushType(previousBrushId);
|
||||
|
||||
// 移除事件监听器
|
||||
this.canvas.off("mouse:down", handleMouseDown);
|
||||
};
|
||||
|
||||
// 添加事件监听器
|
||||
this.canvas.on("mouse:down", handleMouseDown);
|
||||
|
||||
// 设置吸色光标
|
||||
this.canvas.defaultCursor = "crosshair";
|
||||
|
||||
console.log("吸色工具已激活,点击画布选择颜色");
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁资源
|
||||
*/
|
||||
dispose() {
|
||||
// 销毁当前笔刷
|
||||
if (this.activeBrush) {
|
||||
this.activeBrush.destroy();
|
||||
this.activeBrush = null;
|
||||
}
|
||||
|
||||
this.canvas = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export default BrushManager;
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,244 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
|
||||
/**
|
||||
* 蜡笔笔刷
|
||||
* 模拟蜡笔效果,具有颗粒感和纹理
|
||||
*/
|
||||
export class CrayonBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "crayon",
|
||||
name: "蜡笔",
|
||||
description: "模拟蜡笔效果,具有颗粒感和纹理",
|
||||
category: "特效笔刷",
|
||||
icon: "crayon",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 蜡笔笔刷特有属性
|
||||
this._baseWidth = options._baseWidth || 15;
|
||||
this._size = options._size || 0;
|
||||
this._sep = options._sep || options._sep === 0 ? options._sep : 3;
|
||||
this._inkAmount = options._inkAmount || 10;
|
||||
this.randomness = options.randomness || 0.5; // 随机性
|
||||
this.texture = options.texture || "default"; // 纹理类型
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric笔刷实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生蜡笔笔刷
|
||||
this.brush = new fabric.CrayonBrush(this.canvas);
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @param {Object} brush fabric笔刷实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
configure(brush, options = {}) {
|
||||
if (!brush) return;
|
||||
|
||||
// 基础属性配置
|
||||
if (options.width !== undefined) {
|
||||
brush.width = options.width;
|
||||
// 更新笔刷相关属性
|
||||
this._baseWidth = options.width / 2;
|
||||
this._size = options.width / 2 + this._baseWidth;
|
||||
}
|
||||
|
||||
if (options.color !== undefined) {
|
||||
brush.color = options.color;
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
brush.opacity = options.opacity;
|
||||
}
|
||||
|
||||
// 蜡笔笔刷特有属性
|
||||
if (options._baseWidth !== undefined) {
|
||||
brush._baseWidth = options._baseWidth;
|
||||
this._baseWidth = options._baseWidth;
|
||||
this._size = this.width / 2 + this._baseWidth;
|
||||
}
|
||||
|
||||
if (options._sep !== undefined) {
|
||||
brush._sep = options._sep;
|
||||
this._sep = options._sep;
|
||||
}
|
||||
|
||||
if (options._inkAmount !== undefined) {
|
||||
brush._inkAmount = options._inkAmount;
|
||||
this._inkAmount = options._inkAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置颗粒分离度
|
||||
* @param {Number} sep 分离度值
|
||||
*/
|
||||
setSeparation(sep) {
|
||||
this._sep = Math.max(0.5, Math.min(10, sep));
|
||||
|
||||
if (this.brush) {
|
||||
this.brush._sep = this._sep;
|
||||
}
|
||||
|
||||
return this._sep;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置墨量
|
||||
* @param {Number} amount 墨量值
|
||||
*/
|
||||
setInkAmount(amount) {
|
||||
this._inkAmount = Math.max(1, Math.min(50, amount));
|
||||
|
||||
if (this.brush) {
|
||||
this.brush._inkAmount = this._inkAmount;
|
||||
}
|
||||
|
||||
return this._inkAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置随机性
|
||||
* @param {Number} value 随机性值(0-1)
|
||||
*/
|
||||
setRandomness(value) {
|
||||
this.randomness = Math.max(0, Math.min(1, value));
|
||||
return this.randomness;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置纹理类型
|
||||
* @param {String} type 纹理类型
|
||||
*/
|
||||
setTexture(type) {
|
||||
this.texture = type;
|
||||
// 实际应用可能需要更多的实现逻辑
|
||||
return this.texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 定义蜡笔笔刷特有属性
|
||||
const crayonProperties = [
|
||||
{
|
||||
id: "separation",
|
||||
name: "颗粒分离度",
|
||||
type: "slider",
|
||||
defaultValue: this._sep,
|
||||
min: 0.5,
|
||||
max: 10,
|
||||
step: 0.5,
|
||||
description: "控制蜡笔颗粒的分离程度",
|
||||
category: "蜡笔设置",
|
||||
order: 100,
|
||||
},
|
||||
{
|
||||
id: "inkAmount",
|
||||
name: "墨量",
|
||||
type: "slider",
|
||||
defaultValue: this._inkAmount,
|
||||
min: 1,
|
||||
max: 50,
|
||||
step: 1,
|
||||
description: "控制蜡笔的颜料量",
|
||||
category: "蜡笔设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "randomness",
|
||||
name: "随机性",
|
||||
type: "slider",
|
||||
defaultValue: this.randomness,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制蜡笔纹理的随机程度",
|
||||
category: "蜡笔设置",
|
||||
order: 120,
|
||||
},
|
||||
{
|
||||
id: "texture",
|
||||
name: "纹理类型",
|
||||
type: "select",
|
||||
defaultValue: this.texture,
|
||||
options: [
|
||||
{ value: "default", label: "默认" },
|
||||
{ value: "rough", label: "粗糙" },
|
||||
{ value: "smooth", label: "平滑" },
|
||||
],
|
||||
description: "设置蜡笔的纹理类型",
|
||||
category: "蜡笔设置",
|
||||
order: 130,
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...crayonProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理蜡笔笔刷特有属性
|
||||
if (propId === "separation") {
|
||||
this.setSeparation(value);
|
||||
return true;
|
||||
} else if (propId === "inkAmount") {
|
||||
this.setInkAmount(value);
|
||||
return true;
|
||||
} else if (propId === "randomness") {
|
||||
this.setRandomness(value);
|
||||
return true;
|
||||
} else if (propId === "texture") {
|
||||
this.setTexture(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cmVjdCB4PSIxMCIgeT0iMTAiIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjIiLz48cmVjdCB4PSIyMCIgeT0iMjAiIHdpZHRoPSI2MCIgaGVpZ2h0PSI2MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjIiLz48cmVjdCB4PSIzMCIgeT0iMzAiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=";
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,196 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
|
||||
/**
|
||||
* 钢笔笔刷
|
||||
* 模拟钢笔效果,具有变化的透明度
|
||||
*/
|
||||
export class CustomPenBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "pen",
|
||||
name: "钢笔",
|
||||
description: "模拟钢笔效果,具有变化的透明度",
|
||||
category: "基础笔刷",
|
||||
icon: "pen",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 钢笔笔刷特有属性
|
||||
this._baseWidth = options._baseWidth || 15;
|
||||
this._lineWidth = options._lineWidth || 2;
|
||||
this.inkOpacityMin = options.inkOpacityMin || 0.2;
|
||||
this.inkOpacityMax = options.inkOpacityMax || 0.6;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric笔刷实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生钢笔笔刷
|
||||
this.brush = new fabric.PenBrush(this.canvas);
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @param {Object} brush fabric笔刷实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
configure(brush, options = {}) {
|
||||
if (!brush) return;
|
||||
|
||||
// 基础属性配置
|
||||
if (options.width !== undefined) {
|
||||
brush.width = options.width;
|
||||
// 更新笔刷相关属性
|
||||
this._baseWidth = options.width / 2;
|
||||
}
|
||||
|
||||
if (options.color !== undefined) {
|
||||
brush.color = options.color;
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
brush.opacity = options.opacity;
|
||||
}
|
||||
|
||||
// 钢笔笔刷特有属性
|
||||
if (options._baseWidth !== undefined) {
|
||||
brush._baseWidth = options._baseWidth;
|
||||
this._baseWidth = options._baseWidth;
|
||||
}
|
||||
|
||||
if (options._lineWidth !== undefined) {
|
||||
brush._lineWidth = options._lineWidth;
|
||||
this._lineWidth = options._lineWidth;
|
||||
}
|
||||
|
||||
// 确保线条连接设置正确
|
||||
brush.canvas.contextTop.lineJoin = "round";
|
||||
brush.canvas.contextTop.lineCap = "round";
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最小墨水透明度
|
||||
* @param {Number} opacity 透明度值(0-1)
|
||||
*/
|
||||
setInkOpacityMin(opacity) {
|
||||
this.inkOpacityMin = Math.max(0.1, Math.min(0.5, opacity));
|
||||
return this.inkOpacityMin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大墨水透明度
|
||||
* @param {Number} opacity 透明度值(0-1)
|
||||
*/
|
||||
setInkOpacityMax(opacity) {
|
||||
this.inkOpacityMax = Math.max(0.3, Math.min(1, opacity));
|
||||
return this.inkOpacityMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 定义钢笔笔刷特有属性
|
||||
const penProperties = [
|
||||
{
|
||||
id: "lineWidth",
|
||||
name: "线条宽度",
|
||||
type: "slider",
|
||||
defaultValue: this._lineWidth,
|
||||
min: 1,
|
||||
max: 10,
|
||||
step: 0.5,
|
||||
description: "控制钢笔线条的宽度",
|
||||
category: "钢笔设置",
|
||||
order: 100,
|
||||
},
|
||||
{
|
||||
id: "inkOpacityMin",
|
||||
name: "最小墨水透明度",
|
||||
type: "slider",
|
||||
defaultValue: this.inkOpacityMin,
|
||||
min: 0.1,
|
||||
max: 0.5,
|
||||
step: 0.05,
|
||||
description: "控制钢笔墨水最小透明度",
|
||||
category: "钢笔设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "inkOpacityMax",
|
||||
name: "最大墨水透明度",
|
||||
type: "slider",
|
||||
defaultValue: this.inkOpacityMax,
|
||||
min: 0.3,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制钢笔墨水最大透明度",
|
||||
category: "钢笔设置",
|
||||
order: 120,
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...penProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理钢笔笔刷特有属性
|
||||
if (propId === "lineWidth") {
|
||||
this._lineWidth = value;
|
||||
if (this.brush) {
|
||||
this.brush._lineWidth = value;
|
||||
}
|
||||
return true;
|
||||
} else if (propId === "inkOpacityMin") {
|
||||
this.setInkOpacityMin(value);
|
||||
return true;
|
||||
} else if (propId === "inkOpacityMax") {
|
||||
this.setInkOpacityMax(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBkPSJNMjAgMjBMODAgODBNMjAgODBMODAgMjAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L3N2Zz4=";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
|
||||
/**
|
||||
* 毛发笔刷
|
||||
* 创建类似于毛发或草的效果
|
||||
*/
|
||||
export class FurBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "fur",
|
||||
name: "毛发笔刷",
|
||||
description: "创建类似于毛发或草的纹理效果",
|
||||
category: "特效笔刷",
|
||||
icon: "fur",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 毛发笔刷特有属性
|
||||
this.furLength = options.furLength || 10;
|
||||
this.furDensity = options.furDensity || 0.7;
|
||||
this.furRandomness = options.furRandomness || 0.5;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric笔刷实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生毛发笔刷
|
||||
this.brush = new fabric.FurBrush(this.canvas);
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @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) {
|
||||
brush.color = options.color;
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
brush.opacity = options.opacity;
|
||||
}
|
||||
|
||||
// 这里可以添加对毛发笔刷特有属性的配置
|
||||
// 由于fabric.FurBrush的原始实现可能没有直接暴露这些属性,
|
||||
// 我们可能需要在onMouseMove等事件中动态调整行为
|
||||
|
||||
// 存储特有属性,供后续使用
|
||||
if (options.furLength !== undefined) {
|
||||
this.furLength = options.furLength;
|
||||
}
|
||||
|
||||
if (options.furDensity !== undefined) {
|
||||
this.furDensity = options.furDensity;
|
||||
}
|
||||
|
||||
if (options.furRandomness !== undefined) {
|
||||
this.furRandomness = options.furRandomness;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置毛发长度
|
||||
* @param {Number} length 长度值
|
||||
*/
|
||||
setFurLength(length) {
|
||||
this.furLength = Math.max(1, Math.min(50, length));
|
||||
return this.furLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置毛发密度
|
||||
* @param {Number} density 密度值(0-1)
|
||||
*/
|
||||
setFurDensity(density) {
|
||||
this.furDensity = Math.max(0.1, Math.min(1, density));
|
||||
return this.furDensity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置毛发随机性
|
||||
* @param {Number} randomness 随机性值(0-1)
|
||||
*/
|
||||
setFurRandomness(randomness) {
|
||||
this.furRandomness = Math.max(0, Math.min(1, randomness));
|
||||
return this.furRandomness;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 定义毛发笔刷特有属性
|
||||
const furProperties = [
|
||||
{
|
||||
id: "furLength",
|
||||
name: "毛发长度",
|
||||
type: "slider",
|
||||
defaultValue: this.furLength,
|
||||
min: 1,
|
||||
max: 50,
|
||||
step: 1,
|
||||
description: "控制毛发的长度",
|
||||
category: "毛发设置",
|
||||
order: 100,
|
||||
},
|
||||
{
|
||||
id: "furDensity",
|
||||
name: "毛发密度",
|
||||
type: "slider",
|
||||
defaultValue: this.furDensity,
|
||||
min: 0.1,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制毛发的密度",
|
||||
category: "毛发设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "furRandomness",
|
||||
name: "随机性",
|
||||
type: "slider",
|
||||
defaultValue: this.furRandomness,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制毛发的随机分布程度",
|
||||
category: "毛发设置",
|
||||
order: 120,
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...furProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理毛发笔刷特有属性
|
||||
if (propId === "furLength") {
|
||||
this.setFurLength(value);
|
||||
return true;
|
||||
} else if (propId === "furDensity") {
|
||||
this.setFurDensity(value);
|
||||
return true;
|
||||
} else if (propId === "furRandomness") {
|
||||
this.setFurRandomness(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBkPSJNMTAgODBMNTAgMjBNMjAgODBMNjAgMjBNMzAgODBMNzAgMjBNNDAgODBMODAgMjBNNTAgODBMOTAgMjAiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIi8+PC9zdmc+";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
|
||||
/**
|
||||
* 水墨笔刷
|
||||
* 模拟中国传统水墨画效果
|
||||
*/
|
||||
export class InkBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "ink",
|
||||
name: "水墨笔刷",
|
||||
description: "模拟中国传统水墨画效果,墨色深浅不一",
|
||||
category: "特效笔刷",
|
||||
icon: "ink",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 水墨笔刷特有属性
|
||||
this._baseWidth = options._baseWidth || 15;
|
||||
this._inkAmount = options._inkAmount || 7;
|
||||
this._range = options._range || 10;
|
||||
this.splashEnabled =
|
||||
options.splashEnabled !== undefined ? options.splashEnabled : true;
|
||||
this.splashSize = options.splashSize || 5;
|
||||
this.splashDistance = options.splashDistance || 30;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric笔刷实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生水墨笔刷
|
||||
this.brush = new fabric.InkBrush(this.canvas);
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @param {Object} brush fabric笔刷实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
configure(brush, options = {}) {
|
||||
if (!brush) return;
|
||||
|
||||
// 基础属性配置
|
||||
if (options.width !== undefined) {
|
||||
brush.width = options.width;
|
||||
this._baseWidth = options.width / 2;
|
||||
}
|
||||
|
||||
if (options.color !== undefined) {
|
||||
brush.color = options.color;
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
brush.opacity = options.opacity;
|
||||
}
|
||||
|
||||
// 水墨笔刷特有属性
|
||||
if (options._inkAmount !== undefined) {
|
||||
brush._inkAmount = options._inkAmount;
|
||||
this._inkAmount = options._inkAmount;
|
||||
}
|
||||
|
||||
if (options._range !== undefined) {
|
||||
brush._range = options._range;
|
||||
this._range = options._range;
|
||||
}
|
||||
|
||||
// 更新溅墨相关配置(这需要修改原始InkBrush的drawSplash方法)
|
||||
if (this.splashEnabled !== undefined) {
|
||||
// 由于原始InkBrush没有直接暴露这个配置,我们可能需要覆盖方法
|
||||
// 这里仅保存配置,实际逻辑需要在brush创建后处理
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置墨量
|
||||
* @param {Number} amount 墨量值
|
||||
*/
|
||||
setInkAmount(amount) {
|
||||
this._inkAmount = Math.max(1, Math.min(20, amount));
|
||||
|
||||
if (this.brush) {
|
||||
this.brush._inkAmount = this._inkAmount;
|
||||
}
|
||||
|
||||
return this._inkAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置笔触范围
|
||||
* @param {Number} range 范围值
|
||||
*/
|
||||
setRange(range) {
|
||||
this._range = Math.max(5, Math.min(50, range));
|
||||
|
||||
if (this.brush) {
|
||||
this.brush._range = this._range;
|
||||
}
|
||||
|
||||
return this._range;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用溅墨效果
|
||||
* @param {Boolean} enabled 是否启用
|
||||
*/
|
||||
setSplashEnabled(enabled) {
|
||||
this.splashEnabled = enabled;
|
||||
|
||||
// 实际应用需要更多的逻辑来支持这个功能
|
||||
// 由于需要修改fabric.InkBrush的内部行为
|
||||
|
||||
return this.splashEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置溅墨大小
|
||||
* @param {Number} size 大小值
|
||||
*/
|
||||
setSplashSize(size) {
|
||||
this.splashSize = Math.max(1, Math.min(20, size));
|
||||
return this.splashSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置溅墨距离
|
||||
* @param {Number} distance 距离值
|
||||
*/
|
||||
setSplashDistance(distance) {
|
||||
this.splashDistance = Math.max(10, Math.min(100, distance));
|
||||
return this.splashDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 定义水墨笔刷特有属性
|
||||
const inkProperties = [
|
||||
{
|
||||
id: "inkAmount",
|
||||
name: "墨量",
|
||||
type: "slider",
|
||||
defaultValue: this._inkAmount,
|
||||
min: 1,
|
||||
max: 20,
|
||||
step: 1,
|
||||
description: "控制水墨的浓度",
|
||||
category: "水墨设置",
|
||||
order: 100,
|
||||
},
|
||||
{
|
||||
id: "range",
|
||||
name: "笔触范围",
|
||||
type: "slider",
|
||||
defaultValue: this._range,
|
||||
min: 5,
|
||||
max: 50,
|
||||
step: 1,
|
||||
description: "控制水墨扩散的范围",
|
||||
category: "水墨设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "splashEnabled",
|
||||
name: "溅墨效果",
|
||||
type: "checkbox",
|
||||
defaultValue: this.splashEnabled,
|
||||
description: "是否启用溅墨效果",
|
||||
category: "水墨设置",
|
||||
order: 120,
|
||||
},
|
||||
{
|
||||
id: "splashSize",
|
||||
name: "溅墨大小",
|
||||
type: "slider",
|
||||
defaultValue: this.splashSize,
|
||||
min: 1,
|
||||
max: 20,
|
||||
step: 1,
|
||||
description: "溅墨点的大小",
|
||||
category: "水墨设置",
|
||||
order: 130,
|
||||
visibleWhen: { splashEnabled: true },
|
||||
},
|
||||
{
|
||||
id: "splashDistance",
|
||||
name: "溅墨距离",
|
||||
type: "slider",
|
||||
defaultValue: this.splashDistance,
|
||||
min: 10,
|
||||
max: 100,
|
||||
step: 5,
|
||||
description: "溅墨可扩散的最大距离",
|
||||
category: "水墨设置",
|
||||
order: 140,
|
||||
visibleWhen: { splashEnabled: true },
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...inkProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理水墨笔刷特有属性
|
||||
if (propId === "inkAmount") {
|
||||
this.setInkAmount(value);
|
||||
return true;
|
||||
} else if (propId === "range") {
|
||||
this.setRange(value);
|
||||
return true;
|
||||
} else if (propId === "splashEnabled") {
|
||||
this.setSplashEnabled(value);
|
||||
return true;
|
||||
} else if (propId === "splashSize") {
|
||||
this.setSplashSize(value);
|
||||
return true;
|
||||
} else if (propId === "splashDistance") {
|
||||
this.setSplashDistance(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBkPSJNMjAgODBDNDAgNjAgNjAgNDAgODAgMjAiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSI1IiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiLz48Y2lyY2xlIGN4PSI3MCIgY3k9IjMwIiByPSI1IiBmaWxsPSIjMDAwIi8+PGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iMyIgZmlsbD0iIzAwMCIvPjxjaXJjbGUgY3g9IjMwIiBjeT0iNzAiIHI9IjYiIGZpbGw9IiMwMDAiLz48L3N2Zz4=";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
|
||||
/**
|
||||
* 长毛发笔刷
|
||||
* 创建类似于长毛、毛皮、草或头发的效果
|
||||
*/
|
||||
export class LongfurBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "longfur",
|
||||
name: "长毛发",
|
||||
description: "创建流动的长毛发效果,适合绘制动物毛皮、草或头发",
|
||||
category: "特效笔刷",
|
||||
icon: "longfur",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 长毛发笔刷特有属性
|
||||
this.furLength = options.furLength || 20;
|
||||
this.furDensity = options.furDensity || 0.7;
|
||||
this.furFlowFactor = options.furFlowFactor || 0.5;
|
||||
this.furCurvature = options.furCurvature || 0.3;
|
||||
this.randomizeDirection =
|
||||
options.randomizeDirection !== undefined
|
||||
? options.randomizeDirection
|
||||
: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric笔刷实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生长毛发笔刷
|
||||
this.brush = new fabric.LongfurBrush(this.canvas);
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @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) {
|
||||
brush.color = options.color;
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
brush.opacity = options.opacity;
|
||||
}
|
||||
|
||||
// 长毛发笔刷特有属性
|
||||
if (options.furLength !== undefined) {
|
||||
this.furLength = options.furLength;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.furLength !== undefined) {
|
||||
brush.furLength = this.furLength;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.furDensity !== undefined) {
|
||||
this.furDensity = options.furDensity;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.furDensity !== undefined) {
|
||||
brush.furDensity = this.furDensity;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.furFlowFactor !== undefined) {
|
||||
this.furFlowFactor = options.furFlowFactor;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.furFlowFactor !== undefined) {
|
||||
brush.furFlowFactor = this.furFlowFactor;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.furCurvature !== undefined) {
|
||||
this.furCurvature = options.furCurvature;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.furCurvature !== undefined) {
|
||||
brush.furCurvature = this.furCurvature;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.randomizeDirection !== undefined) {
|
||||
this.randomizeDirection = options.randomizeDirection;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.randomizeDirection !== undefined) {
|
||||
brush.randomizeDirection = this.randomizeDirection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置毛发长度
|
||||
* @param {Number} length 长度值
|
||||
*/
|
||||
setFurLength(length) {
|
||||
this.furLength = Math.max(5, Math.min(100, length));
|
||||
|
||||
if (this.brush && this.brush.furLength !== undefined) {
|
||||
this.brush.furLength = this.furLength;
|
||||
}
|
||||
|
||||
return this.furLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置毛发密度
|
||||
* @param {Number} density 密度值(0-1)
|
||||
*/
|
||||
setFurDensity(density) {
|
||||
this.furDensity = Math.max(0.1, Math.min(1, density));
|
||||
|
||||
if (this.brush && this.brush.furDensity !== undefined) {
|
||||
this.brush.furDensity = this.furDensity;
|
||||
}
|
||||
|
||||
return this.furDensity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置毛发流动系数
|
||||
* @param {Number} factor 流动系数(0-1)
|
||||
*/
|
||||
setFurFlowFactor(factor) {
|
||||
this.furFlowFactor = Math.max(0, Math.min(1, factor));
|
||||
|
||||
if (this.brush && this.brush.furFlowFactor !== undefined) {
|
||||
this.brush.furFlowFactor = this.furFlowFactor;
|
||||
}
|
||||
|
||||
return this.furFlowFactor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置毛发弯曲度
|
||||
* @param {Number} curvature 弯曲度(0-1)
|
||||
*/
|
||||
setFurCurvature(curvature) {
|
||||
this.furCurvature = Math.max(0, Math.min(1, curvature));
|
||||
|
||||
if (this.brush && this.brush.furCurvature !== undefined) {
|
||||
this.brush.furCurvature = this.furCurvature;
|
||||
}
|
||||
|
||||
return this.furCurvature;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否随机化方向
|
||||
* @param {Boolean} randomize 是否随机化
|
||||
*/
|
||||
setRandomizeDirection(randomize) {
|
||||
this.randomizeDirection = randomize;
|
||||
|
||||
if (this.brush && this.brush.randomizeDirection !== undefined) {
|
||||
this.brush.randomizeDirection = this.randomizeDirection;
|
||||
}
|
||||
|
||||
return this.randomizeDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 定义长毛发笔刷特有属性
|
||||
const longfurProperties = [
|
||||
{
|
||||
id: "furLength",
|
||||
name: "毛发长度",
|
||||
type: "slider",
|
||||
defaultValue: this.furLength,
|
||||
min: 5,
|
||||
max: 100,
|
||||
step: 1,
|
||||
description: "控制毛发的长度",
|
||||
category: "长毛发设置",
|
||||
order: 100,
|
||||
},
|
||||
{
|
||||
id: "furDensity",
|
||||
name: "毛发密度",
|
||||
type: "slider",
|
||||
defaultValue: this.furDensity,
|
||||
min: 0.1,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制毛发的密度",
|
||||
category: "长毛发设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "furFlowFactor",
|
||||
name: "流动系数",
|
||||
type: "slider",
|
||||
defaultValue: this.furFlowFactor,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制毛发的流动感",
|
||||
category: "长毛发设置",
|
||||
order: 120,
|
||||
},
|
||||
{
|
||||
id: "furCurvature",
|
||||
name: "弯曲度",
|
||||
type: "slider",
|
||||
defaultValue: this.furCurvature,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制毛发的弯曲程度",
|
||||
category: "长毛发设置",
|
||||
order: 130,
|
||||
},
|
||||
{
|
||||
id: "randomizeDirection",
|
||||
name: "随机方向",
|
||||
type: "checkbox",
|
||||
defaultValue: this.randomizeDirection,
|
||||
description: "是否随机化毛发方向",
|
||||
category: "长毛发设置",
|
||||
order: 140,
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...longfurProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理长毛发笔刷特有属性
|
||||
if (propId === "furLength") {
|
||||
this.setFurLength(value);
|
||||
return true;
|
||||
} else if (propId === "furDensity") {
|
||||
this.setFurDensity(value);
|
||||
return true;
|
||||
} else if (propId === "furFlowFactor") {
|
||||
this.setFurFlowFactor(value);
|
||||
return true;
|
||||
} else if (propId === "furCurvature") {
|
||||
this.setFurCurvature(value);
|
||||
return true;
|
||||
} else if (propId === "randomizeDirection") {
|
||||
this.setRandomizeDirection(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBkPSJNMjUgNTBDMjUgNTAgNTAgMTAgNTAgNTBDNTAgNTAgNTAgOTAgNzUgNTAiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJub25lIi8+PGxpbmUgeDE9IjMwIiB5MT0iNDUiIHgyPSIzMCIgeTI9IjEwIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMSIvPjxsaW5lIHgxPSI0MCIgeTE9IjQwIiB4Mj0iNDAiIHkyPSI1IiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMSIvPjxsaW5lIHgxPSI1MCIgeTE9IjQwIiB4Mj0iNTAiIHkyPSIxMCIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjEiLz48bGluZSB4MT0iNjAiIHkxPSI0MCIgeDI9IjYwIiB5Mj0iNSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjEiLz48bGluZSB4MT0iNzAiIHkxPSI0NSIgeDI9IjcwIiB5Mj0iMTAiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxIi8+PC9zdmc+";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
|
||||
/**
|
||||
* 马克笔笔刷
|
||||
* 模拟马克笔效果,具有半透明平头笔触
|
||||
*/
|
||||
export class MarkerBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "marker",
|
||||
name: "马克笔",
|
||||
description: "模拟马克笔效果,具有半透明平头笔触",
|
||||
category: "基础笔刷",
|
||||
icon: "marker",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 马克笔特有属性
|
||||
this._baseWidth = options._baseWidth || 15;
|
||||
this._lineWidth = options._lineWidth || 2;
|
||||
this.capStyle = options.capStyle || "round"; // "round" 或 "square"
|
||||
this.blendMode = options.blendMode || "multiply"; // 混合模式
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric笔刷实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生马克笔笔刷
|
||||
this.brush = new fabric.MarkerBrush(this.canvas);
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @param {Object} brush fabric笔刷实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
configure(brush, options = {}) {
|
||||
if (!brush) return;
|
||||
|
||||
// 基础属性配置
|
||||
if (options.width !== undefined) {
|
||||
brush.width = options.width;
|
||||
// 更新笔刷相关属性
|
||||
this._baseWidth = options.width;
|
||||
}
|
||||
|
||||
if (options.color !== undefined) {
|
||||
brush.color = options.color;
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
// 马克笔的透明度默认不要太高
|
||||
brush.opacity = Math.min(0.8, options.opacity || 0.6);
|
||||
}
|
||||
|
||||
// 马克笔笔刷特有属性
|
||||
if (options._baseWidth !== undefined) {
|
||||
brush._baseWidth = options._baseWidth;
|
||||
this._baseWidth = options._baseWidth;
|
||||
}
|
||||
|
||||
if (options._lineWidth !== undefined) {
|
||||
brush._lineWidth = options._lineWidth;
|
||||
this._lineWidth = options._lineWidth;
|
||||
}
|
||||
|
||||
// 笔触样式设置
|
||||
brush.canvas.contextTop.lineJoin = "round";
|
||||
brush.canvas.contextTop.lineCap = this.capStyle || "round";
|
||||
|
||||
// 马克笔的混合模式设置
|
||||
if (this.blendMode === "multiply") {
|
||||
brush.canvas.contextTop.globalCompositeOperation = "multiply";
|
||||
} else {
|
||||
brush.canvas.contextTop.globalCompositeOperation = "source-over";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置笔触线宽
|
||||
* @param {Number} width 线宽值
|
||||
*/
|
||||
setLineWidth(width) {
|
||||
this._lineWidth = Math.max(1, Math.min(10, width));
|
||||
|
||||
if (this.brush) {
|
||||
this.brush._lineWidth = this._lineWidth;
|
||||
}
|
||||
|
||||
return this._lineWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置笔头样式
|
||||
* @param {String} style 笔头样式 ('round' 或 'square')
|
||||
*/
|
||||
setCapStyle(style) {
|
||||
if (style === "round" || style === "square") {
|
||||
this.capStyle = style;
|
||||
|
||||
if (this.brush && this.brush.canvas) {
|
||||
this.brush.canvas.contextTop.lineCap = style;
|
||||
}
|
||||
}
|
||||
|
||||
return this.capStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置混合模式
|
||||
* @param {String} mode 混合模式 ('multiply' 或 'normal')
|
||||
*/
|
||||
setBlendMode(mode) {
|
||||
this.blendMode = mode;
|
||||
|
||||
if (this.brush && this.brush.canvas) {
|
||||
this.brush.canvas.contextTop.globalCompositeOperation =
|
||||
mode === "multiply" ? "multiply" : "source-over";
|
||||
}
|
||||
|
||||
return this.blendMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 定义马克笔笔刷特有属性
|
||||
const markerProperties = [
|
||||
{
|
||||
id: "lineWidth",
|
||||
name: "笔触宽度",
|
||||
type: "slider",
|
||||
defaultValue: this._lineWidth,
|
||||
min: 1,
|
||||
max: 10,
|
||||
step: 0.5,
|
||||
description: "控制马克笔笔触的宽度",
|
||||
category: "马克笔设置",
|
||||
order: 100,
|
||||
},
|
||||
{
|
||||
id: "capStyle",
|
||||
name: "笔头样式",
|
||||
type: "select",
|
||||
defaultValue: this.capStyle,
|
||||
options: [
|
||||
{ value: "round", label: "圆形" },
|
||||
{ value: "square", label: "方形" },
|
||||
],
|
||||
description: "设置马克笔笔头的形状",
|
||||
category: "马克笔设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "blendMode",
|
||||
name: "混合模式",
|
||||
type: "select",
|
||||
defaultValue: this.blendMode,
|
||||
options: [
|
||||
{ value: "multiply", label: "正片叠底" },
|
||||
{ value: "normal", label: "正常" },
|
||||
],
|
||||
description: "设置马克笔的颜色混合方式",
|
||||
category: "马克笔设置",
|
||||
order: 120,
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...markerProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理马克笔笔刷特有属性
|
||||
if (propId === "lineWidth") {
|
||||
this.setLineWidth(value);
|
||||
return true;
|
||||
} else if (propId === "capStyle") {
|
||||
this.setCapStyle(value);
|
||||
return true;
|
||||
} else if (propId === "blendMode") {
|
||||
this.setBlendMode(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBkPSJNMTAgNTBIOTAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIyMCIgc3Ryb2tlLW9wYWNpdHk9IjAuNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9zdmc+";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
|
||||
/**
|
||||
* 铅笔笔刷
|
||||
* fabric原生铅笔笔刷的包装类
|
||||
*/
|
||||
export class PencilBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "pencil",
|
||||
name: "铅笔",
|
||||
description: "基础铅笔工具,适合精细线条绘制",
|
||||
category: "基础笔刷",
|
||||
icon: "pencil",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 铅笔笔刷特有属性
|
||||
this.decimate = options.decimate || 0.4;
|
||||
this.strokeLineCap = options.strokeLineCap || "round";
|
||||
this.strokeLineJoin = options.strokeLineJoin || "round";
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric.PencilBrush实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生铅笔笔刷
|
||||
this.brush = new fabric.PencilBrush(this.canvas);
|
||||
|
||||
// 重写 _finalizeAndAddPath 方法,使其调用 convertToImg 而不是创建 Path 对象
|
||||
const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind(
|
||||
this.brush
|
||||
);
|
||||
const self = this; // 保存外部this引用
|
||||
|
||||
this.brush._finalizeAndAddPath = function () {
|
||||
console.log("PencilBrush: _finalizeAndAddPath called");
|
||||
const ctx = this.canvas.contextTop;
|
||||
ctx.closePath();
|
||||
|
||||
// 应用点简化
|
||||
if (this.decimate) {
|
||||
this._points = this.decimatePoints(this._points, this.decimate);
|
||||
}
|
||||
|
||||
console.log(
|
||||
"PencilBrush: points count =",
|
||||
this._points ? this._points.length : 0
|
||||
);
|
||||
|
||||
// 检查是否有有效的路径数据
|
||||
if (!this._points || this._points.length < 2) {
|
||||
// 如果点数不足,直接请求重新渲染
|
||||
console.log("PencilBrush: Not enough points, skipping");
|
||||
this.canvas.requestRenderAll();
|
||||
return;
|
||||
}
|
||||
|
||||
const pathData = this.convertPointsToSVGPath(this._points);
|
||||
|
||||
const isEmpty = self._isEmptySVGPath(pathData);
|
||||
console.log("PencilBrush: isEmpty =", isEmpty);
|
||||
|
||||
if (isEmpty) {
|
||||
// 如果路径为空,直接请求重新渲染
|
||||
console.log("PencilBrush: Path is empty, skipping");
|
||||
this.canvas.requestRenderAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// 先触发事件,模拟原生行为
|
||||
const path = this.createPath(pathData);
|
||||
this.canvas.fire("before:path:created", { path: path });
|
||||
|
||||
console.log("PencilBrush: Calling convertToImg");
|
||||
|
||||
// 调用 convertToImg 方法将绘制内容转换为图片
|
||||
if (typeof this.convertToImg === "function") {
|
||||
this.convertToImg();
|
||||
console.log("PencilBrush: convertToImg called successfully");
|
||||
} else {
|
||||
console.warn(
|
||||
"convertToImg method not found, falling back to original behavior"
|
||||
);
|
||||
// 如果没有convertToImg方法,回退到原始行为
|
||||
this.canvas.add(path);
|
||||
this.canvas.fire("path:created", { path: path });
|
||||
this.canvas.clearContext(this.canvas.contextTop);
|
||||
}
|
||||
|
||||
// 重置阴影
|
||||
this._resetShadow();
|
||||
};
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 SVG 路径是否为空
|
||||
* @private
|
||||
* @param {Array} pathData SVG 路径数据
|
||||
* @returns {Boolean} 是否为空路径
|
||||
*/
|
||||
_isEmptySVGPath(pathData) {
|
||||
if (!pathData || pathData.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查路径是否只包含移动命令或者是一个点
|
||||
let hasDrawing = false;
|
||||
let moveCount = 0;
|
||||
|
||||
for (let i = 0; i < pathData.length; i++) {
|
||||
const command = pathData[i];
|
||||
if (command[0] === "M") {
|
||||
moveCount++;
|
||||
} else if (
|
||||
command[0] === "L" ||
|
||||
command[0] === "Q" ||
|
||||
command[0] === "C"
|
||||
) {
|
||||
hasDrawing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果只有移动命令且超过1个,或者没有绘制命令,则认为是空路径
|
||||
return !hasDrawing || (moveCount > 0 && pathData.length <= moveCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @param {Object} brush fabric.PencilBrush实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
configure(brush, options = {}) {
|
||||
if (!brush) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 基础属性配置
|
||||
if (options.width !== undefined) {
|
||||
brush.width = options.width;
|
||||
}
|
||||
|
||||
if (options.color !== undefined) {
|
||||
brush.color = options.color;
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
brush.opacity = options.opacity;
|
||||
}
|
||||
|
||||
// 特殊属性配置
|
||||
if (options.decimate !== undefined) {
|
||||
brush.decimate = options.decimate;
|
||||
this.decimate = options.decimate;
|
||||
}
|
||||
|
||||
if (options.strokeLineCap !== undefined) {
|
||||
brush.strokeLineCap = options.strokeLineCap;
|
||||
this.strokeLineCap = options.strokeLineCap;
|
||||
}
|
||||
|
||||
if (options.strokeLineJoin !== undefined) {
|
||||
brush.strokeLineJoin = options.strokeLineJoin;
|
||||
this.strokeLineJoin = options.strokeLineJoin;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 定义铅笔笔刷特有属性
|
||||
const pencilProperties = [
|
||||
{
|
||||
id: "decimate",
|
||||
name: "精细度",
|
||||
type: "slider",
|
||||
defaultValue: this.decimate,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.1,
|
||||
description: "控制笔触路径的简化程度,值越小路径越精细",
|
||||
category: "铅笔设置",
|
||||
order: 100,
|
||||
},
|
||||
{
|
||||
id: "strokeLineCap",
|
||||
name: "线条端点",
|
||||
type: "select",
|
||||
defaultValue: this.strokeLineCap,
|
||||
options: [
|
||||
{ value: "round", label: "圆形" },
|
||||
{ value: "butt", label: "平直" },
|
||||
{ value: "square", label: "方形" },
|
||||
],
|
||||
description: "线条端点的形状",
|
||||
category: "铅笔设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "strokeLineJoin",
|
||||
name: "线条连接",
|
||||
type: "select",
|
||||
defaultValue: this.strokeLineJoin,
|
||||
options: [
|
||||
{ value: "round", label: "圆角" },
|
||||
{ value: "bevel", label: "斜角" },
|
||||
{ value: "miter", label: "尖角" },
|
||||
],
|
||||
description: "线条拐角的连接方式",
|
||||
category: "铅笔设置",
|
||||
order: 120,
|
||||
},
|
||||
{
|
||||
id: "smoothingEnabled",
|
||||
name: "启用平滑",
|
||||
type: "checkbox",
|
||||
defaultValue: false,
|
||||
description: "是否对线条进行平滑处理",
|
||||
category: "铅笔设置",
|
||||
order: 130,
|
||||
},
|
||||
{
|
||||
id: "smoothingFactor",
|
||||
name: "平滑程度",
|
||||
type: "slider",
|
||||
defaultValue: 0.5,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "线条平滑的强度",
|
||||
category: "铅笔设置",
|
||||
order: 140,
|
||||
// 只有当smoothingEnabled为true时才显示
|
||||
visibleWhen: { smoothingEnabled: true },
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...pencilProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理铅笔特有属性
|
||||
if (propId === "decimate") {
|
||||
this.decimate = value;
|
||||
if (this.brush) {
|
||||
this.brush.decimate = value;
|
||||
return true;
|
||||
}
|
||||
} else if (propId === "strokeLineCap") {
|
||||
this.strokeLineCap = value;
|
||||
if (this.brush) {
|
||||
this.brush.strokeLineCap = value;
|
||||
return true;
|
||||
}
|
||||
} else if (propId === "strokeLineJoin") {
|
||||
this.strokeLineJoin = value;
|
||||
if (this.brush) {
|
||||
this.brush.strokeLineJoin = value;
|
||||
return true;
|
||||
}
|
||||
} else if (propId === "smoothingEnabled") {
|
||||
this.smoothingEnabled = value;
|
||||
// 实现平滑逻辑...
|
||||
return true;
|
||||
} else if (propId === "smoothingFactor") {
|
||||
this.smoothingFactor = value;
|
||||
// 实现平滑度调整...
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
// 实际项目中可以返回一个实际的预览图URL
|
||||
return "data:image/svg+xml;base64,..."; // 示例SVG
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
|
||||
/**
|
||||
* 丝带笔刷
|
||||
* 创建流畅的飘带状线条
|
||||
*/
|
||||
export class RibbonBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "ribbon",
|
||||
name: "飘带",
|
||||
description: "创建流畅的飘带状线条,具有动态宽度变化和曲线美感",
|
||||
category: "特效笔刷",
|
||||
icon: "ribbon",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 丝带笔刷特有属性
|
||||
this.ribbonWidth = options.ribbonWidth || 20;
|
||||
this.widthVariation = options.widthVariation || 0.5;
|
||||
this.ribbonSmoothness = options.ribbonSmoothness || 0.7;
|
||||
this.gradient = options.gradient !== undefined ? options.gradient : true;
|
||||
this.gradientColors = options.gradientColors || ["#000000", "#555555"];
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric笔刷实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生丝带笔刷
|
||||
this.brush = new fabric.RibbonBrush(this.canvas);
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @param {Object} brush fabric笔刷实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
configure(brush, options = {}) {
|
||||
if (!brush) return;
|
||||
|
||||
// 基础属性配置
|
||||
if (options.width !== undefined) {
|
||||
brush.width = options.width;
|
||||
// 基于主宽度更新丝带宽度
|
||||
this.ribbonWidth = options.width * 2;
|
||||
}
|
||||
|
||||
if (options.color !== undefined) {
|
||||
brush.color = options.color;
|
||||
|
||||
// 如果启用渐变,更新渐变的第一个颜色
|
||||
if (this.gradient && this.gradientColors.length > 0) {
|
||||
this.gradientColors[0] = options.color;
|
||||
this.updateGradient();
|
||||
}
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
brush.opacity = options.opacity;
|
||||
}
|
||||
|
||||
// 丝带笔刷特有属性
|
||||
if (options.ribbonWidth !== undefined) {
|
||||
this.ribbonWidth = options.ribbonWidth;
|
||||
|
||||
// 如果原生笔刷支持此属性
|
||||
if (brush.ribbonWidth !== undefined) {
|
||||
brush.ribbonWidth = this.ribbonWidth;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.widthVariation !== undefined) {
|
||||
this.widthVariation = options.widthVariation;
|
||||
|
||||
// 如果原生笔刷支持此属性
|
||||
if (brush.widthVariation !== undefined) {
|
||||
brush.widthVariation = this.widthVariation;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.ribbonSmoothness !== undefined) {
|
||||
this.ribbonSmoothness = options.ribbonSmoothness;
|
||||
|
||||
// 如果原生笔刷支持此属性
|
||||
if (brush.ribbonSmoothness !== undefined) {
|
||||
brush.ribbonSmoothness = this.ribbonSmoothness;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.gradient !== undefined) {
|
||||
this.gradient = options.gradient;
|
||||
this.updateGradient();
|
||||
}
|
||||
|
||||
if (options.gradientColors !== undefined) {
|
||||
this.gradientColors = options.gradientColors;
|
||||
this.updateGradient();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新渐变设置
|
||||
* @private
|
||||
*/
|
||||
updateGradient() {
|
||||
if (!this.brush || !this.canvas) return;
|
||||
|
||||
if (this.gradient && this.gradientColors.length >= 2) {
|
||||
// 创建渐变对象
|
||||
const ctx = this.canvas.contextTop;
|
||||
const gradient = ctx.createLinearGradient(0, 0, this.ribbonWidth, 0);
|
||||
|
||||
// 添加渐变色
|
||||
const colorCount = this.gradientColors.length;
|
||||
this.gradientColors.forEach((color, index) => {
|
||||
gradient.addColorStop(index / (colorCount - 1), color);
|
||||
});
|
||||
|
||||
// 如果原生笔刷支持渐变
|
||||
if (typeof this.brush.setGradient === "function") {
|
||||
this.brush.setGradient(gradient);
|
||||
} else if (this.brush.gradient !== undefined) {
|
||||
this.brush.gradient = gradient;
|
||||
}
|
||||
|
||||
// 如果原生笔刷支持渐变标志
|
||||
if (this.brush.useGradient !== undefined) {
|
||||
this.brush.useGradient = true;
|
||||
}
|
||||
} else if (this.brush.useGradient !== undefined) {
|
||||
// 禁用渐变
|
||||
this.brush.useGradient = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置丝带宽度
|
||||
* @param {Number} width 宽度值
|
||||
*/
|
||||
setRibbonWidth(width) {
|
||||
this.ribbonWidth = Math.max(5, Math.min(100, width));
|
||||
|
||||
if (this.brush && this.brush.ribbonWidth !== undefined) {
|
||||
this.brush.ribbonWidth = this.ribbonWidth;
|
||||
}
|
||||
|
||||
// 更新渐变(因为宽度变了)
|
||||
if (this.gradient) {
|
||||
this.updateGradient();
|
||||
}
|
||||
|
||||
return this.ribbonWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置宽度变化率
|
||||
* @param {Number} variation 变化率(0-1)
|
||||
*/
|
||||
setWidthVariation(variation) {
|
||||
this.widthVariation = Math.max(0, Math.min(1, variation));
|
||||
|
||||
if (this.brush && this.brush.widthVariation !== undefined) {
|
||||
this.brush.widthVariation = this.widthVariation;
|
||||
}
|
||||
|
||||
return this.widthVariation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置丝带平滑度
|
||||
* @param {Number} smoothness 平滑度值(0-1)
|
||||
*/
|
||||
setRibbonSmoothness(smoothness) {
|
||||
this.ribbonSmoothness = Math.max(0, Math.min(1, smoothness));
|
||||
|
||||
if (this.brush && this.brush.ribbonSmoothness !== undefined) {
|
||||
this.brush.ribbonSmoothness = this.ribbonSmoothness;
|
||||
}
|
||||
|
||||
return this.ribbonSmoothness;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用渐变效果
|
||||
* @param {Boolean} enabled 是否启用
|
||||
*/
|
||||
setGradient(enabled) {
|
||||
this.gradient = enabled;
|
||||
this.updateGradient();
|
||||
return this.gradient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置渐变颜色
|
||||
* @param {Array} colors 颜色数组
|
||||
*/
|
||||
setGradientColors(colors) {
|
||||
if (Array.isArray(colors) && colors.length >= 2) {
|
||||
this.gradientColors = colors;
|
||||
this.updateGradient();
|
||||
}
|
||||
return this.gradientColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 定义丝带笔刷特有属性
|
||||
const ribbonProperties = [
|
||||
{
|
||||
id: "ribbonWidth",
|
||||
name: "飘带宽度",
|
||||
type: "slider",
|
||||
defaultValue: this.ribbonWidth,
|
||||
min: 5,
|
||||
max: 100,
|
||||
step: 5,
|
||||
description: "控制飘带的最大宽度",
|
||||
category: "飘带设置",
|
||||
order: 100,
|
||||
},
|
||||
{
|
||||
id: "widthVariation",
|
||||
name: "宽度变化",
|
||||
type: "slider",
|
||||
defaultValue: this.widthVariation,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制飘带宽度的变化程度",
|
||||
category: "飘带设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "ribbonSmoothness",
|
||||
name: "平滑度",
|
||||
type: "slider",
|
||||
defaultValue: this.ribbonSmoothness,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制飘带曲线的平滑程度",
|
||||
category: "飘带设置",
|
||||
order: 120,
|
||||
},
|
||||
{
|
||||
id: "gradient",
|
||||
name: "启用渐变",
|
||||
type: "checkbox",
|
||||
defaultValue: this.gradient,
|
||||
description: "是否启用渐变效果",
|
||||
category: "飘带设置",
|
||||
order: 130,
|
||||
},
|
||||
{
|
||||
id: "gradientColor1",
|
||||
name: "渐变起始颜色",
|
||||
type: "color",
|
||||
defaultValue: this.gradientColors[0] || "#000000",
|
||||
description: "设置渐变的起始颜色",
|
||||
category: "飘带设置",
|
||||
order: 140,
|
||||
visibleWhen: { gradient: true },
|
||||
},
|
||||
{
|
||||
id: "gradientColor2",
|
||||
name: "渐变结束颜色",
|
||||
type: "color",
|
||||
defaultValue: this.gradientColors[1] || "#555555",
|
||||
description: "设置渐变的结束颜色",
|
||||
category: "飘带设置",
|
||||
order: 150,
|
||||
visibleWhen: { gradient: true },
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...ribbonProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理丝带笔刷特有属性
|
||||
if (propId === "ribbonWidth") {
|
||||
this.setRibbonWidth(value);
|
||||
return true;
|
||||
} else if (propId === "widthVariation") {
|
||||
this.setWidthVariation(value);
|
||||
return true;
|
||||
} else if (propId === "ribbonSmoothness") {
|
||||
this.setRibbonSmoothness(value);
|
||||
return true;
|
||||
} else if (propId === "gradient") {
|
||||
this.setGradient(value);
|
||||
return true;
|
||||
} else if (propId === "gradientColor1") {
|
||||
const colors = [...this.gradientColors];
|
||||
colors[0] = value;
|
||||
this.setGradientColors(colors);
|
||||
return true;
|
||||
} else if (propId === "gradientColor2") {
|
||||
const colors = [...this.gradientColors];
|
||||
colors[1] = value;
|
||||
this.setGradientColors(colors);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImdyYWQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjMDAwIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjNTU1Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTIwIDUwQzMwIDMwIDUwIDMwIDYwIDUwQzcwIDcwIDgwIDcwIDkwIDUwIiBzdHJva2U9InVybCgjZ3JhZCkiIHN0cm9rZS13aWR0aD0iMTAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZmlsbD0ibm9uZSIvPjwvc3ZnPg==";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
|
||||
/**
|
||||
* 阴影笔刷
|
||||
* 创建带有阴影效果的绘制,有深浅变化
|
||||
*/
|
||||
export class ShadedBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "shaded",
|
||||
name: "阴影笔",
|
||||
description: "创建带有阴影效果的绘制,适合素描和明暗表现",
|
||||
category: "绘画笔刷",
|
||||
icon: "shaded",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 阴影笔刷特有属性
|
||||
this.shadowColor = options.shadowColor || "#000000";
|
||||
this.shadowBlur = options.shadowBlur || 5;
|
||||
this.shadowOffsetX = options.shadowOffsetX || 2;
|
||||
this.shadowOffsetY = options.shadowOffsetY || 2;
|
||||
this.blendMode = options.blendMode || "multiply";
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric笔刷实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生阴影笔刷
|
||||
this.brush = new fabric.ShadedBrush(this.canvas);
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @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) {
|
||||
brush.color = options.color;
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
brush.opacity = options.opacity;
|
||||
}
|
||||
|
||||
// 阴影笔刷特有属性
|
||||
if (options.shadowColor !== undefined) {
|
||||
this.shadowColor = options.shadowColor;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.shadow) {
|
||||
brush.shadow.color = this.shadowColor;
|
||||
} else {
|
||||
brush.shadow = new fabric.Shadow({
|
||||
color: this.shadowColor,
|
||||
blur: this.shadowBlur,
|
||||
offsetX: this.shadowOffsetX,
|
||||
offsetY: this.shadowOffsetY,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.shadowBlur !== undefined) {
|
||||
this.shadowBlur = options.shadowBlur;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.shadow) {
|
||||
brush.shadow.blur = this.shadowBlur;
|
||||
} else {
|
||||
brush.shadow = new fabric.Shadow({
|
||||
color: this.shadowColor,
|
||||
blur: this.shadowBlur,
|
||||
offsetX: this.shadowOffsetX,
|
||||
offsetY: this.shadowOffsetY,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.shadowOffsetX !== undefined) {
|
||||
this.shadowOffsetX = options.shadowOffsetX;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.shadow) {
|
||||
brush.shadow.offsetX = this.shadowOffsetX;
|
||||
} else {
|
||||
brush.shadow = new fabric.Shadow({
|
||||
color: this.shadowColor,
|
||||
blur: this.shadowBlur,
|
||||
offsetX: this.shadowOffsetX,
|
||||
offsetY: this.shadowOffsetY,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.shadowOffsetY !== undefined) {
|
||||
this.shadowOffsetY = options.shadowOffsetY;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.shadow) {
|
||||
brush.shadow.offsetY = this.shadowOffsetY;
|
||||
} else {
|
||||
brush.shadow = new fabric.Shadow({
|
||||
color: this.shadowColor,
|
||||
blur: this.shadowBlur,
|
||||
offsetX: this.shadowOffsetX,
|
||||
offsetY: this.shadowOffsetY,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.blendMode !== undefined) {
|
||||
this.blendMode = options.blendMode;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.globalCompositeOperation !== undefined) {
|
||||
brush.globalCompositeOperation = this.blendMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置阴影颜色
|
||||
* @param {String} color 颜色值
|
||||
*/
|
||||
setShadowColor(color) {
|
||||
this.shadowColor = color;
|
||||
|
||||
if (this.brush && this.brush.shadow) {
|
||||
this.brush.shadow.color = this.shadowColor;
|
||||
}
|
||||
|
||||
return this.shadowColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置阴影模糊值
|
||||
* @param {Number} blur 模糊值
|
||||
*/
|
||||
setShadowBlur(blur) {
|
||||
this.shadowBlur = Math.max(0, Math.min(50, blur));
|
||||
|
||||
if (this.brush && this.brush.shadow) {
|
||||
this.brush.shadow.blur = this.shadowBlur;
|
||||
}
|
||||
|
||||
return this.shadowBlur;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置阴影X偏移
|
||||
* @param {Number} offset X偏移值
|
||||
*/
|
||||
setShadowOffsetX(offset) {
|
||||
this.shadowOffsetX = Math.max(-20, Math.min(20, offset));
|
||||
|
||||
if (this.brush && this.brush.shadow) {
|
||||
this.brush.shadow.offsetX = this.shadowOffsetX;
|
||||
}
|
||||
|
||||
return this.shadowOffsetX;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置阴影Y偏移
|
||||
* @param {Number} offset Y偏移值
|
||||
*/
|
||||
setShadowOffsetY(offset) {
|
||||
this.shadowOffsetY = Math.max(-20, Math.min(20, offset));
|
||||
|
||||
if (this.brush && this.brush.shadow) {
|
||||
this.brush.shadow.offsetY = this.shadowOffsetY;
|
||||
}
|
||||
|
||||
return this.shadowOffsetY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置混合模式
|
||||
* @param {String} mode 混合模式
|
||||
*/
|
||||
setBlendMode(mode) {
|
||||
const validModes = [
|
||||
"normal",
|
||||
"multiply",
|
||||
"screen",
|
||||
"overlay",
|
||||
"darken",
|
||||
"lighten",
|
||||
"color-dodge",
|
||||
"color-burn",
|
||||
"hard-light",
|
||||
"soft-light",
|
||||
"difference",
|
||||
"exclusion",
|
||||
"hue",
|
||||
"saturation",
|
||||
"color",
|
||||
"luminosity",
|
||||
];
|
||||
|
||||
if (validModes.includes(mode)) {
|
||||
this.blendMode = mode;
|
||||
|
||||
if (this.brush && this.brush.globalCompositeOperation !== undefined) {
|
||||
this.brush.globalCompositeOperation = this.blendMode;
|
||||
}
|
||||
}
|
||||
|
||||
return this.blendMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 定义阴影笔刷特有属性
|
||||
const shadedProperties = [
|
||||
{
|
||||
id: "shadowColor",
|
||||
name: "阴影颜色",
|
||||
type: "color",
|
||||
defaultValue: this.shadowColor,
|
||||
description: "设置阴影的颜色",
|
||||
category: "阴影设置",
|
||||
order: 100,
|
||||
},
|
||||
{
|
||||
id: "shadowBlur",
|
||||
name: "阴影模糊",
|
||||
type: "slider",
|
||||
defaultValue: this.shadowBlur,
|
||||
min: 0,
|
||||
max: 50,
|
||||
step: 1,
|
||||
description: "控制阴影的模糊程度",
|
||||
category: "阴影设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "shadowOffsetX",
|
||||
name: "阴影X偏移",
|
||||
type: "slider",
|
||||
defaultValue: this.shadowOffsetX,
|
||||
min: -20,
|
||||
max: 20,
|
||||
step: 1,
|
||||
description: "控制阴影的水平偏移",
|
||||
category: "阴影设置",
|
||||
order: 120,
|
||||
},
|
||||
{
|
||||
id: "shadowOffsetY",
|
||||
name: "阴影Y偏移",
|
||||
type: "slider",
|
||||
defaultValue: this.shadowOffsetY,
|
||||
min: -20,
|
||||
max: 20,
|
||||
step: 1,
|
||||
description: "控制阴影的垂直偏移",
|
||||
category: "阴影设置",
|
||||
order: 130,
|
||||
},
|
||||
{
|
||||
id: "blendMode",
|
||||
name: "混合模式",
|
||||
type: "select",
|
||||
defaultValue: this.blendMode,
|
||||
options: [
|
||||
{ value: "normal", label: "正常" },
|
||||
{ value: "multiply", label: "正片叠底" },
|
||||
{ value: "screen", label: "滤色" },
|
||||
{ value: "overlay", label: "叠加" },
|
||||
{ value: "darken", label: "变暗" },
|
||||
{ value: "lighten", label: "变亮" },
|
||||
{ value: "color-dodge", label: "颜色减淡" },
|
||||
{ value: "color-burn", label: "颜色加深" },
|
||||
{ value: "hard-light", label: "强光" },
|
||||
{ value: "soft-light", label: "柔光" },
|
||||
{ value: "difference", label: "差值" },
|
||||
{ value: "exclusion", label: "排除" },
|
||||
],
|
||||
description: "设置阴影的混合模式",
|
||||
category: "阴影设置",
|
||||
order: 140,
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...shadedProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理阴影笔刷特有属性
|
||||
if (propId === "shadowColor") {
|
||||
this.setShadowColor(value);
|
||||
return true;
|
||||
} else if (propId === "shadowBlur") {
|
||||
this.setShadowBlur(value);
|
||||
return true;
|
||||
} else if (propId === "shadowOffsetX") {
|
||||
this.setShadowOffsetX(value);
|
||||
return true;
|
||||
} else if (propId === "shadowOffsetY") {
|
||||
this.setShadowOffsetY(value);
|
||||
return true;
|
||||
} else if (propId === "blendMode") {
|
||||
this.setBlendMode(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48Y2lyY2xlIGN4PSI0MCIgY3k9IjQwIiByPSIyMCIgZmlsbD0iIzY2NiIvPjxjaXJjbGUgY3g9IjQ1IiBjeT0iNDUiIHI9IjIwIiBmaWxsPSIjMDAwIi8+PHBhdGggZD0iTTIwIDgwQzMwIDYwIDUwIDcwIDcwIDUwIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iOCIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PHBhdGggZD0iTTIzIDgzQzMzIDYzIDUzIDczIDczIDUzIiBzdHJva2U9IiM2NjYiIHN0cm9rZS13aWR0aD0iOCIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9zdmc+";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
|
||||
/**
|
||||
* 素描笔刷
|
||||
* 创建手绘素描效果,有不规则的线条和纹理
|
||||
*/
|
||||
export class SketchyBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "sketchy",
|
||||
name: "素描",
|
||||
description: "创建手绘素描效果,有不规则的线条和纹理",
|
||||
category: "绘画笔刷",
|
||||
icon: "sketchy",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 素描笔刷特有属性
|
||||
this.roughness = options.roughness || 0.7;
|
||||
this.bowing = options.bowing || 0.5;
|
||||
this.stroke = options.stroke !== undefined ? options.stroke : true;
|
||||
this.hachureAngle = options.hachureAngle || 60;
|
||||
this.dashOffset = options.dashOffset || 0;
|
||||
this.dashArray = options.dashArray || [6, 2];
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric笔刷实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生素描笔刷
|
||||
this.brush = new fabric.SketchyBrush(this.canvas);
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @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) {
|
||||
brush.color = options.color;
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
brush.opacity = options.opacity;
|
||||
}
|
||||
|
||||
// 素描笔刷特有属性
|
||||
if (options.roughness !== undefined) {
|
||||
this.roughness = options.roughness;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.roughness !== undefined) {
|
||||
brush.roughness = this.roughness;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.bowing !== undefined) {
|
||||
this.bowing = options.bowing;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.bowing !== undefined) {
|
||||
brush.bowing = this.bowing;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.stroke !== undefined) {
|
||||
this.stroke = options.stroke;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.stroke !== undefined) {
|
||||
brush.stroke = this.stroke;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.hachureAngle !== undefined) {
|
||||
this.hachureAngle = options.hachureAngle;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.hachureAngle !== undefined) {
|
||||
brush.hachureAngle = this.hachureAngle;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.dashOffset !== undefined) {
|
||||
this.dashOffset = options.dashOffset;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.dashOffset !== undefined) {
|
||||
brush.dashOffset = this.dashOffset;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.dashArray !== undefined) {
|
||||
this.dashArray = options.dashArray;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.dashArray !== undefined) {
|
||||
brush.dashArray = this.dashArray;
|
||||
}
|
||||
}
|
||||
|
||||
// 为笔刷设置手绘效果
|
||||
const originalOnMouseMove = brush.onMouseMove;
|
||||
brush.onMouseMove = function (pointer, options) {
|
||||
// 添加微小随机偏移,模拟手绘效果
|
||||
const jitter = (this.width / 4) * this.roughness;
|
||||
pointer.x += (Math.random() - 0.5) * jitter;
|
||||
pointer.y += (Math.random() - 0.5) * jitter;
|
||||
|
||||
// 调用原始方法
|
||||
if (originalOnMouseMove) {
|
||||
originalOnMouseMove.call(this, pointer, options);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置粗糙度
|
||||
* @param {Number} value 粗糙度值(0-1)
|
||||
*/
|
||||
setRoughness(value) {
|
||||
this.roughness = Math.max(0, Math.min(1, value));
|
||||
|
||||
if (this.brush && this.brush.roughness !== undefined) {
|
||||
this.brush.roughness = this.roughness;
|
||||
}
|
||||
|
||||
return this.roughness;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置弯曲度
|
||||
* @param {Number} value 弯曲度值(0-1)
|
||||
*/
|
||||
setBowing(value) {
|
||||
this.bowing = Math.max(0, Math.min(1, value));
|
||||
|
||||
if (this.brush && this.brush.bowing !== undefined) {
|
||||
this.brush.bowing = this.bowing;
|
||||
}
|
||||
|
||||
return this.bowing;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否描边
|
||||
* @param {Boolean} value 是否描边
|
||||
*/
|
||||
setStroke(value) {
|
||||
this.stroke = value;
|
||||
|
||||
if (this.brush && this.brush.stroke !== undefined) {
|
||||
this.brush.stroke = this.stroke;
|
||||
}
|
||||
|
||||
return this.stroke;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置素描线条角度
|
||||
* @param {Number} value 角度值(0-180)
|
||||
*/
|
||||
setHachureAngle(value) {
|
||||
this.hachureAngle = Math.max(0, Math.min(180, value));
|
||||
|
||||
if (this.brush && this.brush.hachureAngle !== undefined) {
|
||||
this.brush.hachureAngle = this.hachureAngle;
|
||||
}
|
||||
|
||||
return this.hachureAngle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置虚线偏移量
|
||||
* @param {Number} value 偏移量
|
||||
*/
|
||||
setDashOffset(value) {
|
||||
this.dashOffset = value;
|
||||
|
||||
if (this.brush && this.brush.dashOffset !== undefined) {
|
||||
this.brush.dashOffset = this.dashOffset;
|
||||
}
|
||||
|
||||
return this.dashOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置虚线数组
|
||||
* @param {Array} value 虚线数组[线长, 间隔]
|
||||
*/
|
||||
setDashArray(value) {
|
||||
if (Array.isArray(value) && value.length >= 2) {
|
||||
this.dashArray = value;
|
||||
|
||||
if (this.brush && this.brush.dashArray !== undefined) {
|
||||
this.brush.dashArray = this.dashArray;
|
||||
}
|
||||
}
|
||||
|
||||
return this.dashArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 定义素描笔刷特有属性
|
||||
const sketchyProperties = [
|
||||
{
|
||||
id: "roughness",
|
||||
name: "粗糙度",
|
||||
type: "slider",
|
||||
defaultValue: this.roughness,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制素描线条的粗糙程度",
|
||||
category: "素描设置",
|
||||
order: 100,
|
||||
},
|
||||
{
|
||||
id: "bowing",
|
||||
name: "弯曲度",
|
||||
type: "slider",
|
||||
defaultValue: this.bowing,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制素描线条的弯曲程度",
|
||||
category: "素描设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "stroke",
|
||||
name: "描边",
|
||||
type: "checkbox",
|
||||
defaultValue: this.stroke,
|
||||
description: "是否使用描边",
|
||||
category: "素描设置",
|
||||
order: 120,
|
||||
},
|
||||
{
|
||||
id: "hachureAngle",
|
||||
name: "线条角度",
|
||||
type: "slider",
|
||||
defaultValue: this.hachureAngle,
|
||||
min: 0,
|
||||
max: 180,
|
||||
step: 5,
|
||||
description: "控制素描线条的角度",
|
||||
category: "素描设置",
|
||||
order: 130,
|
||||
},
|
||||
{
|
||||
id: "dashOffset",
|
||||
name: "虚线偏移",
|
||||
type: "slider",
|
||||
defaultValue: this.dashOffset,
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 1,
|
||||
description: "控制虚线的偏移量",
|
||||
category: "素描设置",
|
||||
order: 140,
|
||||
},
|
||||
{
|
||||
id: "dashArray",
|
||||
name: "虚线模式",
|
||||
type: "select",
|
||||
defaultValue: JSON.stringify(this.dashArray),
|
||||
options: [
|
||||
{ value: JSON.stringify([0]), label: "实线" },
|
||||
{ value: JSON.stringify([6, 2]), label: "短虚线" },
|
||||
{ value: JSON.stringify([10, 5]), label: "长虚线" },
|
||||
{ value: JSON.stringify([2, 2]), label: "点线" },
|
||||
{ value: JSON.stringify([10, 5, 2, 5]), label: "点划线" },
|
||||
],
|
||||
description: "设置虚线的模式",
|
||||
category: "素描设置",
|
||||
order: 150,
|
||||
parseValue: (value) => JSON.parse(value),
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...sketchyProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理素描笔刷特有属性
|
||||
if (propId === "roughness") {
|
||||
this.setRoughness(value);
|
||||
return true;
|
||||
} else if (propId === "bowing") {
|
||||
this.setBowing(value);
|
||||
return true;
|
||||
} else if (propId === "stroke") {
|
||||
this.setStroke(value);
|
||||
return true;
|
||||
} else if (propId === "hachureAngle") {
|
||||
this.setHachureAngle(value);
|
||||
return true;
|
||||
} else if (propId === "dashOffset") {
|
||||
this.setDashOffset(value);
|
||||
return true;
|
||||
} else if (propId === "dashArray") {
|
||||
let parsedValue = value;
|
||||
if (typeof value === "string") {
|
||||
try {
|
||||
parsedValue = JSON.parse(value);
|
||||
} catch (e) {
|
||||
console.error("Invalid dashArray value:", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.setDashArray(parsedValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBkPSJNMjMgMzBDMjUgMjggNTIgMzggNzUgMzciIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIzIiBmaWxsPSJub25lIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48cGF0aCBkPSJNMjIgNDBDMjIgMzggNTkgNDYgNzYgNDMiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJub25lIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48cGF0aCBkPSJNMjAgNTBDMjIgNDggNTYgNTYgNzYgNTIiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJub25lIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48cGF0aCBkPSJNMjQgNjBDMjMgNTggNDYgNjQgNzUgNjQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIyLjgiIGZpbGw9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjxwYXRoIGQ9Ik0yNiA3MkMyNyA2OSA0OSA3NCA3NSA3MiIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjIuNCIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9zdmc+";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
|
||||
/**
|
||||
* 喷漆笔刷
|
||||
* 创建喷漆效果,点状分散的绘制风格
|
||||
*/
|
||||
export class SpraypaintBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "spraypaint",
|
||||
name: "喷漆笔刷",
|
||||
description: "创建喷漆效果,点状分散的绘制风格",
|
||||
category: "绘画笔刷",
|
||||
icon: "spraypaint",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 喷漆笔刷特有属性
|
||||
this.density = options.density || 20;
|
||||
this.sprayRadius = options.sprayRadius || 10;
|
||||
this.randomOpacity =
|
||||
options.randomOpacity !== undefined ? options.randomOpacity : true;
|
||||
this.dotSize = options.dotSize || 1;
|
||||
this.dotShape = options.dotShape || "circle";
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric笔刷实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生喷漆笔刷
|
||||
this.brush = new fabric.SprayBrush(this.canvas);
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @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) {
|
||||
brush.color = options.color;
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
brush.opacity = options.opacity;
|
||||
}
|
||||
|
||||
// 喷漆笔刷特有属性
|
||||
if (options.density !== undefined) {
|
||||
this.density = options.density;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.density !== undefined) {
|
||||
brush.density = this.density;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.sprayRadius !== undefined) {
|
||||
this.sprayRadius = options.sprayRadius;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.sprayWidth !== undefined) {
|
||||
brush.sprayWidth = this.sprayRadius;
|
||||
} else if (brush.width !== undefined) {
|
||||
brush.width = this.sprayRadius;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.randomOpacity !== undefined) {
|
||||
this.randomOpacity = options.randomOpacity;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.randomOpacity !== undefined) {
|
||||
brush.randomOpacity = this.randomOpacity;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.dotSize !== undefined) {
|
||||
this.dotSize = options.dotSize;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.dotWidth !== undefined) {
|
||||
brush.dotWidth = this.dotSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.dotShape !== undefined) {
|
||||
this.dotShape = options.dotShape;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.dotShape !== undefined) {
|
||||
brush.dotShape = this.dotShape;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置喷漆密度
|
||||
* @param {Number} value 密度值
|
||||
*/
|
||||
setDensity(value) {
|
||||
this.density = Math.max(1, Math.min(100, value));
|
||||
|
||||
if (this.brush && this.brush.density !== undefined) {
|
||||
this.brush.density = this.density;
|
||||
}
|
||||
|
||||
return this.density;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置喷漆半径
|
||||
* @param {Number} value 半径值
|
||||
*/
|
||||
setSprayRadius(value) {
|
||||
this.sprayRadius = Math.max(1, value);
|
||||
|
||||
if (this.brush) {
|
||||
if (this.brush.sprayWidth !== undefined) {
|
||||
this.brush.sprayWidth = this.sprayRadius;
|
||||
} else if (this.brush.width !== undefined) {
|
||||
this.brush.width = this.sprayRadius;
|
||||
}
|
||||
}
|
||||
|
||||
return this.sprayRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否随机透明度
|
||||
* @param {Boolean} value 是否随机透明度
|
||||
*/
|
||||
setRandomOpacity(value) {
|
||||
this.randomOpacity = value;
|
||||
|
||||
if (this.brush && this.brush.randomOpacity !== undefined) {
|
||||
this.brush.randomOpacity = this.randomOpacity;
|
||||
}
|
||||
|
||||
return this.randomOpacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置点大小
|
||||
* @param {Number} value 点大小
|
||||
*/
|
||||
setDotSize(value) {
|
||||
this.dotSize = Math.max(0.1, value);
|
||||
|
||||
if (this.brush && this.brush.dotWidth !== undefined) {
|
||||
this.brush.dotWidth = this.dotSize;
|
||||
}
|
||||
|
||||
return this.dotSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置点形状
|
||||
* @param {String} value 点形状,如 'circle', 'square', 'diamond'
|
||||
*/
|
||||
setDotShape(value) {
|
||||
const validShapes = ["circle", "square", "diamond", "random"];
|
||||
|
||||
if (validShapes.includes(value)) {
|
||||
this.dotShape = value;
|
||||
|
||||
if (this.brush && this.brush.dotShape !== undefined) {
|
||||
this.brush.dotShape = this.dotShape;
|
||||
}
|
||||
}
|
||||
|
||||
return this.dotShape;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 定义喷漆笔刷特有属性
|
||||
const spraypaintProperties = [
|
||||
{
|
||||
id: "density",
|
||||
name: "喷漆密度",
|
||||
type: "slider",
|
||||
defaultValue: this.density,
|
||||
min: 1,
|
||||
max: 100,
|
||||
step: 1,
|
||||
description: "控制喷漆点的密度",
|
||||
category: "喷漆设置",
|
||||
order: 100,
|
||||
},
|
||||
{
|
||||
id: "sprayRadius",
|
||||
name: "喷漆半径",
|
||||
type: "slider",
|
||||
defaultValue: this.sprayRadius,
|
||||
min: 1,
|
||||
max: 50,
|
||||
step: 1,
|
||||
description: "控制喷漆的覆盖半径",
|
||||
category: "喷漆设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "randomOpacity",
|
||||
name: "随机透明度",
|
||||
type: "checkbox",
|
||||
defaultValue: this.randomOpacity,
|
||||
description: "使喷漆点有随机透明度",
|
||||
category: "喷漆设置",
|
||||
order: 120,
|
||||
},
|
||||
{
|
||||
id: "dotSize",
|
||||
name: "点大小",
|
||||
type: "slider",
|
||||
defaultValue: this.dotSize,
|
||||
min: 0.1,
|
||||
max: 10,
|
||||
step: 0.1,
|
||||
description: "控制喷漆点的大小",
|
||||
category: "喷漆设置",
|
||||
order: 130,
|
||||
},
|
||||
{
|
||||
id: "dotShape",
|
||||
name: "点形状",
|
||||
type: "select",
|
||||
defaultValue: this.dotShape,
|
||||
options: [
|
||||
{ value: "circle", label: "圆形" },
|
||||
{ value: "square", label: "方形" },
|
||||
{ value: "diamond", label: "菱形" },
|
||||
{ value: "random", label: "随机" },
|
||||
],
|
||||
description: "设置喷漆点的形状",
|
||||
category: "喷漆设置",
|
||||
order: 140,
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...spraypaintProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理喷漆笔刷特有属性
|
||||
if (propId === "density") {
|
||||
this.setDensity(value);
|
||||
return true;
|
||||
} else if (propId === "sprayRadius") {
|
||||
this.setSprayRadius(value);
|
||||
return true;
|
||||
} else if (propId === "randomOpacity") {
|
||||
this.setRandomOpacity(value);
|
||||
return true;
|
||||
} else if (propId === "dotSize") {
|
||||
this.setDotSize(value);
|
||||
return true;
|
||||
} else if (propId === "dotShape") {
|
||||
this.setDotShape(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48Y2lyY2xlIGN4PSI1NSIgY3k9IjUwIiByPSIyMCIgZmlsbD0icmdiYSgwLDAsMCwwLjEpIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMC41Ii8+PGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iMSIgZmlsbD0iIzAwMCIvPjxjaXJjbGUgY3g9IjU1IiBjeT0iNTUiIHI9IjAuOCIgZmlsbD0iIzAwMCIvPjxjaXJjbGUgY3g9IjYwIiBjeT0iNDUiIHI9IjEuMiIgZmlsbD0iIzAwMCIvPjxjaXJjbGUgY3g9IjQ1IiBjeT0iNTUiIHI9IjAuNyIgZmlsbD0iIzAwMCIvPjxjaXJjbGUgY3g9IjQ3IiBjeT0iNDgiIHI9IjAuOSIgZmlsbD0iIzAwMCIvPjxjaXJjbGUgY3g9IjU4IiBjeT0iNTMiIHI9IjEuMSIgZmlsbD0iIzAwMCIvPjxjaXJjbGUgY3g9IjYyIiBjeT0iNTYiIHI9IjAuNiIgZmlsbD0iIzAwMCIvPjxjaXJjbGUgY3g9IjUyIiBjeT0iNTgiIHI9IjAuOCIgZmlsbD0iIzAwMCIvPjxjaXJjbGUgY3g9IjU0IiBjeT0iNDMiIHI9IjEiIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSI0OSIgY3k9IjQzIiByPSIwLjYiIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSI0MyIgY3k9IjQ3IiByPSIwLjciIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSI2NSIgY3k9IjQ4IiByPSIwLjkiIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSI2MiIgY3k9IjQxIiByPSIwLjUiIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSI0OSIgY3k9IjYxIiByPSIwLjgiIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSI2NiIgY3k9IjUyIiByPSIwLjciIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSI0MSIgY3k9IjUxIiByPSIwLjYiIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSI2OCIgY3k9IjU3IiByPSIwLjQiIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSI0NSIgY3k9IjQwIiByPSIwLjUiIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSI1NyIgY3k9IjYxIiByPSIwLjciIGZpbGw9IiMwMDAiLz48L3N2Zz4=";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,855 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
//import { fabric } from "fabric-with-all";
|
||||
import texturePresetManager from "../TexturePresetManager";
|
||||
|
||||
/**
|
||||
* 纹理笔刷
|
||||
* 使用图像纹理进行绘制的笔刷
|
||||
*/
|
||||
export class TextureBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "texture",
|
||||
name: "纹理笔刷",
|
||||
description: "使用图像纹理进行绘制的笔刷",
|
||||
category: "特效笔刷",
|
||||
icon: "texture",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 纹理笔刷特有属性
|
||||
this.textureSource = options.textureSource || null;
|
||||
this.textureRepeat = options.textureRepeat || "repeat";
|
||||
this.textureScale = options.textureScale || 1;
|
||||
this.textureAngle = options.textureAngle || 0;
|
||||
this.textureOpacity =
|
||||
options.textureOpacity !== undefined ? options.textureOpacity : 1;
|
||||
|
||||
// 预设材质相关
|
||||
this.selectedTextureId = options.selectedTextureId || null;
|
||||
this.texturePresets = [];
|
||||
|
||||
// 加载预设材质
|
||||
this._loadTexturePresets();
|
||||
|
||||
// 当前选中的材质索引
|
||||
this.currentTextureIndex = options.currentTextureIndex || 0;
|
||||
|
||||
// 从预设管理器加载自定义材质
|
||||
texturePresetManager.loadCustomTexturesFromStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载材质预设
|
||||
* @private
|
||||
*/
|
||||
_loadTexturePresets() {
|
||||
// 从预设管理器获取所有材质
|
||||
this.texturePresets = texturePresetManager.getAllTextures();
|
||||
|
||||
// 如果没有选中的材质ID,使用第一个预设材质
|
||||
if (!this.selectedTextureId && this.texturePresets.length > 0) {
|
||||
this.selectedTextureId = this.texturePresets[0].id;
|
||||
this.currentTextureIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric笔刷实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生纹理笔刷
|
||||
this.brush = new fabric.PatternBrush(this.canvas);
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
// 如果有选中的材质,则设置纹理
|
||||
if (this.selectedTextureId) {
|
||||
this.setTextureById(this.selectedTextureId);
|
||||
} else if (this.textureSource) {
|
||||
this.setTexture(this.textureSource);
|
||||
}
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @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) {
|
||||
brush.color = options.color;
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
brush.opacity = options.opacity;
|
||||
}
|
||||
|
||||
// 纹理笔刷特有属性
|
||||
if (options.textureRepeat !== undefined) {
|
||||
this.textureRepeat = options.textureRepeat;
|
||||
// 需要重新应用纹理以应用重复模式
|
||||
if (this.selectedTextureId) {
|
||||
this.setTextureById(this.selectedTextureId);
|
||||
} else if (this.textureSource) {
|
||||
this.setTexture(this.textureSource);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.textureScale !== undefined) {
|
||||
this.textureScale = options.textureScale;
|
||||
// 需要重新应用纹理以应用缩放
|
||||
if (this.selectedTextureId) {
|
||||
this.setTextureById(this.selectedTextureId);
|
||||
} else if (this.textureSource) {
|
||||
this.setTexture(this.textureSource);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.textureAngle !== undefined) {
|
||||
this.textureAngle = options.textureAngle;
|
||||
// 需要重新应用纹理以应用旋转角度
|
||||
if (this.selectedTextureId) {
|
||||
this.setTextureById(this.selectedTextureId);
|
||||
} else if (this.textureSource) {
|
||||
this.setTexture(this.textureSource);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.textureOpacity !== undefined) {
|
||||
this.textureOpacity = options.textureOpacity;
|
||||
// 需要重新应用纹理以应用透明度
|
||||
if (this.selectedTextureId) {
|
||||
this.setTextureById(this.selectedTextureId);
|
||||
} else if (this.textureSource) {
|
||||
this.setTexture(this.textureSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据材质ID设置纹理
|
||||
* @param {String} textureId 材质ID
|
||||
* @returns {Promise} 加载完成的Promise
|
||||
*/
|
||||
setTextureById(textureId) {
|
||||
const texture = texturePresetManager.getTextureById(textureId);
|
||||
if (!texture) {
|
||||
return Promise.reject(new Error(`材质 ${textureId} 不存在`));
|
||||
}
|
||||
|
||||
this.selectedTextureId = textureId;
|
||||
|
||||
// 更新当前材质索引
|
||||
const allTextures = texturePresetManager.getAllTextures();
|
||||
this.currentTextureIndex = allTextures.findIndex((t) => t.id === textureId);
|
||||
|
||||
return this.setTexture(texture.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置纹理
|
||||
* @param {String|Object} source 纹理源(URL或Image对象)
|
||||
* @returns {Promise} 加载完成的Promise
|
||||
*/
|
||||
setTexture(source) {
|
||||
this.textureSource = source;
|
||||
|
||||
if (!this.brush) {
|
||||
return Promise.reject(new Error("笔刷实例不存在"));
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof source === "string") {
|
||||
// 如果是URL,加载图像
|
||||
fabric.util.loadImage(source, (img) => {
|
||||
if (!img) {
|
||||
reject(new Error("纹理加载失败"));
|
||||
return;
|
||||
}
|
||||
this._applyTextureToPatternBrush(img);
|
||||
resolve(img);
|
||||
});
|
||||
} else if (
|
||||
source instanceof Image ||
|
||||
source instanceof HTMLCanvasElement
|
||||
) {
|
||||
// 如果已经是Image或Canvas对象,直接使用
|
||||
this._applyTextureToPatternBrush(source);
|
||||
resolve(source);
|
||||
} else {
|
||||
reject(new Error("无效的纹理源"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将纹理应用到PatternBrush
|
||||
* @param {Object} img 图像对象
|
||||
* @private
|
||||
*/
|
||||
_applyTextureToPatternBrush(img) {
|
||||
if (!this.brush || !img) return;
|
||||
|
||||
// 创建Canvas来处理纹理
|
||||
const canvasTexture = document.createElement("canvas");
|
||||
const ctx = canvasTexture.getContext("2d");
|
||||
|
||||
// 根据缩放设置Canvas大小
|
||||
const width = img.width * this.textureScale;
|
||||
const height = img.height * this.textureScale;
|
||||
canvasTexture.width = width;
|
||||
canvasTexture.height = height;
|
||||
|
||||
// 绘制前应用旋转
|
||||
if (this.textureAngle !== 0) {
|
||||
ctx.save();
|
||||
ctx.translate(width / 2, height / 2);
|
||||
ctx.rotate((this.textureAngle * Math.PI) / 180);
|
||||
ctx.translate(-width / 2, -height / 2);
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
ctx.restore();
|
||||
} else {
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
}
|
||||
|
||||
// 应用透明度
|
||||
if (this.textureOpacity < 1) {
|
||||
ctx.globalAlpha = this.textureOpacity;
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
// 创建Pattern对象
|
||||
const pattern = new fabric.Pattern({
|
||||
source: canvasTexture,
|
||||
repeat: this.textureRepeat,
|
||||
});
|
||||
|
||||
// 设置笔刷源纹理
|
||||
if (typeof this.brush.setSource === "function") {
|
||||
this.brush.setSource(pattern);
|
||||
} else if (typeof this.brush.source === "object") {
|
||||
this.brush.source = pattern;
|
||||
} else if (typeof this.brush.pattern === "object") {
|
||||
this.brush.pattern = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置纹理重复模式
|
||||
* @param {String} mode 重复模式:'repeat', 'repeat-x', 'repeat-y', 'no-repeat'
|
||||
* @returns {String} 设置后的重复模式
|
||||
*/
|
||||
setTextureRepeat(mode) {
|
||||
const validModes = ["repeat", "repeat-x", "repeat-y", "no-repeat"];
|
||||
if (validModes.includes(mode)) {
|
||||
this.textureRepeat = mode;
|
||||
|
||||
// 重新应用纹理以更新重复模式
|
||||
if (this.selectedTextureId) {
|
||||
this.setTextureById(this.selectedTextureId);
|
||||
} else if (this.textureSource) {
|
||||
this.setTexture(this.textureSource);
|
||||
}
|
||||
}
|
||||
|
||||
return this.textureRepeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置纹理缩放比例
|
||||
* @param {Number} scale 缩放比例
|
||||
* @returns {Number} 设置后的缩放比例
|
||||
*/
|
||||
setTextureScale(scale) {
|
||||
this.textureScale = Math.max(0.1, scale);
|
||||
|
||||
// 重新应用纹理以更新缩放
|
||||
if (this.selectedTextureId) {
|
||||
this.setTextureById(this.selectedTextureId);
|
||||
} else if (this.textureSource) {
|
||||
this.setTexture(this.textureSource);
|
||||
}
|
||||
|
||||
return this.textureScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置纹理旋转角度
|
||||
* @param {Number} angle 旋转角度(度)
|
||||
* @returns {Number} 设置后的旋转角度
|
||||
*/
|
||||
setTextureAngle(angle) {
|
||||
this.textureAngle = angle % 360;
|
||||
|
||||
// 重新应用纹理以更新旋转角度
|
||||
if (this.selectedTextureId) {
|
||||
this.setTextureById(this.selectedTextureId);
|
||||
} else if (this.textureSource) {
|
||||
this.setTexture(this.textureSource);
|
||||
}
|
||||
|
||||
return this.textureAngle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置纹理透明度
|
||||
* @param {Number} opacity 透明度
|
||||
* @returns {Number} 设置后的透明度
|
||||
*/
|
||||
setTextureOpacity(opacity) {
|
||||
this.textureOpacity = Math.min(1, Math.max(0, opacity));
|
||||
|
||||
// 重新应用纹理以更新透明度
|
||||
if (this.selectedTextureId) {
|
||||
this.setTextureById(this.selectedTextureId);
|
||||
} else if (this.textureSource) {
|
||||
this.setTexture(this.textureSource);
|
||||
}
|
||||
|
||||
return this.textureOpacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到下一个预设材质
|
||||
* @returns {Promise} 切换完成的Promise
|
||||
*/
|
||||
nextTexture() {
|
||||
const textures = texturePresetManager.getAllTextures();
|
||||
if (textures.length === 0) return Promise.resolve();
|
||||
|
||||
this.currentTextureIndex = (this.currentTextureIndex + 1) % textures.length;
|
||||
const nextTexture = textures[this.currentTextureIndex];
|
||||
|
||||
return this.setTextureById(nextTexture.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到上一个预设材质
|
||||
* @returns {Promise} 切换完成的Promise
|
||||
*/
|
||||
previousTexture() {
|
||||
const textures = texturePresetManager.getAllTextures();
|
||||
if (textures.length === 0) return Promise.resolve();
|
||||
|
||||
this.currentTextureIndex =
|
||||
this.currentTextureIndex === 0
|
||||
? textures.length - 1
|
||||
: this.currentTextureIndex - 1;
|
||||
const prevTexture = textures[this.currentTextureIndex];
|
||||
|
||||
return this.setTextureById(prevTexture.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用索引切换纹理
|
||||
* @param {Number} index 纹理索引
|
||||
*/
|
||||
switchTexture(index) {
|
||||
const textures = texturePresetManager.getAllTextures();
|
||||
if (index >= 0 && index < textures.length) {
|
||||
this.currentTextureIndex = index;
|
||||
const texture = textures[index];
|
||||
return this.setTextureById(texture.id);
|
||||
}
|
||||
return Promise.reject(new Error("无效的纹理索引"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前选中的材质信息
|
||||
* @returns {Object|null} 材质信息
|
||||
*/
|
||||
getCurrentTexture() {
|
||||
if (this.selectedTextureId) {
|
||||
return texturePresetManager.getTextureById(this.selectedTextureId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 获取所有可用材质
|
||||
const allTextures = texturePresetManager.getAllTextures();
|
||||
const textureOptions = allTextures.map((texture, index) => ({
|
||||
value: texture.id,
|
||||
label: texture.name,
|
||||
preview: texturePresetManager.getTexturePreviewUrl(texture),
|
||||
category: texture.category,
|
||||
}));
|
||||
|
||||
// 定义纹理笔刷特有属性
|
||||
const textureProperties = [
|
||||
{
|
||||
id: "textureSelector",
|
||||
name: "材质选择",
|
||||
type: "texture-grid",
|
||||
defaultValue: this.selectedTextureId,
|
||||
options: textureOptions,
|
||||
description: "选择要使用的纹理",
|
||||
category: "纹理设置",
|
||||
order: 100,
|
||||
hidden: allTextures.length === 0,
|
||||
},
|
||||
{
|
||||
id: "textureRepeat",
|
||||
name: "纹理重复模式",
|
||||
type: "select",
|
||||
defaultValue: this.textureRepeat,
|
||||
options: [
|
||||
{ value: "repeat", label: "双向重复" },
|
||||
{ value: "repeat-x", label: "水平重复" },
|
||||
{ value: "repeat-y", label: "垂直重复" },
|
||||
{ value: "no-repeat", label: "不重复" },
|
||||
],
|
||||
description: "设置纹理的重复模式",
|
||||
category: "纹理设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "textureScale",
|
||||
name: "纹理缩放",
|
||||
type: "slider",
|
||||
defaultValue: this.textureScale,
|
||||
min: 0.1,
|
||||
max: 5,
|
||||
step: 0.1,
|
||||
description: "调整纹理的缩放比例",
|
||||
category: "纹理设置",
|
||||
order: 120,
|
||||
},
|
||||
{
|
||||
id: "textureAngle",
|
||||
name: "纹理旋转",
|
||||
type: "slider",
|
||||
defaultValue: this.textureAngle,
|
||||
min: 0,
|
||||
max: 360,
|
||||
step: 5,
|
||||
description: "调整纹理的旋转角度",
|
||||
category: "纹理设置",
|
||||
order: 130,
|
||||
},
|
||||
{
|
||||
id: "textureOpacity",
|
||||
name: "纹理透明度",
|
||||
type: "slider",
|
||||
defaultValue: this.textureOpacity,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "调整纹理的透明度",
|
||||
category: "纹理设置",
|
||||
order: 140,
|
||||
},
|
||||
{
|
||||
id: "uploadTexture",
|
||||
name: "上传纹理",
|
||||
type: "button",
|
||||
action: "uploadTexture",
|
||||
description: "上传自定义纹理",
|
||||
category: "纹理设置",
|
||||
order: 150,
|
||||
},
|
||||
{
|
||||
id: "texturePreview",
|
||||
name: "纹理预览",
|
||||
type: "preview",
|
||||
description: "当前纹理预览",
|
||||
category: "纹理设置",
|
||||
order: 160,
|
||||
getValue: () => {
|
||||
const currentTexture = this.getCurrentTexture();
|
||||
return currentTexture
|
||||
? texturePresetManager.getTexturePreviewUrl(currentTexture)
|
||||
: null;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...textureProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理纹理笔刷特有属性
|
||||
if (propId === "textureSelector") {
|
||||
this.setTextureById(value);
|
||||
return true;
|
||||
} else if (propId === "textureRepeat") {
|
||||
this.setTextureRepeat(value);
|
||||
return true;
|
||||
} else if (propId === "textureScale") {
|
||||
this.setTextureScale(value);
|
||||
return true;
|
||||
} else if (propId === "textureAngle") {
|
||||
this.setTextureAngle(value);
|
||||
return true;
|
||||
} else if (propId === "textureOpacity") {
|
||||
this.setTextureOpacity(value);
|
||||
return true;
|
||||
} else if (propId === "uploadTexture") {
|
||||
// 触发上传纹理事件
|
||||
// 这里通常由外部处理,返回true表示属性被处理
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义材质
|
||||
* @param {Object} textureData 材质数据
|
||||
* @returns {String} 材质ID
|
||||
*/
|
||||
addCustomTexture(textureData) {
|
||||
const textureId = texturePresetManager.addCustomTexture(textureData);
|
||||
|
||||
// 重新加载材质预设
|
||||
this._loadTexturePresets();
|
||||
|
||||
// 保存到本地存储
|
||||
texturePresetManager.saveCustomTexturesToStorage();
|
||||
|
||||
return textureId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除自定义材质
|
||||
* @param {String} textureId 材质ID
|
||||
* @returns {Boolean} 是否删除成功
|
||||
*/
|
||||
removeCustomTexture(textureId) {
|
||||
const success = texturePresetManager.removeCustomTexture(textureId);
|
||||
|
||||
if (success) {
|
||||
// 如果删除的是当前选中的材质,切换到第一个可用材质
|
||||
if (this.selectedTextureId === textureId) {
|
||||
const allTextures = texturePresetManager.getAllTextures();
|
||||
if (allTextures.length > 0) {
|
||||
this.setTextureById(allTextures[0].id);
|
||||
} else {
|
||||
this.selectedTextureId = null;
|
||||
this.currentTextureIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 重新加载材质预设
|
||||
this._loadTexturePresets();
|
||||
|
||||
// 保存到本地存储
|
||||
texturePresetManager.saveCustomTexturesToStorage();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
const currentTexture = this.getCurrentTexture();
|
||||
if (currentTexture) {
|
||||
return texturePresetManager.getTexturePreviewUrl(currentTexture);
|
||||
}
|
||||
|
||||
// 返回默认纹理预览
|
||||
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48ZGVmcz48cGF0dGVybiBpZD0icGF0dGVybiIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgd2lkdGg9IjEwIiBoZWlnaHQ9IjEwIj48cmVjdCB3aWR0aD0iNSIgaGVpZ2h0PSI1IiBmaWxsPSIjZGRkIi8+PHJlY3QgeD0iNSIgeT0iNSIgd2lkdGg9IjUiIGhlaWdodD0iNSIgZmlsbD0iI2RkZCIvPjwvcGF0dGVybj48L2RlZnM+PHJlY3Qgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIGZpbGw9InVybCgjcGF0dGVybikiLz48L3N2Zz4=";
|
||||
}
|
||||
|
||||
/**
|
||||
* 笔刷被选中时调用
|
||||
* @override
|
||||
*/
|
||||
onSelected() {
|
||||
// 重新加载材质预设(可能有新的自定义材质)
|
||||
this._loadTexturePresets();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁笔刷实例并清理资源
|
||||
* @override
|
||||
*/
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.textureSource = null;
|
||||
this.selectedTextureId = null;
|
||||
this.texturePresets = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置材质属性
|
||||
* @param {String} property 属性名称
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否设置成功
|
||||
*/
|
||||
setTextureProperty(property, value) {
|
||||
switch (property) {
|
||||
case "scale":
|
||||
return this.setTextureScale(value);
|
||||
case "opacity":
|
||||
return this.setTextureOpacity(value);
|
||||
case "repeat":
|
||||
return this.setTextureRepeat(value);
|
||||
case "angle":
|
||||
return this.setTextureAngle(value);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取材质属性
|
||||
* @param {String} property 属性名称
|
||||
* @returns {any} 属性值
|
||||
*/
|
||||
getTextureProperty(property) {
|
||||
switch (property) {
|
||||
case "scale":
|
||||
return this.textureScale;
|
||||
case "opacity":
|
||||
return this.textureOpacity;
|
||||
case "repeat":
|
||||
return this.textureRepeat;
|
||||
case "angle":
|
||||
return this.textureAngle;
|
||||
case "textureId":
|
||||
return this.selectedTextureId;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用材质预设
|
||||
* @param {String|Object} preset 预设ID或预设对象
|
||||
* @returns {Boolean} 是否应用成功
|
||||
*/
|
||||
applyTexturePreset(preset) {
|
||||
let presetData = null;
|
||||
|
||||
if (typeof preset === "string") {
|
||||
// 如果是预设ID,从预设管理器获取
|
||||
presetData = texturePresetManager.applyTexturePreset(preset);
|
||||
} else if (typeof preset === "object") {
|
||||
// 如果是预设对象,直接使用
|
||||
presetData = preset;
|
||||
}
|
||||
|
||||
if (!presetData) {
|
||||
console.warn("无效的材质预设:", preset);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 应用预设设置
|
||||
if (presetData.textureId) {
|
||||
this.setTextureById(presetData.textureId);
|
||||
}
|
||||
|
||||
if (presetData.scale !== undefined) {
|
||||
this.setTextureScale(presetData.scale);
|
||||
}
|
||||
|
||||
if (presetData.opacity !== undefined) {
|
||||
this.setTextureOpacity(presetData.opacity);
|
||||
}
|
||||
|
||||
if (presetData.repeat !== undefined) {
|
||||
this.setTextureRepeat(presetData.repeat);
|
||||
}
|
||||
|
||||
if (presetData.angle !== undefined) {
|
||||
this.setTextureAngle(presetData.angle);
|
||||
}
|
||||
|
||||
// 如果预设包含笔刷属性,也一并应用
|
||||
if (presetData.brushSize !== undefined && this.brush) {
|
||||
this.brush.width = presetData.brushSize;
|
||||
}
|
||||
|
||||
if (presetData.brushOpacity !== undefined && this.brush) {
|
||||
this.brush.opacity = presetData.brushOpacity;
|
||||
}
|
||||
|
||||
if (presetData.brushColor !== undefined && this.brush) {
|
||||
this.brush.color = presetData.brushColor;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前材质状态
|
||||
* @returns {Object} 当前材质状态
|
||||
*/
|
||||
getCurrentTextureState() {
|
||||
return {
|
||||
textureId: this.selectedTextureId,
|
||||
scale: this.textureScale,
|
||||
opacity: this.textureOpacity,
|
||||
repeat: this.textureRepeat,
|
||||
angle: this.textureAngle,
|
||||
// 包含笔刷状态
|
||||
brushSize: this.brush ? this.brush.width : this.options.width,
|
||||
brushOpacity: this.brush ? this.brush.opacity : this.options.opacity,
|
||||
brushColor: this.brush ? this.brush.color : this.options.color,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复材质状态
|
||||
* @param {Object} state 要恢复的状态
|
||||
* @returns {Boolean} 是否恢复成功
|
||||
*/
|
||||
restoreTextureState(state) {
|
||||
if (!state) return false;
|
||||
|
||||
try {
|
||||
// 恢复材质属性
|
||||
if (state.textureId) {
|
||||
this.setTextureById(state.textureId);
|
||||
}
|
||||
|
||||
if (state.scale !== undefined) {
|
||||
this.setTextureScale(state.scale);
|
||||
}
|
||||
|
||||
if (state.opacity !== undefined) {
|
||||
this.setTextureOpacity(state.opacity);
|
||||
}
|
||||
|
||||
if (state.repeat !== undefined) {
|
||||
this.setTextureRepeat(state.repeat);
|
||||
}
|
||||
|
||||
if (state.angle !== undefined) {
|
||||
this.setTextureAngle(state.angle);
|
||||
}
|
||||
|
||||
// 恢复笔刷属性
|
||||
if (this.brush) {
|
||||
if (state.brushSize !== undefined) {
|
||||
this.brush.width = state.brushSize;
|
||||
}
|
||||
|
||||
if (state.brushOpacity !== undefined) {
|
||||
this.brush.opacity = state.brushOpacity;
|
||||
}
|
||||
|
||||
if (state.brushColor !== undefined) {
|
||||
this.brush.color = state.brushColor;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("恢复材质状态失败:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建材质预设
|
||||
* @param {String} name 预设名称
|
||||
* @returns {String} 预设ID
|
||||
*/
|
||||
createTexturePreset(name) {
|
||||
const currentState = this.getCurrentTextureState();
|
||||
return texturePresetManager.createTexturePreset(name, currentState);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的材质分类
|
||||
* @returns {Array} 分类数组
|
||||
*/
|
||||
getTextureCategories() {
|
||||
return texturePresetManager.getCategories();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分类获取材质
|
||||
* @param {String} category 分类名称
|
||||
* @returns {Array} 材质数组
|
||||
*/
|
||||
getTexturesByCategory(category) {
|
||||
return texturePresetManager.getTexturesByCategory(category);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索材质
|
||||
* @param {String} query 搜索关键词
|
||||
* @returns {Array} 匹配的材质数组
|
||||
*/
|
||||
searchTextures(query) {
|
||||
return texturePresetManager.searchTextures(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预加载材质图像
|
||||
* @param {String} textureId 材质ID
|
||||
* @returns {Promise<HTMLImageElement>} 图像对象
|
||||
*/
|
||||
preloadTexture(textureId) {
|
||||
return texturePresetManager.loadTextureImage(textureId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量预加载材质
|
||||
* @param {Array} textureIds 材质ID数组
|
||||
* @returns {Promise<Array>} 加载结果数组
|
||||
*/
|
||||
preloadTextures(textureIds) {
|
||||
const loadPromises = textureIds.map((id) =>
|
||||
this.preloadTexture(id).catch((error) => ({ id, error }))
|
||||
);
|
||||
return Promise.all(loadPromises);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取材质统计信息
|
||||
* @returns {Object} 统计信息
|
||||
*/
|
||||
getTextureStats() {
|
||||
return texturePresetManager.getStats();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
import { BaseBrush } from "../BaseBrush";
|
||||
|
||||
/**
|
||||
* 书法笔刷
|
||||
* 模拟中国传统书法效果,具有笔锋和墨色变化
|
||||
*/
|
||||
export class WritingBrush extends BaseBrush {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric画布实例
|
||||
* @param {Object} options 配置选项
|
||||
*/
|
||||
constructor(canvas, options = {}) {
|
||||
super(canvas, {
|
||||
id: "writing",
|
||||
name: "书法笔",
|
||||
description: "模拟中国传统书法毛笔效果,具有笔锋和墨色变化",
|
||||
category: "特效笔刷",
|
||||
icon: "writing",
|
||||
...options,
|
||||
});
|
||||
|
||||
// 书法笔刷特有属性
|
||||
this.brushPressure = options.brushPressure || 0.7;
|
||||
this.inkAmount = options.inkAmount || 20;
|
||||
this.brushTaperFactor = options.brushTaperFactor || 0.6;
|
||||
this.enableInkDripping =
|
||||
options.enableInkDripping !== undefined
|
||||
? options.enableInkDripping
|
||||
: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建笔刷实例
|
||||
* @returns {Object} fabric笔刷实例
|
||||
*/
|
||||
create() {
|
||||
if (!this.canvas) {
|
||||
throw new Error("画布实例不存在");
|
||||
}
|
||||
|
||||
// 创建fabric原生书法笔刷
|
||||
this.brush = new fabric.WritingBrush(this.canvas);
|
||||
|
||||
// 配置笔刷
|
||||
this.configure(this.brush, this.options);
|
||||
|
||||
return this.brush;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置笔刷
|
||||
* @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) {
|
||||
brush.color = options.color;
|
||||
}
|
||||
|
||||
if (options.opacity !== undefined) {
|
||||
brush.opacity = options.opacity;
|
||||
}
|
||||
|
||||
// 书法笔刷特有属性
|
||||
if (options.brushPressure !== undefined) {
|
||||
this.brushPressure = options.brushPressure;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.brushPressure !== undefined) {
|
||||
brush.brushPressure = this.brushPressure;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.inkAmount !== undefined) {
|
||||
this.inkAmount = options.inkAmount;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.inkAmount !== undefined) {
|
||||
brush.inkAmount = this.inkAmount;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.brushTaperFactor !== undefined) {
|
||||
this.brushTaperFactor = options.brushTaperFactor;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.brushTaperFactor !== undefined) {
|
||||
brush.brushTaperFactor = this.brushTaperFactor;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.enableInkDripping !== undefined) {
|
||||
this.enableInkDripping = options.enableInkDripping;
|
||||
|
||||
// 如果原生笔刷支持此属性,则设置
|
||||
if (brush.enableInkDripping !== undefined) {
|
||||
brush.enableInkDripping = this.enableInkDripping;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置笔压感应
|
||||
* @param {Number} pressure 笔压值(0-1)
|
||||
*/
|
||||
setBrushPressure(pressure) {
|
||||
this.brushPressure = Math.max(0.1, Math.min(1, pressure));
|
||||
|
||||
if (this.brush && this.brush.brushPressure !== undefined) {
|
||||
this.brush.brushPressure = this.brushPressure;
|
||||
}
|
||||
|
||||
return this.brushPressure;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置墨量
|
||||
* @param {Number} amount 墨量值
|
||||
*/
|
||||
setInkAmount(amount) {
|
||||
this.inkAmount = Math.max(1, Math.min(50, amount));
|
||||
|
||||
if (this.brush && this.brush.inkAmount !== undefined) {
|
||||
this.brush.inkAmount = this.inkAmount;
|
||||
}
|
||||
|
||||
return this.inkAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置笔锋系数
|
||||
* @param {Number} factor 笔锋系数(0-1)
|
||||
*/
|
||||
setBrushTaperFactor(factor) {
|
||||
this.brushTaperFactor = Math.max(0, Math.min(1, factor));
|
||||
|
||||
if (this.brush && this.brush.brushTaperFactor !== undefined) {
|
||||
this.brush.brushTaperFactor = this.brushTaperFactor;
|
||||
}
|
||||
|
||||
return this.brushTaperFactor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用墨滴效果
|
||||
* @param {Boolean} enabled 是否启用
|
||||
*/
|
||||
setInkDripping(enabled) {
|
||||
this.enableInkDripping = enabled;
|
||||
|
||||
if (this.brush && this.brush.enableInkDripping !== undefined) {
|
||||
this.brush.enableInkDripping = this.enableInkDripping;
|
||||
}
|
||||
|
||||
return this.enableInkDripping;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔刷可配置属性
|
||||
* @returns {Array} 可配置属性描述数组
|
||||
* @override
|
||||
*/
|
||||
getConfigurableProperties() {
|
||||
// 获取基础属性
|
||||
const baseProperties = super.getConfigurableProperties();
|
||||
|
||||
// 定义书法笔刷特有属性
|
||||
const writingProperties = [
|
||||
{
|
||||
id: "brushPressure",
|
||||
name: "笔压感应",
|
||||
type: "slider",
|
||||
defaultValue: this.brushPressure,
|
||||
min: 0.1,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制笔触的力度感应",
|
||||
category: "书法设置",
|
||||
order: 100,
|
||||
},
|
||||
{
|
||||
id: "inkAmount",
|
||||
name: "墨量",
|
||||
type: "slider",
|
||||
defaultValue: this.inkAmount,
|
||||
min: 1,
|
||||
max: 50,
|
||||
step: 1,
|
||||
description: "控制笔触中的墨水量",
|
||||
category: "书法设置",
|
||||
order: 110,
|
||||
},
|
||||
{
|
||||
id: "brushTaperFactor",
|
||||
name: "笔锋系数",
|
||||
type: "slider",
|
||||
defaultValue: this.brushTaperFactor,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
description: "控制笔锋的尖锐程度",
|
||||
category: "书法设置",
|
||||
order: 120,
|
||||
},
|
||||
{
|
||||
id: "enableInkDripping",
|
||||
name: "墨滴效果",
|
||||
type: "checkbox",
|
||||
defaultValue: this.enableInkDripping,
|
||||
description: "是否启用墨滴效果",
|
||||
category: "书法设置",
|
||||
order: 130,
|
||||
},
|
||||
];
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...baseProperties, ...writingProperties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新笔刷属性
|
||||
* @param {String} propId 属性ID
|
||||
* @param {any} value 属性值
|
||||
* @returns {Boolean} 是否更新成功
|
||||
* @override
|
||||
*/
|
||||
updateProperty(propId, value) {
|
||||
// 先检查基类能否处理此属性
|
||||
if (super.updateProperty(propId, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理书法笔刷特有属性
|
||||
if (propId === "brushPressure") {
|
||||
this.setBrushPressure(value);
|
||||
return true;
|
||||
} else if (propId === "inkAmount") {
|
||||
this.setInkAmount(value);
|
||||
return true;
|
||||
} else if (propId === "brushTaperFactor") {
|
||||
this.setBrushTaperFactor(value);
|
||||
return true;
|
||||
} else if (propId === "enableInkDripping") {
|
||||
this.setInkDripping(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览图
|
||||
* @returns {String} 预览图URL
|
||||
*/
|
||||
getPreview() {
|
||||
return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBkPSJNMzAgMzBDNTAgMzAgNjAgNzAgODAgNzAiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTc1IDYwQzc4IDcwIDg1IDY1IDkwIDcwIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBmaWxsPSJub25lIi8+PC9zdmc+";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user